本文以Linux + arm64平台上的测试程序为例,讲解函数调用的栈帧回溯基本原理。
1. Overview
相关的函数调用规范,可参考arm官方的aapcs64文档
undefined
2. Demo
2.1 堆栈
Thread 9 (LWP 1386):
#0 0x0000007faec8fd28 in ?? ()
#1 0x0000007faf208190 in osal_memcpy (dst=0x7f0e6cc727, src=0x7f08024180, count=32477) at vdi/linux/vdi_osal.c:335
#2 0x0000007faf205278 in vdi_write_memory (core_idx=3, dst_addr=17924376359, src_data=0x7f08024180 "", len=32477, endian=16) at vdi/linux/vdi.c:1300
... ...
#11 0x000000000044e9b8 in bmMonkey::VPUTask::VideoCapture::read (this=0x7f917f90e8, frame=0x7f08013e40) at /jenkins/projects/AI_BSP_bmMonkey_daily_build/bmMonkey/src/bmMonkey.cpp:279
#12 0x000000000044ef3c in bmMonkey::VPUTask::run (this=0x4b8900, seq=2) at /jenkins/projects/AI_BSP_bmMonkey_daily_build/bmMonkey/src/bmMonkey.cpp:326
... ...
#19 0x0000007faeee73bc in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
这里就以#12和#11栈帧为例
2.2 查看#12栈帧(caller)
(gdb) f 12
#12 0x000000000044ef3c in bmMonkey::VPUTask::run (this=0x4b8900, seq=2) at
(gdb) i r
x0 0x7f0e6cc727 545702856487
x1 0x7f08024a79 545595214457
x2 0x7594 30100
x3 0x7f0e6ccfe0 545702858720
x4 0x7f0802c05d 545595244637
x5 0x7f0e6d4604 545702888964
x6 0xced9c821daa46d83 -3541579584222499453
x7 0xae3ab9e9ba49762c -5892192748956977620
x8 0x882dfd5053b622c8 -8633966389155716408
x9 0x10f14ede4ad483f6 1220843690639262710
x10 0x544ccab1c46ccb2 379653303891840178
x11 0xe324afdbaeaae7b 1022962518989647483
x12 0xb051c1241528b35f -5741595689202699425
x13 0xd87b19b469eae132 -2847654076719898318
x14 0x7 7
x15 0x2dc62c8656e5 50329173776101
x16 0x7faf233440 548399166528
x17 0x7faec8fc10 548393253904
x18 0x0 0
x19 0x7f08007df8 545595096568
x20 0x4b8900 4950272
x21 0x7fc88517de 548825012190
x22 0x7fc88517df 548825012191
x23 0x0 0
x24 0x4bc170 4964720
x25 0x1000 4096
x26 0x1 1
x27 0x7fb201f030 548447318064
x28 0x7fc88517e8 548825012200
x29 0x7f917f90b0 547901903024
x30 0x44ef3c 4517692
sp 0x7f917f90b0 0x7f917f90b0
pc 0x44ef3c 0x44ef3c <:vputask::run long>
cpsr 0x20000000 [ EL=0 C ]
fpsr 0x10 16
fpcr 0x0 0
反汇编当前函数
(gdb) disas
Dump of assembler code for function bmMonkey::VPUTask::run(unsigned long):
0x000000000044ec6c : stp x29, x30, [sp,#-352]!
0x000000000044ec70 : mov x29, sp
0x000000000044ec74 : stp x19, x20, [sp,#16]
0x000000000044ec78 : str x0, [x29,#40]
0x000000000044ec7c : str x1, [x29,#32]
0x000000000044ec80 : ldr x0, [x29,#40]
0x000000000044ec84 : bl 0x449484 <:task::getname const>
... ...
0x000000000044ef1c : and w0, w0, #0xff
0x000000000044ef20 : eor w0, w0, #0x1
0x000000000044ef24 : and w0, w0, #0xff
0x000000000044ef28 : cmp w0, #0x0
0x000000000044ef2c : b.eq 0x44f070 <:vputask::run long>
0x000000000044ef30 : ldr x1, [x29,#48]
0x000000000044ef34 : add x0, x29, #0x38
0x000000000044ef38 : bl 0x44e850 <:vputask::videocapture::read>
=> 0x000000000044ef3c : and w0, w0, #0xff
0x000000000044ef40 : strb w0, [x29,#335]
0x000000000044ef44 : ldrb w0, [x29,#335]
0x000000000044ef48 : eor w0, w0, #0x1
0x000000000044ef4c : and w0, w0, #0xff
0x000000000044ef50 : cmp w0, #0x0
... ...
0x000000000044f114 : ldp x19, x20, [sp,#16]
0x000000000044f118 : ldp x29, x30, [sp],#352
0x000000000044f11c : ret
End of assembler dump.
先看栈帧保存操作
0x000000000044ec6c : stp x29, x30, [sp,#-352]!
0x000000000044ec70 : mov x29, sp
0x000000000044ec74 : stp x19, x20, [sp,#16]
目前获取的信息如下
12保存的寄存器有: fp, lr, x19, x20
当前fp=0x7f917f90b0
下一条待执行的指令地址为0x44ef3c
最后函数退出前,会再恢复
0x000000000044f114 : ldp x19, x20, [sp,#16]
0x000000000044f118 : ldp x29, x30, [sp],#352
0x000000000044f11c : ret
2.3 查看#11栈帧(callee)
(gdb) f 11
#11 0x000000000044e9b8 in bmMonkey::VPUTask::VideoCapture::read (this=0x7f917f90e8, frame=0x7f08013e40) at
(gdb) i r
x0 0x7f0e6cc727 545702856487
x1 0x7f08024a79 545595214457
x2 0x7594 30100
x3 0x7f0e6ccfe0 545702858720
x4 0x7f0802c05d 545595244637
x5 0x7f0e6d4604 545702888964
x6 0xced9c821daa46d83 -3541579584222499453
x7 0xae3ab9e9ba49762c -5892192748956977620
x8 0x882dfd5053b622c8 -8633966389155716408
x9 0x10f14ede4ad483f6 1220843690639262710
x10 0x544ccab1c46ccb2 379653303891840178
x11 0xe324afdbaeaae7b 1022962518989647483
x12 0xb051c1241528b35f -5741595689202699425
x13 0xd87b19b469eae132 -2847654076719898318
x14 0x7 7
x15 0x2dc62c8656e5 50329173776101
x16 0x7faf233440 548399166528
x17 0x7faec8fc10 548393253904
x18 0x0 0
x19 0x7f08007df8 545595096568
x20 0x4b8900 4950272
x21 0x7fc88517de 548825012190
x22 0x7fc88517df 548825012191
x23 0x0 0
x24 0x4bc170 4964720
x25 0x1000 4096
x26 0x1 1
x27 0x7fb201f030 548447318064
x28 0x7fc88517e8 548825012200
x29 0x7f917f9040 547901902912
x30 0x44e9b8 4516280
sp 0x7f917f9040 0x7f917f9040
pc 0x44e9b8 0x44e9b8 <:vputask::videocapture::read>
cpsr 0x20000000 [ EL=0 C ]
fpsr 0x10 16
fpcr 0x0 0
反汇编当前函数
(gdb) disas
Dump of assembler code for function bmMonkey::VPUTask::VideoCapture::read(AVFrame*):
0x000000000044e850 : stp x29, x30, [sp,#-112]!
0x000000000044e854 : mov x29, sp
0x000000000044e858 : str x19, [sp,#16]
0x000000000044e85c : str x0, [x29,#40]
0x000000000044e860 : str x1, [x29,#32]
0x000000000044e864 : ldr x0, [x29,#40]
0x000000000044e868 : ldr x2, [x0,#96]
0x000000000044e86c : ldr x0, [x29,#40]
0x000000000044e870 : add x0, x0, #0x8
0x000000000044e874 : mov x1, x0
0x000000000044e878 : mov x0, x2
0x000000000044e87c : bl 0x447350
... ...
0x000000000044e9ac : mov x1, x0
0x000000000044e9b0 : mov x0, x2
0x000000000044e9b4 : bl 0x446cc0
=> 0x000000000044e9b8 : str w0, [x29,#108]
0x000000000044e9bc : ldr w0, [x29,#108]
0x000000000044e9c0 : cmp w0, #0x0
... ...
0x000000000044ec60 : ldr x19, [sp,#16]
0x000000000044ec64 : ldp x29, x30, [sp],#112
0x000000000044ec68 : ret
End of assembler dump.
先看栈帧保存操作
0x000000000044e850 : stp x29, x30, [sp,#-112]!
0x000000000044e854 : mov x29, sp
0x000000000044e858 : str x19, [sp,#16]
这里保存了fp/lr/x19三个寄存器值
最后函数退出前,会再恢复
0x000000000044ec60 : ldr x19, [sp,#16]
0x000000000044ec64 : ldp x29, x30, [sp],#112
0x000000000044ec68 : ret
11栈帧里, 存储了caller也就是#12栈帧的部分信息
(gdb) x/14xg 0x7f917f9040
0x7f917f9040: 0x0000007f917f90b0 0x000000000044ef3c
0x7f917f9050: 0x0000007f08007df8 0x0000007f917f9090
0x7f917f9060: 0x0000007f08013e40 0x0000007f917f90e8
0x7f917f9070: 0x0000007f917f9090 0x000000050000ffff
0x7f917f9080: 0x0000007f917f90b0 0x000000000044ef1c
0x7f917f9090: 0x0000000000989680 0x00000000004b8928
0x7f917f90a0: 0x0000000500000005 0x00000000004b8928
前面三个64bit值分别保存的是
0x0000007f917f90b0是#12栈帧的fp
0x000000000044ef3c是#12栈帧里待执行的下一条指令地址
0x0000007f08007df8是#12栈帧里的x19
也就是,我们可以通过#11推导出#12. 以此类推,#12也可以推导出#13等。
3. 总结
默认情况下,arm64平台的每个栈帧都会保存fp(x29)和lr(x30)两个寄存器. 通过递归这两个寄存器,可以得到整个backtrace.
-fomit-frame-pointer编译选项可以优化掉fp
程序员自我修养(ID: dumphex)