# 1.4 Plan 9 汇编语言
本节我们快速介绍 Go 语言使用的 Plan 9 汇编,以方便在后续章节中能够流畅的阅读 Go 源码中关于汇编的部分。
对于一段 Go 程序,我们可以通过下面的命令来获得编译后的汇编代码:
```
go build -gcflags "-N -l" -ldflags=-compressdwarf=false -o main.out main.go
go tool objdump -s "main.main" main.out > main.S
# or
go tool compile -S main.go
# or
go build -gcflags -S main.go
```
FUNCDATA 和 PCDATA 指令包含了由垃圾回收器使用的信息,他们由编译器引入。
## 常量
## 符号
## 指令
全局数据符号由以 DATA 指令开头的序列 全局数据符号由一系列以 DATA 指令起始和一个 GLOBL 指令定义。 每个 DATA 指令初始化相应内存的一部分。未明确初始化的内存为零。 该 DATA 指令的一般形式是
~~~
DATA symbol+offset(SB)/width, value
~~~
在给定的 offset 和 width 处初始化该符号的内存为 value。 DATA 必须使用增加的偏移量来写入给定符号的指令。
该 GLOBL 指令声明符号是全局的。参数是可选标志,并且数据的大小被声明为全局, 除非 DATA 指令已初始化,否则初始值将全为零。该 GLOBL 指令必须遵循任何相应的 DATA 指令。
例如:
~~~
DATA divtab<>+0x00(SB)/4, $0xf4f8fcff
DATA divtab<>+0x04(SB)/4, $0xe6eaedf0
...
DATA divtab<>+0x3c(SB)/4, $0x81828384
GLOBL divtab<>(SB), RODATA, $64
GLOBL runtime·tlsoffset(SB), NOPTR, $4
~~~
| 指令 | 操作符 | 解释 |
| :-- | :-- | :-- |
| JMP | | |
| MOVL | | |
| MOVQ | | |
| MOVEQ | | |
| LEAQ | | |
| SUBQ | | |
| ANDQ | | |
| CALL | | |
| PUSHQ | | |
| POPQ | | |
| CLD | | |
| CMPQ | | |
| CPUID | | |
| JEQ | | |
## 运行时协调
为保证垃圾回收正确运行,在大多数栈帧中,运行时必须知道所有全局数据的指针。 Go 编译器会将这部分信息耦合到 Go 源码文件中,但汇编程序必须进行显式定义。
被标记为`NOPTR`标志的数据符号会视为不包含指向运行时分配数据的指针。 带有`R0DATA`标志的数据符号在只读存储器中分配,因此被隐式标记为`NOPTR`。 总大小小于指针的数据符号也被视为隐式标记`NOPTR`。 在一份汇编源文件中是无法定义包含指针的符号的,因此这种符号必须定义在 Go 原文件中。 一个良好的经验法则是`R0DATA`在 Go 中定义所有非符号而不是在汇编中定义。
每个函数还需要注释,在其参数,结果和本地堆栈框架中给出实时指针的位置。 对于没有指针结果且没有本地堆栈帧或没有函数调用的汇编函数, 唯一的要求是在同一个包中的 Go 源文件中为函数定义 Go 原型。 汇编函数的名称不能包含包名称组件 (例如,`syscall`包中的函数`Syscall`应使用名称`·Syscall`而不是`syscall·Syscall`其TEXT指令中的等效名称)。 对于更复杂的情况,需要显式注释。 这些注释使用标准`#include`文件中定义的伪指令`funcdata.h`。
如果函数没有参数且没有结果,则可以省略指针信息。这是由一个参数大小`$n-0`注释指示`TEXT`对指令。 否则,指针信息必须由Go源文件中的函数的Go原型提供,即使对于未直接从Go调用的汇编函数也是如此。 (原型也将`go vet`检查参数引用。)在函数的开头,假定参数被初始化但结果假定未初始化。 如果结果将在调用指令期间保存实时指针,则该函数应首先将结果归零, 然后执行伪指令`GO_RESULTS_INITIALIZED`。 此指令记录结果现在已初始化,应在堆栈移动和垃圾回收期间进行扫描。 通常更容易安排汇编函数不返回指针或不包含调用指令; 标准库中没有汇编函数使用`GO_RESULTS_INITIALIZED`。
如果函数没有本地堆栈帧,则可以省略指针信息。这由`TEXT`指令上的本地帧大小`$0-n`注释表示。如果函数不包含调用指令,也可以省略指针信息。否则,本地堆栈帧不能包含指针,并且汇编必须通过执行伪指令`TEXTNO_LOCAL_POINTERS`来确认这一事实。因为通过移动堆栈来实现堆栈大小调整,所以堆栈指针可能在任何函数调用期间发生变化:甚至指向堆栈数据的指针也不能保存在局部变量中。
汇编函数应始终给出 Go 原型,既可以提供参数和结果的指针信息,也可以`go vet`检查用于访问它们的偏移量是否正确。
## 寄存器
### 通用寄存器
Plan 9 中的通用寄存器包括:
AX BX CX DX DI SI BP SP R8 R9 R10 R11 R12 R13 R14 PC
### 伪寄存器
伪寄存器不是真正的寄存器,而是由工具链维护的虚拟寄存器,例如帧指针。
FP, Frame Pointer:帧指针,参数和本地 PC, Program Counter: 程序计数器,跳转和分支 SB, Static Base: 静态基指针, 全局符号 SP, Stack Pointer: 当前栈帧开始的地方
所有用户定义的符号都作为偏移量写入伪寄存器 FP 和 SB。
## 寻址模式
汇编语言的一个很重要的概念就是它的寻址模式,Plan 9 汇编也不例外,它支持如下寻址模式:
~~~
R0 数据寄存器
A0 地址寄存器
F0 浮点寄存器
CAAR, CACR, 等 特殊名字
$con 常量
$fcon 浮点数常量
name+o(SB) 外部符号
name<>+o(SB) 局部符号
name+o(SP) 自动符号
name+o(FP) 实际参数
$name+o(SB) 外部地址
$name<>+o(SB) 局部地址
(A0)+ 间接后增量
-(A0) 间接前增量
o(A0)
o()(R0.s)
symbol+offset(SP) 引用函数的局部变量,offset 的合法取值是 [-framesize, 0)
局部变量都是 8 字节,那么第一个局部变量就可以用 localvar0-8(SP) 来表示
如果是 symbol+offset(SP) 形式,则表示伪寄存器 SP
如果是 offset(SP) 则表示硬件寄存器 SP
~~~
```
TEXT pkgname·funcname(SB),NOSPLIT,$-8
JMP _rt0_amd64(SB)
```
## 进一步阅读的参考文献
* [A Quick Guide to Go's Assembler](https://golang.org/doc/asm)
* [Rob Pike, How to Use the Plan 9 C Compiler](http://doc.cat-v.org/plan_9/2nd_edition/papers/comp)
* [Rob Pike, A Manual for the Plan 9 assembler](https://9p.io/sys/doc/asm.html)
* [Debugging Go Code with GDB](https://golang.org/doc/gdb)
- 第一部分 :基础篇
- 第1章 Go语言的前世今生
- 1.2 Go语言综述
- 1.3 顺序进程通讯
- 1.4 Plan9汇编语言
- 第2章 程序生命周期
- 2.1 从go命令谈起
- 2.2 Go程序编译流程
- 2.3 Go 程序启动引导
- 2.4 主Goroutine的生与死
- 第3 章 语言核心
- 3.1 数组.切片与字符串
- 3.2 散列表
- 3.3 函数调用
- 3.4 延迟语句
- 3.5 恐慌与恢复内建函数
- 3.6 通信原语
- 3.7 接口
- 3.8 运行时类型系统
- 3.9 类型别名
- 3.10 进一步阅读的参考文献
- 第4章 错误
- 4.1 问题的演化
- 4.2 错误值检查
- 4.3 错误格式与上下文
- 4.4 错误语义
- 4.5 错误处理的未来
- 4.6 进一步阅读的参考文献
- 第5章 同步模式
- 5.1 共享内存式同步模式
- 5.2 互斥锁
- 5.3 原子操作
- 5.4 条件变量
- 5.5 同步组
- 5.6 缓存池
- 5.7 并发安全散列表
- 5.8 上下文
- 5.9 内存一致模型
- 5.10 进一步阅读的文献参考
- 第二部分 运行时篇
- 第6章 并发调度
- 6.1 随机调度的基本概念
- 6.2 工作窃取式调度
- 6.3 MPG模型与并发调度单
- 6.4 调度循环
- 6.5 线程管理
- 6.6 信号处理机制
- 6.7 执行栈管理
- 6.8 协作与抢占
- 6.9 系统监控
- 6.10 网络轮询器
- 6.11 计时器
- 6.12 非均匀访存下的调度模型
- 6.13 进一步阅读的参考文献
- 第7章 内存分配
- 7.1 设计原则
- 7.2 组件
- 7.3 初始化
- 7.4 大对象分配
- 7.5 小对象分配
- 7.6 微对象分配
- 7.7 页分配器
- 7.8 内存统计
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 写屏幕技术
- 8.3 调步模型与强弱触发边界
- 8.4 扫描标记与标记辅助
- 8.5 免清扫式位图技术
- 8.6 前进保障与终止检测
- 8.7 安全点分析
- 8.8 分代假设与代际回收
- 8.9 请求假设与实务制导回收
- 8.10 终结器
- 8.11 过去,现在与未来
- 8.12 垃圾回收统一理论
- 8.13 进一步阅读的参考文献
- 第三部分 工具链篇
- 第9章 代码分析
- 9.1 死锁检测
- 9.2 竞争检测
- 9.3 性能追踪
- 9.4 代码测试
- 9.5 基准测试
- 9.6 运行时统计量
- 9.7 语言服务协议
- 第10章 依赖管理
- 10.1 依赖管理的难点
- 10.2 语义化版本管理
- 10.3 最小版本选择算法
- 10.4 Vgo 与dep之争
- 第12章 泛型
- 12.1 泛型设计的演进
- 12.2 基于合约的泛型
- 12.3 类型检查技术
- 12.4 泛型的未来
- 12.5 进一步阅读的的参考文献
- 第13章 编译技术
- 13.1 词法与文法
- 13.2 中间表示
- 13.3 优化器
- 13.4 指针检查器
- 13.5 逃逸分析
- 13.6 自举
- 13.7 链接器
- 13.8 汇编器
- 13.9 调用规约
- 13.10 cgo与系统调用
- 结束语: Go去向何方?