6 Debugging c

21
Debugging C (What to do when you make the mistakes from the last lecture) David Chisnall February 20, 2011

Transcript of 6 Debugging c

Page 1: 6 Debugging c

Debugging C(What to do when you make the mistakes from

the last lecture)

David Chisnall

February 20, 2011

Page 2: 6 Debugging c

Buggy C Code

$ ./a.out

Segmentation fault: 11 (core dumped)

C gives very helpful feedback: you did something wrong.Somewhere.

Author's Note
Comment
C code is compiled to native code and, unlike Java, does not run in a virtual machine. The only kind of run-time errors that you will see are ones that the operating system catches. The most common is a segmentation violation (a.k.a. SegV, segmentation fault, segfault), meaning that the program has tried to access some memory that it does not have permission to access. If you're running C code on an embedded system with no OS, or on a platform with no memory protection then you won't even see this.
Page 3: 6 Debugging c

What’s this ‘core’ thing?

$ ls *.core

a.out.core

$ gdb a.out a.out.core

• Core files contain a memory dump of the crashed program

• You can use them to inspect a program that’s crashed

Author's Note
Comment
The term comes form magnetic core memory, which used to be used for active storage (as opposed to offline storage - tapes, disks, and so on). When a program crashed, the contents of this storage was written to the offline storage, so it could be analysed without using the computer. Now, core files are just files that contain the contents of a program's memory at the time of a crash. You can load them into a debugger to inspect the program state.
Page 4: 6 Debugging c

Step 0: Compiler Warnings

$ gcc broken.c

$ clang -Wall broken.c

broken.c:9:12: warning: using the result of an assignment

as a condition without parentheses [-Wparentheses]

if (array = 0)

~~~~~~^~~

broken.c:9:12: note: use ’==’ to turn this assignment into

an equality comparison

if (array = 0)

^

==

broken.c:9:12: note: place parentheses around the

assignment to silence this warning

if (array = 0)

^

( )

Author's Note
Comment
Clang gives much nicer warnings than most other C compilers, but most will give a similar - though less detailed - warning. Fixing compiler warnings is ALWAYS the first step in debugging. Some of them may be spurious, but you can silence them, and then it makes it easier to see the genuine ones.
Page 5: 6 Debugging c

Fix the Warnings First!

• If you really meant the assignment, add the brackets

• If you meant a comparison, fix the code

• Then fix any other warnings.

Author's Note
Comment
The warning meant that you said something that you probably didn't mean. You have two choices: if you did mean that, add the extra brackets as a hint to the compiler that this is what you meant. If didn't, then fix it. Then move on to the next warning.
Page 6: 6 Debugging c

Step 1: Where is the bug?

• Bugs in valid C programs often cause crashing

• The first step is to find where the crash occurred

Author's Note
Comment
If you've still got a bug after fixing the warnings, then this means that there's something a little bit less trivial wrong with your code. Now you need to do some real debugging.
Page 7: 6 Debugging c

Starting the Debugger

$ gdb a.out

"...(no debugging symbols found)...

(gdb) r

Starting program: /tmp/a.out

(no debugging symbols found)...

(no debugging symbols found)...

Program received signal SIGSEGV, Segmentation fault.

0x080485f6 in main ()

So, it crashed somewhere in main()?

Author's Note
Comment
The GNU debugger (gdb) is present on most *NIX systems. It's a simple interactive debugger. If you're using an IDE (e.g. XCode, Visual Studio), then you may have a graphical debugger available too.
Page 8: 6 Debugging c

Debugging Symbols

• The debugger needs information to map memory addressesback to the source code

• This is not provided by default

$ c99 -g broken.c

$ gdb ./a.out

(gdb) r

Starting program: a.out

Program received signal SIGSEGV, Segmentation fault.

0x08048515 in initialise () at broken.c:24

24 array[i] = random();

(gdb) bt

#0 0x08048515 in initialise () at broken.c:24

#1 0x08048592 in main (argc=1, argv=0xbfbfe72c)

at broken.c:40

Author's Note
Comment
For the debugger to work, it needs to be able to, for example, map address 0x08048592 to line 40 in broken.c, map other addresses to global variables, contents of registers to stack frames, and so on. This needs the compiler to embed some metadata in the binary. The -g option to the compiler tells it to do this.
Page 9: 6 Debugging c

What Could Be Wrong Here?

�array[i] = random (); � �

• Memory at array[i] is not valid?

• Array out of bounds error?

Author's Note
Comment
This is the line that crashed. It's an assignment, and it produced a SegV, so the problem is probably that the value on the left of the assignment is an invalid memory location. Remember that arrays in C are just blobs of memory and array subscripting is just a shorthand form of pointer arithmetic, so array[i] is some pointer arithmetic producing an address, and the assignment is trying to store something here
Page 10: 6 Debugging c

Try Another Tool: Valgrind

• Valgrind emulates all load / store instructions

• Tracks memory accesses

• Logs useful errors

• Much slower than running the code normally

Author's Note
Comment
Valgrind is another very useful debugging tool. It can help you find most common memory errors. It makes your code very slow, but it can be worthwhile.
Page 11: 6 Debugging c

Using Valgrind

�$ valgrind ./a.out

Memcheck , a memory error detector

Command: ./a.out

Invalid write of size 4

at 0x8048515: initialise (broken.c:24)

by 0x8048591: main (broken.c:40)

Address 0x1d8ab0 is 0 bytes after a block of

size 400 ,000 alloc ’d

at 0x5A138: malloc

by 0x80484B9: resize (broken.c:11)

by 0x804858C: main (broken.c:39) � �

Author's Note
Comment
Sample valgrind output. It told us where the problem was, and a bit more information.
Page 12: 6 Debugging c

Invalid Write?

�Invalid write of size 4

at 0x8048515: initialise (broken.c:24)

by 0x8048591: main (broken.c:40) � �• Assigning a 32-bit integer value to memory is a write of size 4

• This shows where the crash was

• But we knew that already

Author's Note
Comment
This is just a bit more detail than the SegV message. It says that we tried to write 4 bytes (one 32-bit integer) into a value that we did not have access to. Note that this crash happened sooner than the version in the debugger - valgrind checks the memory that we should be able to access, not the memory that we can, so it fails as soon as we go over the end of the allocation, not when we go over the memory that the allocator has requested from the OS.
Page 13: 6 Debugging c

The Interesting Bit

�Address 0x1d8ab0 is 0 bytes after a block of

size 400 ,000 alloc ’d

at 0x5A138: malloc

by 0x80484B9: resize (broken.c:11)

by 0x804858C: main (broken.c:39) � �• We allocated 400,000 bytes

• The stack trace tells us where

Author's Note
Comment
This is more useful - it tells us the size of the allocation and where it was allocated. The '0 bytes after' bit implies that we just tried to access the address immediately after the end of the array. Either an off-by-one error, or an iteration error.
Page 14: 6 Debugging c

The Bug

�// The allocation

array = malloc(count);

// The use:

for (int i=0 ; i<count ; i++)

{

array[i] = random ();

} � �• What’s the error?

• Allocating 400,000 bytes

• Expecting 400,000 integers

Author's Note
Comment
This is a fairly common error among novice C programmers - forgetting that malloc() does not know the type of the thing that you are assigning to.
Page 15: 6 Debugging c

The Fix

�// Wrong:

array = malloc(count);

// Correct:

array = malloc(count * sizeof(int));

// Also correct:

array = calloc(count , sizeof(int)); � �Always remember that malloc() does not know the size of the

variable, it just expects a size in bytes.

Author's Note
Comment
The first correct version has the potential for an integer overflow, in count * sizeof(int). This can cause some nasty errors - the overflow may give a small value, so you only get a few hundred KB allocated, but think you have a few GBs. The second version is safer, and is recommended everywhere that performance is not absolutely a vital concern. It also zeros the memory for you.
Page 16: 6 Debugging c

Heisenbugs

• Sometimes running in the debugger makes bugs vanish

• In this case, resort to older techniques

• Add log statements to find what’s wrong

Author's Note
Comment
Code run in the debugger will have a slightly different memory layout, which can make some bugs vanish or others appear. Similarly, running code on a different OS may have the same effect.
Page 17: 6 Debugging c

Logging

�printf("About to use array %p\n", array); � �

• Looks right?

• printf() writes to the standard output

• Standard output is buffered

• Buffer might not be flushed before crash

Author's Note
Comment
This is a simple logging statement, but it is not safe to use. If the program crashes before the buffer is flushed, the log statement will be lost.
Page 18: 6 Debugging c

Better Logging

�fprintf(stderr ,"About to use array %p\n",array); � �

• Standard error is unbuffered

• Output is written to the terminal immediately

Author's Note
Comment
Writing to stderr avoids logging, which is an advantage, but it has another problem in multithreaded code: two threads writing to stderr are implicitly synchronised. This means that using reliable logging can cause thread-related problems to vanish. There is no way of logging without subtly affecting the program semantics, unfortunately.
Page 19: 6 Debugging c

Even Better Logging

�#ifdef DEBUG

# define LOG(msg , ...) \

fprintf(stderr , msg , ## __VA_ARGS__)

#else

# define LOG (...)

#endif � �$ c99 -g -DDEBUG code.c

Author's Note
Comment
This is a slightly more complex form, which only enables the log messages when compiled with -DDEBUG, or if you add ##define DEBUG at the top of the source file. This kind of thing is very useful for code that you want to test for a bit and then ship, but may want to test some more later - it lets you turn on and off the debugging on a per-compile basis.
Page 20: 6 Debugging c

Other Tools

• Clang static analyser

• DTrace (Solaris / Mac / FreeBSD)

• ...

Author's Note
Comment
There are lots of other tools that can help with debugging. Some focus on very complex cases, others are simple. The Clang static analyser doesn't run the code, it just provides some warnings that are more advanced than the standard compiler warnings. DTrace uses dynamic modification of the binary to insert arbitrary probes. Pin, from Intel, is similar.
Page 21: 6 Debugging c

Demo / Questions