寄存器
学过 X86 汇编的同学都知道汇编有AX,BX等寄存器,除此之外,Go 还添加了 PC、FP、SP、SB四个伪寄存器。如下图所示,其中第二列为 GO 添加的4 个伪寄存器,第三列为 X86 寄存器。
看到这里,尘封已久的汇编语言知识需要拿出来复习一下。
- FLAGS 是状态寄存器。
- IP 是指令寄存器。
- AX、BX、CX、DX、SI、DI、BP、SP 是通用寄存器。在X86-64中又增加了八个以
R8-R15
方式命名的通用寄存器。
另外 GO 的 4 个伪寄存器作用如下:
FP: Frame pointer
:伪FP寄存器对应函数的栈帧指针,一般用来访问函数的参数和返回值;golang语言中,函数的参数和返回值,函数中的局部变量,函数中调用子函数的参数和返回值都是存储在栈中的,我们把这一段栈内存称为栈帧(frame),伪FP寄存器对应栈帧的底部,但是伪FP只包括函数的参数和返回值这部分内存,其他部分由伪SP寄存器表示;注意golang中函数的返回值也是通过栈帧返回的,这也是golang函数可以有多个返回值的原因;PC: Program counter
:指令计数器,用于分支和跳转,它是汇编的IP寄存器的别名;SB: Static base pointer
:一般用于声明函数或者全局变量,对应代码区(text)内存段底部;SP: Stack pointer
:指向当前栈帧的局部变量的开始位置,一般用来引用函数的局部变量,这里需要注意汇编中也有一个SP寄存器,它们的区别是:1.伪SP寄存器指向栈帧(不包括函数参数和返回值部分)的底部,真SP寄存器对应栈的顶部;所以伪SP寄存器一般用于寻址函数局部变量,真SP寄存器一般用于调用子函数时,寻址子函数的参数和返回值(后面会有具体示例演示);2.当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真SP寄存器,而a(SP)、b+8(SP)有标识符为前缀表示伪寄存器;
Symbols 符号
有些符号比如 R1、LR 是不同架构预定义的寄存器。除此之外,还有 GO 定义的 4 个伪寄存器。
FP
: Frame pointer: arguments and locals.PC
: Program counter: jumps and branches.SB
: Static base pointer: global symbols.SP
: Stack pointer: the highest address within the local stack frame.
所有用户定义的符号都可以写成 FP 或者 SB + offset 的形式。
SB 可以被认为是内存地址,所以 foo(SB)
是 foo 在内存中的地址。 foo+4(SB)
表示 foo 地址后的 4 个字节。
FP 是 virtual frame pointer,用来指示函数参数。因此,0(FP)
表示函数的第一个参数,8(FP)
表示函数的第二个参数 (on a 64-bit machine),通常,还会在前面加上参数的名字 比如 first_arg+0(FP)
and second_arg+8(FP)
。与直接用 SB 不同的是,SB 是针对符号的偏移量,FP 是针对 frame 的偏移量。
SP 是 virtual stack pointer, 指向每个 frame 本地的变量,以及准备向函数传递的参数。他始终指向 local frame 的最高地址,因此都是带 “-” 号的,比如 x-8(SP)
y-4(SP)
。
有些架构上本身就有 SP 和 PC 物理寄存器,Go assembler 仍然按照伪寄存器对待 SP 和 PC ,而把物理寄存器 SP 和 PC 用 R前缀表示,比如 R13, R15 。
所有的指令,寄存器,汇编指令都用大写表示。
Go Assembler 不支持点号和 slash,因此用 period 和 division slash 表示,比如 fmt.Printf
or math/rand.Int
会写成 fmt·Printf
and math∕rand·Int
。
Directives 指令
常量
$1 // 十进制
$0xf4f8fcff // 十六进制
$1.5 // 浮点数
$'a' // 字符
$"abcd" // 字符串
TEXT
定义函数符号
TEXT symbol(SB), [flags,] $framesize[-argsize]
下面是定义一个函数的例子
TEXT runtime·profileloop(SB),NOSPLIT,$8
MOVQ $runtime·profileloop1(SB), CX
MOVQ CX, 0(SP)
CALL runtime·externalthreadhandler(SB)
RET
TEXT 指令后面是函数符号,后面是参数 和一个常量的 frame size 。 通常,frame size 后面还有减号加数字,注意,这里的减号只是个标记不是减的意思,比如 frame size $24-8
表示这个函数有 24 字节的 frame,并且有 8 字节的参数,这些参数在 caller 的 frame 上。
NOSPLIT
表示可以省略这个减号+参数,如果没有 NOSPLIT
则减号+参数必须写上。
DATA
初始化包变量
DATA symbol+offset(SB)/width, value
其中symbol
为变量在汇编语言中对应的标识符,offset
是符号开始地址的偏移量,width
是要初始化内存的宽度大小,value
是要初始化的值。
GLOBL
将符号导出
例如将全局变量导出
GLOBL symbol(SB), width
其中symbol
对应汇编中符号的名字,width
为符号对应内存的大小.
例子1:
结合DATA
和GLOBL
指令,我们就可以初始化并导出一个全局变量:
GLOBL ·Id, $8
DATA ·Id+0(SB)/1,$0x37
DATA ·Id+1(SB)/1,$0x25
DATA ·Id+2(SB)/1,$0x00
DATA ·Id+3(SB)/1,$0x00
DATA ·Id+4(SB)/1,$0x00
DATA ·Id+5(SB)/1,$0x00
DATA ·Id+6(SB)/1,$0x00
DATA ·Id+7(SB)/1,$0x00
例子2:
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
- 声明了一个只读的 64 字节 table,每个元素是 4 字节的整数。
runtime·tlsoffset
是一个3字节的空指针。
NOPTR
这样的符号定义在 textflag.h
文件中。
NOPROF = 1
(For TEXT items.) Don't profile the marked function. This flag is deprecated.
DUPOK = 2
It is legal to have multiple instances of this symbol in a single binary. The linker will choose one of the duplicates to use.
NOSPLIT = 4
(For TEXT items.) Don't insert the preamble to check if the stack must be split. The frame for the routine, plus anything it calls, must fit in the spare space remaining in the current stack segment. Used to protect routines such as the stack splitting code itself.
RODATA = 8
(For DATA and GLOBL items.) Put this data in a read-only section.
NOPTR = 16
(For DATA and GLOBL items.) This data contains no pointers and therefore does not need to be scanned by the garbage collector.
WRAPPER = 32
(For TEXT items.) This is a wrapper function and should not count as disabling recover.
NEEDCTXT = 64
(For TEXT items.) This function is a closure so it uses its incoming context register.
LOCAL = 128
This symbol is local to the dynamic shared object.
TLSBSS = 256
(For DATA and GLOBL items.) Put this data in thread local storage.
NOFRAME = 512
(For TEXT items.) Do not insert instructions to allocate a stack frame and save/restore the return address, even if this is not a leaf function. Only valid on functions that declare a frame size of 0.
TOPFRAME = 2048
(For TEXT items.) Function is the outermost frame of the call stack. Traceback should stop at this function.
其他常用命令
SUBQ $0x18, SP // 分配函数栈,操作数 8 个字节
ADDQ $0x18, SP // 清除函数栈,操作数据 8 个字节
MOVB $1, DI // 拷贝 1个字节
MOVW $0x10, BX // 拷贝 2 个字节
MOVD $1, DX // 拷贝 4 个字节
MOVQ $-10, AX // 拷贝 8 个字节
ADDQ AX, BX // BX = BX + AX 存 BX
SUBQ AX, BX // BX = BX - AX 存 BX
IMULQ AX, BX // BX = BX * AX 存 BX
MOVQ AX, BX // BX = AX 将 AX 中的值赋给 BX
MOVQ (AX), BX // BX = *AX 加载 AX 中指向内存地址的值给 BX
MOVQ 16(AX), BX // BX = *(AX + 16) 偏移 16 个字节后地址中的值