x86 汎用命令 - ModR/M の解説
ModR/Mについては前もちょっと記事を書いたけど、まだちょっと中途半端だったのでもう一度まとめます。
nantonaku-shiawase.hatenablog.com
ModR/Mの実際の例
例えば以下のような命令がある、 /r の意図がみなさんわからないのではないでしょうか?
; IMUL r32, imm32 0x69 /r iw
また、次のような命令、 /7 の意図がみなさんわからないのではないでしょうか?
; CMP r/m8, imm8 0x80 /7 ib
ModR/Mの構造
全体の概要についてはこのページをみたほうがよい
また、このページも役立つ
'''/r'''および'''/7'''の意味
解説を Assembly Programming on x86-64 Linux (06) から引用する
この記事で注目している項目を赤字で表現。
記法 | 解説 |
---|---|
/0 - /7 | ModR/M バイト の reg フィールドの 0 から 7 の数字はオペコードの拡張用に使われる。r/m フィールドだけをオペランドに使用する。 |
/r | 命令には ModR/M バイトが続き、レジスタオペランドと r/m オペランドの両方を使う。 |
cb、cw、cd、cp, co, ct | オペコードの後に 1 バイト(cb)、2 バイト(cw)、4 バイト(cd)、6 バイト(cp)、 8 バイト(co)または 10 バイト(ct)が続く。 |
ib, iw, id, io | オペコード、ModM/R バイト、または SIB の後に続く 1 バイト(ib)、2 バイト(iw)、4 バイト(id)または 8 バイト(io)の定数(即値)。 |
+rb, +rw, +rd, +ro | + の左側のオペコードに加算される 0 から 7 までのレジスタコード。 結果として 1 バイトのオペコードとなる。 |
reg(/0 - /7指定の場合)
- regは、/0 - /7を指定された場合はそれをそのまま使う
もちろんこれは10進数表記なので、2進数に直すと以下のようになる。ModR/Mバイトのregフィールドはこれで埋まるわけだ。
/0 - /7 | 2進数表記 |
---|---|
/0 | 000 |
/1 | 001 |
/2 | 010 |
/3 | 011 |
/4 | 100 |
/5 | 101 |
/6 | 110 |
/7 | 111 |
reg(/0 - /7指定の場合)の実例
- 0x80 /7 ib をアセンブル
; 0x80 /7 ib CMP r/m8, imm8 ; 例 CMP CL,18 ; オペコードは0x80で確定 ; --------------------- ; ModR/Mの値は ; [mod] 11 ; [reg] 111 ; [r/m] 001 ; => "11111001" ; => 0xf9 ; --------------------- ; 18は16進数で0x12 ; --------------------- ; よって、以下がアセンブルされると CMP CL,18 ; 以下のバイナリが出力される 0x80, 0xf9, 0x12
reg('''/r'''指定の場合)
- regは、レジスタコードを指定する
つまり、mod2bitに続いて、regとr/mが3bitずつ続くことになる
reg(/r指定の場合)の実例
- '''0x69 /r iw''' をアセンブル
; 0x69 /r iw IMUL r32, imm32 ; 例 IMUL ECX,4608 ; オペコードは0x69で確定 ; --------------------- ; ModR/Mの値は ; [mod] 11 ; [reg] 001 ; [r/m] 001 ; => "11001001" ; => 0xc9 ; --------------------- ; 4608は16進数で0x1200 ; リトルエンディアンのため、 0x00, 0x12と並ぶ ; --------------------- ; よって、以下がアセンブルされると IMUL ECX,4608 ; 以下のバイナリが出力される 0x69, 0xc9, 0x00, 0x12
/r指定の場合のレジスタの細かい指定
さて、実はここまでregとr/mのどちらがどちらのレジスタを指定しているのか気になった人がいるかもしれない。その指摘はめっちゃ正しい。
ここのサイトに答えが書いてある。
The d bit in the opcode determines which operand is the source, and which is the destination:
d=0: MOD R/M <- REG, REG is the source
d=1: REG <- MOD R/M, REG is the destination
"d"bitというのは、1byte(8bit)のデータを先頭から数えて4番目のbitである。*1
つまり、"d"bitが0か1かによってmodとr/mの3bitをそれぞれ入れ替えなければいけない。
C++の疑似コード、std::bitsetを使うとやりやすい
uint8_t op = 0x8e; std::bitset<8> bsl(op); // オペコード if (bsl[3]) { // d=0 or 1 // d=1: REG <- MOD R/M, REG is the destination } else { // d=0: MOD R/M <- REG, REG is the source }
はじめて読む486、ModRMとかに関してはほとんど役に立たず…
- 作者: 蒲地輝尚
- 出版社/メーカー: アスキー
- 発売日: 1994/09
- メディア: 単行本
- 購入: 20人 クリック: 165回
- この商品を含むブログ (84件) を見る
*1:わかるか…!こんな細かい仕様…!