Core Dump 확인 방법 — 활성화·수집·gdb 분석

NOTE

Core dump는 크래시 “순간의 메모리 스냅샷”이다. 어떤 함수의 몇 번째 줄에서, 어떤 변수 값으로, 어떤 호출 경로를 타고 죽었는지 — 로그로는 못 얻는 정보를 사후에 그대로 재현해서 볼 수 있다.

0. 사전 조건 — core dump가 왜 안 남는가

기본값이 꺼져 있는 배포판이 많다. 순서대로 확인한다.

(1) 현재 셸의 core 크기 제한

ulimit -c            # 0이면 비활성. unlimited면 무제한
ulimit -c unlimited   # 현재 셸 및 이 셸에서 뜬 자식 프로세스에 한해 활성화

이 설정은 셸 세션에만 적용된다. 데몬/서비스로 띄운 프로세스는 아래 systemd 방법을 쓴다.

(2) 코드 내부에서 직접 설정 (셸 설정에 의존하고 싶지 않을 때)

#include <sys/resource.h>
 
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit(RLIMIT_CORE, &rl);

(3) systemd 서비스라면

/etc/systemd/system/foo.service의 [Service] 섹션에 추가한다.

LimitCORE=infinity

systemd 단위는 셸의 ulimit을 상속받지 않으므로 별도 설정이 필수다.

(4) core 파일이 어디에 쌓이는지 — core_pattern

cat /proc/sys/kernel/core_pattern
  • core 또는 core.%p — 크래시한 프로세스의 현재 작업 디렉터리에 생성 (%p=PID)
  • |/usr/lib/systemd/systemd-coredump ... — 파이프로 시작하면 systemd-coredump가 가로채서 저널에 저장한다(요즘 배포판 기본값). 이 경우 작업 디렉터리에 파일이 안 보이는 게 정상이며, 아래 coredumpctl로 조회해야 한다.

1. systemd-coredump 환경 — coredumpctl

coredumpctl list                       # 최근 크래시 목록 (PID, 시각, 실행파일, 시그널)
coredumpctl info <PID 또는 실행파일명>    # 상세 정보(시그널, 커맨드라인, 메모리맵)
coredumpctl gdb <PID 또는 실행파일명>     # 해당 core를 gdb로 바로 열기
coredumpctl dump <PID> -o /tmp/my.core   # core 파일로 따로 저장

2. 전통적인 core 파일 — gdb로 직접 분석

gdb ./myprog core       # 또는 coredumpctl dump로 뽑아낸 파일

gdb 안에서 자주 쓰는 명령은 다음과 같다.

명령용도
bt (backtrace)크래시 시점의 호출 스택 — 가장 먼저 확인할 것
bt full각 프레임의 지역 변수 값까지 같이 출력
frame NN번 프레임으로 이동해서 그 시점 상태 확인
info locals현재 프레임의 지역 변수 전체
print 변수명 / p *포인터특정 값 확인 (NULL인지, 이상한 값인지)
info registers크래시 시점 레지스터 (SIGSEGV의 faulting address 등)
list크래시 라인 주변 소스 코드
thread apply all bt멀티스레드 프로그램에서 모든 스레드의 스택을 한 번에
x/20xw $sp크래시 시점 스택 메모리를 16진수로 덤프

최소 예시

(gdb) bt
#0  strcpy_internal () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000555555555169 in copy_name (dst=0x0, src=0x7fffffffe420 "hello") at main.c:12
#2  0x0000555555555195 in main () at main.c:20
(gdb) frame 1
#1  0x0000555555555169 in copy_name (dst=0x0, src=...) at main.c:12
12          strcpy(dst, src);
(gdb) print dst
$1 = 0x0

dst가 0x0(NULL)인 걸 확인 — NULL 포인터 역참조로 바로 원인이 확정된다.

3. gdb가 쓸모없어지는 흔한 실수들

WARNING

아래 조건 중 하나라도 어기면 bt가 ?? in ?? ()만 찍거나 라인 정보가 아예 안 나온다.

  • -g 없이 컴파일 — 디버그 심볼이 없으면 함수명/라인 정보 자체가 바이너리에 없다. 최소 -g -O0(최적화까지 끄면 변수 값도 정확)로 다시 빌드해서 재현해야 한다.
  • strip된 바이너리 — 배포 시 심볼 테이블을 제거한 경우. 심볼 있는 빌드로 재현하거나 .debug 심볼 파일을 별도 보관해뒀다가 매칭한다.
  • core를 만들 때와 다른 버전의 바이너리/라이브러리로 열기 — 특히 공유 라이브러리 버전이 다르면 백트레이스가 엉뚱하게 나온다. 크래시 당시와 동일한 빌드/배포 아티팩트를 보관해두는 게 중요하다.
  • 최적화 빌드(-O2 등) — 인라이닝/변수 최적화로 bt full의 지역 변수가 <optimized out>으로 나오는 경우가 흔하다. 재현 가능하면 -O0 디버그 빌드로 다시 크래시를 낸다.

4. 실행 중인(아직 안 죽은) 프로세스에 붙어서 보기 — gdb -p

크래시가 아니라 “행(hang)“이나 데드락 의심 상황이면 core 없이도 확인할 수 있다.

gdb -p <PID>
(gdb) bt                # 또는 (gdb) thread apply all bt

관련 문서