GDB Cheatsheet
Starting and Attaching
Use this GDB reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Build So the Debugger Can See
-g is what puts DWARF debug info in the binary. Without it you get addresses and
no variable names. Optimization is the other half of the problem.
gcc -g -Og -fno-omit-frame-pointer -o prog prog.c g++ -g -O0 -std=c++20 -o prog prog.cpp # maximum detail for macros and inline expansion gcc -g3 -O0 -o prog prog.c
| Flag | Why |
|---|---|
-g | DWARF debug info, the minimum requirement |
-g3 | also records #define macros so p MACRO works |
-Og | optimize but keep the debugger usable, best middle ground |
-O0 | nothing folded or reordered, every variable is live |
-fno-omit-frame-pointer | keeps backtraces walkable through library frames |
-fno-inline | stops small functions vanishing from the stack |
-static-libstdc++ | avoids symbol mismatch when shipping a C++ binary |
At -O2 the compiler is allowed to keep a variable only in a register that is
later reused, so gdb honestly reports <optimized out>. That is not a gdb bug and
no gdb setting fixes it. Rebuild at -Og or -O0 for the file you care about.
(gdb) p total
$1 = <optimized out>Launching
gdb ./prog loads symbols but runs nothing. run starts the process, start
sets a temporary breakpoint on main first, starti stops at the very first
instruction before the dynamic loader has done its work.
gdb ./prog # load, then type run gdb --args ./prog -v input.txt # argv for the inferior, not for gdb gdb -q ./prog # skip the license banner gdb -tui ./prog # open straight into the TUI gdb ./prog core.1234 # post-mortem, see Core Dumps gdb -p 1234 # attach to a live pid
| Option | Short | Effect |
|---|---|---|
--args prog a b | — | everything after the program name is its argv |
--command file | -x file | run gdb commands from a script file |
--eval-command cmd | -ex cmd | run one command, repeatable, in order |
--init-eval-command | -iex | run before loading the inferior and .gdbinit |
--batch | — | run the commands, then exit, no prompt |
--nx | -n | ignore every .gdbinit |
--pid n | -p n | attach to a running process |
--core file | -c file | load a core dump |
--symbols file | -s file | read symbols from a separate file |
--directory dir | -d dir | add a source search directory |
Inside the session you can still change argv and the working directory.
(gdb) set args -v input.txt (gdb) show args (gdb) run < in.txt > out.txt # shell redirection works in run (gdb) cd /srv/app (gdb) set cwd /srv/app # cwd for the inferior only
Environment and ASLR
The inferior inherits gdb's environment, and gdb defaults to disabling address randomization so addresses repeat between runs.
(gdb) set environment LD_LIBRARY_PATH=/opt/lib (gdb) unset environment LANG (gdb) show environment PATH (gdb) set disable-randomization off # reproduce a heisenbug that needs ASLR (gdb) show disable-randomization (gdb) set startup-with-shell off # skip /bin/sh, needed in some containers (gdb) set follow-exec-mode new
set exec-wrapper is how you debug a program that must be launched through a
helper, for example under a specific environment or a setarch shim.
(gdb) set exec-wrapper env LD_PRELOAD=./fake.so (gdb) run
Attaching to a Running Process
gdb -p $(pgrep -x myd)
(gdb) attach 1234 # from inside a session (gdb) info inferiors (gdb) detach # let it keep running (gdb) kill # stop it instead
Attaching stops every thread immediately. Anything holding a lock stays locked
while you sit at the prompt, so attaching to one process in a cluster can stall
its peers. On most distros ptrace_scope blocks attaching to a process you did
not start.
cat /proc/sys/kernel/yama/ptrace_scope # 1 means restricted sudo sysctl -w kernel.yama.ptrace_scope=0 sudo gdb -p 1234 # or just use root
If gdb warns the executable does not match the process, the binary on disk was rebuilt or replaced under a running daemon. Point it at the real image.
(gdb) set exec-file-mismatch warn (gdb) file /proc/1234/exe
Symbols and Sources
Distro packages strip symbols into a separate debuginfo file. gdb finds them
through the build ID under /usr/lib/debug, or you fetch them on demand.
sudo apt install libc6-dbg # Debian and Ubuntu sudo dnf debuginfo-install glibc # Fedora and RHEL export DEBUGINFOD_URLS="https://debuginfod.elfutils.org/"
(gdb) info sharedlibrary (gdb) info functions ^parse_ (gdb) info source (gdb) show directories (gdb) directory /home/me/src/lib # sources moved since the build (gdb) set substitute-path /build/tmp /home/me/src
set substitute-path is the fix when the binary was built in a container or CI
directory that does not exist on your machine. info source tells you which path
gdb is actually looking for.