Go随笔 | 汇编指令
Go的编译器先将源代码编译成Plan9风格的汇编指令,之后再通过汇编器和链接器生成不同平台的可执行程序。再深入研究源代码执行表现时,经常需要观察汇编指令,了解基本的Plan9汇编语法不可或缺。
CPU寄存器
32位x86架构的CPU有8个32位的通用寄存器(EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI),在汇编语言中可以通过名称直接引用这8个寄存器。
有些通用寄存器是有特殊用途的:
(1)EAX寄存器会被乘法和除法指令自动使用,通常称为扩展累加寄存器。
(2)ECX被LOOP系列指令用作循环计数器,但是多数上层语言不会使用LOOP指令,一般通过条件跳转系列指令实现。
(3)ESP用来寻址栈上的数据,很少用于普通算数或数据传输,通常称为扩展栈指针寄存器。
(4)ESI和EDI被高速内存传输指令分别用来指向源地址和目的地址,被称为扩展源索引寄存器和扩展目标索引寄存器。
(5)EBP在高级语言中被用来引用栈上的函数参数和局部变量,一般不用于普通算数或数据传输,称为扩展帧指针寄存器。
除了这些通用寄存器之外,还有一个标志寄存器EFLAGS比较重要。汇编语言中用于比较的CMP和TEST会修改标志寄存器里的相关标志,再结合条件跳转系列指令,就能实现上层语言中的大部分流程控制语句。最后还有一个很重要而且很特殊的寄存器,即指令指针寄存器EIP。指令指针寄存器中存储的是下一条将要被执行的指令的地址,而且汇编语言中不能通过名称直接引用EIP,只能通过跳转、CALL和RET等指令间接地修改EIP的值。
64位架构把通用寄存器的个数扩展到16个,之前的8个通用寄存器也被扩展成了64位,每个寄存器的低8位、16位、32位都可以单独使用。
64位架构下16个通用寄存器的结构设计
指令指针EIP被扩展为64位的RIP,但依然不能在代码中直接引用。标志寄存器EFLAGS被扩展为64位的RFLAGS,里面的标志位保持向前兼容。内存地址也扩展到了64位,实际上目前的硬件只使用了低48位,这已经能物理寻址256TB内存,很难有服务器有这么多物理内存。
Plan9的寄存器约定
不同体系结构的 CPU,其内部寄存器的数量、种类以及名称可能大不相同,这里我们只介绍 AMD64 的寄存器。AMD64 有 20 多个可以直接在汇编代码中使用的寄存器,其中有几个寄存器在操作系统代码中才会见到,而应用层代码一般只会用到如下三类寄存器。
|
|
上述这些寄存器除了段寄存器是 16 位的,其它都是 64 位的,也就是 8 个字节,其中的 16 个通用寄存器还可以作为 32/16/8 位寄存器使用。
Plan9中寄存器的一般用途:
伪寄存器是 plan9 伪汇编中的一个助记符, 也是 Plan9 比较有个性的语法之一。常见伪寄存器如下表所示:
-
SB:指向全局符号表。相对于寄存器,SB 更像是一个声明标识,用于标识全局变量、函数等。通过 symbol(SB) 方式使用,symbol<>(SB)表示 symbol 只在当前文件可见,跟 C 中的 static 效果类似。此外可以在引用上加偏移量,如 symbol+4(SB) 表示 symbol+4bytes 的地址。
-
PC:程序计数器(Program Counter),指向下一条要执行的指令的地址,在 AMD64 对应 rip 寄存器。个人觉得,把他归为伪寄存器有点令人费解,可能是因为每个平台对应的物理寄存器名字不一样。
-
SP:SP 寄存器比较特殊,既可以当做物理寄存器也可以当做伪寄存器使用,不过这两种用法的使用语法不同。其中,伪寄存器使用语法是 symbol+offset(SP),此场景下 SP 指向局部变量的起始位置(高地址处);x-8(SP) 表示函数的第一个本地变量;物理 SP(硬件SP) 的使用语法则是 +offset(SP),此场景下 SP 指向真实栈顶地址(栈帧最低地址处)。
-
FP:用于标识函数参数、返回值。被调用者(callee)的 FP 实际上是调用者(caller)的栈顶,即 callee.SP(物理SP) == caller.FP;x+0(FP) 表示第一个请求参数(参数返回值从右到左入栈)。使用如 symbol+offset(FP)的方式,引用 callee 函数的入参参数。例如 arg0+0(FP),arg1+8(FP),使用 FP 必须加 symbol ,否则无法通过编译(从汇编层面来看,symbol 没有什么用,加 symbol 主要是为了提升代码可读性)。另外,需要注意的是:往往在编写 go 汇编代码时,要站在 callee 的角度来看(FP),在 callee 看来,(FP)指向的是 caller 调用 callee 时传递的第一个参数的位置。假如当前的 callee 函数是 add,在 add 的代码中引用 FP,该 FP 指向的位置不在 callee 的 stack frame 之内。而是在 caller 的 stack frame 上,指向调用 add 函数时传递的第一个参数的位置,经常在 callee 中用symbol+offset(FP)来获取入参的参数值。
另外还有 1 个比较特殊的伪寄存器:TLS:存储当前 goroutine 的 g 结构体的指针。实际上,X86 和 AMD64 下的 TLS 是通过段寄存器 FS 或 GS 实现的线程本地存储基地址,而当前 g 的指针是线程本地存储的第一个变量。
Plan9基本知识
Go语言使用的汇编代码风格跟最常见的Intel风格和AT&T风格都不太相同,根据官方文档的说法,是基于Plan 9汇编器的风格做了一些调整。
1.操作数的宽度
在Go汇编中通过指令的后缀来判断操作数的宽度,后缀B代表8位,W代表16位,D或L代表32位,Q代表64位,不像Intel汇编有AX、EAX、RAX不同的寄存器名称。
# Intel
INC EAX
INC RCX
# Go汇编
INCL AX
INCQ CX
2.操作数的顺序
对于常见的有两个操作数的指令,Go汇编中操作数的顺序与Intel汇编中操作数的顺序是相反的,源操作数在前而目的操作数在后。
# Intel
MOV EAX, ECX
# Go汇编
MOVL CX, AX
3.地址的表示
如果要用ESP作为基址寄存器,EBX作为索引寄存器,比例系数取2,位移为16,则可以分别给出两种风格的代码。
# Intel
[ESP + EBX*2 + 16]
# Go汇编
16(SP)(BX*2)
4.立即数格式
Go汇编中的立即数类似于AT&T风格的立即数,需要加上$前缀。
# Intel
MOV EAX, 1234
# Go汇编
MOVL $1234, AX
Go汇编
常见指令如下表:
例如:
MOVB $1, DI // 1 byte; 将 DI 的第一个 Byte 的值设置为 1
MOVW $0x10, BX // 2bytes
MOVD $1, DX // 4 bytes
MOVQ $-10, AX // 8 bytes
SUBQ $0x18, SP // 对SP做减法,扩栈
ADDQ $0x18, SP // 对SP做加法,缩栈
ADDQ AX, BX // BX += AX
SUBQ AX, BX // BX -= AX
IMULQ AX, BX // BX *= AX
JMP addr // 跳转到地址,地址可为代码中的地址,不过实际上手写一般不会出现
JMP label // 跳转到标签,可以跳转到同一函数内的标签位置
JMP 2(PC) // 向前转2行
JMP -2(PC) // 向后跳转2行
JNZ target // 如果zero flag被set过,则跳转
函数声明如下:
函数调用示例:
|
|
- 这里函数栈帧大小,除了本函数内部用到的局部变量,还包含了调用其它函数时,其它函数入参和返回值等需要占用的空间。
- Go新版编译器将函数返回值放入寄存器当中,进一步压缩函数栈帧大小,提高运行速度。根据底层CPU差异有所不同,X86-64大概有(AX,BX,DI,SI,R8,R9,R10,R11,R12)等9个寄存器可以用,再多就需要额外的栈帧空间存放了。
汇编函数中用到的一些特殊命令(伪指令)
- GO_RESULTS_INITIALIZED: 如果 Go 汇编函数返回值含指针,则该指针信息必须由 Go 源文件中的函数的 Go 原型提供,即使对于未直接从 Go 调用的汇编函数也是如此。如果返回值将在调用指令期间保存实时指针,则该函数中应首先将结果归零, 然后执行伪指令 GO_RESULTS_INITIALIZED。表明该堆栈位置应该执行进行 GC 扫描,避免其指向的内存地址呗 GC 意外回收。
- NO_LOCAL_POINTERS: 就是字面意思,表示函数没有指针类型的局部变量。
- PCDATA: Go 语言生成的汇编,利用此伪指令表明汇编所在的原始 Go 源码的位置(file&line&func),用于生成 PC 表格。runtime.FuncForPC 函数就是通过 PC 表格得到结果的。一般由编译器自动插入,手动维护并不现实。
- FUNCDATA: 和 PCDATA 的格式类似,用于生成 FUNC 表格。FUNC 表格用于记录函数的参数、局部变量的指针信息,GC 依据它来跟踪栈中指针指向内存的生命周期,同时栈扩缩容的时候也是依据它来确认是否需要调整栈指针的值(如果指向的地址在需要扩缩容的栈中,则需要同步修改)。
|
|
Go函数调用栈帧
参考阅读:
https://blog.csdn.net/ByteDanceTech/article/details/130212194
https://baijiahao.baidu.com/s?id=1677139791909759032
https://zhuanlan.zhihu.com/p/509151846
(完)
- 原文作者: 闪电侠
- 原文链接:https://chende.ren/2024/04/09130344-012-plan9.html
- 版权声明:本作品采用 开放的「署名 4.0 国际 (CC BY 4.0)」创作共享协议 进行许可