go version: 1.16
汇编指令
AMD64raxrbxrcxrdxrdirsirbprspr8r9r10r11r12r13r14ripPlan9AXBXCXDXDISIBPSPR8R9R10R11R12R13R14PC
rip
寄存器:存放的是CPU
即将执行的下一条指令在内存中的地址。这个rip
是CPU
自动控制的,不用我们修改。
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✔1h21m15s22: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) 所以第四行指令,sub
将rsp
下移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)
分别把1
和2
放入rsp
和8(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函数的栈帧如下:
继续执行,下断点到sum
的ret
处
(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
时候,ret
把rsp
的内容取出来放到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函数的调用函数 此时函数的栈帧如下: