How GDB Inspects Memory, Stack Frames, and Registers
The GNU Debugger (GDB) is an indispensable tool for debugging C programs, providing deep visibility into a program's runtime environment at the lowest levels. This article explains how GDB accesses and examines raw memory addresses, analyzes the call stack and its associated frames, and monitors hardware CPU registers. By leveraging operating system debugging interfaces and debug symbols, GDB exposes execution states that are critical for diagnosing segmentation faults, memory corruption, and logic errors.
Inspecting Memory Addresses
GDB inspects the memory space of a running C process by interfacing
directly with the operating system’s kernel, typically via system calls
like ptrace on Linux or by reading from
/proc/<pid>/mem. Because a C program operates in
virtual memory, GDB reads the bytes at a specified virtual address and
formats them according to user-defined specifications.
The primary command for viewing raw memory is examine,
abbreviated as x. The syntax follows the structure
x/[Count][Format][UnitSize] [Address]:
- Format specifiers: Format output as hexadecimal
(
x), decimal (d), unsigned decimal (u), instruction/disassembly (i), character (c), or string (s). - Unit sizes: Specify bytes (
b), halfwords/2 bytes (h), words/4 bytes (w), or giant words/8 bytes (g).
For example, to view 4 consecutive 32-bit words in hexadecimal
starting at a pointer address ptr:
(gdb) x/4xw ptr
GDB can also dereference pointers and evaluate expressions directly
using the print command (e.g., print *ptr or
print buffer[0]). If debugging symbols compiled via the
-g flag are present, GDB interprets data types according to
the source definitions; without them, it relies purely on specified raw
addresses and formats.
Inspecting Stack Frames
Each time a C function call occurs, an activation record known as a
stack frame is pushed onto the call stack. A stack frame stores the
return address, incoming function arguments, local variables, and the
saved frame pointer (such as EBP on x86 or RBP
on x86_64). GDB reconstructs this call sequence by interpreting the
target architecture's Application Binary Interface (ABI) calling
conventions and reading DWARF unwind tables embedded during
compilation.
Key commands used to navigate and inspect stack frames include:
backtrace(orbt): Prints the entire execution chain from the currently active function back tomain(). Each frame is assigned a numerical index, starting with frame0for the current execution point.frame <number>(orf <number>): Shifts GDB's operational context to a specific frame. This changes the scope so that variable lookups apply to that specific function call.info frame: Displays metadata regarding the selected frame, including its memory address, the addresses of the caller and callee frames, the current instruction pointer, and the saved registers.info localsandinfo args: Dumps all local variables and function parameters associated with the current frame, resolving variable names and types via the symbol table.
Inspecting Register States
Registers represent the highest-speed storage locations residing
directly inside the CPU. They hold critical execution state, such as the
current instruction pointer (RIP/EIP), the
stack pointer (RSP/ESP), base pointers,
function arguments, and CPU condition flags.
When a program stops under GDB, the operating system saves the target
process's CPU context. GDB accesses these snapshots using system
requests (such as PTRACE_GETREGSET) and maps the raw data
to the processor's register architecture.
Common commands for checking register states include:
info registers(orinfo reg): Outputs the values of all general-purpose registers in both hexadecimal and decimal representations.info all-registers: Extends the output to include floating-point, SIMD (AVX/SSE), and system control registers.print $<register_name>: Prints the value of a specific register using GDB's pseudo-variable syntax (e.g.,print $raxorprint/x $rsp).
By modifying register queries, developers can check flags (such as the Zero or Carry flags) to evaluate conditional branch behavior, examine the program counter to see the next instruction scheduled for execution, or manually track assembly-level argument passing as dictated by the system's ABI.