CSCI 104 - Fall 2017 Data Structures and Object Oriented Design

Debugging

What is Debugging? Debugging is the process by which programmers attempt to find and remove bugs and errors. Anytime the code does something you don't want and you attempt to fix it, you are debugging.

What is GDB? GDB stands for GNU Debugger. This is a tool to help you find your issues. GDB won't solve them for you, but it does a good job in helping you find issues in your code by letting you trace and retrace.

0 - Fixing Valgrind on your VM

If you have tried using valgrind for homework (as you should), you might have noticed that valgrind is reporting a block of "still reachable" memory, despite that you did not even use dynamic allocation in your code.

Here's what your valgrind output might look like:

==3730== HEAP SUMMARY:
==3730==     in use at exit: 72,704 bytes in 1 blocks
==3730==   total heap usage: 1 allocs, 0 frees, 72,704 bytes allocated
==3730== 
==3730== LEAK SUMMARY:
==3730==    definitely lost: 0 bytes in 0 blocks
==3730==    indirectly lost: 0 bytes in 0 blocks
==3730==      possibly lost: 0 bytes in 0 blocks
==3730==    still reachable: 72,704 bytes in 1 blocks
==3730==         suppressed: 0 bytes in 0 blocks

As it turns out, this is a bug with valgrind and here we have a workaround for it.

If you have cloned the homework-resources repository, navigate to that directoty and pull. Otherwise, clone the directoty.

# If you have homework-resources cloned on your local machine, navigate to there
cd path/to/homework-resources

# get the lastest
git pull

# we want to go into tools directory
cd tools
# If you don't have homework-resources, clone it
git clone git@github.com:usc-csci104-fall2017/homework-resources.git

# change directory to homework-resources/tools
cd homework-resources/tools

We are going to run a python script to set up some files we need.

# set up the files we need
python gen_supp.py

You should see something like this

Succesfully generated suppression file.
Add --suppressions=/home/student/gcc.supp to the valgrind flags when running valgrind to use this file.

Now when you run valgrind, you need to add --suppressions=/home/student/gcc.supp to your valgrind flags. As an example, if you were to run valgrind on your sum program in homework 1, you would use:

valgrind --tool=memcheck --leak-check=yes --suppressions=/home/student/gcc.supp ./sum 4098

You should see something like this. Notice that "still reachable" memory is now 0 bytes and "suppressed" leak reports 72,704 bytes:

==3759== HEAP SUMMARY:
==3759==     in use at exit: 72,704 bytes in 1 blocks
==3759==   total heap usage: 1 allocs, 0 frees, 72,704 bytes allocated
==3759== 
==3759== LEAK SUMMARY:
==3759==    definitely lost: 0 bytes in 0 blocks
==3759==    indirectly lost: 0 bytes in 0 blocks
==3759==      possibly lost: 0 bytes in 0 blocks
==3759==    still reachable: 0 bytes in 0 blocks
==3759==         suppressed: 72,704 bytes in 1 blocks

When we grade you, we will ignore the particular suppressed memory leak. However, you are still responsible for memory leak and other valgrind errors in your program. If you haven't used valgrind yet, now is a good time to start.

1 - Clone the Labs Repository

Remember how in the last lab we cloned a repository to bring it down to our local machines? We'll do the same here so that you can get Lab 2 on your computer. Open the terminal, find a location you want to place this course's labs folder, and run the following commands:

git clone git@github.com:usc-csci104-fall2017/labs.git
cd labs/lab2/

In the lab2 directory, you should have three files, sample.cpp, lab02.cpp, and README.md.

If you do not have access to the github repository yet, please resolve it this week following instructions in this piazza note. For now, you can download the three files here: README.md, sample.cpp, lab02.cpp.

2 - Compile and Run

GDB should already be installed on the Course Virtual Machine (VM). To use GDB, we must have an executable with it. We'll be using sample.cpp. First compile and run:

g++ -g sample.cpp -o sample

Notice the -g command. This enables debug information for the resulting executable. More on this later. Let's first run the executable:

./sample

The program should have encountered a segmentation fault. This is an error you may see quite often this semester. Simply put, a segmentation fault means the program is accessing memory that it does not have access to.

By looking at the code in the "sample.cpp", it may not be completely obvious as to why the program may issue a seg-fault. Let's use GDB to help find the issue.

3 - How to use GDB

GDB uses its own shell to help debug. This means GDB will look similar to that of a terminal (in fact, it is used inside the terminal!) and you will use gdb-specific commands to perform certain operations.

3.1 - Running GDB

The first step is to get GDB up and running. Because we want to debug sample.cpp, we will pass our sample executable as an argument.

gdb sample

GDB should now be running within the terminal. Great! If at any point you lose track and want to restart, quit the GDB program with the quit command and rerun GDB.

3.2 - Breakpoints

Before we begin running the executable within GDB, we would like to set a breakpoint. A breakpoint is a term you will hear often when it comes to debugging. This is essentially a location that GDB will pause the program at (consider location at a line number). For example, in our sample program, we already know it has seg-faults, so running it through GDB will do nothing for us unless we stop the program before the seg fault actually occurs.

Let's go ahead and do this. To be on the safe side, lets set a breakpoint right at the beginning of the program. Line 22 is the first line in our main function, let's stop the program there. To use breakpoints, use the format break line-number. As such:

break 22

The response you receive should be similar to the following:

Breakpoint 1 at 0x40091b: file sample.cpp, line 22.

Note: if you received the response No symbol table is loaded. Use the 'file' command this means you did not include the -g command when you compiled the program. Follow the "Compile and Run" section to compile correctly.

3.3 - Walking Through the Code

Let's start the debugging process. Begin by running the program:

run

The run command tells GDB to begin running the program. It should stop at the first breakpoint, or until the program stops by itself (whatever comes first).

GDB should have printed out what line we are looking at. It should look something like this:

Breakpoint 1, main () at sample.cpp:22
22 cout << "What is the meaning";

Let's continue slowly by moving to the next line with the **next **command:

next

This brings us to line 23. Many commands in GDB can be shortened by looking at their first letter (for example, next can be abbreviated to n). Because line 23 is just another print statement, let's go to the next line.

n

We should now be at the following line:

cout << TheTruth () << endl;

This line looks interesting, but if we use the *next *command again, then we will reach the last line of the program. Well, the step command can help us here. step allows us to enter into the function given on this line, letting us go a level deeper. In this case, it would step into the TheTruth() function. If we were already at the deepest level, step would function just as next. Let's use step:

step

We should now be at line 11, inside of TheTruth().

3.4 - Viewing Current State of the Process

If we were tracing our steps through a large program, we may need GDB to help us remember where we've been. We can look at how we reached where we currently are by using the backtrace command. This can also be shortened to bt or where:

bt

This should have returned:

#0 TheTruth () at sample.cpp:11
#1 0x000000000040094b in main () at sample.cpp:24 

#0 tells us which function we are currently in. We reached #0 from #1 (and if there was a larger backtrace, #1 would have been reached from #2, #2 from #3, and so on). This is also called the call stack. Keep in mind that the current function is always at the top in a call stack.

We can also view the current state of our variables by using the print command. Let's try printing the new variable located in TheTruth().

print buffer

This is the output on my local machine:

$2 = (void *) 0x7ffff7b6a19e <std::ostream::flush()+30>

Yours may look a little different. Because the variable, *buffer, *is a pointer, it displays an address (and that address may be different on your computer). Because this variable has not been set yet, this value is garbage. After line 11 is executed, it should receive a new valid address. Let's use next to make that happen.

next

Let's make sure buffer received a valid address by printing its value. Here is the output of my terminal beginning from next:

(gdb) next
14 *(int*)buffer = ( (1 << 5) + 10 ) ^ 0x04 ^ 0x04;
(gdb) print buffer
$3 = (void *) 0x0

Oh no! It seems our buffer variable holds the address 0! If you don't know already, 0x0 is an address generally reserved to represent NULL values, implying our buffer is a NULL value!

Our program hasn't seg-faulted yet because we have not attempted using buffer yet. When we execute it on this next line (line 14), we should see it seg-fault. Try using the next command to see for yourself.

This must mean line 11 must not have initialized correctly. Let's change the line in sample.cpp from:

buffer = malloc(1 << 31);

to:

buffer = malloc(1 << 2);

Quit GDB using quit. Recompile and run the sample.cpp. It should no longer seg-fault.

If you're curious as to why this solves the issue, ask a CP or TA.

4 - Assignment: Find the Secret Message

Your job is to now use GDB to fix the program from seg-faulting in lab02.cpp. We will check you off when you show us the secret message and tell us what you did to obtain that message. Good luck!

5 - List of Helpful GDB Commands