しかくいさんかく

解答略のメモ

13日目: [x86] ModRM

この記事はひとりでCPUとエミュレータとコンパイラを作る Advent Calendar 2017の13日目の記事です。

昨日は数値についてまとめた。

今日はModRMを説明する。

ModRMを一言で表すと、必須オプションだ。 たとえばadd命令の場合、何と何を足すかを指定するのがModRMの役目と言える。

add命令

具体的に説明するため、考える対象をadd命令に絞る。 一昨日のx86の命令セット表 の最上段左側のadd命令を切り出すと

f:id:kaitou_ryaku:20171212024535p:plain:w300

機械語0x01ニーモニックadd [M+imm], Rに対応し、0x03add R, [M+imm]に対応する。 ここで[hoge]はメモリのhoge番地を表している。 つまりadd [M+imm], Rを数式的に書くと(M+imm)番地のメモリ = (M+imm)番地のメモリ + Rになり、メモリの値を変更する命令になっている。

一方add R, [M+imm]ではメモリの値は変更されない。

これらを表に記すと

機械語 ニーモニック
0x01 ???? ... add, [address], register
0x03 ???? ... add, register, [address]

????の部分をうまく調整することで何と何を足すか指定できる。 この指定子がModRMだ。

[M+imm], R 型

add [M+imm], R命令(機械語0x01)を知るための画像を用意した。これをじーっくり眺めて欲しい。

f:id:kaitou_ryaku:20171212024547p:plain

右列は機械語を1byteずつ区切ったものだ。 最初に0x01、次に1byteのModRMが来て、最後に即値(無い場合もある)が来る。

ModRMについては、中央のカラフルな列を見れば分かるように、最初の2bitがmod、次の3bitがR、最後の3bitがMと命名されている。

左列の下四段のニーモニックを見ると、[M+imm]の形式が異なっている。これらの形式はmodで指定される。

mod [M+imm]の型
00 [レジスタ]
01 [レジスタ+imm8]
10 [レジスタ+imm32]
11 レジスタ

RとMについては、以下のようにレジスタと対応している。

R or Mの2進表示 レジスタ
000 eax
001 ecx
010 edx
011 ebx
100 esp
101 ebp
110 esi
111 edi

なんだかややこしいが、そういう仕様なので仕方ない。

R, [M+imm] 型

機械語0x03、つまりadd R, [M+imm]の命令を見てみる。

f:id:kaitou_ryaku:20171212024554p:plain

さっきの[M+imm], Rと比較すると、R[M+imm]の位置が入れ替わっただけだ。 ModRMの役割は全く同じなので、説明は略す。

なおadd以外の命令(subcmpmovなど)についても、命令セット表に[M+imm], RR, [M+imm]が出てきたら、ModRMは今説明したadd命令とおなじ型になる。

ModRMの精神について述べると、modは挙動の種類を指定し、Rはレジスタを指定し、Mはメモリを指定する。 この雰囲気を掴むと納得しやすい。

Rで計算種別を指定する型

今までに解説した2タイプのModRMが分かれば、ほぼ困らない。 しかし他の型のModRMもいるので、軽く説明しておく。

命令セット表の8段目の左側0x81, 0x83の命令は、calcになっている。

f:id:kaitou_ryaku:20171212024602p:plain:w300

これらはadd ecx, 即値sub edx, 即値を計算する命令だ。

この場合は、ModRMのRで計算の種類を指定し、Mでレジスタを指定する。

2進表示 オペコード(R) レジスタ(M)
000 add eax
001 or ecx
010 adc edx
011 sbb ebx
100 and esp
101 sub ebp
110 xor esi
111 cmp edi

なお、modは11に固定しておく1。 最後に、calc eax, imm32型の命令については、機械語0x81ではなく機械語0x05等も使用できる。 つまりeaxだけ例外扱いされているのだ2。こういう例外は辛い。

即値を2つ取る型

mov [M+imm], imm32_next

というタイプのニーモニックは、アセンブルすると

0xc7 ModRM imm imm32_next

となる。この場合のModRMは:

ModRM 説明
mod [M+imm]の形式をadd命令の場合と同様に指定
R 000で固定。ということにしてくれ
M レジスタの種類をadd命令の場合と同様に指定

どうでもいい話

今日のModRMの解説で、 x86の命令セット表 の大半は理解できるようになったと思う。

残りはcall, leave, retだけだ。これらは関数呼び出しのための命令で、少しややこしいので明日説明する。

今まで見てきたように、x86は各命令のサイズがバラバラだ。 hltのように1byteの命令もあれば、add [eax+0x12345678], 0x1234のような10byte命令もある。 これをちゃんと読み取るCPUをFPGAで書くのは、結構難しい。 3日前に作ったCPUは、異サイズの命令読み取りの部分でずるいことをして、問題を回避していた。

MIPSならば、この辺の面倒な問題が起きない3のでCPUを作りやすい。 逆に言うとx86がしんどいのだ。x86の辛さを表すエピソードを紹介すると

辛い。


  1. さっきMはメモリを指定すると書いたので、ここでのMも[M+imm]という形のニーモニックになりそうだ。しかしx86命令セットの拡張に次ぐ拡張の結果、安直な期待はしばしば裏切られる。チェックが必要だ。

  2. 90から97までのxchg命令でも、eaxだけ特例扱いされている。

  3. 多分