ios平台上一个由字节对齐问题导致的crash
2013-01-11 12:26
1666 查看
最近,我们负责开发的一个产品,一启动就会Crash,但是我们自己在开发机上编译出来的版本确又是正常的。DB不能工作了,很影响我们日常体验开发中的版本,于是组织就派我来解决这个问题了。
第一个猜测,因为最近公司RDM的证书快到期了,于是就怀疑是证书的问题,找了平台那边的同学帮忙查看,确认证书是没有问题。
不过平台那边的编译环境跟我们的开发环境有一点点版本的差异,于是有折腾平台那边的同学帮忙升级环境。结果发现也不是环境的问题。
很自然的就想到了 debug 和 release 版本的问题了,DB版本的都是release,而我们自己开发编译到手机上的都是debug版本。把项目设置修改一下,编译到真机,crash重现。【能重现的bug跑不掉。:)】
找到了问题我就贴下相关的代码。这里有个相当诡异的bug。
上面的代码其实很简单,就是解析一个2进制格式的数据。这段代码运行也没有问题,但是调整了下顺序后,就导致了 release环境下的crash。 还是先贴代码【只贴变化部分的代码】。
把变化的部分加粗显示了,相比上面的代码,只是简单的调换了下代码执行的顺序,没有任何逻辑的修改。有注释的那行代码会crash,xcode给出的错误是字节对齐错误。很郁闷。后来写代码验证了一下,请看下面的分析过程。
整个数据解析部分分两个循环,在循环最外面还有一个字节的读取。于是数据的解析流程如下:
1.【1字节】【读取一个字节的列表总数】
---
2.外循环开始
【2字节】【读取两字节类型信息】
【1字节】【读取一字节的ip总数】
--------
3.内循环开始
*【4字节】【4字节ip地址】
【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进制数据,在做数据类型转换的时候,最好使用下面的代码来处理,这样就可以避免字节对齐的问题了。
很奇怪的一个问题,在进行强制数据类型转换的时候,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到很多相关的文章
第一个猜测,因为最近公司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字节ip地址】
【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到很多相关的文章
相关文章推荐
- IOS开发笔记之二十七——一个@dynamic导致的crash问题
- ARM平台 结构体字节对齐引起的一个问题
- 解决Oracle中字符集导致一个汉字占用3个字节的问题
- 字节对齐问题产生的Crash
- ARM平台的字节对齐问题
- ios下一个4字节对齐引起崩溃的问题
- C语言中结构体存储的一个字节对齐问题
- iOS在同一个Https目录下放不同ID的包导致的下载问题
- 一个问题解决:adb push build.prop后导致手机crash
- 从一个EXC_ARM_DA_ALIGN的crash来看ios3.2以后内存对齐问题
- vs05字节对齐问题又一不小心就弄去了我一个下午的时间
- iOS开发遇到memory持续上涨导致页面crash问题解决思路
- 字节对齐问题 --- 莫名其妙的crash
- iOS 7.0 使用定位requestAlwaysAuthorization导致crash的问题
- 由字节对齐产生的一个应用崩溃的问题
- ARM平台的字节对齐问题
- 字节对齐问题----一个很古老的基础问题
- C语言中一个字节对齐问题的分析
- Asp.Net Forms认证在移动平台中遇到的一个问题以及调查过程
- UITextView内容不能紧贴边缘,内容不能靠边,导致不能对齐label--iOS 开发