Executable and Linkable Format (ELF) バイナリのロード方法

## 概要

Linuxで使われるバイナリのフォーマットのELFについて、特にexecシステムコールから呼ばれる実行ファイルのロードの処理を調べた。

## ELFの構造

ヘッダが３つと、それらでポイントされるデータによって構成されている。
フラグの意味と構造体の表は[Wikipedia](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format)にまとまっている。

* File header
* Program header
* Section header

### File header

ファイルの最初にあり、バイナリそのものの属性と、他のヘッダ情報のテーブル開始位置のファイルオフセット`e_*hoff`、と個数`e_*hnum`を格納している。

~~~
juntaki@dev ~> readelf -e (which ls)
ELF ヘッダ:
  マジック:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  クラス:                            ELF64
  データ:                            2 の補数、リトルエンディアン
  バージョン:                        1 (current)
  OS/ABI:                            UNIX - System V
  ABI バージョン:                    0
  型:                                EXEC (実行可能ファイル)
  マシン:                            Advanced Micro Devices X86-64
  バージョン:                        0x1
  エントリポイントアドレス:               0x4049a0
  プログラムの開始ヘッダ:          64 (バイト)
  セクションヘッダ始点:          124728 (バイト)
  フラグ:                            0x0
  このヘッダのサイズ:                64 (バイト)
  プログラムヘッダサイズ:            56 (バイト)
  プログラムヘッダ数:                9
  セクションヘッダ:                  64 (バイト)
  セクションヘッダサイズ:            29
  セクションヘッダ文字列表索引:      28
~~~

構造体はこんな感じ。

~~~
typedef struct elf64_hdr {
  unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
  Elf64_Half e_type;
  Elf64_Half e_machine;
  Elf64_Word e_version;
  Elf64_Addr e_entry;   /* Entry point virtual address */
  Elf64_Off e_phoff;    /* Program header table file offset */
  Elf64_Off e_shoff;    /* Section header table file offset */
  Elf64_Word e_flags;
  Elf64_Half e_ehsize;
  Elf64_Half e_phentsize;
  Elf64_Half e_phnum;
  Elf64_Half e_shentsize;
  Elf64_Half e_shnum;
  Elf64_Half e_shstrndx;
} Elf64_Ehdr;
~~~

### Program header

load_elf_phdrs()でポインタ配列として読み込まれる。

~~~c:load_elf_phdrs()
  size = sizeof(struct elf_phdr) * elf_ex->e_phnum;
  if (size > ELF_MIN_ALIGN)
    goto out;

  elf_phdata = kmalloc(size, GFP_KERNEL);
  if (!elf_phdata)
    goto out;

  /* Read in the program headers */
  retval = kernel_read(elf_file, elf_ex->e_phoff,
           (char *)elf_phdata, size);
~~~

Program headerの構造体で、ファイル内でのオフセットは`p_offset`、ファイル上でのサイズは`p_filesz`で指定して各ヘッダが指す内容を読み取ることができる。
ファイル上でのサイズはメモリ上でのサイズ`p_memsz`より小さくなりうる（差分はゼロ埋めされる）
特に実行ファイルの場合、`p_flags`が`PT_LOAD`のものがメモリ上にロードされる。

~~~c
typedef struct elf64_phdr {
  Elf64_Word p_type;
  Elf64_Word p_flags;
  Elf64_Off p_offset;   /* Segment file offset */
  Elf64_Addr p_vaddr;   /* Segment virtual address */
  Elf64_Addr p_paddr;   /* Segment physical address */
  Elf64_Xword p_filesz;   /* Segment size in file */
  Elf64_Xword p_memsz;    /* Segment size in memory */
  Elf64_Xword p_align;    /* Segment alignment, file & memory */
} Elf64_Phdr;
~~~

メモリへのPT_LOADタイプのロードの関数では、`p_vaddr`から、`p_memsz`分バイナリファイルがマップされる。

~~~c:load_elf_binary()
  /* Now we do a little grungy work by mmapping the ELF image into
     the correct location in memory. */
// elf_phdataはProgram headerの配列
  for(i = 0, elf_ppnt = elf_phdata;
      i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {

// PT_LOADのみ対象
    if (elf_ppnt->p_type != PT_LOAD)
      continue;
...

    if (unlikely (elf_brk > elf_bss)) {
      unsigned long nbyte;

      /* There was a PT_LOAD segment with p_memsz > p_filesz
         before this one. Map anonymous pages, if needed,
         and clear the area.  */
  
// ファイル上のサイズより、大きいメモリサイズ(p_memsz)の指定があった場合、
// ファイルをマップするだけではダメなので、setbrkでmemsz分のメモリの確保と
// 初期化をする。
...

    }

// メモリに対する権限(elf_prot)、Read/Write/Executeの設定、
// 共有ライブラリのロードのため、mmapの開始位置(load_bias)を変更する
...

// elf_map()でmmapする
    error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
        elf_prot, elf_flags, total_size);
~~~

とりあえず、一旦ここまで！