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 email@example.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
==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 firstname.lastname@example.org:usc-csci104-fall2017/labs.git cd labs/lab2/
lab2 directory, you should have three files,
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
-g command. This enables debug information for the resulting executable. More on this later. Let's first run the executable:
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 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:
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 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:
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.
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
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
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
#1 (and if there was a larger backtrace,
#1 would have been reached 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
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.
Let's make sure
buffer received a valid address by printing its value. Here is the output of my terminal beginning from
(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);
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
gdb executable-name: used to run the GDB program
r [command-line-arguments]: runs the current executable within gdb with the (optional) specified command line arguments.
q: quits the gdb environment
n: goes to the next line in sequence of the program.
s: attempts to go deeper into the functionality beneath the current line of code (e.g. stepping into a function). Otherwise acts as next.
b line-number: sets a breakpoint at the specified line number. One can also break at a function using the function name.
where: prints the function calls made to get to the current state of the program.
p expr: prints the result of the given expression,
expr. This can be a variable value (e.g.
print x) or a complex statement (e.g.
print it->second.size()as long as the expression is in the scope of your current code location)
b filename:line-number: same as
break line-number, but used to specify which file to put break point in when your program consists of multiple files
c: this is a command you did not learn, but can be quite useful. Instead of stepping line by line,
continueruns the program until a breakpoint is reached or until the program terminates.
info break: list all breakpoints
d N: delete the Nth break point. Use after
info breakfrom which you get a list of breakpoints.