What does gdb 'x' command do?
Categories:
Demystifying GDB's 'x' Command: Examining Memory in C and Assembly

Explore the powerful 'x' command in GDB for inspecting memory contents, understanding data types, and debugging C and assembly code effectively.
When debugging C or assembly code with GDB (GNU Debugger), understanding the state of memory is crucial. The x
command, short for 'examine', is one of GDB's most versatile tools for peeking directly into memory. It allows you to view memory contents at a specified address, interpret them as various data types, and control the format and quantity of data displayed. This article will guide you through the syntax and common uses of the x
command, helping you master memory inspection during your debugging sessions.
Understanding the 'x' Command Syntax
The basic syntax of the x
command is x/nfu addr
, where each component plays a vital role in how GDB interprets and displays the memory. Let's break down each part:
flowchart TD A[x command] --> B{Count 'n'} B --> C{Format 'f'} C --> D{Size 'u'} D --> E[Address 'addr'] subgraph n [Count] n1["Number of units to display (default: 1)"] end subgraph f [Format] f1["x: hex, d: decimal, u: unsigned decimal, o: octal, t: binary, a: address, c: char, s: string, i: instruction"] end subgraph u [Unit Size] u1["b: byte, h: halfword (2 bytes), w: word (4 bytes), g: giant (8 bytes)"] end subgraph addr [Address] addr1["Memory address (variable name, register, expression)"] end
Breakdown of the GDB 'x' command syntax components.
n
(Count): This is an optional decimal integer specifying how many units of memory to display. If omitted, GDB defaults to 1.f
(Format): This single-letter code determines how GDB should interpret and print each unit of memory. Common formats include:x
: hexadecimal (default)d
: signed decimalu
: unsigned decimalo
: octalt
: binarya
: addressc
: characters
: null-terminated stringi
: machine instructions
u
(Unit Size): This single-letter code specifies the size of each unit of memory to be examined. Common sizes are:b
: byte (1 byte)h
: halfword (2 bytes)w
: word (4 bytes, default on most 32-bit systems)g
: giant (8 bytes, default on most 64-bit systems)
addr
(Address): This is the memory address you want to examine. It can be a variable name, a register (e.g.,$rsp
), a pointer, or any expression that evaluates to a memory address.
Practical Examples in C
Let's look at some common scenarios for using x
when debugging C code.
#include <stdio.h>
#include <stdlib.h>
int global_var = 0xDEADBEEF;
int main() {
int local_var = 12345;
char *str = "Hello GDB!";
int *dynamic_arr = (int *)malloc(3 * sizeof(int));
if (dynamic_arr == NULL) {
return 1;
}
dynamic_arr[0] = 100;
dynamic_arr[1] = 200;
dynamic_arr[2] = 300;
printf("Global: %x\n", global_var);
printf("Local: %d\n", local_var);
printf("String: %s\n", str);
printf("Dynamic[0]: %d\n", dynamic_arr[0]);
free(dynamic_arr);
return 0;
}
A simple C program to demonstrate GDB's 'x' command.
# Compile with debug info
gcc -g -o example example.c
# Start GDB
gdb ./example
# Set a breakpoint at main and run
(gdb) b main
(gdb) r
# Examine global_var as a hexadecimal word
(gdb) x/wx &global_var
0x601040 <global_var>:\t0xdeadbeef
# Examine local_var as a signed decimal word
(gdb) x/dw &local_var
0x7fffffffdecc: 12345
# Examine the string 'str' (null-terminated string)
(gdb) x/s str
0x400714: "Hello GDB!"
# Examine the first 3 words of dynamic_arr as decimal integers
(gdb) x/3dw dynamic_arr
0x602010: 100\t200\t300
# Examine memory at a specific address (e.g., 0x400714) as characters
(gdb) x/10cb 0x400714
0x400714: 72 'H'\t101 'e'\t108 'l'\t108 'l'\t111 'o'\t32 ' '\t71 'G'\t68 'D'\t66 'B'\t33 '!'
# Examine the address of local_var
(gdb) x/a &local_var
0x7fffffffdecc: 0x7fffffffdecc
GDB commands and output for examining memory in the C example.
x
without an address, it will examine memory starting from the address immediately following the last examined location. This is useful for stepping through memory.Inspecting Assembly Code and Registers
The x
command is equally powerful for low-level debugging, especially when working with assembly code or understanding how your C code translates to machine instructions.
# Continue from the previous GDB session
# Disassemble the main function to find its start address
(gdb) disas main
Dump of assembler code for function main:
0x00000000004005d6 <+0>: push %rbp
0x00000000004005d7 <+1>: mov %rsp,%rbp
...
# Examine 10 instructions starting from main
(gdb) x/10i main
0x4005d6 <main>: push %rbp
0x4005d7 <main+1>: mov %rsp,%rbp
0x4005da <main+4>: sub $0x30,%rsp
0x4005de <main+8>: mov $0x3039,%eax
0x4005e3 <main+13>: mov %eax,-0x4(%rbp)
0x4005e6 <main+16>: movabs $0x400714,%rax
0x4005f0 <main+26>: mov %rax,-0x10(%rbp)
0x4005f4 <main+30>: mov $0x18,%edi
0x4005f9 <main+35>: callq 0x4004a0 <malloc@plt>
0x4005fe <main+40>: mov %rax,-0x18(%rbp)
# Examine the value of the stack pointer register ($rsp) as a hexadecimal address
(gdb) x/a $rsp
0x7fffffffdeb0: 0x7fffffffdeb0
# Examine the memory pointed to by $rsp as 4 hexadecimal words
(gdb) x/4wx $rsp
0x7fffffffdeb0: 0x00000000\t0x00000000\t0x00000000\t0x00000000
Using 'x' to inspect assembly instructions and register contents.
x/i
), GDB automatically determines the instruction length and disassembles them. This is incredibly useful for understanding the execution flow at a machine code level.Common Pitfalls and Tips
While powerful, the x
command can sometimes be confusing if you're not careful with its parameters.
Here are some tips for effective use:
- Default Behavior: If you omit
n
,f
, oru
, GDB uses the last specified values for those parameters. This can be convenient but also lead to unexpected output if you forget the previous settings. - Address Expressions: GDB's address expressions are very flexible. You can use arithmetic (e.g.,
&my_array + 4
), dereference pointers (e.g.,*my_ptr
), or combine them with register values (e.g.,$rsp + 8
). - Repetition: Simply pressing Enter after an
x
command will repeat the lastx
command, advancing the memory address by the total size of the previously displayed units. This is excellent for scanning through large memory regions. - Type Casting: For complex C data structures, it's often easier to print the variable directly using
print (struct my_struct)my_var
rather than trying to reconstruct it withx
. However,x
is invaluable for examining raw bytes or when the type information is corrupted or unavailable.