go中的go:linkname的理解

什么是 go:linkname

在go语言的源码中,会发现很多,代码只有函数签名,却看不到函数体,如:

// src/os/proc.go 68行
func runtime_beforeExit() // implemented in runtime

此处我们只看到函数签名,却看不到函数体,全局搜了一把,发现它的函数体却定义在src/runtime/proc.go

// os_beforeExit is called from os.Exit(0).
//go:linkname os_beforeExit os.runtime_beforeExit
func os_beforeExit() {
    if raceenabled {
        racefini()
    }
}

它是通过go:linkname把函数签名和函数体连接在一起的。

指令格式:

//go:linkname B A

第一个参数表示当前方法或变量,第二个参数表示需要建立链接方法,变量的路径。

go:linkname引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量,第一个参数表示当前方法或变量,第二个参数表示目标方法或变量,因为这个指令会破坏系统和包的模块化,因此在使用时必须导入unsafe

注意点

  • go:linkname可以跨包使用,需要导入 unsafe包。
  • A所在的包必须要打入B所在的包,这个也好理解,相当于A调用了B。
  • go build无法编译go:linkname,必须用单独的compile命令进行编译,因为go build会加上-complete参数,这个参数会检查到没有方法体的方法,并且不通过,报错missing function body。或者,在这个没有方法体的包下添加一个空的aa.s 的汇编文件标示,就可以编译了。
为什么要用 go:linkname

这个指令不经常用,最好也不要用,但理解这个指令可以帮助你理解核心包的很多代码。在标准库中是为了可以使用另一个包的unexported的方法或者变量,在敲代码的时候是不可包外访问的,但是运行时用这个命令hack了一下,就变得可以访问。

最大的作用就是定向可访问。

实例
go-ole
	hello
		hello.s
		hello.go
	link
		link.go
	demo.go
	go.mod
// hello.go
// 要导入 link 包
// 一般需要一个可导出的方法,例如 CallHello

package hello

import (
	_ "go-ole/link"
)

func hello()

func CallHello() {
	hello()
}
// link.go
// 需要导入 unsafe 包

package link

import _ "unsafe"

//go:linkname helloWorld go-ole/hello.hello
func helloWorld() {
	println("hello world!")
}
// demo.go

package main

import (
	"go-ole/hello"
)

func main() {
	hello.CallHello()
}
go build demo.go
demo.exe