x64 での構造化例外処理と C++ 例外処理のコーディング規則と動作の概要。 例外処理の一般的な情報については、「 Microsoft C++ での例外処理」を参照してください。
例外処理およびデバッガー対応のためのアンワインドデータ
例外が処理されたときに不揮発性レジスタを復旧するために、非リーフ関数には静的データで注釈が付けられます。 このデータは、一般に "関数アンワインド情報" と呼ばれ、任意の命令で関数を適切にアンワインドする方法を説明します。 このデータは pdata、またはプロシージャ データとして格納されます。これによって次に、例外処理データである xdata が参照されます。
関数アンワインド情報は、次に説明するいくつかのデータ構造で構成されます。
Intel APX (Advanced Performance Extensions) をサポートする アンワインド情報については、アンワインド V3 プレビュー仕様を参照してください。
構造体 RUNTIME_FUNCTION
テーブルベースの例外処理には、スタック スペースを割り当てるか、別の関数 (たとえば、非リーフ関数) を呼び出すすべての関数に対してテーブル エントリが必要です。 関数テーブルのエントリの形式は次のとおりです。
| サイズ | 値 |
|---|---|
| ULONG | 関数の開始アドレス |
| ULONG | 関数の終了アドレス |
| ULONG | アンワインド情報のアドレス |
RUNTIME_FUNCTION構造体はメモリ内でDWORD配置する必要があります。 すべてのアドレスはイメージ相対です。つまり、関数テーブル エントリを含むイメージの開始アドレスからの 32 ビット オフセットです。 これらのエントリは並べ替えられて、PE32+ イメージの .pdata セクションに配置されます。 動的に生成される関数 [JIT コンパイラ] の場合、これらの関数をサポートするランタイムは、オペレーティング システムにこの情報を提供するために RtlInstallFunctionTableCallback または RtlAddFunctionTable を使用する必要があります。 これを行わないと、信頼性の低い例外処理とプロセスのデバッグが発生します。
構造体 UNWIND_INFO
アンワインド データ情報構造体は、関数がスタック ポインターに与える影響と、不揮発性レジスタがスタックに保存される場所を記録します。
| サイズ | 値 |
|---|---|
| UBYTE: 3 | バージョン |
| UBYTE: 5 | フラグ |
| UBYTE | プロローグのサイズ |
| UBYTE | アンワインド コードの数 |
| UBYTE: 4 | フレーム レジスタ |
| UBYTE: 4 | フレーム レジスタ オフセット (スケーリング済み) |
| USHORT * n | アンワインドコードの配列 |
| 変数 | 以下の形式 (1) または (2) のいずれかを指定できます |
(1) 例外ハンドラー
| サイズ | 値 |
|---|---|
| ULONG | 例外ハンドラーのアドレス |
| 変数 | 言語固有のハンドラー データ (省略可能) |
(2) チェーン アンワインド情報
| サイズ | 値 |
|---|---|
| ULONG | 関数の開始アドレス |
| ULONG | 関数の終了アドレス |
| ULONG | アンワインド情報のアドレス |
UNWIND_INFO構造体はメモリ内でDWORD配置する必要があります。 各フィールドの意味は次のとおりです。
バージョン
アンワインド データのバージョン番号 (現在は 1)。
フラグ
現在、次の 3 つのフラグが定義されています。
フラグ 説明 UNW_FLAG_EHANDLERこの関数には、オペレーティング システムが例外の状態を調べ、例外を処理するために呼び出す例外ハンドラーがあります。 C __try句などの言語機能は、このようなハンドラーを登録します。UNW_FLAG_UHANDLERこの関数には、スタックをアンワインドするときに操作システムが呼び出す終了ハンドラーがあります。 このハンドラーは、例外セーフ コードで関数によって割り当てられたリソースを解放する可能性があります。 ローカル C++ オブジェクトデストラクターや C __finally句などの言語機能は、このような終了ハンドラーを登録します。UNW_FLAG_CHAININFOこのアンワインド情報構造は、プロシージャの主な構造ではありません。 代わりに、連鎖されたアンワインド情報エントリは、以前の RUNTIME_FUNCTIONエントリの内容です。 詳細については、「チェーン アンワインド情報構造体」を参照してください。 このフラグが設定されている場合は、UNW_FLAG_EHANDLERフラグとUNW_FLAG_UHANDLERフラグをクリアする必要があります。 また、フレーム レジスタと固定スタックの割り当てフィールドには、プライマリ アンワインド情報と同じ値が必要です。プロローグのサイズ
関数プロローグの長さ (バイト単位)。
アンワインド コードの数
アンワインド コード配列内のスロット数。
UWOP_SAVE_NONVOLなどの一部のアンワインド コードでは、配列内に複数のスロットが必要です。フレーム レジスタ
0 以外の場合、関数はフレーム ポインター (FP) を使用し、このフィールドは、
UNWIND_CODEノードの操作情報フィールドに対して同じエンコードを使用して、フレーム ポインターとして使用される不揮発性レジスタの数です。フレームレジスタのオフセット(スケーリング済み)
このフィールドは、
RSPレジスタ値と選択したフレーム ポインター (FP) レジスタ値の間のスケーリングされたオフセットです。 選択した FP レジスタは、RSP+ 16 * この数値に設定されています。これは、0 から 240 までのオフセットを使用できることを意味します。 このオフセットは、FP レジスタを動的スタック フレームのローカル スタック割り当ての途中にポイントするため、短い命令によってコード密度が向上します。 (つまり、より多くの命令で、8 ビット符号付きオフセット形式を使用できます)。解除コード配列
不揮発性レジスタと
RSPに対するプロローグの影響を説明する項目の配列。 個々の項目の意味については、 アンワインド操作コード のセクションを参照してください。 適切なデータ配置を維持するために、この配列には常に偶数のエントリが含まれており、最後のエントリは使用されない可能性があります。 この場合、配列はアンワインド コード フィールドのカウントで示されるよりも 1 つ長くなります。例外ハンドラーのアドレス
フラグ
UNW_FLAG_CHAININFOがクリアで、フラグのUNW_FLAG_EHANDLERまたはUNW_FLAG_UHANDLERのいずれかが設定されている場合は、関数の言語固有の例外または終了ハンドラーへのイメージ相対ポインター。言語固有のハンドラー データ
関数の言語固有の例外ハンドラー データ。 このデータの形式は指定されておらず、完全に使用中の特定の例外ハンドラーによって決まります。
チェーン アンワインド情報
フラグ
UNW_FLAG_CHAININFOが設定されている場合、UNWIND_INFO構造体は 3 つのUWORDで終わります。 これらのUWORDは、チェーンアンワインドの関数のRUNTIME_FUNCTION情報を表します。
構造体 UNWIND_CODE
アンワインド コード配列を使用して、不揮発性レジスタと RSPに影響するプロローグ内の操作のシーケンスを記録します。 各コード項目の形式は次のとおりです。
| サイズ | 値 |
|---|---|
| UBYTE | プロローグ内のオフセット |
| UBYTE: 4 | アンワインド オペコード |
| UBYTE: 4 | 演算情報 |
配列は、プロローグ内のオフセットの降順で並べ替えられます。
プロローグ内のオフセット
命令の終わりのオフセット(プロローグの先頭からの)に 1 を加えた値がこの操作を行う命令の終了位置になります。これはつまり、次の命令の開始オフセットを示します。
アンワインド オペコード
特定の操作コードでは、ローカル スタック フレーム内の値への符号なしオフセットが必要です。 このオフセットは、開始地点、つまり固定スタック割り当ての最小アドレスからのものです。
UNWIND_INFOの [フレーム レジスタ] フィールドが 0 の場合、このオフセットはRSPから取得されます。 [フレーム レジスタ] フィールドが 0 以外の場合、このオフセットは FP レジスタが確立されたときに RSP が配置された場所から取得されます。 これは、FP レジスタから FP レジスタ オフセット (16 * UNWIND_INFO内のスケーリングされたフレーム レジスタ オフセット) を引いた値と等しくなります。 FP レジスタを使用する場合、オフセットを受け取るアンワインド コードは、FP レジスタがプロローグで確立された後で使用する必要があります。
UWOP_SAVE_XMM128 と UWOP_SAVE_XMM128_FAR を除くすべてのオペコードでは、対象となるすべてのスタック値は 8 バイト境界に格納されるため、オフセットは常に 8 の倍数になります (スタック自体は常に 16 バイト境界でアラインされます)。 短いオフセット (512K 未満) を受け取る操作コードの場合、このコードのノードの最終的な USHORT はオフセットを 8 で割った値を保持します。 長いオフセット(512K <= オフセット < 4GB)を取る操作コードについては、このコードの最後の2つのUSHORTノードにオフセットが格納されます(リトルエンディアン形式)。
オペコードの UWOP_SAVE_XMM128 と UWOP_SAVE_XMM128_FARの場合、オフセットは常に 16 の倍数になります。128 ビット XMM 操作はすべて 16 バイトアラインメモリで実行する必要があるためです。 そのため、UWOP_SAVE_XMM128 には 16 の倍率が使用され、1M 未満のオフセットが許可されます。
アンワインド オペコードは、次のいずれかの値になります。
UWOP_PUSH_NONVOL(0) 1 ノード不揮発性整数レジスタをプッシュし、
RSPを 8 減算します。 演算情報はレジスタの番号です。 エピローグに制約があるため、UWOP_PUSH_NONVOLアンワインド コードはプロローグの最初に出現し、それに対応してアンワインド コード配列の最後に出現する必要があります。 この相対的な順序は、UWOP_PUSH_MACHFRAMEを除く他のすべてのアンワインド コードに適用されます。UWOP_ALLOC_LARGE(1) 2 または 3 ノードスタックに大きな領域を割り当てます。 2 つの形式があります。 演算情報が 0 の場合、割り当てのサイズを 8 で割ったものが次のスロットに記録され、最大 512K - 8 の割り当てが可能になります。 演算情報が 1 の場合、割り当てのスケールなしのサイズが次の 2 つのスロットにリトルエンディアン形式で記録され、最大 4GB - 8 の割り当てが可能になります。
UWOP_ALLOC_SMALL(2) 1 ノード小さいサイズの領域をスタックに割り当てます。 割り当てのサイズは演算情報フィールド * 8 + 8 であり、8 から 128 バイトの割り当てが可能です。
スタック割り当てのアンワインド コードには、可能な限り短いエンコードを常に使用するようにします。
アロケーション サイズ アンワインド コード 8 から 128 バイト UWOP_ALLOC_SMALL136 から 512K - 8 バイト UWOP_ALLOC_LARGE、演算情報 = 0512K から 4G - 8 バイト UWOP_ALLOC_LARGE、演算情報 = 1UWOP_SET_FPREG(3) 1 ノード現在の
RSPのオフセットにレジスタを設定して、フレーム ポインター レジスタを確立します。 オフセットは、UNWIND_INFO* 16 のフレーム レジスタ オフセット (拡大縮小) フィールドと等しく、0 から 240 までのオフセットが可能です。 オフセットを使用すると、固定スタック割り当ての中央を指すフレーム ポインターを確立できます。その結果、より多くのアクセスで短い命令形式を使用できるようになるので、コードの密度が向上します。 演算情報フィールドは予約されているため、使用しないでください。UWOP_SAVE_NONVOL(4) 2 ノードPUSH ではなく MOV を使用して、不揮発性整数レジスターをスタックに保存します。 このコードは主に "shrink-wrapping (シュリンクラッピング)" のために使用され、不揮発性レジスタは以前に割り当てられた位置のスタックに保存されます。 演算情報はレジスタの番号です。 8 の倍率でスケールされたスタック オフセットは、前述の注記で説明したように、次のアンワインド オペコード スロットに記録されます。
UWOP_SAVE_NONVOL_FAR(5) 3 ノードPUSH ではなく MOV を使用して、長いオフセットのスタックに不揮発性整数レジスタを保存します。 このコードは主に "shrink-wrapping (シュリンクラッピング)" のために使用され、不揮発性レジスタは以前に割り当てられた位置のスタックに保存されます。 演算情報はレジスタの番号です。 未スケールのスタック オフセットは、前述の注記で説明したように、次の 2 つのアンワインド操作コードスロットに記録されます。
UWOP_SAVE_XMM128(8) 2 ノード不揮発性
XMMレジスタの 128 ビットをすべてスタックに保存します。 演算情報はレジスタの番号です。 16 の倍率でスケールされたスタック オフセットは、次のスロットに記録されます。UWOP_SAVE_XMM128_FAR(9) 3 ノード長いオフセットを持つスタック上の不揮発性
XMMレジスタのすべての 128 ビットを保存します。 演算情報はレジスタの番号です。 スケールなしのスタック オフセットは、次の 2 つのスロットに記録されます。UWOP_PUSH_MACHFRAME(10) 1 ノードマシン フレームをプッシュします。 このアンワインド コードは、ハードウェア割り込みまたは例外の影響を記録します。 これには 2 つの形式があります。 値 0 は、ハードウェアが次のようなフレームをスタックにプッシュしたことを示します。
ロケーション 値 RSP+32SSRSP+24古い RSPRSP+16EFLAGSRSP+8CSRSPRIP値 1 は、ハードウェアがスタック上で次のようなフレームをプッシュしたことを示します。
ロケーション 値 RSP+40SSRSP+32古い RSPRSP+24EFLAGSRSP+16CSRSP+8RIPRSPエラー コード このアンワインド コードは常にダミーのプロローグに出現します。実際には実行されませんが、割り込みルーチンの実際のエントリ ポイントの前に出現します。マシン フレームのプッシュをシミュレートする場所を用意するためにのみ存在します。
UWOP_PUSH_MACHFRAMEによってそのシミュレーションが記録されます。これは、マシンによって概念的に次の演算が実行されたことを示します。スタックの最上部から
RIPリターン アドレスをポップしてTempに格納するプッシュ
SS古いものをプッシュ
RSPプッシュ
EFLAGSプッシュ
CSTemp をプッシュします
エラー コードをプッシュします (演算情報が 1 の場合)
シミュレートされた
UWOP_PUSH_MACHFRAME操作では、RSPが 40(op info が 0 の場合)または 48(op info が 1 の場合)だけデクリメントされます。
演算情報
演算情報ビットの意味は、オペコードによって異なります。 汎用 (整数) レジスタをエンコードするには、次のマッピングを使用します。
| ビット | 登録する |
|---|---|
| 0 | RAX |
| 1 | RCX |
| 2 | RDX |
| 3 | RBX |
| 4 | RSP |
| 5 | RBP |
| 6 | RSI |
| 7 | RDI |
| 8 から 15 |
R8 から R15 に変更します |
チェインアンワインド情報構造体
UNW_FLAG_CHAININFO フラグが設定されている場合、アンワインド情報構造体はセカンダリ構造になり、共有例外ハンドラー/チェーン情報アドレス フィールドにはプライマリ アンワインド情報が含まれます。 このサンプル コードでは、 unwindInfo が UNW_FLAG_CHAININFO フラグが設定されている構造体であると仮定して、プライマリ アンワインド情報を取得します。
PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);
チェーンされた情報は二つの状況で有用です。 まず、連続していないコード セグメントに使用できます。 チェーン情報を使用すると、プライマリ アンワインド情報からアンワインド コード配列を複製する必要がないため、必要なアンワインド情報のサイズを小さくできます。
また、チェーン情報を使用して、揮発性レジスタの保存をグループ化することもできます。 コンパイラは、一部の揮発性レジスタの保存を、関数エントリのプロローグ外になるまで遅延する可能性があります。 それらを記録するには、グループ化されたコードより前の関数部分に対する主要なアンワインド情報を用意し、次に、不揮発性レジスタの保存を反映したアンワインド コードを含む、プロローグ サイズが 0 以外の連結情報を設定します。 その場合、アンワインド コードはすべて UWOP_SAVE_NONVOLのインスタンスです。
PUSHを使用して不揮発性レジスタを保存したり、追加の固定スタック割り当てを使用してRSPレジスタを変更したりするグループ化はサポートされていません。
UNW_FLAG_CHAININFO が設定された UNWIND_INFO 項目には、UNW_FLAG_CHAININFO も設定された UNWIND_INFO 項目を持つ RUNTIME_FUNCTION エントリを含めることができます。これは 多重シュリンクラップ と呼ばれることもあります。 最終的に、チェーンされたアンワインド情報ポインターは、UNW_FLAG_CHAININFO がクリアされた UNWIND_INFO 項目に到達します。 この項目は、実際のプロシージャ エントリ ポイントを指すプライマリ UNWIND_INFO 項目です。
アンワインド プロシージャ
アンワインド コード配列は降順に並べ替えられています。 例外が発生すると、オペレーティング システムはコンテキスト レコードに完全なコンテキストを格納します。 次に、例外ディスパッチ ロジックが呼び出され、例外ハンドラーを見つけるためにこれらの手順が繰り返し実行されます。
コンテキスト レコードに格納されている現在の
RIPを使用して、現在の関数を記述するRUNTIME_FUNCTIONテーブル エントリ (または、チェーンされたUNWIND_INFOエントリの関数部分) を検索します。検索で関数テーブルエントリが見つからない場合、コードはリーフ関数の一部であると見なされ、
RSPはリターン ポインターに直接対処します。 [RSP] の戻りポインターは更新されたコンテキストに格納され、シミュレートされたRSPは 8 ずつインクリメントされ、手順 1 が繰り返されます。検索で関数テーブルエントリが見つかると、
RIPは 3 つのリージョン内に存在する可能性があります。a) エピローグ内、b) プロローグ内、または c) 例外ハンドラーによってカバーされる可能性があるコード内。ケース a)
RIPがエピローグ内にある場合、コントロールは関数を終了します。 この関数に対して、この例外に関連付けられている例外ハンドラーを指定することはできません。 エピローグの効果は、呼び出し元関数のコンテキストを引き続き計算する必要があります。RIPがエピローグ内にあるかどうかを判断するために、RIP以降のコード ストリームが調べされます。 そのコード ストリームが正当なエピローグの末尾の部分と一致する場合は、エピローグ内にあります。 エピローグの残りの部分はシミュレートされ、各命令が処理されるとコンテキスト レコードが更新されます。 この処理の後、手順 1 が繰り返されます。- ケース b)
RIPがプロローグ内にある場合、コントロールは関数に入っていません。 この関数に対して、この例外に関連付けられている例外ハンドラーを指定することはできません。 呼び出し元関数のコンテキストを計算するには、プロローグの効果を元に戻す必要があります。 関数の開始からRIPまでの距離がアンワインド情報でエンコードされたプロローグ サイズ以下の場合、RIPはプロローグ内にあります。 アンワインダーは、アンワインド コード配列をスキャンして、関数の開始からのRIPのオフセット以下のオフセットを持つ最初のエントリをスキャンし、アンワインド コード配列内の残りのすべての項目の効果を元に戻します。 次に、手順 1 を繰り返します。
- ケース b)
ケース c)
RIPがプロローグまたはエピローグ内に存在せず、関数に例外ハンドラー (UNW_FLAG_EHANDLERが設定されている) がある場合は、言語固有のハンドラーが呼び出されます。 ハンドラーによってそのデータがスキャンされ、必要に応じてフィルター関数が呼び出されます。 言語固有のハンドラーから、例外が処理されたこと、または検索が続行されることが返される可能性があります。 それは直接アンワインドを開始することもできます。
言語固有のハンドラーが処理済みの状態を返した場合、元のコンテキスト レコードを使用して実行が続行されます。
言語固有のハンドラーがない場合、またはハンドラーが "検索の続行" 状態を返す場合は、コンテキスト レコードを呼び出し元の状態に戻す必要があります。 アンワインダーは、アンワインド コード配列内の各要素の効果を元に戻します。 次に、手順 1 を繰り返します。
チェーン アンワインド情報が関係する場合でも、これらの基本的な手順に従います。 唯一の違いは、アンワインド コード配列をウォークしてプロローグの効果をアンワインドする際に、プロセスが配列の末尾に到達すると、親アンワインド情報にリンクされ、そこで見つかったアンワインド コード配列全体をウォークするという点です。 このリンクは、 UNW_CHAINED_INFO フラグなしでアンワインド情報に到達するまで続行され、アンワインド コード配列のウォークが完了します。
アンワインド データの最小セットは 8 バイトです。 このようなセットは、128 バイト以下のスタックのみを割り当て、場合によっては 1 つの不揮発性レジスタを保存した関数を表します。 これは、アンウィンド コードのないゼロ長のプロローグに対するチェインされたアンウィンド情報構造体のサイズでもあります。
言語固有のハンドラー
UNWIND_INFO構造体は、UNW_FLAG_EHANDLERまたはUNW_FLAG_UHANDLERフラグが設定されている場合に、言語固有のハンドラーの相対アドレスを提供します。 前のセクションで説明したように、例外ハンドラーの検索またはアンワインドプロセスによって言語固有のハンドラーが呼び出されます。 ハンドラーは、次のプロトタイプを使用します。
typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN ULONG64 EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
ExceptionRecord では、標準の Win64 定義を持つ例外レコードへのポインターが提供されます。
EstablisherFrame は、この関数の固定スタック割り当てのベースのアドレスです。
ContextRecord は、例外が発生したときの例外コンテキスト (例外ハンドラーの場合) または現在の "アンワインド" コンテキスト (終了ハンドラーの場合) を指します。
DispatcherContext は、この関数のディスパッチャー コンテキストを指します。 この定義は次のとおりです。
typedef struct _DISPATCHER_CONTEXT {
ULONG64 ControlPc;
ULONG64 ImageBase;
PRUNTIME_FUNCTION FunctionEntry;
ULONG64 EstablisherFrame;
ULONG64 TargetIp;
PCONTEXT ContextRecord;
PEXCEPTION_ROUTINE LanguageHandler;
PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
ControlPc は、この関数内の RIP の値です。 この値は、例外アドレスか、制御が確立元の関数から離れたアドレスです。
RIPは、制御がこの関数内の保護されたコンストラクト内にあるかどうかを判断するために使用されます (たとえば、__try__try/または__except__try/の__finally ブロック)。
ImageBase は、この関数を含むモジュールのイメージ ベース (読み込みアドレス) です。 関数エントリとアンワインド情報で使用される 32 ビット オフセットを ImageBase に追加して、最終的なアドレスを取得する必要があります。
FunctionEntry は、この関数の関数本体およびアンワインド情報のイメージ ベース相対アドレスを保持する RUNTIME_FUNCTION 関数エントリへのポインターを提供します。
EstablisherFrame は、この関数の固定スタック割り当てのベースのアドレスです。
TargetIp は、アンワインドの継続アドレスを指定する省略可能な命令アドレスを提供します。 EstablisherFrame が指定されていない場合、このアドレスは無視されます。
ContextRecord は、システム例外のディスパッチまたはアンワインド コードで使用される例外コンテキストを指します。
LanguageHandler は、呼び出される言語固有の言語ハンドラー ルーチンを指します。
HandlerData は、この関数の言語固有のハンドラー データを指します。
MASM のアンワインド ヘルパー
適切なアセンブリ ルーチンを記述するには、実際のアセンブリ命令と共に一連の擬似演算を使用します。 これらの擬似演算により、適切な .pdata と .xdataが作成されます。 また、最も一般的な用途でこれらの擬似演算の使用を簡略化する一連のマクロを使用します。
生の擬似演算
| 擬似演算 | 説明 |
|---|---|
| PROC FRAME [:ehandler] | MASM は、関数の構造化例外処理アンワインド動作に対して、 .pdata に関数テーブル エントリを生成し、 .xdata で情報をアンワインドします。
ehandler が存在する場合、このプロシージャは言語固有のハンドラーとして .xdata に入力されます。FRAME 属性を使用する場合は、その後に .ENDPROLOG ディレクティブを続けて指定してください。 関数がリーフ関数 ( 関数型で定義されている) の場合、これらの擬似演算の残りの部分と同様に、FRAME 属性は不要です。 |
| .PUSHREG レジスタ | プロローグ内の現在のオフセットを使用して、指定したレジスタ番号の UWOP_PUSH_NONVOL アンワインド コード エントリを生成します。不揮発性整数レジスタでのみ使用します。 揮発性レジスタをプッシュする場合は、代わりに .ALLOCSTACK 8 を使用します。 |
| .SETFRAME レジスタ、 オフセット | 指定されたレジスタとオフセットを利用して、アンワインド情報内のフレームレジスタフィールドとオフセットを設定します。 オフセットは、240 以下の 16 の倍数にする必要があります。 このディレクティブは、現在のプロローグ オフセットを使用し、指定したレジスタの UWOP_SET_FPREG アンワインド コード エントリも生成します。 |
| .ALLOCSTACK サイズ | プロローグ内の現在のオフセットに対して指定したサイズの UWOP_ALLOC_SMALL または UWOP_ALLOC_LARGE を生成します。size オペランドは 8 の倍数にする必要があります。 |
| .SAVEREG レジスタ、 オフセット | 現在のプロローグ オフセットを使用して、指定したレジスタとオフセットの UWOP_SAVE_NONVOL または UWOP_SAVE_NONVOL_FAR アンワインド コード エントリを生成します。 MASM によって最も効率的なエンコードが選択されます。offset は正の 8 の倍数にする必要があります。 offset は、プロシージャのフレームのベース (通常は RSP)、またはフレーム ポインターを使用している場合は、スケーリングされていないフレーム ポインターを基準とします。 |
| .SAVEXMM128 レジスタ、 オフセット | 現在のプロローグ オフセットを使用して、指定したXMMレジスタとオフセットに対するUWOP_SAVE_XMM128またはUWOP_SAVE_XMM128_FARアンワインド コード エントリを生成します。 MASM によって最も効率的なエンコードが選択されます。offset は正の 16 の倍数にする必要があります。 offset は、プロシージャのフレームのベース (通常は RSP)、またはフレーム ポインターを使用している場合は、スケーリングされていないフレーム ポインターを基準とします。 |
| .PUSHFRAME [コード] |
UWOP_PUSH_MACHFRAME アンワインド コード エントリを生成します。 省略可能な コードを指定すると、アンワインド コード エントリは修飾子 1 を取得します。 それ以外の場合、修飾子は 0 です。 |
| .ENDPROLOG | プロローグ宣言の終了を通知します。 関数の最初の 255 バイトで発生する必要があります。 |
ほとんどのオペコードを適切に使用したサンプルの関数プロローグを次に示します。
sample PROC FRAME
db 048h; emit a REX prefix, to enable hot-patching
push rbp
.pushreg rbp
sub rsp, 040h
.allocstack 040h
lea rbp, [rsp+020h]
.setframe rbp, 020h
movdqa [rbp], xmm7
.savexmm128 xmm7, 020h ;the offset is from the base of the frame
;not the scaled offset of the frame
mov [rbp+018h], rsi
.savereg rsi, 038h
mov [rsp+010h], rdi
.savereg rdi, 010h ; you can still use RSP as the base of the frame
; or any other register you choose
.endprolog
; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer
sub rsp, 060h
; we can unwind from the next AV because of the frame pointer
mov rax, 0
mov rax, [rax] ; AV!
; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5
movdqa xmm7, [rbp]
mov rsi, [rbp+018h]
mov rdi, [rbp-010h]
; Here's the official epilog
lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
pop rbp
ret
sample ENDP
エピローグの例の詳細については、「x64 でのプロローグとエピローグ」の「エピローグ コード」を参照してください。
MASM マクロ
Raw 擬似演算の使用を簡略化するには、ksamd64.incで定義されているマクロのセットを使用します。 これらのマクロは、一般的なプロシージャ プロローグとエピローグを作成するのに役立ちます。
| マクロ | 説明 |
|---|---|
| alloc_stack(n) | ( を使用して) sub rsp, n バイトのスタック フレームを割り当て、適切なアンワインド情報 (.allocstack n) を出力します。 |
| save_reg reg、 loc | オフセット loc のスタックに不揮発性レジスタ RSP保存し、適切なアンワインド情報 (.savereg reg, loc) を出力します。 |
| push_reg reg | スタックに不揮発性レジスタ reg を プッシュし、適切なアンワインド情報 (.pushreg reg) を出力します。 |
| rex_push_reg reg | 2 バイトのプッシュを使用してスタックに不揮発性レジスタを保存し、適切なアンワインド情報 (.pushreg reg) を出力します。 プッシュがその関数の最初の命令である場合は、このマクロを使用して、関数のホットパッチが可能であることを確認します。 |
| save_xmm128 reg、 loc | 非揮発性 XMM レジスタ reg をスタックの RSP オフセット loc に保存し、適切なアンワインド情報 (.savexmm128 reg, loc) を出力します。 |
| set_frame reg、 offset | フレーム レジスタ reg を (RSPまたは + を使用して) movlea に設定し、適切なアンワインド情報 (.set_frame reg、offset) を出力します。 |
| push_eflags |
pushfq命令を使用して eflags をプッシュし、適切なアンワインド情報を出力します (.alloc_stack 8) |
マクロを適切に使用したサンプル関数のプロローグを次に示します。
sampleFrame struct
Fill dq ?; fill to 8 mod 16
SavedRdi dq ?; Saved Register RDI
SavedRsi dq ?; Saved Register RSI
sampleFrame ends
sample2 PROC FRAME
alloc_stack(sizeof sampleFrame)
save_reg rdi, sampleFrame.SavedRdi
save_reg rsi, sampleFrame.SavedRsi
.end_prolog
; function body
mov rsi, sampleFrame.SavedRsi[rsp]
mov rdi, sampleFrame.SavedRdi[rsp]
; Here's the official epilog
add rsp, (sizeof sampleFrame)
ret
sample2 ENDP
C でのアンワインド データの定義
アンワインド データの C の記述を次に示します。
typedef enum _UNWIND_OP_CODES {
UWOP_PUSH_NONVOL = 0, /* info == register number */
UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */
UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */
UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */
UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;
typedef unsigned char UBYTE;
typedef union _UNWIND_CODE {
struct {
UBYTE CodeOffset;
UBYTE UnwindOp : 4;
UBYTE OpInfo : 4;
};
USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
#define UNW_FLAG_EHANDLER 0x01
#define UNW_FLAG_UHANDLER 0x02
#define UNW_FLAG_CHAININFO 0x04
typedef struct _UNWIND_INFO {
UBYTE Version : 3;
UBYTE Flags : 5;
UBYTE SizeOfProlog;
UBYTE CountOfCodes;
UBYTE FrameRegister : 4;
UBYTE FrameOffset : 4;
UNWIND_CODE UnwindCode[1];
/* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
* union {
* OPTIONAL ULONG ExceptionHandler;
* OPTIONAL ULONG FunctionEntry;
* };
* OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;
typedef struct _RUNTIME_FUNCTION {
ULONG BeginAddress;
ULONG EndAddress;
ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
#define GetUnwindCodeEntry(info, index) \
((info)->UnwindCode[index])
#define GetLanguageSpecificDataPtr(info) \
((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))
#define GetExceptionHandler(base, info) \
((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))
#define GetChainedFunctionEntry(base, info) \
((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))
#define GetExceptionDataPtr(info) \
((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))