CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS

# はじめに
linux kernel configの一つ、`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`について掘り下げてみる。
主に参考にしたlinux kernelは、[v4.15](https://github.com/torvalds/linux/releases/tag/v4.15)。

# TL;DR
`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`はdefconfigで結構無効にされてしまっているけど、使える`gdb`のバージョンが6.7以降なら有効にしておこう。

# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERSとは何か

core dump生成時、Disk I/Oやストレージ容量への負荷を減らすため、テキスト領域のように変更されないメモリはデフォルトで保存しない。
デバッグ時に、必要に応じて実行ファイルを読みこめばいいからだ。
実行ファイルのELFヘッダもその部類に入る。
つまりメモリにロードされてから変更されることは基本的にない。
ただ、`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`が有効な場合は、デフォルトでクラッシュしたプログラムのELFヘッダもcoreファイル中に保存するようになる。

ELFヘッダにはビルドID等の情報が含まれるため、解析時に対応させるべきデバッグシンボルを特定する際などに有益な場合があるからだ。

`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`はあくまでもデフォルト値を変更するだけで、起動後にprocfs経由で変更することもできる。
[`/proc/PID/coredump_filter`](https://github.com/torvalds/linux/blob/d8a5b80568a9cb66810e75b182018e9edb68e8ff/Documentation/filesystems/proc.txt#L1634)の"(bit 4) ELF header pages in file-backed private memory areas"を立てればよい。


## 実装
`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`が使われているのは下記の一箇所。
有効なときはMMF_DUMP_FILTER_DEFAULTにMMF_DUMP_ELF_HEADERSビットが立つ。

```c:include/linux/sched/coredump.h
#define MMF_DUMP_FILTER_DEFAULT \
        ((1 << MMF_DUMP_ANON_PRIVATE) | (1 << MMF_DUMP_ANON_SHARED) |\
         (1 << MMF_DUMP_HUGETLB_PRIVATE) | MMF_DUMP_MASK_DEFAULT_ELF)

#ifdef CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS
# define MMF_DUMP_MASK_DEFAULT_ELF      (1 << MMF_DUMP_ELF_HEADERS)
#else
# define MMF_DUMP_MASK_DEFAULT_ELF      0
#endif
```

`MMF_DUMP_ELF_HEADERS`をチェックしているのは、`vma_dump_size`関数。
マクロを使っているため単純なgrepではヒットしないので注意。
その名のとおり、core dump生成時vmaごとにダンプすべきサイズを算出している。
vmaの先頭がELFヘッダを示すマジックワード(0x7f, 'E' ,'L', 'F')と一致したら、PAGE_SIZEを返す。
（＝ELFヘッダが含まれると見られる領域を1ページ分ダンプする）

```c:linux/fs/binfmt_elf.c
static unsigned long vma_dump_size(struct vm_area_struct *vma,
                                   unsigned long mm_flags)
{
#define FILTER(type)    (mm_flags & (1UL << MMF_DUMP_##type))

(中略)

        if (FILTER(ELF_HEADERS) &&
            vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ)) {
                u32 __user *header = (u32 __user *) vma->vm_start;
                u32 word;
                mm_segment_t fs = get_fs();
                /*                                                                                                                                
                 * Doing it this way gets the constant folded by GCC.                                                                             
                 */
                union {
                        u32 cmp;
                        char elfmag[SELFMAG];
                } magic;
                BUILD_BUG_ON(SELFMAG != sizeof word);
                magic.elfmag[EI_MAG0] = ELFMAG0;
                magic.elfmag[EI_MAG1] = ELFMAG1;
                magic.elfmag[EI_MAG2] = ELFMAG2;
                magic.elfmag[EI_MAG3] = ELFMAG3;
                /*                                                                                                                                
                 * Switch to the user "segment" for get_user(),                                                                                   
                 * then put back what elf_core_dump() had in place.                                                                               
                 */
                set_fs(USER_DS);
                if (unlikely(get_user(word, header)))
                        word = 0;
                set_fs(fs);
                if (word == magic.cmp)
                        return PAGE_SIZE;
        }

#undef  FILTER
                                                                                                                                                  
        return 0;
```

# 実験
ELFヘッダありのcoreダンプとELFヘッダなしのcoreダンプを実際に取得して比較してみる。

## 準備
事前にcoreファイルを出力できるよう、resoure limitをなくしておく。
また、出力先もわかりやすくするため、core_patternも変更しておく。

```shell-session
$ ulimit -c unlimited
$ sudo su
# echo 'core.%p' > /proc/sys/kernel/core_pattern
# exit
```

## ELFヘッダなし
`coredump_filter`で"(bit 4) ELF header pages in file-backed private memory areas"を落とした状態でcoreファイルを生成する。

```shell-session
$ echo 0x23 > /proc/self/coredump_filter
$ sleep 100 &
$ kill -SEGV $!
$ mv core.$! core.no_elf_header
```

## ELFヘッダあり
今度は"(bit 4) ELF header pages in file-backed private memory areas"を立てた状態でcoreファイルを生成する。

```shell-session
$ echo 0x33 > /proc/self/coredump_filter
$ sleep 100 &
$ kill -SEGV $!
$ mv core.$! core.with_elf_header
```

## 比較
それぞれのcoreファイルのセクション情報を比較して、PIDの違いやオフセットの違いを除いたものが下記。
`load1`セクションが`load1a`と`load1b`に、分かれていることがわかる。
同様に`load6`と`load11`も分かれている。
`load1`はSizeが0だったのに対して、`load1a`は00001000(=PAGE_SIZE)となっている。
確かに追加でデータが保存されてそうだ。

```
$ objdump -h core.no_elf_header > sections.no_elf_header
$ objdump -h core.with_elf_header > sections.with_elf_header
$ diff -u sections.no_elf_header sections.with_elf_header
--- sections.no_elf_header	2019-03-30 11:11:08.051431648 +0900
+++ sections.with_elf_header	2019-03-30 11:11:28.923522648 +0900

 Sections:
 Idx Name          Size      VMA               LMA               File off  Algn

(中略)

- 12 load1         00000000  0000000000400000  0000000000000000  00002000  2**12
+ 12 load1a        00001000  0000000000400000  0000000000000000  00002000  2**12
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 13 load1b        00000000  0000000000401000  0000000000001000  00003000  2**12
                   ALLOC, READONLY, CODE

(中略)

- 17 load6         00000000  00007f136b94f000  0000000000000000  00025000  2**12
+ 18 load6a        00001000  00007fae7829d000  0000000000000000  00026000  2**12
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 19 load6b        00000000  00007fae7829e000  0000000000001000  00027000  2**12
                   ALLOC, READONLY, CODE

(中略)

- 22 load11        00000000  00007f136bd19000  0000000000000000  0002f000  2**12
+ 24 load11a       00001000  00007fae78667000  0000000000000000  00031000  2**12
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 25 load11b       00000000  00007fae78668000  0000000000001000  00032000  2**12
                   ALLOC, READONLY, CODE

```

## 追加された領域の中身を見てみる

追加されたセクションのオフセット、サイズはわかったので、中身を確認してみる。

`load1a`セクションを取り出し、`readelf`コマンドに食わせると、1ページ分しかないためか少しエラーが出たが、ちゃんとELFとして解釈し、BUILD_IDも取り出せている。
また、最初のテキスト領域ということで実行ファイルそのもの（今回は`sleep`コマンド）の先頭1ページと一致することも確認できた。

（本当は`objcopy`コマンドでスマートに取り出したかったが、coreファイルだからかエラーになってしまったので、`dd`コマンドで取り出している。）

```shell-session
$ dd if=core.with_elf_header of=core.with_elf_header.load1a bs=4096 skip=2 count=1
$ readelf --notes core.with_elf_header.load1a
readelf: Error: Reading 0x740 bytes extends past end of file for section headers
readelf: Error: the dynamic segment offset + size exceeds the size of the file

Displaying notes found at file offset 0x00000254 with length 0x00000044:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 5ff67ba1f7dc0370081f7f6aad5c173d1825c087


$ dd if=/bin/sleep of=sleep.head bs=4096 count=1
$ diff sleep.head core.with_elf_header.load1a
```

では、同様にELFヘッダが追加された`load6`, `load11`は何者か？
`gdb`で`info sharelibrary`を見てみると`load6a`の直後に`/lib/x86_64-linux-gnu/libc.so.6`が、`load11a`の直後に`/lib64/ld-linux-x86-64.so.2`がロードされている。
これらのsharedlibrayもELFファイルなので、そのヘッダが保存されたのだろう。
ただ、ローダが書き換えているのか、ライブラリそのもののヘッダとは中身が異なっていた。
そのためこれらのELFヘッダがどういうときに役に立つのかはわからなかった。
（ご存知の方はぜひ教えてください。）

```
(gdb) target core core.with_elf_header
[New LWP 12138]
Core was generated by `sleep 100'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007fae783692f0 in ?? ()
(gdb) info files
Local core dump file:
        `/home/kondo/work/190330_linux_kernel/core.with_elf_header', file type elf64-x86-64.
        0x0000000000400000 - 0x0000000000401000 is load1a
        0x0000000000401000 - 0x0000000000401000 is load1b
        0x0000000000606000 - 0x0000000000607000 is load2
        0x0000000000607000 - 0x0000000000608000 is load3
        0x0000000000ab4000 - 0x0000000000ad5000 is load4
        0x00007fae77ec8000 - 0x00007fae77ec8000 is load5
        0x00007fae7829d000 - 0x00007fae7829e000 is load6a
        0x00007fae7829e000 - 0x00007fae7829e000 is load6b
        0x00007fae7845d000 - 0x00007fae7845d000 is load7
        0x00007fae7865d000 - 0x00007fae78661000 is load8
        0x00007fae78661000 - 0x00007fae78663000 is load9
        0x00007fae78663000 - 0x00007fae78667000 is load10
        0x00007fae78667000 - 0x00007fae78668000 is load11a
        0x00007fae78668000 - 0x00007fae78668000 is load11b
        0x00007fae78869000 - 0x00007fae7886c000 is load12
        0x00007fae7888c000 - 0x00007fae7888d000 is load13
        0x00007fae7888d000 - 0x00007fae7888e000 is load14
        0x00007fae7888e000 - 0x00007fae7888f000 is load15
        0x00007fff0efcf000 - 0x00007fff0eff0000 is load16
        0x00007fff0eff7000 - 0x00007fff0effa000 is load17
        0x00007fff0effa000 - 0x00007fff0effc000 is load18
        0xffffffffff600000 - 0xffffffffff601000 is load19
Local exec file:
        <no file loaded>
(gdb) info sharedlibrary
From                To                  Syms Read   Shared Object Library
0x00007fae782bc8b0  0x00007fae7840fb04  No          /lib/x86_64-linux-gnu/libc.so.6
0x00007fae78667ac0  0x00007fae78685850  No          /lib64/ld-linux-x86-64.so.2
```

# `CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`の歴史

通常、coreファイル自体もELFフォーマットで出力されるので少し混乱するかも知れないが、この場合coreファイルの中にさらにELFヘッダがあることになる。
実際、このことにより6.7より古いgdbでは、ELFの解釈に失敗してしまったらしい。

そのことは`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`の説明にも書かれている。

```config:fs/Kconfig.binfmt
config CORE_DUMP_DEFAULT_ELF_HEADERS
        bool "Write ELF core dumps with partial segments"
        default y
        depends on BINFMT_ELF && ELF_CORE
        help
          ELF core dump files describe each memory mapping of the crashed
          process, and can contain or omit the memory contents of each one.
          The contents of an unmodified text mapping are omitted by default.

          For an unmodified text mapping of an ELF object, including just
          the first page of the file in a core dump makes it possible to
          identify the build ID bits in the file, without paying the i/o
          cost and disk space to dump all the text.  However, versions of
          GDB before 6.7 are confused by ELF core dump files in this format.

          The core dump behavior can be controlled per process using
          the /proc/PID/coredump_filter pseudo-file; this setting is
          inherited.  See Documentation/filesystems/proc.txt for details.

          This config option changes the default setting of coredump_filter
          seen at boot time.  If unsure, say Y.
```

実はこのgdbでただしく解釈できないという課題があったため、[2008年の導入当初(v2.6.28)](https://github.com/torvalds/linux/commit/656eb2cd5da153762f2e8419ca117ce12ef522c3)`CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS`はデフォルト**無効**だった。

ただ、環境が追いついたこともあり、2010年[デフォルト**有効**にするパッチが投稿](https://lore.kernel.org/patchwork/patch/216316/)され、v2.6.37でマージされたようだ。

ここからは推測だが、そのデフォルト**無効**時に作成されたdefconfigがそのままだったり参考にされたりして、結構無効になっているarchがある。
coreファイルのサイズも数ページ分しか増えないし、最悪ツールが対応していなかったとしてもELFから該当のセクションを取り除けばいいだけなので、特別な理由がない限り有効にしておきたい。

# 参考
[proc (Linux Documentation)](https://github.com/torvalds/linux/blob/v4.15/Documentation/filesystems/proc.txt)
[default CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y (LKML)](https://lore.kernel.org/patchwork/patch/216316/)