这两天看go的源码,突然发现很多函数都是汇编写的go去调用的时候通过在函数上添加 //go:linkname xxx yyy 等形式进行调用。
由于go是按照首字母大小写决定是否可以被外部包引用的。所以,如果我们想方位某个包中的私有成员,就需要用到go:linkname了,也就是说我们可以通过 //go:linkname localname linkname 这种方式将本地的私有函数/变量,提供给外部使用。但是经过试验,常量是不可以的。同时还有一个 //go:nosplit 在源码中也是经常出现的,其实就是告诉编译器,下面的函数不会产生堆栈溢出,不需要插入堆栈溢出检查。
具体的使用有大致有两种
1. 通过 //go:linkname localname linkname 将函数 localname 连接到 当前包(a),然后在使用的时候(b)同样需要 通过此命令连接到之前的符号。也就是 a.go 在函数上加 //go:linkname say a.say 此时无论使用方采用首字母大写关联还是小写进行管理,均不能直接使用。 之后 b.go 中
//go:linkname sayTest a.say
func sayTest(name string) string
func Greet(name string) string {
return sayTest(name)
}
2. 不需要在使用的时候在用 go:linkname 去关联,只需要在定义处直接 关联到目标包的函数即可,则目标包只需要定义一个关联时候定义的 函数体即可直接使用 func Hi(name string) string
我们先看一下代码的结构
具体代码:
a/a.go
package a
import _ "unsafe"
//go:linkname say a.say
//go:nosplit
func say(name string) string {
return "say:hello," + name
}
//go:linkname say1 a.say1
//go:nosplit
func say1(name string) string {
return "say1:hello," + name
}
//将 say2 连接到 对应的域名下的函数
//go:linkname say2 SourceCodeTest/go-linkname-test/b.Hi
//go:nosplit 不得包含堆栈溢出检查 因为只是一个简单的字符串拼合,不需要移除检测
func say2(name string) string {
return "say2:hi," + name
}
//go:linkname aaaStr SourceCodeTest/go-linkname-test/b.AAAStr
var aaaStr = "1111"
//go:linkname name SourceCodeTest/go-linkname-test/b.Name
const name = "33333"
b/b.go
package b
import (
_ "SourceCodeTest/go-linkname-test/a"
_ "unsafe"
)
var AAAStr = ""
const Name = "222"
//go:linkname sayTest a.say
func sayTest(name string) string
//及时是 大写的 但是在main包和其他任何包中,是不能直接调用的,因为
//go:linkname sayHi a.say1
func sayHi(name string) string
func SayHii(name string) string {
return sayHi(name)
}
func Greet(name string) string {
return sayTest(name)
}
/*
*如果不写此函数,a.go中的 //go:linkname say2 SourceCodeTest/go-linkname-test/b.Hi 并不会报错
*/
func Hi(name string) string
c/c.go
package c
import (
"SourceCodeTest/go-linkname-test/b"
)
func SayHii(name string) string {
return b.Hi(name)
}
main.go
package main
import (
"SourceCodeTest/go-linkname-test/b"
"SourceCodeTest/go-linkname-test/c"
"fmt"
)
func main() {
s := b.Greet("world")
fmt.Println(s)
s = b.Hi("world")
fmt.Println(s)
s = b.SayHii("world")
fmt.Println(s)
s = c.SayHii("world")
fmt.Println(s)
fmt.Println(b.AAAStr)
fmt.Println(b.Name)
}
运行结果:
通过以上测试我们就可以知道,函数,变量可以通过这种方式打破go原本的包之间的调用规则,但是常量是不行的。至于b/b.s的文件,没有则会报错,源码中类似的文件则为 汇编语言定义的函数,此处我们只放一个空文件,应对go语言编译检测即可。需要注意的点是,是用 go:linkname需要引入 unsafe 包 import _ "unsafe" 在要使用私有变量的包中(b)同时还要引入 调用的包,只是我们并不显示调用,所以用“ _ ” 忽略掉即可。
参考:https://colobu.com/2017/05/12/call-private-functions-in-other-packages/