首页>>后端>>Golang->gdb 分析go函数栈帧

gdb 分析go函数栈帧

时间:2023-11-30 本站 点击:0

go version: 1.16

汇编指令

AMD64raxrbxrcxrdxrdirsirbprspr8r9r10r11r12r13r14ripPlan9AXBXCXDXDISIBPSPR8R9R10R11R12R13R14PC

rip寄存器:存放的是CPU即将执行的下一条指令在内存中的地址。这个ripCPU自动控制的,不用我们修改。

rsp栈顶寄存器和rbp栈基寄存器:rsp存放栈帧顶部地址,rbp存放栈帧起始地址。

其他的寄存器,没有做特殊规定,我们可以拿来自己用 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反汇编成平台相关的汇编代码

packagemainfuncsum(a,bint)int{c:=a+breturnc}funcmain(){sum(1,2)}

编译

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

使用gdb反汇编

gdb main

~/workspace/study/$gdbmain✔1h21m15s22:16:31GNUgdb(GDB)11.1Copyright(C)2021FreeSoftwareFoundation,Inc.LicenseGPLv3+:GNUGPLversion3orlater<http://gnu.org/licenses/gpl.html>Thisisfreesoftware:youarefreetochangeandredistributeit.ThereisNOWARRANTY,totheextentpermittedbylaw.Type"showcopying"and"showwarranty"fordetails.ThisGDBwasconfiguredas"x86_64-apple-darwin20.4.0".Type"showconfiguration"forconfigurationdetails.Forbugreportinginstructions,pleasesee:<https://www.gnu.org/software/gdb/bugs/>.FindtheGDBmanualandotherdocumentationresourcesonlineat:<http://www.gnu.org/software/gdb/documentation/>.Forhelp,type"help".Type"aproposword"tosearchforcommandsrelatedto"word"...Readingsymbolsfrommain...(Nodebuggingsymbolsfoundinmain)LoadingGoRuntimesupport.(gdb)disas'main.main'//反汇编main函数Dumpofassemblercodeforfunctionmain.main:0x000000000105e2a0<+0>:mov%gs:0x30,%rcx0x000000000105e2a9<+9>:cmp0x10(%rcx),%rsp0x000000000105e2ad<+13>:jbe0x105e2e0<main.main+64>0x000000000105e2af<+15>:sub$0x20,%rsp0x000000000105e2b3<+19>:mov%rbp,0x18(%rsp)0x000000000105e2b8<+24>:lea0x18(%rsp),%rbp0x000000000105e2bd<+29>:movq$0x1,(%rsp)0x000000000105e2c5<+37>:movq$0x2,0x8(%rsp)0x000000000105e2ce<+46>:call0x105e260<main.sum>0x000000000105e2d3<+51>:mov0x18(%rsp),%rbp0x000000000105e2d8<+56>:add$0x20,%rsp0x000000000105e2dc<+60>:ret0x000000000105e2dd<+61>:nopl(%rax)0x000000000105e2e0<+64>:call0x1059560<runtime.morestack_noctxt>0x000000000105e2e5<+69>:jmp0x105e2a0<main.main>Endofassemblerdump.(gdb)b*0x000000000105e2a0//在main函数汇编代码第一行下断点Breakpoint1at0x105e2a0(gdb)r//运行,停在刚打的断点位置Startingprogram:/Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main[NewThread0x2b03ofprocess27105][NewThread0x5403ofprocess27105]warning:unhandleddyldversion(17)[NewThread0x2c07ofprocess27105][NewThread0x2e03ofprocess27105][NewThread0x3003ofprocess27105][NewThread0x5303ofprocess27105]Thread2hitBreakpoint1,0x000000000105e2a0inmain.main()(gdb)disas//列出汇编代码Dumpofassemblercodeforfunctionmain.main:=>0x000000000105e2a0<+0>:mov%gs:0x30,%rcx//=>代表当前block的位置0x000000000105e2a9<+9>:cmp0x10(%rcx),%rsp0x000000000105e2ad<+13>:jbe0x105e2e0<main.main+64>0x000000000105e2af<+15>:sub$0x20,%rsp0x000000000105e2b3<+19>:mov%rbp,0x18(%rsp)0x000000000105e2b8<+24>:lea0x18(%rsp),%rbp0x000000000105e2bd<+29>:movq$0x1,(%rsp)0x000000000105e2c5<+37>:movq$0x2,0x8(%rsp)0x000000000105e2ce<+46>:call0x105e260<main.sum>0x000000000105e2d3<+51>:mov0x18(%rsp),%rbp0x000000000105e2d8<+56>:add$0x20,%rsp0x000000000105e2dc<+60>:ret0x000000000105e2dd<+61>:nopl(%rax)0x000000000105e2e0<+64>:call0x1059560<runtime.morestack_noctxt>0x000000000105e2e5<+69>:jmp0x105e2a0<main.main>Endofassemblerdump.(gdb)irrbprsprip//查看这几个寄存器的值rbp0xc0000507d00xc0000507d0rsp0xc0000507800xc000050780rip0x105e2a00x105e2a0<main.main>(gdb)

此时main函数的栈帧如下:

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

(gdb)b*0x000000000105e2bdBreakpoint2at0x105e2bd(gdb)cContinuing.Thread2hitBreakpoint2,0x000000000105e2bdinmain.main()(gdb)disasDumpofassemblercodeforfunctionmain.main:0x000000000105e2a0<+0>:mov%gs:0x30,%rcx0x000000000105e2a9<+9>:cmp0x10(%rcx),%rsp0x000000000105e2ad<+13>:jbe0x105e2e0<main.main+64>0x000000000105e2af<+15>:sub$0x20,%rsp0x000000000105e2b3<+19>:mov%rbp,0x18(%rsp)0x000000000105e2b8<+24>:lea0x18(%rsp),%rbp=>0x000000000105e2bd<+29>:movq$0x1,(%rsp)0x000000000105e2c5<+37>:movq$0x2,0x8(%rsp)0x000000000105e2ce<+46>:call0x105e260<main.sum>0x000000000105e2d3<+51>:mov0x18(%rsp),%rbp0x000000000105e2d8<+56>:add$0x20,%rsp0x000000000105e2dc<+60>:ret0x000000000105e2dd<+61>:nopl(%rax)0x000000000105e2e0<+64>:call0x1059560<runtime.morestack_noctxt>0x000000000105e2e5<+69>:jmp0x105e2a0<main.main>Endofassemblerdump.(gdb)irrbprspriprbp0xc0000507780xc000050778rsp0xc0000507600xc000050760rip0x105e2bd0x105e2bd<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函数的栈帧如下:

下断点到call指向位置

(gdb)b*0x000000000105e2ceBreakpoint3at0x105e2ce(gdb)cContinuing.Thread2hitBreakpoint3,0x000000000105e2ceinmain.main()(gdb)disasDumpofassemblercodeforfunctionmain.main:0x000000000105e2a0<+0>:mov%gs:0x30,%rcx0x000000000105e2a9<+9>:cmp0x10(%rcx),%rsp0x000000000105e2ad<+13>:jbe0x105e2e0<main.main+64>0x000000000105e2af<+15>:sub$0x20,%rsp0x000000000105e2b3<+19>:mov%rbp,0x18(%rsp)0x000000000105e2b8<+24>:lea0x18(%rsp),%rbp0x000000000105e2bd<+29>:movq$0x1,(%rsp)0x000000000105e2c5<+37>:movq$0x2,0x8(%rsp)=>0x000000000105e2ce<+46>:call0x105e260<main.sum>0x000000000105e2d3<+51>:mov0x18(%rsp),%rbp0x000000000105e2d8<+56>:add$0x20,%rsp0x000000000105e2dc<+60>:ret0x000000000105e2dd<+61>:nopl(%rax)0x000000000105e2e0<+64>:call0x1059560<runtime.morestack_noctxt>0x000000000105e2e5<+69>:jmp0x105e2a0<main.main>Endofassemblerdump.(gdb)irrbprspriprbp0xc0000507780xc000050778rsp0xc0000507600xc000050760rip0x105e2ce0x105e2ce<main.main+46>(gdb)

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

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

(gdb)disas'main.sum'Dumpofassemblercodeforfunctionmain.sum:0x000000000105e260<+0>:sub$0x10,%rsp0x000000000105e264<+4>:mov%rbp,0x8(%rsp)0x000000000105e269<+9>:lea0x8(%rsp),%rbp0x000000000105e26e<+14>:movq$0x0,0x28(%rsp)0x000000000105e277<+23>:mov0x18(%rsp),%rax0x000000000105e27c<+28>:add0x20(%rsp),%rax0x000000000105e281<+33>:mov%rax,(%rsp)0x000000000105e285<+37>:mov%rax,0x28(%rsp)0x000000000105e28a<+42>:mov0x8(%rsp),%rbp0x000000000105e28f<+47>:add$0x10,%rsp0x000000000105e293<+51>:ret0x000000000105e294<+52>:int30x000000000105e295<+53>:int30x000000000105e296<+54>:int30x000000000105e297<+55>:int30x000000000105e298<+56>:int30x000000000105e299<+57>:int30x000000000105e29a<+58>:int30x000000000105e29b<+59>:int30x000000000105e29c<+60>:int30x000000000105e29d<+61>:int30x000000000105e29e<+62>:int30x000000000105e29f<+63>:int3Endofassemblerdump.(gdb)b*0x000000000105e260Breakpoint4at0x105e260(gdb)disasDumpofassemblercodeforfunctionmain.main:0x000000000105e2a0<+0>:mov%gs:0x30,%rcx0x000000000105e2a9<+9>:cmp0x10(%rcx),%rsp0x000000000105e2ad<+13>:jbe0x105e2e0<main.main+64>0x000000000105e2af<+15>:sub$0x20,%rsp0x000000000105e2b3<+19>:mov%rbp,0x18(%rsp)0x000000000105e2b8<+24>:lea0x18(%rsp),%rbp0x000000000105e2bd<+29>:movq$0x1,(%rsp)0x000000000105e2c5<+37>:movq$0x2,0x8(%rsp)=>0x000000000105e2ce<+46>:call0x105e260<main.sum>0x000000000105e2d3<+51>:mov0x18(%rsp),%rbp0x000000000105e2d8<+56>:add$0x20,%rsp0x000000000105e2dc<+60>:ret0x000000000105e2dd<+61>:nopl(%rax)0x000000000105e2e0<+64>:call0x1059560<runtime.morestack_noctxt>0x000000000105e2e5<+69>:jmp0x105e2a0<main.main>Endofassemblerdump.(gdb)irrbprspriprbp0xc0000507780xc000050778rsp0xc0000507600xc000050760rip0x105e2ce0x105e2ce<main.main+46>(gdb)cContinuing.Thread2hitBreakpoint4,0x000000000105e260inmain.sum()(gdb)disasDumpofassemblercodeforfunctionmain.sum:=>0x000000000105e260<+0>:sub$0x10,%rsp0x000000000105e264<+4>:mov%rbp,0x8(%rsp)0x000000000105e269<+9>:lea0x8(%rsp),%rbp0x000000000105e26e<+14>:movq$0x0,0x28(%rsp)0x000000000105e277<+23>:mov0x18(%rsp),%rax0x000000000105e27c<+28>:add0x20(%rsp),%rax0x000000000105e281<+33>:mov%rax,(%rsp)0x000000000105e285<+37>:mov%rax,0x28(%rsp)0x000000000105e28a<+42>:mov0x8(%rsp),%rbp0x000000000105e28f<+47>:add$0x10,%rsp0x000000000105e293<+51>:ret0x000000000105e294<+52>:int30x000000000105e295<+53>:int30x000000000105e296<+54>:int30x000000000105e297<+55>:int30x000000000105e298<+56>:int30x000000000105e299<+57>:int30x000000000105e29a<+58>:int30x000000000105e29b<+59>:int30x000000000105e29c<+60>:int30x000000000105e29d<+61>:int30x000000000105e29e<+62>:int30x000000000105e29f<+63>:int3Endofassemblerdump.(gdb)irrbprspriprbp0xc0000507780xc000050778rsp0xc0000507580xc000050758rip0x105e2600x105e260<main.sum>(gdb)

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

继续执行sum

(gdb)b*0x000000000105e26eBreakpoint5at0x105e26e(gdb)cContinuing.Thread2hitBreakpoint5,0x000000000105e26einmain.sum()(gdb)disasDumpofassemblercodeforfunctionmain.sum:0x000000000105e260<+0>:sub$0x10,%rsp0x000000000105e264<+4>:mov%rbp,0x8(%rsp)0x000000000105e269<+9>:lea0x8(%rsp),%rbp=>0x000000000105e26e<+14>:movq$0x0,0x28(%rsp)0x000000000105e277<+23>:mov0x18(%rsp),%rax0x000000000105e27c<+28>:add0x20(%rsp),%rax0x000000000105e281<+33>:mov%rax,(%rsp)0x000000000105e285<+37>:mov%rax,0x28(%rsp)0x000000000105e28a<+42>:mov0x8(%rsp),%rbp0x000000000105e28f<+47>:add$0x10,%rsp0x000000000105e293<+51>:ret0x000000000105e294<+52>:int30x000000000105e295<+53>:int30x000000000105e296<+54>:int30x000000000105e297<+55>:int30x000000000105e298<+56>:int30x000000000105e299<+57>:int30x000000000105e29a<+58>:int30x000000000105e29b<+59>:int30x000000000105e29c<+60>:int30x000000000105e29d<+61>:int30x000000000105e29e<+62>:int30x000000000105e29f<+63>:int3Endofassemblerdump.(gdb)irrbprspriprbp0xc0000507500xc000050750rsp0xc0000507480xc000050748rip0x105e26e0x105e26e<main.sum+14>(gdb)

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

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

接下来五行汇编代码

=>0x000000000105e26e<+14>:movq$0x0,0x28(%rsp)0x000000000105e277<+23>:mov0x18(%rsp),%rax0x000000000105e27c<+28>:add0x20(%rsp),%rax0x000000000105e281<+33>:mov%rax,(%rsp)0x000000000105e285<+37>:mov%rax,0x28(%rsp)

40(rsp)的位置赋值为0

24(rsp)的值,也就是1存到rax

32(rsp)的值,也就是2,与rax相加,并将结果存到rax里,此时rax的值为3

rax的值赋值给rsp(就是本地变量c)

rax的值赋值给40(rsp),这个位置代表sum函数的返回值

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

继续执行,下断点到sumret

(gdb)b*0x000000000105e293Breakpoint6at0x105e293(gdb)cContinuing.Thread2hitBreakpoint6,0x000000000105e293inmain.sum()(gdb)disasDumpofassemblercodeforfunctionmain.sum:0x000000000105e260<+0>:sub$0x10,%rsp0x000000000105e264<+4>:mov%rbp,0x8(%rsp)0x000000000105e269<+9>:lea0x8(%rsp),%rbp0x000000000105e26e<+14>:movq$0x0,0x28(%rsp)0x000000000105e277<+23>:mov0x18(%rsp),%rax0x000000000105e27c<+28>:add0x20(%rsp),%rax0x000000000105e281<+33>:mov%rax,(%rsp)0x000000000105e285<+37>:mov%rax,0x28(%rsp)0x000000000105e28a<+42>:mov0x8(%rsp),%rbp0x000000000105e28f<+47>:add$0x10,%rsp=>0x000000000105e293<+51>:ret0x000000000105e294<+52>:int30x000000000105e295<+53>:int30x000000000105e296<+54>:int30x000000000105e297<+55>:int30x000000000105e298<+56>:int30x000000000105e299<+57>:int30x000000000105e29a<+58>:int30x000000000105e29b<+59>:int30x000000000105e29c<+60>:int30x000000000105e29d<+61>:int30x000000000105e29e<+62>:int30x000000000105e29f<+63>:int3Endofassemblerdump.(gdb)irrbprspriprbp0xc0000507780xc000050778rsp0xc0000507580xc000050758rip0x105e2930x105e293<main.sum+51>(gdb)

解读下这两行

0x000000000105e28a<+42>:mov0x8(%rsp),%rbp0x000000000105e28f<+47>:add$0x10,%rsp

8(rsp)的值赋值给rbp

rsp上移16字节 此时main函数的栈帧如下: 在执行ret时候,retrsp的内容取出来放到rip寄存器中,并且return addr出栈,rsp上移8字节,这样rip就指向了调用sum之前call指令的下一个地址,从而返回到main函数中继续执行 此时main函数的栈帧如下:

下断点到call指令的下一行

packagemainfuncsum(a,bint)int{c:=a+breturnc}funcmain(){sum(1,2)}0

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

packagemainfuncsum(a,bint)int{c:=a+breturnc}funcmain(){sum(1,2)}1

将24(rsp)的值放入rbp

将rsp上移32字节

ret返回main函数的调用函数 此时函数的栈帧如下:


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Golang/4562.html