Saat program C crash karena sinyal fatal seperti SIGSEGV (segmentation fault) atau SIGABRT, kernel Linux bisa menyimpan snapshot memori proses ke file yang disebut core dump. File ini berisi seluruh memori proses saat crash, tumpukan pemanggilan fungsi, isi register CPU, dan daftar file yang dipetakan. Dengan file core dan binary yang dikompilasi dengan simbol debug, kita bisa membongkar penyebab crash setelah kejadian—bahkan bila crash baru terjadi sekali dan sulit direproduksi. Pada chapter ini kita akan mengaktifkan core dump via ulimit, membuat program yang crash, memeriksa file core dengan file dan readelf, lalu menganalisisnya dengan addr2line dan (secara konseptual) gdb.

Mengaktifkan Core Dump #

Secara default, banyak distribusi Linux mematikan pembuatan core dump karena ukuran file bisa sangat besar. Dua pengaturan mengendalikan perilaku ini:

  • ulimit -c <ukuran>: batas ukuran file core dalam blok 512 byte. Nilai 0 berarti core dimatikan; unlimited berarti tanpa batas.
  • /proc/sys/kernel/core_pattern: template lokasi dan nama file core. Bila diawali karakter |, core disalurkan ke program external (misalnya systemd-coredump); bila hanya core atau path polos, file core ditulis langsung ke direktori kerja.
bash
# Lihat batas saat ini
ulimit -c
# Aktifkan core dump tanpa batas ukuran
ulimit -c unlimited
# Verifikasi
ulimit -c
# Lihat pola lokasi core
cat /proc/sys/kernel/core_pattern

Output yang Dihasilkan:

text
0
unlimited
core

Mari kita bedah tiga output di atas:

  1. 0 (output pertama): batas awal ukuran core adalah 0 blok—core dump dimatikan. Karakter inilah yang membuat banyak program crash tanpa meninggalkan jejak.
  2. unlimited (output kedua): setelah ulimit -c unlimited, batas berubah menjadi tak terbatas. Sekarang crash akan menghasilkan file core dengan ukuran penuh sesuai memori proses.
  3. core (output ketiga): pola lokasi file core adalah core, artinya file akan ditulis di direktori kerja proses dengan nama core. Bila nilainya |/usr/lib/systemd/systemd-coredump, core ditangani oleh systemd-coredump dan disimpan di /var/lib/systemd/coredump/.

Program yang Menghasilkan Core Dump #

Kita akan memakai program berikut yang berisi bug use-after-free. Program mengalokasikan sebuah Node di heap, membebaskannya, lalu mencoba membaca field value dari pointer yang sudah tidak valid. Karena pointer kemudian diset ke NULL sebelum dibaca, crash yang terjadi adalah null pointer dereference.

c
/* File: /tmp/test_core_dump.c */
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
    int value;
    struct Node *next;
} Node;
static Node *buat_node(int v) {
    Node *n = (Node *)malloc(sizeof(Node));
    if (n) {
        n->value = v;
        n->next = NULL;
    }
    return n;
}
static int baca_node_value(Node *n) {
    /* Bug: tidak cek n == NULL */
    return n->value;
}
int main(void) {
    Node *head = buat_node(10);
    free(head);
    head = NULL;
    int v = baca_node_value(head);  /* null deref */
    printf("v = %d\n", v);
    return 0;
}
bash
gcc -g -O0 -std=c11 -Wall -Wextra /tmp/test_core_dump.c -o /tmp/test_core_dump
ulimit -c unlimited
cd /tmp && /tmp/test_core_dump

Output yang Dihasilkan:

text
Segmentation fault (core dumped)

Shell menampilkan pesan Segmentation fault (core dumped) dengan kode keluar 139 (128 + 11, di mana 11 adalah nomor sinyal SIGSEGV). Verifikasi file core telah dibuat:

bash
ls -la /tmp/core
file /tmp/core

Output yang Dihasilkan:

text
-rw------- 1 z z 450560 Oct 13 08:04 /tmp/core
/tmp/core: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from '/tmp/test_core_dump', real uid: 1001, effective uid: 1001, real gid: 1001, effective gid: 1001, execfn: '/tmp/test_core_dump', platform: 'x86_64'

Ukuran 450560 byte (sekitar 440 KB) adalah ukuran tipikal core dump program kecil. Perintah file mengonfirmasi formatnya ELF core file untuk arsitektur x86-64, lengkap dengan UID dan GID proses pemilik.

Inspeksi Core dengan readelf #

Core dump adalah file ELF dengan tipe ET_CORE. Kita bisa memeriksa isinya menggunakan readelf yang tersedia di hampir semua sistem Linux. Catatan ELF (notes) menyimpan metadata penting seperti sinyal yang menyebabkan crash dan daftar file yang dipetakan ke memori.

bash
readelf --notes --wide /tmp/core | head -10

Output yang Dihasilkan:

text
Displaying notes found at file offset 0x000005b8 with length 0x000034b0:
  Owner                Data size        Description
  CORE                 0x00000150       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000150       NT_AUXV (auxiliary vector)
  CORE                 0x0000037b       NT_FILE (mapped files)

Mari kita uraikan tipe catatan utama pada output readelf di atas:

  1. NT_PRSTATUS: berisi struktur prstatus yang menyimpan nilai register CPU (RIP, RSP, RBP, dst.) pada saat crash. Catatan ini adalah sumber utama alamat instruksi yang menyebabkan crash.
  2. NT_PRPSINFO: informasi proses seperti PID, PPID, command line, dan nama user.
  3. NT_SIGINFO: struktur siginfo_t yang menjelaskan sinyal: nomor sinyal (SIGSEGV = 11), kode (SEGV_MAPERR = akses ke halaman tidak terpetakan), dan alamat yang diakses.
  4. NT_AUXV: vektor auxiliary yang dipakai dynamic linker saat proses dimuat.
  5. NT_FILE: daftar file yang dipetakan ke memori—binary utama, shared library (libc.so.6, ld-linux.so.2), beserta alamat awal dan akhir tiap mapping.

Analisis dengan addr2line #

addr2line menerjemahkan alamat instruksi menjadi nama fungsi dan baris kode sumber. Tool ini memerlukan binary yang dikompilasi dengan -g. Untuk menemukan alamat instruksi yang menyebabkan crash, kita bisa memakai objdump -d pada binary.

bash
objdump -d /tmp/test_core_dump | grep -A8 '<baca_node_value>'

Output yang Dihasilkan:

text
0000000000001194 <baca_node_value>:
    1194:       55                      push   %rbp
    1195:       48 89 e5                mov    %rsp,%rbp
    1198:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
    119c:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    11a0:       8b 00                   mov    (%rax),%eax
    11a2:       5d                      pop    %rbp
    11a3:       c3                      ret

Instruksi pada alamat 0x11a0 adalah mov (%rax),%eax— instruksi yang membaca 4 byte dari alamat di register RAX. Karena RAX berisi 0 (NULL), instruksi inilah yang menyebabkan SIGSEGV. Mari konversi alamat tersebut ke baris sumber:

bash
addr2line -e /tmp/test_core_dump -f 0x11a0

Output yang Dihasilkan:

text
baca_node_value
/tmp/test_core_dump.c:17

Hasil addr2line menunjukkan fungsi baca_node_value pada baris 17 file /tmp/test_core_dump.c. Baris 17 adalah return n->value;—tepat baris yang melakukan dereference n->value ketika n == NULL.

Analisis dengan gdb (Konseptual) #

Cara paling lengkap menganalisis core dump adalah dengan gdb. Sintaks dasarnya adalah gdb <binary> <core-file>. gdb memuat kedua file, lalu memberi prompt untuk inspeksi. Perintah bt langsung menampilkan backtrace lengkap tanpa perlu mengulang eksekusi.

bash
gdb /tmp/test_core_dump /tmp/core

Output yang Dihasilkan (format resmi gdb):

text
GNU gdb (Debian 15.x) 15.x
...
Reading symbols from /tmp/test_core_dump...
[New LWP 9027]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `/tmp/test_core_dump'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000... in baca_node_value (n=0x0) at /tmp/test_core_dump.c:17
17          return n->value;
[Current thread is 1 (LWP 9027)]
(gdb)

Mari kita bedah elemen kunci pada output gdb di atas:

  1. [New LWP 9027]: gdb menemukan satu Light Weight Process (thread) dengan ID 9027. LWP adalah ID thread level kernel. Untuk program single-threaded seperti contoh ini, hanya satu LWP.
  2. Core was generated by '/tmp/test_core_dump': gdb menampilkan command line yang dipakai menjalankan program. Informasi ini berasal dari catatan NT_PRPSINFO di core file.
  3. Program terminated with signal SIGSEGV, Segmentation fault.: sinyal yang menyebabkan crash. gdb menampilkan nama sinyal dan deskripsi singkat.
  4. #0 0x0000... in baca_node_value (n=0x0) at /tmp/test_core_dump.c:17: frame paling dalam (#0) menunjukkan fungsi baca_node_value menerima argumen n=0x0 (NULL) dan berhenti di baris 17. Penyebab crash jelas: dereferensi n->value ketika n == NULL.

Setelah prompt (gdb), kita bisa memakai perintah interaktif seperti bt full (backtrace dengan semua variabel lokal), info locals, frame 1, atau print *head untuk inspeksi mendalam.

Strategi Penanganan Core Dump di Produksi #

Pada server produksi, core dump bisa sangat besar (gigabyte untuk proses dengan memori besar) dan menumpuk cepat. Strategi praktis:

  1. Pakai systemd-coredump yang mengompresi core dengan zstd dan menyimpannya di /var/lib/systemd/coredump/ dengan rotasi otomatis.
  2. Batas ukuran via ulimit -c <bytes> untuk membatasi core maksimum (misalnya 100 MB—cukup untuk backtrace, tidak cukup untuk memori penuh).
  3. Compile dengan -g dan simpan binary yang dipakai produksi untuk dianalisis kemudian. Binary tanpa simbol debug membuat core dump hampir tidak bisa dianalisis.
  4. Pasang ASan/UBSan sebagai opsi pertama untuk bug memori—core dump adalah opsi fallback untuk kasus yang lolos sanitizer.