c-7.3默认选项编译结果如下,fp已经不在了,虽然这里仍然可能通过r7得知上个栈帧的位置,但是已经没法使用fp获取栈帧了。此时是不保证栈帧保存在栈中的。所以依赖栈帧内容进行恢复已经非常不可靠。那么既然无法依赖fp,那该怎么进行栈帧回溯呢,gnu说使用unwind方法回溯,这节暂时不会介绍unwind方法。
000103c8 <func1>:
103c8: b580 push {r7, lr}
103ca: b082 sub sp, #8
103cc: af00 add r7, sp, #0
103ce: 2300 movs r3, #0
103d0: 607b str r3, [r7, #4]
103d2: f240 4048 movw r0, #1096 ; 0x448
103d6: f2c0 0001 movt r0, #1
103da: f7ff ef7e blx 102d8 <puts@plt>
103de: 687b ldr r3, [r7, #4]
103e0: 4798 blx r3
103e2: 2300 movs r3, #0
103e4: 4618 mov r0, r3
103e6: 3708 adds r7, #8
103e8: 46bd mov sp, r7
103ea: bd80 pop {r7, pc}
000103ec <main>:
103ec: b580 push {r7, lr}
103ee: af00 add r7, sp, #0
103f0: f7ff ffea bl 103c8 <func1>
103f4: 2300 movs r3, #0
103f6: 4618 mov r0, r3
103f8: bd80 pop {r7, pc}
使用栈帧进行回溯
这一节使用gcc4.7版本,默认编译选项编译出来的程序,演示调用栈回溯。该编译选项下,压栈的寄存器为{fp, lr}。
下边的内容是一段core dump中的寄存器和调用栈,本节将对这段内容进行回溯。
Reg: r9, Val = 0xf7578000; Reg: r10, Val = 0x00000001;
Reg: fp, Val = 0x827d3104; Reg: ip, Val = 0xf7578ae0;
Reg: sp, Val = 0x827d30e0; Reg: lr, Val = 0xf7549990;
Reg: pc, Val = 0xf7548c20; Reg: cpsr, Val = 0x60000210;
0x827d30e0: 0x00000031 0x827d31a0 0x00000001 0xd5dff060
0x827d30f0: 0xd5e0e6b1 0xd5dec134 0xf7578000 0xf7577c40
0x827d3100: 0x827d313c 0xf7549990
0x827d3140: 0x00000000 0xd5dec104 0xf7568514 0x00000002
0x827d3150: 0xd5dec104 0xf7577c40 0xf7577c38 0xd5de9224
0x827d3160: 0x827d31a0 0xf757a084 0xf7577c40 0xd5df6dd4
0x827d3170: 0x827d3194 0x00000001 0xd5e0e678 0xd5dec104
0x827d3180: 0xd5de9224 0xf7568548 0x00000000 0xf7568550
- 当前sp地址为0x827d30e0,fp地址为0x827d3104,从而得知当前函数frame0的栈帧。fp指向的地址0x827d3104为frame1的lr,0x827d3100为上一个栈帧的fp。
0x827d30e0: 0x00000031 0x827d31a0 0x00000001 0xd5dff060
0x827d30f0: 0xd5e0e6b1 0xd5dec134 0xf7578000 0xf7577c40
0x827d3100: 0x827d313c(fp) 0xf7549990(lr)
- 从frame0的fp地址0x827d313c可知,frame1的调用栈起始地址,去掉frame0的内容,得到frame1的栈帧。
0x827d312c 0xf7530c14
0x827d3110: 0xd5dff060 0x0000002c 0xd5e0e6b1 0xd5e0e6b1
0x827d3120: 0x00000001 0xd5e0e6b1 0xd5dff060 0xd5dec134
0x827d3130: 0xf7578000 0xf7577c40 0x827d3194(fp) 0xf754ad0c(lr)
- 依次类推,依次得到frame2、frame3...的栈帧。
当汇编代码的函数调用使用push {fp, ip, lr, pc}
时,则上一个栈帧的fp2在当前栈帧的(fp - #4)位置。栈帧的回溯要结合程序的汇编代码具体分析,有可能程序并不使用fp指针,也有可能栈中根本没有保存fp。
unwind方法回溯
TODO
附录1-函数调用标准缩略语
- PCS Procedure Call Standard.
- AAPCS Procedure Call Standard for the ARM Architecture (this standard).
- APCS ARM Procedure Call Standard (obsolete).
- TPCS Thumb Procedure Call Standard (obsolete).
- ATPCS ARM-Thumb Procedure Call Standard (precursor to this standar
参考资料
- ARM 体系结构概述
- Procedure Call Standard for the ARM® Architecture
- GCC 5 Release Series