本文1.首先介绍创建KLEE Docker容器的方法;2.然后介绍官网给出的一个指导实例;3.最后给出程序测试实例。
创建KLEE Docker容器
相关Dokcer的知识可以参见官网:https://docs.docker.com/get-started/
1.创建临时KLEE Docker容器
$ docker run --rm -ti --ulimit='stack=-1:-1' klee/klee:2.0
成功之后,shell提示符发生改变,变成klee用户,可以在容器中继续使用shell命令
$ whoami
$ klee --version
$ clang --version
通过这3个命令,可以查看到类似以下的信息:
最后,退出容器
$ exit
注意,因为我们创建画像的时候用了--rm参数,在我们退出后容器会销毁,因此是临时的。
2.创建永久容器
上一个例子简单的介绍了如何创建临时容器,接下来介绍如何创建永久容器,并用来做一些有意义的事。
首先,创建并进入容器:
$ docker run -ti --name=my_first_klee_container --ulimit='stack=-1:-1' klee/klee:2.0
这里我们没有用--rm参数,因此创建了永久容器。同时,这里用--name参数指定了容器的名字。--ulimit参数是为了防止堆栈溢出问题,这里不做深究,按照官网推荐书写。
接下来我们退出容器,检查容器是否依然存在
$ exit
$ docker ps -a
可以看到,容器依然是存在的,我们可以重新进入容器
$ docker start -ai my_first_klee_container
可以看到我们的容器依然存在。最后,如果想删除容器,可以用命令
$ docker rm my_first_klee_container
官方指导实例
官方教程网址:http://klee.github.io/tutorials/testing-function/
这个实例完整程序如下,在 examples/get_sign 目录下,
用来判断一个整数的正,负,或者为0.
/*
* First KLEE tutorial: testing a small function
*/
#include <klee/klee.h>
int get_sign(int x) {
if (x == 0)
return 0;
if (x < 0)
return -1;
else
return 1;
}
int main() {
int a;
klee_make_symbolic(&a, sizeof(a), "a");
return get_sign(a);
}
其中,klee_make_sybolic是KLEE自带的函数,用来产生符号化的输入。
因为KLEE是在LLVM字节码上进行工作,所以我们首先需要将.c编译为LLVM字节码。首先,我们进入到该文件目录(~/klee_src/examples/get_sign)下执行命令
clang -I ../../include -emit-llvm -c -g -O0 -Xclang -disable-O0-optnone get_sign.c
其中,参数-I是为了编译器找到头文件klee/klee.h,-g是为了在字节码文件中添加debug信息,还有后面的,具体不深究,按照官网推荐来。
同目录下我们会生成一个get-sign.bc的字节码文件,然后进行测试:
$ klee get_sign.bc
可以看到结果中KLEE给出了总指令数,完整路径和生成的测试案例数。
最后,我们看当前目录下多生成了两个文件:klee-last 和 klee-out-0。其中klee-out-0是本次测试结果,klee-last是最新测试结果,每次测试后覆盖。
程序测试实例
实例来源:https://blog.csdn.net/vincent_nkcs/article/details/85224491
源程序代码如下:
#include<stdio.h>
#include<stdlib.h>
void kleeTest(int a){
int arr[10];
int d[10];
for (int i = 0; i < 10; i++){ //赋初始值
arr[i] = i;
}
if (a < -50){ //求余分母为0
for (int i = 0; i < 10; i++){
int num = i;
d[i] = arr[i] % num;
}
}
else if(a < -25){ //除法分母为0
for (int i = 0; i <= 10; i++){
int num = i ;
d[i] = arr[i] / num;
}
}
else if (a < 0){ //数组越界
for(int i = 0; i<= 11; i++){
arr[i] = i;
}
}
else if (a < 25){ //空指针
int *a = NULL;
int b = *a + 1;
}
else if(a < 50){ //内存泄漏
free(arr);
}
}
int main(){
int n;
klee_make_symbolic(&n, sizeof(n), "n");
kleeTest(n);
return 0;
}
首先,我们需要把实例拷贝到Docker容器中去,需要用到Docker cp命令
命令格式为:$ docker cp /host/path/target <containerId>:/file/path/within/container
详细可以参考:https://www.runoob.com/docker/docker-cp-command.html
以我为例:
$ docker cp /Users/lichao/Desktop/mytest.c my_first_klee_container:/home/klee/klee_src/examples/get_sign
/Users/lichao/Desktop/mytest.c 为我的实例程序路径
/home/klee/klee_src/examples/get_sign为目标路径
my_first_klee_container为容器名字
完成拷贝后,我们可以进入到容器中查看
$ docker start -ai my_first_klee_container
$ ls /home/klee/klee_src/examples/get_sign/
可以看到,mytest.c已经成功拷贝至容器目标路径下。
接下来,按照我们之前官方教程一样操作:1.进入到文件目录;2.clang编译为字节码;3.klee编译
$ cd /home/klee/klee_src/examples/get_sign/
$ clang -I ../../include -emit-llvm -c -g -O0 -Xclang -disable-O0-optnone mytest.c
$ klee mytest.bc
可以看到结果,给出了错误类型及对应的源程序位置以及指令、路径、生成的用例数。
我们看下得到的测试文件
$ ls klee-last
可以看到,对应每个test主要有两种后缀文件。其中1.
.ktest文件是KLEE生成的测试用例,是二进制文件,可以用ktest-tool打开
$ ktest-tool klee-last/test000001.ktest
可以看到结果,对于第一个用例,给的输入是25。同理可以查看第二个和第三个测试用例,不再累述。
2. 其他后缀结尾的,包括.free,err、.div,err等,则是对应错误的相关信息,我们可以将他们复制到自己主机上进行查看。
命令格式:$ docker cp <containerId>:/file/path/within/container /host/path/target
然后用文本格式打开,可以看到类似以下信息,包括错误的位置以及原因:
至此,KLEE的基本使用方法已经习得。