crash堆栈
1 | Thread 16 name: xxx |
源码
1 | - (NSInteger)getMainModuleFrameCount:(NSArray *)frames { |
定位crash变量
符号化显示第127行,难道就是这个_mainModuleInfo变量造成的?
实际上符号化指向的行号是不准确的,一方面oc语言会插入一些retain,release函数导致映射关系不准确,另一方面堆栈显示的是返回地址,并不是进入子函数的位置,因此需要从其他角度进行现场的还原。
直接调试ipa包
原理是通过对ipa包进行重签名就可以结合xcode进行调试了,可以使用IPAPatch或者MonkeyDev这类工具进行。
计算crash发生的地址,设置断点
1 | 1 _demo_ 0x00000001008e2c38 -[BSAPMMachoManager getMainModuleFrameCount:] (in _demo_) (BSAPMMachoManager.m:127) |
- 计算偏移量
0x00000001008e2c38 - 0x10005c000 = 0x0000000000886c38
- 在xcode调试控制台 获取ipa包的 载入地址
image list demo
1 | (lldb) image list _demo_ |
- 计算实际地址,设置断点
0x00000001000e0000 + 0x0000000000886c38 = 0x0000000100966c38
1 | breakpoint set -a 0x0000000100966c38 |
分析寄存器与变量之间的对应关系
进入断点后,进一步的分析。
1 | _demo_`___lldb_unnamed_symbol38887$$_demo_: |
- 沿着函数参数进行数据流向分析
0x100966c38是函数返回地址,并不是函数进入的地址,实际上,是在0x100966c34当中执行了objc_release的方法,而objc_release接受了一个野指针所以也就造成了crash。
因此要分析0x100966c34传入的参数,第1个参数对应寄存器x0,第2个参数对应x1,以此类推。此外,x0同时也作为函数的返回值。
0x100966c30执行了objc_retainAutoreleasedReturnValue,也就是x0是来自于这个临时变量的。
大致的伪代码如下:
1 | - (NSInteger)getMainModuleFrameCount:(NSArray *)frames { |
经过这里的分析,实际上导致crash的变量并不是_mainModuleInfo,而是[self getCurrentModuleInfo]返回的临时变量。
问题分析
定位到crash变量之后进行进一步的分析,为什么这个返回的临时变量会发生crash,首先确认一下是否是多线程操作造成了这种情况。
1 | - (NSDictionary *)getCurrentModuleInfo { |
看起来此处逻辑简单,有一个只执行一次的block,然后就是返回一个实例变量。
继续查看代码确认是有另外一个线程对_moduleInfo进行赋值
1 | - (void)updateModuleInfo { |
在多线程直接对实例变量进行读写是会造成crash的,需要进行保护,oc语法上提供了这种属性读写的语法糖,@property (atomic)
编译器会自动生成属性读写多线程安全的代码,通过@property 以及 self.xxx的方式就能避免上面的crash,当然这种自动生成的多线程安全代码也是比较简陋的,具体生成怎样的代码这里就不展开诉述了。