ELF (Executable and Linkable Format) ファイルとは

[ゼロからのOS自作入門 ](https://www.amazon.co.jp/dp/B08Z3MNR9J/) を読んでいて、ELFファイルが全くわからなかったので調べてみました。


ELF（Executable and Linkable Format）は、UNIX系のOSで広く採用されている実行ファイルやオブジェクトファイルの標準的なフォーマットで、実行ファイル、共有ライブラリ（.so）、カーネルモジュール（.ko）などに利用されています。

- 参考
    - [ELF Formatについて](https://www.hazymoon.jp/OpenBSD/annex/elf.html)
    - [Chapter 7 Object File Format - Linker and Libraries Guide | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblh/index.html)

# ELFファイルの種類

ELFファイルには以下のざっくり4種類のファイルがあります

1. **ET_REL (再配置可能ファイル)**
オブジェクトファイル (`.o`)。コンパイルされたが、リンクされていないファイル。
複数のオブジェクトファイルをリンクすることで、実行可能ファイル(ET_EXEC)や共有オブジェクト(ET_DYN)を生成します。
    - セクションヘッダテーブルを含み、`.text` (コード)、 `.data`(データ)、`.bss`(未初期化データ)などのセクションを保持します。
    - プログラムヘッダテーブルは通常含まれません。
    - 再配置情報やシンボルテーブルを含み、リンク時にアドレスの解決や配置を行います。
1. **ET_EXEC (実行可能ファイ)**
リンクが完了し、直接実行可能なバイナリファイル。
    - プログラムヘッダテーブルを含み、実行時に必要なセグメント(`.text` (コード)、 `.data`(データ))の情報を持ちます。
    - `e_entry` (エントリーポイント)が指定され、OSがこのアドレスからプログラムの事項を開始します
    - セクションヘッダテーブルは含まれる場合と含まれない場合があります。
1. **ET_DYN (共有オブジェクト)**
動的リンクに使用される共有ライブラリ(`libc.so.6`など)や一独立実行可能ファイル(PIE)。
    - プログラムヘッダテーブルを含みます。
    - 位置独立コード(PIC)を含み、実行時に任意のアドレスにロード可能です。
    - `.so` (共有ライブラリ)やPIE形式の実行ファイルに使用されます。
1. **ET_CORE (コアファイル)**
プログラムが異常終了した際に生成される、メモリダンプファイル。  
プログラムがセグメンテーションフォルトなどでクラッシュしたときに生成される `core` ファイル。
    - 実行中のプロセスのメモリ内容、レジスタ状態、スタック情報などを含みます。
    - `gdb` でプログラムのクラッシュ時の状態を解析するために使用されます。
    - セクションヘッダは通常含まれません。


# ELFファイルの構造

[File Format | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblj/index.html)

ELFファイルは以下の4要素で構成されています。

1.  **ELFヘッダ**
1.  **プログラムヘッダテーブル**
1.  **セクション・セグメント**
    - **セクション**
    ELFファイル内で処理できる最小の分割不可能な単位で、リンクやデバッグ時に使用される情報単位です。
    リンク、デバッグ時に利用されます。
    - **セグメント** 
    実行時にメモリにロードする単位となる、**セクションの集合**  です。
    実行時に利用されます。
1.  **セクションヘッダテーブル**


<img width="1080" alt="スクリーンショット 2025-05-24 16.26.57.png (56.8 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/a424085f-0d13-47a8-867f-d69f4d99bcf8.png">














## ELF 64 bit のデータ型

ELFファイルのヘッダで利用されるデータ型の定義

| Name            | Size (byte) | Alignment | Purpose                  |
|-----------------|------|-----------|--------------------------|
| Elf64_Addr      | 8    | 8         | 符号なしプログラムアドレス  |
| Elf64_Half      | 2    | 2         | 符号なし 16bit  整数  |
| Elf64_Off       | 8    | 8         | 符号なしファイルオフセット     |
| Elf64_Sword     | 4    | 4         | 符号付き32bit  整数           |
| Elf64_Word      | 4    | 4         | 符号なし 32bit  整数         |
| Elf64_Xword     | 8    | 8         | 符号なし 64bit  整数    |
| Elf64_Sxword    | 8    | 8         | 符号付き 64bit  整数      |
| unsigned char   | 1    | 1         | 符号なし 8bit  整数   |

## ELFヘッダ


詳しくはこちら -> [ELF Header - Linker and Libraries Guide | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-43405/index.html)

```c
#define EI_NIDENT 16

// 64bit ELFファイルのヘッダ
typedef struct {
  unsigned char e_ident[EI_NIDENT];  // ELFファイルの識別情報を含む1byte * 16個の要素を持つ配列で、マジックナンバー（0x7F 'E' 'L' 'F'）やファイルクラス（32bit/64bit）、エンディアン、ELFバージョンなどが含まれる
  Elf64_Half    e_type;      // ファイルの種類(ET_EXEC, ET_DYN, ET_REL, ET_CORE)
  Elf64_Half    e_machine;   // CPUアーキテクチャ (EM_386など)
  Elf64_Word    e_version;   // ELFファイルのバージョン。通常は `EV_CURRENT`（1）
  Elf64_Addr    e_entry;     // プログラムのエントリーポイントの仮想アドレス
  Elf64_Off     e_phoff;     // プログラムヘッダテーブルのオフセット
  Elf64_Off     e_shoff;     // セクションヘッダテーブルのオフセット
  Elf64_Word    e_flags;     // プロセッサ固有のフラグ (現在、定義されたフラグはない)
  Elf64_Half    e_ehsize;    // ELFヘッダのサイズ(バイト)
  Elf64_Half    e_phentsize; // プログラムヘッダテーブルのエントリ1個のサイズ(バイト)
  Elf64_Half    e_phnum;     // プログラムヘッダテーブルのエントリ数
  Elf64_Half    e_shentsize; // セクションヘッダテーブルのエントリ1個のサイズ(バイト)
  Elf64_Half    e_shnum;     // セクションヘッダテーブルのエントリ数
  Elf64_Half    e_shstrndx;  // セクションヘッダテーブルの、セクション名文字列テーブルに結びつけられたエントリへのインデックス (よくわからん)
} Elf64_Ehdr;
```

- `e_type`
    - **ET_NONE (0) (ファイルタイプなし )**
    - **ET_REL (1) (再配置可能ファイル)**
    - **ET_EXEC (2) (実行可能ファイ)**
    - **ET_DYN (3) (共有オブジェクト)**
    - **ET_CORE (4) (コアファイル)**

### readelfで実際のELFヘッダを確認

```bash
readelf -h build/kernel/kernel.elf 
# ELF Header:
#   Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
#   Class:                             ELF64
#   Data:                              2's complement, little endian
#   Version:                           1 (current)
#   OS/ABI:                            UNIX - System V
#   ABI Version:                       0
#   Type:                              EXEC (Executable file)
#   Machine:                           Advanced Micro Devices X86-64
#   Version:                           0x1
#   Entry point address:               0x101120
#   Start of program headers:          64 (bytes into file)
#   Start of section headers:          1072 (bytes into file)
#   Flags:                             0x0
#   Size of this header:               64 (bytes)
#   Size of program headers:           56 (bytes)
#   Number of program headers:         4
#   Size of section headers:           64 (bytes)
#   Number of section headers:         14
#   Section header string table index: 12
```

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

詳しくはこちら -> [Program Header - Linker and Libraries Guide | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-83432/index.html)

```c
typedef struct {
  Elf64_Word  p_type;    // PT_PHDR, PT_LOADなどのセグメント種別
  Elf64_Word  p_flags;   // セグメントの属性を表すフラグ
  Elf64_Off   p_offset;  // ファイル先頭からセグメントの先頭バイトまでのオフセット
  Elf64_Addr  p_vaddr;   // セグメントの先頭バイトの仮想アドレス
  Elf64_Addr  p_paddr;   // 物理アドレス指定が関連するシステムにおけるセグメントの物理アドレス。
  Elf64_Xword p_filesz;  // セグメントのファイルイメージ内のバイト数。 (0になることがある)
  Elf64_Xword p_memsz;   // セグメントのメモリイメージ内のバイト数。 (0になることがある)
  Elf64_Xword p_align;   // メモリ中およびファイル中でのセグメントのアラインメント
} Elf64_Phdr;
```

- `p_type` : セグメント種別
    - `PT_LOAD (1)` : ロード可能なセグメント
    - `PT_PHDR (6)` : プログラムヘッダーテーブル自体の位置とサイズを、ファイル内とプログラムのメモリイメージ内の両方で指定します。
    - 詳しくはこちら: [Segment Types | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblk/index.html#chapter6-69880)
- `p_flags` : セグメントの属性を表すフラグ
    -  `PF_X (1)` (実行可能)
    -  `PF_W (2)` (書き込み可能)
    -  `PF_R (4)` (読み取り可能)
    -  テキストセグメントは通常 `PF_X` と `PF_R` を持つ
    - データセグメントは通常 `PF_X` `PF_W` `PF_R` を持つ
    - 詳しくはこちら: [Segment Flags | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblk/index.html#chapter6-tbl-39)

### readelfで実際のプログラムヘッダテーブルを確認

```bash
readelf -l build/kernel/kernel.elf 
# Elf file type is EXEC (Executable file)
# Entry point 0x101120
# There are 4 program headers, starting at offset 64
# 
# Program Headers:
#   Type           Offset             VirtAddr           PhysAddr
#                  FileSiz            MemSiz              Flags  Align
#   PHDR           0x0000000000000040 0x0000000000100040 0x0000000000100040
#                  0x00000000000000e0 0x00000000000000e0  R      0x8
#   LOAD           0x0000000000000000 0x0000000000100000 0x0000000000100000
#                  0x0000000000000120 0x0000000000000120  R      0x1000
#   LOAD           0x0000000000000120 0x0000000000101120 0x0000000000101120
#                  0x0000000000000013 0x0000000000000013  R E    0x1000
#   GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
#                  0x0000000000000000 0x0000000000000000  RW     0x0
# 
#  Section to Segment mapping:
#   Segment Sections...
#    00     
#    01     
#    02     .text 
#    03
```
## セクションヘッダテーブル

詳しくはこちら -> [Sections - Linker and Libraries Guide | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-94076/index.html)

```c
typedef struct {
  Elf64_Word   sh_name;       // セクション名。値はセクションヘッダ文字列テーブルセクション内のインデックス
  Elf64_Word   sh_type;       // セクションの種類
  Elf64_Xword  sh_flags;      // セクションの属性フラグ
  Elf64_Addr   sh_addr;       // セクションがプロセスのメモリイメージに出現する場合のセクション先頭の仮想アドレス
  Elf64_Off    sh_offset;     // ファイルの先頭からセクションの最初のバイトまでのバイトオフセット。
  Elf64_Xword  sh_size;       // セクションのサイズ（バイト単位）
  Elf64_Word   sh_link;       // セクションヘッダーテーブルインデックスリンク??
  Elf64_Word   sh_info;       // セクションタイプに応じて解釈が異なる追加情報
  Elf64_Xword  sh_addralign;  // セクションのアラインメント(一部のセクションにはアドレスアライメント制約がある)
  Elf64_Xword  sh_entsize;    // シンボルテーブルなどの固定サイズのエントリのテーブルを持つ一部のセクションにおける各エントリのサイズ(バイト単位)
} Elf64_Shdr;
```

- `sh_type` : セクションの種類
    - 詳しくはこちら: [Section Types - Linker and Libraries Guide | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblj/index.html#chapter6-73445)
- `sh_flags` : セクションのフラグ
    - 詳しくはこちら : [ELF Section Attribute Flags - Linker and Libraries Guide | Oracle](https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblj/index.html#chapter6-10675)

### readelfで実際のセクションヘッダテーブルを確認

```bash
readelf -S build/kernel/kernel.elf 
# There are 14 section headers, starting at offset 0x430:
# 
# Section Headers:
#   [Nr] Name              Type             Address           Offset
#        Size              EntSize          Flags  Link  Info  Align
#   [ 0]                   NULL             0000000000000000  00000000
#        0000000000000000  0000000000000000           0     0     0
#   [ 1] .text             PROGBITS         0000000000101120  00000120
#        0000000000000013  0000000000000000  AX       0     0     16
#   [ 2] .debug_abbrev     PROGBITS         0000000000000000  00000133
#        000000000000002d  0000000000000000           0     0     1
#   [ 3] .debug_info       PROGBITS         0000000000000000  00000160
#        000000000000002f  0000000000000000           0     0     1
#   [ 4] .debug_str_o[...] PROGBITS         0000000000000000  0000018f
#        0000000000000018  0000000000000000           0     0     1
#   [ 5] .debug_str        PROGBITS         0000000000000000  000001a7
#        0000000000000069  0000000000000001  MS       0     0     1
#   [ 6] .debug_addr       PROGBITS         0000000000000000  00000210
#        0000000000000010  0000000000000000           0     0     1
#   [ 7] .comment          PROGBITS         0000000000000000  00000220
#        0000000000000042  0000000000000001  MS       0     0     1
#   [ 8] .debug_frame      PROGBITS         0000000000000000  00000268
#        0000000000000038  0000000000000000           0     0     8
#   [ 9] .debug_line       PROGBITS         0000000000000000  000002a0
#        000000000000005e  0000000000000000           0     0     1
#   [10] .debug_line_str   PROGBITS         0000000000000000  000002fe
#        0000000000000037  0000000000000001  MS       0     0     1
#   [11] .symtab           SYMTAB           0000000000000000  00000338
#        0000000000000048  0000000000000018          13     2     8
#   [12] .shstrtab         STRTAB           0000000000000000  00000380
#        0000000000000097  0000000000000000           0     0     1
#   [13] .strtab           STRTAB           0000000000000000  00000417
#        0000000000000015  0000000000000000           0     0     1
# Key to Flags:
#   W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
#   L (link order), O (extra OS processing required), G (group), T (TLS),
#   C (compressed), x (unknown), o (OS specific), E (exclude),
#   D (mbind), l (large), p (processor specific)
```

# 実行時のメモリへのロード方法

1. ELFヘッダーの解析
    1. ELFヘッダを読み取りプログラムヘッダーテーブルの位置( `e_phoff` ) 、エントリサイズ( `e_phentsize` )、要素数( `e_phnum` )を取得します。
1. プログラムヘッダーテーブルの読み込み
    1. プログラムヘッダを読み取る
1. セグメントのロード
    1.  `p_type` が `PT_LOAD` のセグメントを対象とする
    1. `p_offset` から `p_filesz` バイトまでをメモリ上の `p_vaddr` にロード
    1. `p_memsz` が `p_filesz` よりも大きい場合は残りの領域を `0` で初期化
1. エントリーポイントへのジャンプ
    1. ELFヘッダーの `e_entry` のアドレスに制御を移し、プログラムの実行を開始