10

スタックポインタレジスタをプッシュおよびポップする動作を理解しようとしています。AT&Tの場合:

pushl %esp

popl %esp

計算された値をに戻すことに注意してください%esp

私はこれらの指示を順番にではなく、独立して検討しています。に格納されている値%espは常にインクリメント/デクリメント前の値であることを知っていますが、アセンブリ言語で動作をどのように表すことができますか?これは私がこれまでに思いついたものです。

pushl %esp(FLAGSと一時レジスタへの影響を無視して):

movl %esp, %edx     1. save value of %esp
subl  $4, %esp      2. decrement stack pointer
movl %edx, (%esp)   3. store old value of %esp on top of stack

の場合popl %esp

movl (%esp), %esp   You wouldn’t need the increment portion. 

これは正しいです?そうでない場合、どこが間違っているのですか?

4

2 に答える 2

12

Intel® 64 and IA-32 Architectures Developer's Manual: Combined Volumes (実際には第 2 巻、またはhttps://www.felixcloutier.com/x86/pushの HTML スクレイプ)で述べpush espられているように:

PUSH ESP 命令は、命令が実行される前に存在していた ESP レジスタの値をプッシュします。PUSH 命令が、オペランド アドレスの計算に ESP レジスタが使用されるメモリ オペランドを使用する場合、ESP レジスタがデクリメントされる前にオペランドのアドレスが計算されます。

そしてpop esphttps://www.felixcloutier.com/x86/pop)に関して:

POP ESP 命令は、スタックの古い最上位にあるデータが宛先に書き込まれる前に、スタック ポインター (ESP) をインクリメントします。

pop 16(%esp)

ESP レジスタがメモリ内のデスティネーション オペランドをアドレス指定するためのベース レジスタとして使用される場合、POP 命令は、ESP レジスタをインクリメントした後、オペランドの実効アドレスを計算します。

はい、FLAGS と を変更することを除いて、擬似コードは正しい%edxです。

于 2013-02-19T22:50:58.690 に答える
1

はい、これらのシーケンスは FLAGS への影響を除いて正しく、もちろんpush %espclobber しません%edx。代わりに、他の処理を行う前に入力 (ソース オペランド) のスナップショットを作成するプリミティブ操作を考えるのではなく、個別のステップに分割する場合は、内部の一時的な1を想像してください。push

(同様に/pop DSTとしてモデル化することができ、 pop のすべての効果は、それがスタック ポインターであるか、スタック ポインターを含む場合でも、評価されて宛先に書き込まれる前に終了します。)pop %tempmov %temp, DST

pushESP の特殊なケースでも機能する同等のもの

(これらすべてにおいて、SS が正常に構成された 32 ビット互換モードまたは保護モードを想定しています。スタック アドレス サイズがモードと一致する場合、そうでない可能性さえあると想定しています。64 ビット モードと同等の%rsp動作-8/と同じように+8. 16ビットモードでは(%sp)アドレッシングモードが許可されないため、これを疑似コードと見なす必要があります.)

#push SRC         for any source operand including %esp or 1234(%esp)
   mov  SRC, %temp
   lea  -4(%esp), %esp         # esp-=4 without touching FLAGS
   mov  %temp, (%esp)

すなわちmov SRC, %temp; push %temp
または、とにかく中断できないトランザクション (単一のpush命令)
を記述しているため、保存する前に ESP を移動する必要はありません

#push %REG              # or immediate, but not memory source
   mov  %REG, -4(%esp)
   lea  -4(%esp), %esp

(この単純なバージョンは、メモリ ソースを使用して実際にアセンブルすることはなく、レジスタまたはイミディエイトのみであり、割り込みまたはシグナル ハンドラが mov と LEA の間で実行される場合は安全ではありません。実際のアセンブリではmov mem, mem、2 つの明示的なアドレッシング モードでは安全ではありません。 t はエンコードpush (%eax)できませんが、メモリの宛先が暗黙的であるためです. メモリ ソースの場合でも疑似コードと見なすことができます. しかし、一時的なスナップショットは、最初のブロックやmov SRC, %temp/のように、内部で発生することのより現実的なモデルですpush %temp.)

実際のプログラムでそのようなシーケンスを実際に使用することについて話している場合、一時レジスタ (最初のバージョン)、または (2 番目のバージョン) 割り込みを無効にするか、赤の ABI を使用せずに正確に複製する方法はないと思いますpush %esp-ゾーン。(非カーネル コードの x86-64 System V のように、複製することができますpush %rsp。)

pop同等のもの:

#pop DST   works for any operand
  mov  (%esp), %temp
  lea  4(%esp), %esp      # esp += 4 without touching FLAGS
  mov  %temp, DST         # even if DST is %esp or 1234(%esp)

すなわちpop %temp/ mov %temp, DST。これは、ESP を含むメモリ アドレッシング モードの場合を正確に反映しています。インクリメントDSTの ESP の値が使用されます。これに関する Intel のドキュメントを;で確認しました。. これにより、Skylake CPU の GDB でシングルステップしたときに書き込まれた dword のすぐ下にある dword にdword がコピーされました。その命令が実行される前に ESP を使用してアドレス計算が行われた場合、4 バイトのギャップが生じます。push $5pop -8(%esp)5push-8(%esp)

の特殊なケースではpop %esp、yes はインクリメントを踏んで、次のように単純化します。

#pop %esp  # 3 uops on Skylake, 1 byte
   mov  (%esp), %esp             # 1 uop on Skylake.  3 bytes of machine-code size

Intel のマニュアルには、誤解を招く疑似コードがあります

Intel の命令セット マニュアル エントリ (SDM vol.2) の Operation セクションにある疑似コードは、スタック ポインタの特殊なケースを正確に反映していません。説明セクションの余分な段落 ( @nrz の回答で引用) のみがその権利を取得します。

https://www.felixcloutier.com/x86/popは、(StackAddrSize = 32 および OperandSize = 32 の場合) DEST へのロードと ESP のインクリメントを示しています。

     DEST ← SS:ESP; (* Copy a doubleword *)
     ESP ← ESP + 4;

しかし、pop %espこれは ESP = load(SS:ESP) の後に ESP += 4 が発生することを意味するため、誤解を招く可能性があります。正しい疑似コードは使用します

 if ... operand size etc.
     TEMP ← SS:ESP; (* Copy a doubleword *)
     ESP ← ESP + 4;

 ..
 // after all the if / else size blocks:
 DEST ← TEMP 

インテルは、読み取り/書き込み宛先オペランドの元の状態のスナップショットpshufbを作成するために擬似コードが開始する場所など、他の命令に対してこの権利を取得します。TEMP ← DEST

同様に、https://www.felixcloutier.com/x86/push#operationは、 RSP が最初にデクリメントされることを示しており、srcオペランドがその前にスナップショットされていることは示していません。テキストの説明セクションの余分な段落のみが、その特殊なケースを正しく処理します。


AMD のマニュアルVolume 3: General-Purpose and System Instructions (2021 年 3 月)は、これについて同様に間違っています (私の強調):

スタック ポインタ (SS:rSP) が指す値を指定されたレジスタまたはメモリ位置 コピーし、rSP を 16 ビット ポップの場合は 2、32 ビット ポップの場合は 4、64 ビット ポップの場合は 8 ずつインクリメントします。ポップ。

Intel とは異なり、スタック ポインター自体または rSP を含むメモリ オペランドを使用してポップする特殊なケースについても文書化していません。少なくともここにはありません。検索してpush rsppush esp何も見つかりませんでした。

(AMD はrSP、SS によって選択された現在のスタックサイズ属性に応じて、SP / ESP / RSP を意味します。)

AMD には、Intel のような疑似コード セクションがありません。少なくとも、プッシュ/ポップのような単純と思われる命令には対応していません。( 用がありpushaます。)


脚注 1 : それは一部の CPU で発生する可能性さえあります (私はそうは思いませんが)。たとえば、Skylake では、 Agner Fogは、フロントエンドで 2 uops と測定されましたが、他のレジスタをプッシュするためのマイクロ融合ストアは 1 でした。 push %esp

Intel CPU には、アーキテクチャ レジスタのように名前が変更されるが、マイクロコードによってのみアクセス可能なレジスタがいくつかあることはわかっています。たとえば、 https://blog.stuffedcow.net/2013/05/measuring-rob-capacity/は、「内部使用のためのいくつかの追加のアーキテクチャレジスタ」について言及しています。だからmov %esp, %temp/push %temp理論的には、それがどのようにデコードされたかである可能性があります。

しかし、より可能性の高い説明は、プッシュ/ポップ操作の後に OoO バックエンドが明示的に ESP を読み取るたびに得られるように、命令の長いシーケンスで余分に測定された uops は、push %esp単なるスタック同期 uopsであるということです。たとえば、 push %eax/mov %esp, %edxもスタック同期 uop を引き起こします。(「スタックエンジンesp -= 4」は、の部分に余分なuopを必要としないようにするものですpush

push %espたとえば、予約したばかりのスタック空間のアドレスをプッシュする場合などに便利です。

  sub   $8, %esp
  push  %esp
  push  $fmt         # "%lf"
  call  scanf
  movsd 8(%esp), %xmm0

  # add $8, %esp    # balance out the pushes at some point, or just keep using that allocated space for something.  Or clean it up just before returning along with the space for your local var.

pop %espSkylake で 3 uops、1 つのロード (p23)、任意の整数 ALU ポート (2p0156) で 2 つの ALU が必要です。そのため、効率はさらに低下しますが、基本的にユースケースはありません。スタック上のスタック ポインターを有効に保存/復元することはできません。保存した場所に移動する方法がわかっている場合は、 で復元できますadd

于 2021-10-08T02:28:44.963 に答える