Core Dump 완벽 가이드 | 생성·분석·디버깅 실전 총정리
이 글의 핵심
Core Dump 생성 방법, GDB로 분석하는 법, Segmentation Fault 디버깅, 실무 사례까지. ulimit 설정, 심볼 파일, 백트레이스 분석으로 프로덕션 크래시 문제를 해결하는 완벽 가이드.
들어가며: Core Dump가 필요한 이유
프로덕션 환경에서 프로그램이 갑자기 크래시하면 어떻게 디버깅할까요? 재현이 어려운 버그, 간헐적인 Segmentation Fault, 메모리 손상 문제를 해결하려면 Core Dump가 필수입니다.
Core Dump의 가치:
- 크래시 시점의 완전한 메모리 스냅샷
- 모든 변수 값과 스택 트레이스
- 재현 없이 사후 분석 가능
- 프로덕션 환경의 실제 데이터
이 글에서 다룰 내용:
- Core Dump 생성 설정
- GDB로 Core Dump 분석
- 실전 디버깅 시나리오
- 자동화 및 모니터링
목차
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
→ 힙 프로파일링
참고 자료
- GDB Documentation
- Core Dump Analysis Tutorial
- Linux Core Dump Guide
- AddressSanitizer
- Valgrind User Manual
- Google Breakpad
한 줄 요약: Core Dump는 크래시 시점의 완전한 메모리 스냅샷으로, ulimit -c unlimited로 활성화하고 GDB의 bt, print, info 명령어로 분석하여 재현 어려운 프로덕션 버그를 해결할 수 있습니다.