【iOS/Mac OS】程序崩在objc_msgSend(),怎么办?

来源:互联网 发布:淘宝刷钻价格表 编辑:程序博客网 时间:2024/06/15 22:48

程序崩在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_fpretobjc_msgSend_stret receiverSELreceiverSELi386eax*ecxeax*ecxx86_64rdirsirsirdxppcr3r4r4r5ppc64r3r4r4r5armr0r1r1r2

* 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

当一个块被释放后,内存分配器会写入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().

 

原创粉丝点击