gcc -g -fsanitize=address -fno-omit-frame-pointer program.c -o program && ./program
-g before debugging — without debug symbols, GDB and ASan
cannot show source file names or line numbers. [src1, src5]-Wall -Wextra catches many patterns that lead
to segfaults (uninitialized variables, format string mismatches, implicit function declarations). [src2]-fsanitize=address OR run under valgrind, never both simultaneously. [src4]
-fsanitize=address. [src6]ulimit -c defaults to 0. Set
ulimit -c unlimited before running the crashing program. [src5]| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | NULL pointer dereference | ~25% | Crash at address 0x0; si_addr=(nil) |
Check pointer for NULL before dereference; initialize pointers [src2] |
| 2 | Array/buffer out-of-bounds | ~20% | Crash near valid heap/stack; Valgrind: "Invalid read/write of size N" | Use bounds checking; replace char[] with std::vector [src2] |
| 3 | Use-after-free (dangling pointer) | ~15% | ASan: "heap-use-after-free"; Valgrind: "Invalid read/write" | Set pointer to NULL after free()/delete; use smart pointers [src2, src5] |
| 4 | Uninitialized pointer (wild pointer) | ~12% | Crash at random address; si_addr is garbage value |
Always initialize pointers to NULL/nullptr [src1] |
| 5 | Stack overflow (deep recursion) | ~10% | Crash near stack boundary; GDB frame count is huge | Add recursion base case; increase stack with ulimit -s; convert to iteration [src2] |
| 6 | Double free | ~8% | free(): double free detected; ASan: "double-free" |
Set pointer to NULL after free; use smart pointers [src2] |
| 7 | Writing to read-only memory | ~5% | Crash on string literal address; char *s = "hello"; s[0] = 'H'; |
Use const char* for literals; use char s[] = "hello" for mutable [src2, src7] |
| 8 | Mismatched alloc/dealloc | ~3% | ASan: "alloc-dealloc-mismatch"; malloc freed with delete |
Match pairs: malloc/free, new/delete,
new[]/delete[] [src2] |
| 9 | Format string mismatch | ~2% | printf("%d", string_ptr) — crash or garbage output |
Use -Wformat flag; match format specifiers to argument types [src1] |
START — Program crashes with "Segmentation fault (core dumped)"
├── Do you have the source code?
│ ├── YES → Recompile: gcc -g -fsanitize=address -fno-omit-frame-pointer
│ │ ├── ASan prints error report?
│ │ │ ├── YES → Read report: file:line, access type, alloc/free stack
│ │ │ │ ├── "heap-use-after-free" → Cause #3: dangling pointer
│ │ │ │ ├── "heap-buffer-overflow" → Cause #2: array out-of-bounds
│ │ │ │ ├── "stack-buffer-overflow" → Cause #2: local array overflow
│ │ │ │ ├── "double-free" → Cause #6
│ │ │ │ └── "SEGV on unknown address 0x000000000000" → Cause #1: NULL
│ │ │ └── NO → Run under GDB (see step-by-step below)
│ │ └── ASan unavailable (old compiler)?
│ │ └── valgrind --tool=memcheck ./program
│ └── NO (binary only) → Valgrind or core dump + GDB
│
├── Is there a core dump file?
│ ├── YES → gdb ./program core → bt → frame N → info locals
│ └── NO → ulimit -c unlimited → reproduce → analyze core
│
└── Crash is intermittent / hard to reproduce?
├── Valgrind for sustained testing (2x slowdown, no recompile)
├── ASan for always-on detection (2x slowdown, needs recompile)
└── Multithreaded? → gcc -fsanitize=thread
Always start by recompiling with maximum diagnostics. [src1, src2]
# C
gcc -g -Wall -Wextra -Werror -O0 program.c -o program
# C++
g++ -g -Wall -Wextra -Werror -O0 -std=c++17 program.cpp -o program
Verify: file program → should show "with debug_info, not stripped"
GDB stops at the exact instruction that triggered SIGSEGV. [src1, src5]
gdb ./program
(gdb) run
# ... program crashes ...
(gdb) bt # Backtrace — full call stack
(gdb) bt full # Backtrace with local variables
(gdb) frame 0 # Go to the crashing frame
(gdb) info locals # Show all local variables
(gdb) print ptr # Inspect a specific variable
(gdb) list # Show source around crash point
Verify: bt output → the top frame shows the file and line of the crash.
Analyze crashes after the fact when you cannot run interactively. [src5]
# Enable core dumps
ulimit -c unlimited
# Optional: set core dump location (Linux)
echo "/tmp/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
# Load core dump in GDB
gdb ./program /tmp/core.program.12345
(gdb) bt
(gdb) bt full
(gdb) frame 3
(gdb) info locals
Verify: ls -la /tmp/core.* → core file exists and is non-empty.
Valgrind finds memory errors that happen BEFORE the crash. No recompilation needed. [src4, src5]
valgrind --tool=memcheck \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
./program
Verify: Valgrind exit summary shows "ERROR SUMMARY: 0 errors" when all memory errors are fixed.
ASan is built into GCC (>= 4.8) and Clang (>= 3.1). ~2x slowdown vs Valgrind's ~20x. [src3, src6]
# Compile with ASan
gcc -g -fsanitize=address -fno-omit-frame-pointer -O1 program.c -o program
# Run — ASan prints detailed error report on crash
./program
# Combine with GDB for interactive debugging
ASAN_OPTIONS=abort_on_error=1 gdb ./program
(gdb) run
(gdb) bt
Verify: Run with ASAN_OPTIONS=detect_leaks=1 → ASan also reports memory leaks at
exit.
Many segfaults are caused by undefined behavior that corrupts memory silently. [src6]
# Compile with both ASan and UBSan
gcc -g -fsanitize=address,undefined -fno-omit-frame-pointer program.c -o program
Verify: ./program → UBSan prints runtime errors with
file:line:col precision.
// Input: None (demonstration of segfault causes)
// Output: Shows 4 common segfault patterns with correct alternatives
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Pattern 1: NULL pointer dereference
void null_deref_bad(void) {
int *ptr = NULL;
*ptr = 42; // SEGFAULT
}
void null_deref_good(void) {
int *ptr = malloc(sizeof(int));
if (ptr == NULL) { perror("malloc"); return; }
*ptr = 42;
free(ptr);
}
// Pattern 2: Use after free
void uaf_bad(void) {
int *ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
printf("%d\n", *ptr); // SEGFAULT — use after free
}
void uaf_good(void) {
int *ptr = malloc(sizeof(int));
*ptr = 42;
printf("%d\n", *ptr);
free(ptr);
ptr = NULL; // Prevent accidental reuse
}
// Pattern 3: Writing to string literal
void literal_bad(void) {
char *s = "hello";
s[0] = 'H'; // SEGFAULT — read-only memory
}
void literal_good(void) {
char s[] = "hello"; // Mutable copy on stack
s[0] = 'H'; // Safe
}
// Input: None (C++ safety patterns)
// Output: Smart pointer usage to prevent segfaults
#include <memory>
#include <vector>
#include <iostream>
// GOOD: unique_ptr — automatic cleanup, no double-free
void unique_ptr_good() {
auto data = std::make_unique<int[]>(100);
data[0] = 42;
// Freed when scope exits, even on exception
}
// GOOD: shared_ptr — safe shared ownership
void shared_ptr_good() {
auto data = std::make_shared<std::vector<int>>(100, 0);
auto alias = data; // Reference count = 2
}
// GOOD: .at() for bounds-checked access
void bounds_checked() {
std::vector<int> v = {1, 2, 3};
try {
int val = v.at(10); // Throws std::out_of_range
} catch (const std::out_of_range& e) {
std::cerr << "Bounds error: " << e.what() << "\n";
}
}
#!/bin/bash
# Input: Source file path (C or C++)
# Output: ASan + Valgrind diagnostics
SRC="$1"
BIN="${SRC%.c*}.debug"
if [[ -z "$SRC" ]]; then
echo "Usage: $0 <source.c|source.cpp>"; exit 1
fi
if [[ "$SRC" == *.cpp ]]; then CC="g++ -std=c++17"; else CC="gcc"; fi
echo "=== Step 1: Compile with ASan + UBSan ==="
$CC -g -fsanitize=address,undefined -fno-omit-frame-pointer \
-Wall -Wextra -O1 "$SRC" -o "$BIN"
echo "=== Step 2: Run with AddressSanitizer ==="
ASAN_OPTIONS=detect_leaks=1:abort_on_error=0 ./"$BIN"
echo "=== Step 3: Run with Valgrind Memcheck ==="
$CC -g -Wall -Wextra -O0 "$SRC" -o "${BIN}.valgrind"
valgrind --tool=memcheck --leak-check=full \
--show-leak-kinds=all --track-origins=yes \
./"${BIN}.valgrind"
// BAD — ptr could be NULL if function returns NULL [src1, src2]
int *ptr = find_record(id);
printf("Value: %d\n", *ptr); // SEGFAULT if NULL
// GOOD — defensive programming prevents crash [src1, src2]
int *ptr = find_record(id);
if (ptr != NULL) {
printf("Value: %d\n", *ptr);
} else {
fprintf(stderr, "Record %d not found\n", id);
}
// BAD — classic use-after-free [src2, src5]
char *buf = malloc(256);
snprintf(buf, 256, "data");
free(buf);
printf("Buffer: %s\n", buf); // SEGFAULT or garbage
// GOOD — pointer safely nulled [src2, src5]
char *buf = malloc(256);
if (buf == NULL) { perror("malloc"); return; }
snprintf(buf, 256, "data");
printf("Buffer: %s\n", buf);
free(buf);
buf = NULL;
// BAD — stack memory invalid after return [src1]
int* get_data(void) {
int arr[10] = {1, 2, 3};
return arr; // Returns address of local variable!
}
int *data = get_data();
printf("%d\n", data[0]); // SEGFAULT — stack frame gone
// GOOD — heap allocation survives function return [src1]
int* get_data(void) {
int *arr = malloc(10 * sizeof(int));
if (arr == NULL) return NULL;
arr[0] = 1; arr[1] = 2; arr[2] = 3;
return arr; // Caller must free()
}
// BAD — string literals in read-only memory [src2, src7]
char *greeting = "hello world";
greeting[0] = 'H'; // SEGFAULT
// GOOD — array copies literal to mutable stack memory [src2, src7]
char greeting[] = "hello world";
greeting[0] = 'H'; // Safe
-O2/-O3 reorders code,
making GDB show wrong line numbers or "optimized out" variables. Fix: debug with -O0 or
-O1 -fno-omit-frame-pointer. [src1, src6]ulimit -c 0. Fix: add
ulimit -c unlimited to your shell profile. [src5]-fsanitize=memory (Clang MSan) or use Valgrind
--track-origins=yes. [src4,
src5]free(ptr) on stack variable: Passing a stack address to free()
corrupts the heap. Fix: only free() what was returned by
malloc()/calloc()/realloc(). [src2]-fsanitize=undefined. [src6]# === Compile with sanitizer support ===
gcc -g -fsanitize=address -fno-omit-frame-pointer -O1 program.c -o program
gcc -g -fsanitize=address,undefined -fno-omit-frame-pointer program.c -o program
clang -g -fsanitize=address -fno-omit-frame-pointer -O1 program.c -o program
# === Enable core dumps ===
ulimit -c unlimited
echo "/tmp/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
# === GDB commands ===
gdb ./program # Start debugger
gdb ./program core # Load core dump
# run / bt / bt full / frame N / info locals / print var / x/16xb ptr
# === Valgrind ===
valgrind ./program
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./program
# === Check binary for debug info ===
file ./program
readelf -S ./program | grep debug
# === Check for core dumps (systemd) ===
coredumpctl list
coredumpctl gdb
| Tool | Version | Key Feature | Notes |
|---|---|---|---|
| GDB | >= 7.0 (2009) | Python scripting, reverse debugging | Available on all Linux distros [src1] |
| Valgrind | >= 3.10 (2014) | Full C++11 support | ~20x slowdown; no recompile needed [src4] |
| ASan (GCC) | >= 4.8 (2013) | Basic ASan support | Requires recompilation [src6] |
| ASan (Clang) | >= 3.1 (2012) | Original ASan implementation | Generally better ASan support than GCC [src3] |
| UBSan (GCC) | >= 4.9 (2014) | Undefined behavior detection | -fsanitize=undefined [src6]
|
| MSan (Clang) | >= 3.3 (2013) | Uninitialized memory detection | Clang only; not in GCC |
| TSan | >= GCC 4.8 / Clang 3.2 | Data race detection | For multithreaded segfaults |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Program crashes with "Segmentation fault" | Crash message is "Aborted" (SIGABRT) | Check for assert() failures or abort() calls |
| GDB backtrace shows your code | Crash inside system library (libc, libpthread) | Check your arguments to library calls |
| Need to debug production binary (no source) | Have source and can recompile | Use ASan — faster and more detailed than Valgrind |
| Intermittent crash in multithreaded code | Single-threaded memory bug | Use ThreadSanitizer: -fsanitize=thread |
| Linux or macOS environment | Windows environment | Visual Studio debugger or WinDbg |
-march=native for Valgrind testing. [src4]
-O0. Use -O1 -fno-omit-frame-pointer as a compromise.
[src1, src6]lldb is the default debugger and has similar commands (bt,
frame select, v for locals). [src4]