函数调用栈帧回溯

本文以Linux + arm64平台上的测试程序为例,讲解函数调用的栈帧回溯基本原理。

1. Overview

相关的函数调用规范,可参考arm官方的aapcs64文档

undefined

2. Demo

2.1 堆栈

1
2
3
4
5
6
7
8
9
10
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(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 <bmMonkey::VPUTask::run(unsigned long)+720>
cpsr 0x20000000 [ EL=0 C ]
fpsr 0x10 16
fpcr 0x0 0

反汇编当前函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(gdb) disas
Dump of assembler code for function bmMonkey::VPUTask::run(unsigned long):
0x000000000044ec6c <+0>: stp x29, x30, [sp,#-352]!
0x000000000044ec70 <+4>: mov x29, sp
0x000000000044ec74 <+8>: stp x19, x20, [sp,#16]
0x000000000044ec78 <+12>: str x0, [x29,#40]
0x000000000044ec7c <+16>: str x1, [x29,#32]
0x000000000044ec80 <+20>: ldr x0, [x29,#40]
0x000000000044ec84 <+24>: bl 0x449484 <bmMonkey::Task::getName[abi:cxx11]() const>

... ...

0x000000000044ef1c <+688>: and w0, w0, #0xff
0x000000000044ef20 <+692>: eor w0, w0, #0x1
0x000000000044ef24 <+696>: and w0, w0, #0xff
0x000000000044ef28 <+700>: cmp w0, #0x0
0x000000000044ef2c <+704>: b.eq 0x44f070 <bmMonkey::VPUTask::run(unsigned long)+1028>
0x000000000044ef30 <+708>: ldr x1, [x29,#48]
0x000000000044ef34 <+712>: add x0, x29, #0x38
0x000000000044ef38 <+716>: bl 0x44e850 <bmMonkey::VPUTask::VideoCapture::read(AVFrame*)>
=> 0x000000000044ef3c <+720>: and w0, w0, #0xff
0x000000000044ef40 <+724>: strb w0, [x29,#335]
0x000000000044ef44 <+728>: ldrb w0, [x29,#335]
0x000000000044ef48 <+732>: eor w0, w0, #0x1
0x000000000044ef4c <+736>: and w0, w0, #0xff
0x000000000044ef50 <+740>: cmp w0, #0x0

... ...

0x000000000044f114 <+1192>: ldp x19, x20, [sp,#16]
0x000000000044f118 <+1196>: ldp x29, x30, [sp],#352
0x000000000044f11c <+1200>: ret
End of assembler dump.

先看栈帧保存操作

1
2
3
0x000000000044ec6c <+0>:	stp	x29, x30, [sp,#-352]!
0x000000000044ec70 <+4>: mov x29, sp
0x000000000044ec74 <+8>: stp x19, x20, [sp,#16]

目前获取的信息如下

  • #12保存的寄存器有: fp, lr, x19, x20
  • 当前fp=0x7f917f90b0
  • 下一条待执行的指令地址为0x44ef3c

最后函数退出前,会再恢复

1
2
3
0x000000000044f114 <+1192>:	ldp	x19, x20, [sp,#16]
0x000000000044f118 <+1196>: ldp x29, x30, [sp],#352
0x000000000044f11c <+1200>: ret

2.3 查看#11栈帧(callee)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(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 <bmMonkey::VPUTask::VideoCapture::read(AVFrame*)+360>
cpsr 0x20000000 [ EL=0 C ]
fpsr 0x10 16
fpcr 0x0 0

反汇编当前函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) disas
Dump of assembler code for function bmMonkey::VPUTask::VideoCapture::read(AVFrame*):
0x000000000044e850 <+0>: stp x29, x30, [sp,#-112]!
0x000000000044e854 <+4>: mov x29, sp
0x000000000044e858 <+8>: str x19, [sp,#16]
0x000000000044e85c <+12>: str x0, [x29,#40]
0x000000000044e860 <+16>: str x1, [x29,#32]
0x000000000044e864 <+20>: ldr x0, [x29,#40]
0x000000000044e868 <+24>: ldr x2, [x0,#96]
0x000000000044e86c <+28>: ldr x0, [x29,#40]
0x000000000044e870 <+32>: add x0, x0, #0x8
0x000000000044e874 <+36>: mov x1, x0
0x000000000044e878 <+40>: mov x0, x2
0x000000000044e87c <+44>: bl 0x447350 <av_read_frame@plt>

... ...
0x000000000044e9ac <+348>: mov x1, x0
0x000000000044e9b0 <+352>: mov x0, x2
0x000000000044e9b4 <+356>: bl 0x446cc0 <avcodec_send_packet@plt>
=> 0x000000000044e9b8 <+360>: str w0, [x29,#108]
0x000000000044e9bc <+364>: ldr w0, [x29,#108]
0x000000000044e9c0 <+368>: cmp w0, #0x0

... ...

0x000000000044ec60 <+1040>: ldr x19, [sp,#16]
0x000000000044ec64 <+1044>: ldp x29, x30, [sp],#112
0x000000000044ec68 <+1048>: ret
End of assembler dump.

先看栈帧保存操作

1
2
3
0x000000000044e850 <+0>:	stp	x29, x30, [sp,#-112]!
0x000000000044e854 <+4>: mov x29, sp
0x000000000044e858 <+8>: str x19, [sp,#16]

这里保存了fp/lr/x19三个寄存器值

最后函数退出前,会再恢复

1
2
3
0x000000000044ec60 <+1040>:	ldr	x19, [sp,#16]
0x000000000044ec64 <+1044>: ldp x29, x30, [sp],#112
0x000000000044ec68 <+1048>: ret

#11栈帧里, 存储了caller也就是#12栈帧的部分信息

1
2
3
4
5
6
7
8
(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)

坚持技术原创,感谢您的支持!