LoginSignup
0
1

More than 3 years have passed since last update.

C/C++ (cdecl) における関数呼び出しの stack 処理

Last updated at Posted at 2020-07-25

復習 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 に書き出しておく。

FuncB終了後のFuncAの戻り先を保存
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 に書き出しておく。

FuncAのEBPの保存
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 の値を更新する。

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 が取られたりもする。

FuncB用変数の確保
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 される。
FuncBからさらに関数が呼ばれた場合
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, ebppop ebp の 2 つを併せた処理が leave 命令。

3-2. ESP の値を初期値 (EBP) に戻して FuncB のスタックフレームを破棄する

処理: mov esp, ebp
目的: FuncB 用に確保したスタック領域 (スタックフレーム) を捨てて、FuncB 呼び出し初期状態 (正確には EBP を push した直後) にする。

FuncBのスタックフレームの破棄
           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 の値に書き戻す。

FuncAのEBPの復元
           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 呼び出しの続きから再開する。

FuncAの続きに戻る
           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 の位置を元に戻す。

FuncAが用意した引数を破棄
           0x1fcc (内容: 変数)
           0x1fd0 (内容: 0x2000)
==== v FuncA 領域 / ^ FuncB 領域 ====
           0x1fd4 (内容例: 0x10000100)
           0x1fd8 (内容: 引数 a)
           0x1fdc (内容: 引数 b)
ESP     -> 0x1fe0 (FuncB を呼び出す直前の stack の先頭)
            ...
EBP     -> 0x2000 (FuncA の stack の始まり)

Ref.

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1