NAME

core - 코어 덤프 파일

DESCRIPTION

어떤 시그널들의 기본 동작은 프로세스를 종료시키고 코어 덤프 파일, 즉 종료 시점의 프로세스 메모리 이미지를 담은 파일을 만드는 것이다. 디버거(가령 gdb(1))에서 그 이미지를 이용해 종료 시점의 프로그램 상태를 조사할 수 있다. 프로세스 코어 덤프를 만들게 하는 시그널 목록은 signal(7)에서 볼 수 있다.

프로세스에서 연성 RLIMIT_CORE 자원 제한을 설정해서 "코어 덤프" 시그널 수신 시 만드는 코어 덤프 파일의 크기에 상한을 둘 수 있다. 자세한 내용은 getrlimit(2)을 보라.

코어 덤프 파일이 생기지 않는 다양한 경우들이 있다.

그리고 madvise(2)MADV_DONTDUMP 플래그를 사용했다면 프로세스 주소 공간 중 일부가 코어 덤프에서 제외될 수 있다.

init 프레임워크로 systemd(1)를 쓰는 시스템에서는 코어 덤프 위치를 systemd(1)에서 정할 수 있다. 자세한 내용은 아래 참고.

코어 덤프 파일 이름

기본적으로 코어 덤프 파일의 이름은 core지만 (리눅스 2.6 및 2.4.21부터는) /proc/sys/kernel/core_pattern 파일을 통해 코어 덤프 파일 이름의 템플릿을 지정할 수 있다. 템플릿에 % 지시자가 들어갈 수 있으며 코어 파일 생성 시 다음 값들로 바뀐다.

%% % 문자
%c 사고 프로세스의 코어 파일 크기 연성 자원 제한. (리눅스 2.6.24부터)
%d 덤프 모드. prctl(2) PR_GET_DUMPABLE이 반환하는 값과 같음. (리눅스 3.7부터)
%e 프로세스 내지 스레드의 comm 값. 보통 (경로 선두부를 제외하고 최대 15글자로 자른) 실행 파일 이름과 같지만 다른 값으로 변경됐을 수도 있다. proc(5)/proc/[pid]/comm/proc/[pid]/task/[tid]/comm 설명 참고.
%E 실행 파일 이름. 슬래시('/')를 느낌표('!')로 바꿔서. (리눅스 3.0부터)
%g 덤프 대상 프로세스의 숫자로 된 실제 GID
%h 호스트명. uname(2)이 반환하는 nodename과 같음.
%i 코어 덤프를 유발한 스레드의 TID. 스레드가 위치한 PID 네임스페이스 기준. (리눅스 3.18부터)
%I 코어 덤프를 유발한 스레드의 TID. 최초 PID 네임스페이스 기준. (리눅스 3.18부터)
%p 덤프 대상 프로세스의 PID. 프로세스가 위치한 PID 네임스페이스 기준.
%P 덤프 대상 프로세스의 PID. 최초 PID 네임스페이스 기준. (리눅스 3.12부터)
%s 덤프를 일으킨 시그널의 번호.
%t 덤프 시간. 에포크, 즉 1970-01-01 00:00:00 +0000 (UTC) 이후 경과한 초의 수로 표현.
%u 덤프 대상 프로세스의 숫자로 된 실제 UID

템플릿 끝에 %가 한 개만 있으면 코어 파일 이름에서 빠진다. % 다음에 위에 나열된 것 외의 문자가 조합된 경우도 마찬가지다. 그 외의 다른 문자들은 모두 코어 파일 이름에 그대로 들어간다. 템플릿에 '/' 문자가 포함될 수 있으며 디렉터리 이름 구분자로 해석한다. 결과로 나오는 코어 파일 이름의 최대 길이는 128바이트다. (2.6.19 전 커널에서는 64바이트였다.) 이 파일의 기본값은 "core"다. 하위 호환성을 위해서 /proc/sys/kernel/core_pattern%p가 없고 /proc/sys/kernel/core_uses_pid(아래 참고)가 0이 아니면 코어 파일 이름에 .PID를 덧붙인다.

경로 해석은 사고 프로세스에서 활성인 구성에 따라 이뤄진다. 사고 프로세스의 마운트 네임스페이스(mount_namespaces(7) 참고), (getcwd(2)로 얻을 수 있는) 현재 작업 디렉터리, 루트 디렉터리(chroot(2) 참고) 말이다.

버전 2.4부터 리눅스에선 코어 덤프 파일 이름을 제어하는 더 단순한 방법을 함께 제공했다. /proc/sys/kernel/core_uses_pid 파일에 0 값이 들어 있으면 코어 덤프 파일의 이름이 그냥 core다. 그 파일에 0 아닌 값이 들어 있으면 코어 덤프 파일 이름에 프로세스 ID가 포함되어 core.PID 형태가 된다.

리눅스 3.6부터는 /proc/sys/fs/suid_dumpable을 2("suidsafe")로 설정하는 경우 패턴이 ('/' 문자로 시작하는) 절대 경로명이거나 아래 설명하는 파이프여야 한다.

코어 덤프를 프로그램으로 보내기

리눅스 커널 2.6.19부터 /proc/sys/kernel/core_pattern 파일에서 또 다른 문법을 지원한다. 파일의 첫 번째 문자가 파이프 기호(|)이면 행 나머지 부분이 실행해야 할 사용자 공간 프로그램 (내지 스크립트) 명령행으로 해석된다.

커널 5.3.0부터는 파이프 템플릿을 공백으로 쪼개서 인자 목록으로 만든 후에 템플릿 매개변수들을 확장한다. 그 전 커널에서는 템플릿 매개변수들을 먼저 확장해서 나온 문자열을 공백으로 쪼개서 인자 목록으로 만든다. 즉, 이전 커널에선 템플릿 매개변수 %e%E로 추가된 실행 파일 이름이 여러 인자로 쪼개질 수도 있다. 그래서 코어 덤프 처리 프로그램에서 실행 파일 이름을 마지막 인자로 두고 실행 파일 조각들을 공백을 이용해 다시 붙여야 한다. 그런데 연속으로 공백이 여러 개 들어 있는 실행 파일 이름이 올바르게 표현되지 않으며, 따라서 코어 덤프 처리 프로그램에서 실행 파일 이름을 알아내는 메커니즘을 사용해야 한다.

코어 덤프가 디스크 파일에 기록되는 게 아니라 프로그램에 표준 입력으로 주어진다. 다음에 유의해야 한다.

/proc/sys/kernel/core_pipe_limit

사용자 공간 프로그램으로 파이프를 연결해서 코어 덤프를 수집할 때 사고 프로세스의 /proc/[pid] 디렉터리에서 얻는 데이터가 수집 프로그램에게 유용할 수 있다. 안전하게 수집을 할 수 있으려면 코어 덤프 수집 프로그램이 끝날 때까지 커널이 기다리게 해서 사고 프로세스의 /proc/[pid] 파일들을 때 이르게 없애지 않도록 해야 한다. 그런데 이렇게 하면 오동작하는 수집 프로그램이 절대 끝나지 않아서 사고 프로세스 정리를 막게 될 가능성이 생긴다.

리눅스 2.6.32부터는 /proc/sys/kernel/core_pipe_limit을 써서 그런 가능성에 대비할 수 있다. 이 파일의 값은 동시에 얼마나 많은 사고 프로세스들이 병렬로 사용자 공간 프로그램으로 파이프 연결될 수 있는지를 규정한다. 이 값을 초과하게 되면 값을 넘은 사고 프로세스들을 커널 로그에만 기록하고 코어 덤프를 건너뛴다.

이 파일에서 0 값은 특별하다. 무제한의 프로세스들을 동시에 잡고 있을 수 있음을 나타내며, 또한 커널에서 기다려 주지 않음을 (즉 수집 프로그램에서 사고 프로세스의 /proc/[pid]에 접근하지 못할 수도 있음을) 나타낸다. 이 파일의 기본값은 0이다.

코어 덤프에 기록할 매핑 제어

커널 2.6.23부터는 리눅스 전용인 /proc/[pid]/coredump_filter 파일을 사용해 해당 프로세스 ID를 가진 프로세스에 대해 코어 덤프를 수행하는 경우 코어 덤프 파일에 어떤 메모리 세그먼트들이 기록되게 할지를 제어할 수 있다.

파일의 값은 메모리 매핑 종류들(mmap(2) 참고)의 비트 마스크다. 마스크의 어떤 비트가 설정돼 있으면 대응하는 종류의 메모리 매핑들을 덤프하고, 아니면 덤프하지 않는다. 비트들의 의미는 다음과 같다.

0번 비트 익명 비공유 매핑 덤프.
1번 비트 익명 공유 매핑 덤프.
2번 비트 파일 기반 비공유 매핑 덤프.
3번 비트 파일 기반 공유 매핑 덤프.
4번 비트 (리눅스 2.6.24부터) ELF 헤더 덤프.
5번 비트 (리눅스 2.6.28부터) 비공유 거대 페이지 덤프.
6번 비트 (리눅스 2.6.28부터) 공유 거대 페이지 덤프.
7번 비트 (리눅스 4.4부터) 비공유 DAX 페이지 덤프.
8번 비트 (리눅스 4.4부터) 공유 DAX 페이지 덤프.

기본적으로 0번, 1번, 4번 (CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS 커널 구성 옵션이 켜진 경우), 5번 비트가 설정돼 있다. coredump_filter 부팅 옵션을 사용하면 부팅 때 이 기본값을 바꿀 수 있다.

이 파일의 값은 16진수로 표시된다. (따라서 기본값이 33으로 표시된다.)

coredump_filter 값이 어떻든지 프레임 버퍼 같은 메모리 맵 I/O 페이지들은 절대 덤프하지 않으며 가상 DSO(vdso(7)) 페이지들은 항상 덤프한다.

fork(2)를 통해 생성된 자식 프로세스가 부모의 coredump_filter 값을 물려받는다. execve(2)를 거치면서 coredump_filter 값이 유지된다.

프로그램 실행 전에 부모 셸에서 coredump_filter를 설정하는 게 요긴할 수 있다.

$ echo 0x7 > /proc/self/coredump_filter
$ ./some_program

커널을 CONFIG_ELF_CORE 구성 옵션으로 빌드한 경우에만 이 파일이 제공된다.

코어 덤프와 systemd

systemd(1) init 프레임워크를 쓰는 시스템에서는 코어 덤프가 저장되는 위치를 systemd(1)에서 정할 수 있다. 이를 위해 systemd(1)에서는 코어 덤프를 파이프로 프로그램에 보낼 수 있는 core_pattern 기능을 이용한다. 코어 덤프가 파이프를 통해 systemd-coredump(8) 프로그램으로 가고 있는지 다음과 같이 확인해 볼 수 있다.

$ cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e

이 경우 코어 덤프는 systemd-coredump(8)에 설정된 위치로 가게 되는데, 보통 /var/lib/systemd/coredump/ 디렉터리에 lz4(1) 압축 파일로 저장된다. systemd-coredump(8)가 기록해 둔 코어 덤프들의 목록을 coredumpctl(1)을 이용해 볼 수 있다.

$ coredumpctl list | tail -5
Wed 2017-10-11 22:25:30 CEST  2748 1000 1000 3 present  /usr/bin/sleep
Thu 2017-10-12 06:29:10 CEST  2716 1000 1000 3 present  /usr/bin/sleep
Thu 2017-10-12 06:30:50 CEST  2767 1000 1000 3 present  /usr/bin/sleep
Thu 2017-10-12 06:37:40 CEST  2918 1000 1000 3 present  /usr/bin/cat
Thu 2017-10-12 08:13:07 CEST  2955 1000 1000 3 present  /usr/bin/cat

코어 덤프마다 덤프 일시, 덤프 프로세스의 PID, UID, GID, 코어 덤프를 유발한 시그널 번호, 덤프된 프로세스가 실행하고 있던 실행 파일 경로명 등의 정보가 보인다. coredumpctl(1)의 여러 옵션들을 통해 지정한 코어 덤프 파일을 systemd(1)의 위치에서 원하는 파일로 가져올 수 있다. 예를 들어 위에 있는 PID 2955의 코어 덤프를 현재 디렉터리에 core라는 파일로 빼내려면 다음처럼 하면 된다.

$ coredumpctl dump 2955 -o core

더 자세한 내용은 coredumpctl(1) 매뉴얼 페이지를 참고하라.

systemd(1)의 코어 덤프 기록 메커니즘을 (지속해서) 끄고 전통적인 리눅스 동작 방식을 복원하고 싶다면 다음처럼 systemd(1) 메커니즘을 무시하게 설정할 수 있다.

# echo "kernel.core_pattern=core.%p" > \
               /etc/sysctl.d/50-coredump.conf
# /lib/systemd/systemd-sysctl

또 다음과 같은 명령을 써서 core_pattern을 일시적으로 (즉 다음 재부팅까지만) 바꾸는 것도 가능하다. (다음 명령은 코어 덤프 파일 이름에 실행 파일 이름뿐 아니라 코어 덤프를 유발한 시그널 번호도 포함시킨다.)

# sysctl -w kernel.core_pattern="%e-%s.core"

NOTES

gdb(1)gcore 명령을 이용하면 실행 중인 프로세스의 코어 덤프를 얻을 수 있다.

리눅스 2.6.27까지 버전에서는 다중 스레드 프로세스가 (더 정확히는 clone(2)CLONE_VM 플래그로 생성돼서 다른 프로세스와 메모리를 공유하는 프로세스가) 코어를 덤프하는 경우에 /proc/sys/kernel/core_pattern%p 지시자를 통해 파일명 어디에 프로세스 ID가 이미 포함돼 있지 않으면 항상 코어 파일명에 프로세스 ID를 덧붙인다. (프로세스의 스레드마다 PID가 다른 구식 LinuxThreads 구현을 쓸 때 주로 도움이 된다.)

EXAMPLES

아래 프로그램을 통해 /proc/sys/kernel/core_pattern 파일의 파이프 문법 사용 방식을 볼 수 있다. 다음 셸 세션은 이 프로그램 사용례를 보여 준다. (core_pattern_pipe_test라는 실행 파일로 컴파일함.)

$ cc -o core_pattern_pipe_test core_pattern_pipe_test.c
$ su
Password:
# echo "|$PWD/core_pattern_pipe_test %p UID=%u GID=%g sig=%s" > \
    /proc/sys/kernel/core_pattern
# exit
$ sleep 100
^\                     # Ctrl-\ 입력
Quit (core dumped)
$ cat core.info
argc=5
argc[0]=</home/mtk/core_pattern_pipe_test>
argc[1]=<20575>
argc[2]=<UID=1000>
argc[3]=<GID=100>
argc[4]=<sig=3>
Total bytes in core dump: 282624

프로그램 소스

/* core_pattern_pipe_test.c */

#define _GNU_SOURCE
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUF_SIZE 1024

int
main(int argc, char *argv[])
{
    ssize_t nread, tot;
    char buf[BUF_SIZE];
    FILE *fp;
    char cwd[PATH_MAX];

    /* 사고 프로세스의 현재 작업 디렉터리로 이동하기. */

    snprintf(cwd, PATH_MAX, "/proc/%s/cwd", argv[1]);
    chdir(cwd);

    /* 그 디렉터리의 "core.info" 파일로 기록하기. */

    fp = fopen("core.info", "w+");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    /* core_pattern 파이프 프로그램이 받은 명령행 인자
       표시하기. */

    fprintf(fp, "argc=%d\n", argc);
    for (int j = 0; j < argc; j++)
        fprintf(fp, "argc[%d]=<%s>\n", j, argv[j]);

    /* 표준 입력(코어 덤프)의 바이트 수 세기. */

    tot = 0;
    while ((nread = read(STDIN_FILENO, buf, BUF_SIZE)) > 0)
        tot += nread;
    fprintf(fp, "Total bytes in core dump: %zd\n", tot);

    fclose(fp);
    exit(EXIT_SUCCESS);
}

SEE ALSO

bash(1), coredumpctl(1), gdb(1), getrlimit(2), mmap(2), prctl(2), sigaction(2), elf(5), proc(5), pthreads(7), signal(7), systemd-coredump(8)


2021-03-22