上周,Go 1.23 进入冻结期,这意味着不会添加任何新功能,已经添加的功能也不太可能被删除。今天,我们将讨论 Go 1.23 中 //go:linkname 指令的变化。
相关的 issue 是:cmd/link: lock down future uses of linkname · Issue #67401 · golang/go
注://go:linkname 指令不是官方推荐使用的指令,不能保证向前或向后的兼容性。明智的做法是尽可能避免使用该指令。
有鉴于此,让我们深入了解这些新变化。
linkname 的作用是什么
简单来说,链接名指令用于向编译器和链接器传递信息。根据使用情况,可将其分为三类:
1. Pull
pull
的用法是:
1 | import _ "unsafe" // Required to use linkname |
该指令格式为 //go:linkname <本地函数或包级变量> <本包或其他包中完全定义的函数或变量>。它告诉编译器和链接器 my_func 应直接使用 fmt.Println,使 my_func 成为 fmt.Println 的别名。
这种用法可以忽略函数或变量是否被导出,甚至可以使用包私有元素。不过,这种方法有风险,如果出现类型不匹配,可能会引起 panic。
2. Push
1 | import _ "unsafe" // Required to use linkname |
在这里,只需将函数或变量名作为第一个参数传递给指令,并指定使用该函数或变量的软件包名称。这种用法表示函数或变量将被命名为 xxx.yyy。
3. HandShake
1 |
|
这种用法意味着两端要握手,明确标记哪个函数或变量应被链接。
使用 linkname 的风险
主要风险是在软件包不知情的情况下使用软件包私有函数或变量的能力。 例如:
1 | // pkg/mymath/mymath.go |
通常,uintPow 不应在其软件包之外被访问。但 linkname 绕过了这一限制,可能导致严重的类型相关内存错误或运行时 panic。
linkname 也有有用的时候
尽管存在风险,但 linkname 的存在还是有其合理的原因,例如在 Go 程序启动时。例如,在 Go 的运行时: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()
}
这里,linkname 允许运行时调用用户定义的主函数。
Go 1.23 中对 linkname 的更改
考虑到上述风险,Go 核心团队决定限制链接名的使用:
- 新的标准库软件包将禁止 linkname。
- 新增了一个 ldflag -checklinkname=1 来执行限制。默认值为 0,但在 1.23 的最终版本中将设置为 1。
- 标准库将禁止只拉链接名,只允许握手模式。
例如,以下代码在 1.23 版中就无法编译:
1 | package main |
linkname 的未来
长期目标是只允许使用握手模式。作为开发者,我们应该:
- 使用 -checklinkname=1 审核并删除不必要的链接名使用。
- 建议在必要时公开私有 API。
- 最后,使用 -ldflags=-checklinkname=0 禁用限制。
总结
总之,我们还是应避免使用 //go:linkname
以防止出现不可预见的问题。