web-dev-qa-db-ja.com

正確にベースポインターとスタックポインターとは何ですか?彼らは何を指し示していますか?

この例 ウィキペディアから来て、DrawSquare()がDrawLine()を呼び出します。

alt text

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

誰がebpespがこのコンテキストにあるかを説明できますか?

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


編集:私は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から4バイトのvar_4へのギャップがあることがわかります。彼らは何ですか?ウィキペディアの写真に見られるように、それは返信先だと思いますか?


(編集者のメモ:Michaelの回答から長い引用を削除しました。これは質問に含まれていませんが、フォローアップの質問は編集されました):

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

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

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

206

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をプッシュ
  • 地元の人たちにスペースを割り当てる
214
Michael

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

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

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

終了時に、すべての関数はESPをEBPの値に設定し(スタックからローカル変数の割り当てを解除し、スタックの最上部にエントリEBPを公開します)、古いものをポップしますスタックからのEBP値。その後、関数が戻ります(戻りアドレスをEIPにポップします)。

74
David R Tribble

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

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

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

15
Robert Cartaino

EDIT:より良い説明については、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-を追加する必要があります。おそらく、リリース構成でFPOを使用するCまたはC++ランタイムに対してリンクするため、dbghlp.dllなしでスタックウォークを実行するのは困難です。

7
wigy

まず、x86スタックは高いアドレス値から低いアドレス値に構築されるため、スタックポインターはスタックの一番下を指します。スタックポインターは、Push(または呼び出し)の次の呼び出しが次の値を配置するポイントです。操作は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の古い値)を指しているため、前のフレームポインターを復元できます。

6
jmucchiello

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

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

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

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

アセンブリはCPU固有であることに注意してください。リンクしているページには、さまざまな種類のCPUに関する情報があります。

1
Wim ten Brink