Or: "Debugging for fun and profit"
After logging into the lab server, change into the unix directory by typing cd unix and then make a directory called 'lab2' by typing mkdir lab2. Then change into the lab2 directory by typing cd lab2.
For this first exersize, we'll fix a program that compiles just fine, but pukes when you try to run it.
Copy 'buggy.c' from /tmp into the lab2 directory by typing cp /tmp/lab2/buggy.c .
Now compile the buggy program with gcc buggy.c -o buggy. Then try running it by typing ./buggy. Watch it fail horribly.
To include debugging symbols in the executable, pass the -g flag to gcc like so:
gcc -Wall -g buggy.c -o buggy
The -Wall means "Turn on all warnings". I want you to get used to including that.
You might want to write a Makefile that looks something like this:
debug: gcc -Wall -g buggy.c -o buggy strip: debug strip buggy
Now when you want to compile the program with debugging symbols, you can just type 'make'. When you want to build a non-debug version, type 'make strip'. The 'strip' program removes (strips) debugging symbols from an executable file. Note also that the "strip" target depends on the "debug" target (so indicated by putting the target name after the colon) so it will build debug first before running its own commands.
GDB is short for the "GNU Debugger". You can get information from the following sources:
You start GDB by typing gdb progname, where progname is the name of some executable program that you want to debug. If you didn't compile the program with the -g flag you'll get an error that says "No debugging symbols found".
You should find yourself at the (gdb) prompt. You can start the program running by typing run. (If you didn't specify a progname when you started gdb, you can type file progname at the (gdb) prompt.) Now when the program crashes, you can look around at what went wrong.
To display the contents of a variable, type print i. If you try to display the value of the array with print ary[i], you will get an address. This is because the C language treats arrays as pointers. If you want to get the value of the array, you can type print *ary[i] or x/d ary[i].
Having successfully found the cause of the bug, modify the program so that the bug not longer occurs. Left as an exersize for the student to find a good way to do that.
There is a text-window interface built into GDB that you can use. This section of the GDB manual talks about that.
You can also use Emacs as a front-end for gdb. You do this by starting Emacs and then typing M-x gdb progname. You can then type C-xas to step, C-xan for 'next', C-xar for continue, etc. This section of the GDB manual talks about using GDB under Emacs.
If we were able to run an X Window session, we could all be using DDD: The Data Display Debugger. Screenshots. DDD is nice.
Now let's look at some memory-allocation problems.
Copy 'leaky.c' into your own lab2 directory by typing cp /tmp/lab2/leaky.c . This is very similar to the code in the book on pgs. 53-54.
Compile it with:
gcc -g leaky.c -o leaky
You could even modify the Makefile to make it look like this:
debug: buggy leaky buggy: buggy.c gcc -Wall -g buggy.c -o buggy leaky: leaky.c gcc -Wall -g leaky.c -o leaky strip: debug strip buggy strip leaky
Adding 'buggy.c' and 'leaky.c' as a dependency after the respective targets forces make to re-compile those programs if the .c source file is modified (which is what you want it to do).
Now you can just type 'make' to build both.
Note: From here on out, we're following the steps outlined in the book beginning on page 55. Note that I have changed the order of some of the lines in the sample code.
Add -lefence to the 'leaky' target's command so that it reads:
gcc -Wall -g leaky.c -o leaky -lefence
This means "link with the libefence library". Note that you do not type "-llibefence", but just "-lefence"; the lib- prefix is assumed (and standard).
Now if you re-build and re-run the program, it will catch most of the dynamic leaks. You may need to set some environment variables. If you like, you can run the program through gdb to help you get a look at the lines where the memory abuses occur.
An alternative to using Electric Fence is to use 'checkergcc'. You can replace each instance of 'gcc' with 'checkergcc' in your Makefile. This is a good occasion to use makefile variables so you only have to change it in one place. Modify your Makefile to look like this:
CC=checkergcc debug: buggy leaky buggy: buggy.c $(CC) -Wall -g buggy.c -o buggy leaky: leaky.c Makefile $(CC) -Wall -g leaky.c -o leaky #$(CC) -Wall -g leaky.c -o leaky -lefence strip: debug strip buggy strip leaky
Now, to switch back and forth, you can just change the value of the CC variable. Note that checkergcc and libefence do not play nicely together, so you also have to comment out the line that links to libefence. (Online copy of the final Makefile.)
Now you can compile it and run it and it will print lots of helpful information about your memory leaks at run-time, rather than making you use a debugger. Due to the sheer amount of information that checkergcc produces, you will probably want to run the commmand like this:
./leaky 2>&1 | less
The "2>&1" means: "send stdout to the same place as stderr". The "| less" means: "pipe output through the less program", which allows you to scroll up and down, search for things, etc.
You might find it instructive to use the strace program. This program shows system calls made by a program and are occasionally helpful in tracking down where a problem is occurring. The previously mentioned approaches are usually superior. People usually use strace when they haven't got any source code available.
Here is a complete listing of all the files for this lab. You should put them all in a lib2 directory with these names: