絶品ゆどうふのタレ

ふと気づいたことを綴るだけのメモ

機械語入門メモ

  • 11/3(月)
  • 機械語入門に行ってきたメモ
  • 公開し忘れ気づけば週末(ごめんなさい

先にまとめ

  • 相変わらず濃厚な時間で勉強になった
    • 命令の意味どうこうより、アセンブリ言語の表現力を知る、というスタンスはデータシート見ながらすごく納得。
  • 勉強会の間に実装した分まで。時間が取れ次第続きを実装
  • 勉強会同席してた@ryiwoさんがGoで再実装してるみたいで面白そう
    • 自分もGo勉強中だし、Haskell番が終わったら後追いで再実装してみようかな。。。
    • 同じノリでいろんな言語でやってみるの面白そう。
  • @7shiさん今回もありがとうございました。
    • 頑張って都合つけて、顔出せるようにしたいな。

HUnit入門

  • HUnitの使いかた

  • "ラベル" ~: 式 ~?= 期待値演算子でテストする

    • リストを書いて、テストを並べて書く
  • 失敗とエラーは区別されるよ

機械語入門

  • 8086
    • 16bitのintel CPU
    • 今のCPUのベースになっている
    • 32bit / 64bitへの拡張の基礎に為っている
  • 昔の特有の事情などもあるので、いきなり64bitとかやるとなぜ???となりやすい
  • なので16bitからやるほうが、下地の知識として知っておきましょう

2進数

  • 0と1の2つの数字で構成される
  • 10進数で位がひとつ上がると10倍、2進数で位がひとつ上がると2倍
  • 2進数 11 = 3
    • 一桁増える = 2倍なので、110 -> 11 の2倍なので、110 = 3 x 2 = 6と出せる

練習

  • サンプルの逆をやってみる
    • 数値を渡して2進数文字列
  • 下の桁から剰余を求め、どんどん上へ反復。商が0になったら打ち止め

桁揃え

  • 16進数に変換する際に、決まった桁数に揃える処理
    • 上にはみ出したらそれは落とす
  • あとでよく使うので関数を用意

メモリをリストで表現

  • 16進数の文字列を2m文字ずつ区切ってリスト化
  • 逆にリストを16進数文字列に直す
    • リストにしておくと、メモリをシミュレートできる
    • 人間が扱うときは面倒なので文字列に

バイトオーダー

  • メモリの中には16進数の2桁までしか入らない。
    • 1バイト
  • あふれる分はどうするか?
  • 2桁ずつバイトごと分割して入れる

  • 並べ方の方式(バイトオーダー)は2種類

    • ビッグエンディアン
      • 区切ったものを前(上)から順に並べていく
      • 直感的
    • トルエンディアン
      • 区切ったものを下から順にならべていく
      • 直感的ではないが、主流
      • 8086もこれ
    • エンディアン、って言葉はないよ。何エンディアンは?って聞くのは言葉としてはちょっとおかしい
  • 今回はリトルエンディアンを扱う

数値 -> リトルエンディアン

  • 2バイト -> 4桁(4ニブル)
  • 1を2バイトのリトルエンディアンに
    • 0001 -> 01, 00
  • 5桁以上にあふれたものは落とす
  • 桁数を制限されてるものは、上の方を落とす

  • トルエンディアンを数値にする、のも2進数を数値にすると同じような仕組み

    • 元にするリトルエンディアンの何バイトを対称とするか、も指定する
    • もちろん、溢れたら上位の桁(リトルエンディアンの後ろのほう)は落とす

練習

アセンブラ

mov ax, imm16

  • B83412 mov ax,0x1234
    • ぱっと見、3412の部分が0x1234っぽい
    • ということは、B8 = mov axっぽい
    • わかりやすい部分はこう見ちゃえばいい

文字列渡し

  • バイナリを16進数文字列で渡せるように
    • disasm'って便利関数作っとく
    • テスト書くの辛いし。。。

mov r16, imm16

  • レジスタ(REG)の番号に従って命令が変わっていく

    • レジスタ一覧の表にしたがって命令が変わる
  • オペコード = 機械語の中の一番重要な命令を示す部分

    • オペレーションコード

mov r8, imm8

8bitと16bitの統合

  • オペコードが1バイトか2バイトか、の違いしか無い
    • P.1 3行目 immediate to Register に対応
  • ならばまとめられるはず

    • リファクタ
  • 命令表の見方は、命令を一旦2進数になおしてみて、どれが対応するのかを探す

    • 今回のものは4bitで分かったが、7bit見ないとわからないものもあったりする
  • w = 8bitか16bitかを表してる

    • 略語は一応最後のP5にある
    • w = 0 だとbyte命令 byte = 1バイト
    • w = 1 だとword命令 word = 2バイト
      • wordが2バイト、というのは不変な話ではなく、CPU依存。x86の場合の話。
      • ARMの場合はwordは4バイト
  • レジスタが8bitなら1バイト、16bitなら2バイトのデータを受け取る

    • 表の data if w = 1はそれを表してる
  • この表を元にコードをリファクタする

    • 割り算でもとれるが、遅い処理なのでビット演算する
  • シフト
    • 左シフト
      • ひとけた増えて、一番下が0になる
    • 右シフト
      • 一番下の位を押しつぶす
      • 割り算の代わりに、桁をつぶす事ができる
  • マスク
    • 特定の桁だけを残して、他全部0にする処理
    • 論理積(.&.)を使う
      • 両方成り立ってる時だけ1になる
    • 残したいところだけ1にしてマスクすることで、お目当てのbitだけが取れる
  • オペランドに直接数字が書いてるもの(ハードコードされたもの)を即値(immediate)という
  • immの値を何バイト取ってくればいいかは、wに1を足したバイト数だとわかるので、そうやって取ってくればいいか

ビットパターン

  • せっかくHaskellつかってるので、どの命令かをパターンマッチしよう
  • 2進数のままパターンマッチできるようにする

ビット分解

  • 1byte = 8bitなのは不変(8086に限って言えば)なので、タプル8bitぶんに直す
    • 8回shiftとmaskを繰り返せば得られる

レジスタを再構成

  • ビットごとにバラバラになったので、regをくっつける
  • 論理和(.|.)を使う
    • ビットごと別々になってるものをくっつけるのに使える
    • 論理和はビットごとにしか見ないので、くりあがりはない

ビットでパターンマッチ

  • disasmBという別の関数作って、パターンマッチでbit書けるようにする
    • wもそのままとれて便利
  • なるべく仕様書をそのまま読めるように実装してる

mov(続き)

  • 今までは、一番簡単な行をやったので、あとは順次やっていく

  • ちなみに、データのパターンから機械語を手動で作ってく作業をハンドアセンブルと呼ぶ

    • ソラですらすらできなくていいけど、いざとなったらできるぐらいにはなっとくといい
  • アセンブルとは

  • アセンブル

  • ふつう、こういうのはソフトにやらせる

  • やってみよう

  • バイナリエディタで保存

    • 88 00
    • 89 00
    • 8A 00
    • 8B 00
  • 結果

    • 最初の行は、機械語のはいってる位置
$ ndisasm test.bin
00000000  8800              mov [bx+si],al
00000002  8900              mov [bx+si],ax
00000004  8A00              mov al,[bx+si]
00000006  8B00              mov ax,[bx+si]
  • これからやってくことは。。。
    • バイナリ作ってみて、ndisasmして
    • そこから命令とバイナリの対比をみて
    • テストケース作って
    • 実装していく
  • データシートだけ見て、アセンブリ言語との対比はわからない

  • ndisasmがもうあるなら、やらなくていいんじゃ?

    • もちろんその通り
    • 今やっているのは、これらの命令の構造を理解するための勉強として。
  • 普通は命令1個1個の説明を聞いて。。。なんてやるけど、それだと表現力がわかんない

    • なので逆アセンブラを作ってそれを学ぶのを最初にやってみよう!というのがこの会の趣旨なので。
  • さっき見た命令の内容から、wが何を表しているかは知っている

  • データシートの最後のページを見ると、dの説明がある
    • from reg / to reg
    • 代入の方向を表している
    • regから代入か、regへ代入か
  • 残りのmod r/mは、[bi+si]の内容に関係するのでは?という推測はできる

r/m

  • r/mを増やしてやってみる
00000008  8901              mov [bx+di],ax
0000000A  8902              mov [bp+si],ax
0000000C  8903              mov [bp+di],ax
0000000E  8904              mov [si],ax
00000010  8905              mov [di],ax
00000012  89068907          mov [0x789],ax
  • なんだか、8906の時はちょっと様子が違う
    • mod = 00 r/m = 110
      • この場合に即値を取るっぽい
    • 条件に対応させて実装
00000012  89068907          mov [0x789],ax
00000016  88063412          mov [0x1234],al
0000001A  89063412          mov [0x1234],ax
0000001E  8A063412          mov al,[0x1234]
00000022  8B063412          mov ax,[0x1234]

レジスタによるアドレス指定

  • メモリのアドレスは、上位2byteをhigh, 下位2byteをlow

  • 指定できるアドレス指定の組み合わせは、全てのレジスタが使えるわけじゃなくて、決まっている

    • データシートに全て書いてある
    • これしか使わせない、という取り決めがある
    • このようにして、表現力が制限されている
      • 32bitはもっと複雑になって、全てのレジスタが使えるようになってたりする

今度はアセンブラ使ってみよう

  • 仕様書から組み合わせが全部でた
mov [bx+si],ax
mov [bx+di],cx
mov [bp+si],dx
mov [bp+di],bx
mov [si],sp
mov [di],bp
mov [bp],si
mov [bx],di
$ ndisasm test
00000000  8900              mov [bx+si],ax
00000002  8909              mov [bx+di],cx
00000004  8912              mov [bp+si],dx
00000006  891B              mov [bp+di],bx
00000008  8924              mov [si],sp
0000000A  892D              mov [di],bp
0000000C  897600            mov [bp+0x0],si
0000000F  893F              mov [bx],di
  • これで89xxの組み合わせがわかった

    • mod = 00の部分がわかった
    • [bp]はアドレス指定に使っててパターンが異なるので除外
  • mod = 01 の出し方を説明していく

符号の考え方

  • ちょっとした暗算の工夫

    • 12 + 99 を簡単にするには?
    • 12 + 100 - 1にすると簡単
  • 2桁に制限された世界

    • 12 + 99 = 12 + 100 - 1 = 111 = 11
      • そもそも100を足してるけど、100自体がキャンセルされてる
    • 12 + 99 = 12 + 100 - 1 = 12 - 1 = 11
    • つまり、2桁に制限された世界では、99を足すことと、1を引くことが同一視できる
      • このように0~99で制限された数の集合を有限体と呼ぶ
  • 補数

    • 100 - 1 = 99 -> 100 - 99 = 1
    • このように、100から引くことで、お互いを変換できる
    • このような関係を補数という
  • 符号の線引き

    • 足し算と引き算の計算を逆転させるときに補数が出てくる
    • 対比表を見ると、絶対値が一定のところで反転している
    • 補数の代表値は、絶対値の小さい方の数を代表値として、符号反転して考えることで解釈できる
    • 中央値はどうするか?
      • 0を含めて考えるといい
    • 0をプラスに含めると、中央値をマイナスに含めれば均等になる
  • 問題としているのは16進数

    • -0x80~0x7F
  • なぜこれをやる?

    • 桁が制限されている
    • 巨大な数の足し算は、引き算に相当する
    • それをうまいことやるため

ディスプレースメント

00000026  894001            mov [bx+si+0x1],ax
00000029  8949FF            mov [bx+di-0x1],cx
0000002C  895202            mov [bp+si+0x2],dx
0000002F  895BFE            mov [bp+di-0x2],bx
00000032  896464            mov [si+0x64],sp
00000035  896D9C            mov [di-0x64],bp
00000038  897600            mov [bp+0x0],si
0000003B  897601            mov [bp+0x1],si
0000003E  897F01            mov [bx+0x1],di
  • アドレスに対する差分のことをディスプレースメントと呼ぶ
    • [bp+0x1]みたいなやつ
  • 符号付きの表現
    • 符号をうまく吸収するようにmodrmを拡張
    • 符号化のためのdisp8の関数をつくる

ファイルを分ける

  • ちょっとでかくなってきたので分割する
    • Hex
    • DisAsm
    • をそれぞれ作って別に読み込む

練習

  • 順次シートの実装を進める

問5

  • mod = 10, 11に適当に当たりをつける
  • mod = 10の範囲は 8980 ~ 89BF になるはず
  • mod = 11の範囲は 89C0 ~ 89FF になるはず

mod = 10

00000000  89800001          mov [bx+si+0x100],ax
00000004  89840F00          mov [si+0xf],ax
00000008  8990FF00          mov [bx+si+0xff],dx
0000000C  89920E02          mov [bp+si+0x20e],dx
00000010  89A00000          mov [bx+si+0x0],sp
00000014  89A6DD00          mov [bp+0xdd],sp
00000018  89AFBC0F          mov [bx+0xfbc],bp
00000000  89BFFFFF          mov [bx-0x1],di
  • mod = 10の場合は、ディスプレースメントの部分が拡張しているようにみえる
    • mode でビットシフトして取ってくる数が16進数じゃなくて10進数になってる
    • disp16作って何とかする

mod = 11

00000000  88C0              mov al,al
00000002  88C1              mov cl,al
00000004  89C0              mov ax,ax
00000006  89C1              mov cx,ax
00000008  89C2              mov dx,ax
0000000A  89C3              mov bx,ax
0000000C  89D0              mov ax,dx
0000000E  89F0              mov ax,si
00000010  89FF              mov di,di
00000012  8AC0              mov al,al
00000014  8AC1              mov al,cl
00000016  8BC0              mov ax,ax
  • mod = 11 の場合は、r/mのところにregの内容が入ってくる
  • 88C0 - 88FF, 89C0 - 89FFでそれぞれ8bit, 16bitの役割になってる

    • wの影響もregと同様に受けている
  • TODO...

実装

  • めっちゃ途中
  • 一応、当日出来たとこまで載せる

Hex.hs

module Hex where

import Test.HUnit
import Data.Char

binToInt '0' = 0
binToInt '1' = 1

binStrToInt bin = f (reverse bin)
    where
        f ""        = 0
        f (x:xs)    = (binToInt x) + 2 * (f xs)

intToBin 0 = '0'
intToBin 1 = '1'

bin n
    | n' == 0   = s
    | otherwise = bin n' ++ s
    where
        s       = [intToBin (n `mod` 2)]
        n'      = n `div` 2

hexStrToInt bin = f (reverse bin)
    where
        f ""    = 0
        f (x:xs) = (digitToInt x) + 16 * (f xs)

hex n 
    | n' == 0 = s
    | otherwise = hex n' ++ s
    where
        s   = [intToDigit (n `mod` 16)]
        n'  = n `div` 16

hexn n x
    | r < 0 = drop (-r) x'
    | otherwise = replicate r '0' ++ x'
    where
        x' = hex x
        r = n - length x'

hexStrToList "" = []
hexStrToList (h:l:xs) = hexStrToInt [h, l] : hexStrToList xs

listToHexStr [] = ""
listToHexStr (x:xs) = hexn 2 x ++ listToHexStr xs

toLE 0 _ = []
toLE n x = x `mod` 0x100 : toLE (n - 1) (x `div` 0x100)

fromLE 0 _ = 0
fromLE n (x:xs) = x + 0x100 * fromLE (n - 1) xs

toBE 0 _ = []
toBE n x = toBE (n - 1) (x `div` 0x100) ++ [x `mod` 0x100]
-- toBE n x = (x `div` 0x100^(n - 1)) `mod` 0x100 : toBE (n - 1) x

fromBE 0 _ = 0
fromBE n (x:xs) = x * 0x100^(n - 1) + fromBE (n - 1) xs

testHex = TestList
    [ "1 reverse"         ~: reverse  "11001" ~?= "10011"
    , "2 binStrToInt 1"   ~: binStrToInt "101" ~?= 5
    , "3 binStrToInt 2"   ~: binStrToInt "11001" ~?= 25
    , "4 bin 1"   ~: bin 5 ~?= "101"
    , "5 bin 2"   ~: bin 25 ~?= "11001"
    , "6 hexStrToInt 1"   ~: hexStrToInt "F" ~?= 15
    , "7 hexStrToInt 2"   ~: hexStrToInt "FF" ~?= 255
    , "8 hex 1"   ~: hex 15 ~?= "f"
    , "9 hex 2"   ~: hex 256 ~?= "100"
    
    , "10 replicate" ~: replicate 5 'a' ~?= "aaaaa"
    , "11 hexn 1"  ~: hexn 2 1     ~?= "01"
    , "12 hexn 2"  ~: hexn 2 255   ~?= "ff"
    , "13 hexn 3"  ~: hexn 8 65535 ~?= "0000ffff"
    , "14 hexn 4"  ~: hexn 2 256   ~?= "00"
    
    , "15 hexStrToList 1"  ~: hexStrToList "123456" ~?= [0x12, 0x34, 0x56]
    , "16 hexStrToList 2"  ~: hexStrToList "010203" ~?= [1, 2, 3]
    , "17 listToHexStr 1"  ~: listToHexStr [0x12, 0x34, 0x56]  ~?= "123456"
    , "18 listToHexStr 2"  ~: listToHexStr [1 ,2 ,3]           ~?= "010203"
    
    , "19 toLE 1"   ~: toLE 2 1             ~?= [1, 0]
    , "20 toLE 2"   ~: toLE 2 0x10000       ~?= [0, 0]
    , "21 toLE 3"   ~: toLE 4 0x12345678    ~?= [0x78, 0x56, 0x34, 0x12]
    , "22 fromLE 1" ~: fromLE 2 [0, 1]                      ~?= 0x100
    , "23 fromLE 2" ~: fromLE 2 [0x78, 0x56, 0x34, 0x12]    ~?= 0x5678
    , "24 fromLE 3" ~: fromLE 4 [0x78, 0x56, 0x34, 0x12]    ~?= 0x12345678
    
    , "25 toBE 1"   ~: toBE 2 1             ~?= [0, 1]
    , "26 toBE 2"   ~: toBE 2 0x10000       ~?= [0, 0]
    , "27 toBE 3"   ~: toBE 4 0x12345678    ~?= [0x12, 0x34, 0x56, 0x78]
    , "28 fromBE 1" ~: fromBE 2 [0, 1]                      ~?= 0x001
    , "29 fromBE 2" ~: fromBE 2 [0x78, 0x56, 0x34, 0x12]    ~?= 0x7856
    , "30 fromBE 3" ~: fromBE 4 [0x78, 0x56, 0x34, 0x12]    ~?= 0x78563412
    ]
  • Disasm.hs
module Disasm where

import Test.HUnit
import Data.Bits

import Hex

reg8  = ["al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"]
reg16 = ["ax", "cx", "dx", "bx", "sp", "bp", "si", "di"]
regs  = [reg8, reg16]

regad = ["bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"]

getBits :: Int -> (Int, Int, Int, Int, Int, Int, Int, Int)
getBits x = (b 7, b 6, b 5, b 4, b 3, b 2, b 1, b 0)
    where
        b n = (x `shiftR` n) .&. 1

getReg :: Int -> Int -> Int -> Int
getReg r e g =
    (r `shiftL` 2) .|. (e `shiftL` 1) .|. g

modrm (x:xs) = (f mode rm, reg)
    where
        mode = x `shiftR` 6
        reg = (x `shiftR` 3) .&. 7
        rm = x .&. 7
        f 0 6 = "[0x" ++ hex(fromLE 2 xs) ++ "]"
        f 0 rm = "[" ++ regad !! rm ++ "]"
        f 1 rm = "[" ++ regad !! rm ++ disp ++ "]"
            where
                disp = disp8 (xs !! 0)
        f 2 rm = "[" ++ regad !! rm ++ disp ++ "]"
            where
                disp = disp16 (fromLE 2 xs)
        
disp8 x
    | x < 0x80 = "+0x" ++ hex x
    | otherwise = "-0x" ++ hex (0x100 - x)

disp16 x
    | x < 0x8000 = "+0x" ++ hex x
    | otherwise = "-0x" ++ hex (0x10000 - x)

-- DATA TRANSFER
-- MOVE = Move:
-- Immediate to Register [1011wreg][data][data if w = 1]
disasmB (1,0,1,1,w,r,e,g) xs =
    "mov " ++ reg ++ "," ++ imm
    where
        reg = regs !! w !! getReg r e g
        imm = "0x" ++ hex (fromLE (w + 1) xs)

-- Register/Memory to/from Register [100010dw][mod reg r/m]
disasmB(1,0,0,0,1,0,d,w) xs
    | d == 0 = "mov " ++ rm ++ "," ++ reg
    | otherwise = "mov " ++ reg ++ "," ++ rm
    where
        (rm, r) = modrm xs
        reg = regs !! w !! r

disasm (x:xs) = disasmB (getBits x) xs

disasm' hex = disasm $ hexStrToList hex
  • Main.hs
module Main where

import Test.HUnit
import System.IO

import Hex
import Disasm

testDisAsm = TestList
    [ "1 b8 1"  ~: disasm [0xb8, 0, 0]      ~?= "mov ax,0x0"
    , "2 b8 2"  ~: disasm [0xb8, 0x34, 0x12] ~?= "mov ax,0x1234"
    , "3 b8 3"  ~: disasm' "b80000" ~?= "mov ax,0x0"
    , "4 b8 4"  ~: disasm' "b83412" ~?= "mov ax,0x1234"
    
    , "5 b8-bf 0" ~: disasm' "b90100" ~?= "mov cx,0x1"
    , "6 b8-bf 1" ~: disasm' "ba1000" ~?= "mov dx,0x10"
    , "7 b8-bf 2" ~: disasm' "bb0001" ~?= "mov bx,0x100"
    , "8 b8-bf 3" ~: disasm' "bc0010" ~?= "mov sp,0x1000"
    , "9 b8-bf 4" ~: disasm' "bdff00" ~?= "mov bp,0xff"
    , "10 b8-bf 5" ~: disasm' "be00ff" ~?= "mov si,0xff00"
    , "11 b8-bf 6" ~: disasm' "bffeca" ~?= "mov di,0xcafe"
    
    , "12 b0-b7 1" ~: disasm' "b000" ~?= "mov al,0x0"
    , "13 b0-b7 2" ~: disasm' "b101" ~?= "mov cl,0x1"
    , "14 b0-b7 3" ~: disasm' "b210" ~?= "mov dl,0x10"
    , "15 b0-b7 4" ~: disasm' "b311" ~?= "mov bl,0x11"
    , "16 b0-b7 5" ~: disasm' "b412" ~?= "mov ah,0x12"
    , "17 b0-b7 6" ~: disasm' "b5ff" ~?= "mov ch,0xff"
    , "18 b0-b7 7" ~: disasm' "b6ee" ~?= "mov dh,0xee"
    , "19 b0-b7 8" ~: disasm' "b7ca" ~?= "mov bh,0xca"
    
    , "20 getBits" ~: getBits 0xbd ~?= (1,0,1,1,1,1,0,1)
    , "21 getReg"  ~: getReg 1 0 1 ~?= 5
    
    , "22 88-8b mod=00,r/m=000 1" ~: disasm' "8800" ~?= "mov [bx+si],al"
    , "23 88-8b mod=00,r/m=000 2" ~: disasm' "8900" ~?= "mov [bx+si],ax"
    , "24 88-8b mod=00,r/m=000 3" ~: disasm' "8A00" ~?= "mov al,[bx+si]"
    , "25 88-8b mod=00,r/m=000 4" ~: disasm' "8B00" ~?= "mov ax,[bx+si]"
    
    , "26 88-8b mod=00,r/m=110 1" ~: disasm' "88063412" ~?= "mov [0x1234],al"
    , "27 88-8b mod=00,r/m=110 2" ~: disasm' "89063412" ~?= "mov [0x1234],ax"
    , "28 88-8b mod=00,r/m=110 3" ~: disasm' "8A063412" ~?= "mov al,[0x1234]"
    , "29 88-8b mod=00,r/m=110 4" ~: disasm' "8B063412" ~?= "mov ax,[0x1234]"
    
    , "30 88-8b mod=00 1" ~: disasm' "8900" ~?= "mov [bx+si],ax"
    , "31 88-8b mod=00 2" ~: disasm' "8909" ~?= "mov [bx+di],cx"
    , "32 88-8b mod=00 3" ~: disasm' "8912" ~?= "mov [bp+si],dx"
    , "33 88-8b mod=00 4" ~: disasm' "891b" ~?= "mov [bp+di],bx"
    , "34 88-8b mod=00 5" ~: disasm' "8924" ~?= "mov [si],sp"
    , "35 88-8b mod=00 6" ~: disasm' "892d" ~?= "mov [di],bp"
    , "36 88-8b mod=00 7" ~: disasm' "893f" ~?= "mov [bx],di"
    
    , "37 disp8 1" ~: disp8 0    ~?= "+0x0"
    , "38 disp8 2" ~: disp8 0x7f ~?= "+0x7f"
    , "39 disp8 3" ~: disp8 0x80 ~?= "-0x80"
    , "40 disp8 4" ~: disp8 0xff ~?= "-0x1"
    
    , "41 88-8b mod=01 1" ~: disasm' "894001" ~?= "mov [bx+si+0x1],ax"
    , "42 88-8b mod=01 2" ~: disasm' "8949FF" ~?= "mov [bx+di-0x1],cx"
    , "43 88-8b mod=01 3" ~: disasm' "895202" ~?= "mov [bp+si+0x2],dx"
    , "44 88-8b mod=01 4" ~: disasm' "895BFE" ~?= "mov [bp+di-0x2],bx"
    , "45 88-8b mod=01 5" ~: disasm' "896464" ~?= "mov [si+0x64],sp"
    , "46 88-8b mod=01 6" ~: disasm' "896D9C" ~?= "mov [di-0x64],bp"
    , "47 88-8b mod=01 7" ~: disasm' "897600" ~?= "mov [bp+0x0],si"
    , "48 88-8b mod=01 8" ~: disasm' "897601" ~?= "mov [bp+0x1],si"
    , "49 88-8b mod=01 9" ~: disasm' "897F01" ~?= "mov [bx+0x1],di"
    
    , "50 disp16 1" ~: disp16 0      ~?= "+0x0"
    , "51 disp16 2" ~: disp16 0xff   ~?= "+0xff"
    , "52 disp16 3" ~: disp16 0x8000 ~?= "-0x8000"
    , "53 disp16 4" ~: disp16 0xffff ~?= "-0x1"
    
    , "54 88-8b mod=10 1" ~: disasm' "89800001" ~?= "mov [bx+si+0x100],ax"
    , "55 88-8b mod=10 2" ~: disasm' "89840F00" ~?= "mov [si+0xf],ax"
    , "56 88-8b mod=10 3" ~: disasm' "8990FF00" ~?= "mov [bx+si+0xff],dx"
    , "57 88-8b mod=10 4" ~: disasm' "89920E02" ~?= "mov [bp+si+0x20e],dx"
    , "58 88-8b mod=10 5" ~: disasm' "89A00000" ~?= "mov [bx+si+0x0],sp"
    , "59 88-8b mod=10 6" ~: disasm' "89A6DD00" ~?= "mov [bp+0xdd],sp"
    , "60 88-8b mod=10 7" ~: disasm' "89AFBC0F" ~?= "mov [bx+0xfbc],bp"
    , "61 88-8b mod=10 8" ~: disasm' "89BFFFFF" ~?= "mov [bx-0x1],di"
    
    , "88-8b mod=11 1" ~: disasm' "88C0" ~?= "mov al,al"
    , "88-8b mod=11 2" ~: disasm' "88C1" ~?= "mov cl,al"
    , "88-8b mod=11 3" ~: disasm' "89C0" ~?= "mov ax,ax"
    , "88-8b mod=11 4" ~: disasm' "89C1" ~?= "mov cx,ax"
    , "88-8b mod=11 5" ~: disasm' "89C2" ~?= "mov dx,ax"
    , "88-8b mod=11 6" ~: disasm' "89C3" ~?= "mov bx,ax"
    , "88-8b mod=11 7" ~: disasm' "89D0" ~?= "mov ax,dx"
    , "88-8b mod=11 8" ~: disasm' "89F0" ~?= "mov ax,si"
    , "88-8b mod=11 9" ~: disasm' "89FF" ~?= "mov di,di"
    , "88-8b mod=11 10" ~: disasm' "8AC0" ~?= "mov al,al"
    , "88-8b mod=11 11" ~: disasm' "8AC1" ~?= "mov al,cl"
    , "88-8b mod=11 12" ~: disasm' "8BC0" ~?= "mov ax,ax"

    ]

main = do
    runTestText (putTextToHandle stderr False)
        (TestList [testHex, testDisAsm])
    print $ bin 0xb0
    print $ bin 0xb4