这次我们来实现,在一个汇编函数中调用Go方法
先看一下我们的包结构:
+assembly
-stub.go
echo方法
Hello方法声明
-hello.s
Hello方法实现
+main
-main.go
我们先声明一个go方法:
func echo(msg string) int64{
fmt.Println(msg)
return int64(len(msg))
}
我们想要做的就是在汇编程序中调用这个方法。这个方法有一个 string
类型的参数和一个 int64
的返回值。
我们需要先来了解一下 string
的内存布局,不同于 C
的 string
,golang
的 string
是一个结构体:
type stringStruct struct {
str unsafe.Pointer //指定底层的byte数组
len int //字符串长度
}
如上注释,字符串的长度保存在了 len
字段,而不是使用 \0
来作为字符串的结束标记,因此我们在计算 go
中的 string
长度时,不需要像在 c
语言中那样为 \0
预留一个字节。
一个 string
变量包含一个指针和一个整数,在64位的机器上占用16个字节
在写一个汇编方法之前,我们需要先声明这个函数,才能供其他 Go
方法调用:
func Hello() int64
接着使用汇编代码实现 Hello
函数:
#include "textflag.h" //引入textflag.h,因为使用到了RODATA这样的预定义值
//数据段声明一个字符串
DATA msg<> +0(SB)/8,$"hello,wo"
DATA msg<> +8(SB)/4,$"rld!"
GLOBL msg<> +0(SB),RODATA,$12 //GLOBL声明msg<>为全局标记符,RODATA表示 readOnly
//TEXT定义一个函数,包名· 方法名,这里省略包名,默认是当前包
//24表示函数的栈帧大小是24个字节被调用函数的参数和返回值是保存在调用方的栈帧中的
//echo方法需要一个string类型的参数和一个int64类型的返回值,需要24个字节
//8表示该函数的参数和返回值需要占用的空间
TEXT ·Hello(SB),$24-8
//不支持指令的两个参数都是 内存地址,因此需要使用寄存器做媒介
LEAQ msg<> +0(SB),AX //LEAQ为取址,将msg的地址存入AX
MOVQ AX,0(SP) //存入0(SP),相当于string.str
MOVQ $12,8(SP) //string的长度,相当于string.len
CALL ·echo(SB) //调用echo函数
MOVQ 16(SP),AX //返回值保存在16(SP)
MOVQ AX,RET+0(FP) //将返回值保存到RET+0(FP)
RET //返回
在汇编中传递参数,我们需要根据实际类型的内存布局来放置参数。因为我们传递的是一个 string
类型而不是一个 *string
,因此我们需要将 string.str
和 stirng.len
分别 mov
到 0(SP)
和 8(SP)
。
最后看一下我们的 main
函数:
func main() {
fmt.Println(assembly.Hello()) //打印Hello的返回值,即echo中返回的len(msg)
}
输出:
hello,world!
12
网友评论