x64 でのプロローグとエピローグ

スタック領域の割り当て、他の関数の呼び出し、非揮発性レジスタの保存、例外処理の使用を行うすべての関数には、それぞれに対応する関数テーブル エントリに関連付けられたアンワインド データでアドレス制限が記述されたプロローグが必要です。 詳細については、「x64 例外処理」を参照してください。 プロローグは、必要に応じてホーム アドレスに引数レジスタを保存し、スタックに非揮発性レジスタをプッシュして、ローカルと一時要素にスタックの固定部分を割り当て、オプションでフレーム ポインターを設定します。 関連付けられたアンワインド データは、プロローグの処理を記述する必要があり、プロローグ コードの結果を元に戻すために必要な情報を提供する必要があります。

スタック内の固定割り当てが複数のページにわたる (つまり、4096 バイトを超える) 場合、スタック割り当てが複数の仮想メモリ ページにまたがる可能性があります。したがって、割り当てを行う前に割り当て内容を確認する必要があります。 このために、プロローグから呼び出し可能で、引数レジスタを破壊しない特殊なルーチンが用意されています。

非揮発性レジスタの保存方法としては、固定スタック割り当ての前にスタックに移動することをお勧めします。 非揮発性レジスタが保存される前に固定スタック割り当てが行われた場合、保存されているレジスタ領域のアドレス指定には、32ビットの変位が必要になる可能性が高くなります。 (報告によると、レジスタのプッシュは移動と同じくらい高速でありメインプッシュ間の暗黙の依存関係にもかかわらず、予測可能な将来のために行う必要があります)。不揮発性レジスタは任意の順序で保存できます。 ただし、プロローグで最初に使用する非揮発性レジスタは最初に保存する必要があります。

プロローグ コード

一般的なプロローグのコードは次のようになります。

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    sub    RSP, fixed-allocation-size
    lea    R13, 128[RSP]
    ...

このプロローグは、引数レジスタ RCX をホーム位置に格納し、非揮発性レジスタ R13-R15 を保存し、スタック フレームの固定部分を割り当て、128 バイトを指すフレーム ポインターをその固定割り当て領域に設定します。 オフセットを使用すると、より多くの固定割り当て領域を 1 バイトのオフセットでアドレス指定できます。

固定割り当てサイズが 1 ページ以上のメモリである場合は、RSP を変更する前にヘルパー関数を呼び出す必要があります。 このヘルパー __chkstk は、割り当てるスタック範囲をプローブして、スタックが適切に拡張されるようにします。 その場合、前のプロローグの例は次のようになります。

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    mov    RAX,  fixed-allocation-size
    call   __chkstk
    sub    RSP, RAX
    lea    R13, 128[RSP]
    ...

__chkstk ヘルパーは、R10、R11、および条件コード以外のレジスタを変更しません。 具体的には、RAX を変更せずに返し、非揮発性レジスタと引数渡し用のレジスタをすべて変更せずに残します。

エピローグ コード

エピローグ コードは、関数の終了のたびに存在します。 通常、プロローグは 1 つだけですが、エピローグは多数存在する可能性があります。 エピローグ コードは、(必要に応じて) スタックを固定割り当てサイズにトリミングし、固定スタック割り当てを解除し、保存された値をスタックからポップして非揮発性レジスタを復元し、制御を返します。

エピローグ コードは、アンワインド コードの厳密なルールのセットに従って、例外と割り込みを使用して確実にアンワインドする必要があります。 これらのルールにより、必要なアンワインド データの量が減ります。各エピローグを記述するために余分なデータが必要ないためです。 代わりに、アンワインド コードは、コード ストリームを前方にスキャンしてエピローグを識別することで、エピローグが実行されていることを判断できます。

関数でフレーム ポインターが使用されていない場合、エピローグはまずスタックの固定部分の割り当てを解除し、非揮発性レジスタをポップして、呼び出し元の関数に制御を返します。 たとえば、 にします。

    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

関数でフレーム ポインターが使用されている場合は、エピローグを実行する前に、スタックをその固定割り当てサイズにトリミングする必要があります。 この処理は、厳密にはエピローグの一部ではありません。 たとえば、次のエピローグを使用して、前に使用したプロローグを元に戻すことができます。

    lea      RSP, -128[R13]
    ; epilogue proper starts here
    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

実際には、フレーム ポインターを使用するときは、2 つのステップで RSP を調整することは妥当ではないため、代わりに次のエピローグが使用されます。

    lea      RSP, fixed-allocation-size - 128[R13]
    pop      R13
    pop      R14
    pop      R15
    ret

この形式は、エピローグに対してのみ有効です。 add RSP,constant または lea RSP,constant[FPReg] に続けて一連の 0 個以上の 8 バイトのレジスタのポップと return または jmp が含まれている必要があります。 (エピローグでは、ステートメントの jmp サブセットのみが許可されます。サブセットは、ModRM mod フィールド値が 00 である ModRM メモリ参照を持つステートメントの jmp クラスです。ModRM mod フィールド値 01 または 10 のエピローグでのステートメントの使用 jmp は禁止されています。許容される ModRM リファレンスの詳細については、「AMD x86-64 Architecture Programmer's Manual Volume 3: General Purpose and System Instructions」の表 A-15 を参照してください)。他のコードは表示できません。 特に、エピローグ内では、戻り値の読み込みを含め、何もスケジュールできません。

フレーム ポインターが使用されていない場合、エピローグでスタックの固定部分の割り当てを解除するために add RSP,constant を使用する必要があります。 代わりに lea RSP,constant[RSP] を使用することはできません。 この制限が存在する目的は、エピローグを検索するときにアンワインド コードが認識するパターンを減らすためです。

この規則に従うことで、アンワインド コードはエピローグが現在実行されていることを判断し、エピローグの残りの部分の実行をシミュレートして、呼び出し元の関数のコンテキストを再作成できます。

関連項目

x64 ソフトウェア規約