ios平台上一個由字節對齊問題導致的crash


最近,我們負責開發的一個產品,一啟動就會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到很多相關的文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM