Page 20 - 《软件学报》2024年第6期
P. 20
2596 软件学报 2024 年第 35 卷第 6 期
以检查这些在运行过程中发生改变的引用关系. 在 Go 程序中, 如果一个 store 操作可能存入指针类型, 则 Go 编译
器会在编译期间在该 store 操作周围生成特定的控制流, 插入 runtime.gcWriteBarrier 函数, 该函数在 GC 时会接管
相应的 store 操作, 并负责帮助 GC 维护正确的内存引用关系.
Go 编译器只会对用户代码中包含指针的 store 操作插入 runtime.gcWriteBarrier, 且不会对一些可能带来误报
的运行时函数中的 store 操作插入该函数. 若能够在二进制中识别相应的结构, 就能恢复 Go 的 store 语义, 大大减
轻由于 Go 运行时、非指针 store 等带来的误报问题, 同时还能够减少很多对不必要的 store 插桩, 减轻动态二进制
插桩带来的额外开销.
3.2.3 利用 Go 的写屏障机制恢复 store 指针的语义
编译生成的与 runtime.gcWriteBarrier 相关的汇编级控制流模式如图 7 所示. 由于编译器在编译时在 store 周
围插入的特定控制流有固定的结构, 所以其最后生成的二进制中围绕 runtime.gcWriteBarrier 也有特定的结构. 将
这些特征总结抽象, 可以形成图 7 中的特定控制流模式, 以便后续精准识别.
5. bb 3 =BB(j.target) j 的目的地址处的基本块
源代码
编译器转换后代码模式
二进制控制流模式
图 7 编译生成的 gcWriteBarrier 相关控制流
我们提出算法 1 来识别 Go 二进制中该特定的控制流模式. 其主要思路在于通过寻找特定的 cmpl 指令来确
定满足要求的基本块 bb 1 , 再通过 bb 1 中的控制流跳转语句 (JNE) 来确定满足要求的两个后继基本块 bb 2 和 bb 3 ,
要求 bb 2 和 bb 3 有且仅有一个相同的后继.
算法 1. 识别图 7 中的控制流并为其中满足 store 指针语义的指令设置回调函数.
输入: Go 二进制文件 Bin;
输入: Bin 中全局变量 runtime.writeBarrier 的地址 wb.
运行结果: 识别出 Bin 中满足 Go 的 store 语义的指令, 并为其在 Pin 中注册运行时的回调函数
1. FOR ALL instr in Bin DO
2. IF instr.opcode==cmpl THEN
3. IF instr.operands[0]==0 且 instr.operands[1] 的有效地址 == wb THEN
4. j=instr 之后最近的 jne 指令, 且该 jne 之前没有其他跳转指令
6. bb 2 = BB(j.next) j 的下一条指令处的基本块
7. IF len(successors(bb 2 ))==1 且 successors(bb 2 )==successors(bb 3 ) THEN
8. FOR ALL s:store in bb 2 DO
9. 在 store 指令 s 前注册运行时回调函数
10. END FOR
11. FOR ALL c:call in bb 3 DO
12. IF c.targetFunc 以 runtime.gcWriteBarrier 为前缀 THEN