GO 汇编学习笔记(三) —— 基本语法
GO 汇编学习笔记(一)
-
函数概念
因为Go汇编语言中,可以也建议通过Go语言来定义全局变量,那么剩下的也就是函数了。只有掌握了汇编函数的基本用法,才能真正算是Go汇编语言入门。本章将简单讨论Go汇编中函数的定义和用法。
-
基本语法
-
函数标识符通过TEXT汇编指令定义,表示该函数存储在TEXT内存段,TEXT语句后的指令对应函数的实现。
-
TEXT 本身不关心后面十分有指令,因此TEXT和LABEL定义的符号是类似的,区别只是LABEL是用于跳转标号,但是本质上他们都是通过标识符映射一个内存地址。
-
函数的定义的语法如下:
TEXT symbol(SB), [flags,] $framesize[-argsize]
函数的定义部分由5个部分组成:TEXT指令、函数名、可选的flags标志、函数帧大小和可选的函数参数大小。
其中TEXT用于定义函数符号,函数名中当前包的路径可以省略。函数的名字后面是
(SB)
,表示是函数名符号相对于SB伪寄存器的偏移量,二者组合在一起最终是绝对地址。作为全局的标识符的全局变量和全局函数的名字一般都是基于SB伪寄存器的相对地址。标志部分用于指示函数的一些特殊行为,标志在textlags.h
文件中定义,常见的NOSPLIT
主要用于指示叶子函数不进行栈分裂,framesize部分表示函数的局部变量需要多少栈空间,其中包含调用其它函数时准备调用参数的隐式栈空间。最后是可以省略的参数大小,之所以可以省略是因为编译器可以从Go语言的函数声明中推导出函数参数的大小。需要注意的是函数也没有类型,上面定义的Swap函数签名可以下面任意一种格式:
func Swap(a, b, c int) int func Swap(a, b, c, d int) func Swap() (a, b, c, d int) func Swap() (a []int, d int)
目前可能遇到的函数标志有NOSPLIT、WRAPPER和NEEDCTXT几个。
NOSPLIT:不会生成或包含栈分裂代码,这一般用于没有任何其它函数调用的叶子函数,这样可以适当提高性能。
WRAPPER: 表示这个是一个包装函数,在
panic
或runtime.caller
等某些处理函数帧的地方不会增加函数帧计数。 NEEDCTXT: 表示需要一个上下文参数,一般用于闭包函数。
-
-
函数参数和返回值
对于函数来说,最重要的是函数对外提供的API约定,包含函数的名称、参数和返回值。当这些都确定之后,如何精确计算参数和返回值的大小是第一个需要解决的问题。
func Swap(a, b int) (ret0, ret1 int)
对于这个函数,我们可以轻易看出它需要4个int类型的空间,参数和返回值的大小也就是32个字节:TEXT ·Swap(SB), $0-32
Go汇编中引入了一个FP伪寄存器,表示函数当前帧的地址,也就是第一个参数的地址。因此我们以通过
+0(FP)
、+8(FP)
、+16(FP)
和+24(FP)
来分别引用a、b、ret0和ret1四个参数。 但是在汇编代码中,我们并不能直接以
+0(FP)
的方式来使用参数。为了编写易于维护的汇编代码,Go汇编语言要求,任何通过FP伪寄存器访问的变量必和一个临时标识符前缀组合后才能有效,一般使用参数对应的变量名作为前缀。TEXT ·Swap(SB),NOSPLIT,$0-32 MOVQ a+0(FP), AX MOVQ b+8(FP), BX MOVQ BX, ret0+16(FP) MOVQ AX, ret0+24(FP) RET
//test.go func Foo(a bool, b int16) (c []byte) //test_amd64.s TEXT ·Foo(SB),$0 MOVQ a+0(FP),AX MOVQ b+2(FQ),BX MOVQ c_data+8*1,CX MOVQ c_data+8*2,DX MOVQ c_data+8*3,DI RET
其中a和b参数之间出现了一个字节的空洞,b和c之间出现了4个字节的空洞。出现空洞的原因是要保证每个参数变量地址都要对齐到相应的倍数。