Go 語言 go:linkname 用法示例
先看 Go 文檔的說明:
The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported “unsafe”.
go:linkname 是 Go 語言支持的一種指令,這個指令告訴編譯器, 使用導入包的函數或者變量,作爲當前包的函數或者變量在目標文件符號表中的符號。由於這個指令直接修改符合表,故不受函數或者變量訪問權限的限制。
本文將舉例說明 go:linkname 指令的比較 hack 的用法。
GOPATH 的 src 目錄結構如下:
goref/
pkga/
pkga.go
main.go
main.s
引用導入包的變量
goref/pkga/pkga.go 代碼如下:
package pkga
var a = 1
goref/main.go 代碼如下:
package main
import (
"fmt"
_ "goref/pkga"
_ "unsafe"
)
var a int
func () {
fmt.Println(a)
}
運行結果:
1
這裏需要注意,go:linkname 指令必須在 var 之前,如果這樣寫:
var (
a int
)
是不能正常工作的!必須得這樣:
var (
a int
)
修改導入包的變量
goref/pkga/pkga.go 代碼如下:
package pkga
var a = 1
func Geta() int {
return a
}
goref/main.go 代碼如下:
package main
import (
"fmt"
"goref/pkga"
_ "unsafe"
)
var a int
func () {
a = 4
fmt.Println(pkga.Geta())
a++
fmt.Println(pkga.Geta())
}
運行結果:
4
5
這裏需要注意,main 包中不能直接在語句 var a int 爲 a 賦值,如果改成 var a = 4,則連接器會報錯:
# goref
2019/02/10 12:14:14 duplicate symbol goref/pkga.a (types 28 and 28) in main and /Users/apple/Library/Caches/go-build/2a/2a48e7dd65bee2403ccf05bc434242ce87ee52507dbdbaa63d332827a54617aa-d(_go_.o)
可以引用當前包的變量
goref/main.go 代碼如下:
package main
import (
"fmt"
_ "unsafe"
)
var a = 1
//go:linkname a2 main.a
var a2 int
func () {
fmt.Println(a, a2)
}
運行結果:
1 1
此用法可模擬 C++ 語言中的引用。
但是隻能引用一次
例如, 以下代碼:
package main
import (
"fmt"
_ "unsafe"
)
var a = 1
//go:linkname a2 main.a
var a2 int
//go:linkname a3 main.a
var a3 int
func () {
fmt.Println(a, a2, a3)
}
是不能生成目標文件的,連接器報錯如下:
# command-line-arguments
duplicate main.a
<autogenerated>:1: symbol main.a listed multiple times
修改一下代碼, 如下:
package main
import (
"fmt"
_ "unsafe"
)
var a = 1
//go:linkname a2 main.a
var a2 int
//go:linkname a3 main.a2
var a3 int
func () {
fmt.Println(a, a2, a3)
}
運行結果:
1 1 0
a3 引用不到 a 的值,所以說只能引用一次。
引用導入包的私有成員函數
由於函數的操作與上文介紹的變量的操作大同小異,故此部分直開始介紹,如何引用導入包的私有成員函數。
goref/pkga/pkga.go 代碼如下:
package pkga
import (
"fmt"
)
type Student struct {
Name string
}
func (s *Student) say() {
fmt.Println(s.Name)
}
goref/main.go 代碼如下:
package main
import (
"goref/pkga"
_ "unsafe"
)
//go:linkname say goref/pkga.(*Student).say
func say(s *pkga.Student)
func main() {
s := pkga.Student{
Name: "shi",
}
say(&s)
}
運行結果:
shi
核心在於 func (s *Student) say() 實際上是 func say(s *Student) 的語法糖。這個函數在符號表中,表示爲 goref/pkga.(*Student).say
如果是要引用私有對象的私有成員函數呢, 也沒有關係
goref/pkga/pkga.go 代碼如下:
package pkga
import (
"fmt"
)
type student struct {
Name string
}
func (s *student) say() {
fmt.Println(s.Name)
}
goref/main.go 代碼如下:
package main
import (
_ "goref/pkga"
_ "unsafe"
)
type st struct {
d string
}
//go:linkname say goref/pkga.(*student).say
func say(*st)
func main() {
s := st{
d: "shi",
}
say(&s)
}
運行結果:
shi
關鍵在於,構造一個成員類型完全一樣的結構體, 如上述的
type student struct {
Name string
}
爲此構造結構體:
type st struct {
d string
}
名稱可以不一樣,但是類型必須完全一樣(例如這裏的 string), 否則可能會出現非預期錯誤。
本文將舉例說明 go:linkname 指令的比較 hack 的用法,可以看到,這個指令主要是突破外部包函數和變量訪問權限的限制,運用不當可能會出現非預期錯誤。除非萬不得已,否則就不建議使用了。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://www.dazhuanlan.com/2020/02/01/5e34dcf85f4c9/