このページの目次

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 {
    unsigned char e_ident[16];   // Magic + Class + Data + Version + ABI
    uint16_t      e_type;        // ET_EXEC, ET_DYN (PIE/.so), ET_REL (.o), ET_CORE
    uint16_t      e_machine;     // EM_X86_64, EM_AARCH64, EM_RISCV
    uint32_t      e_version;     // EV_CURRENT (1)
    uint64_t      e_entry;       // エントリポイントアドレス (_start)
    uint64_t      e_phoff;       // プログラムヘッダーテーブルのオフセット
    uint64_t      e_shoff;       // セクションヘッダーテーブルのオフセット
    uint32_t      e_flags;
    uint16_t      e_ehsize;      // ELF ヘッダーのサイズ (x86_64 では 64 バイト)
    uint16_t      e_phentsize;   // 各プログラムヘッダーのサイズ
    uint16_t      e_phnum;       // プログラムヘッダーの数
    uint16_t      e_shentsize;   // 各セクションヘッダーのサイズ
    uint16_t      e_shnum;       // セクションヘッダーの数
    uint16_t      e_shstrndx;    // セクション名文字列テーブルのインデックス
} Elf64_Ehdr;

e_ident マジックナンバー

$ xxd /bin/ls | head -1
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............

# バイト 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 {
    uint32_t   sh_name;       // セクション名が .shstrtab 内のどこを指すかのオフセット
    uint32_t   sh_type;       // SHT_PROGBITS, SHT_SYMTAB, SHT_STRTAB, SHT_RELA, ...
    uint64_t   sh_flags;      // SHF_WRITE, SHF_ALLOC, SHF_EXECINSTR, ...
    uint64_t   sh_addr;       // メモリにロードした後の仮想アドレス (実行ファイルの場合)
    uint64_t   sh_offset;     // ファイル内のオフセット
    uint64_t   sh_size;       // セクションのサイズ
    uint32_t   sh_link;       // 関連するセクションのインデックス (例: symtab は strtab と関連)
    uint32_t   sh_info;       // 追加情報
    uint64_t   sh_addralign;  // アライメント
    uint64_t   sh_entsize;    // テーブルの場合、各エントリのサイズ
} Elf64_Shdr;

主要なセクション

セクション内容
.textSHT_PROGBITS実行コード
.rodataSHT_PROGBITS読み取り専用データ (文字列定数など)
.dataSHT_PROGBITS初期化済みグローバル/静的変数
.bssSHT_NOBITS未初期化グローバル変数 (スペースを確保するがファイルサイズは増やさない)
.pltSHT_PROGBITSProcedure Linkage Table (遅延バインディング用)
.got / .got.pltSHT_PROGBITSGlobal Offset Table
.dynsymSHT_DYNSYMダイナミックシンボルテーブル (実行時に必要)
.symtabSHT_SYMTAB完全なシンボルテーブル (strip 後に削除可能)
.dynstr / .strtabSHT_STRTAB文字列テーブル
.rela.dyn / .rela.pltSHT_RELAリロケーションテーブル
.interpSHT_PROGBITSダイナミックリンカのパス (例: /lib64/ld-linux-x86-64.so.2)
.dynamicSHT_DYNAMICダイナミックリンク情報 (DT_NEEDED, DT_SONAME, ...)
.init / .finiSHT_PROGBITS初期化/終了コード (GCC -finit-array)

プログラムヘッダーテーブル

プログラムヘッダーはカーネルに対して「このファイルをどのようにロードするか」を指示します。各セグメント (PT_LOAD) は、どのデータをどの仮想アドレスに、どのような権限でマッピングするかを指定します。

typedef struct {
    uint32_t   p_type;    // PT_LOAD, PT_DYNAMIC, PT_INTERP, PT_NOTE, PT_GNU_STACK, ...
    uint32_t   p_flags;   // PF_R(4) | PF_W(2) | PF_X(1)
    uint64_t   p_offset;  // ファイル内のセグメントのオフセット
    uint64_t   p_vaddr;   // 仮想アドレス (ASLR によりランダムなベースアドレスが加算される)
    uint64_t   p_paddr;   // 物理アドレス (無関係。MMU がないシステムでのみ使用)
    uint64_t   p_filesz;  // ファイル内のサイズ
    uint64_t   p_memsz;   // メモリ内のサイズ (memsz > filesz の場合、残りはゼロ埋めされ、.bss として使用される)
    uint64_t   p_align;   // アライメント
} 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 ヘッダー
readelf -h /bin/ls

# セクションヘッダー
readelf -S /bin/ls

# プログラムヘッダー (セグメント)
readelf -l /bin/ls

# ダイナミックシンボルテーブル
readelf --dyn-syms /bin/ls

# リロケーションテーブル
readelf -r /bin/ls

# .dynamic セクション (DT_NEEDED, DT_RPATH, ...)
readelf -d /bin/ls

# 文字列の全文検索
strings /bin/ls | head -20

# 逆アセンブル
objdump -d /bin/ls | head

# PIE かどうかを確認
file /bin/ls
# "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