go 语言使用的是plan9操作系统自带的汇编器,比较小众,有自己的一套语法,具体原因可以参考博客Go 语言的实现为何使用Plan 9的汇编器?
Plan9 汇编指令
通过后缀B,W,L,Q分别表示1,2,4,8字节
(1)移动指令MOVX SRC DST, X表示数据类型(B,W,L,Q), SRC表示源数据,DST表示目的寄存器或者栈地址
![](https://img-blog.csdnimg.cn/img_convert/685d1d779e3312cdf961a4eca013e42d.png)
(2)计算指令
![](https://img-blog.csdnimg.cn/img_convert/e4b7031db888e8e9fc1ce1142bf9fb58.png)
(3)比较类指令
![](https://img-blog.csdnimg.cn/img_convert/e1f6e838abb8dcfd840aa564cc546e9f.png)
(4)跳转类指令
![](https://img-blog.csdnimg.cn/img_convert/8f520c88460adc38f8db8c3f1886a0a8.png)
(5)操作类指令
![](https://img-blog.csdnimg.cn/img_convert/8e1ed4b290a93d5cd7e1d59a201c526e.png)
(6)调用类指令
![](https://img-blog.csdnimg.cn/img_convert/28762981041c605da67e2f803f05f4b8.png)
(7)伪指令
同时,为了配合运行时优化,Plan 9定了一些伪指令。如下表列出常用的几个伪指令:
![](https://img-blog.csdnimg.cn/img_convert/ef4111db5cf7cd9ae9abb0689eef8830.png)
Plan 9汇编寄存器表示
plan9汇编器的名称作用及和x86-64系统18个通用寄存器的映射关系
![](https://img-blog.csdnimg.cn/img_convert/ee1d0c196a8d74ba0f0be0c52d9df036.png)
paln9汇编 为了编程方便引入的伪寄存器
FP: Frame Pointer:arguments,用于指向第一个入参。格式symbol+offset(FP),symbol从汇编角度是无意义的,但必须有,可以增加可读性,(FP)表示寄存器里面的是地址,取寄存器存储的地址所对应的值
按照C语言的函数调用归约,入参及返回值由调用方维护,因此该寄存器表示的地址实际在本函数的调用函数,参数入栈的顺序为右-左(为了支持变参),因此第一个参数(相比较其他参数)反而更靠近栈顶,而栈地址是由高地址向低地址扩展,也就是栈顶在低地址。假设所有参数都是8字节,因此plan9 汇编可以用
arg1+0(FP)表示第一个参数,arg+8(FP)表示第二个参数。
PC:Program Counter,对应实际的IP寄存器,也就是下一条需要执行的指令地址。
SB:Static Base Pointer,q全局静态基指针,一般用来申明函数和全局变量。
SP:Locals,用于指向局部变量,格式:symbol+offset(SP)
指向第一个局部变量的尾部,局部变量是按照局部变量的顺序入栈的,假设变量都是8字节,第一个变量可以表示localvar1-8(SP),第二个局部变量为localvar1-16(SP)。
注意:需要和真正的寄存器SP区分,真正的SP寄存器,Stack Pointer,指向最后一个栈帧的栈顶,也就是栈顶。在go的汇编源码里面,表示伪SP寄存器,必须是symbol+offset(SP)的格式,类似(SP),8(SP)都是真正的SP寄存器。还有对于编译输出(go tool compile -S / go tool objdump)的代码来讲,所有的 SP 都是硬件 SP 寄存器,无论是否带 symbol(这一点非常具有迷惑性,需要慢慢理解。往往在分析编译输出的汇编时,看到的就是硬件 SP 寄存器)
伪寄存器内存模型
caller:表示调用方
callee:表示被调用方
![](https://img-blog.csdnimg.cn/img_convert/b0a463736d76f046c12980e7c7835207.png)
往栈上插入return addr 是由call指令(函数调用)完成的,而且在调用方的栈帧内。由图可见,FP相当于caller的帧指针,不考虑返回值的情况下。SP指向的是callee函数的栈底,不考虑caller BP的情况下
如何输出go汇编
对于写好的 go 源码,生成对应的 Go 汇编,大概有下面几种
方法 1 先使用 go build -gcflags "-N -l" main.go 生成对应的可执行二进制文件 再使用 go tool objdump -s "main\." main 反编译获取对应的汇编。反编译时"main\." 表示只输出 main 包中相关的汇编"main\.main" 则表示只输出 main 包中 main 方法相关的汇编
方法 2 使用 go tool compile -S -N -l main.go 这种方式直接输出汇编
方法 3 使用go build -gcflags="-N -l -S" main.go 直接输出汇编
注意:在使用这些命令时,加上对应的 flag,否则某些逻辑会被编译器优化掉,而看不到对应完整的汇编代码
-l 禁止内联 -N 编译时,禁止优化 -S 输出汇编代码