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 によってページ単位のアクセス権限(パーミッション)が厳密に設定されている。
R E)の権限が与えられ、書き込みは禁止される。R W)の権限が与えられる。これらはCPUのMMU(メモリ管理ユニット)の機能と連動し、不正なメモリ書き換え(バッファオーバーフロー等によるコード改ざん)を防ぐセキュリティ機構として機能する。
プログラムヘッダーの p_type メンバには、その領域がシステム上でどう処理されるべきかを示す定数が格納されている。
PT_LOAD:メモリに直接ロードされるべき実行コードやデータPT_DYNAMIC:動的リンクに必要な、共有ライブラリ(libc.so等)の依存関係やシンボルテーブルの情報上記で解説した各メタデータ(アドレス、サイズ、権限フラグなど)は、/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のカーネルには、実行ファイルを開いたときに「これはどうやって起動するか?」を判断する Binfmt (Binary Format) という仕組みがある。これにより、ELF以外の形式もそのまま実行できるようになっている。
C言語やGo、Rustなどでコンパイルされた、Linuxの標準的なネイティブバイナリ形式。
/usr/bin/ls、/usr/bin/python3、/usr/bin/bash先頭の1行目に #!/bin/bash や #!/usr/bin/python3 と書かれているテキストファイル。
カーネルがこの1行目(シバンの行)を読み取り、指定されたインタプリタ(bashやpython本体というELFファイル)に自身を読み込ませて実行する。
Windows用の実行ファイル(.exe などの PE形式)も、Linux上でそのまま動かせる場合がある。
binfmt_misc 機能を利用して、WindowsのシステムコールをLinuxのものにリアルタイム変換して実行する。Linuxが起動する直前に動くカーネル自身(vmlinuz)などは、古いx86互換のブートセクタと圧縮されたバイナリが合体した、ELFとは異なる特殊な rawバイナリ形式になっていることがある。
Mathematics is the language with which God has written the universe.