gdb 分析go函数栈帧

1,210 阅读8分钟

go version: 1.16

汇编指令

AMD64  rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 rip
Plan9  AX  BX  CX  DX  DI  SI  BP  SP  R8 R9 R10 R11 R12 R13 R14 PC
  1. rip寄存器:存放的是CPU即将执行的下一条指令在内存中的地址。这个ripCPU自动控制的,不用我们修改。
  2. rsp栈顶寄存器和rbp栈基寄存器:rsp存放栈帧顶部地址,rbp存放栈帧起始地址。
  3. 其他的寄存器,没有做特殊规定,我们可以拿来自己用 Go程序预定义了4个伪寄存器
  • FP: Frame pointer: arguments and locals.
  • PC: Program counter: jumps and branches.
  • SB: Static base pointer: global symbols.
  • SP: Stack pointer: the highest address within the local stack frame.

x86汇编

方法:将代码编译成可执行程序,在通过gdb反汇编成平台相关的汇编代码

package main

func sum(a, b int) int {
	c := a + b

	return c
}

func main() {
	sum(1, 2)
}

编译

go build -gcflags "-N -l" main.go

使用gdb反汇编

gdb main

 ~/workspace/study/$ gdb main                                                                                                     ✔  1h 21m 15s  22:16:31
GNU gdb (GDB) 11.1
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin20.4.0".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main...
(No debugging symbols found in main)
Loading Go Runtime support.
(gdb) disas 'main.main'     //反汇编main函数
Dump of assembler code for function main.main:
   0x000000000105e2a0 <+0>:	mov    %gs:0x30,%rcx
   0x000000000105e2a9 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000105e2ad <+13>:	jbe    0x105e2e0 <main.main+64>
   0x000000000105e2af <+15>:	sub    $0x20,%rsp
   0x000000000105e2b3 <+19>:	mov    %rbp,0x18(%rsp)
   0x000000000105e2b8 <+24>:	lea    0x18(%rsp),%rbp
   0x000000000105e2bd <+29>:	movq   $0x1,(%rsp)
   0x000000000105e2c5 <+37>:	movq   $0x2,0x8(%rsp)
   0x000000000105e2ce <+46>:	call   0x105e260 <main.sum>
   0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
   0x000000000105e2dd <+61>:	nopl   (%rax)
   0x000000000105e2e0 <+64>:	call   0x1059560 <runtime.morestack_noctxt>
   0x000000000105e2e5 <+69>:	jmp    0x105e2a0 <main.main>
End of assembler dump.
(gdb) b *0x000000000105e2a0     //在main函数汇编代码第一行下断点
Breakpoint 1 at 0x105e2a0
(gdb) r                         //运行,停在刚打的断点位置
Starting program: /Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main
[New Thread 0x2b03 of process 27105]
[New Thread 0x5403 of process 27105]
warning: unhandled dyld version (17)
[New Thread 0x2c07 of process 27105]
[New Thread 0x2e03 of process 27105]
[New Thread 0x3003 of process 27105]
[New Thread 0x5303 of process 27105]

Thread 2 hit Breakpoint 1, 0x000000000105e2a0 in main.main ()
(gdb) disas                    // 列出汇编代码
Dump of assembler code for function main.main:
=> 0x000000000105e2a0 <+0>:	mov    %gs:0x30,%rcx    // => 代表当前block的位置
   0x000000000105e2a9 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000105e2ad <+13>:	jbe    0x105e2e0 <main.main+64>
   0x000000000105e2af <+15>:	sub    $0x20,%rsp
   0x000000000105e2b3 <+19>:	mov    %rbp,0x18(%rsp)
   0x000000000105e2b8 <+24>:	lea    0x18(%rsp),%rbp
   0x000000000105e2bd <+29>:	movq   $0x1,(%rsp)
   0x000000000105e2c5 <+37>:	movq   $0x2,0x8(%rsp)
   0x000000000105e2ce <+46>:	call   0x105e260 <main.sum>
   0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
   0x000000000105e2dd <+61>:	nopl   (%rax)
   0x000000000105e2e0 <+64>:	call   0x1059560 <runtime.morestack_noctxt>
   0x000000000105e2e5 <+69>:	jmp    0x105e2a0 <main.main>
End of assembler dump.
(gdb) i r rbp rsp rip     // 查看这几个寄存器的值
rbp            0xc0000507d0        0xc0000507d0
rsp            0xc000050780        0xc000050780
rip            0x105e2a0           0x105e2a0 <main.main>
(gdb)

此时main函数的栈帧如下:

image.png

此时只是停在main汇编代码中第一行,还并未执行这行,所以rip存的就是这行代码的地址,CPU下一条指令就会执行。
继续向下走,直接下断点到第7行

(gdb) b *0x000000000105e2bd
Breakpoint 2 at 0x105e2bd
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x000000000105e2bd in main.main ()
(gdb) disas
Dump of assembler code for function main.main:
   0x000000000105e2a0 <+0>:	mov    %gs:0x30,%rcx
   0x000000000105e2a9 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000105e2ad <+13>:	jbe    0x105e2e0 <main.main+64>
   0x000000000105e2af <+15>:	sub    $0x20,%rsp
   0x000000000105e2b3 <+19>:	mov    %rbp,0x18(%rsp)
   0x000000000105e2b8 <+24>:	lea    0x18(%rsp),%rbp
=> 0x000000000105e2bd <+29>:	movq   $0x1,(%rsp)
   0x000000000105e2c5 <+37>:	movq   $0x2,0x8(%rsp)
   0x000000000105e2ce <+46>:	call   0x105e260 <main.sum>
   0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
   0x000000000105e2dd <+61>:	nopl   (%rax)
   0x000000000105e2e0 <+64>:	call   0x1059560 <runtime.morestack_noctxt>
   0x000000000105e2e5 <+69>:	jmp    0x105e2a0 <main.main>
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050778        0xc000050778
rsp            0xc000050760        0xc000050760
rip            0x105e2bd           0x105e2bd <main.main+29>
(gdb)

由于main函数需要给sum提供参数和返回值,所以main函数要预留32字节的空间(sizeof a + b + return value = 32)
所以第四行指令,subrsp下移32字节大小,第五行指令将rbp的内容放到rsp向上偏移24字节的位置上(对应plan9就是24(SP)),第6行将·24(rsp)的地址存入rbp中,此时rbp指向24(rsp)

此时main函数的栈帧如下:

image.png 下断点到call指向位置

(gdb) b *0x000000000105e2ce
Breakpoint 3 at 0x105e2ce
(gdb) c
Continuing.

Thread 2 hit Breakpoint 3, 0x000000000105e2ce in main.main ()
(gdb) disas
Dump of assembler code for function main.main:
   0x000000000105e2a0 <+0>:	mov    %gs:0x30,%rcx
   0x000000000105e2a9 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000105e2ad <+13>:	jbe    0x105e2e0 <main.main+64>
   0x000000000105e2af <+15>:	sub    $0x20,%rsp
   0x000000000105e2b3 <+19>:	mov    %rbp,0x18(%rsp)
   0x000000000105e2b8 <+24>:	lea    0x18(%rsp),%rbp
   0x000000000105e2bd <+29>:	movq   $0x1,(%rsp)
   0x000000000105e2c5 <+37>:	movq   $0x2,0x8(%rsp)
=> 0x000000000105e2ce <+46>:	call   0x105e260 <main.sum>
   0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
   0x000000000105e2dd <+61>:	nopl   (%rax)
   0x000000000105e2e0 <+64>:	call   0x1059560 <runtime.morestack_noctxt>
   0x000000000105e2e5 <+69>:	jmp    0x105e2a0 <main.main>
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050778        0xc000050778
rsp            0xc000050760        0xc000050760
rip            0x105e2ce           0x105e2ce <main.main+46>
(gdb)

movq $0x1,(%rsp)movq $0x2,0x8(%rsp)分别把12放入rsp8(rsp)位置上,作为sum的参数. 此时main函数的栈帧如下:

image.png 接下来该运行call指令了: 先扫一眼sum函数

(gdb) disas 'main.sum'
Dump of assembler code for function main.sum:
   0x000000000105e260 <+0>:	sub    $0x10,%rsp
   0x000000000105e264 <+4>:	mov    %rbp,0x8(%rsp)
   0x000000000105e269 <+9>:	lea    0x8(%rsp),%rbp
   0x000000000105e26e <+14>:	movq   $0x0,0x28(%rsp)
   0x000000000105e277 <+23>:	mov    0x18(%rsp),%rax
   0x000000000105e27c <+28>:	add    0x20(%rsp),%rax
   0x000000000105e281 <+33>:	mov    %rax,(%rsp)
   0x000000000105e285 <+37>:	mov    %rax,0x28(%rsp)
   0x000000000105e28a <+42>:	mov    0x8(%rsp),%rbp
   0x000000000105e28f <+47>:	add    $0x10,%rsp
   0x000000000105e293 <+51>:	ret
   0x000000000105e294 <+52>:	int3
   0x000000000105e295 <+53>:	int3
   0x000000000105e296 <+54>:	int3
   0x000000000105e297 <+55>:	int3
   0x000000000105e298 <+56>:	int3
   0x000000000105e299 <+57>:	int3
   0x000000000105e29a <+58>:	int3
   0x000000000105e29b <+59>:	int3
   0x000000000105e29c <+60>:	int3
   0x000000000105e29d <+61>:	int3
   0x000000000105e29e <+62>:	int3
   0x000000000105e29f <+63>:	int3
End of assembler dump.
(gdb) b *0x000000000105e260
Breakpoint 4 at 0x105e260
(gdb) disas
Dump of assembler code for function main.main:
   0x000000000105e2a0 <+0>:	mov    %gs:0x30,%rcx
   0x000000000105e2a9 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000105e2ad <+13>:	jbe    0x105e2e0 <main.main+64>
   0x000000000105e2af <+15>:	sub    $0x20,%rsp
   0x000000000105e2b3 <+19>:	mov    %rbp,0x18(%rsp)
   0x000000000105e2b8 <+24>:	lea    0x18(%rsp),%rbp
   0x000000000105e2bd <+29>:	movq   $0x1,(%rsp)
   0x000000000105e2c5 <+37>:	movq   $0x2,0x8(%rsp)
=> 0x000000000105e2ce <+46>:	call   0x105e260 <main.sum>
   0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
   0x000000000105e2dd <+61>:	nopl   (%rax)
   0x000000000105e2e0 <+64>:	call   0x1059560 <runtime.morestack_noctxt>
   0x000000000105e2e5 <+69>:	jmp    0x105e2a0 <main.main>
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050778        0xc000050778
rsp            0xc000050760        0xc000050760
rip            0x105e2ce           0x105e2ce <main.main+46>
(gdb) c
Continuing.

Thread 2 hit Breakpoint 4, 0x000000000105e260 in main.sum ()
(gdb) disas
Dump of assembler code for function main.sum:
=> 0x000000000105e260 <+0>:	sub    $0x10,%rsp
   0x000000000105e264 <+4>:	mov    %rbp,0x8(%rsp)
   0x000000000105e269 <+9>:	lea    0x8(%rsp),%rbp
   0x000000000105e26e <+14>:	movq   $0x0,0x28(%rsp)
   0x000000000105e277 <+23>:	mov    0x18(%rsp),%rax
   0x000000000105e27c <+28>:	add    0x20(%rsp),%rax
   0x000000000105e281 <+33>:	mov    %rax,(%rsp)
   0x000000000105e285 <+37>:	mov    %rax,0x28(%rsp)
   0x000000000105e28a <+42>:	mov    0x8(%rsp),%rbp
   0x000000000105e28f <+47>:	add    $0x10,%rsp
   0x000000000105e293 <+51>:	ret
   0x000000000105e294 <+52>:	int3
   0x000000000105e295 <+53>:	int3
   0x000000000105e296 <+54>:	int3
   0x000000000105e297 <+55>:	int3
   0x000000000105e298 <+56>:	int3
   0x000000000105e299 <+57>:	int3
   0x000000000105e29a <+58>:	int3
   0x000000000105e29b <+59>:	int3
   0x000000000105e29c <+60>:	int3
   0x000000000105e29d <+61>:	int3
   0x000000000105e29e <+62>:	int3
   0x000000000105e29f <+63>:	int3
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050778        0xc000050778
rsp            0xc000050758        0xc000050758
rip            0x105e260           0x105e260 <main.sum>
(gdb)

执行call的时候,rip的值会编程下一条指令的地0x000000000105e2d3,并且call会把当前rip的值入栈(这里代表函数栈的return addr位置),同时栈顶的rsp也会下移到return addr的位置,同时又将rip的值设置为call执行调用的那个函数的第一条指令的位置0x000000000105e260
此时main函数的栈帧如下:

image.png 继续执行sum

(gdb) b *0x000000000105e26e
Breakpoint 5 at 0x105e26e
(gdb) c
Continuing.

Thread 2 hit Breakpoint 5, 0x000000000105e26e in main.sum ()
(gdb) disas
Dump of assembler code for function main.sum:
   0x000000000105e260 <+0>:	sub    $0x10,%rsp
   0x000000000105e264 <+4>:	mov    %rbp,0x8(%rsp)
   0x000000000105e269 <+9>:	lea    0x8(%rsp),%rbp
=> 0x000000000105e26e <+14>:	movq   $0x0,0x28(%rsp)
   0x000000000105e277 <+23>:	mov    0x18(%rsp),%rax
   0x000000000105e27c <+28>:	add    0x20(%rsp),%rax
   0x000000000105e281 <+33>:	mov    %rax,(%rsp)
   0x000000000105e285 <+37>:	mov    %rax,0x28(%rsp)
   0x000000000105e28a <+42>:	mov    0x8(%rsp),%rbp
   0x000000000105e28f <+47>:	add    $0x10,%rsp
   0x000000000105e293 <+51>:	ret
   0x000000000105e294 <+52>:	int3
   0x000000000105e295 <+53>:	int3
   0x000000000105e296 <+54>:	int3
   0x000000000105e297 <+55>:	int3
   0x000000000105e298 <+56>:	int3
   0x000000000105e299 <+57>:	int3
   0x000000000105e29a <+58>:	int3
   0x000000000105e29b <+59>:	int3
   0x000000000105e29c <+60>:	int3
   0x000000000105e29d <+61>:	int3
   0x000000000105e29e <+62>:	int3
   0x000000000105e29f <+63>:	int3
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050750        0xc000050750
rsp            0xc000050748        0xc000050748
rip            0x105e26e           0x105e26e <main.sum+14>
(gdb)

因为sum栈不为空,并且只有一个本地变量,所以需要下移16字节(8字节给局部变量,8字节给caller's BP),并将rbp的值存到8(rsp)中,将8(rsp)的地址存到rbp中

此时main/sum函数的栈帧如下:

image.png 接下来五行汇编代码

=> 0x000000000105e26e <+14>:	movq   $0x0,0x28(%rsp)
   0x000000000105e277 <+23>:	mov    0x18(%rsp),%rax
   0x000000000105e27c <+28>:	add    0x20(%rsp),%rax
   0x000000000105e281 <+33>:	mov    %rax,(%rsp)
   0x000000000105e285 <+37>:	mov    %rax,0x28(%rsp)
  1. 40(rsp)的位置赋值为0
  2. 24(rsp)的值,也就是1存到rax
  3. 32(rsp)的值,也就是2,与rax相加,并将结果存到rax里,此时rax的值为3
  4. rax的值赋值给rsp(就是本地变量c)
  5. rax的值赋值给40(rsp),这个位置代表sum函数的返回值

此时main/sum函数的栈帧如下:

image.png 继续执行,下断点到sumret

(gdb) b *0x000000000105e293
Breakpoint 6 at 0x105e293
(gdb) c
Continuing.

Thread 2 hit Breakpoint 6, 0x000000000105e293 in main.sum ()
(gdb) disas
Dump of assembler code for function main.sum:
   0x000000000105e260 <+0>:	sub    $0x10,%rsp
   0x000000000105e264 <+4>:	mov    %rbp,0x8(%rsp)
   0x000000000105e269 <+9>:	lea    0x8(%rsp),%rbp
   0x000000000105e26e <+14>:	movq   $0x0,0x28(%rsp)
   0x000000000105e277 <+23>:	mov    0x18(%rsp),%rax
   0x000000000105e27c <+28>:	add    0x20(%rsp),%rax
   0x000000000105e281 <+33>:	mov    %rax,(%rsp)
   0x000000000105e285 <+37>:	mov    %rax,0x28(%rsp)
   0x000000000105e28a <+42>:	mov    0x8(%rsp),%rbp
   0x000000000105e28f <+47>:	add    $0x10,%rsp
=> 0x000000000105e293 <+51>:	ret
   0x000000000105e294 <+52>:	int3
   0x000000000105e295 <+53>:	int3
   0x000000000105e296 <+54>:	int3
   0x000000000105e297 <+55>:	int3
   0x000000000105e298 <+56>:	int3
   0x000000000105e299 <+57>:	int3
   0x000000000105e29a <+58>:	int3
   0x000000000105e29b <+59>:	int3
   0x000000000105e29c <+60>:	int3
   0x000000000105e29d <+61>:	int3
   0x000000000105e29e <+62>:	int3
   0x000000000105e29f <+63>:	int3
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050778        0xc000050778
rsp            0xc000050758        0xc000050758
rip            0x105e293           0x105e293 <main.sum+51>
(gdb)

解读下这两行

0x000000000105e28a <+42>:	mov    0x8(%rsp),%rbp
0x000000000105e28f <+47>:	add    $0x10,%rsp
  1. 8(rsp)的值赋值给rbp
  2. rsp上移16字节 此时main函数的栈帧如下: image.png 在执行ret时候,retrsp的内容取出来放到rip寄存器中,并且return addr出栈,rsp上移8字节,这样rip就指向了调用sum之前call指令的下一个地址,从而返回到main函数中继续执行 此时main函数的栈帧如下:

image.png 下断点到call指令的下一行

(gdb) b *0x000000000105e2d3
Breakpoint 7 at 0x105e2d3
(gdb) c
Continuing.

Thread 2 hit Breakpoint 7, 0x000000000105e2d3 in main.main ()
(gdb) disas
Dump of assembler code for function main.main:
   0x000000000105e2a0 <+0>:	mov    %gs:0x30,%rcx
   0x000000000105e2a9 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000105e2ad <+13>:	jbe    0x105e2e0 <main.main+64>
   0x000000000105e2af <+15>:	sub    $0x20,%rsp
   0x000000000105e2b3 <+19>:	mov    %rbp,0x18(%rsp)
   0x000000000105e2b8 <+24>:	lea    0x18(%rsp),%rbp
   0x000000000105e2bd <+29>:	movq   $0x1,(%rsp)
   0x000000000105e2c5 <+37>:	movq   $0x2,0x8(%rsp)
   0x000000000105e2ce <+46>:	call   0x105e260 <main.sum>
=> 0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
   0x000000000105e2dd <+61>:	nopl   (%rax)
   0x000000000105e2e0 <+64>:	call   0x1059560 <runtime.morestack_noctxt>
   0x000000000105e2e5 <+69>:	jmp    0x105e2a0 <main.main>
End of assembler dump.
(gdb) i r rbp rsp rip
rbp            0xc000050778        0xc000050778
rsp            0xc000050760        0xc000050760
rip            0x105e2d3           0x105e2d3 <main.main+51>
(gdb)

可以看到这几个寄存器的值都和图片上一样 接下来三行代码

=> 0x000000000105e2d3 <+51>:	mov    0x18(%rsp),%rbp
   0x000000000105e2d8 <+56>:	add    $0x20,%rsp
   0x000000000105e2dc <+60>:	ret
  1. 将24(rsp)的值放入rbp
  2. 将rsp上移32字节
  3. ret返回main函数的调用函数 此时函数的栈帧如下:

image.png