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["크래시 발생\nSIGSEGV, SIGABRT 등"]
Crash --> Kernel[커널이 신호 수신]
Kernel --> Check{Core Dump\n활성화?}
Check -->|Yes| Dump["메모리 덤프 생성\ncore.12345"]
Check -->|No| Exit[프로그램 종료]
Dump --> File["Core Dump 파일\n- 스택\n- 힙\n- 레지스터\n- 변수 값"]
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
일상 비유로 이해하기: 메모리를 아파트 건물로 생각해보세요. 스택은 엘리베이터 같아서 빠르지만 공간이 제한적입니다. 힙은 창고처럼 넓지만 물건을 찾는 데 시간이 걸립니다. 포인터는 “3층 302호”처럼 주소를 가리키는 메모지라고 보면 됩니다.
자동화된 크래시 리포팅
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["수집 스크립트\n- 압축\n- 메타데이터"]
Collect --> Upload["중앙 저장소 업로드\nS3, NFS 등"]
Upload --> Notify["알림 전송\nSlack, Email"]
Upload --> Analyze["자동 분석\n- 백트레이스\n- 크래시 위치"]
Analyze --> Report["리포트 생성\nJira, GitHub Issue"]
Analyze --> Dedupe["중복 제거\n같은 크래시 그룹화"]
설정 템플릿
# /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 완벽 가이드 | 생성·분석·디버깅 실전 총정리」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 시스템·런타임 경계(스케줄링, I/O, 메모리, 동시성)를 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
경계에서의 지연·실패(시퀀스 관점)
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(프로세스·런타임·게이트웨이) participant D as 의존성(외부 API·DB·큐) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
알고리즘·프로토콜·리소스 관점 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
- 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.
프로덕션 운영 패턴
실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.
확장 예시: 엔드투엔드 미니 시나리오
「Core Dump 완벽 가이드 | 생성·분석·디버깅 실전 총정리」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.
의사코드 스케치(프레임워크 무관)
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request) // 경계에서 거절
authorize(validated, ctx) // 권한·테넌트
result = domainCore(validated) // 순수에 가까운 규칙
persistOrEmit(result, idempotentKey) // I/O: 멱등·재시도 정책
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성 불안정, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정이 로컬과 다름 | 프로필·시크릿·기본값, 지역 리전 | 단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
정리
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\nDereference"]
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
→ 힙 프로파일링
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Core Dump 생성 방법, GDB로 분석하는 법, Segmentation Fault 디버깅, 실무 사례까지. ulimit 설정, 심볼 파일, 백트레이스 분석으로 프로덕션 크래시 문제를 해결하는 완벽 가이드. St… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
참고 자료
- 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 명령어로 분석하여 재현 어려운 프로덕션 버그를 해결할 수 있습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Jest 완벽 가이드 | JavaScript 테스팅·Mocking·Coverage·실전 활용
- C++ 크래시 디버깅 실전 사례 | 간헐적 Segmentation Fault 해결기
- C++ 디버깅 완벽 가이드 | GDB·Sanitizer·메모리 누수·멀티스레드 디버깅 실전
이 글에서 다루는 키워드 (관련 검색어)
CoreDump, GDB, 디버깅, SegmentationFault, C++, Linux, 시스템프로그래밍 등으로 검색하시면 이 글이 도움이 됩니다.