主要观点:软件影子栈在 Go 运行时中比 go1.21 中的帧指针展开能提供高达 8 倍更快的栈跟踪捕获速度,但这一想法不应立即应用,只是展示了硬件加速栈跟踪捕获的潜在未来。
关键信息:
- 捕捉本地语言栈跟踪本质是遍历栈上所有调用帧并收集存储在其中的返回地址,最简单方法是编译器添加前言,Go 1.21 实现了基于帧指针展开的快速栈跟踪捕获。
- 由于历史原因,除 Go 外的编译器一般在优化构建中省略帧指针,需使用 DWARF 或其他展开表来查找栈帧距离,Bytehound 分析器基于 DWARF 且预处理提高查找性能,但帧指针展开仍优于此。
- 反复捕获栈跟踪常涉及重复工作,可通过维护返回地址缓存(影子栈)避免,可由编译器或硬件实现,Linux 内核中现代 Intel CPU 已实现硬件影子栈但用户空间无法访问。
- 在 Go 中实现影子栈,添加新的
shadowStack
字段,ShadowFPCallers
函数结合帧指针展开和影子栈,shadowTrampolineASM
和shadowTrampolineGo
函数处理影子栈操作,需教 Go 的查找表展开器处理注入的跳板函数。 - 基准测试显示在固定栈深度的最佳情况下,
ShadowFPCallers
比FPCallers
快 8 倍,但在最坏情况下(栈深度变化),影子栈方法仅在栈深度略大于 32 帧时有竞争力,更低栈深度时慢 4 倍,可通过一些方式缓解最坏情况。
重要细节: - 实现细节包括在
g
结构体中添加shadowStack
字段,ShadowFPCallers
函数中的帧指针展开循环、特殊返回地址标记及影子栈更新逻辑,shadowTrampolineASM
和shadowTrampolineGo
函数的作用等。 - 在线讨论链接包括 Reddit 和 Hacker News。
- 影子栈的缺点包括低栈深度时最坏情况性能问题、即将到来的硬件安全功能可能破坏此方法、外部展开器会产生错误栈跟踪、使用类似技术的程序可能会中断、需小心避免破坏恐慌等情况以及每个栈帧需额外 8 字节内存。考虑到 Go 执行跟踪中帧指针展开的开销小于百分之几,该技术虽有趣但目前不适用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。