文章目录
- 计算机结构
- 常量与变量
- 全局变量
- 常量
- 数组
- 字符串
- 函数
- 参数与返回值
- goroutine
Go汇编程序无法独立使用,必须以Go包的方式组织,同时包中至少要有一个Go语言文件用于指明当前包名等基本包信息。如果Go汇编代码中定义的变量和函数要被其它Go语言代码引用,还需要通过Go语言代码将汇编中定义的符号声明出来。
查看汇编代码:go tool compile -S asm.go
计算机结构
X86 CPU体系结构
左边为常见内存布局:
- text一般为代码段(只读),存储要执行的指令数据;
- rodata(只读)与data数据段,一般存放全局数据;
- heap段用于动态管理数据;
- stack段为栈数据(一般函数调用与局部变量);
中间为X86寄存器:
- BP记录当前函数帧的开始位置(函数调用会隐式影响其值);
- SP对应当前栈(顶)指针的位置;
Go汇编为了简化汇编代码的编写,引入了PC、FP、SP、SB四个伪寄存器(详见《go汇编ASM简介》)
在AMD64环境:
- 伪PC寄存器是IP指令计数器寄存器的别名;
- 伪FP寄存器对应的是函数的帧指针,一般用来访问函数的参数和返回值;
- 伪SP栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量(真SP寄存器对应的是栈的顶部);
- 使用SP时有一个临时标识符前缀(一般为局部变量名)就是伪SP:如
a(SP)和b-8(SP)
; - 没有临时标识符前缀的是真SP寄存器:如
(SP)和+8(SP)
;
- 使用SP时有一个临时标识符前缀(一般为局部变量名)就是伪SP:如
常量与变量
Go汇编语言提供了DATA命令用于初始化包变量:
- symbol为变量标识符;
- offset为地址偏移;
- width为内存宽度(大小)
- value是初始化值(常量需以
$
开始,如$0xA1)
DATA symbol+offset(SB)/width, value
全局变量
全局变量是包一级的变量,全局变量一般有着较为固定的内存地址,声明周期跨越整个程序运行时间。
GLOBL symbol(SB), width
定义好的变量需要导出后,才可供其他代码引用(在汇编中定义变量,然后在go中使用):
// asm.go
package asm
var count int32 // 声明,会关联到.s中对应变量
var Name string
// asm_amd64.s 是amd64的汇编
#include "textflag.h"
GLOBL ·Id(SB),NOPTR,$4
DATA ·count+0(SB)/1,$1
DATA ·count+1(SB)/1,$2
DATA ·count+2(SB)/1,$3
DATA ·count+3(SB)/1,$4
// 或者(小端)
// DATA ·count+0(SB)/4,$0x04030201
GLOBL ·Name(SB),NOPTR,$24
DATA ·Name+0(SB)/8,$·Name+16(SB)
DATA ·Name+8(SB)/8,$6
DATA ·Name+16(SB)/8,$"gopher"
常量
Go汇编语言中常量以$
美元符号为前缀。常量的类型有整数、浮点数、字符和字符串等几种类型。数值型常量,可通过表达式构造新的常量:
$1 // 十进制
$0x1234ABCD // 十六进制
$1.5 // 浮点数
$'a' // 字符
$"abcd" // 字符串
$2+2 // == $4
$(3&1)<<2 // == $4
数组
Go语言中数组是一种扁平内存结构的基础类型:
var num [2]int
// asm
GLOBL ·num(SB),$16
DATA ·num+0(SB)/8,$0
DATA ·num+8(SB)/8,$0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mOFliZ1F-1670753220575)(pitures/2022-go-asm-array-2int.png)]
注:切片与字符串类似,是一种带Header的结构
字符串
字符串(只读类型,要避免在汇编中直接修改字符串底层数据的内容)在go中是一个struct(包含数据地址与长度);amd64环境中StringHeader有16个字节大小,因此在汇编中定义一个16字节大小的变量(·helloworld):
// data of string
GLOBL text<>(SB),NOPTR,$16
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!"
// header of string
GLOBL ·helloworld(SB),$16
DATA ·helloworld+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld+8(SB)/8,$12 // StringHeader.Len
函数
函数标识符通过TEXT汇编指令定义,其后的指令一般对应函数的实现:
TEXT symbol(SB), [flags,] $framesize[-argsize]
各部分说明:
- symbol:函数名(当前包的路径可以省略)后面是
(SB)
,表示是函数名符号相对于SB伪寄存器的偏移量,二者组合在一起形成最终的绝对地址。 - flags:标志(textlags.h文件中定义):
- NOSPLIT:不生成或包含栈分裂(栈大小检测)代码(一般用于没有任何其它函数调用的叶子函数);
- WRAPPER:表示这个是一个包装函数,在panic或runtime.caller等某些处理函数帧的地方不会增加函数帧计数;
- NEEDCTXT:表示需要一个上下文参数,一般用于闭包函数;
- framesize:表示函数的局部变量需要多少栈空间,包含调用其它函数时准备调用参数的隐式栈空间;
参数与返回值
Go汇编语言要求,任何通过FP伪寄存器访问的变量必和一个临时标识符前缀组合后才能有效,一般使用参数对应的变量名作为前缀。函数的第一个参数和第一个返回值会分别进行一次地址对齐。
goroutine
Goroutine是Go中最基本的执行单元。goroutine就是一段代码加一个函数入口,以及在堆上为其分配的堆栈(初始4k,可随需要增长收缩);因此其非常廉价。
goroutine调用与普通函数类似:
- 先设定参数;
- 将函数地址(f)和参数大小(12)压栈;
- 调用runtime.newproc
- 新建一个栈空间,将参数复制到新栈;
- 保存函数(f存放到strcut G的entry中);
- 等待调度;
- 恢复栈
MOVL $1, 0(SP)
MOVL $2, 4(SP)
MOVL $3, 8(SP)
PUSHQ $f(SB)
PUSHQ $12
CALL runtime.newproc(SB)
POPQ AX
POPQ AX