x86アセンブリのレジスタで使用されるプッシュ/ポップ命令の機能は何ですか?


101

アセンブラについて読んでいると、プロセッサの特定のレジスタをプッシュし、後でもう一度ポップして以前の状態に戻すと書いている人によく出くわします。

  • どうすればレジスターをプッシュできますか?どこにプッシュされますか?なぜこれが必要なのですか?
  • これは、単一のプロセッサ命令に要約されますか、それとももっと複雑ですか?

3
警告:現在の回答はすべてIntelのアセンブリ構文で示されています。例えば、AT&T構文でプッシュポップのようなポストフィックスを使用してbwl、またはq操作されるメモリのサイズを意味します。例:pushl %eaxおよびpopl %eax
ホーケン

5
@hawken AT&T構文(特にガス)を飲み込むことができるほとんどのアセンブラでは、オペランドサイズがオペランドサイズから推定できる場合は、サイズの接尾辞を省略できます。これは、%eax常に32ビットのサイズであるため、指定した例の場合です。
Gunther Piez 2012年

回答:


153

値(必ずしもレジスタに格納されている必要はありません)をプッシュすることは、その値をスタックに書き込むことを意味します。

ポップとは、スタックの一番上にあるものを復元することを意味しますレジスタ。これらは基本的な手順です。

push 0xdeadbeef      ; push a value to the stack
pop eax              ; eax is now 0xdeadbeef

; swap contents of registers
push eax
mov eax, ebx
pop ebx

5
プッシュとポップの明示的なオペランドはr/m、レジスタだけでなく、であるため、次のことができpush dword [esi]ます。またはpop dword [esp]、同じ値をロードしてから同じアドレスに保存することもできます。(github.com/HJLebbink/asm-dude/wiki/POP)。あなたが「必ずしもレジスターではない」と言うので、私はこれに言及するだけです。
Peter Cordes 2018年

2
あなたはすることもできpop、メモリの領域に:pop [0xdeadbeef]
SSアン

こんにちは、push / popとpushq / popqの違いは何ですか?私はMacOSの/インテルによ
SteakOverflow

46

レジスターをプッシュする方法は次のとおりです。x86について話していると思います。

push ebx
push eax

スタックにプッシュされます。ESPx86システムでスタックが下向きに成長するにつれて、レジスタの値はプッシュされた値のサイズにデクリメントされます。

値を保持する必要があります。一般的な使用法は

push eax           ;   preserve the value of eax
call some_method   ;   some method is called which will put return value in eax
mov  edx, eax      ;    move the return value to edx
pop  eax           ;    restore original eax

Apushはx86の単一の命令であり、内部で2つのことを行います。

  1. ESPプッシュされた値のサイズだけレジスタをデクリメントします。
  2. プッシュされた値をESPレジスタの現在のアドレスに格納します。

@vavanが修正のリクエストを送信しました
jgh fun-

38

どこにプッシュされますか?

esp - 4。より正確に:

  • esp 4で減算されます
  • 値はにプッシュされます esp

pop これを逆にします。

System V ABIはrsp、プログラムの実行開始時に適切なスタック位置を指すようにLinuxに指示します。プログラムの起動時のデフォルトのレジスタ状態(asm、linux)は何ですか?これはあなたが通常使うべきものです。

どうすればレジスターをプッシュできますか?

最小限のGNUGASの例:

.data
    /* .long takes 4 bytes each. */
    val1:
        /* Store bytes 0x 01 00 00 00 here. */
        .long 1
    val2:
        /* 0x 02 00 00 00 */
        .long 2
.text
    /* Make esp point to the address of val2.
     * Unusual, but totally possible. */
    mov $val2, %esp

    /* eax = 3 */
    mov $3, %ea 

    push %eax
    /*
    Outcome:
    - esp == val1
    - val1 == 3
    esp was changed to point to val1,
    and then val1 was modified.
    */

    pop %ebx
    /*
    Outcome:
    - esp == &val2
    - ebx == 3
    Inverses push: ebx gets the value of val1 (first)
    and then esp is increased back to point to val2.
    */

実行可能なアサーションを使用したGitHubでの上記。

なぜこれが必要なのですか?

それらの命令を簡単に介して実施することができることが事実であるmovaddsub

それらが存在する理由は、これらの命令の組み合わせが非常に頻繁であるため、Intelがそれらを提供することを決定したためです。

これらの組み合わせが頻繁に行われる理由は、レジスタの値を一時的にメモリに保存および復元して、上書きされないようにするためです。

問題を理解するには、いくつかのCコードを手動でコンパイルしてみてください。

主な問題は、各変数をどこに格納するかを決定することです。

理想的には、すべての変数がレジスタに収まります。これは、アクセスするのに最も速いメモリです(現在、RAMの約100倍高速です)。

しかしもちろん、特にネストされた関数の引数の場合、レジスタよりも多くの変数を簡単に持つことができるため、唯一の解決策はメモリに書き込むことです。

任意のメモリアドレスに書き込むことができますが、関数呼び出しと戻り値のローカル変数と引数は、メモリの断片化を防ぐ優れたスタックパターンに適合するため、これを処理するための最良の方法です。それをヒープアロケータを書くことの狂気と比較してください。

次に、コンパイラにレジスタ割り当てを最適化させます。これはNP完全であり、コンパイラを作成する上で最も難しい部分の1つだからです。この問題はレジスタ割り当てと呼ばグラフ彩色と同型です。

コンパイラのアロケータがレジスタだけでなくメモリに物を格納することを強制される場合、それはスピルとして知られています。

これは、単一のプロセッサ命令に要約されますか、それとももっと複雑ですか?

私たちが確かに知っているのは、Intelがapushpop命令をしているということだけです。したがって、それらはその意味で1つの命令です。

内部的には、1つは変更用esp、もう1つはメモリIOを実行するために、複数のマイクロコードに拡張でき、複数のサイクルを必要とします。

ただしpush、より具体的であるため、単一の命令が他の命令の同等の組み合わせよりも高速である可能性もあります。

これはほとんど文書化されていません:


4
どのようにpush/ popuopsにデコードするかについて推測する必要はありません。パフォーマンスカウンターのおかげで、実験的なテストが可能であり、Agner Fogはそれを実行し、指示表を公開しました。Pentium-M以降のCPUは、スタックエンジンのおかげでシングルuop push/popを備えています(Agnerのmicroarch pdfを参照)。これには、Intel / AMD特許共有契約のおかげで、最近のAMDCPUが含まれます。
ピーターコーデス2016年

@PeterCordes素晴らしい!では、パフォーマンスカウンターは、マイクロオペレーションをカウントするためにIntelによって文書化されていますか?
CiroSantilli郝海东冠事病六四事件法轮功2016年

また、regからこぼれたローカル変数は、それらのいずれかが実際に使用されている場合、通常はL1キャッシュで引き続きホットになります。しかし、レジスタからの読み取りは事実上無料で、待ち時間はゼロです。したがって、用語の定義方法によっては、L1キャッシュよりもはるかに高速です。スタックにこぼれた読み取り専用ローカルの場合、主なコストは追加のロードuops(メモリオペランドの場合もあれば、個別のmovロードの場合もあります)です。こぼれた非const変数の場合、ストア転送のラウンドトリップは多くの余分なレイテンシーになります(直接転送する場合と比べて5c余分にかかり、ストアの指示は安くありません)。
ピーターコーデス2016年

ええ、いくつかの異なるパイプラインステージ(発行/実行/リタイア)で合計uopsのカウンターがあるので、融合ドメインまたは非融合ドメインを数えることができます。たとえば、この回答を参照してください。その答えを今書き直している場合は、ocperf.pyラッパースクリプトを使用して、カウンターの簡単な記号名を取得します。
ピーターコーデス2016年

23

レジスタのプッシュとポップは、これと同等の舞台裏にあります。

push reg   <= same as =>      sub  $8,%rsp        # subtract 8 from rsp
                              mov  reg,(%rsp)     # store, using rsp as the address

pop  reg    <= same as=>      mov  (%rsp),reg     # load, using rsp as the address
                              add  $8,%rsp        # add 8 to the rsp

これはx86-64At&t構文であることに注意してください。

ペアとして使用すると、レジスタをスタックに保存し、後で復元できます。他の用途もあります。


4
はい、これらのシーケンスはプッシュ/ポップを正しくエミュレートします。(プッシュ/ポップがフラグに影響しないことを除いて)。
ピーターコーデス2016

2
/のlea rsp, [rsp±8]代わりにadd/subを使用して、フラグに対するpush/の効果をより適切にエミュレートすることをお勧めしますpop
ルスラン

12

ほとんどすべてのCPUはスタックを使用します。プログラムスタックは、ハードウェアでサポートされている管理機能を備えたLIFO手法です。

スタックは、CPUメモリヒープの最上位に通常割り当てられ、反対方向に成長する(PUSH命令でスタックポインタが減少する)プログラム(RAM)メモリの量です。スタックに挿入するための標準的な用語はPUSHであり、スタックから削除するための標準的な用語はPOPです。

スタックは、スタックポインタとも呼ばれるスタック用CPUレジスタを介して管理されるため、CPUがPOPまたはPUSHを実行する場合、スタックポインタはレジスタまたは定数をスタックメモリにロード/格納し、スタックポインタは自動的に減少またはプッシュされるワード数に応じて増加しますまたはスタックに(から)ポップします。

アセンブラ命令を介して、スタックに格納できます。

  1. CPUレジスタと定数。
  2. 関数またはプロシージャのリターンアドレス
  3. 関数/プロシージャの入力/出力変数
  4. 関数/プロシージャローカル変数。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.