本文将简单介绍一下go语言的汇编。
主要的内容如下:
- plan 9 是什么?为什么学习plan9?
- plan9 的常见指令
- go程序如何转换为plan9 ?
下面就开始简单介绍一下。
plan 9 是什么?为什么学习plan9?
plan9, Go一套自己的汇编。按照官方文档的说法,其设计初衷是解决跨平台的问题,但是没有做好。并且它不同于传统的汇编,也就是说要想学习go汇编,你需要重新学习一套语法。社区在争论这个问题,有人说是因为go的几个大佬,原来是用plan9的。这个问题咱们这儿不讨论。
那作为普通人,我们学汇编干啥呢?
装逼!这是第一生产力!我一直有一个不成熟的想法,希望自己能够像机器一样看穿代码的运行。有时想想,也挺可笑的。
除了装逼,真的一点用处都没有了吗?
当然不是!!!
说下我能想到的几个点:
- 确定一段代码底层执行了什么函数
- 查看基础的数据结构的运行机制
- 查看内存的分配
- 查看go在函数头和函数尾插入的相关的调度
...
难道这几条还不够吗?
plan9 的常见指令
其实汇编跟java, go等语言 没啥区别,无非也是变量、方法等。只是我们做应用层开发不常用而已。如果懂了基础的语法,其实也就这样。下面列几个常用的指令,看不懂也没事,多看几遍就知道了,其实我也不是很熟,啥时候真用到,再回来看也行。
栈扩大、缩小
plan9 中栈操作并没有使用push,pop,而是采用sub 跟add SP。
SUBQ $0x18, SP // 对 SP 做减法,为函数分配函数栈帧
ADDQ $0x18, SP // 对 SP 做加法,清除函数栈帧
数据copy
MOVB $1, DI // 1 byte
MOVW $0x10, BX // 2 bytes
MOVD $1, DX // 4 bytes
MOVQ $-10, AX // 8 bytes
计算指令
ADDQ AX, BX // BX += AX
SUBQ AX, BX // BX -= AX
IMULQ AX, BX // BX *= AX
跳转
// 无条件跳转
JMP addr // 跳转到地址,地址可为代码中的地址,不过实际上手写不会出现这种东西
JMP label // 跳转到标签,可以跳转到同一函数内的标签位置
JMP 2(PC) // 以当前指令为基础,向前/后跳转 x 行
JMP -2(PC) // 同上
// 有条件跳转
JNZ target // 如果 zero flag 被 set 过,则跳转
变量声明
在汇编里所谓的变量,一般是存储在 .rodata 或者 .data 段中的只读值。对应到应用层的话,就是已初始化过的全局的 const、var、static 变量/常量。
DATA symbol+offset(SB)/width, value
使用 DATA 结合 GLOBL 来定义一个变量
GLOBL 必须跟在 DATA 指令之后:
DATA age+0x00(SB)/4, $18 // forever 18
GLOBL age(SB), RODATA, $4
DATA pi+0(SB)/8, $3.1415926
GLOBL pi(SB), RODATA, $8
DATA birthYear+0(SB)/4, $1988
GLOBL birthYear(SB), RODATA, $4
函数声明
先看一个定义:
// func add(a, b int) int
// => 该声明定义在同一个 package 下的任意 .go 文件中
// => 只有函数头,没有实现
TEXT pkgname·add(SB), NOSPLIT, $0-8
MOVQ a+0(FP), AX
MOVQ a+8(FP), BX
ADDQ AX, BX
MOVQ BX, ret+16(FP)
RET
代码存储在TEXT段中。
pkgname 可以省略。
比如你的方法是 runtime·main,在编译之后的程序里的符号则是 runtime.main。
参数及返回值大小
|
TEXT pkgname·add(SB),NOSPLIT,$32-32
| | |
包名 函数名 栈帧大小(局部变量+可能需要的额外调用函数的参数空间的总大小,但不包括调用其它函数时的 ret address 的大小)
寄存器
有4个核心的伪寄存器,这4个寄存器是编译器用来维护上下文、特殊标识等作用的:
FP(Frame pointer): arguments and locals
PC(Program counter): jumps and branches
SB(Static base pointer): global symbols
SP(Stack pointer): top of stack
还有很多其他的用法,就先不摘抄了,后面用到再去查吧。
go程序如何转换为plan9 ?
// 编译
go build -gcflags="-S"
go tool compile -S hello.go
go tool compile -N -S hello.go // 禁止优化
// 反编译
go tool objdump
总结与后记
本文简单的讲述了plan9 是什么?什么用途? 也罗列了几个plan9 常见的指令,增加自信。最后简单的几行指令,教你如何看某一段代码的plan9 指令。
如果时间允许,会结合前人的文章,针对go中常见的类型、api操作、内存分配、channel 等进行汇编指令级的学习,给自己加油~
参考文献
1、汇编 is so easy
2、Go 系列文章3 :plan9 汇编入门
3、golang内核系列--深入理解plan9汇编&实践