Warning
この記事は2019年に書かれたもので、情報として残しておくためにObsidianに移したものです。現在は64bitのOSが主流なので、ここに書かれている内容は時代遅れかもしれません。
x86の汎用命令はオペコードに続き、場合によってはオペランドを決定するModR/Mバイトが付加されます。ModR/Mバイトはmod , reg/opcode , r/m フィールドから構成され、
modフィールドはr/mのアドレス指定モードを決定reg/opcodeフィールドはオペランドのうち、レジスタ側を決定r/mフィールドはオペランドのうち、レジスタ/メモリ側を決定
します。また、mod , r/m フィールドの指定によっては追加のSIBバイトとディスプレースメントバイト(1または4バイト)がつくことがあります。
図示すると、おおよそ以下の様になります。
mod reg r/m scale index base disp
+----+-----+-----+ +----+-----+-----+ +-------
| XX | XXX | XXX | | XX | XXX | XXX | | ...
+----+-----+-----+ +----+-----+-----+ +-------
reg/opcode, r/m, base, indexフィールドのレジスタ指定
reg/opcode , r/m , base , index フィールドのビットに対するレジスタは次の様になります。
reg r/m base index
+-----+--------+--------+ +-------+--------+
| 000 | %eax | %eax | | %eax | %eax |
| 001 | %ecx | %ecx | | %ecx | %ecx |
| 010 | %edx | %edx | | %edx | %edx |
| 011 | %ebx | %ebx | | %ebx | %ebx |
| 100 | %esp | %esp*2 | | %esp | %eiz*4 |
| 101 | %ebp*1 | %ebp | | *3 | %ebp |
| 110 | %esi | %esi | | %esi | %esi |
| 111 | %edi | %edi | | %edi | %edi |
+-----+--------+--------+ +-------+--------+
mod = 0b00のときは、%ebpレジスタの代わりにdisp32(ディスプレースメントだけ)になります。mod = 0b11以外のとき、%espレジスタ/メモリの代わりにSIBバイトで指定したレジスタ/メモリになります。%ebpレジスタの代わりに指定なしになり、ベースが0として扱われます。%espレジスタの代わりにゼロレジスタである%eizになり、インデックスが0として扱われます。
指定モード
レジスタ-レジスタ指定 (mod = 0b11)
mod = 0b11 のときはソースもデスティネーションもレジスタになります。r/m = 0b101 のときはSIBバイトが付加されますが、 mod = 0b11 はそれに優先します。
mod reg r/m
+----+-----+-----+
| 11 | 000 | 000 | -> %eax, %eax
| 11 | 000 | 100 | -> %eax, %esp
+----+-----+-----+
レジスタ-メモリ指定 (mod = 0b00, 0b01, 0b10 | r/m != 0b100)
このときは、 r/m または base フィールドで指定したレジスタにディスプレースメントを加えた実効アドレス指定になります。
mod = 0b00のときは、ディスプレースメントなし- ただし、
r/m = 0b101のときは、32bitディスプレースメントのみを指定
- ただし、
mod = 0b01のときは、8bitディスプレースメントmod = 0b10のときは、32bitディスプレースメント
mod reg r/m
+----+-----+-----+
| 00 | 000 | 000 | -> %eax, (%eax)
| 01 | 000 | 000 | +(1B) -> %eax, disp8(%eax)
| 10 | 000 | 000 | +(4B) -> %eax, disp32(%eax)
| 00 | 000 | 101 | +(4B) -> %eax, disp32
| 01 | 000 | 101 | +(1B) -> %eax, disp8(%ebp)
| 10 | 000 | 101 | +(4B) -> %eax, disp32(%ebp)
+----+-----+-----+
レジスタ-メモリ指定2 (mod = 0b00, 0b01, 0b10 | r/m = 0b100)
r/m = 0b100 のときは、 %esp レジスタの代わりにSIBバイトで base + index * scale のアドレス指定になります。スケールの値はscale フィールドで指定されている値に対して1 << scale で求められます。
mod reg r/m scale index base
+----+-----+-----+ +----+-----+-----+
| 00 | 000 | 100 | | 00 | 000 | 000 | -> %eax, (%eax, %eax, 1)
| 00 | 000 | 100 | | 01 | 000 | 100 | -> %eax, (%esp, %eax, 2)
| 00 | 000 | 100 | | 10 | 000 | 101 | +(4B) -> %eax, disp32(, %eax, 4)
| 00 | 000 | 100 | | 00 | 100 | 101 | +(4B) -> %eax, disp32(, %eiz, 1)
| 01 | 000 | 100 | | 11 | 000 | 101 | +(1B) -> %eax, disp8(%ebp, %eax, 8)
| 00 | 000 | 100 | | 00 | 100 | 000 | -> %eax, (%eax, %eiz, 1)
| 00 | 000 | 100 | | 00 | 101 | 000 | -> %eax, (%eax, %ebp, 1)
| 01 | 000 | 100 | | 00 | 101 | 101 | +(1B) -> %eax, disp8(%ebp, %ebp, 1)
+----+-----+-----+ +----+-----+-----+
- オペランド
%eax, (%esp)の様に%espがアドレス指定だとSIBバイトでの指定が必須になります0b00000100, 0b00100100=%eax, (%esp, %eiz, 1)=%eax, (%esp)
r/m = 0b101と同じ扱いがbaseフィールドにも適用されますbase = 0b101のとき、ベースはなし(0扱い)になり32bitディスプレースメントを指定します
- SIBバイトあり・なしで動作が変わらないオペコードを生成することもできます
0b00000001=%eax, (%ecx)0b00000100, 0b00100001=%eax, (%ecx, %eiz, 1)
オペランドを出力するプログラム例
REG = ['%eax', '%ecx', '%edx', '%ebx', '%esp', '%ebp', '%esi', '%edi']
def print_operand(code):
mrm = code[0]
mod, reg, mem = (mrm >> 6) & 0x03, (mrm >> 3) & 0x07, (mrm >> 0) & 0x07
if mod == 0b11:
return REG[reg], REG[mem]
is_sib = mem == 0b100
is_a32 = mod == 0b00 and mem == 0b101
dst = '' if is_a32 else REG[mem]
if is_sib:
sib = code[1]
scl, idx, bas = (sib >> 6) & 0x03, (sib >> 3) & 0x07, (sib >> 0) & 0x07
is_a32 = mod == 0b00 and bas == 0b101
dst = '{}, {}, {}'.format(
'' if is_a32 else REG[bas],
'%eiz' if idx == 0b100 else REG[idx],
1 << scl
)
dbt = 4 if is_a32 or mod == 0b10 else mod
dsp = int.from_bytes(code[is_sib + 1:is_sib + dbt + 1], byteorder='little')
if dst:
dst = '(' + dst + ')'
return REG[reg], ('0x{:0{}x}'.format(dsp, dbt) if dbt else '') + dst
print(print_operand(bytes([0b11000000])))
print(print_operand(bytes([0b11000100])))
print(print_operand(bytes([0b00000000])))
print(print_operand(bytes([0b01000000, 0xff])))
print(print_operand(bytes([0b10000000, 0xef, 0xbe, 0xad, 0xde])))
print(print_operand(bytes([0b00000101, 0xef, 0xbe, 0xad, 0xde])))
print(print_operand(bytes([0b01000101, 0xff])))
print(print_operand(bytes([0b10000101, 0xef, 0xbe, 0xad, 0xde])))
print(print_operand(bytes([0b00000100, 0b00000000])))
print(print_operand(bytes([0b00000100, 0b01000100])))
print(print_operand(bytes([0b00000100, 0b10000101, 0xef, 0xbe, 0xad, 0xde])))
print(print_operand(bytes([0b00000100, 0b00100101, 0xef, 0xbe, 0xad, 0xde])))
print(print_operand(bytes([0b01000100, 0b11000101, 0xff])))
print(print_operand(bytes([0b00000100, 0b00100000])))
print(print_operand(bytes([0b00000100, 0b00101000])))
print(print_operand(bytes([0b01000100, 0b00101101, 0xff])))