Golang 的騷操作:go:linkname

背景

  1. 在看源碼時,一些源碼方法沒有方法體,難道說明這些方法爲空?例如:time.Now調用的 now(), time.Sleep , reflect.makechan
// Provided by package runtime.
func now() (sec int64, nsec int32, mono int64)

func Sleep(d Duration)

func makechan(typ *rtype, size int) (ch unsafe.Pointer)
  1. 在寫代碼時,如果我們想使用別的包下沒有導出的方法或者變量時,怎麼操作

go:linkname 的用法

實際上,上述提到的三個沒有方法體的方法,其實現都在 src/runtime包下

  1. time.now timestub.go 文件中
//go:linkname time_now time.now
func time_now() (sec int64, nsec int32, mono int64) {
    sec, nsec = walltime()
    return sec, nsec, nanotime()
}
  1. time.Sleep time.go 文件中
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
   if ns <= 0 {
      return
   }

   gp := getg()
   t := gp.timer
   if t == nil {
      t = new(timer)
      gp.timer = t
   }
   t.f = goroutineReady
   t.arg = gp
   t.nextwhen = nanotime() + ns
   if t.nextwhen < 0 { // check for overflow.
      t.nextwhen = maxWhen
   }
   gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}
  1. reflect.makechan
//go:linkname reflect_makechan reflect.makechan
func reflect_makechan(t *chantype, size int) *hchan {
    return makechan(t, size)
}

我們可以看到具體實現都具有 go:linkname, 可以推測就是這個東西把兩個不同包下的函數鏈接到一起了

官方介紹

//go:linkname localname [importpath.name]

這個特殊的指令的作用域並不是緊跟的下一行代碼,而是同一個包下生效。//go:linkname告訴 Go 的編譯器把本地的(私有)變量或者方法localname鏈接到指定的變量或者方法importpath.name。簡單來說,localname import.name指向的變量或者方法是同一個。因爲這個指令破壞了類型系統和包的模塊化原則,只有在引入 unsafe 包的前提下才能使用這個指令。

使用實例

文件結構如下圖所示, inner 包模擬 go 源碼包/外部引入包,outer包需要調用inner包中的方法, main.go 調試

 tree
.
├── go.mod
├── inner
│   └── inner.go
├── main
├── main.go
└── outer
    ├── 1.s
    └── outer.go

空body方法復現:

模擬方法無 body方法,真實實現在另外一個包中的案例

outer.go

package outer

import (
    _ "github.com/he2121/demos/linkname_example/inner"    // 真實方法在 inner 包,必須引用這個編譯器才能找到鏈接
)

func Hello()

如果出現:missing function body 在包內加一個 .s文件。因爲go build默認加會加上-complete參數,加這個 .s 可繞開這個限制。

inner.go

package inner

import (
    _ "unsafe"
)

//go:linkname hello github.com/he2121/demos/linkname_example/outer.Hello
func hello()  {
    println("hello")
}

main.go

package main

import "github.com/he2121/demos/linkname_example/outer"

func main()  {
    outer.Hello()
}
// output:
// hello

這就是 go 源碼中這麼多沒有 body 的方法的原因了,一般到 src/runtime包中搜索就能發現 //go:linkname的實現。

實際使用

實現中,如果我們有一些騷操作需要使用源代碼/第三方包中的未導出變量/方法,就可使用 //go:linkname實現, 如下例子

outer.go: 使用 inner 包中的未導出變量/方法

package outer

import (
    _ "unsafe"

    _ "github.com/he2121/demos/linkname_example/inner"
)

//go:linkname A github.com/he2121/demos/linkname_example/inner.a
var A int

//go:linkname Hello github.com/he2121/demos/linkname_example/inner.hello
func Hello()

inner.go

package inner

var a = 100

func hello()  {
    println("hello")
}

main.go

 
package main

import "github.com/he2121/demos/linkname_example/outer"

func main()  {
    outer.Hello()
    println(outer.A)
}
// output
// hello
// 100

引用自:https://segmentfault.com/a/1190000041056240

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章