程式崩在objc_msgSend(),怎麼辦?
最可能的原因是,當你向一個已經釋放的對象發送訊息時,或者雖然指標是正確的,卻被別的對象破壞了內容(比如記憶體越界),再或者使用了懸擺指標(dangling pointer)。偶爾的時候也會是因為記憶體錯誤導致運行時的資料結構被破壞,但通常問題還是在接收者本身。
無論用Debugger還是通過崩潰日誌(crash log),都可以得到遠比backtrace(呼叫堆疊)多的資訊。
接收者和selector寄存器(Receiver and selector registers)
objc_msgSend() 會在CPU寄存器中儲存接收者對象和selector,這些值可以用來協助分析問題。
在不同的架構下使用的寄存器會有所不同。下表是針對Mac OS X Leopard (Snow Leopard也應該是相同的)。
|
objc_msgSend
objc_msgSend_fpret |
objc_msgSend_stret |
|
receiver |
SEL |
receiver |
SEL |
i386 |
eax* |
ecx |
eax* |
ecx |
x86_64 |
rdi |
rsi |
rsi |
rdx |
ppc |
r3 |
r4 |
r4 |
r5 |
ppc64 |
r3 |
r4 |
r4 |
r5 |
arm |
r0 |
r1 |
r1 |
r2 |
* i386中的注釋: 接收者對象在大多數崩潰中都是存在eax中的。如果調用的路徑過長,eax有可能儲存的是其它值。
解讀接收者和非法地址(Interpreting the receiver and invalid address)
你可以通過使用接收者地址和非法地址來製造崩潰的情況,以此學習一些技巧。在崩潰日誌中,接收者地址使用上表所列的寄存器儲存在Thread State中,而非法地址則列在日誌頂部位置(通常顯示如
KERN_PROTECTION_FAILURE at <invalid address>)。在Debugger控制台中, 非法地址會在程式停止時輸出,此時也可以使用上表所列的寄存器來輸出接收者的地址。
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000001
0x00090ec4 in objc_msgSend ()
(gdb) p/x $eax
$1 = 0x1
這個測試程式崩在[(id)1 release]。
通常是有兩種情況中的一項造成的:接收者的地址是錯誤的(此時非法地址也是一樣的,或者位移16或32位元組),或者接收者的地址是合理的,而非法地址是接收者的isa指標。後者通常是因為你正嘗試使用一個已經釋放的對象。
既要針對性的尋找這些值,也要尋找它們附近的值。在一些架構中,一個非法的isa會導致程式崩在isa+16或isa+32位置。
未對齊,不能被16整除(Not divisble by 16 - misaligned)
malloc() 返回的是16位元組對齊的記憶體塊。如果你的接收者地址沒有按16位元組對齊,那麼它很可能不是一個正確的對象指標。
前兩位和後兩位都被置位 - malloc的free list
當一個塊被釋放後,記憶體 Clerk會寫入free list指標。如果你隨後使用被釋放的對象,它的isa指標的前兩位和後兩位都會被置位。
所有位被取反 - GC的free list
和上面的malloc的free list相像,GC的free list會使得地址值看起來是錯誤的,但~address(取反操作)的值卻看起是合理的。
0xa1b1c1d3 - CF container
CoreFoundation containers 使用這個值來表示已經刪除或清空的項目。
ASCII 文本
或許是一個已經釋放的對象被重新分配為一個字串,亦或者將一個已經釋放的字串重新分配為一個對象,也可能是記憶體越界的原因。使用asciify命令將不同位元組對齊模式下的字串輸出出來,以便於查看。下面是一個看似URL相關的字串:
% asciify 0x2e777777
###.www###
###www.###
追查selector(Interrogating the selector)
編譯最佳化會使呼叫堆疊中指向第二段的調用點(call site)可能並不是真正導致崩潰的調用。它可能已經調用成功了,而是這個方法的一個尾部調用(tail call)導致了崩潰。正是因為尾部調用(tail call)的最佳化會導致其中間調用幀會被忽略而沒有顯示在呼叫堆疊中。我們這裡可以使用selector寄存器來確認真正的崩潰調用。
一個selector會指向一個唯一的C字串。未來有可能在新系統改變,不過現在可以很方便的用來調試。如果你的程式崩在debugger中,開啟Debugger控制台,使用上表中列出的selector寄存器執行如下指令:
(gdb) x/s $ecx
0xa1029: "release"
Snow Leopard系統提供的崩潰日誌已經添加了selector的名字:
Application Specific Information:
objc_msgSend() selector name: release
除此之外,要想只從崩潰日誌中得到selector是很困難的。直到Snow Leopard,你可以使用下面的方法:
1. 從崩潰日誌的Thread State,對照前面表中所列的selector寄存器值,如:
ecx: 0x000a1029
2.從崩潰日誌的Binary Images位置, 找到某個鏡像(image)包含了這個地址。它常常要麼是程式本身,要麼就是 libobjc.A.dylib. 如果沒有找到對應的image,就放棄吧。
0x8b000 - 0x106ff7 libobjc.A.dylib ??? (???) <9b5973b7fa88f9aab7885530c7b278dd> /usr/lib/libobjc.A.dylib
3.找到同崩潰日誌中所列的鏡像匹配的程式檔案。可以使用UUID確認它們的一致性。
% dwarfdump -u /usr/lib/libobjc.A.dylib
UUID: 26650299-C6EA-B1C8-52D6-072AC874D400 (ppc) /usr/lib/libobjc.A.dylib
UUID: 9B5973B7-FA88-F9AA-B788-5530C7B278DD
(i386) /usr/lib/libobjc.A.dylib
UUID: D2A4E8E1-3C1C-E0D9-2249-125B6DD621F8 (x86_64) /usr/lib/libobjc.A.dylib
同時確保系統版本的一致性。
4.計算SEL在鏡像中的位移地址。
0xa1029 - 0x8b000 = 0x16029
5.列印鏡像(image)中指定位移地址處的C字串。記住指定正確的架構。
% otool -v -arch i386 -s __TEXT __cstring /usr/lib/libobjc.A.dylib | grep 16029
00016029 release
轉載請註明出處:http://blog.csdn.net/horkychen
原文地址:
So you crashed in objc_msgSend().