Your program works on test input. It works on sample data. Then one real log line from production crashes the whole thing.
That is how many C bugs actually appear.
Not as neat textbook mistakes. Not as obvious one-line disasters. Just one slightly messy real-world input that breaks an otherwise working program.
This is where GDB becomes useful.
Instead of guessing, adding random printf() calls, and rerunning the program over and over, you can stop execution, inspect the program's state, and trace the bug back to its source.
In this article, we will use one small but realistic example from start to finish.
The Problem
Imagine you are writing a tiny log parser. Most log lines look like this:
INFO auth login-okBut one real line from production looks like this:
ERROR paymentThe message field is missing. That is enough to crash the program.
Here is the code:
#include <stdio.h>
#include <string.h>
void print_log(char *line) {
char *level = strtok(line, " ");
char *service = strtok(NULL, " ");
char *msg = strtok(NULL, "\n");
printf("[%s] %s: %s\n", level, service, msg);
}
int main() {
char line[] = "ERROR payment";
print_log(line);
}At first glance, this looks fine. But it trusts the input too much.
If the log line has only two fields, msg becomes NULL. Then printf("%s", msg) may crash with a segmentation fault.
Now let us debug it properly.
Step 1: Compile for Debugging
Compile the program with debug symbols:
gcc -g -Og app.c -o appThe -g flag gives GDB source-level information like line numbers and variable names.
The -Og flag keeps the code easier to follow during debugging.
Without these flags, GDB is much less helpful.
Step 2: Run the Program in GDB
Start GDB:
gdb ./appInside GDB, run the program:
(gdb) runThe program crashes. GDB stops and shows a segmentation fault.
That tells you the program touched invalid memory, but it does not yet tell you why.
So now you inspect.
Step 3: Read the Backtrace
The first command to run after a crash is:
(gdb) btThis prints the backtrace, which shows the chain of function calls leading to the crash.
In this example, it points to print_log().
That is your first real clue. The crash is happening inside the function that parses and prints the log line.
Step 4: Move to the Right Frame
Now move to the frame that contains your code:
(gdb) frame 1Then inspect the values:
(gdb) print level
(gdb) print service
(gdb) print msgThis is the important moment.
level is valid.
service is valid.
msg is 0x0, which means NULL.
Now the crash makes sense.
The input line did not contain enough fields, so strtok() returned NULL. The program then passed that null pointer into printf() as a string.
Step 5: Look at the Code Around the Crash
To confirm the exact line, run:
(gdb) listGDB shows the code around the current location, including this line:
printf("[%s] %s: %s\n", level, service, msg);At this point, the bug is no longer mysterious.
This is why GDB is so useful. It does not just tell you that the program crashed. It shows you the exact state that caused the crash.
Step 6: Fix the Real Problem
The real issue is not just the crash. The real issue is that the code assumes the input always has all three fields.
A safer version looks like this:
#include <stdio.h>
#include <string.h>
void print_log(char *line) {
char *level = strtok(line, " ");
char *service = strtok(NULL, " ");
char *msg = strtok(NULL, "\n");
if (!level || !service || !msg) {
printf("Malformed log line\n");
return;
}
printf("[%s] %s: %s\n", level, service, msg);
}
int main() {
char line[] = "ERROR payment";
print_log(line);
}Now the program handles incomplete input safely instead of crashing.
Why This Example Matters
This example is small, but the pattern is real.
The same thing happens when:
- An API response is missing a field
- A CSV row has fewer columns than expected
- A log file is incomplete
- Production data is messier than test data
That is the real value of GDB.
You run the program. You read the backtrace. You inspect the variables. You find the bad state. Then you fix the assumption that caused it.
The GDB Commands Worth Remembering
For beginners, a few commands are enough to go surprisingly far:
run
bt
frame
list
print
break
next
continueYou do not need to master everything at once.
Learn these first, and you can debug many real problems with much more confidence.
Final Thoughts
GDB is not just a crash tool. It is a way to inspect reality when your code and your assumptions stop matching.
That is why it matters.
When a real-world input breaks your program, GDB helps you move from confusion to evidence.
The next time your C program crashes, do not panic. Open GDB, inspect the state, and let the program show you what actually went wrong.
If you found this useful, save it for the next time a segmentation fault ruins your day, and share it with another developer who would benefit from it.