分享最近遇到的C符号冲突问题的排查过程
背景
做iOS端的SDK,导出为静态库,供其他业务方使用,静态库在自己的Demo上使用相关功能,都正常,但是业务方集成此静态库运行时,必现crash。
分析
crash报错原因:__stack_chk_fail。
而发生这个错误的原因,基本都是栈内存有损坏。
为什么会有__stack_chk_fail? 这其实算是iOS端特有的一种崩溃原因,在iOS上,可以开启栈内存检测,也就是canary机制:
程序调用某个函数时都会产生栈帧,调用函数时会在栈帧的某个固定偏移位置写入一个固定的值。
正常情况下这块内存不会改变,在函数退出时,会检测这块内存的值,如果发现值有改变,就表示栈内存有损坏,如果值没有改变,表示栈空间正常。
使用IDE调试器,可以看到崩溃时刻的调用栈,但调用栈只显示某个方法体(HelloWorld)崩溃,而这个HelloWorld方法体里有很多代码。
不确定具体哪一行代码导致的栈空间异常,所以需要通过断点的方式,在(HelloWorld)刚开始处打印出canary的值,然后不停的单步执行,看哪一行结束后,canary的值有改变。
首先需要确认canary value存在了哪里,通过disassemble指令查看整个函数的汇编代码,这里有两处关键信息:
一:
上图为函数退出时的部分代码:
-
从x29 - 0x18的位置处,读一个值到x9
-
从5138基址 + 0x9c0位置处,读一个值到x8
-
比较两个值是否相等,如果不相等,触发__stack_chk_fail
二:
下图一段函数开头位置的部分代码:
这段代码把 5138基址+0x9c0的值存到了x29-0x18的位置处。
通过这两步可以确认:canary value的值存到了x29-0x18位置。
所以,这样调试:
-
进入函数时,先register read 0x29,拿到0x29的地址
-
再memory read 0x29-0x18查看canary的值
-
每次都单步执行,执行后再memory read 0x29-0x18查看canary的值,看是否有改变
最后发现,执行到某个三方库中的函数后,发现canary的值变了,找到了问题所在。
再在对应的三方库相关函数中打log,发现日志没有打印 => 最后才确认,因为给的是静态库,静态链接时和业务方中其他模块符号冲突,没有调用它正常应该调用的函数,导致程序crash。
解决方案
出现符号冲突大体有两种解决方案:
- 加namespace,但是里面有很多C代码,不太方便加namespace,所有选用了第二种方案
- 换动态库,走动态链接方式接入,避免和其他符号冲突。
