在这篇博客中,我们将展示如何使用 KLEE 自动地为一些整数相关的小实例(用 C 语言编写)生成测试用例,在下篇博客中,我们将用 KLEE 来测试一段二分查找算法的代码,生成测试用例。
符号执行是不同于实际运行程序的,在符号执行中,我们跟踪的不是变量的实际值,而是其“符号值”和对这些值的操作。所以,对于如下这段函数代码:
int many_math_operations(int x) {
int result = x * 2;
result += 5;
result /= -3;
result *= (result+1);
return result;
}
如果我们将变量 x 的值看成是一个未知的 X,则这个函数的结果是:
符号执行也处理一些条件语句(if,while,do-while,for)等。例如:
int function_with_if(int x) {
if (x > 0) {
return 10 / x;
} else {
return 10 / (x + 1);
}
}
在上面的函数中,符号执行尝试去覆盖到所有可能的路径。对于每条路径,它都会找到满足路径条件的输入并且根据符号表达式产生输出。
对于 X > 0 的情况,输出将是 10/X(接下来都使用大写字母来表示符号值)。
对于 !(X > 0)的情况,输出将会是 10/(X+1)。
你能找到上述函数可能的错误码??KLEE 呢??
1、首先我们创建一个包含上述函数的本地文件(source_to_klee/examples/function_with_if/function_with_if.c)
#include <klee/klee.h>
#include <stdio.h>
int function_with_if(int x) {
printf("input: %d\n", x);
if (x > 0) {
return 10 / x;
} else {
return 10 / (x + 1);
}
}
int main() {
int x;
klee_make_symbolic(&x, sizeof(x), "x");
int result = function_with_if(x);
printf("result = %d\n", result);
return 1;
}
main 函数中表示变量 x 应该视为符号值。
2、编译成 LLVM 字节码
clang -I source_to_klee/include/ -emit-llvm -c -g -O0 -Xclang -disable-O0-optnone function_with_if.c
3、运行 KLEE
klee -external-calls=all source_to_klee/examples/function_with_if/function_with_if.bc
通过命令行选项 -external-calls=all
可以让 KLEE 在遇到 printf 函数调用的时候状态不会终结。
KLEE 找到了一个除零错误!!!
4、让我们看看生成的测试用例:
ktest-tool klee-out-0/test000001.ktest
可以看到在这个测试用例中,变量 x 的值是 int 型的 -1。同样你也可以查看第二个和第三个测试用例,你将看到被选择的输入分别是 0 和 16777216。KLEE 选择这些值是非常有用的,因为他们可以有效的检查边界情况。
5、让我们用不同的输入运行程序,观察程序的行为:
根据第一篇博文中类似的方式复现这个测试用例,如下:
可以看到产生了异常,程序被非正常终止。
同时复现第二个和第三个测试用例,结果如上。
在这篇博文中,我们使用 KLEE 分析了一个非常简单的示例。在下篇博客中,我们将使用 KLEE 自动地分析二分查找的小示例,生成测试用例。
[注]:在 KLEE 输出的文件夹中,发现对于第一个除零错误的测试用例,还生成了一个 .kquery 后缀文件和一个 .div.err 后缀文件,让我们看看里面都有啥。
.div.err 后缀文件(提供了缺陷类型,所在文件名和行数,调用栈信息):
.kquery 后缀文件(Kquery 格式的查询表达式):