復習 for me.
処理を理解するうえで押さえておくべき registers
- EIP: Instruction Pointer、次に実行される address を指す pointer
- ESP: Stack Pointer、現在の stack の先頭を指す pointer
- EBP: Stack Base Pointer、現在の関数の stack frame の始まりを指す pointer で、この位置の中身にはさらに 1 つ前の関数の EBP の値が格納されている。
- EAX: Accumulator Register、戻り値が格納される register
FuncA から FuncB (引数 a, b を受け取る) を呼ぶ手順を考える。
初期状態 (FuncA から FuncB を呼び出す直前)
ESP -> 0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
関数呼び出し、FuncA 側
1-1. FuncA 内で、FuncB に渡すための引数を push する
処理: push b
, push a
、後ろの引数から push するらしい。
目的: FuncB が参照できるように FuncA 側から引数を stack に書き出しておく。
ESP -> 0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
1-2. call 命令により、関数呼び出し後に戻ってくる位置を push する
処理: call
、call 命令により call 命令 + 4 の位置の code address が push される。stack 領域とは関係ない code 領域の話。その後に EIP が FuncB の先頭に書き換わって処理が始まる。
目的: FuncB の処理が終わった後に、どの位置から処理を再開するかを覚えておくため stack に書き出しておく。
ESP -> 0x1fd4 (内容の例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
関数呼び出し、FuncB 側
2-1. あとで復元できるように EBP の値を push して書き出す
処理: push ebp
目的: FuncB の終了時に、呼び出し前に FuncA が保持していた EBP の値に戻せるように、FuncA の EBP の値を stack に書き出しておく。
ESP -> 0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
2-2. 自分自身 (FuncB) 用に EBP の値を更新する
処理: mov ebp, esp
目的: 2-1 で FuncA 用の EBP を保存したので、次に自分 (FuncB) 用に EBP の値を更新する。
ESP/EBP -> 0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
0x2000 (FuncA の stack の始まり)
2-3. (もし必要であれば) 変数の確保
処理: sub esp, 4
(変数 32 bits 分の場合)
目的: ESP を -4 することにより、32 bits 分の領域を stack に確保する。
備考: 変数を初期化しない場合、この場所にもともと入っていた値が見える。あと実際には alignment の処理で多めに stack が取られたりもする。
ESP -> 0x1fcc (内容: 変数)
EBP -> 0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
0x2000 (FuncA の stack の始まり)
2-4. 補足
- 変数は EBP - 4 でアクセスする。
- 引数は EBP + 8 でアクセスする。
- FuncB からさらに関数を呼ぶ場合、同様に引数と戻り先が push される。
ESP/EBP -> 0x1fc0 (内容: 0x1fd0)
==== v FuncB 領域 / ^ 新関数領域 ====
0x1fc4 (内容: 戻り先 address)
0x1fc8 (内容: 引数)
0x1fcc (内容: 変数)
0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
0x2000 (FuncA の stack の始まり)
関数終了処理、FuncB 側
3-1. 戻り値を EAX に入れる
処理: mov eax
目的: 戻り値は EAX に入れるという決まり。関数の呼び元は戻ってきた後に EAX を参照して戻り値を受け取る。
(追記 3-2 と 3-3 の補足
この mov esp, ebp
と pop ebp
の 2 つを併せた処理が leave
命令。
3-2. ESP の値を初期値 (EBP) に戻して FuncB のスタックフレームを破棄する
処理: mov esp, ebp
目的: FuncB 用に確保したスタック領域 (スタックフレーム) を捨てて、FuncB 呼び出し初期状態 (正確には EBP を push した直後) にする。
0x1fcc (内容: 変数)
ESP/EBP -> 0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
0x2000 (FuncA の stack の始まり)
3-3. EBP の値を FuncA 用に書き戻す
処理: pop ebp
目的: FuncA に戻るための準備として、EBP を FuncA の値に書き戻す。
0x1fcc (内容: 変数)
0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
ESP -> 0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
3-4. FuncA の戻り先に飛ぶ
処理: ret
、戻り先の値が pop されて EIP が書き換わる。
目的: FuncB 呼び出しの続きから再開する。
0x1fcc (内容: 変数)
0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
ESP -> 0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
関数終了処理、FuncA 側
4-1. 引数分の stack を元に戻す -> 初期状態に戻る
処理: add esp, +8
(引数 a, b それぞれ 32 bits の場合)
目的: 引数分の stack の位置を元に戻す。
0x1fcc (内容: 変数)
0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
0x1fd4 (内容例: 0x10000100)
0x1fd8 (内容: 引数 a)
0x1fdc (内容: 引数 b)
ESP -> 0x1fe0 (FuncB を呼び出す直前の stack の先頭)
...
EBP -> 0x2000 (FuncA の stack の始まり)
Ref.