Share via


ARM 例外処理

ARM 版 Windows では、ハードウェアで生成される非同期例外とソフトウェアで生成される同期例外に対して、同じ構造化例外処理メカニズムを使用します。 言語固有の例外ハンドラーは、言語ヘルパー関数を使用することで、Windows 構造化例外処理に付加して構築します。 このドキュメントでは、ARM 版 Windows における例外処理と、Microsoft ARM アセンブラおよび MSVC コンパイラの両方によって生成される言語ヘルパーについて説明します。

ARM 例外処理

ARM 版 Windows では、構造化例外処理 (SEH) 中に、アンワインド コードを使用してスタック アンワインドを制御しています。 アンワインド コードは、実行可能イメージの .xdata セクションに格納されるバイトのシーケンスです。 これらのコードには、関数プロローグとエピローグ コードの演算を抽象な方法で記述します。 ハンドラーではそれらを使用して、呼び出し元のスタック フレームにアンワインドするときに、関数プロローグの効果を元に戻します。

ARM EABI (埋め込みアプリケーション バイナリ インターフェイス) では、アンワインド コードを使用する例外アンワインドのモデルが指定されています。 このモデルは、Windows での SEH アンワインドには不十分です。 これは、プロセッサが関数のプロローグまたはエピローグの中間にある非同期ケースを取り扱う必要があります。 また、Windows では、アンワインド制御が関数レベルのアンワインドと言語固有スコープのアンワインドに分離されます。これは、ARM EABI で統合されます。 これらの理由から、ARM 版 Windows では、アンワインドのデータと手順の詳細が指定されます。

前提条件

ARM 版 Windows 用の実行可能イメージでは、移植可能な実行可能 (PE) 形式が使用されます。 詳細については、PE 形式に関するページを参照してください。 例外処理情報は、イメージの .pdata および .xdata セクションに格納されます。

例外処理メカニズムでは、ARM 版 Windows の ABI に従うコードについて、次のことを想定しています。

  • 関数の本体内で例外が発生した場合に、ハンドラーによりプロローグの演算が元に戻されたり、先に進んでエピローグの演算が実行されたりする可能性があります。 両方の場合で同じ結果となります。

  • 通常、プロローグとエピローグは、相互にミラー化されます。 この特徴を利用して、アンワインドの記述に必要なメタデータのサイズを削減できます。

  • 関数は、比較的小さい傾向があります。 最適化のために、この観察を利用してデータを効率的にパッキングすることがあります。

  • エピローグに条件が置かれた場合、その条件はエピローグ内の各命令に一様に適用されます。

  • スタック ポインター (SP) がプロローグで別のレジスタに保存される場合は、元の SP をいつでも復元できるように、そのレジスタを関数全体で変更なく保つ必要があります。

  • SP が別のレジスタに保存されていない限り、SP に対するすべての操作は、厳密にプロローグおよびエピローグ内で発生する必要があります。

  • 任意のスタック フレームをアンワインドするには、次の操作が必要です。

    • r13 (SP) を 4 バイト刻みで調整します。

    • 1 つ以上の整数レジスタをポップします。

    • 1 つ以上の VFP (仮想浮動小数点) レジスタをポップします。

    • 任意のレジスタ値を r13 (SP) にコピーします。

    • 小さな後置デクリメント演算を使用して、SP をスタックからロードします。

    • いくつかの適切に定義されたフレーム タイプの 1 つを解析します。

.pdata レコード

PE 形式イメージ内の .pdata レコードは、各スタック操作関数を記述する、固定長項目の順序付けされた配列です。 リーフ関数 (他の関数を呼び出さない関数) は、スタックを操作しない場合には .pdata レコードを必要としません。 (つまり、ローカル ストレージを必要とせず、非 volatile レジスタを保存または復元する必要がありません)。 領域を節約するため、これらの関数のレコードを .pdata セクションから省略できます。 これらの関数のいずれかからのアンワインド操作では、リンク レジスタ (LR) からプログラム カウンター (PC) へリターン アドレスをコピーして、呼び出し元に移動することのみが可能です。

ARM のすべての .pdata レコードの長さは 8 バイトです。 一般的なレコードの形式の場合、次の表に示すように、最初の 32 ビット ワードに関数の開始の相対仮想アドレス (RVA) が配置され、その後の 2 番目のワードには可変長 .xdata ブロックへのポインター、または標準関数アンワインド シーケンスを記述するパックされたワードが含まれます。

ワード オフセット Bits 目的
0 0-31 Function Start RVA は、関数の開始の 32 ビットの RVA です。 関数に Thumb コードが含まれている場合は、このアドレスの下位ビットを設定する必要があります。
1 0-1 Flag は、2 番目の .pdata ワードの残りの 30 ビットを解釈する方法を示す 2 ビット フィールドです。 Flag が 0 の場合、残りのビットによって "例外情報 RVA" (2 つの最下位ビットが暗黙的に 0) が形成されます。 Flag が 0 以外の場合、残りのビットによって "パックされたアンワインド データ" 構造体が形成されます。
1 2-31 例外情報 RVA、または パックされたアンワインド データ

"例外情報 RVA" は、.xdata セクションに格納されている可変長例外情報構造体のアドレスです。 このデータは、4 バイトでアラインされている必要があります。

パックされたアンワインド データは、関数からのアンワインドに必要な演算の圧縮記述です (正規の形式を想定)。 この場合、.xdata レコードは必要ありません。

パックされたアンワインド データ

関数のプロローグおよびエピローグが下に示す標準形式に従っている場合、パックされたアンワインド データを使用できます。 これにより .xdata レコードは必要なくなり、アンワインド データの提供に必要な領域が大幅に削減されます。 標準のプロローグおよびエピローグは、例外ハンドラーを必要とせず、セットアップおよび終了操作を標準的な順序で実行する単純な関数に共通する要件を満たすように設計されています。

次の表は、パックされたアンワインド データが含まれる .pdata レコードの形式を示したものです。

ワード オフセット Bits 目的
0 0-31 Function Start RVA は、関数の開始の 32 ビットの RVA です。 関数に Thumb コードが含まれている場合は、このアドレスの下位ビットを設定する必要があります。
1 0-1 Flag は、次の意味を持つ 2 ビット フィールドです。

- 00 = パックされたアンワインド データは使用されません。残りのビットで .xdata レコードがポイントされます。
- 01 = パックされたアンワインド データです。
- 10 = パックされたアンワインド データです。関数にプロローグがないことが想定されています。 関数の開始から分離している関数フラグメントの記述に役立ちます。
- 11 = 予約済み。
1 2-12 Function Length は、関数全体の長さを 2 で除算してバイト単位で示す 11 ビット フィールドです。 関数が 4KB より大きい場合、完全な .xdata レコードを代わりに使用する必要があります。
1 13-14 Ret は、関数が戻る方法を示す 2 ビット フィールドです。

- 00 = pop {pc} を介して戻ります (L フラグ ビットは 1 に設定されている必要があります)。
- 01 = 16 ビット分岐を使用して戻ります。
- 10 = 32 ビット分岐を使用して戻ります。
- 11 = エピローグはありません。 プロローグのみが含まれ、エピローグは他の場所にある分離された関数フラグメントの記述に役立ちます。
1 15 H は、関数が、開始時に整数パラメーター レジスタ (r0-r3) をプッシュして、制御を戻す前にスタックの 16 ビットを解放することでこれらのレジスタを元に戻すかどうかを示す 1 ビット フラグです。 (0 = レジスタを元に戻しません、1 = レジスタを元に戻します)。
1 16-18 Reg は、最後に保存された非揮発性レジスタのインデックスを示す 3 ビット フィールドです。 R ビットが 0 の場合、整数レジスタのみが保存され、r4-rN の範囲にあると想定されます。ここで、N は 4 + Reg に等しくなります。 R ビットが 1 の場合、浮動小数点レジスタのみが保存され、d8-dN の範囲にあると想定されます。ここで、N は 8 + Reg に等しくなります。 R = 1 と Reg = 7 の特別な組み合わせは、保存されたレジスタがないことを示します。
1 19 R は、保存された非揮発性レジスタが整数レジスタであるか (0)、浮動小数点レジスタであるか (1) を示す 1 ビットのフラグです。 R が 1 に設定され、Reg フィールドが 7 に設定されている場合は、プッシュされた非揮発性レジスタがないことを示します。
1 20 L は、関数が Reg フィールドによって示されるその他のレジスタと共に、LR の保存/復元を行うかどうかを示す 1 ビット フラグです。 (0 = 保存/復元を行わない、1 = 保存/復元を行う。)
1 21 C は、関数に高速スタック ウォークのためのフレーム チェーンをセットアップするための特別な命令が含まれている (1) か含まれていない (0) かを示す 1 ビット フラグです。 このフラグが設定されている場合、保存された非 volatile 整数レジスタのリストに r11 が暗黙的に追加されています。 (C フラグが使用されている場合は、後述の制限を参照してください。)
1 22-31 Stack Adjust は、この関数に割り当てられたスタックのバイト数を 4 で除算した値を示す 10 ビット フィールドです。 ただし、0x000 ~ 0x3F3 の間の値のみを直接エンコード可能です。 4,044 バイトを超えるスタックを割り当てる関数は、フル .xdata レコードを使用する必要があります。 Stack Adjust フィールドが 0x3F4 以上の場合、下位 4 ビットは特別な意味を持ちます。

- ビット 0 ~ 1 は、スタック調整のワード数 (1 ~4) から 1 を減算した値を示します。
- プロローグでこの調整をプッシュ操作に組み入れている場合、ビット 2 が 1 に設定されます。
- エピローグでこの調整をポップ操作に組み入れている場合、ビット 3 が 1 に設定されます。

上のエンコーディングに冗長性がある可能性があるため、次の制限が適用されます。

  • C フラグが 1 に設定されている場合:

    • フレーム チェーンで r11 と LR の両方が必要となるため、L フラグも 1 に設定されている必要があります。

    • r11 は、Reg で記述されたレジスタのセットに含めることはできません。 つまり、r4-r11 がプッシュされる場合、Reg では r4-r10 のみを記述する必要があります。これは、C フラグが r11 を暗黙的に示すためです。

  • Ret フィールドが 0 に設定されている場合、L フラグが 1 に設定される必要があります。

これらの制限に違反すると、サポートされないシーケンスが発生します。

以下の説明のため、2 つの擬似フラグが Stack Adjust から導出されています。

  • PF つまり "プロローグの折りたたみ" は、Stack Adjust が 0x3F4 以上であり、ビット 2 が設定されていることを示しています。

  • EF つまり "エピローグの折りたたみ" は、Stack Adjust が 0x3F4 以上であり、ビット 3 が設定されていることを示しています。

標準関数のプロローグには、最大 5 個の命令を含めることができます (3a と 3b は同時に指定できません)。

指示 オペコードが想定される条件 サイズ オペコード アンワインド コード
1 H==1 16 push {r0-r3} 04
2 C==1 または L==1 または R==0 または PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3a C==1 および (R==1 および PF==0) 16 mov r11,sp FB
3b C==1 および (R==0 または PF==1) 32 add r11,sp,#xx FC
4 R==1 および Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 および PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

H ビットが 1 に設定されている場合、命令 1 は常に存在します。

フレーム チェーンを設定するために、C ビットが設定されている場合は、命令 3a または 3b のどちらかが存在します。 r11 および LR 以外のレジスタがプッシュされていない場合、16 ビット mov となり、その他の場合は 32 ビット add となります。

折りたたまれない調整が指定された場合、命令 5 は明示的なスタック調整です。

命令 2 および 4 は、プッシュが必要かどうかに基づいて設定されます。 次の表は、CLR、および PF の各フィールドに基づいて保存されるレジスタの概要を示したものです。 すべての場合において、NReg + 4 に等しく、EReg + 8 に等しく、S は (~Stack Adjust) & 3 に等しくなります。

C L R PF プッシュされる整数レジスタ プッシュされる VFP レジスタ
0 0 0 0 r4 - r*N* なし
0 0 0 1 r*S* - r*N* なし
0 0 1 0 なし d8 - d*E*
0 0 1 1 r*S* - r3 d8 - d*E*
0 1 0 0 r4 - r*N*, LR なし
0 1 0 1 r*S* - r*N*, LR なし
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (無効なエンコード) (無効なエンコード)
1 0 0 1 (無効なエンコード) (無効なエンコード)
1 0 1 0 (無効なエンコード) (無効なエンコード)
1 0 1 1 (無効なエンコード) (無効なエンコード)
1 1 0 0 r4 - r*N*, r11, LR なし
1 1 0 1 r*S* - r*N*, r11, LR なし
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

標準関数のエピローグは同様の形式に従いますが、逆順になり、いくつかのオプションが追加されます。 エピローグは最大 5 命令の長さとなり、その形式はプロローグの形式によって厳密に規定されています。

指示 オペコードが想定される条件 サイズ オペコード
6 Stack Adjust!=0 および EF==0 16/32 add sp,sp,#xx
7 R==1 および Reg!=7 32 vpop {d8-dE}
8 C==1 または (L==1 および (H==0 または Ret !=0)) または R==0 または EF==1 16/32 pop {registers}
9a H==1 および (L==0 または Ret!=0) 16 add sp,sp,#0x10
9b H==1 および L==1 および Ret==0 32 ldr pc,[sp],#0x14
10a Ret==1 16 bx reg
10b Ret==2 32 b address

折りたたまれない調整が指定された場合、命令 6 は明示的なスタック調整です。 PFEF から独立しているため、命令 6 を含めずに命令 5 を含めたり、その逆にしたりすることが可能です。

命令 7 と 8 では、プロローグと同じロジックを使用してスタックから復元されるレジスタを決定しますが、次の 3 つのことが異なります。1 つは、PF の代わりに EF が使用される点、2 つめは、Ret = 0 および H = 0 の場合に LR はレジスタ リスト内の PC に置き換えられ、エピローグが直ちに終了する点、3 つめは、Ret = 0 および H = 1 の場合に LR はレジスタ リストから省略され、命令 9b によりポップされる点です。

H が設定されている場合、命令 9a または 9b があります。 命令 9a は、Ret が 0 以外の場合に使用されます。これは、10a または 10b のいずれかの存在も意味します。 L=1 の場合、命令 8 の一部として LR がポップされました。 命令 9b は L が 1 で Ret が 0 の場合に使用され、エピローグの早期終了、およびスタックの戻りと調整の同時実行を示します。

エピローグがまだ終了していない場合、命令 10a または 10b が存在し、Ret の値に基づいて 16 ビットまたは 32 ビットの分岐を示します。

.xdata レコード

パックされたアンワインド形式では関数のアンワインドの記述に十分でない場合、可変長の .xdata レコードを作成する必要があります。 このレコードのアドレスは、.pdata レコードの第 2 ワードに格納されています。 .xdata の形式は、次の 4 つのセクションのある、パックされた可変長ワード セットです。

  1. .xdata 構造体全体のサイズを記述し、主な関数データを提供する 1 から 2 ワードのヘッダー。 2 番目のワードが存在するのは、Epilogue Count フィールドと Code Words フィールドが 0 に設定されている場合のみです。 次の表で、各フィールドについて詳細に説明します。

    Word Bits 目的
    0 0-17 Function Length は、関数全体の長さを 2 で除算してバイト単位で示す 18 ビット フィールドです。 関数が 512 KB より大きい場合、複数の .pdata および .xdata レコードを使用して関数を記述する必要があります。 詳細については、このドキュメントの「大きな関数」セクションを参照してください。
    0 18-19 Vers は、残りの .xdata のバージョンを示す 2 ビット フィールドです。 現在、バージョン 0 のみが定義されています。値 1 ~ 3 は予約済みです。
    0 20 X は、例外データが存在する (1) か存在しない (0) かを示す 1 ビット フィールドです。
    0 21 E は、追加のスコープ ワードを後で必要とする (0) のではなく単一のエピローグを記述した情報がヘッダーにパックされている (1) ことを示す 1 ビット フィールドです。
    0 22 F は、このレコードが関数フラグメントを記述している (1) か、関数全体を記述している (0) かを示す 1 ビット フィールドです。 フラグメントは、プロローグが存在せず、すべてのプロローグ処理が無視されることを意味します。
    0 23-27 Epilogue Count は、E ビットの状態に応じて 2 つの意味を持つ 5 ビット フィールドです。

    - 0 の場合 E 、このフィールドはセクション 2 で説明するエピローグ スコープの合計数のカウントです。 32 個以上のスコープが関数に存在する場合、このフィールドと Code Words フィールドの両方が 0 に設定され、拡張ワードが必要であることを示す必要があります。
    - E が 1 の場合、このフィールドはエピローグのみを記述する最初のアンワインド コードのインデックスを指定します。
    0 28-31 Code Words は、すべてのアンワインド コードをセクション 4 に含めるために必要な 32 ビット ワードの数を指定する 4 ビット フィールドです。 64 個以上のアンワインド コード バイトに対して 16 ワード以上必要な場合、このフィールドと Epilogue Count フィールドの両方が 0 に設定され、拡張ワードが必要であることを示す必要があります。
    1 0-15 Extended Epilogue Count は、エピローグの数が通常よりも多い場合にエンコード用の追加の領域を提供する 16 ビット フィールドです。 このフィールドが含まれる拡張ワードは、最初のヘッダー ワードの Epilogue CountCode Words の各フィールドが 0 に設定されている場合にのみ存在します。
    1 16-23 Extended Code Words は、アンワインド コード ワードの数が通常よりも多い場合にエンコード用の追加の領域を提供する 8 ビット フィールドです。 このフィールドが含まれる拡張ワードは、最初のヘッダー ワードの Epilogue CountCode Words の各フィールドが 0 に設定されている場合にのみ存在します。
    1 24-31 予約されています。
  2. ヘッダーの E ビットが 0 に設定されている場合、例外データの後にはエピローグ スコープに関する情報のリストが続きます。この情報は 1 つのワードにパックされ、開始オフセットの昇順で格納されます。 各スコープには、次のフィールドが含まれます。

    Bits 目的
    0-17 Epilogue Start Offset は、エピローグのオフセット (バイト単位) を 2 で除算した値を記述する 18 ビット フィールドです。これは、関数の開始からの相対値です。
    18-19 Res は、将来の拡張用に予約された 2 ビット フィールドです。 この値は 0 である必要があります。
    20-23 Condition は、エピローグが実行される条件を提示する 4 ビット フィールドです。 条件を伴わないエピローグの場合、このフィールドは 0xE、つまり "常時" に設定する必要があります。 (エピローグは、全体が条件付きであるか全体が条件なしである必要があります。また、Thumb-2 モードでは、エピローグは IT オペコードの後の最初の命令で開始します。)
    24-31 Epilogue Start Index は、このエピローグを記述する最初のアンワインド コードのバイト インデックスを示す 8 ビット フィールドです。
  3. エピローグ スコープのリストの後には、アンワインド コードを含むバイトの配列 (詳細については、この記事の「アンワインド コード」セクションを参照) が存在します。 この配列は、最も近いフルワード境界の末尾に埋め込まれます。 バイトは、リトル エンディアン順で格納され、リトル エンディアン モードで直接フェッチ可能になっています。

  4. ヘッダーの X フィールドが 1 の場合、アンワインド コード バイトの後に例外ハンドラー情報が続きます。 これは、例外ハンドラーのアドレスを含む 1 つの例外ハンドラー RVA で構成されます。直後に、例外ハンドラーで必要とされる (可変長の) データ量が続きます。

.xdata レコードは、最初の 8 バイトをフェッチしてフル サイズのレコード (後続の可変サイズの例外の長さは含まれない) を計算できるように設計されています。 次のコード スニペットはレコードのサイズを計算します。

ULONG ComputeXdataSize(PULONG Xdata)
{
    ULONG Size;
    ULONG EpilogueScopes;
    ULONG UnwindWords;

    if ((Xdata[0] >> 23) != 0) {
        Size = 4;
        EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
        UnwindWords = (Xdata[0] >> 28) & 0x0f;
    } else {
        Size = 8;
        EpilogueScopes = Xdata[1] & 0xffff;
        UnwindWords = (Xdata[1] >> 16) & 0xff;
    }

    if (!(Xdata[0] & (1 << 21))) {
        Size += 4 * EpilogueScopes;
    }

    Size += 4 * UnwindWords;

    if (Xdata[0] & (1 << 20)) {
        Size += 4;  // Exception handler RVA
    }

    return Size;
}

プロローグおよび各エピローグにはアンワインド コードのインデックスがありますが、表はこれらの間で共有されます。 これらすべてが同じアンワインド コードを共有できることは、ほとんどありません。 コンパイラの作成者がこの状況に合わせて最適化することをお勧めします。これは、指定可能な最も大きいインデックスは 255 であり、このことによって、特定の関数に対して許容されるアンワインド コードの合計数が制限されるためです。

アンワインド コード

アンワインド コードの配列は、プロローグの効果を元に戻す方法を、操作を元に戻す順序に従って正確に記述した命令シーケンスのプールです。 アンワインド コードは、小型の命令セットで、バイト列としてエンコードされます。 実行が完了すると、呼び出し元関数へのリターン アドレスが LR レジスタに入り、すべての非 volatile レジスタが関数が呼び出された時点の値に復元されます。

例外が関数本体のみで発生し、プロローグまたはエピローグでは絶対に発生しないことが保証されている場合、必要なアンワインド シーケンスは 1 つのみです。 ただし、Windows アンワインド モデルでは、部分的に実行されたプロローグまたはエピローグからアンワインドする機能が求められます。 この要件に対応するため、アンワインド コードはプロローグおよびエピローグ内の関連オペコードへの明確な 1 対 1 のマッピングを持つように慎重に設計されています。 これは、次のような結果をもたらします。

  • アンワインド コードの数のカウントによるプロローグおよびエピローグの長さの計算が可能です。 16 ビットおよび 32 ビット オペコードの明確なマッピングがあるため、これは可変長 Thumb-2 命令を使用しても可能です。

  • エピローグ スコープの開始後の命令数をカウントすることにより、同等数のアンワインド コードをスキップし、残りのシーケンスを実行して、エピローグが実行していた部分的に実行されたアンワインドを完了することが可能です。

  • プロローグの終了前の命令数をカウントすることにより、同等数のアンワインド コードをスキップし、残りのシーケンスを実行して、実行が完了したプロローグの部分のみを元に戻すことが可能です。

次の表に、アンワインド コードからオペコードへのマッピングを示します。 最も一般的なコードは、1 バイトのみです。あまり一般的ではないものは、2、3、または 4 バイトを必要とします。 各コードは、最上位バイトから最下位バイトへと格納されています。 このアンワインド コード構造体は、ARM EABI で記述されるエンコーディングとは異なります。これは、これらのアンワインド コードが、部分的に実行されたプロローグおよびエピローグのアンワインドを可能にするために、プロローグおよびエピローグ内のオペコードに 1 対 1 でマッピングするように設計されているためです。

バイト 1 バイト 2 バイト 3 バイト 4 オペコード サイズ 説明
00-7F 16 add sp,sp,#X

ここで X は (コード & 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

Code & 0x2000 の場合は LR がポップされ、対応するビットが Code & 0x1FFF で設定されている場合は r0-r12 がポップされます
C0-CF 16 mov sp,rX

X は Code & 0x0F
D0-D7 16 pop {r4-rX,lr}

X は (Code & 0x03) + 4、Code & 0x04 の場合は LR がポップされます
D8-DF 32 pop {r4-rX,lr}

X は (Code & 0x03) + 8、Code & 0x04 の場合は LR がポップされます
E0-E7 32 vpop {d8-dX}

X は (Code & 0x07) + 8
E8-EB 00-FF 32 addw sp,sp,#X

ここで X は (コード & 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

Code & 0x0100 の場合は LR がポップされ、対応するビットが Code & 0x00FF で設定されている場合は r0-r7 がポップされます
EE 00-0F 16 Microsoft 固有の仕様
EE 10-FF 16 利用可能
EF 00-0F 32 ldr lr,[sp],#X

ここで X は (コード & 0x000F) * 4
EF 10-FF 32 利用可能
F0-F4 - 利用可能
F5 00-FF 32 vpop {dS-dE}

S が (コード & 0x00F0) >> 4 で E が Code & 0x000F
F6 00-FF 32 vpop {dS-dE}

S が ((Code & 0x00F0) 4) >> + 16、E が (Code & 0x000F) + 16 である場合
F7 00-FF 00-FF 16 add sp,sp,#X

ここで X は (コード & 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

ここで X は (コード & 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

ここで X は (コード & 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

ここで X は (コード & 0x00FFFFFF) * 4
FB 16 nop (16 ビット)
FC 32 nop (32 ビット)
FD 16 end + エピローグに 16 ビットの nop
FE 32 end + エピローグに 32 ビットの nop
FF - end

これは、アンワインド コード Code 内の各バイトの 16進値の範囲を示し、同時にオペコードのサイズ Opsize および対応する元の命令解釈を示したものです。 空白のセルは、短いアンワインド コードを示しています。 複数のバイトをカバーする大きな値を持つ命令では、最上位ビットが最初に格納されます。 Opsize フィールドは、各 Thumb-2 演算に関連付けられた暗黙的なオペコード サイズを示しています。 表内の異なるエンコーディングの明らかな重複項目は、異なるオペコード サイズの区別に使用されます。

アンワインド コードは、コードの最初のバイトがコードの合計サイズ (バイト) と命令ストリーム内の対応するオペコードのサイズの両方を示すように設計されています。 プロローグまたはエピローグのサイズを計算するには、アンワインド コードをシーケンスの最初から最後まで調べて、ルックアップ テーブルまたは同様の方法を使用して対応するオペコードの長さを特定します。

アンワインド コード 0xFD および 0xFE は、通常の終了コード 0xFF と同等ですが、エピローグの場合は 16 ビットまたは 32 ビットの特別な nop オペコードに相当します。 プロローグでは、コード 0xFD、0xFE および 0xFF は正確に同等です。 これは、一般的なエピローグの終了 bx lr または b <tailcall-target> に相当します。同等のプロローグ命令はありません。 これにより、アンワインド シーケンスをプロローグとエピローグの間で共有できる機会が増えます。

多くの場合、プロローグとすべてのエピローグで同じアンワインド コードのセットを使用することが可能です。 ただし、部分的に実行されたプロローグおよびエピローグのアンワインドを処理するには、順序や動作が異なる複数のアンワインド コード シーケンスが必要となる場合があります。 このため、それぞれのエピローグに、実行を開始する場所を示すアンワインド配列への独自のインデックスがあります。

部分的なプロローグおよびエピローグのアンワインド

アンワインドが行われる状況としては、プロローグやすべてのエピローグではなく、関数の本体で例外が発生した場合が最も一般的です。 この場合は、アンワインダーが、アンワインド配列内のコードを、インデックス 0 から終了オペコードが検出されるまで実行します。

プロローグまたはエピローグの実行中に例外が発生した場合、スタック フレームは部分的にのみ構築されており、正しく元に戻すには、アンワインダーは実行された内容を正確に特定する必要があります。

たとえば、次のプロローグおよびエピローグ シーケンスの場合を考えます。

0000:   push  {r0-r3}         ; 0x04
0002:   push  {r4-r9, lr}     ; 0xdd
0006:   mov   r7, sp          ; 0xc7
...
0140:   mov   sp, r7          ; 0xc7
0142:   pop   {r4-r9, lr}     ; 0xdd
0146:   add   sp, sp, #16     ; 0x04
0148:   bx    lr

各オペコードの次には、この演算を記述する適切なアンワインド コードがあります。 プロローグのアンワインド コード シーケンスは、エピローグのアンワインド コードのミラー イメージになっています。 この状況は一般的であり、このため、プロローグのアンワインド コードがプロローグの実行順と逆の順序で格納されることが常に想定されます。 これにより、次のアンワインドコードの共通セットが提供されます。

0xc7, 0xdd, 0x04, 0xfd

0xFD コードは、シーケンスの終了用の特別なコードであり、エピローグがプロローグよりも長い、1 つの 16 ビット命令であることを意味します。 これにより、より多くのアンワインド コードの共有が可能になっています。

この例では、プロローグとエピローグの間の関数本体の実行中に例外が発生した場合、アンワインドはエピローグの場合で、エピローグ コード内のオフセット 0 で開始されます。 これは、例のオフセット 0x140 に対応します。 クリーンアップは行われていないため、アンワインダーは、アンワインド シーケンス全体を実行します。 一方、例外がエピローグ コードの開始後の 1 つの命令で発生した場合、アンワインダーは最初のアンワインド コードをスキップして正常にアンワインドできます。 オペコードとアンワインド コード間の 1 対 1 のマッピングを前提にすると、エピローグ内の命令 n からのアンワインドの場合は、アンワインダーは最初の n 個のアンワインド コードをスキップします。

プロローグでは同様のロジックが逆に機能します。 プロローグ内のオフセット 0 からのアンワインドの場合、処理を実行する必要はありません。 プロローグ内のある命令からのアンワインドの場合、プロローグ アンワインド コードは逆順で格納されているため、アンワインド シーケンスは 1 つのアンワインド コードを末尾から開始する必要があります。 一般的なケースでは、プロローグ内の命令 n からのアンワインドの場合、アンワインドはコードのリストの末尾から n 番目のコードの実行から開始する必要があります。

プロローグとエピローグのアンワインド コードは、正確に一致していない場合があります。 この場合、アンワインド コード配列にコードのシーケンスがいくつか含まれている必要がある場合があります。 コードの処理を開始するオフセットを特定するには、次のロジックを使用します。

  1. 関数本体内からのアンワインドの場合、インデックス 0 でアンワインド コードの実行を開始し、終了オペコードに到達するまで続行します。

  2. エピローグ内からのアンワインドの場合、エピローグ スコープにより提供されるエピローグ固有の開始インデックスを使用します。 エピローグの開始からの PC のバイト数を計算します。 既に実行済みの命令がすべて含まれるまでアンワインド コードを前方にスキップします。 その位置で開始するアンワインド シーケンスを実行します。

  3. プロローグ内からのアンワインドの場合、アンワインド コードのインデックス 0 から開始します。 シーケンスからプロローグ コードの長さを計算し、プロローグの末尾から PC までのバイト数を計算します。 未実行の命令がすべて含まれるまでアンワインド コードを前方にスキップします。 その位置で開始するアンワインド シーケンスを実行します。

プロローグのアンワインド コードは、常に配列の最初に存在する必要があります。 これらは本体内からのアンワインドという一般的な場合のアンワインドに使用されるコードでもあります。 すべてのエピローグ固有のシーケンスは、プロローグ コード シーケンスの直後に続く必要があります。

関数フラグメント

コードの最適化のために、関数を個別の部分に分割すると便利な場合があります。 このようにすると、各関数フラグメントには、個別の .pdata レコードと、場合によっては .xdata が必要となります。

関数プロローグが関数の先頭にあり分割できない場合は、4 つの関数フラグメントが考えられます。

  • プロローグのみ: すべてのエピローグは別のフラグメントにあります。

  • プロローグと 1 つ以上のエピローグ: 多くのエピローグが別のフラグメントにあります。

  • プロローグまたはエピローグなし: プロローグおよび 1 つ以上のエピローグが別のフラグメントにあります。

  • エピローグのみ: プロローグ、および場合によっては多くのエピローグが別のフラグメントにあります。

1 番目の場合では、プロローグのみを記述する必要があります。 このことは、コンパクト .pdata 形式の場合は通常どおりプロローグを記述して、Ret 値に 3 を指定してエピローグがないことを示すことで実現できます。 フル .xdata 形式では、インデックス 0 に通常どおりプロローグ アンワインド コードを指定して、エピローグ カウントに 0 を指定することでこのことを実現できます。

2 番目の場合は、通常の関数です。 フラグメントにエピローグが 1 つのみ存在する場合で、それがフラグメントの末尾に位置する場合、コンパクト .pdata レコードを使用できます。 その他の場合は、フル .xdata レコードを使用する必要があります。 エピローグ開始用に指定されたオフセットは、フラグメントの開始からの相対値であり、元の関数からの相対値ではない点に留意してください。

3 番目および 4 番目の場合は、プロローグが含まれていない点を除いて、それぞれ 1 番目および 2 番目の場合の変化型です。 これらの状況においては、エピローグの開始前にコードがあることが想定され、そのコードは関数本体の一部と見なされます。通常、これはプロローグの効果を元に戻すことでアンワインドされます。 したがって、これらの場合は、擬似プロローグと共にエンコードする必要があります。擬似プロローグは本体内からアンワィンドする方法を記述しますが、フラグメントの開始で部分アンワインドを実行するかどうかを決定するときに 0 長として扱われます。 この擬似プロローグは、エピローグと同じアンワインド コードを使用して記述することもできます。これは、これらのコードが同等の演算を実行すると想定されるためです。

3 番目および 4 番目の場合、擬似プロローグの存在は、コンパクト .pdata レコードの Flag フィールドを 2 に設定するか、.xdata ヘッダーの F フラグを 1 に設定することで指定されます。 両方の場合で、部分的なプロローグ アンワインドのチェックが無視され、すべての非エピローグ アンワインドは完全なものであると見なされます。

大きな関数

フラグメントを使用して、.xdata ヘッダーのビット フィールドによって設定される 512 KB 制限を超える関数を記述できます。 より大きな関数を記述するには、512 KB より小さいフラグメントに関数を分割します。 各フラグメントは、エピローグが複数の部分に分割されないように調整する必要があります。

関数の最初のフラグメントにのみプロローグが含まれます。 その他のすべてのフラグメントは、プロローグが含まれていないとマークされます。 エピローグの数に応じて、各フラグメントに 0 個以上のエピローグが含まれます。 フラグメント内の各エピローグ スコープでは、フラグメントの開始からの相対値で開始オフセットが指定されています。関数の開始からの相対値ではありません。

フラグメントにプロローグとエピローグが含まれていない場合でも、関数の本体内からのアンワインド方法を記述するための固有の .pdata (および場合によっては .xdata) レコードが必要です。

シュリンク ラップ

より複雑で特殊な場合の関数フラグメントは、シュリンク ラッピングと呼ばれています。 これは、レジスタの保存を関数の最初から後の関数に遅延する手法です。 これにより、レジスタの保存を必要としない単純なケースが最適化されます。 このケースは次の 2 つの部分で構成されています。スタック領域を割り当てるが最小セットのレジスタを保存する外部の領域と、他のレジスタの保存と復元を行う内部の領域です。

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatiles
    sub    sp, sp, #0x100    ; A: allocate all stack space up front
    ...                      ; A:
    add    r0, sp, #0xE4     ; A: prepare to do the inner save
    stm    r0, {r5-r11}      ; A: save remaining non-volatiles
    ...                      ; B:
    add    r0, sp, #0xE4     ; B: prepare to do the inner restore
    ldm    r0, {r5-r11}      ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C:

シュリンク ラップされた関数は、一般的には通常のプロローグ内に追加のレジスタ保存用領域を事前に割り当て、次に str ではなく stm または push を使用してレジスタを保存することが想定されています。 このアクションにより、すべてのスタック ポインター操作が関数の元のプロローグ内で維持されます。

シュリンク ラップされた関数は、3 つの領域に分割されている必要があります。これらは、コメントで AB、および C としてマークされています。 初めの A 領域は、関数の開始から追加の非 volatile 保存の終了までをカバーします。 .pdata または .xdata レコードを構築し、プロローグは含め、エピローグは含めずに、このフラグメントを記述する必要があります。

中央の B 領域では、プロローグおよびエピローグのないフラグメントを記述する固有の .pdata または .xdata レコードを取得します。 ただし、この領域は関数本体と見なされるため、アンワインド コードが存在する必要があります。 コードは、領域 A プロローグ内に保存された元のレジスタと領域 B に入る前に保存された追加のレジスタの両方を表す複合プロローグを、これらが 1 つのシーケンスの操作で生成されたように記述する必要があります。

領域 B のレジスタ保存は、"内部のプロローグ" と見なすことはできません。これは、領域 B 用に記述されたこの複合プロローグは、領域 A プロローグと保存された追加のレジスタの両方を記述する必要があるためです。 フラグメント B にプロローグがある場合、アンワインド コードはそのプロローグのサイズも暗黙的に指定します。また、追加レジスタのみを保存するオペコードに 1 対 1 でマップする方法で複合プロローグを記述する方法はありません。

追加のレジスタ保存は、領域 A の一部と見なされる必要があります。これは、これらが完了するまで、複合プロローグはスタックの状態を正確に示さないためです。

最後の C 領域では、プロローグがなくエピローグがあるフラグメントを記述する固有の .pdata または .xdata レコードを取得します。

領域 B に入る前に行われるスタック操作を 1 つの命令に削減できる場合、別のアプローチも機能できます。

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatile registers
    sub    sp, sp, #0xE0     ; A: allocate minimal stack space up front
    ...                      ; A:
    push   {r4-r9}           ; A: save remaining non-volatiles
    ...                      ; B:
    pop    {r4-r9}           ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C: restore non-volatile registers

重要なことは、各命令の境界で、スタックは該当領域のアンワインド コードと完全に整合しているということです。 この例の内部プッシュの前にアンワインドが発生した場合、これは領域 A の一部と見なされます。 領域 A のプロローグのみがアンワインドされます。 内部プッシュの後にアンワインドが発生した場合、これはプロローグがなく、領域 B の一部と見なされます。 ただし、内部プッシュと領域 A の元のプロローグの両方を記述したアンワインド コードがあります。 同様のロジックは、内部ポップに対して保持されます。

エンコーディングの最適化

アンワインド コードの多様性、およびコンパクト形式および拡張形式のデータを利用する能力により、エンコーディングを最適化して領域を削減する多くの機会が提供されます。 これらの手法を積極的に使用することで、アンワインド コードの使用による関数およびフラグメントの記述の最終的なオーバーヘッドを最小化できます。

最適化における最も重要なアイデア: アンワインド目的のプロローグおよびエピローグの境界を、コンパイラの観点からのプロローグおよびエピローグの境界と混同しないことです。 アンワインド境界は、縮小して緊密にし、効率を改善できます。 たとえば、プロローグにはスタックのセットアップ後に検証チェックを行うためのコードを含めることができます。 ただし、すべてのスタック操作が完了した後は、以降の演算をエンコードする必要はなく、以降のすべてのものをプロローグのアンワインドから除去できます。

同じルールが関数長にも適用されます。 データ (リテラル プールなど) が関数内のエピローグの後に存在する場合、関数長の一部として含めることは適切ではありません。 関数を短縮し、その関数の部分であるコードのみにすることにより、エピローグが末尾に置かれる可能性がかなり高くなり、コンパクト .pdata レコードを使用できます。

プロローグでは、スタック ポインターが別のレジスタに保存されると、一般的には以降のオペコードを記録する必要がなくなります。 関数をアンワインドするには、保存されたレジスタからの SP の復旧が最初に行われます。 以降の演算はアンワインドに影響しません。

単一命令エピローグは、スコープとしても、アンワインド コードとしても、エンコードする必要がありません。 その命令が実行される前にアンワインドが発生する場合、関数の本体内からのアンワインドと見なしても問題ありません。 プロローグのアンワインド コードを実行するだけで十分です。 単一命令が実行された後にアンワインドが発生した場合、本質的にそのアンワインドは別の領域で発生します。

複数命令エピローグは、エピローグの最初の命令をエンコードする必要がありません。これは、アンワインドが命令の実行前に実行された場合はプロローグ全体のアンワインドで十分であるという前述のポイントと同じ理由からです。 最初の命令以降にアンワインドが行われた場合は、以降の演算のみを考慮する必要があります。

アンワインド コードの再使用は積極的に行うべきです。 各エピローグ スコープにより指定されたインデックスは、アンワインド コードの配列内の任意の開始点をポイントします。 これは、前のシーケンスの開始をポイントする必要はありません。中間をポイントできます。 最適な方法は、アンワインド コード シーケンスを生成することです。 次に、既にエンコードされたシーケンスのプール内で、一致する正確なバイトをスキャンします。 完全に一致したすべてのものを再使用の開始点として使用します。

単一命令エピローグが無視された後、残りのエピローグがない場合は、コンパクト .pdata 形式の使用を検討します。エピローグが 1 つも存在しない場合は、使用できる可能性がより高くなります。

これらの例では、イメージ ベースは 0x00400000 です。

例 1: リーフ関数、ローカルなし

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x000535F8 (= 0x004535F8-0x00400000)
  • Word 1

    • Flag = 1。標準のプロローグとエピローグの形式を示します

    • Function Length = 0x31 (= 0x62/2)

    • Ret = 1。16 ビット分岐戻りを示します

    • H = 0。パラメーターが元に戻されなかったことを示します

    • R =0 および Reg = 1。r4-r5 のプッシュ/ポップを示します

    • L = 0。LR の保存/復元を行わないことを示します

    • C = 0。フレーム チェーンがないことを示します

    • Stack Adjust = 0。スタックの調整がないことを示します

例 2: ネストされた関数、ローカル割り当てあり

Prologue:
  004533AC: B5F0      push        {r4-r7, lr}
  004533AE: B083      sub         sp, sp, #0xC
Epilogue:
  00453412: B003      add         sp, sp, #0xC
  00453414: BDF0      pop         {r4-r7, pc}

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x000533AC (= 0x004533AC -0x00400000)
  • Word 1

    • Flag = 1。標準のプロローグとエピローグの形式を示します

    • Function Length = 0x35 (= 0x6A/2)

    • Ret = 0。pop {pc} で戻されることを示します

    • H = 0。パラメーターが元に戻されなかったことを示します

    • R =0 および Reg = 3。r4-r7 のプッシュ/ポップを示します

    • L = 1。LR の保存/復元を行ったことを示します

    • C = 0。フレーム チェーンがないことを示します

    • Stack Adjust = 3 (= 0x0C/4)

例 3: ネストされた可変個引数関数

Prologue:
  00453988: B40F      push        {r0-r3}
  0045398A: B570      push        {r4-r6, lr}
Epilogue:
  004539D4: E8BD 4070 pop         {r4-r6}
  004539D8: F85D FB14 ldr         pc, [sp], #0x14

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x00053988 (= 0x00453988-0x00400000)
  • Word 1

    • Flag = 1。標準のプロローグとエピローグの形式を示します

    • Function Length = 0x2A (= 0x54/2)

    • Ret = 0。pop {pc} スタイルで戻されることを示します (この場合 ldr pc,[sp],#0x14 が戻される)

    • H = 1。パラメーターが元に戻されたことを示します

    • R =0 および Reg = 2。r4-r6 のプッシュ/ポップを示します

    • L = 1。LR の保存/復元を行ったことを示します

    • C = 0。フレーム チェーンがないことを示します

    • Stack Adjust = 0。スタックの調整がないことを示します

例 4: 複数のエピローグを持つ関数

Prologue:
  004592F4: E92D 47F0 stmdb       sp!, {r4-r10, lr}
  004592F8: B086      sub         sp, sp, #0x18
Epilogues:
  00459316: B006      add         sp, sp, #0x18
  00459318: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  0045943E: B006      add         sp, sp, #0x18
  00459440: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  004595D4: B006      add         sp, sp, #0x18
  004595D6: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459606: B006      add         sp, sp, #0x18
  00459608: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459636: F028 FF0F bl          KeBugCheckEx     ; end of function

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x000592F4 (= 0x004592F4-0x00400000)
  • Word 1

    • Flag = 0。.xdata レコードが存在することを示します (複数のエピローグの場合に必要)

    • .xdata アドレス - 0x00400000

.xdata (可変、6 ワード):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0。.xdata の最初のバージョンを示します

    • X = 0、例外データがないことを示します

    • E = 0。エピローグのスコープのリストを示します

    • F = 0、プロローグを含む関数全体の記述を示します

    • Epilogue Count = 0x04、エピローグ スコープが合計 4 個であることを示します

    • Code Words = 0x01、アンワインド コードの、32 ビットの 1 ワードを示します

  • ワード 1~4、4 か所の 4 つのエピローグを記述します。 各スコープには、アンワインド コードの共通セットがあり、オフセット 0x00 でプロローグと共有されています。条件 0x0E (常時) が指定されており、これは条件を伴いません。

  • ワード 5 で始まるアンワインド コード: (プロローグとエピローグ共通)

    • アンワインド コード 0 = 0x06: sp += (6 << 2)

    • アンワインド コード 1 = 0xDE: pop {r4-r10, lr}

    • アンワインド コード 2 = 0xFF: end

例 5: 動的スタックと内部のエピローグを持つ関数

Prologue:
  00485A20: B40F      push        {r0-r3}
  00485A22: E92D 41F0 stmdb       sp!, {r4-r8, lr}
  00485A26: 466E      mov         r6, sp
  00485A28: 0934      lsrs        r4, r6, #4
  00485A2A: 0124      lsls        r4, r4, #4
  00485A2C: 46A5      mov         sp, r4
  00485A2E: F2AD 2D90 subw        sp, sp, #0x290
Epilogue:
  00485BAC: 46B5      mov         sp, r6
  00485BAE: E8BD 41F0 ldm         sp!, {r4-r8, lr}
  00485BB2: B004      add         sp, sp, #0x10
  00485BB4: 4770      bx          lr
  ...
  00485E2A: F7FF BE7D b           #0x485B28    ; end of function

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x00085A20 (= 0x00485A20-0x00400000)
  • Word 1

    • Flag = 0。.xdata レコードが存在することを示します (複数のエピローグの場合に必要)

    • .xdata アドレス - 0x00400000

.xdata (可変、3 ワード):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0。.xdata の最初のバージョンを示します

    • X = 0、例外データがないことを示します

    • E = 0。エピローグのスコープのリストを示します

    • F = 0、プロローグを含む関数全体の記述を示します

    • Epilogue Count = 0x001、エピローグ スコープが合計 1 個であることを示します

    • Code Words = 0x01、アンワインド コードの、32 ビットの 1 ワードを示します

  • ワード 1: オフセット 0xC6 (= 0x18C/2) のエピローグ スコープ、アンワインド コード インデックスを 0x00 で開始、条件は 0x0E (常時)

  • ワード 2 で始まるアンワインド コード: (プロローグとエピローグ共通)

    • アンワインド コード 0 = 0xC6: sp = r6

    • アンワインド コード 1 = 0xDC: pop {r4-r8, lr}

    • アンワインド コード 2 = 0x04: sp += (4 << 2)

    • アンワインド コード 3 = 0xFD: end、エピローグの 16 ビット 命令としてカウント。

例 6: 例外ハンドラーを持つ関数

Prologue:
  00488C1C: 0059 A7ED dc.w  0x0059A7ED
  00488C20: 005A 8ED0 dc.w  0x005A8ED0
FunctionStart:
  00488C24: B590      push        {r4, r7, lr}
  00488C26: B085      sub         sp, sp, #0x14
  00488C28: 466F      mov         r7, sp
Epilogue:
  00488C6C: 46BD      mov         sp, r7
  00488C6E: B005      add         sp, sp, #0x14
  00488C70: BD90      pop         {r4, r7, pc}

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x00088C24 (= 0x00488C24-0x00400000)
  • Word 1

    • Flag = 0。.xdata レコードが存在することを示します (複数のエピローグの場合に必要)

    • .xdata アドレス - 0x00400000

.xdata (可変、5 ワード):

  • Word 0

    • Function Length =0x000027 (= 0x00004E/2)

    • Vers = 0。.xdata の最初のバージョンを示します

    • X = 1。例外データが存在することを示します

    • E = 1。単一エピローグを示します

    • F = 0、プロローグを含む関数全体の記述を示します

    • Epilogue Count = 0x00、エピローグ アンワインド コードがオフセット 0x00で開始することを示します

    • Code Words = 0x02、アンワインド コードの、32 ビットの 2 ワードを示します

  • ワード 1 で開始するアンワインド コード:

    • アンワインド コード 0 = 0xC7: sp = r7

    • アンワインド コード 1 = 0x05: sp += (5 << 2)

    • アンワインド コード 2 = 0xED/0x90: pop {r4, r7, lr}

    • アンワインド コード 4 = 0xFF: end

  • ワード 3 は、例外ハンドラー = 0x0019A7ED (= 0x0059A7ED - 0x00400000) を指定します

  • ワード 4 およびそれ以降は、インライン化された例外データです。

例 7: funclet

Function:
  00488C72: B500      push        {lr}
  00488C74: B081      sub         sp, sp, #4
  00488C76: 3F20      subs        r7, #0x20
  00488C78: F117 0308 adds        r3, r7, #8
  00488C7C: 1D3A      adds        r2, r7, #4
  00488C7E: 1C39      adds        r1, r7, #0
  00488C80: F7FF FFAC bl          target
  00488C84: B001      add         sp, sp, #4
  00488C86: BD00      pop         {pc}

.pdata (固定、2 ワード):

  • Word 0

    • Function Start RVA = 0x00088C72 (= 0x00488C72-0x00400000)
  • Word 1

    • Flag = 1。標準のプロローグとエピローグの形式を示します

    • Function Length = 0x0B (= 0x16/2)

    • Ret = 0。pop {pc} で戻されることを示します

    • H = 0。パラメーターが元に戻されなかったことを示します

    • R=0 および Reg = 7。レジスタが保存/復元されなかったことを示します

    • L = 1。LR の保存/復元を行ったことを示します

    • C = 0。フレーム チェーンがないことを示します

    • Stack Adjust = 1。1 × 4 バイトのスタック調整を示します

関連項目

ARM ABI 規則の概要
Visual C++ の ARM への移行に関する一般的な問題