前言:
问:什么是plan9?
答:plan9是一个很强的操作系统,但我们只需要学习它的汇编语法。
问:为什么说golang开发者需要学习plan9汇编?
答:因为golang的开发团队和bell实验室(开发了Unix的那个实验室)开发plan9操作系统的钢铁糙汉子开发团队是同一批人,他们非要用,咱也没办法。
问:反编译之后玩Intel和AT&T不香吗?
答:确实可以跳过plan9汇编(比如直接拿机器码反编译出intel汇编来看),但是会让阅读变得非常困难。并且在golang的基础方法中,使用了大量plan9汇编,其中包含了一些如4个伪寄存器等plan9特有的语法,能让人更容易读懂代码,少绕弯路。(再说汇编都大同小异,秒学完好伐!)
问:学plan9汇编有什么好处?
答:学习plan9能让你在golang开发者中脱颖而出,随时随地掏出大汇编对bug进行降维打击,成为同事们心中的偶像,获得plmm以及sqgg的芳心,从此走上人生巅峰。
一.plan9简介
1.Plan-9是一款神奇的新版Unix,几乎是由70年代当初开发Unix系统的同一个团队开发的。
2.目的就是要最终解决Unix最初的诺言:一切皆为文件(先进的9P虚拟文件系统协议最终让所有东西都成为了文件。目录变成了“命名空间”,资源被映射成了文件。)
(你可以通过对/proc目录(现在应该成其为一个命名空间)里的一个文件使用“cat”命令来查看进程的情况。同样,打开一个网络连接的方式变成了打开/net/tcp目录里的一个文件。”iotcl”系统调用在这个系统里完全被根除了,因为基于操作系统上的现代文件形式中的这种怪胎已经不再需要了。)
3.Plan-9实际上没有解决任何问题,并且开发者们不屑于商业化,暂时不打算与Unix兼容。这也是为什么plan9操作系统按理说比Unix强但是却没有推广起来的原因。
二.plan9语法的一些特点
1.没有 push 和 pop,栈的调整是通过对硬件 SP 寄存器进行运算来实现的
2.常数在 plan9 汇编用 $num 表示,可以为负数,默认情况下为十进制。
3.操作数方向与intel相反,与AT&T类似
SUBQ $24, SP // 对 SP 做减法,为函数分配函数栈24字节大小的帧 (因为栈是从高地址向低地址增长的)
...
中间的一堆代码
...
ADDQ $24, SP // 对 SP 做加法,清除函数栈帧
4.数据搬运的长度由 MOV 的后缀决定
// plan9
MOVB $1, DI // 1 byte
MOVW $0x10, BX // 2 bytes
MOVD $1, DX // 4 bytes
MOVQ $-10, AX // 8 bytes
// intel
mov rax, 0x1 // 8 bytes
mov eax, 0x100 // 4 bytes
mov ax, 0x22 // 2 bytes
mov ah, 0x33 // 1 byte
mov al, 0x44 // 1 byte
5.为了简化汇编代码的编写,引入了4个伪寄存器。(其实就是Go汇编语言对CPU的重新抽象)
FP
: Frame pointer: arguments and locals.PC
: Program counter: jumps and branches.SB
: Static base pointer: global symbols.SP
: Stack pointer: top of stack.
四个伪寄存器和X86/AMD64的内存和寄存器的相互关系如下图:
在AMD64环境,伪PC寄存器其实是IP指令计数器寄存器的别名。伪FP寄存器对应的是函数的帧指针,用来访问函数的参数和返回值。伪SP栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),用于定位局部变量。伪SP是一个比较特殊的寄存器,因为还存在一个同名的SP真寄存器。真SP寄存器对应的是栈的顶部,用于定位调用其它函数的参数和返回值。
当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)
、+8(SP)
没有标识符前缀为真SP寄存器,而a(SP)
、b+8(SP)
有标识符为前缀表示伪寄存器。
6.被调用函数的入参与出参都在调用函数的栈帧中。