Why Source-Level Debugging Isn't Enough
GDB's default behavior assumes source-level debugging: it steps by source line, shows local variables by name, and hides the underlying instructions. When you're debugging hand-written assembly, or chasing a bug that only manifests in a compiler's generated machine code, you need to switch GDB's mental model down a level — stepping by individual instructions rather than source lines, and reading raw register and memory state rather than named variables. The key command for this is layout asm (or layout regs for a combined register-and-code view), which switches GDB's text-mode UI into a disassembly view showing the actual instruction stream with the current instruction pointer highlighted.
Cricket analogy: It is like watching a match on a highlights package that shows only the boundaries and wickets (source-level debugging) versus watching the full ball-by-ball footage to see exactly how the bowler's wrist position changed before a crucial delivery (instruction-level debugging).
Stepping and Inspecting State
At the instruction level, stepi (si) and nexti (ni) replace step and next — si executes exactly one machine instruction, stepping into calls, while ni executes one instruction but steps over calls, running the entire called function before stopping again. info registers dumps the full general-purpose register set (RAX, RBX, RDI, RIP, RFLAGS, and so on) in one shot, while print $rax or p/x $rax prints a single register, and x/4xg $rsp examines memory at RSP as four hexadecimal giant (64-bit) words — the x command's format letters (x for hex, d for decimal, i for disassembled instruction) combined with size letters (b byte, h halfword, w word, g giant) give precise control over how raw memory is displayed.
Cricket analogy: It is like a slow-motion replay system where 'stepi' rewatches a single delivery in full, including any DRS review that follows it, while 'nexti' skips straight past the DRS review process to the next ball, only showing the final outcome.
Breakpoints, Watchpoints, and ASLR
break *0x401234 sets a breakpoint at a raw address rather than a source line, which is essential in stripped or hand-written binaries with no symbol table; watch *(int*)0x404050 sets a hardware watchpoint that halts execution the instant that memory location's value changes, invaluable for catching exactly which instruction corrupts a variable when the culprit isn't obvious from reading the code. Because modern Linux binaries are typically compiled as position-independent executables and loaded at a randomized base address (ASLR), raw addresses from a previous run, a disassembly listing, or objdump output will not match the addresses GDB sees at runtime unless you either disable ASLR for the debug session (set disable-randomization on, GDB's default) or compute offsets relative to the load base using info proc mappings.
Cricket analogy: It is like setting a fixed camera to trigger recording the instant the ball crosses a specific stump-height threshold (a watchpoint), rather than the vaguer instruction 'watch for something unusual happening near the stumps.'
$ gdb ./a.out
(gdb) layout asm
(gdb) break *0x401136
(gdb) run
Breakpoint 1, 0x0000000000401136 in _start ()
(gdb) info registers rax rdi rsi rdx
rax 0x1 1
rdi 0x1 1
rsi 0x404020 4212768
rdx 0x16 22
(gdb) x/4xg $rsp
0x7fffffffe420: 0x0000000000000000 0x00007fffffffe530
0x7fffffffe430: 0x0000000000000001 0x0000000000401136
(gdb) watch *(long*)0x404020
Hardware watchpoint 2: *(long*)0x404020
(gdb) stepi
0x0000000000401140 in _start ()
(gdb) x/5i $rip
=> 0x401140 <_start+10>: mov edx,0x16
0x401145 <_start+15>: syscall
0x401147 <_start+17>: mov eax,0x3c
0x40114c <_start+22>: xor edi,edi
0x40114e <_start+24>: syscallGDB's TUI layout regs (rather than plain layout asm) shows both the register bank and the disassembly simultaneously, with registers that changed since the last step highlighted, which is often faster for spotting exactly what a single instruction did than switching back and forth between 'info registers' and the code view.
By default, GDB disables ASLR for the process it launches (set disable-randomization on), but if you instead attach to an already-running process with gdb -p <pid>, that process was almost certainly loaded with ASLR active. Addresses you see with info proc mappings in that session are only valid for that specific run and will differ the next time the program is launched.
- layout asm and layout regs switch GDB's TUI to instruction-level views necessary for debugging hand-written or stripped assembly.
- stepi (si) steps a single instruction into calls; nexti (ni) steps over calls, running the whole callee before stopping.
- info registers shows the full register set; print/p and x with format/size letters (x, d, i / b, h, w, g) inspect specific values.
- break *ADDRESS sets a raw-address breakpoint, essential when no symbol table or source line is available.
- watch sets a hardware watchpoint that halts the instant a specific memory location's value changes.
- ASLR randomizes load addresses in position-independent executables; GDB disables it by default for launched processes but not for attached ones.
- info proc mappings shows the actual runtime load base, needed to translate static disassembly addresses to live addresses under ASLR.
Practice what you learned
1. Which GDB command switches the TUI into an instruction-level disassembly view?
2. What is the difference between stepi and nexti?
3. What does the command x/4xg $rsp do?
4. Why is break *0x401234 useful when debugging a stripped binary?
5. By default, what does GDB do about ASLR when it launches a process directly?
Was this page helpful?
You May Also Like
Interrupts Explained
How hardware interrupts, software interrupts, and CPU exceptions divert execution to handler routines, and how the IDT ties it all together.
System Calls Explained
How assembly programs ask the operating system kernel to perform privileged work like reading files, allocating memory, and exiting a process.
Interfacing Assembly with C
How to call C functions from hand-written assembly and expose assembly routines to C, following the System V AMD64 calling convention.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics