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)"]
endBreakdown 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
xcommand will repeat the lastxcommand, 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_varrather than trying to reconstruct it withx. However,xis invaluable for examining raw bytes or when the type information is corrupted or unavailable.