在阅读 Golang 源代码时,总是被其中的汇编代码卡住,读起来不流畅。今天来简要了解下 Golang 中的汇编语言。

汇编分类

按指令集架构分类(针对 CPU)

i386x86AMD64Intel64x86-64x64

按汇编格式分类(针对人的阅读习惯)

  1. Intel 格式
  2. 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

参考