これは、ステートメントの逆アセンブルによって私が見るものですfunction(1,2,3);
:
movl $0x3,0x8(%esp)
movl $0x2,0x4(%esp)
movl $0x1,(%esp)
call 0x4012d0 <_Z8functioniii>
ret アドレスがまったくスタックにプッシュされていないようですが、どのように機能しますret
か?
これは、ステートメントの逆アセンブルによって私が見るものですfunction(1,2,3);
:
movl $0x3,0x8(%esp)
movl $0x2,0x4(%esp)
movl $0x1,(%esp)
call 0x4012d0 <_Z8functioniii>
ret アドレスがまったくスタックにプッシュされていないようですが、どのように機能しますret
か?
x86 プロセッサでは (アセンブリ言語の例のように)、call
命令はリターン アドレスをスタックにプッシュし、制御を関数に移します。
そのため、関数へのエントリでは、スタック ポインターは戻りアドレスを指してret
おり、プログラム カウンター (EIP / RIP) にポップする準備ができています。
すべてのプロセッサ アーキテクチャがリターン アドレスをスタックに置くわけではありません。多くの場合、リターン アドレスを保持するように設計された 1 つまたは複数のレジスタのセットがあります。ARM プロセッサでは、BL
命令は戻りアドレスを特定のレジスタ (LR
または「リンク レジスタ」) に配置し、制御を関数に渡します。
ia64 プロセッサも同様のことを行いますが、戻りアドレスを受け取ることができるレジスタ ( b0
- ) がいくつかあり、そのうちの 1 つが命令で指定されます (これがデフォルトです)。b7
b0
理想的には、call
ステートメントはそれを処理する必要があります。プログラム カウンターの次の位置がスタックにプッシュされます。呼び出された関数 (サブルーチン) が動作を完了し、return ステートメントに遭遇すると、コントロールはスタックにプッシュされたアドレスに移動し、ポップされます。
call
これは ABI とアーキテクチャに依存しますが、リターン アドレスがスタックに置かれる場合は、そこに置く命令の副作用です。
callは RIP レジスタの現在の値 (戻りアドレス) をスタックにプッシュします + 呼び出し
retは戻りアドレス (プッシュされた呼び出し) をスタックの一番上からポップし (RSP レジスタはそこを指します)、RIP レジスタに書き込みます。
GNU/Linux ボックスでの例: 関数 f は関数 g を呼び出し、g のフレームを見てみましょう。
ローアドレス
... <- RSP (スタック ポインタはスタックのトップを示します) レジスタはこのアドレスを指します
g のローカル変数
f のベース ポインタ (古い RBP 値) <- RBP (ベース ポインタ) レジスタはこのアドレスを指します
f の ret アドレス (古い RIP 値) (これは(fからの)呼び出しがプッシュしたものであり、(gからの) retがポップするものです)
fがgを呼び出してレジスタに収まらなかった引数(Windowsではこれは異なると思います)
...
ハイアドレス
g はローカル変数を解放します (movq %rsp, %rbp)
g は「古い RBP」をポップし、RBP レジスターに格納します (pop %rbp)
g retは、RSP が指す場所に格納されている値で RIP を変更しますで
それが役に立てば幸い