Go汇编中的函数
函数调用约定
Go汇编中的函数调用遵循特定的约定
函数声明
TEXT pkgname·funcname(SB), [flags], $framesize-argsize
- pkgname:包名
- funcname:函数名
- flags:函数标志(如NOSPLIT表示不进行栈分裂检查)
- framesize:栈帧大小
- argsize:参数和返回值的总大小
参数传递
Go语言中的参数传递完全通过栈来实现
- 调用参数和返回值空间
- 返回地址
- 局部变量空间
- 调用其他函数的参数空间
参数访问
- 通过FP伪寄存器访问函数参数
- 参数从低地址到高地址排列
- 第一个参数位于0(FP)
- 后续参数根据类型大小顺序偏移
栈帧结构
栈帧布局
栈帧是向下增长的,主要包含以下部分
- 函数参数和返回值区域
- 保存的BP寄存器值
- 局部变量区域
- 临时存储区域
栈操作指令
SUBQ $framesize, SP // 分配栈帧空间
MOVQ BP, (SP) // 保存BP
LEAQ (SP), BP // 设置新的BP
// 函数返回前
MOVQ (SP), BP // 恢复BP
ADDQ $framesize, SP // 释放栈帧空间
函数类型
Go中有四种主要的函数类型
- 顶层函数(top-level func)
- 值接收者方法(method with value receiver)
- 指针接收者方法(method with pointer receiver)
- 函数字面量(func literal,包括闭包)
调用规范
函数调用过程
- 准备参数和返回值空间
- CALL指令调用函数
- 被调用函数分配栈帧
- 执行函数体
- 设置返回值
- RET指令返回
寄存器使用
- BP:保存调用者的栈基址
- SP:指向当前栈顶
- PC:程序计数器,用于指令跳转
栈分裂
为了处理栈溢出,Go使用栈分裂机制
MOVQ (TLS), CX // 获取当前线程的g结构体
CMPQ SP, 16(CX) // 检查是否需要栈扩展
JLS stack_split // 需要扩展则跳转处理
实践建议
- 使用NOSPLIT标志避免栈分裂检查
- 注意参数和返回值的对齐要求
- 正确维护BP寄存器
- 确保栈平衡(分配和释放配对)