Page 15 - 《软件学报》2024年第6期
P. 15
陈金宝 等: DBI-Go: 动态插桩定位 Go 二进制的非法内存引用 2591
(a) issue44614 中的崩溃输出 (b) issue54247 中的崩溃输出
图 4 崩溃输出示例
另外一种途径 [27] 是通过标注的方式来进行检查的. 通过将预先人工标注的信息以及逃逸分析在 debug 模式下
打印输出的信息进行比对来验证逃逸分析算法的正确性.
这两种途径从本质上来讲都是通过标注测试的方式来实现的, 需要事先人工分析 Go 程序并进行标注, 其无
法用于验证实际应用程序中事先未知的错误.
此外, Go 的运行时系统在 GC 时会验证对象的指向是否有错, 但该方法的范围有限. 首先, 并不是所有错误的
指向都能被 GC 发现. 其次, 该验证机制将错误的指向延迟到 GC 时才进行检测, 不能及时发现问题, 且 GC 一旦
发现问题会直接 panic, 造成整个程序的崩溃, 可能在实际生产环境中造成不可弥补的损失. 最后, GC 的崩溃输出
较难理解, 难以帮助开发人员定位问题的发生位置, 为后续排查工作带来不便.
总之, Go 目前测试和验证方法十分有限, 在实际应用程序中发生错误时, 现有方法无法用于定位发生错误的
位置以及发生错误的原因. 图 1 中的各类 issue 也印证了 Go 现有方法的局限性.
1.3 研究动机
传统的内存相关漏洞的寻找工作多集中于 C/C++程序的二进制 [28−30] . 但由于 Go 程序有独特的运行时管理机
制以及独特的语义, 传统基于 C/C++的工作并不能直接应用在 Go 二进制上. 目前学术界在 Go 程序的漏洞寻找上
有不少工作, 但这些工作多集中在并发、数据竞争相关的漏洞寻找上 [17,18,31] . 目前缺少验证 Go 编译器生成的代码
是否满足 Go 逃逸不变式的研究.
如何保证 Go 编译器中逃逸分析的决策结果以及经过其他编译器优化遍之后生成的代码仍然符合逃逸不变
式是非常重要的. 然而, 正如第 1.2 节所述, Go 目前逃逸相关 issue 频发且无较为有效的测试手段, 且现有相关研
究缺失. 为了有效解决这类问题, 确保经过逃逸分析和一系列优化遍后生成的二进制可执行文件中没有违反 Go
逃逸不变式的情况, 研制一个可以有效检测实际应用中违反 Go 逃逸不变式情况的工具是很有必要的.
2 Go 程序逃逸不变式违例的判定规则
本节对 Go 程序进行抽象, 随后结合 Go 的逃逸不变式抽象出 Go 程序中违反逃逸不变式的判定规则.
2.1 Go 程序抽象
根据 Go 的文档 [23,32] , Go 应用程序的内存由全局数据区、受 GC 管理的 Go 堆区、每个 Goroutine 的栈区组
成. 基于此, 我们将一个 Go 程序的内存分为 3 部分: Goroutine 栈、Go 堆区以及 Go 全局数据区, 并提出如公式 (1)
W 由一个含有 n 个 GS 、一个 Go GH 、一个 GG 组
所示的抽象. 整个世界 Goroutine 的集合 堆区 Go 全局数据区
成. 每个 Goroutine G 由运行在其上的用户代码 C 以及对应的 Goroutine 栈 S 组成. GH 、 GG 、 S 均是内存地址
的集合, 其中: GH 和 GG 分别记录 Go 程序在用的有效堆地址和全局数据区地址; S 是 Goroutine 的栈地址, 由从
addr l 到 o addr h 的连续内存地址组成. C 是一个指令列表, 由于这里只关心潜在引起 Go 逃逸不变式违例相关的指
i