最近在学习golang反射,为了了解下原理,就深入了解了reflect源代码
看到了有一些函数,点击跳入源代码时,只在reflect包中找到了一个函数声明
比如以下函数
// resolveNameOff resolves a name offset from a base pointer.
// The (*rtype).nameOff method is a convenience wrapper for this function.
// Implemented in the runtime package.
func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer
看到这种用法,我尝试在自己的代码里面也只声明一个函数,只是无情的被提示missing function body.看了上述函数的注释,是在runtime包中实现的,也在runtime中找到了一个同名函数resolveNameOff,同时又发现了一个类似名称的函数reflect_resolveNameOff
// reflect_resolveNameOff resolves a name offset from a base pointer.
//go:linkname reflect_resolveNameOff reflect.resolveNameOff
func reflect_resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer {
return unsafe.Pointer(resolveNameOff(ptrInModule, nameOff(off)).bytes)
}
在注释中发现了reflect.resolveNameOff,因为reflect包中只有函数声明,注释中有说明了在runtime中实现,个人感觉reflect中resolveNameOf是与runtime.reflect_resolveNameOff函数关联着的,但是不清楚它们是如何关联的.
还是在注释中找到了线索
注释内容 **go:linkname reflect_resolveNameOff reflect.resolveNameOff**
搜索关键字go:linkname,最后搜索出一篇英文博客,很好的解释了只有函数声明的实现规则
博客名字翻译过来大意是golang中如何调用第三方包的私有函数
golang是通过大小写确定函数对外的可见性,大写字母开头可导出,小写开头的为私有函数,只能在包内使用,写代码时候编译器会检测是否引用了第三方的私有函数
有两种方法能够绕过这种检测
- 最老的一种,现在已经不再使用,原文是
assembly level implicit linkage to needed symbols, referred as
assembly stubs` 不太会翻译,个人理解是使用汇编文件.已经不再使用这种方式,目前通过go:linkname实现 - go编译器级别的支持,通过go:linkname ,从11.11.14开始
汇编文件
这种方法很简单,使用汇编文件,通过jmp跳转到需要的函数去.
eg: src/os/signal/sig.s
// Assembly to get into package runtime without using exported symbols.
// +build amd64 amd64p32 arm arm64 386 ppc64 ppc64le
#include "textflag.h"
#ifdef GOARCH_arm
#define JMP B
#endif
#ifdef GOARCH_ppc64
#define JMP BR
#endif
#ifdef GOARCH_ppc64le
#define JMP BR
#endif
TEXT ·signal_disable(SB),NOSPLIT,$0
JMP runtime·signal_disable(SB)
TEXT ·signal_enable(SB),NOSPLIT,$0
JMP runtime·signal_enable(SB)
TEXT ·signal_ignore(SB),NOSPLIT,$0
JMP runtime·signal_ignore(SB)
TEXT ·signal_recv(SB),NOSPLIT,$0
JMP runtime·signal_recv(SB)
在signal_unix.go文件中绑定
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
package signal
import (
"os"
"syscall"
)
// In assembly.
func signal_disable(uint32)
func signal_enable(uint32)
func signal_ignore(uint32)
func signal_recv() uint32
go:linkname
这种方法需要一个空的汇编.s文件
eg: os/signal/sig.s
// The runtime package uses //go:linkname to push a few functions into this
// package but we still need a .s file so the Go tool does not pass -complete
// to the go tool compile so the latter does not complain about Go functions
// with no bodies.
使用方式是 //go:linkname localname linkname
这种方法可以导出函数供链接,也可以导入,绑定现有的函数
go:linkname 导出函数
在runtime/proc.go
中实现的一个函数
...
//go:linkname sync_runtime_doSpin sync.runtime_doSpin
//go:nosplit
func sync_runtime_doSpin() {
procyield(active_spin_cnt)
}
告诉编译器与sync.runtime_doSpin中函数链接
在sync/runtime.go
中使用
package sync
import "unsafe"
...
// runtime_doSpin does active spinning.
func runtime_doSpin()
go:linkname 导入
eg: net/parse.go
package net
import (
...
_ "unsafe" // For go:linkname
)
...
// byteIndex is strings.IndexByte. It returns the index of the
// first instance of c in s, or -1 if c is not present in s.
// strings.IndexByte is implemented in runtime/asm_$GOARCH.s
//go:linkname byteIndex strings.IndexByte
func byteIndex(s string, c byte) int
使用要求
- 引入
unsafe
包 - 函数声明 eg:
func byteIndex(s string, c byte) int
- //go:linkname 正确放在函数声明处.eg
//go:linkname byteIndex strings.IndexByte
,byteIndex是本地函数名,strings.IndexByte是第三方包函数名 - 提供.s文件,允许编译器去除-complete检测
这两种方法实现很简单,这里不列代码了
参考资料
https://sitano.github.io/2016/04/28/golang-private/
发现了英文博客的翻译
有疑问加站长微信联系(非本文作者)