Decoding the Error
Few messages are as cryptic as Segmentation fault (core dumped). This error, often triggered by the SIGSEGV signal (signal 11), occurs when a process attempts to access a memory address it doesn't own. The Linux kernel intervenes, terminates the process to protect system integrity, and saves a snapshot of the memory—the "core dump"—for analysis.
Why It Happens
Most memory violations stem from a handful of coding oversights. While the logic varies, the underlying memory error usually falls into one of these categories:
- Null Pointer Dereference: Your code tries to read or write to address
0x0. - Buffer Overflow: You wrote 128 bytes into a 64-byte buffer, corrupting the adjacent stack frame.
- Stack Exhaustion: Deep recursion (e.g., 10,000+ calls) has pushed the stack pointer beyond its 8MB default limit.
- Use After Free: Accessing a pointer after calling
free()ordelete, which may now point to garbage or a different object. - ABI Mismatches: Linking against a library version that has changed its data structure offsets. This is frequent when mixing Python with compiled extensions like NumPy.
Step 1: Inspect System Logs
Before firing up heavy tools, check the kernel ring buffer. It provides a high-level view of where the crash happened, including the instruction pointer (IP) and the specific library at fault.
sudo dmesg | tail -n 20
Alternatively, use journalctl to filter for recent faults:
journalctl -xe | grep -i segfault
A typical log entry looks like this: segfault at 0 ip 00007f892c... sp 00007ffc... error 4 in libc-2.31.so. The at 0 confirms a null pointer dereference, while libc-2.31.so points to a standard library failure.
Step 2: Generate and Analyze Core Dumps
Many modern distros disable core dumps to prevent filling up the disk. You must manually lift this restriction to capture the crash state.
First, check your current shell limits:
ulimit -c
If the output is 0, enable unlimited core file sizes for your current session:
ulimit -c unlimited
Run your program again. Once it crashes, a file named core or core.PID should appear. Load this into the GNU Debugger (GDB) to see the exact line of failure:
gdb ./your_binary core
Inside the GDB prompt, type bt (backtrace) to view the call stack. This reveals the sequence of function calls that led to the disaster.
Step 3: Deep Memory Inspection with Valgrind
GDB shows you where the program died, but Valgrind shows you why it started dying. It acts as a virtual CPU that tracks every single byte of memory allocation and access.
Install it using your package manager:
sudo apt install valgrind # Debian/Ubuntu
sudo dnf install valgrind # Fedora/RHEL
Execute your program through the Memcheck tool:
valgrind --leak-check=full ./your_binary
Watch for reports like "Invalid write of size 8" or "Address 0x522d040 is 0 bytes inside a block of size 10 free'd". Valgrind catches memory corruption long before the OS notices the violation.
Step 4: Troubleshooting Python Segfaults
Pure Python code rarely segfaults. If it does, a C-extension like TensorFlow or OpenCV is usually the culprit. To find the offending line, use the built-in faulthandler module.
Add these lines to the very top of your main script:
import faulthandler
faulthandler.enable()
Now, when the crash occurs, Python will dump the traceback of all active threads directly to the console, identifying the specific Python function calling the broken C code.
Step 5: Verification and Prevention
Once you apply a fix, don't just assume it works because the crash stopped. Follow these validation steps:
- Compile with Debug Symbols: Always use the
-gflag (e.g.,gcc -g main.c). Without symbols, debuggers can't map memory addresses back to your source code. - Use AddressSanitizer (ASAN): For C++, recompile with
-fsanitize=address. It is faster than Valgrind and catches overflows in real-time. - Verify with Static Analysis: Run
cppcheckorclang-tidy. These tools find potential null dereferences before you even run the code.
Best Practices
Defensive programming is the best cure for segfaults. Always initialize pointers to nullptr in C++. In modern C++, leverage smart pointers like std::unique_ptr to handle deallocation automatically. If you are working with arrays, prefer std::vector::at() during development; it throws an out-of-range exception instead of allowing a silent, dangerous memory breach.

