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 NULL or 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:

  1. Check and Increase Resource Limits: View the current core file size limit in your shell:

    ulimit -c

    If it returns 0, enable unlimited core file sizes for the current session:

    ulimit -c unlimited
  2. Verify 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_pattern

    If systemd-coredump is active, core dumps are automatically managed by systemd and can be inspected via coredumpctl.

  3. 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 -g flag:

    gcc -g -O0 main.c -o my_program

    Disabling optimizations with -O0 ensures 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 core

If 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 0
  • Print variable values:

    (gdb) print node
    $1 = (struct Node *) 0x0
  • View local variables in the current scope:

    (gdb) info locals
  • Inspect 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.