Page 13 - 《软件学报》2024年第6期
P. 13
陈金宝 等: DBI-Go: 动态插桩定位 Go 二进制的非法内存引用 2589
Go 的逃逸分析在决定对象是堆分配还是栈分配时必须遵循上述不变式. 逃逸分析若发现有对象违反上述不
变式 (即违例), 则该对象会被堆分配. 如代码 1 所示, 其中 heapObj 是堆对象, 其在第 5 行引用了 i 的地址. 因此根
据逃逸不变式 1: “指向栈对象的指针不可存储在堆中”, i 需要堆分配. 在代码 2 中, ptr 在第 6 行引用了 x 的地址.
ptr 在外层循环声明, x 在内层循环隐式声明, ptr 的生命期长于 x. 因此根据逃逸不变式 2: “指向栈对象的指针生命
期不可超出该栈对象”, x 需要堆分配.
代码 1. 逃逸不变式 1 示例.
1. //堆对象获取了 i 的指针导致 i 被堆分配
2. func heapAssignment() {
3. heapObj := newobj()
4. i := 1
5. heapObj.a = & i
6. use(heapObj)
7. }
代码 2. 逃逸不变式 2 示例.
1. //ptr 引用 x, ptr 循环深度小于 x, 生命期长于 x, x 堆分配
2. func loop() {
3. var ptr *int
4. for i := 0; i < 5; i++ {
5. x := i
6. ptr = & x
7. }
8. use(ptr) 所示. 在该例子中, 对象
9. }
逃逸分析必须正确地决定对象的分配位置, 若有对象的分配位置违反了上述两个逃逸不变式之一, 即存在非
法内存引用, 则会导致 Go 程序运行时的异常行为甚至导致运行时崩溃 (panic), 在实际应用环境中造成较大损失.
1.2 Go 逃逸问题现状
1.2.1 Go 社区中逃逸类相关问题频发
在社区 issue 中, Go 的逃逸问题频发, 且每次出现的问题都是致命问题. 近几年出现的相关 issue 的不完全统
计可见图 1. 这些 issues 有的是因为逃逸分析算法的错误导致, 有的是因为逃逸分析和其他编译优化的错误配合
导致. 下面将通过这些 issue 中的两个典型例子来进行说明.
• 逃逸分析算法的错误. 逃逸分析算法的错误会直接导致一些对象的分配位置出错. 以 issue#44614 [15] 为例,
其一个简化版的实例如代码 3 r 由于被全局变量 sink 引用而被堆分配. 函数 global2stack
中的参数 p, 在 return p 语句中被堆对象 r 获取, 因此所有传给 global2stack 的指针理应被标记为逃逸, 但由于逃逸
分析的漏洞, 逃逸分析在分析 global2stack 函数时认为参数 p 没有逃逸. 这就导致了在代码 3 中, 地址被传给
global2stack 的变量 i 理应被堆分配, 但却被逃逸分析错误地认为应该栈分配.
代码 3. issue44614 简化版示例.
1. var sink interface{}
2. func global2stack(p *int) (r *int) {
3. sink = & r