x86 アーキテクチャ
Intel x86 プロセッサは、複雑な命令セット コンピューター (CISC) アーキテクチャを使用します。つまり、大量の汎用レジスタではなく、少数の特殊な用途のレジスタが存在します。 さらに、複雑で特殊な目的の命令が優先されることも意味します。
x86 プロセッサは、少なくとも 8 ビット Intel 8080 プロセッサまで、その遺産をトレースします。 x86 命令セットの多くの特殊性は、そのプロセッサ(およびその Zilog Z-80 バリアント) との下位互換性によるものです。
Microsoft Win32 は、x86 プロセッサを 32 ビット フラット モードで使用します。 このドキュメントでは、フラット モードのみに焦点を当てます。
レジスタ
x86 アーキテクチャは、次の特権のない整数レジスタで構成されています。
eax |
アキュムレータ |
ebx |
基底レジスタ |
ecx |
カウンター レジスタ |
edx |
データ レジスタ - I/O ポート アクセスと算術関数に使用できます |
esi |
ソース インデックス レジスタ |
edi |
宛先インデックス レジスタ |
ebp |
基本ポインター レジスタ |
esp |
スタック ポインター |
すべての整数レジスタは 32 ビットです。 ただし、その多くは 16 ビットまたは 8 ビットのサブレジスタを持っています。
ax |
eax の下位 16 ビット |
bx |
ebx の下位 16 ビット |
cx |
ecx の下位 16 ビット |
dx |
edx の下位 16 ビット |
si |
esi の下位 16 ビット |
di |
edi の下位 16 ビット |
bp |
ebp の下位 16 ビット |
sp |
esp の下位 16 ビット |
al |
eax の下位 8 ビット |
ah |
ax の上位 8 ビット |
bl |
ebx の下位 8 ビット |
bh |
bx の上位 8 ビット |
cl |
ecx の下位 8 ビット |
ch |
cx の上位 8 ビット |
dl |
edx の下位 8 ビット |
dh |
dx の上位 8 ビット |
サブレジスタで操作すると、サブレジスタのみが影響を受け、サブレジスタ外の部分は影響を受けません。 たとえば、ax レジスタに格納すると、eax レジスタの上位 16 ビットは変更されません。
? (式の評価) コマンドを使用する場合、レジスタの先頭に "at" 記号 (@) を付ける必要があります。 たとえば、? ax ではなく ? @ax を使用する必要があります。 これにより、デバッガーが記号ではなくレジスタとして ax を認識するようになります。
ただし、r (レジスタ) コマンドでは (@) は必要ありません。 たとえば、r ax=5 は常に正しく解釈されます。
プロセッサの現在の状態にとっては、他の 2 つのレジスタが重要です。
eip |
命令ポインター |
flags |
flags |
命令ポインターは、実行中の命令のアドレスです。
フラグ レジスタは、単一ビット フラグのコレクションです。 命令の結果を記述するため、多くの命令によってフラグが変更されます。 これらのフラグは、条件付きジャンプ命令によってテストできます。 詳しくは、「x86 フラグ」をご覧ください。
呼び出し規則
x86 アーキテクチャには、いくつかの異なる呼び出し規則があります。 幸い、それらはすべて同じレジスタの保持と関数の戻りルールに従います。
関数は、eax、ecx、edx を除き、すべてのレジスタを保持する必要があります。これは関数呼び出し全体で変更でき、esp は呼び出し規則に従って更新する必要があります。
eax レジスタは、結果が 32 ビット以下の場合に関数の戻り値を受け取ります。 結果が 64 ビットの場合、結果は edx:eax ペアに格納されます。
x86 アーキテクチャで使用される呼び出し規則の一覧を次に示します。
Win32 (__stdcall)
関数パラメーターはスタックで渡されて、右から左にプッシュされ、呼び出し先はスタックをクリーンします。
ネイティブ C++ メソッド呼び出し (thiscall とも呼ばれます)
関数パラメーターがスタックで渡されて、右から左にプッシュされ、"this" ポインターが ecx レジスタに渡されて、呼び出し先がスタックをクリーンします。
COM (C++ メソッド呼び出しの __stdcall)
関数パラメーターがスタックで渡され、右から左にプッシュされた後、"this" ポインターがスタックにプッシュされ、関数が呼び出されます。 呼び出し先がスタックをクリーンします。
__fastcall
最初の 2 つの DWORD または小さい方の引数が、ecx レジスタと edx レジスタで渡されます。 残りのパラメーターはスタックで渡され、右から左にプッシュされます。 呼び出し先がスタックをクリーンします。
__cdecl
関数パラメーターはスタックで渡されて、右から左にプッシュされ、呼び出し元はスタックをクリーンします。 __cdecl 呼び出し規則は、可変長パラメーターを持つすべての関数に使用されます。
レジスタとフラグのデバッガー表示
デバッガー レジスタの表示例を次に示します。
eax=00000000 ebx=008b6f00 ecx=01010101 edx=ffffffff esi=00000000 edi=00465000
eip=77f9d022 esp=05cffc48 ebp=05cffc54 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000286
ユーザー モード デバッグでは、iopl とデバッガーの最後の行全体を無視することができます。
x86 フラグ
前の例では、2 行目の末尾にある 2 文字のコードは flags です。 これらはシングル ビット レジスタであり、さまざまな用途があります。
次の表に、x86 フラグを示します。
フラグ コード | フラグ名 | Value | フラグの状態 | 説明 |
---|---|---|---|---|
of | オーバーフロー フラグ | 0 1 | nvov | オーバーフローなし - オーバーフロー |
df | 方向フラグ | 0 1 | updn | 上方向 - 下方向 |
if | 割り込みフラグ | 0 1 | diei | 割り込みが無効 - 割り込みが有効 |
sf | 符号フラグ | 0 1 | plng | 正 (またはゼロ) - 負 |
zf | ゼロ フラグ | 0 1 | nzzr | ゼロ以外 - ゼロ |
af | 補助キャリー フラグ | 0 1 | naac | 補助キャリーなし - 補助キャリー |
/pf | パリティ フラグ | 0 1 | pepo | パリティ奇数 - パリティ偶数 |
cf | キャリー フラグ | 0 1 | nccy | キャリーなし - キャリー |
tf | トラップ フラグ | tf が 1 の場合、プロセッサは 1 つの命令の実行後に STATUS_SINGLE_STEP 例外を発生させます。 このフラグは、シングル ステップ トレースを実装するためにデバッガーによって使用されます。 他のアプリケーションで使用することができません。 | ||
iopl | I/O 特権レベル | I/O 特権レベル 0 ~ 3 の値を持つ 2 ビット整数です。 ハードウェアへのアクセスを制御するためにオペレーティング システムによって使用されます。 アプリケーションで使用することができません。 |
デバッガー コマンド ウィンドウで何らかのコマンドの結果としてレジスタが表示される場合、表示されるフラグの状態です。 ただし、r (レジスタ) コマンドを使用してフラグを変更する場合、フラグ コードにより参照する必要があります。
WinDbg のレジスタ ウィンドウでは、フラグ コードを使用してフラグを表示または変更します。 フラグ ステータスはサポートされていません。
次に例を示します。 前述のレジスタ表示では、フラグ ステータス ng が表示されます。 これは、符号フラグが現在 1 に設定されていることを意味します。 これを変更するには、次のコマンドを使用します。
r sf=0
これにより、符号フラグがゼロに設定されます。 別のレジスタ表示を行った場合、ng ステータス コードは表示されません。 代わりに、pl ステータス コードが表示されます。
符号フラグ、ゼロ フラグ、キャリー フラグは、最も一般的に使用されるフラグです。
条件
条件は、1 つ以上のフラグの状態を表します。 x86 に対するすべての条件付き操作は、条件の観点から表されます。
アセンブラーは、1 文字または 2 文字の省略形を使用して条件を表します。 条件は、複数の省略形で表すことができます。 たとえば、AE ("above or equal") は NB ("not below") と同じ条件です。 次の表に、一般的な条件とその意味を示します。
条件名 | フラグ | 意味 |
---|---|---|
Z |
ZF=1 |
直近の操作の結果がゼロでした。 |
NZ |
ZF=0 |
直近の操作の結果がゼロ以外でした。 |
C |
CF=1 |
直近の操作に、キャリーまたはボローが必要です。 (符号なし整数の場合、オーバーフローを示します)。 |
NC |
CF=0 |
直近の操作では、キャリーまたはボローが必要ありませんでした。 (符号なし整数の場合、オーバーフローを示します)。 |
S |
SF=1 |
直近の操作の結果に、上位ビットが設定されています。 |
NS |
SF=0 |
直近の操作の結果で、上位ビットがクリアされています。 |
O |
OF=1 |
符号付き整数演算として扱われるとき、直近の操作によってオーバーフローまたはアンダーフローが発生しました。 |
使用不可 |
OF=0 |
符号付き整数演算として扱われるとき、直近の操作によってオーバーフローまたはアンダーフローが発生しませんでした。 |
条件を使用して、2 つの値を比較することもできます。 cmp 命令は、その 2 つのオペランドを比較し、一方のオペランドを他方から減算したかのようにフラグを設定します。 次の条件を使用して、 cmp value1、 value2の結果を確認できます。
条件名 | フラグ | CMP 操作後の意味。 |
---|---|---|
E |
ZF=1 |
value1 == value2。 |
NE |
ZF=0 |
value1 != value2。 |
GE NL | SF=OF |
value1>= value2。 値は符号付き整数として扱われます。 |
LE NG | ZF=1 または SF!=OF |
value1<= value2。 値は符号付き整数として扱われます。 |
G NLE | ZF=0 および SF=OF |
value1>value2。 値は符号付き整数として扱われます。 |
L NGE | SF!=OF |
value1<value2。 値は符号付き整数として扱われます。 |
AE NB | CF=0 |
value1>= value2。 値は符号なし整数として扱われます。 |
BE NA | CF=1 または ZF=1 |
value1<= value2。 値は符号なし整数として扱われます。 |
NBE | CF=0 および ZF=0 |
value1>value2。 値は符号なし整数として扱われます。 |
B NAE | CF=1 |
value1<value2。 値は符号なし整数として扱われます。 |
通常、条件は cmp 命令または test 命令の結果に作用するために使用されます。 たとえば、 にします。
cmp eax, 5
jz equal
式 (eax - 5) を計算し、結果に応じてフラグを設定することにより、eax レジスタを数値 5 と比較します。 減算の結果が 0 の場合、zr フラグが設定され、jz 条件が true になるため、ジャンプが実行されます。
データ型
バイト 8 ビット
word: 16 ビット
dword: 32 ビット
qword: 64 ビット (浮動小数点、倍精度を含む)
tword: 80 ビット (浮動小数点、拡張倍精度を含む)
oword: 128 ビット
表記
次の表は、アセンブリ言語命令の記述に使用される表記を示しています。
表記 | 説明 |
---|---|
r, r1, r2... |
Registers |
m |
メモリ アドレス (詳しくは、後続のアドレス指定モードに関するセクションをご覧ください)。 |
#n |
即時定数 |
r/m |
レジスタまたはメモリ |
r/#n |
レジスタ定数または即時定数 |
r/m/#n |
レジスタ、メモリ、または即時定数 |
cc |
前の「条件」セクションに記載されている条件コード。 |
T |
"B"、"W"、または "D" (byte、word、または dword) |
accT |
T アキュムレータのサイズ設定: al if T = "B"、ax if T = "W"、または eax if T = "D" |
アドレス指定モード
いくつかの異なるアドレス指定モードがありますが、それらはすべて T ptr [expr] という形式になります。ここで、T は何らかのデータ型 (前のデータ型セクションを参照) で、expr は定数とレジスタを含む式です。
ほとんどのモードの表記は、推測がそれほど難しくありません。 たとえば、BYTE PTR [esi+edx*8+3] は、"esi レジスタの値を取得して、edx レジスタの値の 8 倍に加算し、3 を加算してから、結果のアドレスのバイトにアクセスする" を意味します。
パイプライン処理
Pentium はデュアル イシューです。つまり、1 クロック ティックで最大 2 つのアクションを実行することができます。 ただし、一度に 2 つのアクション (ペアリングと呼ばれます) を実行できる場合のルールは非常に複雑です。
x86 は CISC プロセッサであるため、ジャンプ遅延スロットについて心配する必要はありません。
同期メモリ アクセス
命令を読み込み、変更、格納すると、次のように命令を変更する lock プレフィックスを受け取ることがあります。
命令を発行する前に、CPU は保留中のすべてのメモリ操作をフラッシュし、一貫性を確保します。 すべてのデータ プリフェッチは破棄されます。
命令の発行中、CPU はバスへの排他的アクセス権を持つことになります。 これにより、読み込み/変更/格納操作のアトミック性が確保されます。
xchg 命令は、値をメモリと交換するたびに、自動的に前の規則に従います。
他のすべての命令は、既定では非ロックです。
ジャンプ予測
無条件ジャンプは、実行されると予測されます。
条件付きジャンプは、最後に実行された時刻に取得されたかどうかに応じて、取得されるかどうかが予測されます。 ジャンプ履歴を記録するためのキャッシュは、サイズが限られています。
前回の実行時に条件付きジャンプが取得されたかどうかの記録が CPU にない場合、後方の条件付きジャンプが取得されると予測され、前の条件付きジャンプは取得されないと予測されます。
Alignment
x86 プロセッサは、パフォーマンスが低下した状態で、整列されていないメモリ アクセスを自動的に修正します。 例外が発生しません。
アドレスがオブジェクト サイズの整数倍数である場合、メモリ アクセスは整列されていると見なされます。 たとえば、すべての BYTE アクセスが整列され (すべてが 1 の整数倍数)、偶数アドレスへの WORD アクセスが整列されている場合に、DWORD アドレスが整列されるには 4 の倍数でなければなりません。
lock プレフィックスは、整列されていないメモリ アクセスには使用しないでください。