C CheckPointer
Memory Safety Checker Example
The C CheckPointer Memory Safety checker tool finds difficult pointer errors. This page shows a buggy C program, and its vanilla execution and mysterious crash after compilation, and its execution and problem diagnosis using the C CheckPointer tool.
A Buggy C Program
This artificial program allocates, updates, and deallocates various bits of storage, and prints its progress as it goes. It does a very bad thing... somewhere. Can you spot the problem?
/* buggy.c */ extern void *malloc(unsigned int n); extern void free(void *); extern int printf(const char *, ...); struct X { int *a; int *b; }; struct X foo(struct X p) { struct X *x = &p; printf("deref a on cpy passed\n"); int pa = (*x->a); printf("deref b on cpy passed\n"); int pb = (*x->b); if (pb<333) { printf("free b\n"); free(x->b); } return p; } int main() { printf("alloc x\n"); struct X *x = malloc(sizeof(struct X)); printf("alloc/assign a\n"); x->a = malloc(sizeof(int)); *x->a = 111 ; printf("alloc/assign b\n"); x->b = malloc(sizeof(int)); *x->b = 222 ; printf("deref a\n"); int za = *(x->a); printf("deref b\n"); int zb = *(x->b); printf("alloc y\n"); struct X *y = malloc(sizeof(struct X)); printf("assign y\n"); *y = *x; printf("deref a\n"); int ra = *(y->a); printf("deref b\n"); int rb = *(y->b); printf("after deref: ra = %d, rb=%d \n",ra,rb); printf("rederef a\n"); za = *(x->a); printf("rederef b\n"); zb = *(x->b); struct X xxx = *x; struct X yyy = xxx; struct X *zzz = &yyy; printf("deref a on copy\n"); za = *(zzz->a); printf("deref b on copy\n"); zb = *(zzz->b); struct X r; r = foo(yyy); struct X *rr = &r; printf("deref a on return\n"); ra = *(rr->a); printf("store through b on return\n"); *(rr->b)=333; printf("deref b after update\n"); rb = *(rr->b); printf("loop..."); int i; for (i=0;i<1000000000;i++) { } printf("\nafter deref on return: ra = %d, rb =%d, done\n",ra,rb); printf("exit\n"); }
A traditional Execution
Here we compile the program with gcc and execute it, getting a crash. See remarks following the program execution.
C:\Example>gcc-4 -g -I.. buggy.c C:\Example>a.exe alloc x alloc/assign a alloc/assign b deref a deref b alloc y assign y deref a deref b after deref: ra = 111, rb=222 rederef a rederef b deref a on copy deref b on copy deref a on cpy passed deref b on cpy passed free b deref a on return store through b on return deref b after update loop... after deref on return: ra = 111, rb =333, done exit 2 [sig] a 9128 open_stackdumpfile: Dumping stack trace to a.exe.stackdump C:\Example>
The program runs apparantly perfectly, computing final values to fill the struct rr, computes for a long time (loop...), prints the apparantly correct values ra and rb stored in the struct, does an exit, ... and then it dies. There is clearly a problem, but where is the origin of the problem? How would you go about debugging this? See the diagnosis using the C CheckPointer tool.
Pointer errors cause Undefined Behavior
In this particular execution, we get a "nice" crash reported by GCC's runtime, so at least it is obvious there is a problem. However, what the C standard says about errors with pointers is that they produce undefined behavior. What this means is the program can do anything including:
- crashing loudly (as for this example),
- producing garbage for output,
- having arbitrary side effects,
- producing the correct answer and exit cleanly,
- producing any of the above behaviors nondeterministically on any specific run
Execution using CheckPointer
Because the problem is so mysterious, we suspect a memory error. We first instrument the program using the CheckPointer tool, which generates an instrumented version of the program (the -D parameter causes the instrumented program to abort after reporting the first safety error encountered). We then compile and run that program:
C:\Example>run ../CheckPointer buggy.c *** Processing file: buggy.c -> buggy.out.c C:Example>gcc -DCHECK_POINTER_TERMINATE_ON_ERROR buggy.out.c check_pointer*.c C:Example> a.exe alloc x alloc/assign a alloc/assign b deref a deref b alloc y assign y deref a deref b after deref: ra = 111, rb=222 rederef a rederef b deref a on copy deref b on copy deref a on cpy passed deref b on cpy passed free b deref a on return store through b on return *** Error: Dereference of dangling pointer. in function: main, line: 64, file C:/Example/buggy.c assertion "0" failed: file "check_pointer.c", line 544, function: check_pointer_print_error Aborted (core dumped) C:\Example>
Notice the program stops with a detected problem before the long-running loop even starts. The CheckPointer tool has diagnosed an erroneous write to an invalid location (a "wild store") in line 64 in the original program; this is the first line of code where an error can be diagnosed as real. One might argue the mistake is the free operation at line 20, but by itself that operation is not erroneous, so the error cannot be diagnosed there. That free operation is relatively easy to find with a debugger once it is known that line 64 is erroneous; whether line 20 or line 64 is modified to fix the program is determined by the purpose of the program.
The C CheckPointer tool can easily find this kind of error. With programs with tens of thousands of lines, this is extremely difficult to do with a debugger.
Semantic Designs also provides a variety of other tools.