SIer だけど技術やりたいブログ

コアダンプにはプロセスのメタ情報も詰まってる

linux

知りたいこと

コアダンプを file コマンドで確認すると、ELF 64-bit LSB core file と表示される。コアダンプだからそりゃそうだが、from 'sleep 100', real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, execfn: '/usr/bin/sleep', platform: 'x86_64' という、クラッシュしたプロセスがどういうものかを示す情報も付いてくる。

[root@ip-172-31-23-157 ~]# file /tmp/dump
/tmp/dump: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'sleep 100', real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, execfn: '/usr/bin/sleep', platform: 'x86_64'

でもコアダンプって実行中のプロセスが利用しているメモリ空間のダンプじゃなかったっけ…? 上記の情報もメモリに載ってるんだっけ?いやプロセスの番号なんてプロセス自身のメモリ空間にないはず…

ということで上記情報がどこから取得されるデータなのか、また、コアダンプに決められたフォーマットが存在するか、が気になったので調べた。

結論

実装見たりググったりしたところ、おそらく次のようになっている。

  • コアダンプは、ELF 形式のバイナリで、Notes セクションにプロセスのメタ情報などを持っている
  • おそらくコアダンプに決まったフォーマットはなさそう

以下、調べたこと。

コアダンプの取得

コアダンプをどう処理するかは kernel.core_pattern で指定できる。最近は systemd-coredump が処理している。 (kernel.core_pattern の先頭がパイプ(|)から始まる場合、カーネルはコアダンプをユーザ空間のプログラムに渡す)

$ sysctl kernel.core_pattern
kernel.core_pattern = |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h

今回は systemd-coredump が加工する(かもしれない)前の、カーネルからの素朴な出力を確認したいので、そのままファイルにリダイレクトしてみる。 (本当は kernel.core_pattern にファイルパスを指定すればいいけど、ユーザ空間のプログラムに渡してるものを確認したかったので念のためこうした)

// 標準入力をそのままファイルに書き出すシェルスクリプト
$ cat << EOF > /tmp/core.sh
#!/bin/bash
cat > /tmp/dump
EOF
$ chmod u+x /tmp/core.sh

$ sudo sysctl kernel.core_pattern='|/tmp/core.sh'
kernel.core_pattern = |/tmp/core.sh

次に、適当なプロセスをクラッシュさせる。今回は、sleep プロセスに ABRT を送ってコアダンプを出力した。

$ id
uid=1000(ec2-user) gid=1000(ec2-user) groups=1000(ec2-user),4(adm),10(wheel),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ sleep 100 &
[1] 13501

// ABRT シグナルを送る
$ kill -SIGABRT 2296

// コアダンプファイルが出力される
$ file /tmp/dump
/tmp/dump: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'sleep 100', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/usr/bin/sleep', platform: 'x86_64'

コアダンプファイルをながめる

readelf -aコマンドでいろいろ表示すると、ELF フォーマットであること、いくつか Note があること、などが分かる。

$ readelf -a /tmp/dump
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              CORE (Core file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         37
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0

There are no sections in this file.

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  NOTE           0x0000000000000858 0x0000000000000000 0x0000000000000000
                 0x0000000000000f84 0x0000000000000000         0x0
  LOAD           0x0000000000002000 0x00005595338ff000 0x0000000000000000
                 0x0000000000001000 0x0000000000002000  R      0x1000
...
There is no dynamic section in this file.

There are no relocations in this file.
No processor specific unwind information to decode

Dynamic symbol information is not available for displaying symbols.

No version information found in this file.

Displaying notes found at file offset 0x00000858 with length 0x00000f84:
  Owner                Data size        Description
  CORE                 0x00000150       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000150       NT_AUXV (auxiliary vector)
  CORE                 0x0000060d       NT_FILE (mapped files)
    Page size: 4096
                 Start                 End         Page Offset
    0x00005595338ff000  0x0000559533901000  0x0000000000000000
        /usr/bin/sleep
...
    0x00007f1ba8d9b000  0x00007f1ba8d9d000  0x0000000000000035
        /usr/lib64/ld-linux-x86-64.so.2
  CORE                 0x00000200       NT_FPREGSET (floating point registers)
  LINUX                0x00000340       NT_X86_XSTATE (x86 XSAVE extended state)

Note のうち、NT_PRSTATUS や NT_PRPSINFO を見ると、コマンド名が含まれているのがわかる(きちんと ELF フォーマットを読まずに strings で文字列だけ抜き出してるので、あくまでも雰囲気だけど)。

$ readelf -n /tmp/dump  | head

Displaying notes found at file offset 0x00000858 with length 0x00000f84:
  Owner                Data size        Description
  CORE                 0x00000150       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000150       NT_AUXV (auxiliary vector)
  CORE                 0x0000060d       NT_FILE (mapped files)
    Page size: 4096
                 Start                 End         Page Offset

// NT_PRSTATUS を雑に表示してみる
$ od --skip-bytes=0x00000858 --read-bytes=0x00000150 --strings /tmp/dump
0004144 CORE

// NT_PRPSINFO を雑に表示してみる
$ od --skip-bytes=$((0x00000858 + 0x00000150)) --read-bytes=0x00000088 --strings /tmp/dump
0004710 CORE
0004770 sleep
0005010 sleep 100

カーネル実装をながめる

コアダンプファイルは、カーネルでいうと fs/coredump.cbinfmt->core_dump あたりで生成してそうだった(実際に読んだのはアップストリームじゃなくて Amazon Linux の srpm だけど大まかには同じだと思うので、リンク貼るのが楽なアップストリームのほうをリンクしとく)。 binfmt はいくつか種類があるが、ほとんどは fs/binfmt_elf.c かなと思う。

でザックリと読んだ感じ、Notes セクションにあった NT_PRSTATUS (prstatus structure)NT_PRPSINFO (prpsinfo structure) の構造体は次のように宣言されていて、やっぱりプロセスのメタ情報が入っているという理解で良さそう。

struct elf_prstatus_common
{
	struct elf_siginfo pr_info;	/* Info associated with signal */
	short	pr_cursig;		/* Current signal */
	unsigned long pr_sigpend;	/* Set of pending signals */
	unsigned long pr_sighold;	/* Set of held signals */
	pid_t	pr_pid;
	pid_t	pr_ppid;
	pid_t	pr_pgrp;
	pid_t	pr_sid;
	struct __kernel_old_timeval pr_utime;	/* User time */
	struct __kernel_old_timeval pr_stime;	/* System time */
	struct __kernel_old_timeval pr_cutime;	/* Cumulative user time */
	struct __kernel_old_timeval pr_cstime;	/* Cumulative system time */
};

struct elf_prstatus
{
	struct elf_prstatus_common common;
	elf_gregset_t pr_reg;	/* GP registers */
	int pr_fpvalid;		/* True if math co-processor being used.  */
};

struct elf_prpsinfo
{
	char	pr_state;	/* numeric process state */
	char	pr_sname;	/* char for pr_state */
	char	pr_zomb;	/* zombie */
	char	pr_nice;	/* nice val */
	unsigned long pr_flag;	/* flags */
	__kernel_uid_t	pr_uid;
	__kernel_gid_t	pr_gid;
	pid_t	pr_pid, pr_ppid, pr_pgrp, pr_sid;
	/* Lots missing */
	/*
	 * The hard-coded 16 is derived from TASK_COMM_LEN, but it can't be
	 * changed as it is exposed to userspace. We'd better make it hard-coded
	 * here.
	 */
	char	pr_fname[16];	/* filename of executable */
	char	pr_psargs[ELF_PRARGSZ];	/* initial part of arg list */
};

実際に Note セクションに書き込んでいる処理はここで、namesz(どちらも “CORE” という文字列だった)の直後から data が続く構造になってるっぽい。

static int writenote(struct memelfnote *men, struct coredump_params *cprm)
{
	struct elf_note en;
	en.n_namesz = strlen(men->name) + 1;
	en.n_descsz = men->datasz;
	en.n_type = men->type;

	return dump_emit(cprm, &en, sizeof(en)) &&
	    dump_emit(cprm, men->name, en.n_namesz) && dump_align(cprm, 4) &&
	    dump_emit(cprm, men->data, men->datasz) && dump_align(cprm, 4);
}

たとえば、prstatus の先頭に入ってるのは pr_info.si_signo という関連するシグナル番号を表すもので、Note セクションにこの値として 006 が入っているのが確認できる(今回は kill -SIGABRT でコアダンプを生成した。SIGABRT のシグナル番号は 6)。

// note->name の値は "CORE"(fill_thread_core_info 関数あたりで設定されている)
// note->data は note->name の直後に来る(writenote を参照)
$ od -Ax -c    --skip-bytes=$((0x00000858)) --read-bytes=0x00000150  /tmp/dump
000858 005  \0  \0  \0   P 001  \0  \0 001  \0  \0  \0   C   O   R   E
000868  \0  \0  \0  \0 006  \

コアダンプファイルからプロセス情報を抜き出す

さすがにカーネルコードの構造体の定義を見ながら od するのは辛いので、他に方法ないか調べたら gdb が使えそうだった。

$ gdb /usr/bin/sleep /tmp/dump
GNU gdb (GDB) Amazon Linux 12.1-5.amzn2023.0.3
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-amazon-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/sleep...
Reading symbols from .gnu_debugdata for /usr/bin/sleep...
(No debugging symbols found in .gnu_debugdata for /usr/bin/sleep)
[New LWP 13501]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `sleep 100'.
Program terminated with signal SIGABRT, Aborted.
#0  0x00007f1ba8b1394a in clock_nanosleep@GLIBC_2.2.5 () from /lib64/libc.so.6
Missing separate debuginfos, use: dnf debuginfo-install coreutils-8.32-30.amzn2023.0.3.x86_64

(gdb) info inferior
  Num  Description       Connection           Executable
* 1    process 13501     1 (core)             /usr/bin/sleep

(gdb) info proc
exe = 'sleep 100'

(gdb) p $_siginfo
$1 = {si_signo = 6, si_errno = 0, si_code = 0, _sifields = {_pad = {2119, 1000, 0 <repeats 26 times>},
    _kill = {si_pid = 2119, si_uid = 1000}, _timer = {si_tid = 2119, si_overrun = 1000, si_sigval = {
        sival_int = 0, sival_ptr = 0x0}}, _rt = {si_pid = 2119, si_uid = 1000, si_sigval = {sival_int = 0,
        sival_ptr = 0x0}}, _sigchld = {si_pid = 2119, si_uid = 1000, si_status = 0, si_utime = 0,
      si_stime = 0}, _sigfault = {si_addr = 0x3e800000847, _addr_lsb = 0, _addr_bnd = {_lower = 0x0,
        _upper = 0x0}}, _sigpoll = {si_band = 4294967298119, si_fd = 0}, _sigsys = {
      _call_addr = 0x3e800000847, _syscall = 0, _arch = 0}}}

コアダンプファイルのフォーマット

stack overflow で ELF core file format というのが見つかった。

The core dump file format is using the ELF format but is not described in the ELF standard. AFAIK, there is no authoritative reference on this.

なるほど。