私はパーティーにかなり遅れています。質問が出されてから過去7年間で、あなたは物事をより明確に理解できるようになったと確信しています。もちろん、質問をさらに追求することを選択した場合です。しかし、私はそれでも、特にプロローグとエピローグの理由の部分を試してみようと思いました。
また、受け入れられた回答は、エピローグとプロローグの方法をエレガントかつ非常に簡単に説明しており、参考になります。私はその答えをなぜ(少なくとも論理的な理由)の部分で補足するつもりです。
受け入れられた回答から以下を引用し、説明を拡張してみます。
IA-32(x86)cdeclでは、ebpレジスタは、関数のスタックフレームを追跡するために言語によって使用されます。espレジスタは、スタック上の最新の加算(最上位値)を指すためにプロセッサによって使用されます。
呼び出し命令は2つのことを行います。最初にリターンアドレスをスタックにプッシュし、次に呼び出されている関数にジャンプします。呼び出しの直後に、espはスタック上のリターンアドレスを指します。
上記の引用の最後の行はimmediately after the call, esp points to the return address on the stack.
なんで?
したがって、現在実行されているコードには、以下の(非常にひどく描かれた)図に示すように、次のような状況があるとしましょう。

したがって、次に実行する命令は、たとえばアドレス2です。これがEIPが指している場所です。現在の命令には関数呼び出しがあります(これは内部でアセンブリcall
命令に変換されます)。
理想的には、EIPが次の命令を指しているので、それが実際に実行される次の命令になります。ただし、現在の実行フローパスからの一種の迂回があるため(これは、現在の理由で予想されcall
ます)、EIPの値が変更されます。なんで?これは、別の命令、たとえばアドレス1234(またはその他)にある可能性があるため、実行する必要がある場合があります。ただし、プログラマーが意図したとおりにプログラムの実行フローを完了するには、迂回アクティビティが実行された後、迂回が発生しなかった場合に次に実行されるはずのアドレス2に制御を戻す必要があります。return address
作成されているコンテキストでは、このアドレスを2と呼びますcall
。
問題1
したがって、迂回が実際に発生する前に、差出人アドレス2を一時的にどこかに保存する必要があります。
使用可能なレジスタのいずれか、またはメモリの場所などに格納するための多くの選択肢があった可能性があります。しかし、(正当な理由があると思いますが)リターンアドレスをスタックに格納することが決定されました。
したがって、ここで行う必要があるのは、スタックの最上位がスタック上の次のアドレスを指すように、ESP(スタックポインター)をインクリメントすることです。したがって、アドレス(292など)を指していたTOS'(増分前のTOS)が増分され、アドレス293を指し始めます。ここに。を配置しますreturn address 2
。だからこのようなもの:

これで、差出人住所を一時的にどこかに保存するという目標を達成したようです。ここで、迂回を行う必要がありcall
ます。そして、私たちはできました。しかし、小さな問題があります。呼び出された関数の実行中に、スタックポインタは、他のレジスタ値とともに、複数回操作される可能性があります。
問題2
したがって、私たちの差出人住所はまだスタックの293の場所に格納されていますが、呼び出された関数の実行が終了した後、実行フローは293に移動する必要があることをどのように認識し、そこに差出人住所が見つかりますか?
したがって、(正当な理由で)上記の問題を解決する方法の1つは、スタックアドレス293(リターンアドレスがある場所)をEBPと呼ばれる(指定された)レジスタに格納することです。では、EBPの内容はどうでしょうか。それは上書きされませんか?確かに、それは有効なポイントです。それでは、EBPの現在の内容をスタックに保存してから、このスタックアドレスをEBPに保存しましょう。このようなもの:

スタックポインタがインクリメントされます。EBPの現在の値(EBP'と表記)、つまりxxxは、スタックの最上位、つまりアドレス294に格納されます。これで、EBPの現在の内容のバックアップを取得したので、安全に配置できます。 EBPへの他の値。そのため、スタックの最上位の現在のアドレス、つまりアドレス294をEBPに配置します。
上記の戦略を実行して、上記の問題2を解決します。どのように?したがって、実行フローがどこからリターンアドレスをフェッチする必要があるかを知りたい場合は、次のようになります。
最初にEBPから値を取得し、ESPにその値を指定します。この場合、これにより、TOS(スタックの最上位)がアドレス294を指すようになります(これがEBPに格納されているため)。
次に、EBPの以前の値を復元します。これを行うには、単純に294(TOS)の値を取得します。これはxxx(実際にはEBPの古い値でした)であり、EBPに戻します。
次に、スタックポインタをデクリメントして、スタック内の次に低いアドレス(この場合は293)に移動します。したがって、最終的に293に到達します(それが私たちの問題2であったことを参照してください)。ここで、2である差出人住所が見つかります。
最終的にこの2つがEIPにポップアウトされます。これは、迂回が発生しなかった場合に理想的に実行されるはずの命令です。覚えておいてください。
そして、リターンアドレスを一時的に保存し、それを取得するために、すべてのジャグラーで実行されているのを見たばかりの手順は、関数プロローグ(関数の前call
)とエピローグ(関数の前)で実行される手順とまったく同じret
です。方法はすでに答えられました、私たちは理由も答えました。
最後に、簡潔にするために、スタックアドレスが逆に大きくなる可能性があるという事実には注意を払いませんでした。