【Free Style】深入Go语言模型(2):Go语言汇编指令简介
前言
在学习深入学习Go语言的过程中,需要大量阅读Go语言的汇编代码来查看Go运行的运行原理,那么本文将简单介绍Go语言的汇编指令语法,让大家后续可以简单读懂Go的汇编指令。
简介
Go语言的汇编是基于Pan9汇编语言的风格(https://9p.io/sys/doc/asm.html)
大部分情况我们是不需要写汇编代码的,所以下面主要简单介绍一下Go的汇编语法
生成汇编代码的方法(基于Go1.8)
在命令行中执行:
go build -gcflags -S main.go 2> main.s
-gcflags表示将后面的参数传给Go编译器,可以通过go tool compile -help来列出了可以被传入编译器的所有的参数列表。
汇编语法介绍
Go语言的汇编指令不一定直接对应机器表示。有一些直接对应,有一些则不是。
编译器产生的是一些中间码,具体的机器指令是在汇编生成之后才定下来的(Linker的工作)。
FUNCDATA和PCDATA是编译器产生的,用于保存一些给垃圾收集的信息。
寄存器
Go语言有4个伪寄存器,实际是对内存位置的一个引用。
FP(frame pointer): 帧指针,保存参数和本地变量
编译器维护了一个虚拟的栈指针,使用对伪寄存器的offsets操作的形式,指向栈上的函数参数。 于是,0(FP)就是第一个参数,8(FP)就是第二个(64位机器)PC:程序指针,负责跳转和流程控制
SB(static base): 静态基指针,全局变量
SB伪寄存器用来表示全局的变量或者函数,比如foo(SB)用来表示foo的地址。加<>表示符号本文件内可见。SP(stack pointer):栈指针,栈顶
用来指向栈帧本地的变量和为函数调用准备参数。它指向本地栈帧的顶部,所以一个对栈帧的引用必须是一个负值且范围在[-framesize:0]之间,例如: x-8(SP),y-4(SP)。 0(SP)表示第一个局部变量
指令解释
func myfun(a int) int { fmt.Println(a) return a + 1 } func main() { r := myfun(2) fmt.Println(r) }
我通过编译生成下面的汇编代码:
"".myfun t=1 size=179 args=0x10 locals=0x50 //定义函数myfun, 栈帧的大小是80字节,16字节的参数和返回值 0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:7) TEXT "".myfun(SB), $80-16 //将Thread Local Storage传给CX 0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:7) MOVQ TLS, CX 0x0009 00009 (D:\Demo\Go\src\zhanghui\main.go:7) MOVQ (CX)(TLS*2), CX //检查当前栈帧额大小是否超过分配的大小 0x0010 00016 (D:\Demo\Go\src\zhanghui\main.go:7) CMPQ SP, 16(CX) //如果超过,就跳转到00169调用runtime.morestack_noctxt 0x0014 00020 (D:\Demo\Go\src\zhanghui\main.go:7) JLS 169 //扩大栈帧,80个字节 0x001a 00026 (D:\Demo\Go\src\zhanghui\main.go:7) SUBQ $80, SP 0x001e 00030 (D:\Demo\Go\src\zhanghui\main.go:7) MOVQ BP, 72(SP) 0x0023 00035 (D:\Demo\Go\src\zhanghui\main.go:7) LEAQ 72(SP), BP //GC标记 0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:7) FUNCDATA $0, gclocals·2589ca35330fc0fce83503f4569854a0(SB) 0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:7) FUNCDATA $1, gclocals·e226d4ae4a7cad8835311c6a4683c14f(SB) //生成空的interface,因为println的参数是Empty Interface 0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ "".a+88(FP), AX 0x002d 00045 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ AX, ""..autotmp_1+48(SP) 0x0032 00050 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ $0, ""..autotmp_0+56(SP) 0x003b 00059 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ $0, ""..autotmp_0+64(SP) 0x0044 00068 (D:\Demo\Go\src\zhanghui\main.go:8) LEAQ type.int(SB), CX 0x004b 00075 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ CX, (SP) 0x004f 00079 (D:\Demo\Go\src\zhanghui\main.go:8) LEAQ ""..autotmp_1+48(SP), CX 0x0054 00084 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ CX, 8(SP) 0x0059 00089 (D:\Demo\Go\src\zhanghui\main.go:8) PCDATA $0, $1 0x0059 00089 (D:\Demo\Go\src\zhanghui\main.go:8) CALL runtime.convT2E(SB) 0x005e 00094 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ 16(SP), AX 0x0063 00099 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ 24(SP), CX 0x0068 00104 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ AX, ""..autotmp_0+56(SP) 0x006d 00109 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ CX, ""..autotmp_0+64(SP) 0x0072 00114 (D:\Demo\Go\src\zhanghui\main.go:8) LEAQ ""..autotmp_0+56(SP), AX 0x0077 00119 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ AX, (SP) 0x007b 00123 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ $1, 8(SP) 0x0084 00132 (D:\Demo\Go\src\zhanghui\main.go:8) MOVQ $1, 16(SP) 0x008d 00141 (D:\Demo\Go\src\zhanghui\main.go:8) PCDATA $0, $1 //调用println 0x008d 00141 (D:\Demo\Go\src\zhanghui\main.go:8) CALL fmt.Println(SB) 0x0092 00146 (D:\Demo\Go\src\zhanghui\main.go:9) MOVQ "".a+88(FP), AX //递增+1 0x0097 00151 (D:\Demo\Go\src\zhanghui\main.go:9) INCQ AX //将结果返回给返回值 0x009a 00154 (D:\Demo\Go\src\zhanghui\main.go:9) MOVQ AX, "".~r1+96(FP) 0x009f 00159 (D:\Demo\Go\src\zhanghui\main.go:9) MOVQ 72(SP), BP 0x00a4 00164 (D:\Demo\Go\src\zhanghui\main.go:9) ADDQ $80, SP 0x00a8 00168 (D:\Demo\Go\src\zhanghui\main.go:9) RET //检查栈帧大小后可能会跳转过来,调用morestack_noctxt 0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:9) NOP 0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:7) PCDATA $0, $-1 0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:7) CALL runtime.morestack_noctxt(SB) 0x00ae 00174 (D:\Demo\Go\src\zhanghui\main.go:7) JMP 0
上面将汇编代码进行了几个简单的描述,下面是main函数的汇编代码,这个里面的就不在详细描述了
"".main t=1 size=179 args=0x0 locals=0x50 0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:12) TEXT "".main(SB), $80-0 0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:12) MOVQ TLS, CX 0x0009 00009 (D:\Demo\Go\src\zhanghui\main.go:12) MOVQ (CX)(TLS*2), CX 0x0010 00016 (D:\Demo\Go\src\zhanghui\main.go:12) CMPQ SP, 16(CX) 0x0014 00020 (D:\Demo\Go\src\zhanghui\main.go:12) JLS 169 0x001a 00026 (D:\Demo\Go\src\zhanghui\main.go:12) SUBQ $80, SP 0x001e 00030 (D:\Demo\Go\src\zhanghui\main.go:12) MOVQ BP, 72(SP) 0x0023 00035 (D:\Demo\Go\src\zhanghui\main.go:12) LEAQ 72(SP), BP 0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:12) FUNCDATA $0, gclocals·69c1753bd5f81501d95132d08af04464(SB) 0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:12) FUNCDATA $1, gclocals·e226d4ae4a7cad8835311c6a4683c14f(SB) 0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:13) MOVQ $2, (SP) 0x0030 00048 (D:\Demo\Go\src\zhanghui\main.go:13) PCDATA $0, $0 //调用myfun函数 0x0030 00048 (D:\Demo\Go\src\zhanghui\main.go:13) CALL "".myfun(SB) 0x0035 00053 (D:\Demo\Go\src\zhanghui\main.go:13) MOVQ 8(SP), AX 0x003a 00058 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ AX, ""..autotmp_6+48(SP) 0x003f 00063 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ $0, ""..autotmp_5+56(SP) 0x0048 00072 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ $0, ""..autotmp_5+64(SP) 0x0051 00081 (D:\Demo\Go\src\zhanghui\main.go:14) LEAQ type.int(SB), AX 0x0058 00088 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ AX, (SP) 0x005c 00092 (D:\Demo\Go\src\zhanghui\main.go:14) LEAQ ""..autotmp_6+48(SP), AX 0x0061 00097 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ AX, 8(SP) 0x0066 00102 (D:\Demo\Go\src\zhanghui\main.go:14) PCDATA $0, $1 //调用convT2E生成Empty Interface 0x0066 00102 (D:\Demo\Go\src\zhanghui\main.go:14) CALL runtime.convT2E(SB) 0x006b 00107 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ 24(SP), AX 0x0070 00112 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ 16(SP), CX 0x0075 00117 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ CX, ""..autotmp_5+56(SP) 0x007a 00122 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ AX, ""..autotmp_5+64(SP) 0x007f 00127 (D:\Demo\Go\src\zhanghui\main.go:14) LEAQ ""..autotmp_5+56(SP), AX 0x0084 00132 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ AX, (SP) 0x0088 00136 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ $1, 8(SP) 0x0091 00145 (D:\Demo\Go\src\zhanghui\main.go:14) MOVQ $1, 16(SP) 0x009a 00154 (D:\Demo\Go\src\zhanghui\main.go:14) PCDATA $0, $1 0x009a 00154 (D:\Demo\Go\src\zhanghui\main.go:14) CALL fmt.Println(SB) 0x009f 00159 (D:\Demo\Go\src\zhanghui\main.go:15) MOVQ 72(SP), BP 0x00a4 00164 (D:\Demo\Go\src\zhanghui\main.go:15) ADDQ $80, SP 0x00a8 00168 (D:\Demo\Go\src\zhanghui\main.go:15) RET 0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:15) NOP 0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:12) PCDATA $0, $-1 0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:12) CALL runtime.morestack_noctxt(SB) 0x00ae 00174 (D:\Demo\Go\src\zhanghui\main.go:12) JMP 0
因为是64位CPU,所以是使用MOVQ,而不是MOVL
指令
TEXT指令声明了符号””.add,指令紧接在类似于函数的主体中。TEXT块的最后必须是某种形式的跳转,通常是一个RET(伪)指令。(如果没有,链接器会追加一个跳转到块自身的指令,TEXT块中没有fallthrough) 符号的后面,参数是标志和栈帧的大小.
比如下面的生成的add方法:TEXT "".add(SB), $0-24 FUNCDATA $0, gclocals·54241e171da8af6ae173d69da0236748(SB) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) MOVQ "".b+16(FP), AX MOVQ "".a+8(FP), CX ADDQ CX, AX MOVQ AX, "".~r2+24(FP) RET
add前面的””表示包名
$0-24表示栈帧的大小,描述了函数有0字节的栈帧,并且需要24字节的参数和返回值的空间MOVQ指令:
MOV指令就是将左侧的内容放到右侧的寄存器或者地址中去LEAQ指令:(Load Effective Address)
LEAQ type.int(SB), AX
这个表示将左侧的变量取地址,然后存放在右边的寄存器中FUNCDATA指令:这个是golang编译器自带的指令,标注做GC的一些变量,用来给GC收集进行提示
FUNCDATA $0, gclocals·54241e171da8af6ae173d69da0236748(SB) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
其中$0,$1用于局部函数调用的参数,需要回收
CALL指令:
CALL runtime.convT2E(SB)
这个表示函数调用,将调用runtime包下面convT2E函数
Go中调用汇编
在Go的源码中有很多*.s的文件,这些就是汇编代码文件
举一个简单的例子
创建add.go文件:
package add func Add(a, b int) int
创建main.go文件
package main import ( "fmt" "add" ) func main() { fmt.Println(add.Add(10, 9)) }
然后在add.go的的同名目录下创建add_amd64.s(64位系统)
TEXT ·Add(SB), $0-24 MOVQ b+8(FP), AX MOVQ a+0(FP), CX ADDQ CX, AX MOVQ AX, res+16(FP) RET
然后执行main.go,就可以发现正常调用汇编实现的Add方法了
总结
暂时只研究这么多了,我觉得大部分情况下是不用深入到汇编层面的,尤其是现在的这些高级语言,但是在定位一些语言运行机制问题,可以辅助汇编信息,查看具体指令流程,更深入了解语言的底层实现,是有好处的。
前期连接
【Free Style】深入Go语言模型(1):interface的底层详解
https://portal.huaweicloud.com/blogs/a6653d83c4f211e7b8317ca23e93a891
- 点赞
- 收藏
- 关注作者
评论(0)