EmbeddedRelated.com

GDB

Category: Tools

GDB (GNU Debugger) is an open-source, command-line debugger that lets you inspect and control the execution of a program, including setting breakpoints, stepping through code, examining memory, and reading CPU registers. In embedded development it typically runs on a host machine and connects to a target device through a debug probe using protocols such as JTAG or SWD.

In practice

GDB is used in embedded work through a client-server model: a GDB server (such as OpenOCD, J-Link GDB Server, or pyOCD) communicates with the hardware debug probe, and the GDB client on the host connects to that server over a local TCP socket or serial/USB-CDC connection. You launch the client with your ELF binary so GDB can resolve addresses to source lines and symbol names, then issue `target remote` or `target extended-remote` to attach to the running target.

Common debug tasks include setting breakpoints on functions or source lines (`break`), single-stepping at the source or instruction level (`step`, `next`, `stepi`, `nexti`), inspecting variables and memory (`print`, `x`), and examining the call stack (`backtrace`). The `info registers` command gives a snapshot of the core CPU registers (use `info all-registers` to include banked or floating-point registers on targets that have them), which is essential when diagnosing hard faults or unexpected resets. The blog post "Examining The Stack For Fun And Profit" illustrates how stack inspection through GDB can reveal the root cause of crashes.

A practical pitfall is that compiler optimizations (O2, Os) can make source-level stepping misleading: variables get optimized away, lines execute out of order, and inline functions disappear. Build a debug configuration with `-Og` or `-O0` and full debug info (`-g3`) to get reliable stepping. Remember to keep the ELF on the host in sync with the binary flashed to the target; a mismatch causes GDB to display wrong source lines and incorrect variable values.

GDB scripting with Python or plain GDB command files (`.gdbinit`) can automate repetitive tasks like loading firmware, setting a standard set of breakpoints, or pretty-printing custom data structures. The blog post "Getting Started With Zephyr: Using GDB To Fix a Driver Bug" walks through a realistic workflow where scripting and structured inspection combine to locate a subtle driver defect.

Discussed on EmbeddedRelated

Frequently asked

What is the difference between GDB and a GDB server?
GDB (the client) runs on your host PC, parses debug symbols, and provides the user interface. A GDB server (OpenOCD, J-Link GDB Server, pyOCD, etc.) handles the low-level communication with the debug probe and through it the target MCU; it typically runs on the host as well, but in some cases it runs directly on the probe itself (for example, the Black Magic Probe exposes GDB RSP from the probe). The two talk to each other using the GDB Remote Serial Protocol (RSP), most commonly over a TCP socket but also over serial or USB-CDC connections.
Can I use GDB with any microcontroller?
GDB supports a wide range of CPU architectures (ARM, RISC-V, AVR, MIPS, Xtensa, etc.), so in practice you can use it with most MCU families as long as there is a compatible GDB server and debug probe for your target. You need an architecture-specific build of GDB, such as arm-none-eabi-gdb for Cortex-M targets.
My variable always shows as 'optimized out'. What should I do?
The compiler has eliminated the variable or kept it only in a register that GDB cannot track at that particular program point. Rebuild with a lower optimization level (-Og or -O0) and ensure debug info generation is enabled (-g or -g3). For production builds where you cannot change optimization, use memory-mapped logging or a dedicated tracing mechanism instead of live variable inspection.
How do I investigate a HardFault or crash with GDB?
If the target is halted at the fault handler, use `backtrace` to walk the stack and `info registers` to see the stacked PC and LR values. On Cortex-M targets, reading the fault status registers (CFSR, HFSR, MMFAR, BFAR) via `x` or `monitor` commands points to the fault cause. The blog post 'Examining The Stack For Fun And Profit' covers this process in detail.
Can GDB debug code running in real time without halting the CPU?
Standard GDB requires halting the CPU to read memory and registers, which is disruptive to real-time behavior. For non-intrusive tracing you need additional mechanisms: some probes support trace hardware (ETM/ITM on Cortex-M) that can stream data without halting, and software approaches like semihosting, RTT, or the technique in 'Delayed printf for real-time logging' can capture diagnostic output with minimal timing impact.

Differentiators vs similar concepts

GDB is sometimes confused with the GDB server (OpenOCD, J-Link GDB Server) it works alongside. GDB is the host-side debugger and user interface; the GDB server is a separate process that translates GDB Remote Serial Protocol commands into probe-specific signaling over JTAG or SWD. Both the client role and the server/probe role are required for embedded debugging, though they do not always run as two separate host processes: the server can run directly on the probe itself, or a vendor tool may integrate both roles.