首先,能够分析klee源码固然重要。但是目前尚未到那个地步。我按照我的过程,记录和分析我所做的实验。
结论性内容是:
1、klee处理printf传入符号值的情形时,报为error,不会将符号值具体化以后再调用printf进行具体执行。
2、klee处理error的时候,如果多条路径覆盖该error,则只报一次该error,并且只生成一个测试用例。
3、klee符号执行时的posix-runtime选项为命令行建模提供支持,uclibc则对atoi等c标准库的函数进行建模。
(如果使用了Posix-rutime选项,但是没有对命令行建模(--sym-args和--sym-arg选项),则会报错;如果没有使用uclibc选项,那么klee符号执行过程会提示atoi等函数是external,无法处理。)
示例代码1:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <klee/klee.h> int main(int argc, char* argv[]) { int result = argc > 1 ? atoi(argv[1]) : 0; printf("result:%d\n",result); if (result == 42) { printf("yes\n"); klee_assert(0); } // printf("result:%d\n",result); }
示例代码2:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <klee/klee.h> int main(int argc, char* argv[]) { int result = argc > 1 ? atoi(argv[1]) : 0; //printf("result:%d\n",result); if (result == 42) { printf("yes\n"); klee_assert(0); } printf("result:%d\n",result); }
运行脚本:
clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c klee --optimize --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3
运行结果:
修改前
klee@ubuntu:~/kleeexperiment/modeltest$ clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c
klee@ubuntu:~/kleeexperiment/modeltest$ klee --optimize --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3
KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca
KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca
KLEE: output directory is "/home/klee/kleeexperiment/modeltest/klee-out-12"
KLEE: Using STP solver backend
KLEE: WARNING: undefined reference to function: puts
KLEE: WARNING ONCE: calling external: syscall(16, 0, 21505, 62967648)
KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of 8.
KLEE: WARNING ONCE: calling external: printf(51779744, 0)
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:19: external call with symbolic argument: printf
KLEE: NOTE: now ignoring this error at this location
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:-9
result:-9
result:0
result:0
result:-9
result:0
result:9
result:-99
result:9
result:9
result:9
result:9
result:9
result:9
result:9
result:99
result:99
result:99
result:99
result:999
KLEE: done: total instructions = 6238
KLEE: done: completed paths = 68
KLEE: done: generated tests = 36
修改后:
klee@ubuntu:~/kleeexperiment/modeltest$ clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c
klee@ubuntu:~/kleeexperiment/modeltest$ klee --optimize --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3
KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca
KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca
KLEE: output directory is "/home/klee/kleeexperiment/modeltest/klee-out-11"
KLEE: Using STP solver backend
KLEE: WARNING: undefined reference to function: puts
KLEE: WARNING ONCE: calling external: syscall(16, 0, 21505, 58818256)
KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of 8.
KLEE: WARNING ONCE: calling external: printf(55045792, 0)
result:0
result:0
result:0
result:0
result:0
result:0
result:0
KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:25: external call with symbolic argument: printf
KLEE: NOTE: now ignoring this error at this location
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
KLEE: WARNING ONCE: calling external: puts(60211056)
yes
KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:23: ASSERTION FAIL: 0
KLEE: NOTE: now ignoring this error at this location
result:9
result:0
result:-9
result:9
result:0
result:9
result:0
result:99
result:9
result:-9
result:9
yes
yes
yes
result:9
result:9
result:-9
result:9
result:-99
yes
result:99
result:99
result:99
result:999
KLEE: done: total instructions = 6381
KLEE: done: completed paths = 73
KLEE: done: generated tests = 37
可以看到结果不一样:后面比前面多生成了一个测试用例(36到37),并且新增汇报了一个error,就是assertion。为什么会多一个呢?因为:
printf放在了if(result==42)的前面。当符号值传递给printf的时候,klee可能有两种做法,一种是放弃分析,另外一种是将符号值具体化(目前我的猜测,继续往下看)。
如果是放弃分析,klee就不可能沿着该路径往后面走了;
如果是符号值具体化,klee也不可能考虑到后续有if(result==42)的语句,而故意将其具体化为42。因此,无论如何就无法检测到klee_assert的错误,即klee都不可能再对符号result添加==42的约束,从而跳入到后面的if语句中,继续符号执行。
针对该现象继续进行解释:即klee处理printf传入符号值的时候,是符号值具体化呢,还是放弃分析呢?我们就结合这个实例来探讨一下:
一,修改前和修改后的测试用例情况和路径覆盖情况对比:(说明针对一个error只产生一个测试用例)
KLEE: done: completed paths = 68 KLEE: done: generated tests = 36 | ||
KLEE: done: completed paths = 73 KLEE: done: generated tests = 37 此外,在符号执行过程中,输出了五次yes,说明有五条路径覆盖了if(result==42)。printf(“yes”)的参数不是符号值,因此可以直接执行。 | ||
现象说明:将printf从if(result==42)之前移动到之后以后,原有的68条路径中,有五条路径的约束还能够满足result==42,因此新增了五条路径(从68变为73)。 但是为什么测试用例只增加了一个呢?这是因为,五条路径都报错的话,klee只汇报一次error,同时只从产生一个覆盖该error的测试用例。 你若不信的话,我预言,你把klee_assert去掉,那么路径肯定是从68增加到73,然后测试用例也增加五个。我们试一试。 验证代码是:
输出信息:
klee@ubuntu:~/kleeexperiment/modeltest$ clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c klee@ubuntu:~/kleeexperiment/modeltest$ klee --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3 KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca KLEE: output directory is "/home/klee/kleeexperiment/modeltest/klee-out-17" KLEE: Using STP solver backend KLEE: WARNING ONCE: calling external: syscall(16, 0, 21505, 47881104) KLEE: WARNING ONCE: calling __user_main with extra arguments. KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of 8. KLEE: WARNING ONCE: calling external: printf(65297728, 0) result:0 result:0 result:0 result:0 result:0 result:0 result:0 result:0 KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:19: external call with symbolic argument: printf KLEE: NOTE: now ignoring this error at this location result:0 result:0 result:0 result:0 result:0 result:0 result:0 result:0 result:-9 result:-9 result:0 result:0 result:-9 result:9 result:9 result:9 result:9 result:-99 result:9 result:9 result:9 result:99 result:9 result:99 result:99 result:99 result:999
KLEE: done: total instructions = 11514 KLEE: done: completed paths = 68 KLEE: done: generated tests = 36
klee@ubuntu:~/kleeexperiment/modeltest$ clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c klee@ubuntu:~/kleeexperiment/modeltest$ klee --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3 KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca KLEE: output directory is "/home/klee/kleeexperiment/modeltest/klee-out-16" KLEE: Using STP solver backend KLEE: WARNING ONCE: calling external: syscall(16, 0, 21505, 31737040) KLEE: WARNING ONCE: calling __user_main with extra arguments. KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of 8. KLEE: WARNING ONCE: calling external: printf(44028960, 0) result:0 result:0 result:0 result:0 result:0 KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:25: external call with symbolic argument: printf KLEE: NOTE: now ignoring this error at this location result:0 result:0 result:0 result:0 result:0 result:0 result:0 result:0 result:0 result:-9 result:0 result:0 result:0 result:9 result:-9 result:0 result:9 result:-99 result:-9 result:9 result:9 yes result:42 result:9 yes result:42 result:9 result:9 yes result:42 result:9 result:99 result:99 yes result:42 result:99 yes result:42 result:99 result:999
KLEE: done: total instructions = 11648 KLEE: done: completed paths = 73 KLEE: done: generated tests = 41 验证了我的推断。 | ||
问题: 为什么路径数目和测试用例不一致呢?可能是由于求解器的原因,一些路径的约束无法计算出来(继续下面看,会发现该程序不是因为这个原因。) |
二、为什么路径数目和测试用例不一致呢?klee处理printf传入符号值,究竟是具体化符号值,还是直接放弃分析,报出error?
- 进一步修改程序:
int main(int argc, char* argv[]) { int result = argc > 1 ? atoi(argv[1]) : 0; //printf("result:%d\n",result); if (result == 42) { printf("yes,");//修改的地方是,加了一个逗号。 klee_assert(0); } printf("result:%d\n",result); }
- klee符号执行结果:
klee@ubuntu:~/kleeexperiment/modeltest$ clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c
klee@ubuntu:~/kleeexperiment/modeltest$ klee --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3
KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca
KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca
KLEE: output directory is "/home/klee/kleeexperiment/modeltest/klee-out-18"
KLEE: Using STP solver backend
KLEE: WARNING ONCE: calling external: syscall(16, 0, 21505, 75566416)
KLEE: WARNING ONCE: calling __user_main with extra arguments.
KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of 8.
KLEE: WARNING ONCE: calling external: printf(66063584, 0)
result:0
result:0
result:0
result:0
result:0
KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:25: external call with symbolic argument: printf
KLEE: NOTE: now ignoring this error at this location
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:-9
result:0
result:9
result:0
result:-9
result:9
result:-99
result:-9
result:9
KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:23: ASSERTION FAIL: 0
KLEE: NOTE: now ignoring this error at this location
yes,result:9
result:9
result:9
yes,yes,result:9
result:9
result:99
result:99
yes,yes,result:99
result:99
result:999
KLEE: done: total instructions = 11623
KLEE: done: completed paths = 73
KLEE: done: generated tests = 37
首先,输出的信息都是符号执行的过程中输出的。产生的测试用例都是符号执行结束以后产生的。根据我前面的论证,当覆盖同一个错误的时候,只报一次error,只产生一个测试用例。所以,五个yes,也就说明实际能够产生测试用例的路径是41条。那么也就说明,符号执行过程中应该输出41组值。我们数一下。一共是40组值(注意,yes,单独算一组)。为什么???
这个时候我进一步摸索和修改(也是误打误撞出来)
版本一程序 | 版本二程序 | 版本三程序 |
int main(int argc, char* argv[]) { int result = argc > 1 ? atoi(argv[1]) : 0; //printf("result:%d\n",result); if (result == 42) { printf("yes,"); //klee_assert(0); } //printf("result:%d\n",result); }
|
int main(int argc, char* argv[]) { int result = argc > 1 ? atoi(argv[1]) : 0; //printf("result:%d\n",result); if (result == 42) { printf("yes,"); klee_assert(0); } //printf("result:%d\n",result); }
|
int main(int argc, char* argv[]) { int result = argc > 1 ? atoi(argv[1]) : 0; printf("result:%d\n",result); if (result == 42) { printf("yes,"); //klee_assert(0); } //printf("result:%d\n",result); }
|
输出结果:
版本一 | 版本二 |
|
|
版本三:
klee@ubuntu:~/kleeexperiment/modeltest$ clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test2.c
klee@ubuntu:~/kleeexperiment/modeltest$ klee --libc=uclibc --posix-runtime test2.bc --sym-args 0 1 3
KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca
KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca
KLEE: output directory is "/home/klee/kleeexperiment/modeltest/klee-out-21"
KLEE: Using STP solver backend
KLEE: WARNING ONCE: calling external: syscall(16, 0, 21505, 60923696)
KLEE: WARNING ONCE: calling __user_main with extra arguments.
KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of 8.
KLEE: WARNING ONCE: calling external: printf(76818928, 0)
result:0
result:0
result:0
result:0
result:0
result:0
result:0
KLEE: ERROR: /home/klee/kleeexperiment/modeltest/test2.c:19: external call with symbolic argument: printf
KLEE: NOTE: now ignoring this error at this location
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:0
result:-9
result:9
result:0
result:0
result:-9
result:9
result:9
result:9
result:9
result:99
result:-9
result:9
result:9
result:9
result:-99
result:99
result:99
result:99
result:999
KLEE: done: total instructions = 11514
KLEE: done: completed paths = 68
KLEE: done: generated tests = 36
比较一下版本一和版本二,可以看出,版本二由于assert原因,73条路径中五条覆盖assert,所以只生成69个测试用例,汇报一次error,error对应一个测试用例。
比较一下版本二和版本三,可以看出,版本三由于printf放在了if(result==42)的前面,无论是printf传入具体值,还是printf传入符号值(符号值具体化或者klee放弃分析,见前述分析),后续覆盖if(result==42)的路径都不会被满足和分析,没有yes被输出。同时,覆盖的路径条数为68。这也说明,if点前一共有68条路径,如果没有printf影响,其中只有五条的约束在添加(result==42)以后,还能够满足,从而覆盖到if(result==42)分支内部内容。
比较一下版本一和版本三,可以看出,符号执行过程中的输出为35组数据(数一数输出的result信息的个数),但是生成了36个测试用例,报了一个error,我们看了一下,error的具体内容是:
同时配套生成了一个error的测试用例。但是符号执行过程中输出为35组数据,这也就说明了,klee处理printf传入符号值的时候,没有将其具体化以后再调用printf,而是直接报error,并且放弃分析。
这也说明了:版本三程序的一共68条路径中,有(68-35)条路径因为执行printf时传入符号值,均为error,但是只报出一个error,最后也只产生一个测试用例覆盖该条路径。这也是前述41个测试用例和40组输出数据的原因。
我们查看一下测试用例的内容和用该测试用例重新运行程序,可以看出,由于printf传入符号值的这种error类型,是klee自身不能处理的情况,而不是我们一般程序运行时的内存错误等,所以该测试用例下重新运行该程序,不会报错。result输出值为0。那为什么还要生成该测试用例呢,因为需要覆盖该error所在路径。
klee@ubuntu:~/kleeexperiment/modeltest$ export LD_LIBRARY_PATH=/home/klee/xiaojiework/klee-xiaojie/build/debug/lib/:$LD_LIBRARY_PATH
klee@ubuntu:~/kleeexperiment/modeltest$ gcc -L /home/klee/xiaojiework/klee-xiaojie/build/debug/lib/ test2.c -lkleeRuntest
klee@ubuntu:~/kleeexperiment/modeltest$ ktest-tool klee-last/test000010.ktestktest file : 'klee-last/test000010.ktest'
args : ['test2.bc', '--sym-args', '0', '1', '3']
num objects: 3
object 0: name: 'n_args'
object 0: size: 4
object 0: data: '\x01\x00\x00\x00'
object 1: name: 'arg0'
object 1: size: 4
object 1: data: '+0\x00\x00'
object 2: name: 'model_version'
object 2: size: 4
object 2: data: '\x01\x00\x00\x00'
klee@ubuntu:~/kleeexperiment/modeltest$ KTEST_FILE=klee-last/test000010.ktest ./a.out
result:0
结论性的东西往往不容易获得。分析完毕!