プロローグとエピローグ
スタック領域の割り当て、他の関数の呼び出し、不揮発性レジスタの保存、または例外処理の使用を行うすべての関数には、各関数テーブルのエントリに関連付けられたアンワインド データにアドレス制限を記述するプロローグを設定する必要があります (「例外処理 (x64)」を参照)。 プロローグは、必要に応じて、引数レジスタをそのホーム アドレスに保存し、不揮発性レジスタをスタックにプッシュし、ローカルおよびテンポラリ用のスタックの固定部分を割り当てます。また、プロローグは、オプションでフレーム ポインターを設定します。 関連付けられたアンワインド データは、プロローグのアクションを記述し、プロローグ コードの効果を元に戻すために必要な情報を提供する必要があります。
スタック内の固定割り当てが複数ページにわたる (つまり、4,096 バイトを上回る) 場合は、スタック割り当てが複数の仮想メモリ ページに及ぶ可能性があるため、スタック割り当てを実際に行う際には事前に割り当てを確認する必要があります。 このために、プロローグから呼び出すことができ、引数レジスタを破壊しない特殊なルーチンが用意されています。
不揮発性レジスタを保存する場合、固定のスタックを割り当てる前に不揮発性レジスタをスタックに移動することをお勧めします。 不揮発性レジスタを保存する前に固定のスタックを割り当てると、保存したレジスタ領域をアドレス指定するため、ほとんどの場合に 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 バイトのレジスタのポップ、およびリターンまたは 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] を使用することはできません。 このような制限があるため、アンワインド コードには、エピローグの検索時に認識されるパターンはほとんどありません。
これらの規則に従うことにより、アンワインド コードは、エピローグが実行されていることを確認し、エピローグの残りの部分の実行をシミュレートして、呼び出し元の関数のコンテキストを再作成できます。