Debugging complex software is as much about methodology as it is about tools. GDB (GNU Debugger) remains the gold standard for low-level inspection of C and C++ programs, offering precise control over execution, memory, and thread scheduling. This guide focuses on advanced, real-world techniques—especially for multi-threaded debugging, where problems are often nondeterministic and hard to reproduce.
🧰 Core Capabilities of GDB #
At its core, GDB enables four critical debugging actions:
- Environment Control – Define arguments, environment variables, and startup conditions.
- Strategic Pausing – Stop execution at specific code locations or under precise conditions.
- State Inspection – Examine variables, memory, registers, and call stacks.
- Live Modification – Change program state at runtime to validate hypotheses quickly.
Common Entry Points #
- Standard debugging:
gdb ./app - Post-crash analysis (core dump):
gdb ./app core - Attach to running process:
gdb attach <pid>
These modes allow GDB to cover the entire lifecycle of a program—from startup logic to post-mortem analysis.
🧪 Advanced Debugging Techniques #
Conditional Breakpoints #
Conditional breakpoints halt execution only when a condition is met, making them ideal for debugging loops or rare edge cases.
break process_data if count == 100
This avoids unnecessary stops and keeps debugging sessions efficient even in hot code paths.
Memory Inspection with x
#
The examine command allows direct inspection of raw memory:
x/<count><format><unit> <address>
Examples:
x/16xw ptr→ 16 words in hexadecimalx/32cb buffer→ 32 bytes as characters
This is especially useful for validating buffers, structs, and pointer arithmetic errors.
Disassembly and Call Stack Analysis #
disassemble /sDisplays assembly instructions alongside source code, useful for compiler-level issues or optimization artifacts.backtrace/btPrints the full call stack, revealing how execution reached the current point.
Together, these tools expose both high-level logic errors and low-level execution details.
🧵 Debugging Multi-Threaded Programs #
Multi-threaded bugs—race conditions, deadlocks, and livelocks—are notoriously difficult to diagnose due to shared state and nondeterministic scheduling.
Essential Thread Commands #
| Command | Purpose |
|---|---|
info threads |
List all threads and their GDB IDs |
thread <id> |
Switch focus to a specific thread |
thread apply all bt |
Show backtraces for all threads |
set scheduler-locking on |
Prevent other threads from running |
Deadlock Analysis Workflow #
When a program appears frozen:
- Interrupt execution:
Press
Ctrl+Cin GDB. - List threads:
info threads - Inspect all stacks:
thread apply all bt - Identify lock contention:
Look for threads blocked in
pthread_mutex_lockor similar synchronization primitives.
This pattern quickly reveals circular wait conditions and lock-order inversions.
Scheduler Locking: Isolating Thread Behavior #
By default, stepping through code allows all threads to run, which can obscure bugs.
set scheduler-locking on
With scheduler locking enabled, only the current thread executes. This is invaluable when verifying thread-local logic without interference from other threads.
📋 GDB Command Quick Reference #
| Action | Command |
|---|---|
| Set breakpoint | b file:line |
| Run program | run |
| Step into | step |
| Step over | next |
| Continue | continue |
| Print variable | print var |
| Auto-display | display var |
| Watch variable | watch var |
Mastering GDB is less about memorizing commands and more about developing a disciplined debugging approach. With conditional breakpoints, memory inspection, and thread-aware controls, GDB becomes a precision instrument—capable of dissecting even the most elusive multi-threaded bugs.