Core Dump 완벽 가이드 | 생성·분석·디버깅 실전 총정리

Core Dump 완벽 가이드 | 생성·분석·디버깅 실전 총정리

이 글의 핵심

Core Dump 생성 방법, GDB로 분석하는 법, Segmentation Fault 디버깅, 실무 사례까지. ulimit 설정, 심볼 파일, 백트레이스 분석으로 프로덕션 크래시 문제를 해결하는 완벽 가이드.

들어가며: Core Dump가 필요한 이유

프로덕션 환경에서 프로그램이 갑자기 크래시하면 어떻게 디버깅할까요? 재현이 어려운 버그, 간헐적인 Segmentation Fault, 메모리 손상 문제를 해결하려면 Core Dump가 필수입니다.

Core Dump의 가치:

  • 크래시 시점의 완전한 메모리 스냅샷
  • 모든 변수 값과 스택 트레이스
  • 재현 없이 사후 분석 가능
  • 프로덕션 환경의 실제 데이터

이 글에서 다룰 내용:

  • Core Dump 생성 설정
  • GDB로 Core Dump 분석
  • 실전 디버깅 시나리오
  • 자동화 및 모니터링

목차

  1. Core Dump 기본 개념
  2. Core Dump 생성 설정
  3. GDB로 Core Dump 분석
  4. 실전 디버깅 시나리오
  5. 자동화 및 수집
  6. 프로덕션 환경 설정
  7. 고급 분석 기법
  8. 문제 해결

1. Core Dump 기본 개념

Core Dump란?

Core Dump는 프로그램이 비정상 종료(크래시)될 때 메모리의 내용을 파일로 저장한 것입니다.

flowchart TB
    Program[프로그램 실행] --> Crash[크래시 발생<br/>SIGSEGV, SIGABRT 등]
    
    Crash --> Kernel[커널이 신호 수신]
    
    Kernel --> Check{Core Dump<br/>활성화?}
    
    Check -->|Yes| Dump[메모리 덤프 생성<br/>core.12345]
    Check -->|No| Exit[프로그램 종료]
    
    Dump --> File[Core Dump 파일<br/>- 스택<br/>- 힙<br/>- 레지스터<br/>- 변수 값]
    
    File --> Debug[GDB로 분석]

Core Dump에 포함된 정보

1. 스택 메모리 (Stack)
   - 함수 호출 스택
   - 지역 변수
   - 함수 인자

2. 힙 메모리 (Heap)
   - 동적 할당된 메모리
   - malloc/new로 생성된 객체

3. 레지스터 상태
   - PC (Program Counter)
   - SP (Stack Pointer)
   - 범용 레지스터

4. 전역 변수
   - 정적 변수
   - 전역 객체

5. 공유 라이브러리 매핑
   - 로드된 .so 파일 목록
   - 메모리 주소 매핑

크래시를 일으키는 신호

// Core Dump를 생성하는 신호들
SIGQUIT    // Quit (Ctrl+\)
SIGILL     // Illegal Instruction
SIGABRT    // Abort (assert 실패)
SIGFPE     // Floating Point Exception (0으로 나누기)
SIGSEGV    // Segmentation Fault (잘못된 메모리 접근)
SIGBUS     // Bus Error (정렬 오류)
SIGTRAP    // Trace/Breakpoint Trap
SIGSYS     // Bad System Call

2. Core Dump 생성 설정

ulimit 설정

# 현재 Core Dump 크기 제한 확인
ulimit -c
# 0 → Core Dump 비활성화
# unlimited → 제한 없음

# Core Dump 활성화 (현재 세션)
ulimit -c unlimited

# 영구 설정 (모든 사용자)
echo "* soft core unlimited" | sudo tee -a /etc/security/limits.conf
echo "* hard core unlimited" | sudo tee -a /etc/security/limits.conf

# 특정 사용자만
echo "myuser soft core unlimited" | sudo tee -a /etc/security/limits.conf

# systemd 서비스의 경우
# /etc/systemd/system/myapp.service
[Service]
LimitCORE=infinity

Core Dump 저장 위치 설정

# 현재 설정 확인
cat /proc/sys/kernel/core_pattern
# 기본값: core (현재 디렉토리에 'core' 파일 생성)

# 커스텀 패턴 설정
sudo sysctl -w kernel.core_pattern=/var/crash/core.%e.%p.%t

# 영구 설정
echo "kernel.core_pattern=/var/crash/core.%e.%p.%t" | sudo tee -a /etc/sysctl.conf

# 패턴 변수:
# %e - 실행 파일 이름
# %p - PID
# %t - 타임스탬프 (Unix time)
# %s - 신호 번호
# %u - UID
# %g - GID
# %h - 호스트명

# 예시: core.myapp.12345.1234567890

systemd-coredump 사용 (권장)

# systemd-coredump 설치 (Ubuntu)
sudo apt install systemd-coredump

# 설정
sudo nano /etc/systemd/coredump.conf

[Coredump]
Storage=external
Compress=yes
ProcessSizeMax=2G
ExternalSizeMax=2G
MaxUse=10G

# Core Dump 저장 위치
# /var/lib/systemd/coredump/

# Core Dump 목록 확인
coredumpctl list

# 특정 Core Dump 정보
coredumpctl info <PID>

# Core Dump 추출
coredumpctl dump <PID> -o core.dump

# GDB로 바로 분석
coredumpctl debug <PID>

3. GDB로 Core Dump 분석

기본 분석 흐름

# 1. 디버그 심볼과 함께 컴파일
g++ -g -O0 myapp.cpp -o myapp

# 2. Core Dump 생성되도록 설정
ulimit -c unlimited

# 3. 프로그램 실행 (크래시 발생)
./myapp
# Segmentation fault (core dumped)

# 4. GDB로 Core Dump 열기
gdb ./myapp core.12345

# 또는 systemd-coredump 사용
coredumpctl debug 12345

GDB 기본 명령어

# Core Dump 로드 후

# 1. 백트레이스 (스택 트레이스)
(gdb) bt
#0  0x00005555555551a9 in crash_function () at myapp.cpp:10
#1  0x00005555555551c8 in main () at myapp.cpp:15

# 상세 백트레이스
(gdb) bt full
#0  0x00005555555551a9 in crash_function () at myapp.cpp:10
        ptr = 0x0
        value = 42
#1  0x00005555555551c8 in main () at myapp.cpp:15
        argc = 1
        argv = 0x7fffffffe0a8

# 2. 특정 프레임으로 이동
(gdb) frame 0
(gdb) frame 1

# 3. 소스 코드 확인
(gdb) list
5       void crash_function() {
6           int* ptr = nullptr;
7           int value = 42;
8           
9           // Segmentation Fault!
10          *ptr = value;
11      }

# 4. 변수 값 확인
(gdb) print ptr
$1 = (int *) 0x0

(gdb) print value
$2 = 42

# 5. 모든 지역 변수
(gdb) info locals
ptr = 0x0
value = 42

# 6. 레지스터 상태
(gdb) info registers
rax            0x0                 0
rbx            0x555555557d60      93824992247136
rsp            0x7fffffffddd0      0x7fffffffddd0
rip            0x5555555551a9      0x5555555551a9 <crash_function()+15>

# 7. 메모리 내용 확인
(gdb) x/10x $rsp
0x7fffffffddd0: 0xffffddf0      0x00007fff      0x555551c8      0x00005555

# 8. 스레드 정보
(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0x7ffff7fc1740 (LWP 12345) crash_function () at myapp.cpp:10
  2    Thread 0x7ffff77c0700 (LWP 12346) 0x00007ffff7bc8e2d in poll ()

# 9. 특정 스레드로 전환
(gdb) thread 2
(gdb) bt

실전 예제 1: Null Pointer Dereference

// crash_null.cpp
#include <iostream>

void process_data(int* data) {
    std::cout << "Processing: " << *data << std::endl;  // 크래시!
}

int main() {
    int* ptr = nullptr;
    process_data(ptr);
    return 0;
}
# 컴파일 (디버그 심볼 포함)
g++ -g -O0 crash_null.cpp -o crash_null

# 실행
./crash_null
# Segmentation fault (core dumped)

# GDB 분석
gdb ./crash_null core

(gdb) bt
#0  0x00005555555551b2 in process_data (data=0x0) at crash_null.cpp:4
#1  0x00005555555551e1 in main () at crash_null.cpp:9

(gdb) frame 0
(gdb) print data
$1 = (int *) 0x0

(gdb) list
1       #include <iostream>
2
3       void process_data(int* data) {
4           std::cout << "Processing: " << *data << std::endl;  // 여기서 크래시!
5       }

# 원인: data가 nullptr
# 해결: nullptr 체크 추가

실전 예제 2: Buffer Overflow

// crash_overflow.cpp
#include <cstring>

void copy_string(const char* src) {
    char buffer[10];
    strcpy(buffer, src);  // 버퍼 오버플로우!
    
    printf("Copied: %s\n", buffer);
}

int main() {
    copy_string("This is a very long string that will overflow");
    return 0;
}
# 컴파일
g++ -g -O0 -fno-stack-protector crash_overflow.cpp -o crash_overflow

# 실행
./crash_overflow
# * stack smashing detected *: terminated
# Aborted (core dumped)

# GDB 분석
gdb ./crash_overflow core

(gdb) bt
#0  0x00007ffff7a42387 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff7a43a78 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007ffff7a851a6 in __libc_message () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007ffff7b27d5c in __fortify_fail () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007ffff7b27d20 in __stack_chk_fail () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x000055555555521e in copy_string (src=0x555555556004 "This is a very long string...") at crash_overflow.cpp:8
#6  0x0000555555555239 in main () at crash_overflow.cpp:12

(gdb) frame 5
(gdb) print buffer
$1 = "This is a \000ery long string that will overflow"
# 버퍼 크기 10바이트를 초과함

(gdb) print src
$2 = 0x555555556004 "This is a very long string that will overflow"

# 해결: strncpy 사용 또는 std::string 사용

4. 실전 디버깅 시나리오

시나리오 1: Double Free

// crash_double_free.cpp
#include <cstdlib>
#include <iostream>

int main() {
    int* ptr = new int(42);
    
    std::cout << "Value: " << *ptr << std::endl;
    
    delete ptr;
    
    // Double free!
    delete ptr;
    
    return 0;
}
# 컴파일
g++ -g -O0 crash_double_free.cpp -o crash_double_free

# 실행
./crash_double_free
# Value: 42
# free(): double free detected in tcache 2
# Aborted (core dumped)

# GDB 분석
gdb ./crash_double_free core

(gdb) bt
#0  0x00007ffff7a42387 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff7a43a78 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007ffff7a8524a in __libc_message () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007ffff7a8c89c in malloc_printerr () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007ffff7a8e5fc in _int_free () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x0000555555555234 in main () at crash_double_free.cpp:12

(gdb) frame 5
(gdb) list
7       int* ptr = new int(42);
8       
9       std::cout << "Value: " << *ptr << std::endl;
10      
11      delete ptr;
12      
13      // Double free!
14      delete ptr;  // 여기서 크래시!

(gdb) print ptr
$1 = (int *) 0x555555559eb0  # 이미 해제된 주소

# 해결: delete 후 ptr = nullptr 설정

시나리오 2: Use After Free

// crash_use_after_free.cpp
#include <iostream>
#include <vector>

class MyClass {
public:
    int value;
    MyClass(int v) : value(v) {}
    void print() { std::cout << "Value: " << value << std::endl; }
};

int main() {
    MyClass* obj = new MyClass(42);
    
    obj->print();
    
    delete obj;
    
    // Use after free!
    obj->print();  // 크래시 또는 이상한 값
    
    return 0;
}
# AddressSanitizer로 컴파일 (권장)
g++ -g -O0 -fsanitize=address crash_use_after_free.cpp -o crash_use_after_free

# 실행
./crash_use_after_free
# Value: 42
# =================================================================
# ==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010
# READ of size 4 at 0x602000000010 thread T0
#     #0 0x555555555289 in MyClass::print() crash_use_after_free.cpp:9
#     #1 0x5555555552f8 in main crash_use_after_free.cpp:18

# Core Dump 분석 (ASan 없이 컴파일한 경우)
gdb ./crash_use_after_free core

(gdb) bt
#0  0x0000555555555289 in MyClass::print (this=0x555555559eb0) at crash_use_after_free.cpp:9
#1  0x00005555555552f8 in main () at crash_use_after_free.cpp:18

(gdb) frame 0
(gdb) print this
$1 = (MyClass * const) 0x555555559eb0

(gdb) print *this
$2 = {value = 0}  # 또는 쓰레기 값

(gdb) print this->value
$3 = 0  # 메모리가 이미 해제됨

# 해결: delete 후 obj = nullptr 설정

시나리오 3: Stack Overflow

// crash_stack_overflow.cpp
#include <iostream>

void recursive_function(int depth) {
    char buffer[10000];  // 큰 지역 변수
    
    std::cout << "Depth: " << depth << std::endl;
    
    // 무한 재귀
    recursive_function(depth + 1);
}

int main() {
    recursive_function(0);
    return 0;
}
# 컴파일
g++ -g -O0 crash_stack_overflow.cpp -o crash_stack_overflow

# 실행
./crash_stack_overflow
# Depth: 0
# Depth: 1
# ...
# Depth: 850
# Segmentation fault (core dumped)

# GDB 분석
gdb ./crash_stack_overflow core

(gdb) bt
#0  0x0000555555555189 in recursive_function (depth=851) at crash_stack_overflow.cpp:4
#1  0x00005555555551c4 in recursive_function (depth=850) at crash_stack_overflow.cpp:9
#2  0x00005555555551c4 in recursive_function (depth=849) at crash_stack_overflow.cpp:9
...
#850 0x00005555555551c4 in recursive_function (depth=1) at crash_stack_overflow.cpp:9
#851 0x00005555555551c4 in recursive_function (depth=0) at crash_stack_overflow.cpp:9
#852 0x00005555555551d9 in main () at crash_stack_overflow.cpp:13

# 스택 프레임 개수 확인
(gdb) bt | wc -l
853

# 각 프레임의 buffer 크기
(gdb) print sizeof(buffer)
$1 = 10000

# 총 스택 사용량: 853 × 10,000 ≈ 8.5MB
# 스택 크기 제한 초과

# 스택 크기 확인
ulimit -s
# 8192 (8MB)

# 해결: 재귀 깊이 제한 또는 힙 사용

시나리오 4: 멀티스레드 크래시

// crash_thread.cpp
#include <iostream>
#include <thread>
#include <vector>

int shared_counter = 0;

void increment() {
    for (int i = 0; i < 100000; i++) {
        shared_counter++;  // Race condition!
    }
}

int main() {
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(increment);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Counter: " << shared_counter << std::endl;
    
    // 메모리 손상으로 인한 크래시 가능
    return 0;
}
# ThreadSanitizer로 컴파일
g++ -g -O0 -fsanitize=thread crash_thread.cpp -o crash_thread -pthread

# 실행
./crash_thread
# ==================
# WARNING: ThreadSanitizer: data race (pid=12345)
#   Write of size 4 at 0x555555558010 by thread T2:
#     #0 increment() crash_thread.cpp:8
#   Previous write of size 4 at 0x555555558010 by thread T1:
#     #0 increment() crash_thread.cpp:8

# Core Dump 분석 (TSan 없이)
gdb ./crash_thread core

(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0x7ffff7fc1740 (LWP 12345) 0x00007ffff7bc8e2d in poll ()
  2    Thread 0x7ffff77c0700 (LWP 12346) increment () at crash_thread.cpp:8
  3    Thread 0x7ffff6fbf700 (LWP 12347) increment () at crash_thread.cpp:8

# 각 스레드의 백트레이스 확인
(gdb) thread apply all bt

Thread 1 (Thread 0x7ffff7fc1740 (LWP 12345)):
#0  0x00007ffff7bc8e2d in poll () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000555555555456 in main () at crash_thread.cpp:22

Thread 2 (Thread 0x7ffff77c0700 (LWP 12346)):
#0  increment () at crash_thread.cpp:8
#1  0x00007ffff7e8e6df in execute_native_thread_routine ()

# 해결: std::mutex 사용

5. 자동화 및 수집

Core Dump 자동 수집 스크립트

#!/bin/bash
# /usr/local/bin/core-collector.sh

CORE_DIR="/var/crash"
MAX_CORES=50
NOTIFY_EMAIL="[email protected]"

# Core Dump 정보 추출
EXECUTABLE="$1"
PID="$2"
TIMESTAMP="$3"
SIGNAL="$4"

CORE_FILE="$CORE_DIR/core.$EXECUTABLE.$PID.$TIMESTAMP"

# 디렉토리 생성
mkdir -p "$CORE_DIR"

# Core Dump 저장 (stdin으로 전달됨)
cat > "$CORE_FILE"

# 압축
gzip "$CORE_FILE"
CORE_FILE="$CORE_FILE.gz"

# 메타데이터 저장
cat > "$CORE_FILE.info" <<EOF
Executable: $EXECUTABLE
PID: $PID
Timestamp: $(date -d @$TIMESTAMP)
Signal: $SIGNAL
Hostname: $(hostname)
User: $(id -un)
Kernel: $(uname -r)
EOF

# 백트레이스 추출 (디버그 심볼 있는 경우)
if [ -f "/usr/bin/$EXECUTABLE" ]; then
    gdb -batch -ex "bt" -ex "quit" "/usr/bin/$EXECUTABLE" <(gunzip -c "$CORE_FILE") \
        > "$CORE_FILE.backtrace" 2>&1
fi

# 오래된 Core Dump 정리
find "$CORE_DIR" -name "core.*" -mtime +7 -delete

# 개수 제한
CORE_COUNT=$(find "$CORE_DIR" -name "core.*.gz" | wc -l)
if [ $CORE_COUNT -gt $MAX_CORES ]; then
    find "$CORE_DIR" -name "core.*.gz" -type f -printf '%T+ %p\n' | \
        sort | head -n $(($CORE_COUNT - $MAX_CORES)) | \
        cut -d' ' -f2- | xargs rm -f
fi

# 알림 전송
echo "Core dump collected: $CORE_FILE" | \
    mail -s "Core Dump Alert: $EXECUTABLE" "$NOTIFY_EMAIL"

# 로그
logger -t core-collector "Core dump: $EXECUTABLE (PID: $PID, Signal: $SIGNAL)"
# 스크립트 등록
sudo chmod +x /usr/local/bin/core-collector.sh

# core_pattern 설정
sudo sysctl -w kernel.core_pattern="|/usr/local/bin/core-collector.sh %e %p %t %s"

자동 분석 스크립트

#!/usr/bin/env python3
"""
Core Dump 자동 분석 도구
"""
import subprocess
import sys
import re
import json
from pathlib import Path

class CoreDumpAnalyzer:
    def __init__(self, executable, core_file):
        self.executable = executable
        self.core_file = core_file
        self.analysis = {}
    
    def analyze(self):
        """Core Dump 분석"""
        print(f"🔍 Analyzing {self.core_file}...")
        
        # 1. 백트레이스
        self.analysis['backtrace'] = self.get_backtrace()
        
        # 2. 크래시 위치
        self.analysis['crash_location'] = self.get_crash_location()
        
        # 3. 스레드 정보
        self.analysis['threads'] = self.get_threads()
        
        # 4. 레지스터 상태
        self.analysis['registers'] = self.get_registers()
        
        # 5. 메모리 맵
        self.analysis['memory_map'] = self.get_memory_map()
        
        return self.analysis
    
    def get_backtrace(self):
        """백트레이스 추출"""
        cmd = [
            'gdb', '-batch',
            '-ex', 'bt',
            '-ex', 'quit',
            self.executable,
            self.core_file
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result.stdout
    
    def get_crash_location(self):
        """크래시 위치 파싱"""
        bt = self.get_backtrace()
        
        # #0 프레임 추출
        match = re.search(r'#0\s+(.+?) at (.+?):(\d+)', bt)
        if match:
            return {
                'function': match.group(1),
                'file': match.group(2),
                'line': int(match.group(3))
            }
        
        return None
    
    def get_threads(self):
        """스레드 정보"""
        cmd = [
            'gdb', '-batch',
            '-ex', 'info threads',
            '-ex', 'quit',
            self.executable,
            self.core_file
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        threads = []
        for line in result.stdout.split('\n'):
            if 'Thread' in line:
                threads.append(line.strip())
        
        return threads
    
    def get_registers(self):
        """레지스터 상태"""
        cmd = [
            'gdb', '-batch',
            '-ex', 'info registers',
            '-ex', 'quit',
            self.executable,
            self.core_file
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result.stdout
    
    def get_memory_map(self):
        """메모리 맵"""
        cmd = [
            'gdb', '-batch',
            '-ex', 'info proc mappings',
            '-ex', 'quit',
            self.executable,
            self.core_file
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result.stdout
    
    def generate_report(self):
        """분석 리포트 생성"""
        report = []
        report.append("=" * 70)
        report.append("CORE DUMP ANALYSIS REPORT")
        report.append("=" * 70)
        
        # 크래시 위치
        if self.analysis.get('crash_location'):
            loc = self.analysis['crash_location']
            report.append(f"\n🔴 CRASH LOCATION:")
            report.append(f"   Function: {loc['function']}")
            report.append(f"   File: {loc['file']}:{loc['line']}")
        
        # 백트레이스
        report.append(f"\n📚 BACKTRACE:")
        report.append(self.analysis['backtrace'])
        
        # 스레드
        if self.analysis.get('threads'):
            report.append(f"\n🧵 THREADS ({len(self.analysis['threads'])}):")
            for thread in self.analysis['threads']:
                report.append(f"   {thread}")
        
        return '\n'.join(report)

# 사용
if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: analyze_core.py <executable> <core_file>")
        sys.exit(1)
    
    analyzer = CoreDumpAnalyzer(sys.argv[1], sys.argv[2])
    analyzer.analyze()
    
    report = analyzer.generate_report()
    print(report)
    
    # JSON으로 저장
    with open('analysis.json', 'w') as f:
        json.dump(analyzer.analysis, f, indent=2)
    
    print("\n✅ Analysis saved to analysis.json")

6. 프로덕션 환경 설정

systemd 서비스 설정

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp

# Core Dump 설정
LimitCORE=infinity
LimitNOFILE=65536

# 환경 변수
Environment="NODE_ENV=production"

# 실행
ExecStart=/opt/myapp/bin/myapp

# 재시작 정책
Restart=on-failure
RestartSec=5s

# 로깅
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Docker 컨테이너에서 Core Dump

# Dockerfile
FROM ubuntu:22.04

# Core Dump 활성화
RUN echo "* soft core unlimited" >> /etc/security/limits.conf && \
    echo "* hard core unlimited" >> /etc/security/limits.conf

# 디버그 도구 설치
RUN apt-get update && apt-get install -y gdb

# 애플리케이션
COPY myapp /usr/local/bin/
RUN chmod +x /usr/local/bin/myapp

CMD ["myapp"]
# Docker 실행 (Core Dump 활성화)
docker run -d \
  --name myapp \
  --ulimit core=-1 \
  -v /var/crash:/var/crash \
  myapp:latest

# Core Dump 확인
docker exec myapp ls -lh /var/crash/

# Core Dump 추출
docker cp myapp:/var/crash/core.12345 ./

# 컨테이너 내에서 GDB 실행
docker exec -it myapp gdb /usr/local/bin/myapp /var/crash/core.12345

Kubernetes에서 Core Dump

# pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: myapp
    image: myapp:latest
    
    # Core Dump 설정
    securityContext:
      capabilities:
        add:
        - SYS_PTRACE
    
    # 볼륨 마운트
    volumeMounts:
    - name: core-dumps
      mountPath: /var/crash
    
    # 리소스 제한
    resources:
      limits:
        memory: "2Gi"
        cpu: "1"
  
  # 호스트 경로 마운트
  volumes:
  - name: core-dumps
    hostPath:
      path: /var/crash
      type: DirectoryOrCreate
  
  # Init 컨테이너로 ulimit 설정
  initContainers:
  - name: setup-core
    image: busybox
    command: ['sh', '-c', 'ulimit -c unlimited']
    securityContext:
      privileged: true

7. 고급 분석 기법

메모리 덤프 분석

# GDB에서 메모리 내용 확인

# 1. 특정 주소의 메모리 (16진수)
(gdb) x/10x 0x7fffffffddd0
0x7fffffffddd0: 0xffffddf0  0x00007fff  0x555551c8  0x00005555
0x7fffffffdde0: 0x00000000  0x00000001  0xffffe0a8  0x00007fff

# 2. 문자열로 해석
(gdb) x/s 0x555555556004
0x555555556004: "Hello, World!"

# 3. 명령어 (어셈블리)
(gdb) x/10i $rip
=> 0x5555555551a9 <crash_function+15>:  mov    %eax,(%rdx)
   0x5555555551ab <crash_function+17>:  nop
   0x5555555551ac <crash_function+18>:  pop    %rbp

# 4. 배열 내용
(gdb) print array[0]@10
$1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

# 5. 구조체 멤버
(gdb) print *my_struct
$2 = {
  id = 42,
  name = 0x555555559eb0 "Test",
  value = 3.14
}

# 6. 포인터 체인 따라가기
(gdb) print *node
$3 = {data = 10, next = 0x555555559ec0}

(gdb) print *node->next
$4 = {data = 20, next = 0x555555559ed0}

(gdb) print *node->next->next
$5 = {data = 30, next = 0x0}

조건부 분석

# 특정 조건의 스택 프레임만 출력
(gdb) bt full
# 출력이 너무 많으면

# Python 스크립트로 필터링
(gdb) python
import gdb

frame = gdb.newest_frame()
while frame:
    func = frame.name()
    if func and 'process' in func:
        print(f"Frame: {func}")
        # 지역 변수 출력
        block = frame.block()
        for symbol in block:
            if symbol.is_variable:
                print(f"  {symbol.name} = {symbol.value(frame)}")
    frame = frame.older()
end

Core Dump 비교

# 두 개의 Core Dump 비교 (재현 가능한 크래시)
#!/bin/bash

CORE1="core.12345"
CORE2="core.12346"
EXECUTABLE="./myapp"

# 백트레이스 추출
gdb -batch -ex "bt" -ex "quit" "$EXECUTABLE" "$CORE1" > bt1.txt
gdb -batch -ex "bt" -ex "quit" "$EXECUTABLE" "$CORE2" > bt2.txt

# 차이점 확인
diff -u bt1.txt bt2.txt

# 같은 위치에서 크래시하는지 확인
if diff -q bt1.txt bt2.txt > /dev/null; then
    echo "✅ Same crash location (reproducible)"
else
    echo "⚠️  Different crash locations"
fi

8. 문제 해결

Core Dump가 생성되지 않을 때

# 1. ulimit 확인
ulimit -c
# 0이면 비활성화됨

# 활성화
ulimit -c unlimited

# 2. core_pattern 확인
cat /proc/sys/kernel/core_pattern
# |/usr/share/apport/apport %p %s %c %d %P (Ubuntu)
# core (기본값)

# 3. 디스크 공간 확인
df -h /var/crash

# 4. 권한 확인
ls -ld /var/crash
# drwxrwxrwt 2 root root 4096 ...

# 5. suid 프로그램 (보안상 Core Dump 안 됨)
ls -l myapp
# -rwsr-xr-x (s 플래그 있으면 suid)

# suid 제거
sudo chmod u-s myapp

# 6. 프로세스가 chroot 환경인 경우
# Core Dump가 chroot 내부에 생성됨

# 7. SELinux/AppArmor 확인
sudo getenforce  # SELinux
sudo aa-status   # AppArmor

Core Dump가 너무 클 때

# Core Dump 크기 제한
ulimit -c 1048576  # 1GB

# 또는 /etc/security/limits.conf
* soft core 1048576
* hard core 1048576

# 특정 메모리 영역만 덤프 (Linux)
# /proc/PID/coredump_filter
# 비트 플래그:
# bit 0: anonymous private memory
# bit 1: anonymous shared memory
# bit 2: file-backed private memory
# bit 3: file-backed shared memory
# bit 4: ELF header pages
# bit 5: private huge pages
# bit 6: shared huge pages

# 예: 스택과 힙만 덤프
echo 0x33 > /proc/self/coredump_filter

심볼 파일 없을 때

# 디버그 심볼 분리 (배포 시)
# 1. 디버그 심볼과 함께 컴파일
g++ -g myapp.cpp -o myapp

# 2. 심볼 분리
objcopy --only-keep-debug myapp myapp.debug
strip --strip-debug --strip-unneeded myapp

# 3. 디버그 링크 추가
objcopy --add-gnu-debuglink=myapp.debug myapp

# 배포: myapp (작은 크기)
# 보관: myapp.debug (디버그 심볼)

# GDB 사용 시 자동으로 myapp.debug 로드
gdb ./myapp core

# 또는 수동 로드
(gdb) symbol-file myapp.debug

실전 도구

coredumpctl (systemd)

# Core Dump 목록
coredumpctl list

# 최근 Core Dump
coredumpctl list --since today

# 특정 프로그램
coredumpctl list myapp

# 상세 정보
coredumpctl info 12345

# Core Dump 추출
coredumpctl dump 12345 -o core.dump

# GDB로 바로 분석
coredumpctl debug 12345

# 삭제
coredumpctl remove 12345

crash (커널 크래시 분석)

# 커널 크래시 덤프 분석 (kdump)
sudo apt install crash kexec-tools

# vmcore 분석
crash /usr/lib/debug/boot/vmlinux-$(uname -r) /var/crash/vmcore

# crash 명령어
crash> bt              # 백트레이스
crash> ps              # 프로세스 목록
crash> log             # 커널 로그
crash> files          # 열린 파일
crash> vm             # 가상 메모리

gcore (실행 중인 프로세스 덤프)

# 실행 중인 프로세스의 Core Dump 생성
# (프로세스 중단 없이)

# PID 확인
ps aux | grep myapp

# Core Dump 생성
gcore 12345
# Saved corefile core.12345

# 또는 GDB로
gdb -p 12345
(gdb) generate-core-file
(gdb) detach
(gdb) quit

# 프로세스는 계속 실행됨

고급 GDB 기법

GDB 스크립트

# analyze.gdb
set pagination off
set logging file analysis.txt
set logging on

echo \n=== BACKTRACE ===\n
bt full

echo \n=== THREADS ===\n
info threads
thread apply all bt

echo \n=== REGISTERS ===\n
info registers

echo \n=== LOCALS ===\n
info locals

echo \n=== SHARED LIBRARIES ===\n
info sharedlibrary

set logging off
quit
# 스크립트 실행
gdb -batch -x analyze.gdb ./myapp core.12345

# 결과 확인
cat analysis.txt

GDB Python API

# gdb_script.py
import gdb

class AnalyzeCommand(gdb.Command):
    """Analyze core dump"""
    
    def __init__(self):
        super(AnalyzeCommand, self).__init__("analyze", gdb.COMMAND_USER)
    
    def invoke(self, arg, from_tty):
        # 현재 프레임
        frame = gdb.selected_frame()
        
        print(f"Function: {frame.name()}")
        print(f"PC: 0x{frame.pc():x}")
        
        # 지역 변수
        block = frame.block()
        print("\nLocal variables:")
        for symbol in block:
            if symbol.is_variable:
                try:
                    value = symbol.value(frame)
                    print(f"  {symbol.name} = {value}")
                except:
                    pass
        
        # 백트레이스
        print("\nBacktrace:")
        i = 0
        f = gdb.newest_frame()
        while f:
            print(f"  #{i} {f.name()} at {f.find_sal().symtab}:{f.find_sal().line}")
            f = f.older()
            i += 1

AnalyzeCommand()
# GDB에서 Python 스크립트 로드
gdb ./myapp core.12345
(gdb) source gdb_script.py
(gdb) analyze

메모리 손상 디버깅

Heap Corruption 분석

// crash_heap_corruption.cpp
#include <iostream>
#include <cstring>

struct Node {
    int data;
    Node* next;
};

int main() {
    Node* head = new Node{10, nullptr};
    Node* second = new Node{20, nullptr};
    head->next = second;
    
    // 버퍼 오버플로우로 메모리 손상
    char* buffer = new char[10];
    strcpy(buffer, "This is a very long string");  // 오버플로우!
    
    // 나중에 크래시 (손상된 메모리 접근)
    std::cout << head->next->data << std::endl;
    
    delete[] buffer;
    delete second;
    delete head;
    
    return 0;
}
# Valgrind로 메모리 오류 감지
valgrind --leak-check=full --track-origins=yes ./crash_heap_corruption

# ==12345== Invalid write of size 1
# ==12345==    at 0x4C2FB7F: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
# ==12345==    by 0x108A2B: main (crash_heap_corruption.cpp:16)

# AddressSanitizer로 컴파일
g++ -g -O0 -fsanitize=address crash_heap_corruption.cpp -o crash_heap_corruption

./crash_heap_corruption
# =================================================================
# ==12345==ERROR: AddressSanitizer: heap-buffer-overflow
# WRITE of size 28 at 0x602000000010 thread T0
#     #0 0x7ffff7b3a1b0 in strcpy
#     #1 0x555555555289 in main crash_heap_corruption.cpp:16

GDB로 힙 상태 확인

# malloc 디버깅 (glibc)
(gdb) set environment MALLOC_CHECK_ 2

# 힙 청크 확인
(gdb) print *(mchunkptr)0x555555559eb0

# 힙 통계
(gdb) call malloc_stats()

# 메모리 맵 확인
(gdb) info proc mappings

자동화된 크래시 리포팅

Sentry 통합

// sentry_example.cpp
#include <sentry.h>
#include <signal.h>

void signal_handler(int sig) {
    // Sentry에 크래시 리포트
    sentry_capture_event(sentry_value_new_message_event(
        SENTRY_LEVEL_FATAL,
        "crash",
        "Application crashed"
    ));
    
    // Core Dump 생성
    signal(sig, SIG_DFL);
    raise(sig);
}

int main() {
    // Sentry 초기화
    sentry_options_t* options = sentry_options_new();
    sentry_options_set_dsn(options, "https://[email protected]/project");
    sentry_options_set_release(options, "[email protected]");
    sentry_init(options);
    
    // 신호 핸들러 등록
    signal(SIGSEGV, signal_handler);
    signal(SIGABRT, signal_handler);
    
    // 애플리케이션 로직
    // ...
    
    sentry_close();
    return 0;
}

Breakpad (Google)

// breakpad_example.cpp
#include "client/linux/handler/exception_handler.h"

static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
                         void* context,
                         bool succeeded) {
    printf("✅ Minidump created: %s\n", descriptor.path());
    
    // 크래시 정보를 서버로 전송
    // upload_crash_report(descriptor.path());
    
    return succeeded;
}

int main() {
    google_breakpad::MinidumpDescriptor descriptor("/tmp/crashes");
    google_breakpad::ExceptionHandler eh(
        descriptor,
        nullptr,
        dumpCallback,
        nullptr,
        true,
        -1
    );
    
    // 애플리케이션 로직
    int* ptr = nullptr;
    *ptr = 42;  // 크래시
    
    return 0;
}

프로덕션 베스트 프랙티스

Core Dump 수집 파이프라인

flowchart TB
    Crash[애플리케이션 크래시] --> Generate[Core Dump 생성]
    
    Generate --> Collect[수집 스크립트<br/>- 압축<br/>- 메타데이터]
    
    Collect --> Upload[중앙 저장소 업로드<br/>S3, NFS 등]
    
    Upload --> Notify[알림 전송<br/>Slack, Email]
    
    Upload --> Analyze[자동 분석<br/>- 백트레이스<br/>- 크래시 위치]
    
    Analyze --> Report[리포트 생성<br/>Jira, GitHub Issue]
    
    Analyze --> Dedupe[중복 제거<br/>같은 크래시 그룹화]

설정 템플릿

# /etc/sysctl.d/50-coredump.conf
kernel.core_pattern=|/usr/local/bin/core-handler %e %p %t %s
kernel.core_pipe_limit=0
fs.suid_dumpable=2

# /etc/security/limits.d/core.conf
* soft core unlimited
* hard core unlimited

# /usr/local/bin/core-handler
#!/bin/bash
EXECUTABLE="$1"
PID="$2"
TIMESTAMP="$3"
SIGNAL="$4"

CORE_DIR="/var/crash"
CORE_FILE="$CORE_DIR/core.$EXECUTABLE.$PID.$TIMESTAMP.gz"

mkdir -p "$CORE_DIR"
gzip > "$CORE_FILE"

# S3 업로드
aws s3 cp "$CORE_FILE" "s3://my-crashes/$EXECUTABLE/"

# Slack 알림
curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
  -H 'Content-Type: application/json' \
  -d "{\"text\":\"🔴 Crash: $EXECUTABLE (PID: $PID)\"}"

모니터링 대시보드

#!/usr/bin/env python3
"""
Core Dump 모니터링 대시보드
"""
import os
import time
from pathlib import Path
from collections import defaultdict
import json

class CoreDumpMonitor:
    def __init__(self, crash_dir='/var/crash'):
        self.crash_dir = Path(crash_dir)
        self.stats = defaultdict(int)
    
    def scan(self):
        """Core Dump 파일 스캔"""
        cores = list(self.crash_dir.glob('core.*'))
        
        for core in cores:
            parts = core.name.split('.')
            if len(parts) >= 2:
                executable = parts[1]
                self.stats[executable] += 1
        
        return self.stats
    
    def generate_report(self):
        """리포트 생성"""
        stats = self.scan()
        
        print("=" * 60)
        print("CORE DUMP STATISTICS")
        print("=" * 60)
        
        total = sum(stats.values())
        print(f"\nTotal crashes: {total}")
        
        print("\nBy application:")
        for app, count in sorted(stats.items(), key=lambda x: x[1], reverse=True):
            bar = '█' * min(count, 50)
            print(f"  {app:20s} {count:5d} {bar}")
        
        # JSON 저장
        with open('/var/www/html/crash-stats.json', 'w') as f:
            json.dump({
                'total': total,
                'by_app': dict(stats),
                'timestamp': time.time()
            }, f)
    
    def cleanup_old(self, days=7):
        """오래된 Core Dump 삭제"""
        cutoff = time.time() - (days * 86400)
        
        for core in self.crash_dir.glob('core.*'):
            if core.stat().st_mtime < cutoff:
                print(f"🗑️  Deleting old core: {core.name}")
                core.unlink()

# Cron으로 주기적 실행
# 0 * * * * /usr/local/bin/monitor-cores.py

언어별 Core Dump 생성

C/C++

#include <signal.h>
#include <unistd.h>

// 강제로 Core Dump 생성
void force_core_dump() {
    abort();  // SIGABRT 발생
    // 또는
    raise(SIGSEGV);
    // 또는
    kill(getpid(), SIGQUIT);
}

// 신호 핸들러로 정보 추가
void signal_handler(int sig) {
    fprintf(stderr, "Caught signal %d\n", sig);
    
    // 백트레이스 출력
    void* callstack[128];
    int frames = backtrace(callstack, 128);
    char** symbols = backtrace_symbols(callstack, frames);
    
    for (int i = 0; i < frames; i++) {
        fprintf(stderr, "%s\n", symbols[i]);
    }
    
    free(symbols);
    
    // Core Dump 생성
    signal(sig, SIG_DFL);
    raise(sig);
}

int main() {
    signal(SIGSEGV, signal_handler);
    signal(SIGABRT, signal_handler);
    
    // 애플리케이션 로직
    
    return 0;
}

Python

import faulthandler
import sys

# Core Dump 활성화
faulthandler.enable()

# 파일로 출력
with open('crash.log', 'w') as f:
    faulthandler.enable(file=f)

# 강제 크래시 (테스트용)
def crash():
    import ctypes
    ctypes.string_at(0)  # Segmentation Fault

try:
    crash()
except:
    pass

# 또는 신호 등록
import signal
faulthandler.register(signal.SIGUSR1)

# SIGUSR1 전송 시 백트레이스 출력
# kill -USR1 <PID>

Go

package main

import (
    "os"
    "os/signal"
    "runtime/pprof"
    "syscall"
)

func main() {
    // SIGQUIT 시 고루틴 덤프
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGQUIT)
    
    go func() {
        <-sigChan
        
        // 고루틴 스택 덤프
        pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
        
        // 힙 프로파일
        f, _ := os.Create("heap.prof")
        pprof.WriteHeapProfile(f)
        f.Close()
        
        os.Exit(1)
    }()
    
    // 애플리케이션 로직
    select {}
}

// SIGQUIT 전송
// kill -QUIT <PID>

성능 영향 최소화

Core Dump 생성 시 성능

Core Dump 생성 시간:
- 1GB 메모리: 약 1-2초
- 10GB 메모리: 약 10-20초

프로세스는 생성 중 중단됨 (응답 불가)

선택적 Core Dump

#include <sys/prctl.h>

// Core Dump 비활성화
prctl(PR_SET_DUMPABLE, 0);

// 특정 조건에서만 활성화
if (is_debug_mode) {
    prctl(PR_SET_DUMPABLE, 1);
}

// 민감한 정보 처리 전 비활성화
void process_sensitive_data() {
    prctl(PR_SET_DUMPABLE, 0);
    
    // 민감한 작업
    
    prctl(PR_SET_DUMPABLE, 1);
}

압축 및 필터링

# Core Dump 즉시 압축
kernel.core_pattern=|gzip > /var/crash/core.%e.%p.%t.gz

# 특정 프로그램만 수집
kernel.core_pattern=|/usr/local/bin/selective-core-handler %e %p

# selective-core-handler
#!/bin/bash
EXECUTABLE="$1"
PID="$2"

# 특정 프로그램만 저장
if [[ "$EXECUTABLE" == "myapp" || "$EXECUTABLE" == "critical-service" ]]; then
    cat > "/var/crash/core.$EXECUTABLE.$PID"
else
    # 무시
    cat > /dev/null
fi

정리

Core Dump 체크리스트

개발 환경

  • ulimit -c unlimited 설정
  • -g 플래그로 컴파일
  • 최적화 비활성화 (-O0)
  • AddressSanitizer 사용 (-fsanitize=address)
  • Valgrind로 메모리 검사

프로덕션 환경

  • /etc/security/limits.conf에 core 제한 설정
  • kernel.core_pattern 설정
  • systemd-coredump 설치 및 설정
  • 디스크 공간 모니터링
  • 자동 수집 스크립트 구성
  • 알림 시스템 연동

분석

  • GDB 설치 및 사용법 숙지
  • 디버그 심볼 보관
  • 백트레이스 자동 추출
  • 크래시 패턴 분석
  • 중복 제거 및 그룹화

GDB 명령어 치트시트

# 기본
gdb <executable> <core>     # Core Dump 로드
bt                          # 백트레이스
bt full                     # 상세 백트레이스
frame <N>                   # N번 프레임으로 이동
list                        # 소스 코드

# 변수
print <var>                 # 변수 값
print *<ptr>                # 포인터가 가리키는 값
print <array>[0]@10         # 배열 10개 요소
info locals                 # 모든 지역 변수
info args                   # 함수 인자

# 메모리
x/10x <addr>                # 메모리 (16진수)
x/10s <addr>                # 문자열
x/10i <addr>                # 명령어 (어셈블리)

# 스레드
info threads                # 스레드 목록
thread <N>                  # N번 스레드로 전환
thread apply all bt         # 모든 스레드 백트레이스

# 레지스터
info registers              # 모든 레지스터
print $rip                  # 특정 레지스터

# 기타
info sharedlibrary          # 로드된 라이브러리
info proc mappings          # 메모리 맵

일반적인 크래시 원인

flowchart TB
    Crash[프로그램 크래시]
    
    Null[Null Pointer<br/>Dereference]
    Overflow[Buffer Overflow]
    UseAfter[Use After Free]
    DoubleFree[Double Free]
    StackOver[Stack Overflow]
    Race[Race Condition]
    Assert[Assert Failure]
    
    Crash --> Null
    Crash --> Overflow
    Crash --> UseAfter
    Crash --> DoubleFree
    Crash --> StackOver
    Crash --> Race
    Crash --> Assert
    
    Null --> Fix1[nullptr 체크]
    Overflow --> Fix2[경계 검사]
    UseAfter --> Fix3[스마트 포인터]
    DoubleFree --> Fix4[delete 후 nullptr]
    StackOver --> Fix5[재귀 제한]
    Race --> Fix6[뮤텍스]
    Assert --> Fix7[조건 검증]

디버깅 전략

1. 재현 가능한 크래시
   → GDB로 직접 디버깅
   → 중단점 설정
   → 단계별 실행

2. 재현 불가능한 크래시
   → Core Dump 분석
   → 로그 확인
   → 패턴 찾기

3. 간헐적 크래시
   → AddressSanitizer
   → Valgrind
   → 스트레스 테스트

4. 멀티스레드 크래시
   → ThreadSanitizer
   → 모든 스레드 백트레이스
   → Race condition 확인

5. 메모리 손상
   → Valgrind
   → AddressSanitizer
   → 힙 프로파일링

참고 자료

한 줄 요약: Core Dump는 크래시 시점의 완전한 메모리 스냅샷으로, ulimit -c unlimited로 활성화하고 GDB의 bt, print, info 명령어로 분석하여 재현 어려운 프로덕션 버그를 해결할 수 있습니다.

---
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3