ELFプログラムヘッダーのデータ構造と型

Linuxシステムにおいて、実行ファイル(ELF)の具体的なデータ構造や定数、型定義を記述したシステムヘッダーファイルが /usr/include/elf.h 。この仕様書に従って、コンパイラ(gcc)が実際のバイナリファイル内に書き込んだ実体データがELFプログラムヘッダー(Program Header)。このELFプログラムヘッダーは、Linuxカーネル(具体的には fs/binfmt_elf.c のローダー)が実行ファイルをメモリ空間へ配置(マッピング)する際に参照する、固定長のメタデータ(データ構造)の配列として機能する。

仮想メモリシステムと密接に関係しており、主な役割と仕様は以下の3点に定義される。

仮想メモリへのマッピング(プロセスメモリ空間の構築)

Linuxカーネルは、プログラムの実行要求(execve システムコール)を受けると、プログラムヘッダーテーブルを読み込む。

ヘッダーに記述された p_offset(ファイル内オフセット)から p_filesz(ファイルサイズ)分のデータを、指定された p_vaddr(仮想アドレス)へ mmap システムコール等を用いてマッピングし、プロセスの仮想メモリ空間を構築する。

メモリ保護(アクセス権限)の割り当て

各セグメントには、p_flags によってページ単位のアクセス権限(パーミッション)が厳密に設定されている。

これらはCPUのMMU(メモリ管理ユニット)の機能と連動し、不正なメモリ書き換え(バッファオーバーフロー等によるコード改ざん)を防ぐセキュリティ機構として機能する。

セグメントタイプによる役割の識別

プログラムヘッダーの p_type メンバには、その領域がシステム上でどう処理されるべきかを示す定数が格納されている。

プログラムヘッダーのデータ構造

上記で解説した各メタデータ(アドレス、サイズ、権限フラグなど)は、/usr/include/elf.h 内で Elf64_Phdr 構造体 として定義されている。

まず、Elf64_Phdr 構造体 の中身(定義されている変数の一覧)をそのまま表示してみる。

!grep -n -B 10 "Elf64_Phdr;" /usr/include/elf.h

アウトプットは以下のようになる。

typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;

次に、 Elf64_Phdr 構造体 のメンバ(p_type や p_flags)に使われている Elf64_Word という独自型の正体(ビット幅・バイト数)を表示する。

!grep -n "Elf64_Word;" /usr/include/elf.h

アウトプットは以下のようになる。つまり、Elf64_Word の実体が uint32_t(符号なし32ビット整数型)として定義されていることを確認できる。

typedef uint32_t Elf64_Word;

次に、Elf64_Phdr 構造体 の中身を確認する。

!grep -B 10 -w "Elf64_Phdr;" /usr/include/elf.h | sed -n '/typedef struct/,/Elf64_Phdr;/ { s/\bElf64_Word\b/uint32_t/g; s/\bElf64_Off\b/uint64_t/g; s/\bElf64_Addr\b/uint64_t/g; s/\bElf64_Xword\b/uint64_t/g; p; }'

アウトプットは次の通り。

typedef struct
{
uint32_t p_type; /* Segment type */
uint32_t p_flags; /* Segment flags */
uint64_t p_offset; /* Segment file offset */
uint64_t p_vaddr; /* Segment virtual address */
uint64_t p_paddr; /* Segment physical address */
uint64_t p_filesz; /* Segment size in file */
uint64_t p_memsz; /* Segment size in memory */
uint64_t p_align; /* Segment alignment */
} Elf64_Phdr;

Linuxにおける実行ファイルの起動システム

Linuxのカーネルには、実行ファイルを開いたときに「これはどうやって起動するか?」を判断する Binfmt (Binary Format) という仕組みがある。これにより、ELF以外の形式もそのまま実行できるようになっている。

Linuxで動く実行ファイルの4つの分類

ELFファイル(主流のバイナリ)

C言語やGo、Rustなどでコンパイルされた、Linuxの標準的なネイティブバイナリ形式。

スクリプトファイル(テキスト)

先頭の1行目に #!/bin/bash や #!/usr/bin/python3 と書かれているテキストファイル。

カーネルがこの1行目(シバンの行)を読み取り、指定されたインタプリタ(bashやpython本体というELFファイル)に自身を読み込ませて実行する。

他のOS用のバイナリ(PE形式など)

Windows用の実行ファイル(.exe などの PE形式)も、Linux上でそのまま動かせる場合がある。

特殊なカーネルイメージ

Linuxが起動する直前に動くカーネル自身(vmlinuz)などは、古いx86互換のブートセクタと圧縮されたバイナリが合体した、ELFとは異なる特殊な rawバイナリ形式になっていることがある。

Mathematics is the language with which God has written the universe.





















ELFプログラムヘッダーのデータ構造と型 Encoder-Decoder型とDecoder-only型 GenRec式 マルチヘッド・アテンション 学習式位置エンコーディング Dockerのネットワーク構成