Go汇编中的函数

函数调用约定

Go汇编中的函数调用遵循特定的约定 1

函数声明

TEXT pkgname·funcname(SB), [flags], $framesize-argsize
  • pkgname:包名
  • funcname:函数名
  • flags:函数标志(如NOSPLIT表示不进行栈分裂检查)
  • framesize:栈帧大小
  • argsize:参数和返回值的总大小

参数传递

Go语言中的参数传递完全通过栈来实现 2。函数调用时的栈布局如下:

  1. 调用参数和返回值空间
  2. 返回地址
  3. 局部变量空间
  4. 调用其他函数的参数空间

参数访问

  • 通过FP伪寄存器访问函数参数
  • 参数从低地址到高地址排列
  • 第一个参数位于0(FP)
  • 后续参数根据类型大小顺序偏移

栈帧结构

栈帧布局

栈帧是向下增长的,主要包含以下部分 4

  1. 函数参数和返回值区域
  2. 保存的BP寄存器值
  3. 局部变量区域
  4. 临时存储区域

栈操作指令

SUBQ $framesize, SP // 分配栈帧空间 MOVQ BP, (SP) // 保存BP LEAQ (SP), BP // 设置新的BP // 函数返回前 MOVQ (SP), BP // 恢复BP ADDQ $framesize, SP // 释放栈帧空间

函数类型

Go中有四种主要的函数类型 2

  1. 顶层函数(top-level func)
  2. 值接收者方法(method with value receiver)
  3. 指针接收者方法(method with pointer receiver)
  4. 函数字面量(func literal,包括闭包)

调用规范

函数调用过程

  1. 准备参数和返回值空间
  2. CALL指令调用函数
  3. 被调用函数分配栈帧
  4. 执行函数体
  5. 设置返回值
  6. RET指令返回

寄存器使用

  • BP:保存调用者的栈基址
  • SP:指向当前栈顶
  • PC:程序计数器,用于指令跳转

栈分裂

为了处理栈溢出,Go使用栈分裂机制 5

MOVQ (TLS), CX // 获取当前线程的g结构体 CMPQ SP, 16(CX) // 检查是否需要栈扩展 JLS stack_split // 需要扩展则跳转处理

实践建议

  1. 使用NOSPLIT标志避免栈分裂检查
  2. 注意参数和返回值的对齐要求
  3. 正确维护BP寄存器
  4. 确保栈平衡(分配和释放配对)