x86 命令セットは非常に複雑であり (とにかく CISC です)、ここで多くの人がそれを理解しようとする試みを思いとどまらせているのを見てきましたが、私は逆に言います: それはまだ理解可能であり、途中で学ぶことができます。なぜそんなに複雑なのか、Intel が 8086 から最新のプロセッサに至るまで何度もそれを拡張した方法を説明します。
x86 命令は可変長エンコーディングを使用するため、複数のバイトで構成できます。各バイトはさまざまなものをエンコードするためにあり、そのうちのいくつかはオプションです (オプションのフィールドが使用されているかどうかにかかわらず、オペコードでエンコードされています)。
たとえば、各オペコードの前に 0 ~ 4 バイトのプレフィックス バイトを付けることができますが、これはオプションです。通常、それらについて心配する必要はありません。これらは、オペランドのサイズを変更するため、または最新の CPU (MMX、SSE など) の拡張命令を使用してオペコード テーブルの「2 階」へのエスケープ コードとして使用されます。
次に、実際のオペコードがあります。通常は 1 バイトですが、拡張命令の場合は最大 3 バイトになる場合があります。基本的な命令セットのみを使用する場合は、それらについても心配する必要はありません。
次に、アドレッシング モードとオペランド タイプをエンコードする、いわゆるModR/M
バイト ( と呼ばれることもあります) があります。これは、そのようなオペランドを持つmode-reg-reg/mem
オペコードによってのみ使用されます。3 つのビット フィールドがあります。
- 最初の 2 ビット (左から最上位ビット) は、アドレッシング モード (4 つの可能なビットの組み合わせ) をエンコードします。
- 次の 3 ビットは、最初のレジスタをエンコードします (8 つの可能なビットの組み合わせ)。
- 最後の 3 ビットは、最初の 2 ビットの設定に応じて、別のレジスタをエンコードしたり、アドレッシング モードを拡張したりできます。
バイトの後に、ModR/M
(アドレス指定モードに応じて) 別のオプションのバイト ( calendex ase ) が存在する可能性SIB
がS
ありますI
。B
これは、スケーリング ファクタ (1x、2x、4x)、ベース アドレス/レジスタ、および使用されるインデックス レジスタをエンコードするために、よりエキゾチックなアドレッシング モードで使用されます。バイトと同様のレイアウトですがModR/M
、名前が示すように、左から最初の 2 ビット (最上位) はスケールをエンコードするために使用され、次の 3 ビットと最後の 3 ビットはインデックスとベース レジスタをエンコードします。
ディスプレイスメントが使用されている場合は、その直後に移動します。アドレッシング モードと実行モード (16 ビット/32 ビット/64 ビット) に応じて、0、1、2、または 4 バイトの長さになります。
最後のデータは、存在する場合は常に即時データです。また、0、1、2、または 4 バイト長にすることもできます。
したがって、x86 命令の全体的な形式がわかったら、これらすべてのバイトのエンコーディングを知る必要があります。そして、一般的な信念に反して、いくつかのパターンがあります。
たとえば、すべてのレジスタ エンコーディングはきちんとしたパターンに従いますACDB
。つまり、8 ビット命令の場合、レジスタ コードの最下位 2 ビットが A、C、D、および B レジスタをエンコードします。
00
=A
レジスタ (アキュムレータ)
01
=C
レジスタ (カウンタ)
10
=D
レジスタ (データ)
11
=B
レジスタ (ベース)
彼らの 8 ビット プロセッサは、次のようにエンコードされた 4 つの 8 ビット レジスタだけを使用していたのではないかと思います。
second
+---+---+
f | 0 | 1 | 00 = A
i +---+---+---+ 01 = C
r | 0 | A : C | 10 = D
s +---+ - + - + 11 = B
t | 1 | D : B |
+---+---+---+
次に、16 ビット プロセッサでは、このレジスタ バンクを 2 倍にし、バンクを選択するためにレジスタ エンコーディングにもう 1 ビット追加しました。
second second 0 00 = AL
+----+----+ +----+----+ 0 01 = CL
f | 0 | 1 | f | 0 | 1 | 0 10 = DL
i +---+----+----+ i +---+----+----+ 0 11 = BL
r | 0 | AL : CL | r | 0 | AH : CH |
s +---+ - -+ - -+ s +---+ - -+ - -+ 1 00 = AH
t | 1 | DL : BL | t | 1 | DH : BH | 1 01 = CH
+---+---+-----+ +---+----+----+ 1 10 = DH
0 = BANK L 1 = BANK H 1 11 = BH
ただし、これらのレジスタの両方の半分を一緒に使用して、完全な 16 ビット レジスタとして使用することも選択できるようになりました。これは、オペコードの最後のビット(最下位ビット、右端のビット) によって行われます。これが の場合0
、これは 8 ビット命令です。ただし、このビットが設定されている場合 (つまり、オペコードが奇数の場合)、これは 16 ビット命令です。このモードでは、ACDB
以前と同様に、2 ビットがレジスタの 1 つをエンコードします。パターンはそのままです。しかし、現在は完全な 16 ビット レジスタをエンコードしています。しかし、3 番目のバイト (最上位バイト) も設定されると、インデックス/ポインター レジスターと呼ばれるまったく別のレジスター バンクに切り替わります。これらはSP
(スタック ポインター)、BP
(ベース ポインター)、SI
(ソース インデックス)、DI
(宛先/データ インデックス)。したがって、アドレス指定は次のようになります。
second second 0 00 = AX
+----+----+ +----+----+ 0 01 = CX
f | 0 | 1 | f | 0 | 1 | 0 10 = DX
i +---+----+----+ i +---+----+----+ 0 11 = BX
r | 0 | AX : CX | r | 0 | SP : BP |
s +---+ - -+ - -+ s +---+ - -+ - -+ 1 00 = SP
t | 1 | DX : BX | t | 1 | SI : DI | 1 01 = BP
+---+----+----+ +---+----+----+ 1 10 = SI
0 = BANK OF 1 = BANK OF 1 11 = DI
GENERAL-PURPOSE POINTER/INDEX
REGISTERS REGISTERS
32 ビット CPU を導入したとき、これらのバンクは再び 2 倍になりました。しかし、パターンは同じままです。奇数オペコードは 32 ビット レジスタを意味し、偶数オペコードは以前と同様に 8 ビット レジスタを意味します。CPUとその現在の動作モードに応じて16/32ビットバージョンが使用されるため、奇妙なオペコードを「ロング」バージョンと呼びます。16 ビット モードで動作する場合、奇数 (「長い」) オペコードは 16 ビット レジスタを意味しますが、32 ビット モードで動作する場合、奇数 (「長い」) オペコードは 32 ビット レジスタを意味します。命令全体に66
プレフィックス (オペランド サイズのオーバーライド) を付けることで、反転させることができます。偶数オペコード (「短い」オペコード) は常に 8 ビットです。したがって、32 ビット CPU では、レジスタ コードは次のようになります。
0 00 = EAX 1 00 = ESP
0 01 = ECX 1 01 = EBP
0 10 = EDX 1 10 = ESI
0 11 = EBX 1 11 = EDI
ご覧のとおり、ACDB
パターンは同じままです。こちらもSP,BP,SI,SI
柄はそのまま。より長いバージョンのレジスタを使用するだけです。
オペコードにもいくつかのパターンがあります。そのうちの 1 つについては既に説明しました (偶数対奇数 = 8 ビットの「短い」対 16/32 ビットの「長い」もの)。それらの多くは、簡単な参照と手動でのアセンブル/逆アセンブルのために一度作成したこのオペコードマップで見ることができます:
(まだ完全なテーブルではなく、いくつかのオペコードが欠落しています. いつか更新するかもしれません.)
ご覧のとおり、算術命令と論理命令はほとんど表の上半分に配置され、左半分と右半分は同様のレイアウトに従います。データ移動命令は下半分にあります。すべての分岐命令 (条件付きジャンプ) は行 にあり7*
ます。また、命令B*
用に予約された完全な行が 1 つありmov
ます。これは、即値 (定数) をレジスタにロードするための省略形です。これらはすべて 1 バイトのオペコードであり、直後に定数が続きます。これは、オペコードの宛先レジスタをエンコードするため (この表の列番号によって選択されます)、最下位 3 バイト (右端のもの) に格納されます。 . これらは、レジスタ エンコーディングと同じパターンに従います。4 番目のビットは、「ショート」/「ロング」のどちらかを選択します。あなたはそれを見ることができますimul
命令はすでにテーブルにあり、正確にその69
位置にあります (huh.. ;J)。
多くの命令では、「ショート/ロング」ビットの直前のビットは、オペランドの順序をエンコードするためのものです。ModR/M
バイトにエンコードされた 2 つのレジスタのどちらがソースで、どちらがデスティネーションであるか (これは命令に適用されます) 2 つのレジスタ オペランドを使用)。
ModR/M
バイトのアドレッシング モード フィールドについては、次のように解釈します。
11
は最も単純で、レジスタからレジスタへの転送をエンコードします。1 つのレジスタは次の 3 ビット (フィールド) によってエンコードされ、もう 1 つのレジスタはこのバイトreg
の残りの 3 ビット (フィールド) によってエンコードされます。R/M
01
このバイトの後に、1 バイトの変位が存在することを意味します。
10
同じ意味ですが、使用される変位は 4 バイトです (32 ビット CPU の場合)。
00
フィールドの内容に応じて、間接アドレス指定または単純な置換を意味しR/M
ます。
バイトが存在する場合は、ビット内のビット パターンSIB
によって通知されます。バイトをまったく使用しない 32 ビット ディスプレイスメントのみのモードのコードもあります。100
R/M
101
SIB
これらすべてのアドレッシング モードの概要を以下に示します。
Mod R/M
11 rrr = register-register (one encoded in `R/M` bits, the other one in `reg` bits).
00 rrr = [ register ] (except SP and BP, which are encoded in `SIB` byte)
00 100 = SIB byte present
00 101 = 32-bit displacement only (no `SIB` byte required)
01 rrr = [ rrr + disp8 ] (8-bit displacement after the `ModR/M` byte)
01 100 = SIB + disp8
10 rrr = [ rrr + disp32 ] (except SP, which means that the `SIB` byte is used)
10 100 = SIB + disp32
それでは、あなたのをデコードしましょうimul
:
69
はそのオペコードです。imul
これは、8 ビット オペランドを符号拡張しない のバージョンをエンコードします。バージョンはそれら6B
を符号拡張します。(誰かが尋ねた場合、それらはオペコードのビット 1 によって異なります。)
62
RegR/M
バイトです。バイナリでは0110 0010
または01 100 010
です。最初の 2 バイト (Mod
フィールド) は間接アドレッシング モードを意味し、ディスプレースメントは 8 ビットになります。次の 3 ビット (reg
フィールド) は、宛先レジスタとしてレジスタ (この場合は 32 ビット モードであるため)100
をエンコードします。最後の 3 ビットはフィールドであり、使用される他の (ソース) レジスタとしてレジスタ (この場合は)をエンコードします。SP
ESP
R/M
010
D
EDX
ここで、8 ビットの変位が予想されます。2f
変位、正の変位 (10 進数で +47) です。
最後の部分は、imul
命令に必要な即値定数の 4 バイトです。あなたの場合、これは6c 64 2d 6c
リトルエンディアンでは$6c2d646c
.
そして、それがクッキーが崩れる方法です;-J