Lab 5: Debugging C with gdb
Goals
Your primary goal for this lab is to get familiar with using the GNU debugger (gdb
) to explore and debug C programs.
Documentation and tutorials
You will find some potentially useful gdb resources posted on our Resources page. There is also a really handy summary sheet on page 280 of the textbook.
Getting started
Here are a few rough steps to guide you through some gdb
basics. You’ll have questions, so write them down and ask on Slack!.
-
Grab a copy of the
gdb_test.c
sample. -
Compile it like so:
gcc -Wall -Werror -g -Og -o gdb_test gdb_test.c
The
-g
means “include debugging info in the executable. The-Og
(that’s a capital O, not a zero) means “optmize this code for the best debugging experience”.
-
Try running the program to compute the factorial of 6:
./gdb_test 6
-
Open up
gdb
:gdb gdb_test ...lots of notices print out... (gdb) [this is the gdb prompt]
Note that I’ll use
[
and]
to delineate comments to you in terminal interactions withgdb
.
-
Run the program to compute 6! again:
(gdb) run 6
-
Most
gdb
commands can be executed using abbreviations:(gdb) r 6
-
You can show 10 lines (the default count) of the source code:
(gdb) list
The
list
command makes guesses about which 10 lines you want to see. For example, it selects the “current” source file and starting line (whatever that may mean in context).
-
Instead, show lines 10 through 25:
(gdb) list 10,25
-
You can also explicitly name the source file if your executable comes from multiple sources (remember
qtest
from the queues assignment?):(gdb) list gdb_test.c:10,25
-
For all
gdb
commands, if you hitreturn
at the next prompt,gdb
tries to repeat the previous command. So, for example, try this:(gdb) list 1,20 ...[listing of source code]... (gdb) [hit return] ...[what do you see?]
Working with breakpoints
Let’s set our first breakpoint and explore. Like in VS Code, a breakpoint is a place where gdb
will pause execution of your code and let you look at the values in variables, registers, and memory at that moment.
-
Set a breakpoint at line 19 (this pauses just before executing the recursive call
factorial(n-1)
, but you can put it anywhere):(gdb) br gdb_test.c:19 [or] (gdb) br 19 [if the filename were ambiguous]
-
Run the program, which will pause at your breakpoint:
(gdb) r 6 [recall that r=run]
-
Look at the code just before and after your breakpoint:
(gdb) list
-
We can also look at the current contents of a parameter or local variable:
(gdb) print n
-
We can also look at the current “backtrace” (i.e., the list of function calls that are currently active, which is especially handy for recursion):
(gdb) bt
-
Let’s continue the program’s execution:
(gdb) continue [or] (gdb) c
-
Again, this breaks (i.e., pauses) at line 19. Take a look at the backtrace (also known as a stack trace), to see that there’s a new call to
factorial(5)
on the stack:(gdb) bt
Looking at registers and memory
If you aren’t already debugging gdb_test.c
, follow the steps in the previous section. We’ll continue where that left off.
-
Repeat the continue+backtrace sequence until the top of the backtrace is
factorial(3)
. -
Now, we can see the contents of the registers:
(gdb) info reg [or] (gdb) i r
Take special note of the stack-pointer register
%rsp
. For me, as I write this,factorial(3)
is the most recent function call in the backtrace, and the value stored in%rsp
is0x7fffffffe980
.
- Let’s look at the system stack, starting where
%rsp
points:(gdb) x/40wx 0x7fffffffe980 [change to whatever %rsp contains]
Here, the first
x
stands for “examine”, thew
is “show me 4-byte words”, the40
is how many words to see, and the secondx
means “show me the word values in hex”. You can find more formats in quick reference guides and documentation.Study the memory contents. Can you see where the
n
variable for all of those successive calls tofactorial()
are stored? How many bytes seem to be in each of the function calls’ stack frames? What is stored in the rest of a given call’s stack frame?
Stepping through code beyond breakpoints
- If you want to execute exactly one line of code, you can use
next
orni
(for “next instruction”):(gdb) ni
Note that if the upcoming line of code includes a function call and there’s a breakpoint inside of that function, execution will pause before your “next” operation gets a change to execute the entirety of line 19.
- If you’re paused at line 19 (
result = factorial(n-1)
) and you want to “step into” the function callfactorial(n-1)
, you can usesi
:(gdb) si
In this case, that should take you to the first line of factorial with a smaller value of
n
.
- Keep playing around!
Next steps
Our next step is to do this same thing in assembly. In the meantime, you can start attempting to escape from the zoo. Good luck!