Debug a Segfault with GDB
Goal: Take a program that crashes with a segmentation fault. Use GDB to find the exact bug — without adding a single printf.
Prerequisites: Debugging with GDB, Pointers and Memory, C Language Essentials
The Buggy Program
Save this as buggy.c. It has three bugs that cause crashes. Your job: find and fix them all using GDB.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
int score;
} Player;
Player *create_player(const char *name, int score) {
Player *p = malloc(sizeof(Player));
p->name = strdup(name);
p->score = score;
return p;
}
void free_player(Player *p) {
free(p->name);
free(p);
}
// Bug 1 is somewhere in this function
char *get_greeting(Player *p) {
char buf[64];
snprintf(buf, sizeof(buf), "Hello, %s! Score: %d", p->name, p->score);
return buf; // what's wrong here?
}
// Bug 2 is somewhere in this function
void print_top_players(Player **players, int count) {
for (int i = 0; i <= count; i++) { // careful with the condition
printf("#%d: %s (%d)\n", i + 1, players[i]->name, players[i]->score);
}
}
// Bug 3 is somewhere in this function
void use_after_free_demo(void) {
Player *p = create_player("ghost", 0);
free_player(p);
printf("Ghost score: %d\n", p->score); // hmm...
}
int main(void) {
Player *players[3];
players[0] = create_player("alice", 100);
players[1] = create_player("bob", 85);
players[2] = create_player("carol", 92);
// This will crash — use GDB to find out why
char *msg = get_greeting(players[0]);
printf("%s\n", msg);
print_top_players(players, 3);
use_after_free_demo();
for (int i = 0; i < 3; i++)
free_player(players[i]);
return 0;
}Step 1: Compile with Debug Symbols
gcc -g -O0 -fsanitize=address -o buggy buggy.c-g: include debug symbols (so GDB knows variable names, line numbers)-O0: disable optimization (so variables aren’t optimized away)-fsanitize=address: optional but catches memory bugs automatically
Step 2: Run in GDB
gdb ./buggy(gdb) run
The program crashes. GDB shows where:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff... in printf () from /lib/libc.so.6
Step 3: Backtrace — Where Did It Crash?
(gdb) backtrace
#0 printf () at ...
#1 main () at buggy.c:42
Frame #1 tells you: the crash is at buggy.c:42 — the printf("%s\n", msg) line.
(gdb) frame 1
(gdb) print msg
$1 = 0x7fffffffdc10 "\200\..." (garbage)
msg points to garbage! Look at get_greeting — it returns a pointer to a local buf[64] on the stack. That memory is invalid after the function returns.
Fix Bug 1
// Replace: return buf;
// With:
return strdup(buf); // allocate a copy on the heapNow the caller must free it. Add free(msg) after the printf.
Step 4: Run Again — Find Bug 2
After fixing bug 1, recompile and run again:
gcc -g -O0 -o buggy buggy.c
gdb ./buggy
(gdb) runNew crash in print_top_players. Use backtrace:
(gdb) bt
#0 print_top_players (players=..., count=3) at buggy.c:30
(gdb) print i
$1 = 3
(gdb) print count
$2 = 3
i == 3 but valid indices are 0, 1, 2. The loop uses i <= count instead of i < count — classic off-by-one.
Fix Bug 2
// Replace: for (int i = 0; i <= count; i++)
// With:
for (int i = 0; i < count; i++)Step 5: Find Bug 3 with AddressSanitizer
Bug 3 (use-after-free) may not crash reliably. Compile with ASan:
gcc -g -O0 -fsanitize=address -o buggy buggy.c
./buggyASan output:
==12345==ERROR: AddressSanitizer: heap-use-after-free
READ of size 4 at 0x602000000014
#0 use_after_free_demo buggy.c:37
Line 37: p->score is accessed after free_player(p). The memory has been freed — reading it is undefined behavior.
Fix Bug 3
void use_after_free_demo(void) {
Player *p = create_player("ghost", 0);
printf("Ghost score: %d\n", p->score); // print BEFORE free
free_player(p);
}GDB Cheat Sheet for Debugging
| Command | What | When |
|---|---|---|
run | Start the program | First thing |
bt (backtrace) | Show call stack | After a crash |
frame N | Switch to frame N | To inspect local variables |
print var | Print variable value | Any time when stopped |
break func | Set breakpoint | Before running |
next / step | Step over / into | Single-step through code |
watch var | Break when var changes | Finding “who modified this?” |
x/10xw ptr | Examine 10 words at ptr | Inspecting raw memory |
info locals | Show all local vars | Quick state dump |
Verify: Clean Run
After all three fixes:
gcc -g -O0 -fsanitize=address -o buggy buggy.c
./buggy
# Hello, alice! Score: 100
# #1: alice (100)
# #2: bob (85)
# #3: carol (92)
# Ghost score: 0
valgrind --leak-check=full ./buggy
# All heap blocks were freed -- no leaks are possibleExercises
-
Watchpoint practice: Add a bug where a global counter gets corrupted by a stray pointer write. Use
watch counterin GDB to find the exact line. -
Core dump debugging: Run the original buggy program outside GDB. Generate a core dump (
ulimit -c unlimited), thengdb ./buggy coreto do post-mortem analysis. -
Conditional breakpoint: Set
break print_top_players if i == 2to stop right before the off-by-one hit. Examine the state. -
Your own bugs: Write a program with a double-free and a buffer overflow. Practice finding them with GDB and ASan.
Next: 04 - Build a Mini Shell — use fork, exec, and pipes to build a working command interpreter.