在阅读 Golang 源代码时,总是被其中的汇编代码卡住,读起来不流畅。今天来简要了解下 Golang 中的汇编语言。
汇编分类
按指令集架构分类(针对 CPU)
i386x86AMD64Intel64x86-64x64
按汇编格式分类(针对人的阅读习惯)
- Intel 格式
- AT&T 格式
平时我们说 golang 中汇编属于 plan9 风格,是按第二种方式分类的,其阅读风格(符号)与 Intel 与 AT&T 都有不同。plan9 汇编作者是 unix 操作系统的同一批人,bell 实验室所开发的。
Go汇编语言是基于 plan9 汇编,但是现实世界还有这么多不同架构的 CPU 在这。所以 golang 汇编在 plan9 风格下,同一个方法还有不同指令集架构的多种实现。
在哪能看到 Golang 汇编代码
src/runtime/asm_amd64.ssrc/math/big/go tool compile -S main.goamd64
Go 汇编基础语法
1. 寄存器
通用寄存器
寄存器与物理机架构相关, 不同的架构有不同的物理寄存器。
amd64
plan9 汇编语言提供了如下映射,在汇编语言中直接引用就可使用物理寄存器了。
| 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 |
SPAXR14BP
虚拟寄存器
Go 汇编引入了 4 个 虚拟寄存器
FPPCamd64SBSP
用法:
0(FP)8(FP)first_arg+0(FP)localvar0-8(SP)symbol+offset(SP)offset(SP)8(SP)foo(SB)foo+4(SB)<>
从网上偷的图:
2. 指令
1. 变量声明
DATAGLOBL
表示意义:
symboloffsetoffset + widthvaluesizesymbolRODATA<>GLOBL bio<>(SB), RODATA, $16
实际例子:
NOPTR
offset0(FP)localvar0-8(SP)
2. 函数声明
格式:
TEXT pkgname·funname(SB),flag,$framesize-argsize
表示意义:
pkgname
funname
flagNOSPLITNOSPLIT
framesize
argsize
- GO 1.7 之前 参数+返回值都存在栈帧中
- GO 1.7 更新后, 优先使用 9 个 通用寄存器传递参数与返回值,超出部分再存在栈中。并且寄存器中返回值会覆盖参数中的值
- 参数 + 返回值少于 9 个,argsize 值是参数的大小
- 返回值 > 9 个,argsize = 参数大小 + 返回值超出 9 个的部分
实际例子:
上述例子:函数内不需存放局部变量,framesize = 0, 两个 int64 的参数,argsize = 16
3. 常见操作指令
下面介绍下常见的
1. 数据搬运
MOV$NUM
LEA
2. 计算指令
ADDSUBIMULQ
SPSP
3. 条件跳转/无条件跳转
JMPJZJLS
4. 其它
CMP
go CMPQ BX, $0 // 比较与 BX 与 0 的大小 JNE 3(PC) // 左边小于右边则执行跳到当前 PC 指令后第三条指令的位置
ANDORXOR
总结
经过这篇文章,相信你已经能大致读懂一些简单的汇编程序了。这里推荐几个源代码的汇编阅读。
src/runtime/asm_amd64.srt0_go(SB)src/runtime/internal/atomic_amd64.sCase