Featured image of post Golang 1.23: Changes to `//go:linkname` and What It Means for Developers

Golang 1.23: Changes to `//go:linkname` and What It Means for Developers

 

Last week, Go 1.23 entered the freeze period, meaning no new features will be added, and any already added features are unlikely to be removed. This is a great opportunity to preview the upcoming changes.

This article is first published in the medium MPP plan. If you are a medium user, please follow me in medium. Thank you very much.

Discussion on Google Groups

Today, we will discuss the changes to the //go:linkname directive in Go 1.23.

Related issue: GitHub Issue #67401

TL;DR: The //go:linkname directive is not officially recommended and does not guarantee any forward or backward compatibility. It is wise to avoid using it whenever possible.

With that in mind, let’s dive into the new changes and see how they relate to us.

What Does the linkname Directive Do?

In simple terms, the linkname directive is used to pass information to the compiler and linker. Depending on its usage, it can be divided into three categories:

1. Pull

The “pull” usage is as follows:

1
2
3
4
5
6
import _ "unsafe" // Required to use linkname

import _ "fmt" // The pulled package must be explicitly imported (except the runtime package)

//go:linkname my_func fmt.Println
func my_func(...any) (n int, err error)

This directive format is //go:linkname <local function or package-level variable> <fully defined function or variable in this or another package>. It tells the compiler and linker that my_func should directly use fmt.Println, making my_func an alias for fmt.Println.

This usage allows ignoring whether functions or variables are exported, pulling even package-private elements into use. However, this method is risky and can lead to panics if there’s a type mismatch.

2. Push

The “push” usage looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import _ "unsafe" // Required to use linkname

//go:linkname main.fastHandle
func fastHandle(input io.Writer) error {
...
}

// package main
func fastHandle(input io.Writer) error

// The main package can directly use fastHandle

Here, you only need to pass the function or variable name as the first parameter to the directive, specifying the package name where it should be used. This usage signifies that the function or variable will be named xxx.yyy.

3. Handshake

The “handshake” usage combines both methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package mypkg

import _ "unsafe" // Required to use linkname

//go:linkname fastHandle
func fastHandle(input io.Writer) error {
...
}

package main

import _ "unsafe" // Required to use linkname

//go:linkname fastHandle mypkg.fastHandle
func fastHandle(input io.Writer) error

The pull side remains unchanged, but the push side doesn’t need to specify the package name. This usage implies a handshake between the two ends, clearly marking which function or variable should be linked.

Risks of linkname

The primary risk is the ability to use package-private functions or variables without the package’s knowledge. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// pkg/mymath/mymath.go
package mymath

func uintPow(n uint) uint {
    return n * n
}

// main.go
package main

import (
    "fmt"
    _ "linkname/pkg/mymath"
    _ "unsafe"
)

//go:linkname pow linkname/pkg/mymath.uintPow
func pow(n uint) uint

func main() {
    fmt.Println(pow(6)) // 36
}

Normally, uintPow shouldn’t be accessible outside its package. But linkname bypasses this restriction, which can lead to severe type-related memory errors or runtime panics.

Positive Aspects of linkname

Despite its risks, linkname exists for valid reasons, such as during the startup of Go programs. For example, in Go’s runtime:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// runtime/proc.go

//go:linkname main_main main.main
func main_main()

// runtime.main
func main() {
    fn := main_main
    fn()
}

Here, linkname allows the runtime to call the user-defined main function.

Changes to linkname in Go 1.23

Given the risks, the Go core team has decided to limit linkname usage:

  1. New standard library packages will prohibit linkname.
  2. A new ldflag, -checklinkname=1, has been added to enforce restrictions. It defaults to 0 but will be set to 1 in the final release of 1.23.
  3. Pull-only linkname will be prohibited for the standard library, allowing only the handshake mode.

For instance, the following code will no longer compile in 1.23:

1
2
3
4
5
6
7
8
package main
import _ "unsafe"
//go:linkname corostart runtime.corostart
func corostart()

func main() {
    corostart()
}

Future of linkname

The long-term goal is to only allow handshake mode. As developers, we should:

  1. Use -checklinkname=1 to audit and remove unnecessary linkname usage.
  2. Propose to make private APIs public if necessary.
  3. As a last resort, disable the restriction with -ldflags=-checklinkname=0.

Conclusion

In summary, avoid using //go:linkname to prevent unforeseen issues.

Licensed under CC BY-NC-SA 4.0
Last updated on May 27, 2024 09:40 CST
Built with Hugo
Theme Stack designed by Jimmy