Debugging with GDB and LLDB
This chapter covers interactive debugging with GDB and LLDB: breakpoints, stepping, inspecting state, and watchpoints.
Why This Matters
Print statements only take you so far. A debugger lets you stop execution, inspect variables and memory, and step through code — the most direct way to understand and fix bugs.
Prerequisites
c.build.10— debug builds.
Core Concept
A debugger controls a running program: it can set breakpoints, step through code, inspect registers/memory, and watch variables for changes. It relies on debug info (-g, DWARF) to map machine code back to source.
GDB Commands (also work in LLDB with small differences)
| Command | Effect |
|---|---|
break file.c:line | set breakpoint |
break function | break at function entry |
run / r | start program |
next / n | step over |
step / s | step into |
continue / c | resume |
print expr / p | print value |
print/x expr | print in hex |
backtrace / bt | stack trace |
frame N | select frame |
info registers | show registers |
x/nfu addr | examine memory |
watch var | watchpoint (stop on change) |
list | show source |
LLDB uses similar commands (b, n, s, c, p, bt, fr, register read, memory read).
Examples
A GDB session
gcc -g -O0 main.c -o app
gdb ./app
(gdb) break main
(gdb) run
(gdb) next
(gdb) print x
(gdb) backtrace
(gdb) continue
Conditional breakpoint
(gdb) break main.c:20 if x == 5
How It Works
The debugger uses ptrace (Linux) or equivalent to control the process, reads DWARF info to map addresses to source, and reads/writes memory and registers when stopped. Breakpoints are implemented by patching the instruction with a trap, and watchpoints use hardware debug registers or single-stepping.
Variations
LLDB vs. GDB
LLDB's command language is similar but not identical. Both are scriptable (Python). Pick the one your toolchain targets.
Core-file analysis
You can also debug a crashed program post-mortem via a core dump (c.debug.2).
Common Mistakes
- Debugging an optimized build without realizing variables may be optimized
away.
- Forgetting
-g(no debug info). - Confusing
step(into) withnext(over).
Undefined Behavior
- None; debugging is tooling.
Portability
- GDB is common on Linux; LLDB on macOS and also Linux. The concepts are
portable; exact commands differ.
Under the Hood
The debugger reads DWARF sections (.debug_info, .debug_line) to map addresses to source lines and variables. It uses the symbol table for function names.
Practical Usage
- Build with
-g -O0for easy debugging. - Use
backtracefirst to locate a crash. - Use watchpoints for "what changed this variable" questions.
- Use conditional breakpoints to skip noisy iterations.
Exercises
1. Compile a program with -g and set a breakpoint at main; step through. 2. Print a struct and an array, and inspect memory with x. 3. Set a watchpoint on a variable and find the line that modifies it. 4. Use a conditional breakpoint inside a loop.
Deep Challenge
Debug a use-after-free with GDB: set a breakpoint after free, inspect the heap, and use a watchpoint or ASan to pinpoint the invalid access. Explain your process.
Related Concepts
c.debug.2— core dumps and DWARF.c.debug.5— debugging optimized builds.c.build.8— debug flags.
References
- GDB manual, LLDB documentation.
Verification
- Command semantics are documented tool behavior.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Breakpoints
- [ ] Stepping (next/step)
- [ ] Inspecting variables and memory
- [ ] Watchpoints
- [ ] Backtraces
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.debug.gdb | 0 | 6 |
| c.debug.lldb | 0 | 5 |