πŸ‡―πŸ‡΅ ζ—₯本θͺž | πŸ‡ΊπŸ‡Έ English

Hands-on gdb for C

Walk through a complete debugging session on a buggy program.

Why gdb

printf debugging works, but gdb is faster when you need to:

Setup (-g flag)

Always build with debug info enabled first.
$ gcc -g -O0 main.c -o app # -g embeds line numbers & variable names $ gdb ./app (gdb)
Why -O0? With optimizations on, variables can vanish into registers and source lines can disappear, so step and print won't behave as expected. Always use -O0 while debugging.

Sample buggy program

// sum.c: should compute 1+2+...+N (has a bug)
#include <stdio.h>

int sum_to(int n) {
    int s = 0;
    for (int i = 1; i < n; i++) {   // BUG: should be i <= n
        s += i;
    }
    return s;
}

int main(void) {
    int r = sum_to(10);
    printf("sum = %d\n", r);   // expected 55, got 45
    return 0;
}

Basic session: break / run / step / print

$ gcc -g sum.c -o sum $ gdb ./sum (gdb) break sum_to # breakpoint at function start Breakpoint 1 at 0x1149: file sum.c, line 5. (gdb) run # start program Breakpoint 1, sum_to (n=10) at sum.c:5 5 int s = 0; (gdb) next # advance one line (don't step into calls) 6 for (int i = 1; i < n; i++) { (gdb) print n $1 = 10 (gdb) print i No symbol "i" in current context. # not declared yet (gdb) next 7 s += i; (gdb) print i $2 = 1 # now i=1 (gdb) continue sum = 45 # wrong! expected 55

Conditional breakpoints

(gdb) break sum.c:7 if i == 5 (gdb) run Breakpoint 2, sum_to at sum.c:7 (gdb) print s $3 = 10 # 0+1+2+3+4 = 10 βœ“
step vs. next: step (s) descends into called functions; next (n) treats a function call as a single line.

Auto-print with display

(gdb) display i # print at every stop (gdb) display s (gdb) next 1: i = 3 2: s = 3

Stack trace (bt / frame)

When a bug happens deep inside a chain of calls, you'll want to know who called the current function.
(gdb) backtrace # or 'bt' #0 sum_to (n=10) at sum.c:7 #1 main () at sum.c:13 (gdb) frame 1 # jump to main's frame #1 main () at sum.c:13 13 int r = sum_to(10); (gdb) print r $4 = 0 # still 0, the call isn't finished (gdb) frame 0 # back to the inner frame
Debugging recursion: bt shows every level; hop between them with frame N and inspect each set of arguments.

Watchpoints (stop when a variable changes)

Perfect for "somehow this variable ended up with the wrong value" bugs.
(gdb) break main (gdb) run (gdb) watch r # watch writes to r Hardware watchpoint 2: r (gdb) continue Hardware watchpoint 2: r Old value = 0 New value = 45 main () at sum.c:13
Scope: A watchpoint on a local variable is released when its function returns. For globals or heap memory, use watch *ptr or watch -location.

Debugging a segfault

When run under gdb, a crash stops right at the offending instruction.
// crash.c
#include <stdio.h>
#include <string.h>

int main(void) {
    char *p = NULL;
    strcpy(p, "hello");    // write through NULL β†’ crash
    return 0;
}
$ gcc -g crash.c -o crash $ gdb ./crash (gdb) run Program received signal SIGSEGV, Segmentation fault. __strcpy_avx2 () at .. # inside libc (gdb) bt #0 __strcpy_avx2 () from /lib/libc.so.6 #1 0x... in main () at crash.c:6 # ← our code (gdb) frame 1 #1 main () at crash.c:6 6 strcpy(p, "hello"); (gdb) print p $1 = 0x0 # NULL, confirmed
Post-mortem with core dumps: Enable them with ulimit -c unlimited, then after the crash run gdb ./app core.

Cheat sheet

CommandShortMeaning
break fn / file:linebSet a breakpoint
info breakpointsi bList them
delete NdRemove Nth
run [args]rStart program
continuecResume until next break
nextnNext line (don't step into calls)
stepsNext line (step into calls)
finishfinRun until current function returns
print exprpShow a value (p *p, p a[5], p arr@10)
display exprdispAuto-print at every stop
backtracebtShow the stack trace
frame NfSwitch to frame N
watch expr-Stop when expr changes
listlShow 10 lines of source
set variable x=5set varModify a variable live
quitqExit gdb

TUI mode

$ gdb -tui ./app # source pane on top, prompt at bottom # toggle with Ctrl+X then A
IDEs: VS Code and CLion both drive gdb under the hood. Once you're comfortable on the command line, their GUIs make things even faster.

Challenges

Challenge 1: Fix sum_to
Use gdb to figure out why sum_to(10) returns 45 instead of 55, then fix it.
Challenge 2: Watchpoint practice
Write a program where a global int g = 0; is modified by several functions. Use watch g to observe each change in order.
Challenge 3: Inspect recursion
Compute fact(5) recursively, break when n==2, then walk through each frame with frame N and print n to see the stack.
Challenge 4: Locate a segfault
Write a small out-of-bounds array access, run it under gdb, and use bt and print i to find the offending index.

Review Quiz

Check your understanding of this lesson.

Q1. Which command starts program execution in gdb?

run
go
exec

run (or r) starts the program and runs it until the next breakpoint. You can pass arguments like run arg1 arg2.

Q2. Which command sets a breakpoint at the start of main?

break main
stop main
watch main

break (or b) stops at a specific location, while watch stops when a variable's value changes.

Q3. Which command shows the current value of variable x?

print x or p x
show x
get x

print evaluates an expression and displays the result. Arrays and structs are expanded so you can inspect their contents.