Page 30 - 《软件学报》2025年第7期
P. 30
欧先飞 等: 语义可感知的灰盒编译器模糊测试 2951
表 1 展示了这 43 个初始变异操作符中的一小部分, 这些操作符涵盖了表达式、语句、函数、变量等多个方
面, 并且其中包含一些相当复杂的操作, 如表 1 所示的“扁平化高维数组”. 不过即便如此, 在我们所搜集的测试程
序集中, 依旧有不少的程序无法被所有这些操作符变异, 这也驱使我们去添加更多的变异操作符来覆盖这些无法
被变异的程序.
表 1 本文实现的部分变异操作符
变异操作符 功能描述
修改表达式操作符 修改表达式的操作符, 并保证语义合法, 如a+b改为a–b
简单内联 将被调用函数内联至掉用处
分支转多路分支 将if分支语句转化为switch语句
翻转内联修饰符 若选定函数为内联函数, 则移除内联修饰符, 反之添加内联修饰符
表达式拷贝 对在同一个作用域内类型相同的两个表达式, 将其中一个替换为另一个
扁平化高维数组 将多维数组a[M][N][...]降为一维数组, 并相应的修改所有引用
分支条件翻转 将if分支语句的判断表达式取反
2.1.1 可用性增强
对于我们所设计和实现的操作符而言, 可用性问题主要包含两个方面, 一是有一部分测试程序无法被任何一
个我们的操作符所变异, 另一个是操作符由于实现当中的缺陷, 会导致输出程序非法亦或是操作符自身崩溃. 针对
这两个问题, 如图 4 所示, 我们会采用如下的解决方案来缓解.
(1) 无法被变异的程序. 在获得表 1 所示的初始的操作符集合 (包括名称, 功能描述和代码实现) 之后, 如图 4
所示, 我们会在搜集来的测试程序上进行大量的测试运行. 如果某个程序未能被所有操作符成功变异, 我们会人工
分析该程序, 设计出可以覆盖该程序的变异操作符. 该过程会持续迭代, 直至所有测试程序都能被至少一个操作符
变异. 这一过程迭代结束后, 共计获得了 72 个变异操作符.
(2) 变异操作符的缺陷. 为了降低操作符实现中的缺陷带来的影响, 我们在测试运行的时候也会搜集分析缺
陷. 如图 4 所示, 如果测试过程中, 在某个变异操作符出现了崩溃或者变异出了不可编译的程序, 我们会人工介入
并修复这个变异操作符.
2.2 集成适配与可靠性增强
为了将这些操作符和模糊测试相结合, 我们还需要将这些操作符集成进模糊测试工具中. 这里我们选择了目
前最先进的灰盒模糊测试工具 AFL++ [20] . AFL++为二次开发提供了一套插件机制, 我们可以将已实现的变异操作
符按 AFL++的接口描述打包成动态链接库, 然后将其作为基于语义信息的变异模块交由 AFL++加载运行. 具体来
说, 我们需要实现如下接口.
1. // 自定义的变异操作模块
2. size_t afl_custom_havoc_mutation(my_mutator_t *data, …);
如算法 1 所示, 在具体实现过程中, 我们在该函数内部会先通过 select 函数选出合适的种子程序 p 和变异操
o, 随后我们通过 request_mutation p 获得 , 之后通过 update 函数更新一下
p
′
作符 函数将变异操作符 o 应用于程序
′
select 函数的选择权重, 最后将变异后的程序 p 返回给 AFL++进行后续的模糊测试, 包括覆盖率更新、超时检查、
崩溃检查以及种子程序池更新等.
算法 1. 自定义变异操作.
′
输出: 变异后的程序 p .
1. function afl_custom_havoc_mutation() {
2. p,o ← select()
3. p,o)
p ← request_mutation(
′

