ARM 例外処理
ARM 版 Windows は、ハードウェアで生成される非同期例外とソフトウェアで生成される同期例外に対して、同じ構造化例外処理メカニズムを使用します。言語固有の例外ハンドラーは、言語ヘルパー関数を使用することで、Windows 構造化例外処理に付加して構築します。このドキュメントでは、ARM 版 Windows における例外処理、MASM および Visual C++ コンパイラによって生成されたコードが使用する言語ヘルパーについて説明します。
ARM 例外処理
ARM 版 Windows では、structured exception handling (SEH) 中に、アンワインド コード を使用してスタック アンワインドを制御しています。アンワインド コードは、実行可能イメージの .xdata セクションに格納されるバイトのシーケンスです。これらのコードには、関数のプロローグおよびエピローグ コードの演算を抽象的な方法で記述し、呼び出し元のスタック フレームへのアンワインドに備えて、関数のプロローグの効果を元に戻すことができるようにします。
ARM EABI (埋め込みアプリケーション バイナリ インターフェイス) には、アンワインド コードを使用する例外アンワインド モデルを指定しますが、Windows での SEH アンワインドの場合、プロセッサが関数のプロローグまたはエピローグの途中にある非同期の場合を処理する必要があるため、これは十分ではありません。また、Windows では、アンワインド制御が関数レベルのアンワインドと言語固有スコープのアンワインドに分離されます。これは、ARM EABI で統合されます。これらの理由から、ARM 版 Windows では、アンワインドのデータと手順の詳細が指定されます。
外部からの影響
ARM 版 Windows 用の実行可能イメージでは、移植可能な実行可能 (PE) 形式が使用されます。詳細については、「Microsoft PE and COFF 仕様」を参照してください。例外処理情報は、イメージの .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 ブロックへのポインター、または標準関数アンワインド シーケンスを記述するパックされたワードが含まれます。
ワード オフセット |
ビット |
目的 |
---|---|---|
0 |
0-31 |
Function Start RVA は、関数の開始の 32 ビット の RVA です。関数に Thumb コードが含まれている場合は、このアドレスの下位ビットを設定する必要があります。 |
1 |
0-1 |
Flag は、2 番目の .pdata ワードである残りの 30 ビットを解釈する方法を示す 2 ビット フィールドです。Flag が 0 の場合、残りのビットは Exception Information RVA を形成します (下位 2 ビットは暗黙的に 0)。Flag が 0 以外の場合、残りのビットは Packed Unwind Data 構造体を形成します。 |
1 |
2-31 |
Exception Information RVA または Packed Unwind Data。 Exception Information RVA は、.xdata セクションに格納されている可変長例外情報構造体のアドレスです。このデータは、4 バイトでアラインされている必要があります。 Packed Unwind Data は、関数からのアンワインドに必要な演算の圧縮記述です (標準フォームを想定)。この場合、.xdata レコードは必要ありません。 |
パックされたアンワインド データ
関数のプロローグおよびエピローグが下に示す標準形式に従っている場合、パックされたアンワインド データを使用できます。これにより .xdata レコードが必要なくなり、アンワインド データの提供に必要な領域が大幅に削減されます。標準のプロローグおよびエピローグは、例外ハンドラーを必要とせず、セットアップおよび終了操作を標準的な順序で実行する単純な関数に共通する要件を満たすように設計されています。
次の表は、パックされたアンワインド データが含まれる .pdata レコードの形式を示しています。
ワード オフセット |
ビット |
目的 |
---|---|---|
0 |
0-31 |
Function Start RVA は、関数の開始の 32 ビット の RVA です。関数に Thumb コードが含まれている場合は、このアドレスの下位ビットを設定する必要があります。 |
1 |
0-1 |
Flag は、次の意味を持つ 2 ビットフィールドです。
|
1 |
2-12 |
Function Length は、関数全体の長さを 2 で除算してバイト単位で示す 11 ビット フィールドです。関数が 4KB より大きい場合、フル .xdata レコードを代わりに使用する必要があります。 |
1 |
13-14 |
Ret は、関数が戻される方法を示す 2 ビット フィールドです。
|
1 |
15 |
H は、関数が、開始時に整数パラメーター レジスタ (r0-r3) をプッシュして、戻される前にスタックの 16 ビットを解放することでこれらのレジスタを元に戻すかどうかを示す 1 ビット フラグです。(0 = レジスタを元に戻しません、1 = レジスタを元に戻します。) |
1 |
16-18 |
Reg は、最後に保存された非 volatile レジスタのインデックスを示す 3 ビット フィールドです。R ビットが 0 の場合、整数レジスタのみが保存され、r4-rN の範囲にあると想定されます。ここで、N は 4 + Reg に等しくなります。R ビットが 1 の場合、浮動小数点レジスタのみが保存され、d8-dN の範囲にあると想定されます。ここで、N は 8 + Reg に等しくなります。R = 1 と Reg = 7 の特別な組み合わせは、保存されたレジスタがないことを示します。 |
1 |
19 |
R は、保存された非 volatile レジスタが整数レジスタであるか (0)、浮動小数点レジスタであるか (1) を示す 1 ビットのフラグです。R が 1 に設定され、Reg フィールドが 7 に設定されている場合は、プッシュされた非 volatile レジスタがないことを示します。 |
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 ビットは特別な意味を持ちます。
|
上のエンコーディングに冗長性がある可能性があるため、次の制限が適用されます。
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 or L==1 or R==0 or PF==1 |
16/32 |
push {registers} |
80-BF/D0-DF/EC-ED |
3a |
C==1 and (L==0 and R==1 and PF==0) |
16 |
mov r11,sp |
C0-CF/FB |
3b |
C==1 and (L==1 or R==0 or PF==1) |
32 |
add r11,sp,#xx |
FC |
4 |
R==1 and Reg != 7 |
32 |
vpush {d8-dE} |
E0-E7 |
5 |
Stack Adjust != 0 and 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 は、プッシュが必要かどうかに基づいて設定されます。次の表は、C、L、R、および PF の各フィールドに基づいて保存されるレジスタの概要を示したものです。すべての場合において、N は Reg + 4 に等しく、E は Reg + 8 に等しく、S は (~Stack Adjust) & 3 に等しいです。
C |
L |
R |
PF |
プッシュされる整数レジスタ |
プッシュされる VFP レジスタ |
---|---|---|---|---|---|
0 |
0 |
0 |
0 |
r4-rN |
none |
0 |
0 |
0 |
1 |
rS-rN |
none |
0 |
0 |
1 |
0 |
none |
d8-dE |
0 |
0 |
1 |
1 |
rS-r3 |
d8-dE |
0 |
1 |
0 |
0 |
r4-rN, LR |
none |
0 |
1 |
0 |
1 |
rS-rN, LR |
none |
0 |
1 |
1 |
0 |
LR |
d8-dE |
0 |
1 |
1 |
1 |
rS-r3, LR |
d8-dE |
1 |
0 |
0 |
0 |
r4-rN, r11 |
none |
1 |
0 |
0 |
1 |
rS-rN, r11 |
none |
1 |
0 |
1 |
0 |
r11 |
d8-dE |
1 |
0 |
1 |
1 |
rS-r3, r11 |
d8-dE |
1 |
1 |
0 |
0 |
r4-rN, r11, LR |
none |
1 |
1 |
0 |
1 |
rS-rN, r11, LR |
none |
1 |
1 |
1 |
0 |
r11, LR |
d8-dE |
1 |
1 |
1 |
1 |
rS-r3, r11, LR |
d8-dE |
標準関数のエピローグは同様の形式に従いますが、逆順になり、いくつかのオプションが追加されます。エピローグは最大 5 命令の長さとなり、その形式はプロローグの形式によって厳密に規定されています。
命令 |
オペコードが想定される条件 |
サイズ |
オペコード |
---|---|---|---|
6 |
Stack Adjust != 0 and EF==0 |
16/32 |
add sp,sp,#xx |
7 |
R==1 and Reg != 7 |
32 |
vpop {d8-dE} |
8 |
C==1 or (L==1 and H==0) or R==0 or EF==1 |
16/32 |
pop {registers} |
9a |
H==1 and L==0 |
16 |
add sp,sp,#0x10 |
9b |
H==1 and L==1 |
32 |
ldr pc,[sp],#0x14 |
10a |
Ret==1 |
16 |
bx reg |
10b |
Ret==2 |
32 |
b address |
折りたたまれない調整が指定された場合、命令 6 は明示的なスタック調整です。PF は EF から独立しているため、命令 6 を含めずに命令 5 を含めたり、その逆にしたりすることが可能です。
命令 7 と 8 では、プロローグと同じロジックを使用してスタックから復元されるレジスタを決定しますが、次の 2 つのことが異なります。1 つは、PF の代わりに EF が使用される点、もう 1 つは、Ret = 0 の場合に LR はレジスタ リスト内の PC に置き換えられ、エピローグが直ちに終了する点です。
H が設定されている場合、命令 9a または 9b があります。命令 9a は L が 0 の場合に使用され、LR がスタック上にないことを示します。この場合、スタックは手動で調整され、明示的な戻りを指定するために Ret が 1 または 2 である必要があります。命令 9b は L が 1 の場合に使用され、エピローグの早期終了、およびスタックの戻りと調整の同時実行を示します。
エピローグがまだ終了していない場合、命令 10a または 10b が存在し、Ret の値に基づいて 16 ビットまたは 32 ビットのブランチを示します。
.xdata レコード
パックされたアンワインド形式では関数のアンワインドの記述に十分でない場合、可変長の .xdata レコードを作成する必要があります。このレコードのアドレスは、.pdata レコードの第 2 ワードに格納されています。.xdata の形式は、次の 4 つのセクションのある、パックされた可変長ワード セットです。
.xdata 構造体全体のサイズを記述し、主な関数データを提供する 1 ~ 2 ワードのヘッダー。2 番目のワードは、Epilogue Count と Code Words フィールドが両方とも 0 に設定されている場合のみ存在します。次の表で、各フィールドについて詳細に説明します。
Word
ビット
目的
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 ビット フィールドです。
E が 0 の場合、このフィールドはセクション 3 に記述される例外スコープの合計数のカウントとなります。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 Count と Code Words の各フィールドが 0 に設定されている場合にのみ存在します。
1
16-23
Extended Code Words は、アンワインド コード ワードの数が通常よりも多い場合にエンコーディング用の追加の領域を提供する 8 ビット フィールドです。このフィールドが含まれる拡張ワードは、最初のヘッダー ワードの Epilogue Count と Code Words の各フィールドが 0 に設定されている場合にのみ存在します。
1
24-31
予約されています。
ヘッダーの E ビットが 0 に設定されている場合、例外データの後にはエピローグ スコープに関する情報のリストが続きます。この情報は 1 つのワードにパックされ、開始オフセットの昇順で格納されます。各スコープには、次のフィールドが含まれます。
ビット
目的
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 ビット フィールドです。
エピローグ スコープのリストの後には、アンワインド コードを含むバイトの配列 (詳細については、この記事の「アンワインド コード」セクションを参照) が存在します。この配列は、最も近いフルワード境界の末尾に埋め込まれます。バイトは、リトル エンディアン順で格納され、リトル エンディアン モードで直接フェッチ可能になっています。
ヘッダーの X フィールドが 1 の場合、アンワインド コード バイトの後に例外ハンドラー情報が続きます。これは、例外ハンドラーのアドレスを含む 1 つの Exception Handler RVA で構成されます。直後に、例外ハンドラーで必要とされる (可変長の) データ量が続きます。
.xdata レコードは、最初の 8 バイトをフェッチしてフル サイズのレコード (後続の可変サイズの例外の長さは含まれない) を計算できるように設計されています。次のコード スニペットはレコードのサイズを計算します。
ULONG ComputeXdataSize(PULONG *Xdata)
{
ULONG EpilogueScopes;
ULONG Size;
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;
}
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 は (Code & 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 は (Code & 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 は (Code & 0x000F) * 4 |
||
EF |
10-FF |
32 |
使用可能 |
||
F0-F4 |
- |
使用可能 |
|||
F5 |
00-FF |
32 |
vpop {dS-dE} S は (Code & 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 は (Code & 0x00FFFF) * 4 |
|
F8 |
00-FF |
00-FF |
00-FF |
16 |
add sp,sp,#X X は (Code & 0x00FFFFFF) * 4 |
F9 |
00-FF |
00-FF |
32 |
add sp,sp,#X X は (Code & 0x00FFFF) * 4 |
|
FA |
00-FF |
00-FF |
00-FF |
32 |
add sp,sp,#X X は (Code & 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 番目のコードの実行から開始する必要があります。
プロローグとエピローグのアンワインド コードは、正確に一致していない場合があります。この場合、アンワインド コード配列にコードのシーケンスがいくつか含まれている必要がある場合があります。コードの処理を開始するオフセットを特定するには、次のロジックを使用します。
関数本体内からのアンワインドの場合、インデックス 0 でアンワインド コードの実行を開始し、終了オペコードに到達するまで続行します。
エピローグ内からのアンワインドの場合、エピローグ スコープにより提供されるエピローグ固有の開始インデックスを使用します。エピローグの開始からの PC のバイト数を計算します。既に実行済みの命令がすべて含まれるまでアンワインド コードを前方にスキップします。その位置で開始するアンワインド シーケンスを実行します。
プロローグ内からのアンワインドの場合、アンワインド コードのインデックス 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 レコードも) が必要です。
シュリンク ラップ
より複雑な特殊な場合の関数フラグメントがシュリンク ラップです。これは、レジスタの保存を関数の開始から関数の後半に遅延させて、レジスタの保存を必要としない、単純な場合に最適化する手法です。これは、スタック領域を割り当てるが最小セットのレジスタを保存する外部の領域、および追加のレジスタの保存と復元を行う内部の領域として記述できます。
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:
シュリンク ラップされた関数は、一般的には通常のプロローグ内に追加のレジスタ保存用領域を事前に割り当てて、次に push ではなく str または stm を使用してレジスタ保存を実行することが想定されています。これにより、すべてのスタック ポインター操作が関数の元のプロローグ内で維持されます。
シュリンク ラップされた関数は、3 つの領域に分割されている必要があります。これらは、コメントで A、B、および 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 のプロローグのみがアンワインドされます。内部のプッシュの後にアンワインドが発生した場合、これは、プロローグがなく、内部のプッシュと領域 A の元のプロローグの両方を記述したアンワインド コードを持つ領域 B の一部と見なされます。同様のロジックが内部のポップにも適用されます。
エンコーディングの最適化
アンワインド コードの多様性、およびコンパクト形式および拡張形式のデータを利用する能力により、エンコーディングを最適化して領域を削減する多くの機会が存在します。これらの手法を積極的に使用することで、アンワインド コードの使用による関数およびフラグメントの記述の最終的なオーバーヘッドを最小化できます。
アンワインド目的のプロローグおよびエピローグの境界を、コンパイラの観点からのプロローグおよびエピローグの境界と混同しないように注意することが、最適化において最も重要です。アンワインド境界は、縮小して緊密にし、効率を改善できます。たとえば、プロローグにはスタックのセットアップ後に追加の検証チェックを実行するためのコードを含めることができます。ただし、すべてのスタック操作が完了した後は、以降の演算をエンコードする必要はなく、以降のすべてのものをプロローグのアンワインドから除去できます。
同じルールが関数長にも適用されます。データ (リテラル プールなど) が関数内のエピローグの後に存在する場合、関数長の一部として含めることは適切ではありません。関数を短縮し、その関数の部分であるコードのみにすることにより、エピローグが末尾に置かれ、かつコンパクトになる可能性がかなり高くなります。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 address - 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 address - 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 address - 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 バイトのスタック調整を示します。
参照
関連項目
Visual C++ の ARM への移行に関する一般的な問題