Debugging Linux Systems with GDB
Why GDB Still Matters
In an era of sophisticated IDE debuggers and observability tools, GDB remains the most versatile and lowest-level debugger available on Linux. It works everywhere — remote targets, embedded systems, core dumps, kernel modules — with zero dependencies.
Knowing GDB well separates engineers who can reproduce and fix bugs quickly from those who rely entirely on printf.
Essential Commands
Most GDB sessions involve a handful of commands used repeatedly:
(gdb) break main # set breakpoint
(gdb) run # start the process
(gdb) next # step over
(gdb) step # step into
(gdb) finish # run until function returns
(gdb) print var # inspect a variable
(gdb) x/10xw 0xdeadbeef # examine memory
(gdb) backtrace # show call stack
(gdb) info registers # dump CPU registers
Attaching to a Running Process
You do not always have the luxury of starting a process under GDB. Attaching to a running PID is straightforward:
gdb -p $(pgrep myprocess)
Once attached, the process is paused. Set breakpoints, then continue.
Debugging with Core Dumps
When a program crashes in production, a core dump is your best post-mortem tool. Enable them with:
ulimit -c unlimited
Then load the core alongside the binary:
gdb ./myprogram core
(gdb) backtrace
The backtrace command immediately shows you where the crash happened and the full call stack at that moment.
Conditional Breakpoints
Unconditional breakpoints in hot loops are unusable. Conditional breakpoints let you stop only when a specific condition is true:
(gdb) break process_packet if packet->type == 0x05
This is invaluable when debugging race conditions or rare error paths.
Watchpoints
Watchpoints halt execution whenever a memory location is read or written — perfect for tracking down memory corruption:
(gdb) watch *ptr # break on write
(gdb) rwatch *ptr # break on read
(gdb) awatch *ptr # break on read or write
Hardware watchpoints (backed by debug registers) have no performance cost and can watch up to 4 locations simultaneously on x86.
Remote Debugging with gdbserver
For debugging across machines or in constrained environments:
# target machine
gdbserver :1234 ./myprogram
# host machine
gdb ./myprogram
(gdb) target remote 192.168.1.100:1234
This is the standard setup for debugging programs running inside virtual machines or embedded targets.