基本指令

栈调整

intel 或 AT&T 汇编提供了 push 和 pop 指令族,plan9 中没有 push 和 pop,栈的调整是通过对硬件 SP 寄存器进行运算来实现的,例如:
SUBQ $0x18, SP // 对 SP 做减法,为函数分配函数栈帧 ... // 省略无用代码 ADDQ $0x18, SP // 对 SP 做加法,清除函数栈帧

数据搬运

常数在 plan9 汇编用 $num 表示,可以为负数,默认情况下为十进制。可以用 $0x123 的形式来表示十六进制数。
MOVB $1, DI // 1 byte MOVW $0x10, BX // 2 bytes MOVD $1, DX // 4 bytes MOVQ $-10, AX // 8 bytes
plan9 的汇编的操作数的方向是和 intel 汇编相反的,与 AT&T 类似。
MOVQ $0x10, AX ===== mov rax, 0x10 | |------------| | |------------------------|

常见计算指令

ADDQ AX, BX // BX += AX SUBQ AX, BX // BX -= AX IMULQ AX, BX // BX *= AX
类似数据搬运指令,同样可以通过修改指令的后缀来对应不同长度的操作数。例如 ADDQ/ADDW/ADDL/ADDB。

条件跳转/无条件跳转

// 无条件跳转 JMP addr // 跳转到地址,地址可为代码中的地址,不过实际上手写不会出现这种东西 JMP label // 跳转到标签,可以跳转到同一函数内的标签位置 JMP 2(PC) // 以当前指令为基础,向前/后跳转 x 行 JMP -2(PC) // 同上 // 有条件跳转 JNZ target // 如果 zero flag 被 set 过,则跳转

寄存器

amd64 的通用寄存器:
(lldb) reg read General Purpose Registers: rax = 0x0000000000000005 rbx = 0x000000c420088000 rcx = 0x0000000000000000 rdx = 0x0000000000000000 rdi = 0x000000c420088008 rsi = 0x0000000000000000 rbp = 0x000000c420047f78 rsp = 0x000000c420047ed8 r8 = 0x0000000000000004 r9 = 0x0000000000000000 r10 = 0x000000c420020001 r11 = 0x0000000000000202 r12 = 0x0000000000000000 r13 = 0x00000000000000f1 r14 = 0x0000000000000011 r15 = 0x0000000000000001 rip = 0x000000000108ef85 int`main.main + 213 at int.go:19 rflags = 0x0000000000000212 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
下面是通用通用寄存器的名字在 IA64 和 plan9 中的对应关系:
 
寄存器名字对应关系
IA64
RAX
RBX
RCX
RDX
RDI
RSI
RBP
RSP
R8
R9
R10
R11
R12
R13
R14
RIP
AX
BX
CX
DX
DI
SI
BP
SP
R8
R9
R10
R11
R12
R13
R14
PC

伪寄存器

FP: Frame pointer: arguments and locals. PC: Program counter: jumps and branches. SB: Static base pointer: global symbols. SP: Stack pointer: top of stack.
  1. 伪 SP 和硬件 SP 不是一回事,在手写代码时,伪 SP 和硬件 SP 的区分方法是看该 SP 前是否有 symbol。如果有 symbol,那么即为伪寄存器,如果没有,那么说明是硬件 SP 寄存器。
  1. SP 和 FP 的相对位置是会变的,所以不应该尝试用伪 SP 寄存器去找那些用 FP + offset 来引用的值,例如函数的入参和返回值。
  1. 官方文档中说的伪 SP 指向 stack 的 top,是有问题的。其指向的局部变量位置实际上是整个栈的栈底(除 caller BP 之外),所以说 bottom 更合适一些。
  1. 在 go tool objdump/go tool compile -S 输出的代码中,是没有伪 SP 和 FP 寄存器的,我们上面说的区分伪 SP 和硬件 SP 寄存器的方法,对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中,只有真实的 SP 寄存器。
  1. FP 和 Go 的官方源代码里的 framepointer 不是一回事,源代码里的 framepointer 指的是 caller BP 寄存器的值,在这里和 caller 的伪 SP 是值是相等的。

变量声明

在汇编里所谓的变量,一般是存储在 .rodata 或者 .data 段中的只读值。对应到应用层的话,就是已初始化过的全局的 const、var、static 变量/常量。
使用 DATA 结合 GLOBL 来定义一个变量。DATA 的用法为:
DATA symbol+offset(SB)/width, value
大多数参数都是字面意思,不过这个 offset 需要稍微注意。其含义是该值相对于符号 symbol 的偏移,而不是相对于全局某个地址的偏移。
使用 GLOBL 指令将变量声明为 global,额外接收两个参数,一个是 flag,另一个是变量的总大小。
GLOBL divtab(SB), RODATA, $64
badge