Finding Bugs
ex2.c shows a program that contains an error. It is equivalent to tossing a coin 5 times and checking whether heads appears at least once. In on average 1 of 32 tries however, this is not the case.
The special functions declared before the main function follow a convention that is used in the Competition on Software Verification and as such understood by many different software verifiers.
The function __VERIFIER_nondet_uint
returns a nondeterministic unsigned integer. The function __VERIFIER_error
signals to the verifier that an error /specification violation has occurred.
With CPAchecker
We now try to find the error in ex2.c with CPAchecker using three different analysis.
Try with value analysis:
scripts/cpa.sh -valueAnalysis-NoCegar examples/ex2.c
In the report named Report.html we can see that the bug is found, but the ARG shows a lot of branching.
Try with predicate analysis:
scripts/cpa.sh -predicateAnalysis examples/ex2.c
In the report named Counterexample.1.html we can observe that the bug is found, and the ARG shows less branching. We also can distinguish between abstration states(blue), non-abstraction states(no background color), states that were not expanded (orange) and error states (red). There is also a additional bar at the left that lets us step through the counterexample and look at the values of each variable at each step (this explains the different name of the report file, when multiple counterexamples are found, more than one Counterexample.X.html file might be generated).
Try with symbolic execution:
scripts/cpa.sh -symbolicExecution examples/ex2.c
In the report named Result.html we can now observe that the ARG contains states with symbolic values.
With Klee
We need to modify the example file in order for it to work with KLEE. See ex2_klee.c:
The main changes are that we define __VERIFIER_nondet_uint
__VERIFIER_error
in a way KLEE understands. klee_assert(0)
will tell KLEE to log if this assertion is reached
We start klee as docker container and mount the current working directory to /home/klee/dir inside the container:
$ docker run --rm -ti --ulimit='stack=-1:-1' -v $(pwd):/home/klee/dir klee/klee
klee@7435ce6644ce:~$ cd dir
klee@7435ce6644ce:~/dir$ ls
ex2_klee.c
(If you got the KLEE binaries installed otherwise (e.g. via TBF), just move into the directory where the example files are) Now the program needs to be compiled before we can use KLEE:
klee@7435ce6644ce:~/dir$ clang -I ~/klee_src/include -emit-llvm -c -g ex2_klee.c
This creates a file ex2_klee.bc
that can then be given as input program to KLEE:
klee@7435ce6644ce:~/dir$ klee ex2_klee.bc
KLEE: output directory is "/home/klee/dir/klee-out-14"
KLEE: Using STP solver backend
KLEE: ERROR: /home/klee/dir/ex2_klee.c:5: ASSERTION FAIL: 0
KLEE: NOTE: now ignoring this error at this location
KLEE: done: total instructions = 191
KLEE: done: completed paths = 6
KLEE: done: generated tests = 6
Result:
- KLEE found (all) six paths through the program and generated test cases, which are in the output folder klee-out-xx.
- The output also indicates that it found an ERROR. This means that there is a test case that triggered the klee_assert(0).
It is also described in a file with the extension
.assert.err
in the klee-out-xx folder. The corresponding .ktest files can be viewed with ktest-tool:ktest-tool --write-ints klee-out-xx/test000yyy.ktest
Variations
if the number of iterations is increased in ex2.c/ex2_klee.c, e.g. to 50, predicate analysis takes longer while KLEE still returns almost instantly. This changes if the break instruction is removed => KLEE will not terminate in reasonable time (at least not with the default search strategy) while predicate analysis takes longer, but finishes eventually. The symbolicExecution in CPAchecker behaves similar to KLEE, as far as i can tell.
Another Example
A program that takes a nondeterministic, positive integer and calculates the length of the corresponding collatz sequence. The program assumes that this length is always smaller than a certain boundary (here 6 is chosen). This is of course wrong, so the program has a bug. It is interesting to see if and how CPAchecker/KLEE find this. Here is the source code of ex3.c:
And the modified source code for KLEE is ex3_klee.c:
Check program with CPAchecker:
scripts/cpa.sh -predicateAnalysis examples/ex3.c
# ... usual output, CPAchecker finds the bug
Check program with KLEE:
klee@7435ce6644ce:~/dir$ clang -I ~/klee_src/include -emit-llvm -c -g ex3_klee.c
klee@7435ce6644ce:~/dir$ klee ex3_klee.c
KLEE: output directory is "/home/klee/dir/klee-out-6"
KLEE: Using STP solver backend
KLEE: ERROR: /home/klee/dir/ex3_klee.c:5: ASSERTION FAIL: 0
KLEE: NOTE: now ignoring this error at this location
^CKLEE: ctrl-c detected, requesting interpreter to halt.
KLEE: ctrl-c detected, requesting interpreter to halt.
KLEE: halting execution, dumping remaining states
KLEE: done: total instructions = 9188
KLEE: done: completed paths = 210
KLEE: done: generated tests = 195
With KLEE, we need to abort the analysis after the ERROR is discovered, otherwise KLEE will run indefinitely. Alternatively we can call KLEE with an extra option:
klee --exit-on-error ex3_klee.c
There is also an option only-output-states-covering-new
that reduces the amount of generated test cases. This will only output a new testcase if it covers new code:
klee only-output-states-covering-new ex3_klee.c
Result:
- CPAchecker (predicate analysis) finds the bug. In the report, we can see that n=64 is chosen as the initial value of n
- KLEE also finds the bug. By inspection of the test case, we see that n=10 is chosen as initial value.
Variations
Changing the length from 6 to 15:
- KLEE takes very long to find the bug, while CPAchecker(predicate analysis) is faster.
This is only half the truth. KLEE can find it faster with different search strategies, e.g.:
klee -search=dfs ex3_klee.bc
- CPAchecker with symbolicExecution does not terminate here. Maybe another search strategy would change this?