このページの目次
ELF ファイル形式
カバー範囲: ELFヘッダー → セクションヘッダーテーブル → プログラムヘッダーテーブル → 手動解析 → readelf/objdump の使用方法 対象: x86-64 / ARM64 ELF, ET_EXEC / ET_DYN / ET_REL
概要
ELF (Executable and Linkable Format) は、Linux 上のほぼすべてのバイナリファイルの標準フォーマットです。実行ファイル、共有ライブラリ (.so)、オブジェクトファイル (.o)、コアダンプはいずれも ELF を使用します。ELF を理解することは学術的な練習ではありません。「なぜこの .so がロードに失敗するのか」「なぜ LD_PRELOAD が効かないのか」「strip 後にコアダンプが解析できないのはなぜか」といった問題をトラブルシューティングする際、ELF ヘッダーの詳細が答えとなります。
ELF の3つのビュー
flowchart LR
subgraph LINK["🔗 Linking View (コンパイル/リンク時)"]
L_HDR["ELF Header"]
L_SHDR["Section Header Table"]
L_TEXT[".text (コード)"]
L_RODATA[".rodata"]
L_DATA[".data"]
L_BSS[".bss"]
L_SYMTAB[".symtab"]
L_DYNSYM[".dynsym"]
L_PLT[".plt"]
L_GOT[".got"]
end
subgraph EXEC["▶️ Execution View (実行時)"]
E_HDR["ELF Header"]
E_PHDR["Program Header Table"]
E_LOAD_RX["PT_LOAD<br/>読み取り+実行可能"]
E_LOAD_RW["PT_LOAD<br/>読み取り+書き込み"]
E_DYNAMIC["PT_DYNAMIC<br/>ダイナミックリンク情報"]
E_INTERP["PT_INTERP<br/>ld.so のパス"]
end
L_TEXT -.->|"所属"| E_LOAD_RX
L_RODATA -.->|"所属"| E_LOAD_RX
L_DATA -.->|"所属"| E_LOAD_RW
L_BSS -.->|"所属"| E_LOAD_RW
L_DYNSYM -.->|"所属"| E_DYNAMIC
classDef link fill:#e3f2fd,stroke:#1565c0
classDef exec fill:#fff3e0,stroke:#ef6c00
class LINK link
class EXEC exec
セクションはリンカ向け(機能ごとに整理)、セグメントはカーネル/ダイナミックリンカ向け(権限とロード要件ごとに整理)です。同じ .text セクションは、1つの PT_LOAD セグメントに属します。
ELF ヘッダー
// /usr/include/elf.h
typedef struct Elf64_Ehdr;
e_ident マジックナンバー
|
# バイト 0-3: 0x7f 'E' 'L' 'F' — マジックナンバー
# バイト 4: EI_CLASS — 1=32ビット, 2=64ビット
# バイト 5: EI_DATA — 1=リトルエンディアン, 2=ビッグエンディアン
# バイト 6: EI_VERSION — 常に 1
# バイト 7: EI_OSABI — 0=UNIX System V, 3=GNU/Linux
# バイト 8-15: パディング
e_type の主要な値
ET_EXEC (2): 従来の実行ファイル。固定アドレスにロードされる (PIE ではない)
ET_DYN (3): 共有ライブラリまたは PIE 実行ファイル。ランダムなベースアドレスにロードされる (現代のデフォルト)
ET_REL (1): リロケータブルなオブジェクトファイル (.o)。リンカの入力
ET_CORE (4): コアダンプファイル
現代の Linux (GCC のデフォルトは -pie) における実行ファイルはすべて ET_DYN です。file /bin/ls で確認できます。
セクションヘッダーテーブル
各セクションはファイル内の領域を記述します。コード、データ、シンボルテーブル、リロケーションテーブルなどです。
typedef struct Elf64_Shdr;
主要なセクション
| セクション | 型 | 内容 |
|---|---|---|
| .text | SHT_PROGBITS | 実行コード |
| .rodata | SHT_PROGBITS | 読み取り専用データ (文字列定数など) |
| .data | SHT_PROGBITS | 初期化済みグローバル/静的変数 |
| .bss | SHT_NOBITS | 未初期化グローバル変数 (スペースを確保するがファイルサイズは増やさない) |
| .plt | SHT_PROGBITS | Procedure Linkage Table (遅延バインディング用) |
| .got / .got.plt | SHT_PROGBITS | Global Offset Table |
| .dynsym | SHT_DYNSYM | ダイナミックシンボルテーブル (実行時に必要) |
| .symtab | SHT_SYMTAB | 完全なシンボルテーブル (strip 後に削除可能) |
| .dynstr / .strtab | SHT_STRTAB | 文字列テーブル |
| .rela.dyn / .rela.plt | SHT_RELA | リロケーションテーブル |
| .interp | SHT_PROGBITS | ダイナミックリンカのパス (例: /lib64/ld-linux-x86-64.so.2) |
| .dynamic | SHT_DYNAMIC | ダイナミックリンク情報 (DT_NEEDED, DT_SONAME, ...) |
| .init / .fini | SHT_PROGBITS | 初期化/終了コード (GCC -finit-array) |
プログラムヘッダーテーブル
プログラムヘッダーはカーネルに対して「このファイルをどのようにロードするか」を指示します。各セグメント (PT_LOAD) は、どのデータをどの仮想アドレスに、どのような権限でマッピングするかを指定します。
typedef struct Elf64_Phdr;
PT_LOAD のマッピング処理
// カーネル fs/binfmt_elf.c: elf_map()
// 各 PT_LOAD セグメントに対して:
// 1. mmap(p_vaddr, p_memsz, p_flags に基づく PROT, MAP_PRIVATE, fd, p_offset)
// 2. p_memsz > p_filesz の場合 → 残りのバイトは 0 でマッピングされる (anonymous MAP_ANONYMOUS)
// これにより .bss の実行時動作が実現される
// 例: /bin/ls には 2 つの PT_LOAD がある:
// Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
// LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x002e58 0x002e58 R E 0x1000
// LOAD 0x0000000000003000 0x0000000000403000 0x0000000000403000 0x000fb8 0x001640 RW 0x1000
//
// 最初の LOAD: コード+読み取り専用データ → R+X (memsz == filesz, .bss なし)
// 2 番目の LOAD: データ+.bss → R+W (memsz > filesz, 残りは .bss を埋めるためにゼロ埋め)
手動解析
# ELF ヘッダー
# セクションヘッダー
# プログラムヘッダー (セグメント)
# ダイナミックシンボルテーブル
# リロケーションテーブル
# .dynamic セクション (DT_NEEDED, DT_RPATH, ...)
# 文字列の全文検索
|
# 逆アセンブル
|
# PIE かどうかを確認
# "ELF 64-bit LSB pie executable" → PIE
# "ELF 64-bit LSB executable" → non-PIE
参考
- 仕様: ELF spec (System V ABI, Chapter 4-5),
man 5 elf - ヘッダーファイル:
/usr/include/elf.h,/usr/include/link.h - ツール:
readelf,objdump,nm,size,strip,patchelf - LWN: "The ELF object file format" シリーズ
キーワード: ELF ヘッダー, セクション, セグメント, PT_LOAD, SHT_SYMTAB, ET_DYN, ET_EXEC, PIE, readelf, objdump