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

Hands-on gdb for C

Walk through a full debugging session on a buggy program.

Why gdb

printf debugging works, but gdb is faster for:

Setup (-g flag)

Always build with debug info first.
$ gcc -g -O0 main.c -o app # -g embeds line numbers & variable names $ gdb ./app (gdb)
Why -O0? With optimization, variables may vanish into registers and lines disappear. Step/print won't behave as you expect. Always -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 call as one 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 call chain, you 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 depth; switch between them with frame N and read the args.

Watchpoints (stop when a variable changes)

Perfect for "somehow this variable has 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: watching a local variable is released when its function returns. For globals or heap memory use watch *ptr / watch -location.

Debugging a segfault

When run from 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 with ulimit -c unlimited, then gdb ./app core after the program crashed.

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 (do not step in)
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
backtracebtStack 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

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 speed things up further.

Challenges

Challenge 1: Fix sum_to
Use gdb to find 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 from several functions. Use watch g to see each change in order.
Challenge 3: Inspect recursion
Compute fact(5) recursively, break when n==2, then walk 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 under gdb, and use bt plus print i to find the offending index.