ゼロからのOS自作入門 を読んでいて、ELFファイルというのがどういうものなのか全くわからなかったので調べてみました。
ELF(Executable and Linkable Format)は、UNIX系のオペレーティングシステムで広く採用されている実行ファイルやオブジェクトファイルの標準的なフォーマットで、実行ファイル(バイナリ)、共有ライブラリ(.so)、カーネルモジュール(.ko)などに利用されています。
ELFファイルの種類
ELFファイルには以下のざっくり4種類のファイルがあります
-
ET_REL (再配置可能ファイル)
オブジェクトファイル (.o
)。コンパイルされたが、リンクされていないファイル。
複数のオブジェクトファイルをリンクすることで、実行可能ファイル(ET_EXEC)や共有オブジェクト(ET_DYN)を生成します。- セクションヘッダテーブルを含み、
.text
(コード)、.data
(データ)、.bss
(未初期化データ)などのセクションを保持します。 - プログラムヘッダテーブルは通常含まれません。
- 再配置情報やシンボルテーブルを含み、リンク時にアドレスの解決や配置を行います。
- セクションヘッダテーブルを含み、
-
ET_EXEC (実行可能ファイ)
リンクが完了し、直接実行可能なバイナリファイル。- プログラムヘッダテーブルを含み、実行時に必要なセグメント(
.text
(コード)、.data
(データ))の情報を持ちます。 -
e_entry
(エントリーポイント)が指定され、OSがこのアドレスからプログラムの事項を開始します - セクションヘッダテーブルは含まれる場合と含まれない場合があります。
- プログラムヘッダテーブルを含み、実行時に必要なセグメント(
-
ET_DYN (共有オブジェクト)
動的リンクに使用される共有ライブラリ(libc.so.6
など)や一独立実行可能ファイル(PIE)。- プログラムヘッダテーブルを含みます。
- 位置独立コード(PIC)を含み、実行時に任意のアドレスにロード可能です。
-
.so
(共有ライブラリ)やPIE形式の実行ファイルに使用されます。
-
ET_CORE (コアファイル)
プログラムが異常終了した際に生成される、メモリダンプファイル。
プログラムがセグメンテーションフォルトなどでクラッシュしたときに生成されるcore
ファイル。- 実行中のプロセスのメモリ内容、レジスタ状態、スタック情報などを含みます。
-
gdb
でプログラムのクラッシュ時の状態を解析するために使用されます。 - セクションヘッダは通常含まれません。
ELFファイルの構造
ELFファイルは以下の4要素で構成されています。
- ELFヘッダ
- プログラムヘッダテーブル
-
セクション・セグメント
-
セクション
ELFファイル内で処理できる最小の分割不可能な単位で、リンクやデバッグ時に使用される情報単位です。
リンク、デバッグ時に利用されます。 -
セグメント
実行時にメモリにロードする単位となる、セクションの集合 です。
実行時に利用されます。
-
セクション
- セクションヘッダテーブル

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
#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ヘッダを確認
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
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
-
-
p_flags
: セグメントの属性を表すフラグ-
PF_X (1)
(実行可能) -
PF_W (2)
(書き込み可能) -
PF_R (4)
(読み取り可能) - テキストセグメントは通常
PF_X
とPF_R
を持つ - データセグメントは通常
PF_X
PF_W
PF_R
を持つ - 詳しくはこちら: Segment Flags | Oracle
-
readelfで実際のプログラムヘッダテーブルを確認
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
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
: セクションの種類 -
sh_flags
: セクションのフラグ
readelfで実際のセクションヘッダテーブルを確認
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)
実行時のメモリへのロード方法
- ELFヘッダーの解析
- ELFヘッダを読み取りプログラムヘッダーテーブルの位置(
e_phoff
) 、エントリサイズ(e_phentsize
)、要素数(e_phnum
)を取得します。
- ELFヘッダを読み取りプログラムヘッダーテーブルの位置(
- プログラムヘッダーテーブルの読み込み
- プログラムヘッダを読み取る
- セグメントのロード
-
p_type
がPT_LOAD
のセグメントを対象とする -
p_offset
からp_filesz
バイトまでをメモリ上のp_vaddr
にロード -
p_memsz
がp_filesz
よりも大きい場合は残りの領域を0
で初期化
-
- エントリーポイントへのジャンプ
- ELFヘッダーの
e_entry
のアドレスに制御を移し、プログラムの実行を開始
- ELFヘッダーの