Article :: Getting Started with GDB

Introduction

This comprehensive guide covers everything you need to start debugging C programs with GDB, from compiling with debug symbols to inspecting memory and controlling program execution.

Overview

GDB (GNU Debugger) is the standard debugger for programs compiled with GCC. Unlike tools like strace that show system calls, GDB lets you step through your source code line by line, inspect variables, set conditional breakpoints, and even modify program behavior during execution.

Whether you’re tracking down a segfault, understanding how code executes, or reverse-engineering a binary, GDB is an indispensable tool for any C/C++ developer or security researcher.

Prerequisites

Before starting, you should:

Compiling with Debug Symbols

To debug with GDB effectively, you need debug symbols in your binary. These symbols map machine code back to your source code (functions, variables, line numbers).

1
gcc -ggdb source.c -o prog_with_symbols

The -ggdb flag produces GDB-specific debug information with more details than the standard -g flag.

Managing Debug Symbols

Stripping Symbols from Binaries

For production deployments, remove debug symbols to reduce binary size:

Using strip (removes all symbols):

1
strip --strip-debug --strip-unneeded prog_not_stripped -o prog_stripped

Using objcopy (creates separate debug file):

1
objcopy --only-keep-debug binary_file debug_file

This approach keeps symbols in a separate file, allowing you to debug production issues without shipping large binaries.

Adding Debug Symbols Back

Option 1: Link debug file to binary

1
objcopy --add-gnu-debuglink=debug_file binary_file

GDB will automatically find and load debug_file when debugging binary_file.

Option 2: Load symbol file manually in GDB

1
(gdb) symbol-file debug_file

Analyzing Symbols with nm

The nm command lists symbols in object files and binaries, useful for understanding what functions and variables exist in a program.

Symbol case conventions:

Common Symbol Types

SymbolMeaning
TIn the Text Section (code)
DIn the Initialized Data Section
BIn the Uninitialized Data Section (BSS)
UUndefined (external reference)
AAbsolute symbol
NDebugging Symbol

Useful nm Commands

Search for a specific symbol:

1
nm -A Binary_File | grep function_name

Display symbols sorted by address:

1
nm -n Binary_File

Show only external (global) symbols:

1
nm -g Binary_File

Show all symbols including debugger-only symbols:

1
nm -a Binary_File

List only symbols in TEXT section (executable code):

1
nm -a Binary_File | grep ' T '  # Spaces around T are important

Decompiling with objdump

The objdump command disassembles binaries, showing the actual assembly code:

1
objdump -M intel -D a.out | grep -A20 main.:

This shows 20 lines of assembly starting from the main function using Intel syntax.


GDB Command Reference

Starting GDB

1
2
gdb ./program           # Load program
gdb --args ./program arg1 arg2   # Load with arguments

Switching Syntax (AT&T vs Intel)

By default, GDB uses AT&T assembly syntax. Intel syntax is often more readable:

1
(gdb) set disassembly-flavor intel

Add this to ~/.gdbinit to make it permanent.

Disassembling Code

1
2
3
4
(gdb) disassemble main              # Disassemble main function
(gdb) disassemble /r main           # Show raw opcodes too
(gdb) disassemble _start            # Disassemble _start
(gdb) disassemble 0x80484b0         # Disassemble specific address

Disassemble a range:

1
(gdb) disassemble main,+30          # 30 bytes from main

Two arguments (separated by comma) specify a range: start,end or start,+length.

Running the Program

1
2
3
(gdb) run                    # Run with no arguments
(gdb) run arg1 arg2          # Run with arguments
(gdb) start                  # Run and break at main (GEF enhancement)

Listing Source Code

Requires source file available in the same directory:

1
2
3
(gdb) list                   # Show lines near current position
(gdb) list 1                 # List from line 1
(gdb) list main              # List the main function

Inspecting Program State

Register information:

1
2
(gdb) info registers         # Show all registers
(gdb) info registers eax     # Show specific register

Function listing:

1
(gdb) info functions         # List all functions

Source files:

1
2
(gdb) info sources           # List all source files
(gdb) info source            # Show current source file

Variable information:

1
2
(gdb) info variables         # List global/static variables
(gdb) info scope Function_Name   # List local variables in function

Symbol table dump:

1
2
(gdb) maintenance print symbols              # Print to console
(gdb) maintenance print symbols output.txt   # Print to file

Working with Breakpoints

Set breakpoints:

1
2
3
(gdb) break main             # Break at function
(gdb) break 42               # Break at line 42
(gdb) break *0x080484b0      # Break at address

List breakpoints:

1
(gdb) info breakpoints       # Show all breakpoints

Enable/disable breakpoints:

1
2
(gdb) disable 1              # Disable breakpoint #1
(gdb) enable 1               # Enable breakpoint #1

Delete breakpoints:

1
2
(gdb) delete 1               # Delete breakpoint #1
(gdb) delete                 # Delete all breakpoints

Modifying Memory and Registers

GDB lets you modify program state during execution:

1
2
3
4
(gdb) set {char} 0xbffff7e6 = 'B'              # Write byte to memory
(gdb) set {int} (0xbffff7e6 + 1) = 66          # Write int to memory
(gdb) set var1 = 100                            # Modify variable
(gdb) set $eax = 10                             # Modify register

Opcodes can be patched too:

1
(gdb) set {char} 0x080484b0 = 0xb8              # Write opcode

Defining Macros

Create custom commands that run automatically:

1
2
3
4
5
(gdb) define hook-stop
    info registers
    x/8xw $esp
    disassemble $eip,+10
end

This runs after every step, showing registers, stack, and next instructions. Save to ~/.gdbinit to use in all sessions.

Working with Data

Print register values in different formats:

1
2
3
(gdb) print /d $eax          # Decimal
(gdb) print /t $eax          # Binary
(gdb) print /x $eax          # Hexadecimal

Example output:

(gdb) print /d $eax
$17 = 13
(gdb) print /t $eax
$18 = 1101
(gdb) print /x $eax
$19 = 0xd

Examine memory (x/nyz):

Format: x/[count][format][size] address

Examples:

1
2
3
(gdb) x/8xw $esp             # Show 8 words in hex from stack pointer
(gdb) x/s 0x08048000         # Show string at address
(gdb) x/20i $eip             # Show 20 instructions at instruction pointer

Convenience Variables

Create temporary variables for debugging:

1
2
3
4
(gdb) set $i = 10
(gdb) set $dyn = (char *)malloc(10)
(gdb) set $demo = "psylinux"
(gdb) set argv[1] = $demo                      # Modify program arguments

These variables exist only in the GDB session.

Calling Functions

You can call functions in the debugged program:

1
2
3
4
(gdb) info functions                            # List available functions
(gdb) call Function_1(args_list)               # Call custom function
(gdb) call strlen("psylinux")                  # Call standard library
(gdb) call strcpy($dyn, argv[1])              # Use convenience vars

Key Takeaways

Practice Exercises

Further Reading