手把手教你如何分析 iOS 系统栈 crash
先上栈,这个 crash 是我们目前开发产品的 top5 crash
第一步
对于死在 ojbc _ msgSend 的函数(不仅仅是 msgSend, objc_retain 等一切没有创建栈帧的都需要注意),请先检查 crash 上报的寄存器信息
一般来说,lr 肯定不等于第一个栈。 目前的 crash上报功能,丢失了最顶层的栈。因为 objc_msgSend 并没有创建栈帧。
这样,我们就得根据 lr,来计算真实的最后一个栈了。
栈帧介绍
栈帧,保存当前函数返回地址,以及上级栈帧地址。 这样,通过枚举栈帧,即可得到函数的调用栈。
第二步
在模块列表查找,lr 是那个模块的
找到了,计算绝对偏移,找出对应函数地址。(函数绝对偏移 = lr - 模块基址)同样,反过来就可以在本机或者 IDA 查找函数了)
libAVFAudio.dylib`AVAudioSessionPropertyListener(void, unsigned int, unsigned int, void const) + 1796
好了,终于找到调用 objc_msgSend 的点了。
第三步
运行程序,找到野掉的对象到底是个什么
AVAudioSessionRouteDescription (这里需要根据 selector 再次核实上一步骤的调用点是否正确。crash 时 selector 存放在 x1 寄存器, 有时候上报平台会打印出 x-selector detect, 对比下 selector 是否一致,一致则说明上一步得到的地址没有问题)
第四步
这个对象是哪里来的
需要调试神器 lzmalloc 命令(话说,这个命令实在是太好用了。) lzmalloc 为我们自己开发的调试器辅助命令,用于打印对象分配以及释放点的堆栈信息。 下面为 lzmalloc 结果
第五步
到这里为止,首先排查了自己代码内部对于 AVAudioSessionRouteDescription 确定不存在过度释放的问题,不得已,只有逆向了。(最蛋疼的步骤了)
首先确定野掉的 AVAudioSessionRouteDescription 来源于 libAVFAudio.dylib`AVAudioSessionPropertyListener(void, unsigned int, unsigned int, void const) + 1768
而此行是调用函数 -[AVAudioSession privateConfigureRouteDescription:]
而 privateConfigureRouteDescription 从lzmalloc 结论来看,内部是调用 +[AVAudioSessionRouteDescription privateCreateOrConfigure:withRawDescription:]
第六步
首先逆向 +[AVAudioSessionRouteDescription privateCreateOrConfigure:withRawDescription:]
函数逻辑大概如下
test config
if(!change) return orgDes;
else release(orgDes); alloc newDes。 newDes retain autorelease
从这个逻辑,可以看出来,如果是 new 出来的对象,那是绝对不可能野的。 所以,对象只可能是返回了 orgDes。
第七步
逆向 -[AVAudioSession privateConfigureRouteDescription:]
函数逻辑大概如下
lock
{
get orgDes
newdes = call privateCreateOrConfigure:withRawDescription:
return newdes
}
发现问题了吗? 如果 newdes = orgdes 呢。 而函数返回后,刚好另一个线程执行了 privateCreateOrConfigure:withRawDescription: 而这个时候,config 又恰好变动呢。 orgDes 会被释放!! 哎,这个锁算是白加了。
第八步
问题原因可能猜到了。但是如何修改呢?
hook -[AVAudioSession privateConfigureRouteDescription:] 内部调用原函数之后加上 retain autorelease? 似乎挺理想,但是仔细想想,还是没什么用啊,照样阻止不了其他线程 privateCreateOrConfigure:withRawDescription:的调用。当然这个可以很大降低概率,因为间隔代码很少。
so,换种思路,根据之前动态调试的结果 privateCreateOrConfigure:withRawDescription: 触发时机,有两个,一个是系统耳机插拔通知的时候,另一个就是我们自己调用 audiosession.currentroute 的时候。 而系统通知只在 audio 线程调用。所以呢,既然如此,那我们自己干脆不调用了,在系统通知的时候,在回调里面保存最新的。 当需要访问 audiosession.currentroute 直接返回我们保存的值。 这样,冲突不就没了
第九步
修改外发 很幸运,已经消灭了这个问题。
第十步
总结下
最近发现不少苹果的内存问题。 不知道为什么苹果自己代码很多都不使用 arc,也许这样做很 cool!!
不过,连苹果这么牛这么自信的开发,都弄出了这么多难缠的问题。我们还是不要向他学习,老老实实的用好 ARC 吧。
本文系腾讯Bugly独家内容,转载请在文章开头显眼处注明注明作者和出处“腾讯Bugly(http://bugly.qq.com)”
腾讯Bugly 最专业的质量跟踪平台
精神哥、小萝莉,为您定期分享应用崩溃解决方案
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法