惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
O
OpenAI News
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
罗磊的独立博客
S
SegmentFault 最新的问题
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
雷峰网
雷峰网
V
Visual Studio Blog
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog
博客园 - 【当耐特】
G
Google Developers Blog
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
Martin Fowler
Martin Fowler
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
AI
AI
Google Online Security Blog
Google Online Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

文章列表 on polarisxu

通过一个例子让你彻底掌握 Go 工作区模式 这道题正确率竟然只有 22% Go 开源项目推荐:一个简单的 Go 练手项目 go test 如何禁用缓存? Go1.18 快讯:constraints 包被移除标准库 跟着 Go 作者学泛型 「2022 版」轻松搞定 Go 开发环境 鹅厂小哥用行动反对加班:程序员加班能被终结吗? Go 为什么选择 Gopher 作为吉祥物? 本地如何配置多个 GitHub/Gitee 账号? 为 Java 开发者准备的 Go 教程 02:Java 有而 Go 无 GitHub 发现了 studygolang 项目依赖的漏洞 Typora 宣布收费后,这款开源 Markdown 编辑器火了 为 Java 开发者准备的 Go 教程 01:漫游了 我的 2021 年终总结 泛型版 singleflight:Go 中如何防止缓存击穿? 2021 年你写了多少代码?这个 Go 工具帮你统计 Go1.18 新特性:TryLock Go 泛型入门教程 程序员瑞士军刀:各种结构的转换工具 终于有 Go 版的 Elasticsearch 了 Go泛型系列:maps 包讲解 Go 1.18 中的 any 是什么? Go1.18 快讯:新的 IP 包 Go编程模式:详解函数式选项模式 Go泛型系列:slices 包讲解 Nginx 竟然也有 playground — Go 语言构建的 Go泛型系列:Go1.18 类型约束那些事 Go1.18 快讯:Module 工作区模式 Go1.18 快讯:新增的 Cut 函数太方便了 Go1.18 快讯:废弃了这个 API Go1.18 快讯:新增字符串 Clone API Go:如何获得项目根目录? Go 如何获取和设置环境变量 重磅变动:更快找到微信公众号里的Go文章 Go 这样设置版本号:我们的项目也可以 假期结束,推荐 2 本 manning 出的 Go 图书 Go Fiber 框架系列教程 04:测试应用 Go Fiber 框架系列教程 03:中间件 Go泛型系列:提前掌握Go泛型的基本使用 推荐一个 Go GUI 实战项目 Go Fiber 框架系列教程 02:详解相关 API 的使用 Go Fiber 框架系列教程 01: 和 Express 对比学习 Go Module:私有不合规库怎么解决引用问题 Go1.17 新特性:go get 变了 Go1.17 新特性:testing 包的相关变化 StackOverflow 上关于 Go select 死锁的问题 新书推荐:用 Gin 框架构建分布式应用 Go 官网要变天。。。 Gin 这是要成为 Go 官方框架? 扬眉吐气:刚刚,Go 已经默认支持泛型了 Go 1.17 新特性:Module 有哪些变化? Go 第三方库推荐:类型转换如此简单 担心密码提交到 GitHub?建议使用这个 Go 开源工具 Go 的时间格式化为什么是 2006-01-02 15:04:05? GitHub 为 Go 社区带来安全支持 Go开源项目推荐:500行代码确认请求时间花在哪 一道关于 len 函数的诡异 Go 面试题解析 厉害了我的 Go:推荐系统都有开源实现 Go1.17 新特性:新版构建约束 这个工具真好:看看你的Go项目依赖有无漏洞 网友很强大,发现了Go并发下载的Bug Rust 劝退系列 09:函数 Go项目实战:一步步构建一个并发文件下载器 这本 Go 新书挺期待的:100 个常见错误 Go 启用新的官方问答社区 这是要干嘛?!微软招 Go 编译器全职开发人员 Go1.17 快报之标准库越来越注重易用性 Go1.17 新特性之切片变数组 回顾 Go 官网的演变史 站长8年前的Go代码竟然进入大厂的项目里了 Go图书翻译:一个好消息,一个坏消息 不怕烂尾!决定组织翻译这本 Go 图书 Rust 劝退系列 08:模式匹配 一本花了2.5年写成的Go免费在线图书 被黑惨了:一句话,说明自己会 Go,咋整? Go1.16 中的新函数 signal.NotifyContext 怎么用? Go 1.17 新特性提前学之测试执行随机化 Rust 劝退系列 07:流程控制 官方的 Go 多版本管理:使用和原理 Rust新书:给你一个劝退的理由 周刊题解:常量表达式这个规则应该了解下 盘点那些使用 Go 语言的国外公司 编写了50万行Go代码是一种什么体验 Rust 劝退系列 06:常量 Uber 使用 Go 的规模这么大?!都自己定制的 Go 编译器了 Go Team Leader — rsc 大神新开源了一个库,增强模板功能 再一次看到了 Go 的节制:int128 类型要不要支持? Rust 劝退系列 05:复合数据类型 「卷」有理论依据:海勒姆定律—Go又是怎么卷的 Go 真的也可以进行 GUI 开发:还有这样的图书呢 Rust 劝退系列 04:基本数据类型 我的 Go 语言书单 Rust 劝退系列 03:变量 我又来推荐免费 Go 新书了:一本用 Go 讲架构的书 这个功能,公众号、微信群会不会被玩坏? 一道 Go 闭包题,面试官说原来自己答错了:面别人也涨知识 Rust 劝退系列 02:第一个 Rust 程序 Rust 劝退系列 01:打造开发环境 答应我,这次一定彻底搞懂 Go 中的类型别名
注释竟然还有特殊用途?一文解惑 //go:linkname 指令
2021-04-15 · via 文章列表 on polarisxu

大家好,我是站长 polarisxu。

我之前写过一篇文章:为什么 Go 标准库中有些函数只有签名,没有函数体? ,其中有一点就是 //go:linkname 这个指令。

Go 中类似的指令挺多的,比如 Go1.16 中的 //go:embed。前些天有人问我,为什么它用 //go:embed 不起作用?我一看,它是这么写的:// go:embed,不知道你看到问题了没有?是的,指令是通过注释的方式,但有三点要求,要特别注意:

  • // 后不能有空格。有些人可能习惯 // 后不加空格。但一般认为,// 后应该加一个空格。不过 go 指令却要求不能有空格,这是一个小“坑”,得注意。所以上面那位朋友就是加了空格,导致出问题。(程序并不会报错,只是没有得到自己想要的结果)
  • 代码和指令之间不能有空行或其他注释。这一点应该还好,很多人不会用错吧;
  • 一般来说,使用指令需要导入相应的包。比如 //go:linkname 指令要求导入 unsafe 包,一般会 import _ "unsafe”//go:embed 指令,要求导入 embed 包。

有另外一位 Go 朋友「橘中秘士」微信私聊我:

大佬好,能不能写一篇 linkname 的文章。目前已经有了一些初步概念,但是尚有一些疑团不是特别清晰。

//go:linkname localname remotename,其中 local 作为占位符 remote 作为实现者或者 local 作为实现者 remote 作为占位符都是可以的。目前理解的就是给 Symbol 添加了一个 Linkname,查找 Symbo l的时候用 remote。

譬如 //go:linkname runtimeNano runtime.nanotime,runtimeNano 作为占位符 runtime.nanotime 提供实现,任何调用 runtimeNano 的地方实际替换为对 runtime.nanotime 的调用,这种场景比较容易接受。

譬如 //go:linkname runtime_cmpstring runtime.cmpstring,runtime_cmpstring 提供实现 runtime.cmpstring作为占位符,是不是这时符号表里不存在 runtime_cmpstring 只有 runtime.cmpstring?

经过简单沟通,他写了一篇文章解决自己的困惑。希望对各位有帮助。以下是他写的关于 //go:linkname 的文章(我做了一些调整)。


01 格式

//go:linkname local remote

remote 可以没有,此时 remote 使用 local 的值,效果就是 local 被导出。

02 local 和 remote 同时为函数

local 作为占位符,remote 作为实现者

标准库中的例子:

// 来自 time 包
//go:linkname runtimeNano runtime.nanotime
func runtimeNano() int64

// 来自 runtime 包
//go:nosplit
func nanotime() int64 {
	return nanotime1()
}

此时二进制文件中并没有runtimeNano,直接转化为对runtime.nanotime的调用。

local 作为实现者,remote 作为占位符

同样来自标准库。这里存在函数没有函数体,但是被反向引用。

// 在标准库的一个 internal 中
//go:linkname runtime_cmpstring runtime.cmpstring
func runtime_cmpstring(a, b string) int {
	l := len(a)
	if len(b) < l {
		l = len(b)
	}
	for i := 0; i < l; i++ {
		c1, c2 := a[i], b[i]
		if c1 < c2 {
			return -1
		}
		if c1 > c2 {
			return +1
		}
	}
	if len(a) < len(b) {
		return -1
	}
	if len(a) > len(b) {
		return +1
	}
	return 0
}

// 来自 runtime
func cmpstring(string, string) int

此时二进制文件中并没有runtime_cmpstring,对应的函数已经被命名为runtime.cmpstring。也就是说,实现在 internal 包,但最终通过 runtime.cmpstring 来引用。

一个占位符+一个汇编函数

// 在标准库的一个 internal 中
//go:linkname abigen_runtime_memequal runtime.memequal
func abigen_runtime_memequal(a, b unsafe.Pointer, size uintptr) bool

注意runtime.memequal的实现并不在runtime包中,使用汇编实现的话并不要求必须在相应的包中。

# memequal(a, b unsafe.Pointer, size uintptr) bool
TEXT runtime·memequal(SB),NOSPLIT,$0-25
	MOVQ	a+0(FP), SI
	MOVQ	b+8(FP), DI
	CMPQ	SI, DI
	JEQ	eq
	MOVQ	size+16(FP), BX
	LEAQ	ret+24(FP), AX
	JMP	memeqbody<>(SB)
eq:
	MOVB	$1, ret+24(FP)
	RET

03 local 和 remote 同时为变量

两个常规变量

//go:linkname overflowError runtime.overflowError
var overflowError error

//go:linkname divideError runtime.divideError
var divideError error

//go:linkname zeroVal runtime.zeroVal
var zeroVal [maxZero]byte

//go:linkname _iscgo runtime.iscgo
var _iscgo bool = true

//go:cgo_import_static x_cgo_setenv
//go:linkname x_cgo_setenv x_cgo_setenv
//go:linkname _cgo_setenv runtime._cgo_setenv
var x_cgo_setenv byte
var _cgo_setenv = &x_cgo_setenv

//go:cgo_import_static x_cgo_unsetenv
//go:linkname x_cgo_unsetenv x_cgo_unsetenv
//go:linkname _cgo_unsetenv runtime._cgo_unsetenv
var x_cgo_unsetenv byte
var _cgo_unsetenv = &x_cgo_unsetenv

一个占位符+一个伪符号

//go:linkname runtime_inittask runtime..inittask
var runtime_inittask initTask

//go:linkname main_inittask main..inittask
var main_inittask initTask

注意是..inittask不是.inittask,而且.inittask只存在于编译阶段,任何包中都无法声明该变量。

这里额外解释下 ..inittask 为什么两个点。第一个点就是普通的 runtime. 这种调用方式,第二个点和 inittask 一起构成一个符号(变量)。注意,Go 中的变量是不允许以 . 开头的,所以,这个叫伪符号,只在不编译阶段存在。

04 一个例子

研究 //go:linkname 是因为如下的背景:

Java 里有 InheritableThreadLocal,SpringWeb 在 ServletActionContext 里使用它,达到在任何地方都能方便的获取HttpServletRequest。

Go 并没有提供类似的机制,即使通过 stack 找到 goroutine id(99% 的文章都是这么介绍的),再配合 sync.Map,也只是实现了一个比较粗糙的 ThreadLocal,在子协程里仍然获取不到父协程的内容。

g.label 虽然不是给这种场景准备的,但它具备了 InheritableThreadLocal 的一切要求,只要我们能够访问到 label 私有字段,我们就有了完整版的 InheritableThreadLocal。

下面这个例子是作者真实项目中用的。

在 runtime 和 runtime/pprof 包中有两个函数:runtime_setProfLabel 和 runtime_getProfLabel。其中,runtime 包中的提供了实现,而 pprof 中的没有提供实现。如果基于它们创建另外的函数,如下:

//go:linkname SetPointer runtime/pprof.runtime_setProfLabel
func SetPointer(ptr unsafe.Pointer)

//go:linkname GetPointer runtime/pprof.runtime_getProfLabel
func GetPointer() unsafe.Pointer

根据前面的分析,虽然runtime.runtime_setProfLabel/runtime.runtime_getProfLabel提供了函数实现,但是二进制文件中并不会出现(见下方代码),此时想要调用必须通过runtime/pprof.runtime_setProfLabel/runtime/pprof.runtime_getProfLabel,这也是上面linknamepprof而不是runtime的根本原因。

// 来自 runtime 包
//go:linkname runtime_setProfLabel runtime/pprof.runtime_setProfLabel
func runtime_setProfLabel(labels unsafe.Pointer) {
	if raceenabled {
		racereleasemerge(unsafe.Pointer(&labelSync))
	}
	getg().labels = labels
}

// 来自 runtime/pprof 包
func runtime_setProfLabel(labels unsafe.Pointer)

// 来自 runtime 包
//go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel
func runtime_getProfLabel() unsafe.Pointer {
	return getg().labels
}

// 来自 runtime/pprof 包
func runtime_getProfLabel() unsafe.Pointer

05 总结

Go 中有不少指令,有些指令你可能不太需要关心,也不会用到。然而有些指令了解它们的意思,对阅读相关代码很有帮助。

这篇文章全面介绍了 //go:linkname 指令,不知道是否彻底解除了你的疑惑?欢迎留言交流!