ARM macOS下显式调用raise()触发信号时栈返回地址乱码问题排查
ARM macOS上
raise()触发信号时栈回溯地址乱码的原因与解决办法 你碰到的这个ARM macOS上的栈回溯乱码问题,本质是苹果的安全机制在搞事情——raise()函数触发信号时会偷偷修改栈里的返回地址,加了个高位的混淆标记,而你的手动回溯逻辑没处理这个标记,所以就出现了奇怪的乱码地址。
为啥raise()会搞出这个问题?
在ARM64的macOS里,raise()为了抵御ROP攻击(一种常见的内存漏洞利用方式),会在触发信号前做个小动作:
- 它给返回地址的高16位塞了一个随机的“混淆值”(就是你看到的
0x116a000、0x8f6a000这类前缀) - 这个操作是在用户态完成的,只有像
raise()这种主动触发信号的函数才会这么干 - 但你用访问空指针触发的段错误不一样,那是硬件直接抛出的异常,栈里的返回地址完全是原生的,没被篡改过
你的回溯代码哪里漏了?
你手动遍历帧指针的代码直接把被篡改的返回地址读出来用了:
programCounter = *(uint64_t*)(framePointer + 8);
这个地址的高16位是无效的混淆值,传给backtrace_symbols()后,自然就解析出乱码了(比如0x116a000185422e14其实是正确地址0x185422e14加了高16位的混淆前缀)。
怎么解决?
很简单,把高16位的混淆值去掉就行。ARM64的有效地址只需要低48位,所以用0x0000FFFFFFFFFFFF这个掩码做按位与操作,就能得到正确的返回地址:
programCounter = *(uint64_t*)(framePointer + 8) & 0x0000FFFFFFFFFFFF;
改完之后,你的回溯代码就能正确识别地址,符号化的结果也就正常了。
额外补充几个细节
- 这个高16位的混淆标记是苹果Stack Pointer Guard安全机制的一部分,专门针对用户态主动发信号的场景
- 系统自带的
backtrace()函数内部已经处理了这个标记,所以如果你直接调用backtrace(bt, 100)而不是手动遍历帧指针,也能拿到正确的调用栈 - 不管你触发的是SIGSEGV还是其他信号,只要是通过
raise()这类用户态函数触发的,都会有这个地址篡改的行为,所以你才会看到所有信号都有同样的问题
内容的提问来源于stack exchange,提问作者Donpedro
相关产品推荐
相关产品推荐

