Go Assembly Programming

polaris · · 106 次点击 · · 开始浏览    

源自 Plan9 汇编实现。 保存在 .s 文件中,编译器自动编译、链接。

本文内容基于 amd64 架构。

指令

指令参数长度。

MOVB: 1-byte
MOVW: 2 
MOVL: 4 
MOVQ: 8 

数据移动方向:从左往右。

 ADD R1, R2        // R2 += R1
 SUB R3, R4        // R4 -= R3
 SUB R3, R4, R5    // R5 = R4 - R3
 MUL $7, R6        // R6 *= 7

内存访问。

 MOV (R1), R2            // R2 = *R1
 MOV 8(R1), R2           // R2 = *(8 + R2)
 MOV 16(R1)(R2*2), R3    // R3 = *(16 + R1 + R2*2)
 MOV runtime·x(SB), R2   // R2 = *runtime·x

跳转指令。

 JMP label      // 跳转到标签。
 JMP 2(PC)      // 跳转到 PC + n 行。
 JMP -2(PC)

数字常量以 $ 开头,十进制($10)和 十六进制($0x10)。 标签仅在函数內有效。

伪寄存器

伪寄存器(pseudo-register)由语言定义并使用,最终会被编译为硬件寄存器引用。

考虑到平台差异,编译后的机器代码,可能须保存 PC、BP、SP 等物理寄存器值。 在编写汇编代码时,很难事先计算好实际所需偏移量。为此,汇编语言用伪寄存器表示某个相对位置就很有必要。

  • SB: Static Base Pointer(全局符号)

表示一个全局符号地址,通常应用于全局函数或数据。
例如 CALL add(SB) 表示对应符号名字为 add 的内存地址。
在名字后添加尖括号(add<>(SB)),表示该符号名仅在当前文件內可见。
还可用偏移量表示基于某个符号名字的地址,例如 add+8(SB)。

  • FP: Frame Pointer(参数地址)

指向由调用方提供的参数列表起始地址,通过偏移量指向不同参数或返回值。
通常在偏移量前包含参数名。例如 MOVQ size+16(FP), AX
  • SP: Stack Pointer (栈局部变量内存地址)

伪 SP 寄存器表示栈帧內,用于本地局部变量操作的起始地址。
鉴于栈从底开始的操作方式,SP 实际是栈底位置(等同调整后的 BP 地址)。
使用该方式访问局部变量,须添加变量名,如 x-8(SP)。如果省略变量名,则表示硬件寄存器。

  • PC: Program Counter(指令地址)

可用来按指令行数条转。
比如 JMP 2(PC) 表示以当前位置为 0 基准,往下跳到第 2 行。

考虑到栈帧内存实际上分成局部变量(底)和调用参数(顶)两部分使用,所以用伪 SP 寄存器负值便宜访问局部变量是很自然的做法。 如此,物理寄存器 SP 用来操作调用参数入栈;而伪寄存器 SP 用来访问局部变量。 毕竟 BP 寄存器是可选的。

注意 x+0(FP)gobuf_pc(AX) 宏函数的区别。

                         CALLEE
  lo               SP +-----------+   ..........................
                      |           |                            .
                      +-----------+                            .
                      |           |                       frame size(包括 caller BP)
       BP (pseudo SP) +-----------+                            .
                      | caller BP |                            .
                      +-----------+   ..........................
                      | caller PC |
                   FP +-----------+------------+ SP   ..........
                      | arg0      | call arg0  |               .
                      +-----------+------------+               .
                      | argn      | call argn  |       argument size
                      +-----------+------------+               .
                      | return    | call ret   |               .
  hi                  +-----------+------------+   .............
                                  | local var0 |
                                  +------------+
                                  | local varn |
                                  +------------+ BP (pseudo SP)
                                      CALLER

函数

函数定义。

                                          参数及返回值大小
                                          | 
 TEXT runtime·cgocallback(SB),NOSPLIT,$32-32
       |        |                      |
      包名     函数名                   栈帧大小(不包括参数及返回值)

当前包,可省略包名,直接以中心点开始。

由调用者(caller)负责分配目标函数(callee)参数和返回值内存。 调用者须自行保存相关寄存器状态。

示例

使用汇编代码编写一个简单的加法。

add.s

 #include "textflag.h"
 
 // add(x, y int) int
 TEXT ·add(SB), NOSPLIT, $8-24
     MOVQ $0, z-0x8(SP)
     MOVQ x+0x0(FP), AX
     MOVQ y+0x8(FP), BX
     ADDQ AX, BX       
     MOVQ BX, z-0x8(SP)
     MOVQ BX, ret+0x10(FP)
     RET

main.go

 package main
 
 func add(x, y int) (z int)    // 声明汇编函数原型
 
 func main() {
    z := add(0x100, 0x200)
    println(z)
 }

可以看到编译器插入栈帧调整,环境保存等指令。

 $ go build -gcflags "-l"
 
 $ go tool objdump -s "main\.add" test
 
 TEXT main.add(SB) add.s
    add.s:5     0x104bfe0       SUBQ $0x10, SP        // 因为要保存 BP,所以栈帧大小调整到 0x10。
    add.s:5     0x104bfe4       MOVQ BP, 0x8(SP)    
    add.s:5     0x104bfe9       LEAQ 0x8(SP), BP    
 
    add.s:6     0x104bfee       MOVQ $0x0, 0(SP)    
    add.s:7     0x104bff6       MOVQ 0x18(SP), AX   
    add.s:8     0x104bffb       MOVQ 0x20(SP), BX   
    add.s:9     0x104c000       ADDQ AX, BX     
    add.s:10    0x104c003       MOVQ BX, 0(SP)      
    add.s:11    0x104c007       MOVQ BX, 0x28(SP)   
    add.s:12    0x104c00c       MOVQ 0x8(SP), BP    
 
    add.s:12    0x104c011       ADDQ $0x10, SP        // 清除栈帧。
    add.s:12    0x104c015       RET     

使用 -gcflags -S 输出反汇编时,会有 FUNCDATA 和 PCDATA 信息。 它们是编译器引入,包含垃圾回收器要使用的信息。

106 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet