Back to Technology

x86 Assembly Series Part 14: Debugging with GDB & objdump

February 6, 2026 Wasil Zafar 30 min read

Master debugging assembly: GDB basics, setting breakpoints, stepping through code, examining registers and memory, objdump for disassembly, and advanced debugging workflows.

Table of Contents

  1. GDB Basics
  2. Breakpoints
  3. Stepping & Execution
  4. Examining Registers
  5. Examining Memory
  6. objdump Disassembly
  7. Debugging Workflow
  8. Advanced Tips

Breakpoints

# Set breakpoints
(gdb) break _start          # Break at label
(gdb) break *0x401000       # Break at address
(gdb) break main            # Break at function (if linked with C)

# Manage breakpoints
(gdb) info breakpoints      # List all breakpoints
(gdb) delete 1              # Delete breakpoint 1
(gdb) disable 2             # Disable breakpoint 2
(gdb) enable 2              # Re-enable breakpoint

Stepping & Execution

# Stepping commands
(gdb) stepi                 # Step one instruction (si)
(gdb) nexti                 # Step over call (ni)
(gdb) continue              # Continue to next breakpoint (c)
(gdb) finish                # Run until current function returns

# Show current instruction
(gdb) display/i $pc         # Auto-display at each step
(gdb) x/5i $pc              # Show next 5 instructions

Examining Registers

# View registers
(gdb) info registers        # All general-purpose registers
(gdb) info all-registers    # All registers including FP/SIMD
(gdb) print $rax            # Print specific register
(gdb) print/x $rax          # Print in hex

# Modify registers
(gdb) set $rax = 42
(gdb) set $rip = 0x401020   # Jump to address

Examining Memory

# x/ 
# Formats: x(hex), d(decimal), s(string), i(instruction) # Sizes: b(byte), h(halfword), w(word), g(giant/8-byte) (gdb) x/10xb $rsp # 10 bytes in hex at RSP (gdb) x/4xg $rsp # 4 quadwords (64-bit) at RSP (gdb) x/s 0x402000 # String at address (gdb) x/10i _start # 10 instructions at _start # Stack examination (gdb) x/16xg $rsp # View stack (16 quadwords)

objdump Disassembly

# Disassemble entire binary
objdump -d program

# Disassemble with Intel syntax
objdump -d -M intel program

# Show all sections
objdump -h program

# Show symbol table
objdump -t program

# Full disassembly with source interleaved
objdump -d -S program

Debugging Workflow

A typical assembly debugging session follows this pattern:

# 1. Assemble with debug symbols
nasm -f elf64 -g -F dwarf program.asm -o program.o

# 2. Link with debug info preserved
ld -o program program.o

# 3. Start GDB
gdb ./program

Step-by-Step Session

(gdb) layout asm              # Show assembly view
(gdb) layout regs             # Also show registers
(gdb) break _start            # Set breakpoint at entry
(gdb) run                     # Start execution

# Now at _start, examine state:
(gdb) info registers          # View all registers
(gdb) x/10i $rip              # Show next 10 instructions
(gdb) x/20xg $rsp             # Examine stack (20 qwords, hex)

# Step through code:
(gdb) stepi                   # Single instruction
(gdb) nexti                   # Step over (skip calls)
(gdb) continue                # Run to next breakpoint

# Examine memory:
(gdb) x/s 0x402000            # Print as string
(gdb) x/4xw &variable         # 4 dwords at variable address

# Modify values:
(gdb) set $rax = 42           # Change register
(gdb) set {int}0x404000 = 99  # Change memory

Debug Challenge

Find the bug in this code using GDB:

; Buggy: Should compute array sum
section .data
    arr dd 1, 2, 3, 4, 5
    len equ ($ - arr) / 4
section .text
global _start
_start:
    xor eax, eax      ; sum = 0
    mov ecx, len      ; counter
    lea rsi, [arr]
.loop:
    add eax, [rsi]    ; BUG: should use esi for index or advance rsi
    dec ecx
    jnz .loop
    ; Sum is wrong! Use GDB to find why.
Save & Compile: buggy_sum.asm

Linux (compile with debug symbols for GDB)

nasm -f elf64 -g -F dwarf buggy_sum.asm -o buggy_sum.o
ld buggy_sum.o -o buggy_sum
gdb ./buggy_sum

macOS (change _start_main, use lldb instead of gdb)

nasm -f macho64 -g buggy_sum.asm -o buggy_sum.o
ld -macos_version_min 10.13 -e _main -static buggy_sum.o -o buggy_sum
lldb ./buggy_sum

Windows (use WinDbg or Visual Studio debugger)

nasm -f win64 -g -F cv8 buggy_sum.asm -o buggy_sum.obj
link /DEBUG /subsystem:console /entry:_start buggy_sum.obj /out:buggy_sum.exe

Advanced Tips

TUI Mode (Text User Interface)

# Start GDB with TUI
gdb -tui ./program

# Or enable inside GDB:
(gdb) tui enable

# Cycle through layouts:
(gdb) layout next          # Cycle: src -> asm -> split -> regs
(gdb) layout asm           # Assembly view
(gdb) layout regs          # Registers + asm
(gdb) layout split         # Source + asm

# TUI key bindings:
# Ctrl+X, A    Toggle TUI mode
# Ctrl+X, 2   Two-window mode
# Ctrl+L      Refresh screen

Watchpoints

# Break when memory changes
(gdb) watch variable        # Break on write to 'variable'
(gdb) watch *0x404000       # Break on write to address
(gdb) rwatch *0x404000      # Break on read
(gdb) awatch *0x404000      # Break on read OR write

# Conditional watchpoint
(gdb) watch counter if counter > 100

# Hardware vs software watchpoints
(gdb) set can-use-hw-watchpoints 1  # Use hardware (faster, limited)
(gdb) info watchpoints              # List active watchpoints

GDB Scripting

# Create .gdbinit file for auto-commands:
echo "
set disassembly-flavor intel
layout asm
break _start
define hook-stop
  x/i \$rip
  info registers rax rbx rcx rdx
end
" > ~/.gdbinit

# Command file for specific program:
(gdb) source mycommands.gdb

# Example mycommands.gdb:
# break main_loop
# commands
#   printf "Loop iteration: rax=%d\\n", $rax
#   continue
# end
Pro Tips:
  • set pagination off - Disable "press enter to continue"
  • set logging on - Log session to gdb.txt
  • reverse-stepi - Step backwards (requires record mode)
  • catch syscall - Break on system calls