254

DrawSquare() が DrawLine() を呼び出すウィキペディアからのこの例を使用すると、

代替テキスト

(この図では、下部に上位アドレスがあり、上部に下位アドレスがあることに注意してください。)

誰かが私にこの文脈で何ebpを説明できますか?esp

私が見たところ、スタック ポインターは常にスタックの一番上を指し、ベース ポインターは現在の関数の先頭を指していると思いますか? または何?


編集:これは、Windowsプログラムのコンテキストで意味します

edit2: また、どのように機能しeipますか?

edit3: MSVC++ の次のコードがあります。

var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr  8
hPrevInstance= dword ptr  0Ch
lpCmdLine= dword ptr  10h
nShowCmd= dword ptr  14h

それらはすべて dword のように見えるため、それぞれ 4 バイトを使用します。したがって、hInstance から var_4 までに 4 バイトのギャップがあることがわかります。彼らは何ですか?ウィキペディアの写真に見られるように、それは返送先住所だと思いますか?


(編集者注:質問に属さないマイケルの回答から長い引用を削除しましたが、フォローアップの質問が編集されました):

これは、関数呼び出しの流れが次のとおりであるためです。

* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals

私の質問 (最後、願っています!) では、呼び出したい関数の引数をポップした瞬間からプロローグの最後まで、正確には何が起こるのでしょうか? それらの瞬間にebp、espがどのように進化するかを知りたいです(プロローグがどのように機能するかはすでに理解しています。引数をスタックにプッシュした後、プロローグの前に何が起こっているかを知りたいだけです)。

4

7 に答える 7

258

espあなたが言うように、スタックの一番上です。

ebp通常esp、関数の開始時にに設定されます。関数パラメーターとローカル変数は、から定数オフセットをそれぞれ加算および減算することによってアクセスされますebp。すべてのx86呼び出し規約はebp、関数呼び出し間で保持されると定義しています。 ebpそれ自体が実際には前のフレームのベースポインタを指しているため、デバッガでスタックをウォークし、他のフレームのローカル変数を表示できます。

ほとんどの関数プロローグは次のようになります。

push ebp      ; Preserve current frame pointer
mov ebp, esp  ; Create new frame pointer pointing to current stack top
sub esp, 20   ; allocate 20 bytes worth of locals on stack.

次に、関数の後半で、次のようなコードを使用できます(両方のローカル変数が4バイトであると想定)

mov [ebp-4], eax    ; Store eax in first local
mov ebx, [ebp - 8]  ; Load ebx from second local

有効にできるFPOまたはフレームポインタ省略のebp最適化は、実際にはこれを排除し、別のレジスタとして使用し、から直接ローカルにアクセスespしますが、デバッガが以前の関数呼び出しのスタックフレームに直接アクセスできなくなるため、デバッグが少し難しくなります。

編集:

更新された質問の場合、スタックに欠落している2つのエントリは次のとおりです。

var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
*savedFramePointer = dword ptr 0*
*return address = dword ptr 4*
hInstance = dword ptr  8h
PrevInstance = dword ptr  0C
hlpCmdLine = dword ptr  10h
nShowCmd = dword ptr  14h

これは、関数呼び出しのフローが次のとおりであるためです。

  • プッシュパラメータ(hInstanceなど)
  • リターンアドレスをプッシュする関数を呼び出す
  • 押すebp
  • 地元の人のためのスペースを割り当てる
于 2009-09-08T18:46:30.933 に答える
124

ESP現在のスタック ポインタであり、ワードまたはアドレスがスタックにプッシュまたはポップされるたびに変更されます。EBPコンパイラが関数のパラメーターとローカル変数を追跡するには、ESP直接使用するよりも便利な方法です。

一般に (これはコンパイラによって異なる場合があります)、呼び出される関数へのすべての引数は、呼び出し元の関数によってスタックにプッシュされます (通常、関数プロトタイプで宣言された順序とは逆の順序ですが、これは異なります)。 . 次に関数が呼び出され、リターン アドレス( EIP) がスタックにプッシュされます。

関数に入ると、古いEBP値がスタックにプッシュさEBPれ、 の値に設定されますESP。次に、ESP関数のローカル変数と一時変数にスペースを割り当てるために (スタックがメモリ内で下方に成長するため) がデクリメントされます。その時点から、関数の実行中、関数への引数は(関数呼び出しの前にプッシュされたため) からののオフセットでスタックに配置されEBP、ローカル変数は(からののオフセットで配置されるEBPため)それらは関数エントリの後にスタックに割り当てられました)。EBPが フレーム ポインターと呼ばれる理由は、フレーム ポインターの中心を指しているからです。関数呼び出しフレーム.

終了時に関数がしなければならないことはESP、 (スタックからローカル変数の割り当てを解除し、スタックの一番上にEBPあるエントリを公開する) の値に設定し、スタックから古い値をポップし、次に関数をリターンします(リターンアドレスをにポップします)。EBPEBPEIP

呼び出し元の関数に戻るESPと、他の関数を呼び出す直前にスタックにプッシュされた関数引数を削除するためにインクリメントできます。この時点で、スタックは呼び出された関数を呼び出す前と同じ状態に戻ります。

于 2009-09-08T19:44:52.343 に答える
17

あなたはそれを正しく持っています。スタックポインタはスタックの一番上の項目を指し、ベースポインタは関数が呼び出される前のスタックの「前の」一番上を指します。

関数を呼び出すと、ローカル変数がスタックに格納され、スタックポインタがインクリメントされます。関数から戻ると、スタック上のすべてのローカル変数がスコープ外になります。これを行うには、スタックポインタをベースポインタ(関数呼び出しの前の「前の」トップ)に戻します。

この方法でメモリ割り当てを行うことは、非常高速で効率的です。

于 2009-09-08T18:48:39.727 に答える
7

編集:より良い説明については、x86 アセンブリに関する WikiBook のx86 Disassembly/Functions and Stack Framesを参照してください。Visual Studio の使用に興味があるかもしれない情報を追加しようとしました。

呼び出し元の EBP を最初のローカル変数として格納することは、標準スタック フレームと呼ばれ、Windows のほぼすべての呼び出し規則で使用できます。呼び出し元または呼び出し先が渡されたパラメーターの割り当てを解除するかどうか、およびどのパラメーターがレジスターで渡されるかに違いがありますが、これらは標準のスタック フレームの問題と直交しています。

Windows プログラムについて言えば、おそらく Visual Studio を使用して C++ コードをコンパイルするかもしれません。Microsoft は Frame Pointer Omission と呼ばれる最適化を使用しているため、dbghlp ライブラリと実行可能ファイルの PDB ファイルを使用せずにスタックをウォークすることはほとんど不可能であることに注意してください。

このフレーム ポインターの省略は、コンパイラーが古い EBP を標準の場所に格納せず、EBP レジスターを別のものに使用することを意味します。そのため、ローカル変数が特定の関数に必要なスペースを知らずに呼び出し元の EIP を見つけるのは困難です。もちろん、Microsoft はこの場合でもスタック ウォークを実行できる API を提供していますが、PDB ファイル内のシンボル テーブル データベースの検索に時間がかかりすぎるユース ケースもあります。

コンパイル単位で FPO を回避するには、/O2 の使用を避けるか、プロジェクトの C++ コンパイル フラグに /Oy- を明示的に追加する必要があります。おそらく、Release 構成で FPO を使用する C または C++ ランタイムに対してリンクするため、dbghlp.dll なしでスタック ウォークを実行するのは困難です。

于 2009-09-08T19:20:51.037 に答える
6

まず、x86 スタックは高いアドレス値から低いアドレス値に構築されるため、スタック ポインターはスタックの下部を指します。スタック ポインターは、プッシュ (または呼び出し) への次の呼び出しが次の値を配置するポイントです。その操作は、C/C++ ステートメントと同等です。

 // push eax
 --*esp = eax
 // pop eax
 eax = *esp++;

 // a function call, in this case, the caller must clean up the function parameters
 move eax,some value
 push eax
 call some address  // this pushes the next value of the instruction pointer onto the
                    // stack and changes the instruction pointer to "some address"
 add esp,4 // remove eax from the stack

 // a function
 push ebp // save the old stack frame
 move ebp, esp
 ... // do stuff
 pop ebp  // restore the old stack frame
 ret

ベース ポインターは、現在のフレームの先頭です。ebp は通常、返送先住所を指します。ebp+4 は、関数の最初のパラメーター (またはクラス メソッドの this 値) を指します。ebp-4 は、関数の最初のローカル変数 (通常は ebp の古い値) を指しているため、前のフレーム ポインターを復元できます。

于 2009-09-08T18:59:05.900 に答える
1

アセンブリプログラミングを行ってから長い時間が経ちましたが、このリンクは役に立つかもしれません...

プロセッサには、データを格納するために使用される一連のレジスタがあります。これらの一部は直接値ですが、その他は RAM 内の領域を指しています。レジスタは特定の特定のアクションに使用される傾向があり、アセンブリのすべてのオペランドは特定のレジスタに一定量のデータを必要とします。

スタック ポインターは、他のプロシージャを呼び出すときに主に使用されます。最新のコンパイラでは、一連のデータが最初にスタックにダンプされ、その後に戻りアドレスが続きます。これにより、システムは、戻るように指示されたときにどこに戻るかを知ることができます。スタック ポインターは、新しいデータをスタックにプッシュできる次の場所を指し、再びポップされるまでそこに留まります。

ベース レジスタまたはセグメント レジスタは、大量のデータのアドレス空間を指すだけです。2 番目のレジスターと組み合わせると、ベース ポインターはメモリを巨大なブロックに分割し、2 番目のレジスターはこのブロック内の項目を指します。そのためのベース ポインタは、データ ブロックのベースを指します。

アセンブリは非常に CPU 固有であることに注意してください。私がリンクしたページは、さまざまなタイプの CPU に関する情報を提供します。

于 2009-09-08T18:50:18.290 に答える
-8

esp は「Extended Stack Pointer」の略で、ebp は「Something Base Pointer」を表し、eip は「Something Instruction Pointer」を表します。スタック ポインタは、スタック セグメントのオフセット アドレスを指します。 . Base Pointer は、追加セグメントのオフセット アドレスを指します。命令ポインタは、コード セグメントのオフセット アドレスを指します。さて、セグメントについて...それらはプロセッサのメモリ領域の64KBの小さな分割です...このプロセスはメモリセグメンテーションとして知られています。この投稿がお役に立てば幸いです。

于 2010-08-01T12:06:41.173 に答える