本文以Linux + arm64平台上的测试程序为例,讲解函数调用的栈帧回溯基本原理。
1. Overview
相关的函数调用规范,可参考arm官方的aapcs64文档
2. Demo
2.1 堆栈
1 | Thread 9 (LWP 1386): |
这里就以#12和#11栈帧为例
2.2 查看#12栈帧(caller)
1 | (gdb) f 12 |
反汇编当前函数
1 | (gdb) disas |
先看栈帧保存操作
1 | 0x000000000044ec6c <+0>: stp x29, x30, [sp,#-352]! |
目前获取的信息如下
- #12保存的寄存器有: fp, lr, x19, x20
- 当前fp=0x7f917f90b0
- 下一条待执行的指令地址为0x44ef3c
最后函数退出前,会再恢复
1 | 0x000000000044f114 <+1192>: ldp x19, x20, [sp,#16] |
2.3 查看#11栈帧(callee)
1 | (gdb) f 11 |
反汇编当前函数
1 | (gdb) disas |
先看栈帧保存操作
1 | 0x000000000044e850 <+0>: stp x29, x30, [sp,#-112]! |
这里保存了fp/lr/x19三个寄存器值
最后函数退出前,会再恢复
1 | 0x000000000044ec60 <+1040>: ldr x19, [sp,#16] |
#11栈帧里, 存储了caller也就是#12栈帧的部分信息
1 | (gdb) x/14xg 0x7f917f9040 |
前面三个64bit值分别保存的是
- 0x0000007f917f90b0是#12栈帧的fp
- 0x000000000044ef3c是#12栈帧里待执行的下一条指令地址
- 0x0000007f08007df8是#12栈帧里的x19
也就是,我们可以通过#11推导出#12. 以此类推,#12也可以推导出#13等。
3. 总结
- 默认情况下,arm64平台的每个栈帧都会保存fp(x29)和lr(x30)两个寄存器. 通过递归这两个寄存器,可以得到整个backtrace.
- -fomit-frame-pointer编译选项可以优化掉fp