Analyzing Linux Core Dumps with GDB
Configure core_pattern, capture a crash, and read the failure with GDB — part of our free Linux kernel development course
When an embedded Linux process segfaults in the field, you rarely get a second chance to reproduce it. A core dump freezes the process’s memory image at the exact moment of the crash, and GDB can replay that image afterwards as if the program were still running under the debugger. This lecture, part of our free Linux kernel development course, shows how to configure where core files land, decode the naming pattern the kernel uses, and walk a real crash back to its root cause with GDB — all without needing to reproduce the bug live.
Topics Covered
What You Will Learn
Prerequisites
You should be comfortable building a C program with debug symbols (-g -O0) and have gdb installed on your development host. Root access on the target device is needed to change the core dump pattern, since it lives under /proc/sys/kernel.
Where Core Dumps Come From
When a process receives a fatal signal such as SIGSEGV, SIGABRT, or SIGBUS, and that signal isn’t caught, the kernel’s default action is to write the process’s memory pages, registers, and open file descriptor table to a file before terminating it. Whether that file gets written at all depends on two things: the process’s core-file resource limit (ulimit -c), and where the kernel is told to put it.
By default many distributions disable core dumps (limit of 0) or drop them silently into the working directory as a plain file named core. For embedded debugging you want dumps collected somewhere predictable, named so you can tell crashes apart, and — ideally — piped through a small handler that can filter or compress them before they hit flash.
Configuring core_pattern
The kernel reads its dump destination and naming template from /proc/sys/kernel/core_pattern at the moment of the crash. You can point it at an absolute path with format specifiers, or at a pipe to an external program.
Enable core dumps for the current shell
$ ulimit -c unlimited
Then set a pattern that collects every dump into one directory, tagged with the executable name and a timestamp:
# echo /var/crash/core.%e.%p.%t > /proc/sys/kernel/core_pattern
The commonly used specifiers are:
| Specifier | Meaning |
|---|---|
| %e | Executable filename (no path) |
| %p | PID of the dumped process |
| %t | Dump time, seconds since the Unix epoch |
| %h | Hostname of the machine |
| %s | Number of the signal that caused the dump |
| %g | Real GID of the dumped process |
| %E | Full executable path, slashes replaced with ‘!’ |
Because the setting lives under /proc, it resets on reboot unless you persist it through sysctl.conf or an init script — something worth baking into your board’s rootfs rather than setting by hand each time.
Piping Dumps to a Handler
Instead of a path, core_pattern can start with a pipe character followed by a program and arguments. The kernel then runs that program while the crashed process’s memory is still resident, streaming the core image to the handler’s standard input rather than a file:
# echo "|/usr/local/bin/ep_crash_collector %e %p %s %t" > /proc/sys/kernel/core_pattern
A handler like this can inspect /proc/<pid> while it still exists, strip large or sensitive memory regions before writing to flash, or compress the image on the fly. Once the handler finishes reading standard input, the kernel reclaims the process and that /proc entry disappears — so any inspection has to happen while data is still arriving on the pipe, not afterward.
Core Dump Flow
Reading a Core File in GDB
Here is a small original demo that deliberately dereferences a null pointer, so we have something real to load into GDB:
/* ep_crashdemo.c */
#include <stdio.h>
#include <stdlib.h>
struct ep_node {
int value;
struct ep_node *next;
};
static void ep_bump(struct ep_node *n)
{
n->value += 1; /* crashes if n is NULL */
}
int main(void)
{
struct ep_node *head = NULL;
printf("about to bump a null node\n");
ep_bump(head);
return 0;
}
Build with debug info and run it
$ gcc -g -O0 -o ep_crashdemo ep_crashdemo.c
$ ulimit -c unlimited
$ ./ep_crashdemo
about to bump a null node
Segmentation fault (core dumped)
$ ls /var/crash/
core.ep_crashdemo.4211.1735489213
Load the binary together with its core file:
$ gdb ./ep_crashdemo /var/crash/core.ep_crashdemo.4211.1735489213
...
Core was generated by `./ep_crashdemo'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000555555555149 in ep_bump (n=0x0) at ep_crashdemo.c:11
11 n->value += 1;
GDB has already told us the culprit: n is 0x0. Two commands confirm the picture. list shows the surrounding source, and backtrace (or bt) shows the call chain that led here:
(gdb) list
6 struct ep_node {
7 int value;
8 struct ep_node *next;
9 };
10
11 n->value += 1; /* crashes if n is NULL */
12 }
13
(gdb) bt
#0 0x0000555555555149 in ep_bump (n=0x0) at ep_crashdemo.c:11
#1 0x0000555555555172 in main () at ep_crashdemo.c:18
The backtrace pinpoints exactly where a NULL check was skipped: main() passed an uninitialized-on-purpose null head straight into ep_bump(). On real crashes the same two commands — list and bt — are usually enough to get you from “it crashed somewhere” to “it crashed here, because of this.”
Common Mistakes
Best Practices
Keep a debug-symbol copy of every release binary you ship, even if the field image is stripped — you can point GDB at the separate symbol file with symbol-file. Rotate or cap the crash directory so a crash loop doesn’t fill flash. And prefer piping to a small collector over writing raw dumps directly when memory images might contain sensitive data.
Summary
core_pattern controls where and how the kernel saves a crashed process’s memory image, either as a named file or a stream to a handler process. GDB reopens that image against the matching binary, and bt plus list turn “segmentation fault” into an exact line of code and call path — a core workflow in any free linux device drivers course or field-debugging toolkit.
FAQ
Why is my core file 0 bytes or missing entirely?
Almost always the core-file resource limit is 0. Run ulimit -c unlimited in the shell that launches the process, and check that core_pattern points at a writable, absolute path.
Can I analyze a core file without the exact original binary?
Not reliably. GDB matches addresses to source lines using the binary’s symbol table, so a binary built from different source or optimization flags gives you a misleading backtrace.
What does the %s specifier show me?
The number of the signal that caused the dump — 11 for SIGSEGV, 6 for SIGABRT, and so on — useful for triaging crash types without opening every file.
Is piping to a handler faster than writing to disk?
It avoids one file-write cycle, and lets the handler filter or compress before anything touches flash, which matters more on embedded storage than raw speed.
Does this apply to multi-threaded crashes?
Yes — the core file includes every thread’s state, and GDB’s thread apply all bt shows a backtrace for each one.
Where can I learn more for free?
EmbeddedPathashala’s free linux kernel development course and free embedded linux course cover this workflow alongside kernel-level debugging with kgdb.
Keep Building Your Debugging Skills
This lecture is part of EmbeddedPathashala’s free Linux kernel development course. Explore more chapters on GDB, kgdb, and kernel internals.
Browse the Course Join Free
2 Comments