惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
The GitHub Blog
The GitHub Blog
C
Check Point Blog
博客园_首页
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
F
Full Disclosure
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
G
GRAHAM CLULEY
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
K
Kaspersky official blog
博客园 - 司徒正美
S
Schneier on Security
T
The Exploit Database - CXSecurity.com
Project Zero
Project Zero
云风的 BLOG
云风的 BLOG
Cisco Talos Blog
Cisco Talos Blog
Know Your Adversary
Know Your Adversary
雷峰网
雷峰网
V
V2EX - 技术
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Spread Privacy
Spread Privacy
罗磊的独立博客
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security Affairs
SecWiki News
SecWiki News
Schneier on Security
Schneier on Security
O
OpenAI News
Jina AI
Jina AI
PCI Perspectives
PCI Perspectives
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog RSS Feed
I
InfoQ
D
Docker
P
Palo Alto Networks Blog
Recorded Future
Recorded Future
M
MIT News - Artificial intelligence
博客园 - Franky
B
Blog
Scott Helme
Scott Helme
博客园 - 叶小钗
D
DataBreaches.Net

博客园 - 啊夏

iOS6.0以后App对内存警告的处理 NSURLConnection 网络超时的那些事。 我看QQ与360的恩怨情仇 [转载]NSString+NSMutableString+NSValue+NSAraay用法汇总 详解百度手机输入法“搜索框”的秘密 一道求单向链表倒数第N个结点的算法题。 真正认识 realloc 的工作方式。 什么是最优秀的IT员工? 【转帖】有关次级贷的一个故事。很贴切 获取字符串的拼音首字母 - 啊夏 [转载]关于const用法的 笔记记录 最大公约数-辗转相除法 向量的旋转算法---编程珠玑读书笔记。 - 啊夏 activex 控件回调 javascript 一个“利”与“义”的故事。 重启PPC系统 GLDEF_C, LOCAL_C, GLREF_C 的含义 看看80的我们小时候都在玩些什么。 Carbide.c++ IDE的常用快捷键和技巧
ios平台上一个由字节对齐问题导致的crash
啊夏 · 2013-01-11 · via 博客园 - 啊夏

最近,我们负责开发的一个产品,一启动就会Crash,但是我们自己在开发机上编译出来的版本确又是正常的。DB不能工作了,很影响我们日常体验开发中的版本,于是组织就派我来解决这个问题了。

    第一个猜测,因为最近公司RDM的证书快到期了,于是就怀疑是证书的问题,找了平台那边的同学帮忙查看,确认证书是没有问题。

不过平台那边的编译环境跟我们的开发环境有一点点版本的差异,于是有折腾平台那边的同学帮忙升级环境。结果发现也不是环境的问题。

    很自然的就想到了 debug  和 release 版本的问题了,DB版本的都是release,而我们自己开发编译到手机上的都是debug版本。把项目设置修改一下,编译到真机,crash重现。【能重现的bug跑不掉。:)】

    找到了问题我就贴下相关的代码。这里有个相当诡异的bug。

 1 Byte *bytes = (Byte*)[ipData bytes];  
 2  //读取总的ip列表组数  
 3  Byte cIPGroupCount = bytes[0];  
 4    
 5  if (cIPGroupCount == 0)  
 6  {  
 7      return YES;  
 8  }  
 9    
10  int idx = sizeof(Byte);  
11  for (Byte groupIdx = 0; groupIdx < cIPGroupCount; ++groupIdx)  
12  {  
13      //先读取一个short位的下发列表类型  
14      unsigned short type = NTOHS(*(unsigned short*)(bytes+idx));  
15      idx += sizeof(unsigned short);  
16        
17      //读取当前ip列表组总列表的ip数  
18      Byte ipItemCount = (Byte)*(bytes + idx);  
19      idx += sizeof(Byte);  
20        
21      NSMutableArray *ips = [[NSMutableArray alloc] initWithCapacity:ipItemCount];  
22      NSMutableArray *ports = [[NSMutableArray alloc] initWithCapacity:ipItemCount];  
23        
24      for (Byte itemIdx = 0; itemIdx < ipItemCount; itemIdx ++)  
25      {  
26          //读取ip地址信息(IP 地址字段不需要转换字节序)  
27          unsigned int ip = *(unsigned int*)(bytes + idx);  
28          idx += sizeof(unsigned int);  
29          [ips addObject:[NSNumber numberWithInt:ip]];  
30          //读取端口信息  
31          unsigned int port = NTOHL(*(unsigned int*)(bytes + idx));  
32          idx += sizeof(unsigned int);  
33          [ports addObject:[NSNumber numberWithInt:port]];  
34      }  
35   }

   上面的代码其实很简单,就是解析一个2进制格式的数据。这段代码运行也没有问题,但是调整了下顺序后,就导致了 release环境下的crash。 还是先贴代码【只贴变化部分的代码】。

 1   
 2   for (Byte itemIdx = 0; itemIdx < ipItemCount; itemIdx ++)  
 3    {  
 4       unsigned int ip = *(unsigned int*)(bytes + idx);  //这行代码crash  
 5       idx += sizeof(unsigned int);  
 6       //读取端口信息  
 7       unsigned int port = NTOHL(*(unsigned int*)(bytes + idx));  
 8       idx += sizeof(unsigned int);  
 9          
10        [ips addObject:[NSNumber numberWithInt:ip]];  
11        [ports addObject:[NSNumber numberWithInt:port]];  
12  }

把变化的部分加粗显示了,相比上面的代码,只是简单的调换了下代码执行的顺序,没有任何逻辑的修改。有注释的那行代码会crash,xcode给出的错误是字节对齐错误。很郁闷。后来写代码验证了一下,请看下面的分析过程。

   整个数据解析部分分两个循环,在循环最外面还有一个字节的读取。于是数据的解析流程如下:

1.【1字节】【读取一个字节的列表总数】   

---

   2.外循环开始

        【2字节】【读取两字节类型信息】

        【1字节】【读取一字节的ip总数】

--------

         3.内循环开始

             【4字节】【4字节端口号】

     当执行上述流程 1 + 2×1(外循环执行一次) + 3×1(内循环执行一次) 的时候,整个偏移量是 (1+2+1+4+4),是4的倍数。这里不管 3(内循环)执行多少次,整个偏移量都是4的倍数。

但是只要 2(外循环)执行次数超过一次,上述流程执行到标记了 “*” 的那一行的时候,偏移量就再也不是4的倍数了。这个时候unsigned int ip = *(unsigned int*)(bytes + idx); 这行代码在relase环境下就会crash。

   过程分析完了,我还有一个疑问没有解开,为什么第一段代码在同样的数据下,确没有Crash。我只能猜测是因为一行c的代码间隔执行了一行oc的代码。第2行代码也许是编译器优化导致的。如果对这个问题有研究的同学欢迎交流。

   最后给出我现在的解决方案,对于解析这种紧凑格式的2进制数据,在做数据类型转换的时候,最好使用下面的代码来处理,这样就可以避免字节对齐的问题了。

1  //读取ip地址信息(IP 地址字段不需要转换字节序)  
2 unsigned int ip = 0;  
3 memcpy(&ip, bytes + idx, sizeof(unsigned int));  
4 idx += sizeof(unsigned int);  

很奇怪的一个问题,在进行强制数据类型转换的时候,ios平台竟然要求内存字节对齐。而debug环境又不要求。如果两次强制类型转换用oc的代码隔开,release执行又是正确的,所以再次怀疑是xocde在编译的时候,编译器优化导致的。

--------------- 后面的讨论----------

   感谢 @springhu 指出错误,需要用memcpy,而不是memccpy【原来我一直理解错了memccpy的用法】。

   跟springhu讨论了半天,我们分别单独写了demo工程来模拟上面的case,结果发现在release环境下也并不会crash。问题只出现在我的工程里面。经过一些列的测试,发现这个诡异的问题只出在我的情景代码里面,把解析部分单独封装个函数后,在应用里面调用也是不会出问题的。

     unsigned int ip = *(unsigned int*)(bytes + idx);  这种写法理论上是没有任何问题的,在应用里面使用的时候也不需要考虑字节对齐的问题,但是不怕一万,就怕万一啊。就怕编译器好心干坏事。

 

EXC_ARM_DA_ALIGN 用这个关键字可以google到很多相关的文章