导出 golang 中的私有 (unexported) 项的方法

本文将以 go 标准库中的 sync/pool 为例探讨导出 unexported 项的方法

导出私有全局变量

通过 go:linkname 来导出私有全局变量

1
2
3
4
5
6
7
8
9
10
11
12
package main

import _ "unsafe"

//go:linkname poolRaceHash sync.poolRaceHash
var poolRaceHash [128]uint64

func main() {
// main.poolRaceHash is an alias of sync.poolRaceHash
// -> unexported item sync.poolRaceHash is stolen
var _ = poolRaceHash
}

导出私有 struct field

可通过裸指针直接操作内存来导出私有 field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"sync"
"unsafe"
)

type Pool struct {
noCopy noCopy

local unsafe.Pointer
localSize uintptr

New func() interface{}
}

type noCopy struct{}

func main() {
// literally construct a `sync.Pool`
var externPool = sync.Pool{
New: func() interface{} {
return nil
},
}
var pool = (*Pool)(unsafe.Pointer(&externPool))
// unexported item `sync.Pool.local` is stolen
_ = pool.local
}

导出私有 function 或 method

方法 1: 通过 go:linkname

第一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
"sync"
"unsafe"
)

type Pool struct {
noCopy noCopy

local unsafe.Pointer
localSize uintptr

New func() interface{}
}

type noCopy struct{}

type poolLocal struct {
private interface{}
shared []interface{}
sync.Mutex
pad [128]byte
}

//go:linkname pinSlow sync.(*Pool).pinSlow
func pinSlow(p *sync.Pool) *poolLocal

//go:linkname (*Pool).pinSlow sync.(*Pool).pinSlow
func (p *Pool) pinSlow() *poolLocal

//go:linkname poolRaceAddr sync.poolRaceAddr
func poolRaceAddr(x interface{}) unsafe.Pointer

func main() {
// literally construct a `sync.Pool`
var externPool = sync.Pool{
New: func() interface{} {
return nil
},
}
var pool = (*Pool)(unsafe.Pointer(&externPool))

// main.pinSlow is an alias of sync.(*Pool).pinSlow
// -> unexported item sync.(*Pool).pinSlow is stolen
var _ = pinSlow(&externPool)

// main.(*Pool).pinSlow is an alias of sync.(*Pool).pinSlow
// -> unexported item sync.(*Pool).pinSlow is stolen
var _ = (*Pool).pinSlow(pool)

// main.poolRaceAddr is an alias of sync.poolRaceAddr
// -> unexported item sync.poolRaceAddr is stolen
var _ = poolRaceAddr(nil)
}

第二步

在项目中的新建一个空白的、任意名称的汇编代码文件(比如 asm.s)以帮助编译器完成函数完整性检查

1
2
3
go_export
├── asm.s
└── main.go

第三步

1
$ go build

方法 2: 直接获取运行时中的函数指针

go-forceexport 提供了这种便利

注意事项

  • 当使用 go:linkname 导出非标准库中的私有项时,须在当前包中显式地 import 来源包

参考资料https://sitano.github.io/2016/04/28/golang-private/