您的位置:首页 > 移动开发 > IOS开发

ios平台上一个由字节对齐问题导致的crash

2013-01-11 12:26 1666 查看
最近,我们负责开发的一个产品,一启动就会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字节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到很多相关的文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: