Core Dump Mastery: Fast-Track C++ Memory Crash Debugging with GDB
This guide explains how to enable core dumps, preserve debug symbols, and use GDB to analyze C++ memory crashes like null pointer dereferences, heap overflows, and stack overflows, with practical code examples and step-by-step commands.
What Is a Core Dump?
A core dump is a memory snapshot captured by the operating system when a process crashes due to signals such as SIGSEGV (segmentation fault), SIGABRT, or stack overflow. It follows the standard ELF format and consists of four parts: the ELF header (file metadata), the program header table (segment layout), the NOTE segment (crash metadata like signal, PID, timestamp), and the LOAD segment (the actual runtime memory image including code, stack, heap, and register state). Together these sections preserve the full call chain, register values, and memory layout at the moment of failure, making core dumps the most powerful tool for diagnosing hard-to-reproduce memory bugs.
Three Prerequisites for Generating a Usable Core File
Many developers find no core file after a crash because the system defaults suppress generation. All three conditions below must be satisfied:
Remove the core file size limit. By default ulimit -c is 0. Run ulimit -c unlimited for the current shell, or make it permanent by adding * soft core unlimited and * hard core unlimited to /etc/security/limits.conf.
Configure the core file path and naming pattern. The kernel uses /proc/sys/kernel/core_pattern. Many distributions pipe cores to systemd-coredump (shown as |/usr/lib/systemd/systemd-coredump). To write files directly to a directory, set a pattern like echo "/tmp/core.%e.%p.%t" > /proc/sys/kernel/core_pattern (where %e =executable name, %p =PID, %t =timestamp). Persist with kernel.core_pattern=/tmp/core.%e.%p.%t and kernel.core_uses_pid=1 in /etc/sysctl.conf.
Compile with debug symbols ( -g ). Core files only contain raw addresses; -g embeds function names, line numbers, and variable information. Use -O0 to prevent optimizer reordering. For production binaries, keep a separate debug symbol file: objcopy --only-keep-debug myprog myprog.debug, then strip myprog, and load in GDB with gdb -s myprog.debug -c core.12345.
Basic GDB Analysis Workflow
Launch GDB with the executable and core file: gdb ./executable ./corefile. Then follow these steps:
bt (backtrace) – lists the call stack at crash time. Frame #0 is the crashing function. Example output shows foo (p=0x0) at test.c:4, revealing a null pointer argument.
(gdb) bt
#0 0x0000000000400523 in foo (p=0x0) at test.c:4
#1 0x0000000000400544 in main () at test.c:8Use bt full to also print local variables in each frame.
frame N – switch to a specific frame (N from bt output), then inspect state:
(gdb) frame 1 # switch to main frame
(gdb) info locals # list all locals
(gdb) p buf # print variable value
(gdb) p *buf # dereference pointer
(gdb) p *ptr@10 # print 10 elements as array
(gdb) x/20xw 0x7fff... # examine memory in hex
(gdb) info registers # show CPU registers
(gdb) list # show source around crash lineCase Study 1: Null Pointer Dereference (SIGSEGV)
Code:
#include <stdio.h>
void process(int *data) {
printf("value is %d
", *data); // line 5
}
int main() {
int *ptr = NULL;
process(ptr); // line 10
return 0;
}Compile: gcc -g -o segv segv.c. Run: ./segv → Segmentation fault (core dumped).
GDB session:
(gdb) bt
#0 0x0000000000400539 in process (data=0x0) at segv.c:5
#1 0x0000000000400565 in main () at segv.c:10
(gdb) frame 0
(gdb) list
1 #include <stdio.h>
2
3 void process(int *data) {
4 printf("value is %d
", *data); // crash here
5 }
(gdb) p data
$1 = (int *) 0x0The parameter data is NULL; dereferencing it on line 5 causes the segfault. Fix by adding a null check in process or ensuring ptr is properly initialized in main.
Case Study 2: Heap Overflow Detected at free()
Heap allocators (ptmalloc) place metadata (chunk headers) before and after user blocks. Overwriting this metadata corrupts the heap, but the crash often occurs later during free or the next malloc, so the backtrace points to the allocator, not the offending write.
Code:
#include <stdlib.h>
#include <string.h>
int main() {
char *buf = (char *)malloc(8); // allocate 8 bytes
strcpy(buf, "0123456789ABCDEF"); // write 17 bytes → overflow
free(buf); // line 7: crash here
return 0;
}Run: gcc -g -o heap_overflow heap_overflow.c && ./heap_overflow → malloc(): corrupted top size / Aborted (core dumped).
Backtrace:
(gdb) bt
#0 __GI_raise (sig=6) at raise.c:50
#1 __GI_abort () at abort.c:79
#2 __libc_message ... at libc_message.c:187
#3 malloc_printerr ... at malloc.c:5342
#4 _int_free ... at malloc.c:4151
#5 0x0000000000400638 in main () at heap_overflow.c:7Frame #5 shows main at line 7 ( free), but the real bug is the strcpy on line 6. Three ways to catch the actual overwrite:
Enable glibc heap checking: MALLOC_CHECK_=3 ./heap_overflow – glibc aborts immediately when metadata corruption is detected.
GDB watchpoint on heap metadata: After stopping at main, print buf address, then watch the metadata location (e.g., watch *(long*)(buf+24)). Continuing will break exactly at the strcpy instruction.
(gdb) break main
(gdb) run
(gdb) print buf
$1 = 0x602010 ""
(gdb) watch *(long*)(0x602010+24) # watch metadata after user block
(gdb) continue
# breaks at strcpy, bt shows culpritCompile with AddressSanitizer:
gcc -g -fsanitize=address -o heap_debug heap_overflow.c && ./heap_debug– reports heap-buffer-overflow at heap_overflow.c:6 directly. Trade-off: slower execution and higher memory use, suitable for test environments.
Case Study 3: Stack Overflow (Infinite Recursion)
Linux default stack size is 8 MB ( ulimit -s to check). Each call pushes a frame (return address, saved base pointer, locals). Infinite recursion or large stack arrays exhaust the stack, triggering SIGSEGV when the guard page is hit. Code: <code>void func() { char big[1024 * 1024]; // 1 MB per call func(); // infinite recursion } int main() { func(); return 0; }</code> Backtrace shows hundreds of identical frames: <code>(gdb) bt #0 0x0000000000400522 in func () at stack_overflow.c:2 #1 0x000000000040052a in func () at stack_overflow.c:3 #2 0x000000000040052a in func () at stack_overflow.c:3 ... repeats hundreds of times ...</code> Seeing the same function repeated confirms missing recursion termination. Fix by adding a base case or moving large arrays to the heap ( malloc ).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
