Mac arm64下AddressSanitizer仅显示同文件堆栈跟踪问题咨询
问题现象
从Intel架构Mac更换为M1(arm64)设备后,使用Homebrew安装的clang开启AddressSanitizer(ASAN)时,整体功能正常,但存在异常:堆栈跟踪仅显示与触发信号的调用位于同一源文件内的栈上函数,该问题在Intel架构Mac或Linux平台均未出现。
示例1:所有函数位于同一源文件
a.c代码:
void bar(void) { *(volatile char *)0 = 0; } void foo(void) { bar(); } int main(void) { foo(); }
编译、链接与符号化命令:
$ /opt/homebrew/opt/llvm/bin/clang -c -fsanitize=address -O1 -g -fno-omit-frame-pointer a.c -o a.o $ /opt/homebrew/opt/llvm/bin/clang -fsanitize=address a.o -o a.out $ dsymutil a.out
运行程序输出:
$ ./a.out AddressSanitizer:DEADLYSIGNAL ================================================================= ==31187==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x00010050bf5c bp 0x00010054d08c sp 0x00016f8f7660 T0) ==31187==The signal is caused by a UNKNOWN memory access. ==31187==Hint: address points to the zero page. #0 0x10050bf5c in bar /Users/jpc/src/asantest/a.c:2:23 #1 0x10050bf5c in foo /Users/jpc/src/asantest/a.c:6:3 #2 0x10050bf5c in main /Users/jpc/src/asantest/a.c:10:3 ==31187==Register values: x[0] = 0x0000000000000001 x[1] = 0x000000016f8f77d0 x[2] = 0x000000016f8f77e0 x[3] = 0x000000016f8f78f8 x[4] = 0x0000000000000000 x[5] = 0x0000000000000000 x[6] = 0x0000000000000000 x[7] = 0x0000000000000000 x[8] = 0x0000000000000000 x[9] = 0x0000000000000002 x[10] = 0x0000000000000000 x[11] = 0x0000000000000002 x[12] = 0x0000000000000002 x[13] = 0x0000000000000000 x[14] = 0x0000000000000020 x[15] = 0x0000000000000000 x[16] = 0x0000000300fd7088 x[17] = 0x6ae100016f8f6a70 x[18] = 0x0000000000000000 x[19] = 0x00000001005fc060 x[20] = 0x000000010050bf34 x[21] = 0x00000001005a8070 x[22] = 0x0000000000000000 x[23] = 0x0000000000000000 x[24] = 0x0000000000000000 x[25] = 0x0000000000000000 x[26] = 0x0000000000000000 x[27] = 0x0000000000000000 x[28] = 0x0000000000000000 fp = 0x000000016f8f7660 lr = 0x000000010054d08c sp = 0x000000016f8f7660 AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV /Users/jpc/src/asantest/a.c:2:23 in bar ==31187==ABORTING zsh: abort ./a.out
栈上三个函数均正常显示,符合预期。
示例2:触发违规的函数位于其他源文件
b1.c代码:
void bar(void); void foo(void) { bar(); } int main(void) { foo(); }
b2.c代码:
void bar(void) { *(volatile char *)0 = 0; }
编译、链接与符号化命令:
$ /opt/homebrew/opt/llvm/bin/clang -c -fsanitize=address -O1 -g -fno-omit-frame-pointer b1.c -o b1.o $ /opt/homebrew/opt/llvm/bin/clang -c -fsanitize=address -O1 -g -fno-omit-frame-pointer b2.c -o b2.o $ /opt/homebrew/opt/llvm/bin/clang -fsanitize=address b1.o b2.o -o b.out $ dsymutil b.out
运行程序输出:
$ ./b.out AddressSanitizer:DEADLYSIGNAL ================================================================= ==31297==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x000102afff5c bp 0x000102afff10 sp 0x00016d303650 T0) ==31297==The signal is caused by a UNKNOWN memory access. ==31297==Hint: address points to the zero page. #0 0x102afff5c in bar /Users/jpc/src/asantest/b2.c:2:23 ==31297==Register values: x[0] = 0x0000000000000001 x[1] = 0x000000016d3037d0 x[2] = 0x000000016d3037e0 x[3] = 0x000000016d3038f8 x[4] = 0x0000000000000000 x[5] = 0x0000000000000000 x[6] = 0x0000000000000000 x[7] = 0x0000000000000000 x[8] = 0x0000000000000000 x[9] = 0x0000000000000002 x[10] = 0x0000000000000000 x[11] = 0x0000000000000002 x[12] = 0x0000000000000002 x[13] = 0x0000000000000000 x[14] = 0x0000000000000020 x[15] = 0x0000000000000000 x[16] = 0x00000003074e7088 x[17] = 0x6ae100016d302a70 x[18] = 0x0000000000000000 x[19] = 0x0000000102b08060 x[20] = 0x0000000102afff04 x[21] = 0x0000000102cb8070 x[22] = 0x0000000000000000 x[23] = 0x0000000000000000 x[24] = 0x0000000000000000 x[25] = 0x0000000000000000 x[26] = 0x0000000000000000 x[27] = 0x0000000000000000 x[28] = 0x0000000000000000 fp = 0x000000016d303650 lr = 0x0000000102afff10 sp = 0x000000016d303650 AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV /Users/jpc/src/asantest/b2.c:2:23 in bar ==31297==ABORTING zsh: abort ./b.out
堆栈跟踪仅显示b2.c中的违规函数bar,b1.c中的foo、main两个调用方均未显示,和Intel Mac、Linux平台的表现不一致。
原因与解决方案
这是Homebrew分发的LLVM版本在Apple Silicon(arm64)平台的已知适配问题:ASAN默认开启的快速栈回溯器依赖帧指针链遍历栈帧,但arm64 Darwin ABI下,单独编译的叶子函数(比如示例2里的bar)即便加了-fno-omit-frame-pointer,默认也不会生成完整的栈帧结构,快速回溯器读取到错误的帧指针地址就会提前终止遍历,只能读到当前源文件内的栈帧。同文件编译时优化会将多个函数合并内联,不存在跨编译单元的栈帧跳转,所以回溯正常。
可任选以下一种方案修复:
- 推荐方案:所有编译、链接命令追加参数
-mllvm -asan-use-fast-unwind=false,强制ASAN使用基于DWARF调试信息的慢速回溯器,不依赖帧指针链即可正确解析所有跨源文件栈帧,仅回溯速度略慢,日常调试完全无感知。 - 若需要保留快速回溯的性能,给所有编译命令追加两个参数
-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer,后者强制所有叶子函数也生成标准的栈帧结构,保证快速回溯器能正常遍历整条调用链。 - 小项目可临时跳过单独编译目标文件的步骤,直接将所有源文件传入clang一次完成编译链接,避免跨编译单元的栈帧问题,不适合中大型项目使用。
内容的提问来源于stack exchange,提问作者John Costella
相关产品推荐
相关产品推荐

