CBMC使用注意事项
最近在使用CBMC来分析一系列的C文件,在使用过程中碰到很多微妙的问题,参数选取的好坏直接影响了最终分析结果的完整性和准确性。下面讲解一下个人的使用经验,仅供参考。
我们先考虑单文件的情况
下面做的所有实验是针对我自己编写的一个简单的充满Bug的C程序来进行的。该文件内容如下:
#include <string.h>
int singleFunc(int j)
{
int i;
int a[5]={1,2,3,4,5};
for(i=0;i<j;i++)
{
a[i]=0; //Upper Bound overflow
}
}
void twoSeparate(int j,int k)
{
short i=-32765;
short m=32765;
short low=i-j; //Arithmetic overflow when j>=2
short upper=m+k; //Arithmetic overflow when j>=2
}
void twoSeparateString()
{
int i=0;
char a[5],b[3];
char *c=a;
char *d=b;
//int s=-257330;
//int low=s-i;
twoSeparate(3,100);
strcpy(d,c); //Deference error
//strcpy(b,a);
}
void mainSubfunc(int i)
{
int j=100;
int k=10;
j=j/i; //Divison by zero,But won't happen since input i will never be zero
int m=0;
k=k/m; //Division by zero
}
void main()
{
int i=0,j=0;
int a[2]={1,2};
while(1)
{
a[i]=3; //Upper Bound Overflow
i++;
}
mainSubfunc(j+1);
}
该文件中共包含了5个函数,包括1个独立函数singleFunc,2个与main不相关但彼此间存在调用关系的函数twoSeparate和twoSeparateString,以及main和它所调用的一个函数mainSubfunc构成。
下面是实验流程:
一、
(1)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c –show-claims
将产生15个声明,并且对twoSeparateString 不会存在任何声明,。
(2)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --show-claims --function twoSeparateString 将产生额外7个与twoSeparateString相关的声明。
(3)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --show-claims --function mainSubfunc
将产生15个声明,并且对twoSeparateString 不会存在任何声明
(4)在函数twoSeparateString中添加额外的一段可疑代码,如
int s=-257330;
int low=s-i;
再运行cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c –show-claims 将产生 14个声明,其中多的两个是在twoSeparateString中,并是与low=s-i相关的算术溢出声明。
综上可得,--function的指定一般不会影响可疑声明的生成,但如果函数中存在strcpy等库函数操作的时候,这些函数中是不会生成与strcpy等操作相关的可疑声明的,而不影响该函数中其他可疑声明的生成。
二
(1)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --function twoSeparate 会在函数twoSeparate的low=i-j这一行检测出下溢出错误,并停止。所以需要使用claim来指定,使其能检测多个错误。
(2)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --function twoSeparate –claim twoSeparate.4 指定为twoSeparate.2可以检测出low=i-j的下溢出(其实是伪错误,因为函数twoSeparate是由twoSeparateString调用的,传入的j值不会使low下溢出。当将twoSeparateString作为入口函数的话,即cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --function twoSeparateString –claim twoSeparate.2 –unwind 20会排除这个错误),指定为twoSeparate.4可以检测出upper=m+k;的上溢出。
(3)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --function twoSeparate --claim main.
2 不会验证main中的声明,因为twoSeparate中没直接或间接调用main
综上可得,CBMC只会检验—function指定的函数,及该函数直接或间接调用的函数中的相关声明
三、
(1)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --unwind 20
循环展开20次,会检测到main中a[i]的数组越界错误。
(2)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --unwind 20 --claim mainSubfunc.2
不会检测到任何错误,而实际上mainSubfunc.2指明了一个除0错误。
(3)cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.c --function mainSubfunc
会检测到除0错误
综上可得,如果所检测的函数中包含while等循环,必须指定unwind次数,否则不会终止。而且对于那些位于while循环之后的那些声明是不会被检测到的。
四、
cbmc --bounds-check --pointer-check --overflow-check --div-by-zero-check --nan-check usageTest.gb --function mainSubfunc --unwind 20 --claim mainSubfunc.2
如果先用goto-cc将文件进行处理,生成.gb文件。那么--function等参数会失效,也就是说系统会将main设为入口函数并且不可改变,所以它也就只能检验到main以及被main所直接和间接调用的函数中存在的声明。这会导致很大一部分声明都检测不到,所以尽量不要使用goto-cc进行预先处理。
下面我们先考虑多文件的情况
我采用了下面两个文件
unwind.c
void main()
{
int i=0;
int a[2]={1,2};
subfunc(i+1); //Location ditermines different results
while(1)
{
a[i]=3;
i++;
}
}
和 subfunc.c
void subfunc(int i)
{
int j=100;
int k=10;
j=j/i;
int m=0;
k=k/m;
}
下面进行实验,先用goto-cc编译成一个文件做法是可以的,不过正如上面所说这样做会导致一部分声明检验不到。当然针对上面的代码是都可以检测到的,但如果将subfunc(i+1)放到while循环之后就检测不到subfunc中的声明了,正如上面的步骤三中说的。
正如前面所讲最好还是使用源文件作为输入,其实只要将所有相关的检测文件列到参数中就可以了
cbmc --bounds-check --div-by-zero-check unwind.c subfunc.c --unwind 20 --claim subfunc.2
cbmc --bounds-check --div-by-zero-check unwind.c subfunc.c --claim main.2 --unwind 20