---
title: ELF ファイル形式
url: https://doc.liz6.com/ja/systems-programming/01-elf-and-binary-formats/01-elf-file-format
locale: ja
area: systems-programming
tags:
- systems-programming
- elf-and-binary-formats
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: ELFヘッダー → セクションヘッダーテーブル → プログラムヘッダーテーブル → 手動解析 → readelf/objdump の使用方法 対象: x86-64 / ARM64 ELF, ET_EXEC / ET_DYN / ET_REL'
---

# 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つのビュー

```mermaid
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 ヘッダー

```c
// /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 マジックナンバー

```bash
$ 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` で確認できます。

---

## セクションヘッダーテーブル

各セクションはファイル内の領域を記述します。コード、データ、シンボルテーブル、リロケーションテーブルなどです。

```c
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;
```

### 主要なセクション

| セクション | 型 | 内容 |
|---------|------|------|
| .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) は、どのデータをどの仮想アドレスに、どのような権限でマッピングするかを指定します。

```c
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 のマッピング処理

```c
// カーネル 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 を埋めるためにゼロ埋め)
```

---

## 手動解析

```bash
# 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*
