Debugging with GDB
GDB (GNU Debugger) lets you pause a running program, inspect its state, and step through code line by line. It’s the primary tool for diagnosing segfaults, logic errors, and memory corruption in C programs.
Why It Matters
printf debugging doesn’t scale. When a program segfaults inside a library call, or corrupts memory that only crashes later, you need to inspect the actual state — registers, stack frames, memory contents. GDB gives you x-ray vision into a running process.
Setup
gcc -g -O0 program.c -o program # -g = debug symbols, -O0 = no optimization
gdb ./program # launchAlways compile with -g. Optimization (-O2) can reorder code and eliminate variables — use -O0 for debugging.
Essential Commands
| Command | Short | What It Does |
|---|---|---|
break main | b main | Breakpoint at function entry |
break file.c:42 | b file.c:42 | Breakpoint at specific line |
run | r | Start (or restart) program |
run arg1 arg2 | Start with arguments | |
next | n | Step over (execute line, don’t enter functions) |
step | s | Step into function call |
finish | fin | Run until current function returns |
continue | c | Resume execution until next breakpoint |
print x | p x | Print variable value |
print *ptr | Dereference and print | |
print arr[0]@10 | Print 10 elements starting at arr[0] | |
backtrace | bt | Show call stack (most useful after crash) |
info locals | Show all local variables | |
list | l | Show source code around current line |
quit | q | Exit GDB |
Watchpoints
Break when a variable changes, not at a specific line:
(gdb) watch counter # break when counter changes
(gdb) watch *0x7ffff000 # break when memory at address changes
(gdb) rwatch buffer[0] # break on READ of buffer[0]
Useful for catching “who modified this variable?” bugs.
Conditional Breakpoints
(gdb) break process_item if i == 99 # only stop on 100th iteration
(gdb) break parser.c:50 if len > 1024 # stop when buffer looks suspicious
Examining Memory
The x command (examine) reads raw memory:
(gdb) x/4xw &variable # 4 words in hex
(gdb) x/16xb ptr # 16 bytes in hex
(gdb) x/s string_ptr # as null-terminated string
(gdb) x/10i $pc # 10 instructions at program counter
Format: x/<count><format><size> where format is x(hex), d(decimal), s(string), i(instruction) and size is b(byte), h(halfword), w(word), g(giant/8-byte).
Debugging a Crash
Typical workflow when a program segfaults:
$ gdb ./program
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x00401234 in parse_line (line=0x0) at parser.c:47
(gdb) bt
#0 parse_line (line=0x0) at parser.c:47 ← NULL pointer!
#1 process_file (path=0x7fff...) at main.c:23
#2 main (argc=2, argv=0x7fff...) at main.c:8
(gdb) frame 1 # switch to caller's frame
(gdb) print path # inspect what was passed
(gdb) info locals # see all locals in this frame
Core Dumps
Debug a crash after the fact:
ulimit -c unlimited # enable core dumps
./program # crashes, produces core file
gdb ./program core # load core dump
(gdb) bt # see where it crashedTUI Mode
(gdb) tui enable # split-screen: source + command
(gdb) layout split # source + assembly
# Ctrl-x a to toggle TUI on/off
Related
- C Language Essentials — compile with
-g -O0for debugging - Pointers and Memory — most crashes are pointer bugs
- Makefiles and Build Systems — add debug build target
- System Calls — combine with
stracefor syscall-level debugging