Analyzing Core Dumps in C Programming
When a C application crashes unexpectedly, it often leaves behind a core dump—a recorded state of the working memory at that exact moment of failure. This article covers what a core dump is, how operating systems produce them, how to configure your environment to capture them, and the step-by-step process of using the GNU Debugger (GDB) to perform post-mortem analysis and locate the root cause of fatal bugs.
What Is a Core Dump?
A core dump is a file containing an address space snapshot of a running process at the moment it terminated abnormally. In the C programming language, low-level memory mismanagement frequently causes the operating system to send terminating signals such as:
- SIGSEGV (Segmentation Fault): Invalid memory
reference, such as dereferencing a
NULLor wild pointer. - SIGABRT (Abort): Triggered by an explicit call to
abort()or an assertion failure (assert()). - SIGFPE (Floating Point Exception): Fatal arithmetic errors, such as integer division by zero.
- SIGBUS (Bus Error): Memory access misalignment or non-existent physical address access.
When these signals occur without a custom signal handler catching them, the kernel terminates the process and dumps its memory image, processor registers, program counter, and stack pointers into a file.
Configuring the Environment to Capture Dumps
Many Unix-like systems disable core dump generation by default to conserve disk space. To capture a crash, configure your environment using the following steps:
Check and Increase Resource Limits: View the current core file size limit in your shell:
ulimit -cIf it returns
0, enable unlimited core file sizes for the current session:ulimit -c unlimitedVerify the Dump Location: On modern Linux distributions, the naming and storage location of core files are managed by the kernel. You can check the path pattern by reading:
cat /proc/sys/kernel/core_patternIf
systemd-coredumpis active, core dumps are automatically managed bysystemdand can be inspected viacoredumpctl.Compile with Debug Symbols: A core dump only provides useful line numbers and variable names if the executable contains debugging symbols. Always compile with the
-gflag:gcc -g -O0 main.c -o my_programDisabling optimizations with
-O0ensures the compiler does not optimize away variables or inline functions, making the call stack easier to read.
Analyzing the Crash with GDB
Once the application terminates and a core file is generated, you can conduct post-mortem analysis using GDB without having to recreate the runtime conditions.
1. Launching GDB with the Core File
Invoke GDB by passing both the path to the executable and the core file:
gdb ./my_program coreIf using systemd-coredump, run:
coredumpctl gdb <PID or executable name>GDB will load the program symbols, read the core file, and immediately display the signal that terminated the process along with the exact instruction or line of code where execution halted.
2. Inspecting the Call Stack
To see how the program reached the faulty state, print the call stack
using the backtrace command:
(gdb) backtrace
#0 0x0000555555555151 in process_node (node=0x0) at main.c:12
#1 0x0000555555555198 in parse_list (head=0x5555555592a0) at main.c:24
#2 0x00005555555551d5 in main () at main.c:35
The stack trace displays the chain of function calls. Frame
#0 is the function where the crash happened, frame
#1 is the caller, and so on.
3. Navigating Frames and Inspecting Variables
Move through the call stack to examine local variables and pointers leading up to the crash:
Select a frame:
(gdb) frame 0Print variable values:
(gdb) print node $1 = (struct Node *) 0x0View local variables in the current scope:
(gdb) info localsInspect processor registers:
(gdb) info registers
In this scenario, printing node confirms that the
function attempted to dereference a NULL pointer, which
directly caused the SIGSEGV.
Summary
Post-mortem analysis using core dumps eliminates guesswork when
debugging fatal errors in C. By compiling with debug flags
(-g), ensuring the operating system is configured to save
core files (ulimit -c unlimited), and inspecting the
resulting dumps with GDB, developers can reliably trace segmentation
faults and aborts back to the exact line of code and memory state
responsible for the crash.