代码包中如何只有函数声明,引用其他包的函数

懒散 · · 973 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

最近在学习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

使用要求

  1. 引入 unsafe
  2. 函数声明 eg: func byteIndex(s string, c byte) int
  3. //go:linkname 正确放在函数声明处.eg //go:linkname byteIndex strings.IndexByte,byteIndex是本地函数名,strings.IndexByte是第三方包函数名
  4. 提供.s文件,允许编译器去除-complete检测

这两种方法实现很简单,这里不列代码了

参考资料

https://sitano.github.io/2016/04/28/golang-private/

发现了英文博客的翻译

中文翻译


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:懒散

查看原文:代码包中如何只有函数声明,引用其他包的函数

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

973 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传