Lab 1 - Using gdb

Background

The gdb utility is the debugger that comes with the GNU compilers such as gcc and g++. It allows you to step through a program's execution one line at a time or debug what caused a core dump. In order to get the most information from gdb, you must compile the program with debugging info. This is achieved by using the -g option. If the program is compiled with debugging info, gdb can give you information about the program such as line numbers, function names, statements being executed and so on.

Resources:

Part I: Debugging a core dump

A core dump occurs when the program encounters a run-time error such as divide-by-zero or using a pointer that has not been initialized. For this part, you will be compiling the file lab1.cpp, which has an intentional bug that will cause a core dump. Use the following commands to copy the file over to your current directory and compile it with debugging info:
cp /home/fac/melissa/public_html/cs223/lab1.cpp .
g++ -g -o lab1 lab1.cpp
You will then need to enable core dumps on Sleipnir. This command will have to be run once every login when you wish to use gdb to debug core dumps. If you forget this command, your code will crash but a core dump file will not be generated:
ulimit -c unlimited
You should now have an executable called lab1 which will core dump when run. Run the program and verify that you have a core dump by looking for the file core in the current directory.
./lab1
ls -lh core
Notice that the core file can be quite large. You should always delete the core file when you are done debugging it. To invoke the debugger type:
gdb lab1 core
The first argument to gdb is the executable name (lab1) and the second argument is the core dump filename (core).

You will now be at the command prompt for gdb. Type the command

bt
This will show you all the function calls that lead to the problem code. Sometimes, as with this example, your code may crash while executing a library function. The bt command will eventually print out line numbers from your code that you'll wish to investigate. At the start of each line, there will be a frame number such as #0, #1, etc.

Find the frame number for the line of code you wish to look further at. In this example, we're interested in looking at frame 1. We can look at frame 1 by typing the command

frame 1

Now we want to see the value for all variables local to the function. Type the command info locals. For this example, you should see something like:

(gdb) info locals
i = 0
a = (int *) 0x0
size = 5
The value for size will be whatever you entered as the size when you ran lab1. Notice the value for a is 0x0. This means that the pointer has not been initialized since there was no new statement after asking for the size.

The info locals command only works with variables that were declared within the current scope. It does not work on parameters passed to functions or global parameters. However, if that variable is available in the current scope, you can use the print command to display its current value. For example:

print size
Exit gdb by typing the command quit. Don't forget to delete the core file so you don't run out of disk quota.

Part II - Stepping through code

Fix the error in lab1.cpp by adding the statements to allocate memory with the new command. Recompile the code and see if your changes worked by using line-by-line debugging mode:
g++ -g -o lab1 lab1.cpp
gdb lab1
Notice that we don't run the executable at the command line. Instead we are using the debugger to trace the execution. When you start the debugger, it will again present you with the gdb.

Your first task is to set up breakpoints. This is where the debugger will stop execution of the code and let you investigate the state of the variables. You can set a breakpoint at a function or a line number. For example, to set a breakpoint at main and another at line 20, use the commands:

break main
break 20
You can list your current breakpoints with the command
info breakpoints
To start the execution of the code, give the command
run
This will cause the code to execute until a breakpoint is reached. When a breakpoint is reached, you can use the info locals and print commands to print out the value of variables.

Another useful thing to do when you reach a breakpoint is to trace the execution line-by-line, also called stepping through code. This allows you to pinpoint exactly where the code is malfunctioning. There are two commands to do this:

step
next
The only difference between these two is that step will go line-by-line through any function call while next will execute the entire function and go to the next line after the function call. Both commands are useful in different circumstances. You should be familiar with both.

After you are done stepping through that section of code, you can tell the compiler to continue execution until the next breakpoint or the code exits with the command

continue

Use the debugger commands to see if you have properly fixed the code and the array is being allocated. Try setting breakpoints at various line numbers and function names. Get familiar with using the various commands, particularly with printing out pointers. This will be helpful for the rest of the course.

Email the corrected lab1.cpp to me as your submission for this lab.