1. Cの文字列がCPUへ届くまで
1.1 ストレージ、メモリ、CPU
| 要素 | このプログラムでの役割 |
|---|---|
| ストレージ | 実行ファイルを長期保存する |
| メモリ | 実行中の命令とデータをアドレス付きで置く |
| CPU | メモリから命令を取得し、解釈し、実行する |
| レジスタ | CPU内部の小さく高速な状態保存場所 |
ストレージにあるバイナリは、そのままCPUが読むわけではない。OSがプロセスを作り、実行に必要な部分を仮想メモリに対応付ける。CPUはその仮想アドレスを使って命令とデータにアクセスする。
1.2 コンパイルから実行まで

図: ソースの関数名は、最終的にはアドレス付きの命令列になる。
- プリプロセッサが
#includeなどを処理する。 - コンパイラがCをアセンブリ相当の命令へ変換する。
- アセンブラが命令をオブジェクトファイルへ符号化する。
- リンカが必要なコードとデータをまとめ、ELF実行ファイルを作る。
- OSのローダがELFのプログラムヘッダを読み、命令やデータを仮想メモリへ対応付ける。
- OSが初期スタックとレジスタ状態を準備し、エントリポイントから実行を始める。
mainは実行ファイルの先頭命令ではない。ランタイムの初期化後、最終的にmainが呼ばれる。本編はその後から追う。
1.3 デモのビルド条件
cc -std=c11 -O0 -g3 -Wall -Wextra \
-fno-omit-frame-pointer -fno-stack-protector -fno-pie \
ret2win.c -o ret2win -no-pie -Wl,-z,noexecstack| 指定 | 目的 |
|---|---|
-O0 |
最適化による関数の削除や変形を抑える |
-fno-omit-frame-pointer |
RBPでフレームを追いやすくする |
-fno-stack-protector |
カナリを無効化し、原因の連鎖を観察する |
-fno-pie -no-pie |
デモで本体コードのアドレスを固定する |
-z noexecstack |
スタックは実行不可にし、既存のwinへの遷移だけを見る |
コンパイラは16バイトの配列へ128バイトを書き込み得ることを警告する。それでもビルドするのは、警告対象の原因を自作プログラムで観察するためである。