アセンブラについて読んでいると、プロセッサの特定のレジスタをプッシュし、後でもう一度ポップして以前の状態に戻すと書いている人によく出くわします。
- どうすればレジスターをプッシュできますか?どこにプッシュされますか?なぜこれが必要なのですか?
- これは、単一のプロセッサ命令に要約されますか、それとももっと複雑ですか?
アセンブラについて読んでいると、プロセッサの特定のレジスタをプッシュし、後でもう一度ポップして以前の状態に戻すと書いている人によく出くわします。
%eax
常に32ビットのサイズであるため、指定した例の場合です。
回答:
値(必ずしもレジスタに格納されている必要はありません)をプッシュすることは、その値をスタックに書き込むことを意味します。
ポップとは、スタックの一番上にあるものを復元することを意味しますにレジスタ。これらは基本的な手順です。
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
r/m
、レジスタだけでなく、であるため、次のことができpush dword [esi]
ます。またはpop dword [esp]
、同じ値をロードしてから同じアドレスに保存することもできます。(github.com/HJLebbink/asm-dude/wiki/POP)。あなたが「必ずしもレジスターではない」と言うので、私はこれに言及するだけです。
pop
、メモリの領域に:pop [0xdeadbeef]
レジスターをプッシュする方法は次のとおりです。x86について話していると思います。
push ebx
push eax
スタックにプッシュされます。ESP
x86システムでスタックが下向きに成長するにつれて、レジスタの値はプッシュされた値のサイズにデクリメントされます。
値を保持する必要があります。一般的な使用法は
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つのことを行います。
ESP
プッシュされた値のサイズだけレジスタをデクリメントします。ESP
レジスタの現在のアドレスに格納します。どこにプッシュされますか?
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.
*/
なぜこれが必要なのですか?
それらの命令を簡単に介して実施することができることが事実であるmov
、add
とsub
。
それらが存在する理由は、これらの命令の組み合わせが非常に頻繁であるため、Intelがそれらを提供することを決定したためです。
これらの組み合わせが頻繁に行われる理由は、レジスタの値を一時的にメモリに保存および復元して、上書きされないようにするためです。
問題を理解するには、いくつかのCコードを手動でコンパイルしてみてください。
主な問題は、各変数をどこに格納するかを決定することです。
理想的には、すべての変数がレジスタに収まります。これは、アクセスするのに最も速いメモリです(現在、RAMの約100倍高速です)。
しかしもちろん、特にネストされた関数の引数の場合、レジスタよりも多くの変数を簡単に持つことができるため、唯一の解決策はメモリに書き込むことです。
任意のメモリアドレスに書き込むことができますが、関数呼び出しと戻り値のローカル変数と引数は、メモリの断片化を防ぐ優れたスタックパターンに適合するため、これを処理するための最良の方法です。それをヒープアロケータを書くことの狂気と比較してください。
次に、コンパイラにレジスタ割り当てを最適化させます。これはNP完全であり、コンパイラを作成する上で最も難しい部分の1つだからです。この問題はレジスタ割り当てと呼ばれ、グラフ彩色と同型です。
コンパイラのアロケータがレジスタだけでなくメモリに物を格納することを強制される場合、それはスピルとして知られています。
これは、単一のプロセッサ命令に要約されますか、それとももっと複雑ですか?
私たちが確かに知っているのは、Intelがapush
とpop
命令をしているということだけです。したがって、それらはその意味で1つの命令です。
内部的には、1つは変更用esp
、もう1つはメモリIOを実行するために、複数のマイクロコードに拡張でき、複数のサイクルを必要とします。
ただしpush
、より具体的であるため、単一の命令が他の命令の同等の組み合わせよりも高速である可能性もあります。
これはほとんど文書化されていません:
push
、pop
1つのマイクロオペレーションを実行することを示唆していると述べています。 push
/ pop
uopsにデコードするかについて推測する必要はありません。パフォーマンスカウンターのおかげで、実験的なテストが可能であり、Agner Fogはそれを実行し、指示表を公開しました。Pentium-M以降のCPUは、スタックエンジンのおかげでシングルuop push
/pop
を備えています(Agnerのmicroarch pdfを参照)。これには、Intel / AMD特許共有契約のおかげで、最近のAMDCPUが含まれます。
mov
ロードの場合もあります)です。こぼれた非const変数の場合、ストア転送のラウンドトリップは多くの余分なレイテンシーになります(直接転送する場合と比べて5c余分にかかり、ストアの指示は安くありません)。
ocperf.py
ラッパースクリプトを使用して、カウンターの簡単な記号名を取得します。
レジスタのプッシュとポップは、これと同等の舞台裏にあります。
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構文であることに注意してください。
ペアとして使用すると、レジスタをスタックに保存し、後で復元できます。他の用途もあります。
lea rsp, [rsp±8]
代わりにadd
/sub
を使用して、フラグに対するpush
/の効果をより適切にエミュレートすることをお勧めしますpop
。
ほとんどすべてのCPUはスタックを使用します。プログラムスタックは、ハードウェアでサポートされている管理機能を備えたLIFO手法です。
スタックは、CPUメモリヒープの最上位に通常割り当てられ、反対方向に成長する(PUSH命令でスタックポインタが減少する)プログラム(RAM)メモリの量です。スタックに挿入するための標準的な用語はPUSHであり、スタックから削除するための標準的な用語はPOPです。
スタックは、スタックポインタとも呼ばれるスタック用CPUレジスタを介して管理されるため、CPUがPOPまたはPUSHを実行する場合、スタックポインタはレジスタまたは定数をスタックメモリにロード/格納し、スタックポインタは自動的に減少またはプッシュされるワード数に応じて増加しますまたはスタックに(から)ポップします。
アセンブラ命令を介して、スタックに格納できます。
b
、w
、l
、またはq
操作されるメモリのサイズを意味します。例:pushl %eax
およびpopl %eax