Page 25 - 《软件学报》2024年第6期
P. 25
陈金宝 等: DBI-Go: 动态插桩定位 Go 二进制的非法内存引用 2601
初始化开销是惊人的. 为了了解该部分比值为何如此之高, 我们将 R i/ 大于 100 的部分单独进行分析. 通过分析发
o
现, 这部分例子原生开销很小, 均不超过 10 ms, 与其相对应的 DBI-Go 的初始化开销均在 500 ms 左右, 如图 12 所
示, 仅在极个别例子上初始化开销超过了 1 500 ms. 这表明 DBI-Go 的初始化开销的下限在 500 ms 左右, 因此在遇
到原生开销很小, 只有几毫秒的测例时显得 R i/ 很大. 但实际上, 500 ms 的初始化开销是完全可以接受的.
o
0.8
callback overhead 0.008 init overhead
0.7
0.007
0.6
0.006
0.5
0.005
密度 0.4 密度 0.004
0.3
0.003
0.2 0.002
0.1 0.001
0 0
0 2 4 6 8 10 12 0 100 200 300 400 500
额外开销相比与原生开销比值 额外开销相比与原生开销比值
图 10 R c/ 的核密度分布曲线 图 11 R i/o 的核密度分布曲线
o
3 000 000
DBI-Go 初始化开销 (μs) 2 000 000
2 500 000
1 500 000
1 000 000
500 000
0 所示. 从中看出, 单独使用措施
2 228 2 486 2 617 2 738 2 808 2 882 2 905 2 978 3 004 3 071 3 220 3 300 3 479 3 558 3 662 3 803 3 896 3 926 3 964 4 338 5 032 6 429
原生开销 (μs)
图 12 比值大于 100 的额外初始化开销与原生开销
4.3 误报率测试
为了验证 DBI-Go 所利用的 Go 写屏障机制以及第 3.4 节中的两个措施对误报率的影响, 对这些措施进行了
单独或组合的测试. 在下文中, 使用“措施 1” 来代表第 3.4 节中的“过滤掉非 Go 函数” 措施; 使用“措施 2” 来代表
第 3.4 节中的“过滤掉 Go 运行时函数”措施; 使用“措施 3”来代表使用第 3.4 节中的“过滤掉非指针 store” 措施; 使
用“无”代表不使用任何措施, 直接插桩 Go 二进制中的所有 store. 我们使用 Go 的标准库和编译工具链提供的 277
个包, 测试方法与第 4.1 节相同.
最终的测试结果如表 1 1 和 2 都没有效果. 这是因为目前 G 的二进制中都包
含大量的运行时管理函数以及汇编函数等非 Go 函数, 单独过滤运行时函数或者非 Go 函数无法消除在单一二进
制 (包) 上的误报. 从表 1 中可以看出, 同时使用措施 1 和 2 相比单独的措施 1 或 2 可以大大降低误报的包的数量,
但此时误报率仍然较高, 这是因为此时还没有恢复 Go 二进制中 store 指针的语义, 仍对所有 Go 用户代码中的
store 进行检查. 单独使用措施 3 的误报率也较高, 原因在于 Go 运行时中有诸多违反 Go 逃逸不变式的 store, 但运
行时保证了其安全性. 同时使用措施 2 和措施 3 可以带来最低的误报率, 在测试的 277 个包中误报率为 0. 在测试
中, 措施 1 无法在措施 3 的基础上进一步降低误报, 这是因为 Go 编译器只会对 Go 函数插入写屏障, 因此使用措
施 3 就潜在的消除了所有非 Go 函数带来的影响. 虽然措施 1 无法在措施 3 的基础上进一步降低误报率, 但并不