Diagnosing Segmentation Faults in C with Sanitizers: A Practical Guide
When a C program crashes with SIGSEGV, the culprit is often a subtle memory bug. Use AddressSanitizer and UndefinedBehaviorSanitizer to pinpoint the faulting line, then apply the right fix. This guide walks through symptoms, checks, and escalation steps.
12 Nov 2025, 00:32 UTC

Recognizable Symptoms
Intermittent SIGSEGV crashes, corrupted data that appears only after a few runs, or crashes that change when you reorder code or tweak the optimization level are classic signs of memory corruption. Typical culprits include:
- Use‑after‑free or double‑free
- Heap or stack buffer overflows
- Uninitialized pointer dereferences
- Signed/unsigned overflow or misaligned access
- Race conditions that become visible only under certain timings
Diagnostic Table
| Root Cause | Typical Symptom | Sanitizer Flag | Quick Fix Pattern |
|---|---|---|---|
| Heap buffer overflow | Corrupted adjacent data, crash after free | -fsanitize=address | Replace strcpy with strncpy or snprintf |
| Use‑after‑free | Crash on dereference after free() | -fsanitize=address | Set pointer to NULL after free; guard against double free |
| Stack buffer overflow | Crash before return, stack trace shows local array | -fsanitize=address | Use strncpy or guard page with mprotect |
| Uninitialized read | Random values, crash on dereference | -fsanitize=undefined | Initialize memory before use; use memset |
| Signed overflow | Unexpected wraparound, crash on arithmetic | -fsanitize=undefined | Check bounds before arithmetic; use int64_t |
Step‑by‑Step Checks
- Build with Sanitizers
# Compile with AddressSanitizer and UndefinedBehaviorSanitizer # Run on a machine that supports the runtime gcc -fsanitize=address,undefined -g -O1 -o myprog myprog.c # Use -O1 to keep optimization but reduce memory overheadPermissions:
sudomay be required forld.so.preloadif you use custom sanitizer runtimes. - Reproduce the Crash
Run the instrumented binary. The sanitizer will abort and print a stack trace pointing to the offending line, the allocation site, and, for use‑after‑free, the free site.
- Interpret the Report
Typical ASan output looks like:
================================================================= ==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000030 in function bar at myprog.c:42 freed at myprog.c:30 by thread T0 #0 0x7f6a... in bar (myprog.c:42) #1 0x7f6a... in main (myprog.c:55) ================================================================= Aborted (core dumped)Here, line 42 is the faulting dereference; line 30 is where the memory was freed.
- Apply the Fix
Based on the report, modify the code. For a use‑after‑free, set the pointer to
NULLafterfree():free(ptr); ptr = NULL; // Prevent accidental reuse - Re‑run Sanitized Build
Confirm the sanitizer no longer reports the error. If the crash disappears, the bug is fixed.
- Test at Production Optimizations
Run the same build with
-O2or the production flags. If the error re‑appears, the bug is likely masked by the sanitizer or is tied to strict‑aliasing assumptions.
Fixes Tied to Findings
- Buffer overflows: Replace
gets(),strcpy(),sprintf()with safer variants that take buffer size. - Uninitialized reads: Zero‑initialize structs or arrays with
memset(&obj, 0, sizeof obj)before first use. - Signed/unsigned overflow: Validate indices before arithmetic; use
int64_tfor large ranges. - Race conditions: If the sanitizer flags a data race, compile with
-fsanitize=threadto locate the offending access.
Escalation Criteria
- Crash disappears under ASan: Likely a timing or race issue. Switch to ThreadSanitizer or add
__sync_synchronize()fences. - Crash only in optimized builds: Test with
-O0 -fno-strict-aliasing. If the bug vanishes, it may be an aliasing violation; adjust code to userestrictor separate pointers. - No sanitizer report, still crash: Use
gdbcore dump analysis,valgrind --leak-check=full, or Electric Fence for a guard‑page allocator.
Practical Verification Checklist
- Reproduce the crash with
-fsanitize=address,undefined -g. - Apply the fix and rerun the same build; verify the sanitizer report is gone.
- Run the full test suite under the sanitizer in CI; any new reports should fail the build.
- Confirm the program runs cleanly at the target optimization level (
-O2or higher).
Limitations & Caveats
- Sanitizers roughly double memory usage and can slow execution 2–5×; avoid shipping with them to production.
- They only detect errors on executed paths; low test coverage can miss bugs.
- Some embedded targets lack full sanitizer runtimes; fallback to
valgrindorElectric Fenceif needed. - Custom allocators or use of
longjmpacross instrumented frames may break sanitizer instrumentation.
Concrete Example
Consider the following snippet that triggers a heap buffer overflow:
void copy_input(const char *src) {
char *buf = malloc(10); // only 10 bytes
strcpy(buf, src); // no bounds check
printf("%s\n", buf);
free(buf);
}
Build and run with sanitizers:
gcc -fsanitize=address,undefined -g -O1 -o test test.c
./test
ASan will report the overflow at the strcpy call, pointing to the allocation site. Fix by using strncpy or snprintf:
snprintf(buf, 10, "%s", src);
Re‑run; the sanitizer should now exit cleanly.
Conclusion
AddressSanitizer and UndefinedBehaviorSanitizer provide a fast, accurate first line of defense against memory corruption in C. By following the diagnostic table, ordered checks, and escalation criteria above, you can turn a mysterious SIGSEGV into a precise, fixable bug report, then verify the fix across all target builds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.