프로세스가 시그널로 비정상 종료될 때 커널이 메모리 상태를 파일로 남긴다. 어디에 어떤 이름으로 남는지는 core_pattern 이 정한다.
cat /proc/sys/kernel/core_pattern
ulimit -c
core_pattern 이 |/usr/lib/systemd/systemd-coredump ... 처럼 파이프로 시작하면 systemd-coredump 가 받아 /var/lib/systemd/coredump/ 에 저장한다. 평범한 경로 문자열이면 그 경로에 그대로 떨어진다. ulimit -c 가 0 이면 애초에 덤프가 만들어지지 않는다. systemd 서비스는 셸의 ulimit 이 아니라 유닛의 LimitCORE= 를 본다.
systemd-coredump 로 수집한 것은 다음으로 다룬다.
coredumpctl list
coredumpctl info <PID|실행파일명>
coredumpctl dump <PID> --output=/tmp/core
coredumpctl debug <PID>
coredumpctl dump 는 압축 해제까지 알아서 한다. 파일을 직접 다뤄야 한다면 압축 형식을 확인한다. systemd 기본 압축은 배포판과 버전에 따라 lz4 또는 zstd 다.
lz4 -d core.xxx.lz4 /tmp/core
zstd -d core.xxx.zst -o /tmp/core
gdb 는 코어 파일만으로는 심볼을 해석하지 못한다. 덤프를 만든 실행 파일이 무엇인지부터 알아낸다. file 이 알려 준다.
file core.1234
ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style,
from '/usr/bin/python3.8 /opt/example/bin/agent', real uid: 0, ...,
execfn: '/usr/bin/python3.8', platform: 'x86_64'
from 에 전체 명령줄이, execfn 에 실제 실행 파일 경로가 들어 있다. 이 경로가 실행 파일이다.
gdb /usr/bin/python3.8 /tmp/core
(gdb) bt
(gdb) thread apply all bt
(gdb) info registers
(gdb) x/16i $pc
thread apply all bt 로 모든 스레드의 스택을 봐야 한다. 크래시한 스레드가 1번이 아닌 경우가 흔하다.
실행 파일이 그 자리에 없다. No such file or directory 가 뜨면 덤프를 만든 노드와 분석하는 노드가 다르거나, 패키지가 이미 지워졌거나, 컨테이너 안의 경로라 호스트에서 보이지 않는 경우다. 코어 파일은 반드시 덤프를 만든 것과 같은 바이너리 와 짝을 지어야 한다. 버전이 조금만 달라도 스택이 어긋난다. 컨테이너 안에서 죽은 프로세스라면 같은 이미지로 컨테이너를 띄워 그 안에서 분석한다.
bt 가 ?? () 만 찍는다. 심볼 정보가 없다는 뜻이다. 배포판 패키지는 대개 스트립되어 있으므로 디버그 심볼 패키지를 따로 깐다.
debuginfo-install python3
# 또는
dnf debuginfo-install python3
RHEL 계열에서 이 명령을 쓰려면 debuginfo 저장소가 켜져 있어야 한다. 폐쇄망이면 *-debuginfo RPM 을 직접 받아 설치한다.
gdb 가 너무 오래됐다. RHEL 7 의 gdb 7.6 처럼 오래된 버전은 새 컴파일러가 만든 DWARF 5 정보를 읽지 못한다. 스택이 이상하면 gdb 버전을 먼저 의심한다.
파이썬 프로세스의 스택이 C 레벨만 보인다. py-bt 는 CPython 이 제공하는 gdb 확장(python3-debuginfo 에 들어 있는 libpython.py)이 로드돼야 동작한다. 심볼 패키지를 깔지 않으면 쓸 수 없다.
(gdb) py-bt
기본값이 꺼져 있는 환경에서 재현을 잡아야 한다면 다음을 켠다.
ulimit -c unlimited
sysctl -w kernel.core_pattern='/var/crash/core.%e.%p.%t'
sysctl -w fs.suid_dumpable=1
%e 는 실행 파일명, %p 는 PID, %t 는 시각이다. 저장 경로는 미리 만들어 두고 쓰기 권한을 준다. 없는 디렉터리를 지정하면 커널은 덤프를 조용히 버린다. systemd 서비스라면 유닛에 LimitCORE=infinity 를 넣는다.
코어 파일은 프로세스 메모리 전체 크기만큼 커진다. 수십 GB 를 쓰는 프로세스라면 디스크 여유를 먼저 확인한다.
core 를 포함한 자원 한계 설정.