频道栏目
首页 > 程序开发 > 移动开发 > IOS > 正文
【iOS/Mac OS】程序崩在objc_msgSend(),怎么办?
2012-09-25 10:17:47           
收藏   我要投稿

程序崩在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 pisble 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
 
 

点击复制链接 与好友分享!回本站首页
相关TAG标签 怎么办 程序
上一篇: IOS-MVC范型和处理基本交互
下一篇:iOS中读取照片库
相关文章
图文推荐
文章
推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站