Written by Chris Gregg and Nate Hardison, with modifications by Nick Troccoli and Lisa Yan
Click here for a walkthrough video.
In CS106A and CS106B, you may have used a graphical debugger; these debuggers were built into the program you used to write your code, and allowed you to set breakpoints, step through your code, and see variable values, among other features. In CS107, the debugger we are using is a separate program from your text editor, called gdb
(the "GNU Debugger"). It is a command-line debugger, meaning that you interact with it on the command line using text-based commands. But it shares many similarities with debuggers you might have already used; it also allows you to set breakpoints, step through your code, and see variable values. We recommend familiarizing yourself with how to use gdb
as soon as possible.
This page will list the basic gdb
commands you will need to use as you progress through CS107. However, it takes practice to become proficient in using the tool, and gdb
is a large program that has a tremendous number of features. See the bottom of the page for more resources to help you master gdb
.
Getting Started
Before you start using gdb
, you'll want to configure it to use the CS107 default preferences; this sets up the debugger to know things like our work will be in 64-bit systems. To do this, execute the following command after logging into myth
:
wget https://cs107.stanford.edu/resources/sample_gdbinit -O ~/.gdbinit
gdb
looks for a special .gdbinit
file on your system to know what preferences you would like, and this command downloads our pre-made .gdbinit
file and puts it on your system. If you don't do this, some behavior may not match guides and examples in CS107.
Compiling for gdb
: gcc
does not automatically put debugging information into the executable program, but our Makefiles all include the -g -Og
flags that give gdb
information about our programs so we can use the debugger efficiently.
Running gdb
gdb
takes as its argument the executable file that you want to debug. This is not the .c file or the .o file, instead it is the name of the compiled program:
$ gdb myprogram
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from square...done.
(gdb)
The (gdb)
prompt is where you start typing your commands. Note that nothing has happened yet - your program has not started running to debug - gdb
is simply awaiting further instructions.
Once you've got the (gdb) prompt, the run
command (shorthand: r
) starts the executable running. If the program you are debugging requires any command-line arguments, you specify them to the run
command. To run myprogram
with the arguments "hi" and "there", for instance, you would type the following:
(gdb) run hello world
Starting program: /cs107/myprogram hi there
This starts the program running. When the program stops, you'll get your (gdb) prompt back.
Breakpoints
Normally, your program only stops when it exits. Breakpoints allow you to pause your program's execution wherever you want, be it at a function call or a particular line of code, and examine the program state.
Before you start your program running, you want to set up your breakpoints. The break
command (shorthand: b
) allows you to do so.
To set a breakpoint at the beginning of the function named main
:
(gdb) break main
Breakpoint 1 at 0x400a6e: file myprogram.c, line 44.
To set a breakpoint at line 47 in myprogram.c
:
(gdb) break myprogram.c:47
Breakpoint 2 at 0x400a8c: file myprogram.c, line 47.
If there is only once source file, you do not need to include the filename.
Each breakpoint you create is assigned a sequentially increasing number (the first breakpoint is 1, the second 2, etc.).
If you want to delete a breakpoint, just use the delete
command (shorthand: d
) and specify the breakpoint number to delete.
To delete the breakpoint numbered 2:
(gdb) delete 2
If you lose track of your breakpoints, or you want to see their numbers again, the info break
command lets you know the breakpoint numbers:
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a6e in main at myprogram.c:44
Finally, notice that it's much easier to remember function names than line numbers (and line numbers change from run to run when you're changing your code), so ideally you will set breakpoints by name. If you have decomposed your code into small, tight functions, setting breakpoints will be easy. On the other hand, wading through a 50-line function to find the right place for a breakpoint is unpleasant, so yet another reason to decompose your code cleanly from the start!
Extra: Conditional Breakpoints
You can set breakpoints to only trigger when certain conditions in your code are true. For instance, say you have the following loop in your code:
1 for (int i = 0; i < count; i++) {
2 ...
3 }
If you wanted to step through the code inside the loop just the last time the loop executed, with a normal loop you may have to skip over many program breaks before you get to the part you want to examine. However, gdb
lets you add an optional condition (in C code syntax) for when the breakpoint should be stopped at:
(gdb) break 2 if i == count - 1
The format is [BREAKPOINT] if [CONDITION]
. Now this breakpoint will only be hit the last time around the loop! You can even use local variables in your expression, as shown above with i
and count
. Experiment to see what other useful conditions you might use.
The following sections deal with things you can do when you're stopped at a breakpoint, or when you've encountered a segfault.
Examining Program State
Backtrace
Easily one of the most immediately useful things about gdb
is its ability to give you a backtrace (or a "stack trace") of your program's execution at any given point. This works especially well for locating things like crashes ("segfaults"). If a program named reassemble
segfaults while running in GDB during execution of a function named read_frag
, gdb
will print the following information:
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400ac1 in read_frag (fp=fp@entry=0x603010, nread=nread@entry=0) at reassemble.c:51
51 if (strlen(unusedptr) == MAX_FRAG_LEN)
Not only is this information vastly more useful than the terse "Segmentation fault" error that you get outside of gdb
, you can use the backtrace
command to get a full stack trace of the program's execution when the error occurred:
(gdb) backtrace
#0 0x0000000000400ac1 in read_frag (fp=fp@entry=0x603010, nread=nread@entry=0) at reassemble.c:51
#1 0x0000000000400bd7 in read_all_frags (fp=fp@entry=0x603010, arr=arr@entry=0x7fffffff4cb0, maxfrags=maxfrags@entry=5000) at reassemble.c:69
#2 0x00000000004010ed in main (argc=<optimized out>, argv=<optimized out>) at reassemble.c:211
Each line represents a stack frame (ie. a function call). Frame #0 is where the error occurred, during the call to read_frag
. The hexadecimal number 0x0000000000400ac1 is the address of the instruction that caused the segfault (don't worry if you don't understand this, we'll get to it later in the quarter). Finally, you see that the error occurred from the code in reassemble.c
, on line 51. All of this is helpful information to go on if you're trying to debug the segfault.
Variables and Expressions
You're likely to want to check into the values of certain key variables at the time of the problem. The print
command (shorthand: p
) is perfect for this. To print out the value of variables such as nread
, fp
and start
:
(gdb) print nread
$1 = 0
(gdb) print fp
$2 = (FILE *) 0x603010
(gdb) print start
$3 = 123 '{'
You can also use print
to evaluate expressions, make function calls, reassign variables, and more.
Sets the first character of buffer to be 'Z':
print buffer[0] = 'Z'
$4 = 90 'Z'
Print result of function call:
(gdb) print strlen(buffer)
$5 = 10
The following commands are handy for quickly printing out a group of variables in a particular function:
info args
prints out the arguments (parameters) to the current function you're in:
(gdb) info args
fp = 0x603010
nread = 0
info locals
prints out the local variables of the current function:
(gdb) info locals
start = 123 '{'
end = 125 '}'
nscanned = 3
Stack Frames
If you're stopped at a breakpoint or at an error, you may also want to examine the state of stack frames further back in the calling sequence. You can use the up and down commands for this.
up
moves you up one stack frame (e.g. from a function to its caller)
(gdb) up
#1 0x0000000000400bd7 in read_all_frags (fp=fp@entry=0x603010, arr=arr@entry=0x7fffffff4cb0, maxfrags=maxfrags@entry=5000) at reassemble.c:69
69 char *frag = read_frag(fp, i);
down
moves you down one stack frame (e.g. from the function to its callee)
(gdb) down
#0 0x0000000000400ac1 in read_frag (fp=fp@entry=0x603010, nread=nread@entry=0) at reassemble.c:51
51 if (strlen(unusedptr) == MAX_FRAG_LEN)
The commands above are really helpful if you're stuck at a segfault and want to know the arguments and local vars of the faulting function's caller (or that function's caller, etc.).
Controlling Execution
run
will start (or restart) the program from the beginning and continue execution until a breakpoint is hit or the program exits. start
will start (or restart) the program and stop execution at the beginning of the main
function.
Once stopped at a breakpoint, you have choices for how to resume execution. continue
(shorthand: c
) will resume execution until the next breakpoint is hit or the program exits. finish
will run until the current function call completes and stop there. You can single-step through the C source using the next
(shorthand: n
) or step
(shorthand: s
) commands, both of which execute a line and stop. The difference between these two is that if the line to be executed is a function call, next
executes the entire function, but step
goes into the function implementation and stops at the first line.
Useful Tips
- If you're using a Makefile, you can recompile from within
gdb
so that you don't have to exit and lose all your breakpoints. Just typemake
at the (gdb) prompt, and it will rebuild the executable. The next time yourun
, it will reload the updated executable and reset your existing breakpoints. - Use
gdb
inside Emacs! It's just another reason why Emacs is really cool. Use "Ctrl-x 3" to split your Emacs window in half, and then use "Esc-x gdb" or "Alt-x gdb" to start upgdb
in your new window. If you are physically at one of the UNIX machines, or if you have X11 forwarding enabled, your breakpoints and current line show up in the window margin of your source code file. - If you simply type the enter key, the last command will be re-run. This is nice if you are stepping through a program line-by-line: you can use the
next
command once, and then hitting the enter key will re-run thenext
command again without you having to type the command.
Debugging Strategies
Along with gdb
, it's important to use good debugging strategies when working on your programs. Many students have up to now done all their debugging via print statements. That works for simple cases, but becomes unwieldy as programs get larger and have more complex interactions. Now is the time to invest in mastering the powerful tools provided by a debugger like gdb
, and using a careful and systematic debugging approach:
- Observe the bug. If you never see the bug, you'll likely never fix it. Another reason you want comprehensive testing!
- Create a reproducible input. Creating a trivial input that reliably induces the failure is a huge help.
-
Narrow the search space. Studying the entire program or tracing the execution line-by-line is generally not feasible. Some suggestions for how to narrow down your focus :
- Start where your intuition believes is the likely culprit, such as a function that recently changed or one you find suspicious.
- Use binary search to dissect. Set a breakpoint at the midpoint and poke around to determine whether the program state is already corrupt (which indicates the problem is in the front half) or looks good (so you need to focus your attention on the back half). Repeat to further narrow down.
- Run under Valgrind to identify the root cause of any lurking memory errors.
- Run under GDB to identify the root cause of any crashes
-
Analyze. With only a small amount of code under scrutiny, execution tracing becomes feasible. Use
gdb
to see what the facts (values of variables and flow of control) are telling you. Drawing pictures may help. - Devise and run experiments. Make inferences about the root cause and run experiments to validate your hypothesis. Iterate until you identify the root cause.
- Modify code to squash bug. The fix should be validated by your experiments and passing the original failed test case. You should be able explain the series of facts, tests, and deductions which match the observed symptom to the root cause and the corrected code.
Do not change your code haphazardly. This is like a scientist who changes more than one variable at a time. It makes the observed behavior much more difficult to interpret, and tends to introduce new bugs. That said, if you find buggy code, even if it is not obviously related to the bug you are tracking, you still might want to make a detour to fix it, using a reproducible input to trigger that bug and validate its fix. That bug might be related to or obscuring the original bug and it's good to remove any source of potential interference.
gdb
Commands Reference
Here is a full list of the commands you'll want to be familiar with. For even more, check out this gdb
Quick Reference document.
Command | Abbreviation | Description |
---|---|---|
help [command] |
h [command] |
Provides help (information) about a particular command or keyword. |
apropos [command] |
appr [command] |
Same as help |
info [cmd] |
i [cmd] |
Provides information about your program, such as the breakpoints (info breakpoints , local variables (info locals ), parameters (info args ), breakpoint numbers (info break ), etc. |
run [args] |
r [args] |
The run command runs your program. You can enter command line arguments after the command, if your program requires them. If you are already running your program and you want to re-run it, you should answer y to the prompt that says, "The program being debugged has been started already. // Start it from the beginning? (y or n)" |
list |
l |
The list command lists your program code either from the beginning, or with a range around where you are currently stopped. |
next |
n |
The next command steps to the next program line and completely runs functions. This is important: if you have a function (even one you didn't write) and you use next , the function will run to completion, and will stop at the next line after the function (unless there is a breakpoint inside the function). |
step |
s |
The step command is similar to next , but it will step into functions. This means that it will attempt to go to the first line in a function if there is a function called on the current line. Importantly, it will also take you into functions you didn't write (such as printf ), which can be annoying (you should use next instead). If you do accidentally step into a function, you can use the finish command to finish the function immediately and go back to the next line after the function. |
continue |
c |
This will continue running the program until the next breakpoint or until the program ends. |
print [x] |
p [x] |
This very important command lets you see the value of a variable [x] when you are stopped in a program. If you see the error "No symbol xxxx in current context", or the error, "optimized out", you probably aren't in a place in the program where you can read the variable. |
break [x] |
b [x] |
This will put a breakpoint in the program at a specified function name or a particular line number. If you have multiple files, you should use file:lineNum when specifying the line number (e.g. source.c:57 ). |
clear [x] |
Removes the breakpoint at a specified line number or at the start of the specified function. | |
delete [x] |
Removes the breakpoint with the given number. If the number is omitted, deletes all breakpoints after confirming. | |
backtrace |
bt |
This will print a stack trace to let you know where in the program you are currently stopped. This is a useful command if your program has a segmentation fault: the backtrace command will tell you the last place in your program that had the problem (sometimes you need to look at the entire stack trace to go back to the last line your program tried to execute). |
up and down |
These commands allow you to go up and down the stack trace. For instance, if you are inside a function and want to see the status of variables from the calling function, you can use up to place the program into the calling function, and then use p variable to look at the state of the program in that function. You would then use down to go back to the function that was called. |
|
finish |
Runs a function until it completes, which is helpful if you accidentally step into a function. | |
disassemble |
disas |
Disassembles your program into assembly language. We will be discussing this in depth in cs107. |
ctrl-x, ctrl-a |
Go into or leave "TUI" mode: gdb has a mode that shows you source code, or assembly output in a manner that allows you to scroll up and down. It is useful but sometimes can be a bit buggy. You will want to use the ctrl-l command to refresh the display. |
|
quit |
q |
Quit gdb |
Example gdb
Session
Below, we've included a program and a sample usage of GDB - lines that start with (gdb) # some text
are comments. Also see this video for a walkthrough demonstration of gdb
.
We will be looking at this simple program below:
#include<stdlib.h>
#include<stdio.h>
int square(int x);
int main(int argc, char *argv[]) {
printf("This program will square an integer.\n");
// the program should have one number as an argument
if (argc != 2) {
printf("Usage:\n\t./square number\n");
return 0;
}
// the first argument after the filename
int numToSquare = atoi(argv[1]);
int squaredNum = square(numToSquare);
printf("%d squared is %d\n",numToSquare,squaredNum);
return 0;
}
int square(int x) {
int sq = x * x;
return sq;
}
The sample GDB run output can be found here.
Viewing assembly code with tui
Post-midterm, you will be viewing assembly code, often in combination with C code. The tui
(text user interface) splits your session into panes for simultaneously viewing the C source, assembly translation, and/or current register state.
Opening, refreshing, and closing tui
Tui mode is great for tracing execution and observing what is happening with code/registers as you stepi
. Occasionally, tui trips over itself and garbles the display. The gdb command refresh
sometimes works to clean it up, or you can try ctrl-l
. If things get really out of hand, ctrl-x a
will exit tui mode and return you to ordinary non-graphical gdb.
Command | Description |
---|---|
layout split |
Starts tui mode with C file, assembly file, and gdb |
refresh , ctrl-l |
Refreshes the tui display. |
ctrl-x a |
Exits tui mode and return to ordinary non-graphical gdb. |
Advanced use of tui
You may want to customize your tui
layout---say, to only view the assembly code, or to view the assembly and register state simultaneously.
In addition, when you open tui
with layout split
, by default your arrow keys map to the C source file, meaning that you can't use arrow keys as normal to navigate your gdb commands.
Command | Description |
---|---|
focus cmd |
Changes active window to gdb command window to allow use arrow keys. |
layout <argument> |
Specifies which pane you want (src , asm , regs , split , or next ). |
focus <argument> |
Uses same arguments as layout to change active window. |
winheight <name> +<count> |
Change the height of the window name by count lines. To reduce height by count lines, supply argument -<count> . |
ctrl-x 1 |
Opens one window, default is src (C file). |
Suppose you want to view assembly, registers, and your gdb command window at the same time, and you want your arrow keys to work in gdb:
(gdb) layout regs
(gdb) focus cmd
More Resources
If you're interested in even more information about gdb
, check out the following resources:
- This CS107 gdb Reference Card
- Section 3 of this Stanford Unix Programming Tools document
- The full gdb manual (from GNU)
- This extensive gdb guide
- Two gdb articles written by Julie Zelenski, another Stanford Lecturer, for a programming journal: Breakpoint Tricks and GDB's Greatest Hits
Frequently-Asked Questions
When I run my program under gdb, it complains about missing symbols. What is wrong?
gdb myprogram
Reading symbols from myprogram...(no debugging symbols found)...done.
(gdb)
This means the program was not compiled with debugging information. Without this information, gdb won't have full symbols for setting breakpoints or printing values or other program tracing features. There's a flag you need to pass to gcc to build debugging info into the executable. If you're using raw gcc, add the -g flag to your command. If you're using a Makefile, make sure the CFLAGS line includes the -g flag.
When I view my code from within in gdb, it warns that the source file is more recent. What does this mean?
(gdb) list
warning: Source file is more recent than executable.
This means that you have edited one or more of your .c source files, but have not recompiled those changes. The program being executed will not match the edited source code and gdb's efforts to try to match up the two will be hopelessly confused. You can quit out of gdb, make, and then restart gdb, or even more conveniently, make
from with gdb will rebuild the program and reload it at next run, all without leaving gdb.
I step into a library function and gdb complains about a missing file. What is this and what should I do about it?
(gdb) s
_IO_new_fopen (filename=0xffffdc9f "samples/input", mode=0x804939a "r") at iofopen.c:102
102 iofopen.c: No such file or directory.
The step
command usually executes the next single line of C code. If that line makes a function call, step will advance into that function and allow you trace inside the call. However, if the function is a library function, gdb will not be able to display/trace inside it because the necessary source files are not available on the system. Thus, if asked to step into a library call, gdb responds with this harmless complaint about "No such file". At that point, you can use finish
to continue through the current function. As alternative to step
, the next
command will execute the entirety of the next line, completing all function calls rather than attempting to step into them.
My program crashes within a library function. It's not my fault the library is broken! What can I do?
Program received signal SIGSEGV, Segmentation fault.
__strcmp_ssse3 () at ../sysdeps/i386/i686/multiarch/strcmp-ssse3.S:232
232 ../sysdeps/i386/i686/multiarch/strcmp-ssse3.S: No such file or directory.
The example crash shown above is occurring during a call to strcmp
, a function from the standard library. The arguments to strcmp
are expected to be valid char*
s. If given an invalid address, the function will crash trying to read from that location. The library function does not have a bug, the mistake is that you are passing an invalid argument; look at your call to strcmp
to resolve your bug. The complaint about missing files (discussed above) is a harmless warning you can safety ignore. On the other hand, the bug in your use of the library function needs to be investigated further! :-)
When I start gdb, it gives a long warning about auto-loading being declined. What's wrong?
Reading symbols from bomb...(no debugging symbols found)...done.
warning: File "/afs/ir.stanford.edu/users/z/e/zelenski/a5/.gdbinit" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
add-auto-load-safe-path /afs/ir.stanford.edu/users/z/e/zelenski/a5/.gdbinit
line to your configuration file "/afs/ir/users/z/e/zelenski/.gdbinit".
... blah blah blah ...
A .gdbinit file is used to set a startup sequence for gdb. However, for security reasons, loading from a local .gdbinit is disabled by default. If there is a .gdbinit in the current directory and you have not configured gdb to allow loading it, gdb complains to alert you that this .gdbinit file is being ignored. To enable loading, you must edit your personal gdb configuration file to change your auto-load setting. Your personal gdb configuration is ~/.gdbinit
(a hidden file in your home directory). A .gdbinit file is a plain text file you can edit with your favorite editor. If file doesn't yet exist, you will need to create it. The line you need to add is set auto-load safe-path /
. Alternatively, you can copy and paste the command below to append the proper setting to your personal configuration file, creating the file if it doesn't already exists. You will need to make this configuration change only once.
bash -c 'echo set auto-load safe-path / >> ~/.gdbinit'
You can check your current configuration by searching your personal configuration file for the setting. See command below and expected response:
myth> grep auto-load ~/.gdbinit
set auto-load safe-path /
Once your personal configuration is appropriately set, there will be no further complaints from gdb about declining auto-load and it will load any local .gdbinit file on start.
When my program finishes, gdb prints a message calling my program "inferior". What have I done to offend gdb?
[Inferior 1 (process 25178) exited normally]
or
[Inferior 1 (process 25609) exited with code 01]
Don't take it personally, gdb runs your program as a sub-process which it terms the "inferior". The message indicates the program has run to completion and exited in a controlled fashion-- there was no segmentation fault, abort, hang, or other catastrophic termination condition.
When I run gdb without a program, some things (like sizeof and typecasts) behave differently. What's happening?
myth$ gdb
(gdb) p sizeof(long)
$1 = 4
(gdb) p/x (long)-1
$2 = 0xffffffff
(gdb)
By default, gdb determines your CPU architecture based on the program you're debugging. If you don't specify a program when starting gdb, it defaults to assuming a 32-bit system, rather than the 64-bit system that all of the programs we write will use. Starting gdb on one of your CS107 programs (any one will do) will cause gdb to use the proper architecture. You can also manually put gdb into 64-bit mode with the following command:
set architecture i386:x86-64
After that, the above commands should now print correctly:
myth$ gdb
(gdb) set architecture i386:x86-64
The target architecture is assumed to be i386:x86-64
(gdb) p sizeof(long)
$1 = 8
(gdb) p/x (long)-1
$2 = 0xffffffffffffffff
(gdb)