概要
Linuxで使われるバイナリのフォーマットのELFについて、特にexecシステムコールから呼ばれる実行ファイルのロードの処理を調べた。
ELFの構造
ヘッダが3つと、それらでポイントされるデータによって構成されている。
フラグの意味と構造体の表はWikipediaにまとまっている。
- 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()でポインタ配列として読み込まれる。
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
のものがメモリ上にロードされる。
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
分バイナリファイルがマップされる。
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);
とりあえず、一旦ここまで!