The magic of go:linkname

  sonic0002        2022-04-10 08:39:00       16,815        0    

When writing Go program, there is frequent need on using time.Sleep() function to pause the logic for some time. And if jumping to the definition of this function, can see below definition:

// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)

I's strange that there is no function body defined here. What happened? The actual definition of the function body is residing at runtime/time.go indeed.

// 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 = timer{}
    t.when = nanotime() + ns
    t.f = goroutineReady
    t.arg = gp
    tb := t.assignBucket()
    lock(&tb.lock)
    if !tb.addtimerLocked(t) {
        unlock(&tb.lock)
        badTimer()
    }
    goparkunlock(&tb.lock, waitReasonSleep, traceEvGoSleep, 2)
}

How is this linked to the original time.Sleep() function? The magic is at the line:

//go:linkname timeSleep time.Sleep

According to the official documentation:

//go:linkname localname importpath.name

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".

In above example, localname refers to timeSleep while importpath.name refers to time.Sleep. The reason for doing this is that time.Sleep is in time package and it's exported while timeSleep is in runtime package and it's un-exported. To be able to use it, this go:linkname is used. This is a bit hacky though.

Here is another example to demonstrate the usage. Assume there is a directory structure

➜  demo git:(master) ✗ tree
.
├── linkname
│   └── a.go
├── main.go
└── outer
    └── world.go

The contents in a.go

package linkname

import _ "unsafe"

//go:linkname hello examples/demo/outer.World
func hello() {
    println("hello,world!")
}

The contents in world.go

package outer

import (
    _ "examples/demo/linkname"
)

func World()

The contents in main.go

package main

import (
    "examples/demo/outer"
)

func main() {
    outer.World()
}

The output is

# examples/demo/outer
outer/world.go:7:6: missing function body

The reason for the error is that go build will run with -complete flag which detects the function has no body and will report error as it assume package has no non-Go components. To workaround this, just add a file with extension name .s.

➜  demo git:(master) ✗ tree
.
├── linkname
│   └── a.go
├── main.go
└── outer
    ├── i.s
    └── world.go

Run the program again and it will output

hello,world!

This trick is useful in some cases where there is really a need to access some un-exported functions in some packages. Use it cautiously.

Reference: https://studygolang.com/articles/15842

TRICKS  GOLANG  GO:LINKNAME 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Bad design or bad operation