Go 汇编随笔
Go 汇编小记
-
学习了 Go 汇编,在内存分布和底层函数调用有了一定理解,对常用概念做个小结
本文结构
-
介绍内存、寄存器及相关指令
-
变量、函数 申明
-
函数帧内存分布图
-
程序代码是通过 CPU 执行相关汇编指令
运行的。计算机内部布局划分为三个部分,内存
、寄存器
、指令
。本篇内容主要是通过这三个部分介绍 Go 汇编,作为学习总结 -
Go 内存组成:heap、stack、frame、local、data(read、roread)、text
- frame: 一个函数对应一个 frame
- stack: 一个 stack 中至少有一个 frame,管理每个函数(frame)被调用时相关的数据
- local: 局部变量,一般在函数内定义,只有在函数被执行才会在 stack 上被创建,
当函数调用完后被回收(进阶:闭包中对局部变量捕获问题) - data: 数据段,一般用于存放全局数据,roread 只读数据数据段
- text: 代码段,用于存储要执行的指令数据,一般是只读
-
Go 几个常见寄存器:SB、FP、SP、PC
-
SB: Staic Base Pointer,静态内存 的开始地址
-
FP: Frame Pointer,对应函数的帧指针,一般用来访问函数的参数和返回值
-
SP: Stack Pointer,分
伪SP
与真SP
- 伪 SP:位于当前栈帧(frame)底部,用于
定位局部变量
,形如 tmp-2(SP),即相对与真 SP 地址偏移量 - 真 SP:位于当前栈帧(frame)顶部,用于
定位调用其他函数的 参数 和 返回值
- 伪 SP:位于当前栈帧(frame)底部,用于
-
-
常见指令:CALL、RET、LEA、PUSH、POP
类别 | 指令名称 | 解释 |
---|---|---|
控制流 | CMP | 比较指令 |
JMP / JMP-if-x | 跳转指令 | |
CALL | 调用函数 | |
RET | 函数返回 | |
其他 | LEA | 取地址,将内存地址加载到寄存器 |
PUSH | 压栈 | |
POP | 出栈 |
-
注意
-
调用函数时, 被调用函数(callee)的参数和返回值的内存空间必须由调用者(caller)提供
,因此函数的局部变量和为调用其他函数准备的栈空间总和就确定了函数帧的大小(暂不考虑栈分裂扩容),
调用其他函数前,调用方(caller)要选择保存相关寄存器到栈中,并在调用函数返回后选择要恢复的寄存器进行保存 -
函数的第一个参数和第一个返回值会分别进行一次地址对齐
-
- Go 汇编常见指令
-
注意:
- 在汇编中,没有函数类型,GO 自动回收会报错,所以在引用汇编定义的内容前,需要在 go 文件前指定
变量或函数的类型 - Go 语言函数的调用参数和返回值均是通过栈传输
- 在汇编中,没有函数类型,GO 自动回收会报错,所以在引用汇编定义的内容前,需要在 go 文件前指定
-
变量定义
// symbol: 对应汇编的变量名
// width,该符号对应内存大小,内存宽度必须是 2 的指数倍,单位是字节(byte)
GLOBL symbol(SB), width
- 变量初始化
// symbol: 对应汇编的变量名,offset(SB),符号开始地址的偏移量
// width:要初始化的内存宽度大小,初始化始必须是 1,2,4,8几个宽度之一,即对应到机器的整数倍,单位是字节(byte)
// value: 变量初始化值,可以是具体值,也可以是一个已经声明变量(symbol)
DATA symbol+offset(SB) /width,value
- 函数定义
//TEXT 用于定义函数符号
// symbol: 函数名,是包含包路径的,可省略包路径
/** flags:用于指示函数的一些特殊行为,
NOSPLIT 函数不进行栈分裂,一般用于没有其他调用的叶子函数
WRAPPER包装函数 ,在panic 或 runtime.caller等某些处理函数帧的地方不会增加函数帧计数
NEEDCTXT 表示需要一个上下文参数,一般用于闭包函数
*/
//$framesize 表示函数的局部变量需要多大的栈空间,其中包含调用其他函数时准备调用参数的隐式栈空间
TEXT symbol(SB), [flags] $framesize[-argsize]
- 函数帧
func main() {
printsum(a,b)
}
func printsum(a,b int){
var ret = sum(a,b)
fmt.Println(ret)
}
func sum(a,b int)int{
return a + b
}
- 引用
- 内容总结与曹大的书,Go 语言高级编程
严律己、宽待人