go:linkname 指令
go:linkname 指令
//go:linkname 指令允许我们在编译时将一个函数或变量链接到另一个包中的实现,即使该函数或变量是私有的(小写字母开头)。
指令格式
import _ "unsafe" // 需要引入unsafe包
//go:linkname local remote
remote 可以没有,此时 remote 使用 local 的值,效果就是 local 被导出。
这个特殊的指令的作用域并不是紧跟的下一行代码,而是同一个包下生效。
local 和 remote 同时为函数
local 作为占位符,remote 作为实现者
标准库中的例子:
// 来自 time 包
//go:linkname runtimeNano runtime.nanotime
func runtimeNano() int64
// 来自 runtime 包
//go:nosplit
func nanotime() int64 {
return nanotime1()
}
此时二进制文件中并没有runtimeNano,直接转化为对runtime.nanotime的调用。
local 作为实现者,remote 作为占位符
同样来自标准库。这里存在函数没有函数体,但是被反向引用。
// 在标准库的一个 internal 中
//go:linkname runtime_cmpstring runtime.cmpstring
func runtime_cmpstring(a, b string) int {
l := len(a)
if len(b) < l {
l = len(b)
}
for i := 0; i < l; i++ {
c1, c2 := a[i], b[i]
if c1 < c2 {
return -1
}
if c1 > c2 {
return +1
}
}
if len(a) < len(b) {
return -1
}
if len(a) > len(b) {
return +1
}
return 0
}
// 来自 runtime
func cmpstring(string, string) int
此时二进制文件中并没有runtime_cmpstring,对应的函数已经被命名为runtime.cmpstring。也就是说,实现在 internal 包,但最终通过 runtime.cmpstring 来引用。
一个占位符+一个汇编函数
// 在标准库的一个 internal 中
//go:linkname abigen_runtime_memequal runtime.memequal
func abigen_runtime_memequal(a, b unsafe.Pointer, size uintptr) bool
注意runtime.memequal的实现并不在runtime包中,使用汇编实现的话并不要求必须在相应的包中。
# memequal(a, b unsafe.Pointer, size uintptr) bool
TEXT runtime·memequal(SB),NOSPLIT,$0-25
MOVQ a+0(FP), SI
MOVQ b+8(FP), DI
CMPQ SI, DI
JEQ eq
MOVQ size+16(FP), BX
LEAQ ret+24(FP), AX
JMP memeqbody<>(SB)
eq:
MOVB $1, ret+24(FP)
RET
local 和 remote 同时为变量
两个常规变量
//go:linkname overflowError runtime.overflowError
var overflowError error
//go:linkname divideError runtime.divideError
var divideError error
//go:linkname zeroVal runtime.zeroVal
var zeroVal [maxZero]byte
//go:linkname _iscgo runtime.iscgo
var _iscgo bool = true
//go:cgo_import_static x_cgo_setenv
//go:linkname x_cgo_setenv x_cgo_setenv
//go:linkname _cgo_setenv runtime._cgo_setenv
var x_cgo_setenv byte
var _cgo_setenv = &x_cgo_setenv
//go:cgo_import_static x_cgo_unsetenv
//go:linkname x_cgo_unsetenv x_cgo_unsetenv
//go:linkname _cgo_unsetenv runtime._cgo_unsetenv
var x_cgo_unsetenv byte
var _cgo_unsetenv = &x_cgo_unsetenv
一个占位符+一个伪符号
//go:linkname runtime_inittask runtime..inittask
var runtime_inittask initTask
//go:linkname main_inittask main..inittask
var main_inittask initTask
注意是..inittask不是.inittask,而且.inittask只存在于编译阶段,任何包中都无法声明该变量。
这里额外解释下
..inittask为什么两个点。第一个点就是普通的runtime.这种调用方式,第二个点和inittask一起构成一个符号(变量)。注意,Go 中的变量是不允许以.开头的,所以,这个叫伪符号,只在编译阶段存在。
常见用途
示例:https://gitee.com/meha555/go-learn/tree/master/01_basic/tricks/linkname
使用别的包下没有导出的方法或者变量。但是不建议使用,因为这样属于打洞,被你偷偷导出的方法或变量的作者并不会考虑这种打洞操作,万一未来这个符号被作者改名或者删除了就寄了。
一个例子
研究 //go:linkname 是因为如下的背景:
Java 里有 InheritableThreadLocal,SpringWeb 在 ServletActionContext 里使用它,达到在任何地方都能方便的获取HttpServletRequest。
Go 并没有提供类似的机制,即使通过 stack 找到 goroutine id(99% 的文章都是这么介绍的),再配合 sync.Map,也只是实现了一个比较粗糙的 ThreadLocal,在子协程里仍然获取不到父协程的内容。
g.label 虽然不是给这种场景准备的,但它具备了 InheritableThreadLocal 的一切要求,只要我们能够访问到 label 私有字段,我们就有了完整版的 InheritableThreadLocal。
下面这个例子是作者真实项目中用的。
在 runtime 和 runtime/pprof 包中有两个函数:runtime_setProfLabel 和 runtime_getProfLabel。其中,runtime 包中的提供了实现,而 pprof 中的没有提供实现。如果基于它们创建另外的函数,如下:
//go:linkname SetPointer runtime/pprof.runtime_setProfLabel
func SetPointer(ptr unsafe.Pointer)
//go:linkname GetPointer runtime/pprof.runtime_getProfLabel
func GetPointer() unsafe.Pointer
根据前面的分析,虽然runtime.runtime_setProfLabel/runtime.runtime_getProfLabel提供了函数实现,但是二进制文件中并不会出现(见下方代码),此时想要调用必须通过runtime/pprof.runtime_setProfLabel/runtime/pprof.runtime_getProfLabel,这也是上面linkname到pprof而不是runtime的根本原因。
// 来自 runtime 包
//go:linkname runtime_setProfLabel runtime/pprof.runtime_setProfLabel
func runtime_setProfLabel(labels unsafe.Pointer) {
if raceenabled {
racereleasemerge(unsafe.Pointer(&labelSync))
}
getg().labels = labels
}
// 来自 runtime/pprof 包
func runtime_setProfLabel(labels unsafe.Pointer)
// 来自 runtime 包
//go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel
func runtime_getProfLabel() unsafe.Pointer {
return getg().labels
}
// 来自 runtime/pprof 包
func runtime_getProfLabel() unsafe.Pointer

浙公网安备 33010602011771号