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 |
ベース ポインター レジスタ |
特に |
スタック ポインター |
すべての整数レジスタは 32 ビットです。 ただし、その多くは 16 ビットまたは 8 ビットのサブレジスタを持っています。
ax |
下位 16 ビットの eax |
bx |
ebx の下位 16 ビット |
cx |
ecx の下位 16 ビット |
Dx |
edx の下位 16 ビット |
si |
esi の下位 16 ビット |
di |
下位 16 ビットの edi |
Bp |
ebp の下位 16 ビット |
sp |
esp の下位 16 ビット |
al |
下位 8 ビットの eax |
ああ |
高 8 ビットの ax |
bl |
ebx の下位 8 ビット |
Bh |
bx の上位 8 ビット |
Cl |
ecx の下位 8 ビット |
ch |
高 8 ビットの cx |
Dl |
edx の下位 8 ビット |
dh |
上位 8 ビットの dx |
サブ登録で操作すると、サブ登録だけが影響を受け、サブ登録の外部の部分には影響しません。 たとえば、 ax レジスタに格納すると、 eax レジスタの上位 16 ビットは変更されません。
を使用する場合 (式の評価) コマンドでは、レジスタの先頭に "at" 記号 ( @ ) を付ける必要があります。 たとえば、? ax ではなく ? を@ax使用する必要があります。 これにより、デバッガーは ax をシンボルではなくレジスタとして認識します。
ただし、 r (Registers ) コマンドでは (@) は必要ありません。 たとえば、 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 文字のコードは フラグです。 これらはシングルビット レジスタであり、さまざまな用途があります。
次の表に、x86 フラグを示します。
フラグ コード | フラグ名 | 値 | フラグの状態 | 説明 |
---|---|---|---|---|
の | オーバーフロー フラグ | 0 1 | nvov | オーバーフローなし - オーバーフロー |
df | 方向フラグ | 0 1 | updn | 上方向 - 下方向 |
if | 割り込みフラグ | 0 1 | diei | 割り込みを無効にする - 割り込みを有効にする |
Sf | 署名フラグ | 0 1 | plng | 正 (またはゼロ) - 負 |
Zf | ゼロ フラグ | 0 1 | nzzr | 0 以外 - 0 |
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 (Registers) コマンドを使用してフラグを変更する場合は、 フラグ コードで参照する必要があります。
WinDbg の [レジスタ] ウィンドウでは、フラグ コードを使用してフラグを表示または変更します。 フラグの状態はサポートされていません。
次に例を示します。 上記のレジスタ表示では、フラグステータス ng が表示されます。 これは、署名フラグが現在 1 に設定されていることを意味します。 これを変更するには、次のコマンドを使用します。
r sf=0
これにより、符号フラグが 0 に設定されます。 別のレジスタ表示を行うと、 ng ステータス コードは表示されません。 代わりに、 pl 状態コードが表示されます。
署名フラグ、ゼロ フラグ、およびキャリー フラグは、最も一般的に使用されるフラグです。
条件
条件は、1 つ以上のフラグの状態を表します。 x86 に対するすべての条件付き操作は、条件の観点から表されます。
アセンブラーは、1 文字または 2 文字の省略形を使用して条件を表します。 条件は、複数の省略形で表すことができます。 たとえば、AE ("above or equal") は NB ("not below") と同じ条件です。 次の表に、いくつかの一般的な条件とその意味を示します。
条件名 | Flags | 意味 |
---|---|---|
Z |
ZF=1 |
最後の操作の結果は 0 でした。 |
NZ |
ZF=0 |
最後の操作の結果が 0 ではありません。 |
C |
CF=1 |
最後の操作には、持ち込みまたは借用が必要でした。 (符号なし整数の場合、オーバーフローを示します)。 |
NC |
CF=0 |
最後の操作では、持ち込みまたは借用は必要ありませんでした。 (符号なし整数の場合、オーバーフローを示します)。 |
S |
SF=1 |
最後の操作の結果には、上位ビットが設定されています。 |
NS |
SF=0 |
最後の操作の結果は、高ビットクリアになります。 |
O |
OF=1 |
符号付き整数演算として扱われると、最後の操作でオーバーフローまたはアンダーフローが発生しました。 |
NO |
OF=0 |
符号付き整数演算として扱われた場合、最後の操作ではオーバーフローやアンダーフローは発生しませんでした。 |
条件を使用して、2 つの値を比較することもできます。 cmp 命令は 2 つのオペランドを比較し、一方のオペランドを他方から減算したかのようにフラグを設定します。 次の条件を使用して、 cmpvalue1、 value2 の結果を確認できます。
条件名 | Flags | 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。 値は符号なし整数として扱われます。 |
A NBE | CF=0 および ZF=0 |
value1>value2。 値は符号なし整数として扱われます。 |
B NAE | CF=1 |
value1<value2。 値は符号なし整数として扱われます。 |
条件は、通常、 cmp または テスト 命令の結果に対して動作するために使用されます。 たとえば、オブジェクトに適用された
cmp eax, 5
jz equal
は、式 ( eax - 5) を計算し、結果に従ってフラグを設定することで、eax レジスタを数値 5 と比較します。 減算の結果が 0 の場合、 zr フラグが設定され、 jj 条件が true になるため、ジャンプが取得されます。
データ型
byte: 8 ビット
word: 16 ビット
dword: 32 ビット
qword: 64 ビット (浮動小数点倍精度浮動小数点を含む)
tword: 80 ビット (浮動小数点拡張倍精度浮動小数点を含む)
oword: 128 ビット
表記
次の表は、アセンブリ言語命令の記述に使用される表記を示しています。
Notation | 意味 |
---|---|
r、 r1、 r2... |
レジスタ |
m |
メモリ アドレス (詳細については、「後続のアドレス指定モード」セクションを参照してください)。 |
#n |
イミディエイト定数 |
r/m |
レジスタまたはメモリ |
r/#n |
レジスタまたは即時定数 |
r/m/#n |
レジスタ、メモリ、または即時定数 |
Cc |
前の「条件」セクションに記載されている条件コード。 |
T |
"B"、"W"、または "D" (バイト、ワード、または 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 は 2 つの問題です。つまり、1 クロック ティックで最大 2 つのアクションを実行できます。 ただし、一度に 2 つのアクション ( ペアリングと呼ばれます) を実行できる場合のルールは非常に複雑です。
x86 は CISC プロセッサであるため、ジャンプ遅延スロットについて心配する必要はありません。
同期されたメモリ アクセス
読み込み、変更、および格納命令は 、次 のように命令を変更するロック プレフィックスを受け取ることができます。
命令を発行する前に、CPU は一貫性を確保するために、保留中のすべてのメモリ操作をフラッシュします。 すべてのデータ プリフェッチは破棄されます。
命令の発行中に、CPU はバスへの排他的アクセス権を持つことになります。 これにより、読み込み/変更/ストア操作のアトミック性が確保されます。
xchg 命令は、値をメモリと交換するたびに、前の規則に自動的に従います。
その他のすべての命令は、既定で非ロックです。
ジャンプ予測
無条件ジャンプは取られると予測されます。
条件付きジャンプは、最後に実行された日時に取得されたかどうかに応じて、取得されるかどうかが予測されます。 ジャンプ履歴を記録するためのキャッシュのサイズは制限されています。
CPU に、前回の実行時に条件付きジャンプが取得されたかどうかの記録がない場合は、後方の条件付きジャンプが取得済みとして予測され、前の条件付きジャンプは取得されなかったと予測されます。
配置
x86 プロセッサは、パフォーマンスが低下した状態で、アライメントされていないメモリ アクセスを自動的に修正します。 例外は発生しません。
アドレスがオブジェクト サイズの倍数の整数である場合、メモリ アクセスはアラインされたと見なされます。 たとえば、すべての BYTE アクセスがアラインされます (すべてが 1 の整数の倍数)、偶数アドレスへの WORD アクセスがアラインされ、DWORD アドレスがアラインされるには 4 の倍数である必要があります。
ロック プレフィックスは、アライメントされていないメモリ アクセスには使用しないでください。