Skip to main content

Debugging Fundamentals

Debugging C is an art. Unlike managed languages where the runtime catches null references and out-of-bounds accesses with clear exceptions, C will silently corrupt memory, produce wrong results, or crash with nothing but “Segmentation fault (core dumped).” The bug that crashes your program at line 500 may have been caused by a buffer overflow at line 50. This chapter arms you with the tools and techniques to systematically hunt down even the most devious bugs.

GDB Essentials

Starting GDB

Basic Commands

Advanced GDB

GDB Init File


Core Dumps

Enabling Core Dumps

Analyzing Core Dumps

Triggering Core Dumps


Memory Debugging

Valgrind

Valgrind runs your program on a synthetic CPU, intercepting every memory access. This gives it superpowers (detecting reads of uninitialized memory, off-by-one errors, leaks) but makes your program 10-50x slower. Always test with Valgrind before shipping, but do not use it for performance benchmarks.

Understanding Valgrind Output

This means: reading 4 bytes right after a 16-byte allocation (buffer overflow).

AddressSanitizer

AddressSanitizer (ASan) is a compile-time instrumentation tool that catches memory errors at runtime with only 2x slowdown (much faster than Valgrind). It detects heap/stack/global buffer overflows, use-after-free, use-after-return, double-free, and memory leaks. In practice, run your test suite with ASan enabled as part of CI — it catches bugs that would take days to track down with GDB.

Common Memory Errors

These are the seven deadly sins of C memory management. Every one of them is undefined behavior — meaning the program might crash, produce wrong results, appear to work fine, or even format your hard drive (in theory). The terrifying part: “appears to work fine” is the most common outcome in development, and “crashes in production” is when you find out.

Debugging Patterns

Printf Debugging (But Better)

Assertions

Memory Debugging Wrapper


Tracing System Calls

ltrace (Library Calls)


Debugging Crashes Systematically

1

Reproduce Consistently

Find the minimal input/steps to trigger the crash. Randomness = harder debugging.
2

Get the Stack Trace

Run in GDB or analyze core dump. Know exactly where it crashed.
3

Examine State

Check variable values, pointer validity, array bounds at crash site.
4

Find the Root Cause

The crash site is almost never the bug itself. A segfault in free() usually means someone corrupted the heap much earlier. A wrong value in a calculation often traces back to an uninitialized variable set 50 lines ago. Work backwards from the crash: when did the state first become invalid? Use watchpoints in GDB (watch variable) to catch the exact moment a value changes unexpectedly.
5

Verify Fix

Run the same reproduction steps. Then run your test suite.

Exercises

1

GDB Mastery

Write a program with a linked list. Use GDB to traverse the list, print node values, and find a bug you intentionally introduce.
2

Core Dump Analysis

Write a program that crashes. Generate a core dump and analyze it to find the crash location.
3

Memory Bug Hunt

Write a program with at least 3 different memory bugs. Use Valgrind and AddressSanitizer to find them all.
4

Debug Wrapper

Extend the debug_malloc wrapper to detect double-frees and use-after-free.

Next Up

Pointers Deep Dive

Master the heart of C programming