Page 23 - 《软件学报》2024年第6期
P. 23
陈金宝 等: DBI-Go: 动态插桩定位 Go 二进制的非法内存引用 2599
复现代码并可复现的 issue 共有 5 个, 分别为 issue#29000 [37] , issue#31573 [38] , issue#44614 [15] , issue#47276 [39] 和
issue#54247 [14] . 其涉及的 Go 版本为 Go1.11 至 Go1.17, 年份跨度为 2018–2022 年. 最终的结果显示, DBI-Go 的漏
洞覆盖率较高, 其可以检测出图 1 中所有可复现的例子中的违反 Go 逃逸不变式的 store 指令. 输出的 log 文件相
比于 Go 原始 GC panic 时产生的信息可以更清晰地展示出产生错误的指令及其所在的函数, 可以帮助更快定位原
因. 以 issue#44614 [15] 为例, 其简化版代码可见代码 3. 图 8(a) 为社区 issue 中 Go GC 的 log, 图 8(b) 为 DBI-Go 的
log. 相比于 GC 的 log, 其可以更精确的显示问题的产生地, 比如二进制指令地址以及所在的函数. 若能结合
DWARF 信息, 还可以找到对应的源代码的位置, 更便于问题定位.
(a) issue 中 GC log (b) DBI-Go log
图 8 GC log 与 DBI-Go log
Go 的标准库 (std) 和编译工具链 (cmd) 提供了大量测试用例和 Benchmark, 覆盖了其中的众多常用 API. Go
的标准库和编译工具链中共有 277 个包提供了测试用例. 我们使用最新版本的 Go (Go1.20.5), 将这些测试用例和
Benchmark 编译成可执行文件, 并随后使用 DBI-Go 进行检测. 结果表明, 在这 277 个包中的 276 个没有发现问题,
然而, 在 syscall 包中, DBI-Go 发现 Go 在处理切片的字面量时将栈上的数组地址存到了全局变量中. 它的简化版
中, 接下来的
0x8(SP) 存入了寄存器
示例如图 9 所示. LEAQ 0x8(SP), AX issue, 有待 Go 官方的进一步修复 指令
指令将栈对象的地址
MOVQ
AX
将该栈地址 store 到了某全局变量处. 该 store 违反了规则 1 并被 DBI-Go 所捕获.
经Go编译器
编译
图 9 syscall 包中发现的违反 Go 逃逸不变式的 store
目前该问题已经在 golang-nuts 中得到 Go 官方维护人员的确认 (https://groups.google.com/g/golang-nuts/
c/YZVFzwnPixM), 并已向社区提交 (https://github.com/golang/go/issues/61730).
除了上述对使用 Go 原生逃逸分析算法的编译器生成的二进制的测试, 我们还将 DBI-Go 用于评测一个在
Gollvm (一个基于 LLVM 的 Go 编译器) 上重构的 Go 逃逸分析算法. 通过用 DBI-Go 检测经重构逃逸分析算法后
的 Gollvm 生成的二进制中是否有非法的内存引用, 来判断重构后的新逃逸分析算法的正确性, 并辅助开发人员进
行 Debug. 在实际评测中, 用 DBI-Go 可以发现新逃逸分析算法引起的内存分配问题. 以代码 5 和代码 6 为例, 代
码 5 中的 New 函数将字面量 prefixError{}的地址返回出函数; 代码 6 中, 对象 b 的地址被全局对象 gm 获取, 根据
逃逸不变式 2 它们理应堆分配. DBI-Go 发现重构后的逃逸分析算法在特定场景下会将代码 5 中本应在堆中分配
的 prefixError{}以及代码 6 中本应在堆中分配的 b 均分配在栈上. 通过这些例子, DBI-Go 帮助新逃逸算法的开发