Haskellによる8086逆アセンブラ開発入門

  • 55
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

8086(16bit x86)の機械語に触れる導入として、Haskellによる逆アセンブラ開発の取っ掛かりを説明します。ここから機械語の勉強を始めることを想定していますので、機械語に関する知識は特に前提とはしていません。

Haskellは初歩的な機能のみ使います。以下の内容を理解していれば十分です。

練習の解答例は別記事に掲載します。

この記事のコードをまとめたリポジトリです。

この記事は勉強会のテキストとして使用していました。

この記事には姉妹編があります。

2進数

文字列で指定した2進数を数値に変換する関数です。HUnitのテストも書きます。

※ 使用例をメモする目的で、使用している関数のテストも書いています。

Main.hs
import Test.HUnit
import System.IO

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

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

tests = TestList
    [ "reverse"       ~: reverse     "11001" ~?= "10011"
    , "binStrToInt 1" ~: binStrToInt "101"   ~?= 5
    , "binStrToInt 2" ~: binStrToInt "11001" ~?= 25
    ]

main = do
    runTestText (putTextToHandle stderr False) tests
実行結果
Cases: 3  Tried: 3  Errors: 0  Failures: 0

以降はこのコードに付け足しながら進みます。

リビジョン 0

練習

【問1】数値を2進数の文字列に変換する関数binをテストファーストで作成してください。

解答例

16進数

練習

【問2】16進数の文字列を数値に変換する関数hexStrToIntをテストファーストで作成してください。

ヒント: Data.Char.digitToIntリファレンス

【問3】数値を16進数の文字列に変換する関数hexをテストファーストで作成してください。

解答例

桁揃え

16進数の頭に0を付加して桁を揃えます。指定した桁から溢れる場合は切り揃えます。

テストファーストを意識して進めます。テストコードの追加分と実装を分けて掲載します。

テスト
    , "replicate" ~: replicate 5 'a' ~?= "aaaaa"
    , "hexn 1" ~: hexn 2 1     ~?= "01"
    , "hexn 2" ~: hexn 2 255   ~?= "ff"
    , "hexn 3" ~: hexn 8 65535 ~?= "0000ffff"
    , "hexn 4" ~: hexn 2 256   ~?= "00"
hexn n x
    | r < 0     = drop (-r) x'
    | otherwise = replicate r '0' ++ x'
    where
        x' = hex x
        r  = n - length x'

replicateはリストを指定した回数だけ繰り返します。

リビジョン 4

メモリをリストで表現

メモリをシミュレートするためバイト区切りのリストを作成します。

16進数文字列→リスト

テスト
    , "hexStrToList 1" ~: hexStrToList "123456" ~?= [0x12, 0x34, 0x56]
    , "hexStrToList 2" ~: hexStrToList "010203" ~?= [1, 2, 3]
hexStrToList ""       = []
hexStrToList (h:l:xs) = hexStrToInt [h, l] : hexStrToList xs

リビジョン 5

リスト→16進数文字列

テスト
    , "listToHexStr 1" ~: listToHexStr [0x12, 0x34, 0x56] ~?= "123456"
    , "listToHexStr 2" ~: listToHexStr [1, 2, 3]          ~?= "010203"
listToHexStr []     = ""
listToHexStr (x:xs) = hexn 2 x ++ listToHexStr xs

リビジョン 6

バイトオーダー

メモリはバイトごとに区切られています。1バイトに収まりきらない数値は分割して格納します。

バイトごと区切って並べる方法にいくつか種類があります。これをバイトオーダーと呼びます。

分割した際に逆順に並べ替える方式をリトルエンディアンと呼びます。並べ替えない方式はビッグエンディアンです。

  • リトルエンディアン: 0x12345678[0x78, 0x56, 0x34, 0x12]
  • ビッグエンディアン: 0x12345678[0x12, 0x34, 0x56, 0x78]

【注意】バイトオーダーを考えるのは分割後です。分割前のバイトオーダーは考えません。

人間の目にはビッグエンディアンの方が直感的ですが、現在主流なのはリトルエンディアンです。今回対象とする8086はリトルエンディアンのみを使います。

数値→リトルエンディアン

バイト数を指定してリトルエンディアンを作成します。

テスト
    , "toLE 1" ~: toLE 2 1          ~?= [1, 0]
    , "toLE 2" ~: toLE 2 0x10000    ~?= [0, 0]
    , "toLE 3" ~: toLE 4 0x12345678 ~?= [0x78, 0x56, 0x34, 0x12]
toLE 0 _ = []
toLE n x = x `mod` 0x100 : toLE (n - 1) (x `div` 0x100)

リビジョン 7

リトルエンディアン→数値

バイト数を指定して数値を読み取ります。

テスト
    , "fromLE 1" ~: fromLE 2 [0, 1]                   ~?= 0x100
    , "fromLE 2" ~: fromLE 2 [0x78, 0x56, 0x34, 0x12] ~?= 0x5678
    , "fromLE 3" ~: fromLE 4 [0x78, 0x56, 0x34, 0x12] ~?= 0x12345678
fromLE 0 _      = 0
fromLE n (x:xs) = x + 0x100 * fromLE (n - 1) xs

以上で逆アセンブラ開発の準備は完了です。

リビジョン 8

練習

【問4】数値⇔ビッグエンディアンの相互変換を実装してください。

ヒント: べき乗(累乗)の演算子は^です。例: 2^3(2の3乗)

解答例

ビット演算

2進数を対象にしたビット演算という操作があります。

Haskellでは関数名が長くてやや煩雑なため、導入はPythonで行います。簡単な式しか扱わないため、Pythonの知識は前提としません。

HaskellではPythonとは演算子が異なるため、対応を示します。

import Data.Bitsが必要です。

操作 Python Haskell
左シフト << shiftL
右シフト >> shiftR
論理積 & .&.
論理和 | .|.
排他的論理和 ^ xor
反転 ~ complement

※ Pythonの演算子はC言語やJavaとも共通です。

練習

【問5】今まで実装した関数をビット演算で書き換えてください。

解答例

逆アセンブラ

いよいよ逆アセンブラ作りに入ります。

テストが長くなったので、今までのテストは分離します。

テストを分離
testHex = TestList
    [ "reverse"       ~: reverse     "11001" ~?= "10011"
    (略)
    ]

データシートを参照しながら進めます。

mov ax, imm16

簡単な命令をndisasmで逆アセンブルします。

逆アセンブル結果
B83412            mov ax,0x1234

B83412を機械語、movをニーモニック、ax0x1234をオペランドと呼びます。

機械語をリストで渡して逆アセンブルします。

実装とテスト
disasm (x:xs)
    | x == 0xb8 =
        "mov ax,0x" ++ hex (fromLE 2 xs)

testDisAsm = TestList
    [ "b8 1" ~: disasm [0xb8, 0, 0]       ~?= "mov ax,0x0"
    , "b8 2" ~: disasm [0xb8, 0x34, 0x12] ~?= "mov ax,0x1234"
    ]

main = do
    runTestText (putTextToHandle stderr False)
        (TestList [testHex, testDisAsm])

リビジョン 10

文字列渡し

バイナリを16進数文字列で渡せるようにします。

テスト
    , "b8 3" ~: disasm' "b80000" ~?= "mov ax,0x0"
    , "b8 4" ~: disasm' "b83412" ~?= "mov ax,0x1234"
disasm' hex = disasm $ hexStrToList hex

リビジョン 11

mov r16, imm16

レジスタ番号で処理するように拡張します。

テスト
    , "b8-bf 0" ~: disasm' "b90100" ~?= "mov cx,0x1"
    , "b8-bf 1" ~: disasm' "ba1000" ~?= "mov dx,0x10"
    , "b8-bf 2" ~: disasm' "bb0001" ~?= "mov bx,0x100"
    , "b8-bf 3" ~: disasm' "bc0010" ~?= "mov sp,0x1000"
    , "b8-bf 4" ~: disasm' "bdff00" ~?= "mov bp,0xff"
    , "b8-bf 5" ~: disasm' "be00ff" ~?= "mov si,0xff00"
    , "b8-bf 6" ~: disasm' "bffeca" ~?= "mov di,0xcafe"
reg16 = ["ax", "cx", "dx", "bx", "sp", "bp", "si", "di"]

disasm (x:xs)
    | 0xb8 <= x && x <= 0xbf =
        "mov " ++ reg16 !! (x - 0xb8) ++ ",0x" ++ hex (fromLE 2 xs)

リビジョン 12

mov r8, imm8

8bitレジスタにも対応します。

テスト
    , "b0-b7 1" ~: disasm' "b000" ~?= "mov al,0x0"
    , "b0-b7 2" ~: disasm' "b101" ~?= "mov cl,0x1"
    , "b0-b7 3" ~: disasm' "b210" ~?= "mov dl,0x10"
    , "b0-b7 4" ~: disasm' "b311" ~?= "mov bl,0x11"
    , "b0-b7 5" ~: disasm' "b412" ~?= "mov ah,0x12"
    , "b0-b7 6" ~: disasm' "b5ff" ~?= "mov ch,0xff"
    , "b0-b7 7" ~: disasm' "b6ee" ~?= "mov dh,0xee"
    , "b0-b7 8" ~: disasm' "b7ca" ~?= "mov bh,0xca"
reg8  = ["al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"]

disasm (x:xs)
    | 0xb0 <= x && x <= 0xb7 =
        "mov " ++ reg8  !! (x - 0xb0) ++ ",0x" ++ hex (xs !! 0)

リビジョン 13

8bitと16bitの統合

wフラグを見て切り替えます。

regs  = [reg8, reg16]

disasm (x:xs)
    -- DATA TRANSFER
    -- MOV = Move:
    -- Immediate to Register [1011wreg][data][data if w=1]
    | 0xb0 <= x && x <= 0xbf =
        "mov " ++ reg ++ "," ++ imm
        where
            w = (x `shiftR` 3) .&. 1
            reg = regs !! w !! (x .&. 7)
            imm = "0x" ++ hex (fromLE (w + 1) xs)

リビジョン 14

ビットパターン

データシートには機械語が2進数で表記されていますが、ここまでの実装では16進数に変換して範囲チェックしていました。2進数のままパターンマッチできるように修正します。

ビット分解

1バイトを8ビットに分解する関数を実装します。

テスト
    , "getBits" ~: getBits 0xbd ~?= (1,0,1,1,1,1,0,1)
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

このままではエラーになります。

エラー内容
No instance for (Bits t0) arising from a use of `getBits'
The type variable `t0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
  instance Bits Int -- Defined in `Data.Bits'
  instance Bits Integer -- Defined in `Data.Bits'
  instance Bits ghc-prim:GHC.Types.Word -- Defined in `Data.Bits'
(略)

テストで指定した0xbdInt, Integer, ghc-prim:GHC.Types.Wordのどの型か曖昧だというエラーメッセージです。このような場合、関数に型注釈を追加します。

文法
名前 :: 引数の型 -> 戻り値の型
型注釈
getBits :: Int -> (Int,Int,Int,Int,Int,Int,Int,Int)

これでテストが通ります。

※ 今までは型推論に任せて型注釈は省略していましたが、型注釈を付けるのが一般的です。

リビジョン 15

レジスタを再構成

レジスタは3ビットで表されますが、getBitsによってビットごとにばらばらになってしまうため、再構成する関数を実装します。

テスト
    , "getReg" ~: getReg 1 0 1 ~?= 5
getReg :: Int -> Int -> Int -> Int
getReg r e g =
    (r `shiftL` 2) .|. (e `shiftL` 1) .|. g

これもエラー回避のため型注釈が必要です。

引数が複数ある場合は、このように引数を1つずつ->で区切って書きます。最後が戻り値の型です。

文法
名前 :: 引数の型 -> 引数の型 -> 引数の型 -> 戻り値の型

※ このような表記になるのはカリー化が関係していますが、詳細は省略します。

リビジョン 16

ビットでパターンマッチ

disasmでビット分解してパターンマッチ用にdisasmBを追加します。

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

-- DATA TRANSFER
-- MOV = 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)

データシートに近い表記で実装することを意図しています。

リビジョン 17

mov(続き)

movを進めていきます。

データシートより
Register/Memory to/from Register [100010dw][mod reg r/m]

このパターンに合わせてデータを作ります。最初の1バイトを列挙します。

  • 10001000 0000000088 00
  • 10001001 0000000089 00
  • 10001010 000000008A 00
  • 10001011 000000008B 00

バイナリエディタで16進数を打ち込みます。この作業をハンドアセンブルと呼びます。

機械語をアセンブリ言語に変換する処理を逆アセンブルと呼びます。

  • アセンブル: アセンブリ言語 → 機械語
  • 逆アセンブル: 機械語 → アセンブリ言語

ndisasmで逆アセンブルします。

逆アセンブル結果
00000000  8800              mov [bx+si],al
00000002  8900              mov [bx+si],ax
00000004  8A00              mov al,[bx+si]
00000006  8B00              mov ax,[bx+si]

dがオペランドの順番、wがレジスタサイズを表します。modとr/mで1つのオペランドを表します。(以後ModR/M)

テスト
    , "88-8b mod=00,r/m=000 1" ~: disasm' "8800" ~?= "mov [bx+si],al"
    , "88-8b mod=00,r/m=000 2" ~: disasm' "8900" ~?= "mov [bx+si],ax"
    , "88-8b mod=00,r/m=000 3" ~: disasm' "8A00" ~?= "mov al,[bx+si]"
    , "88-8b mod=00,r/m=000 4" ~: disasm' "8B00" ~?= "mov ax,[bx+si]"

ModR/Mを処理する関数を実装してオペランドとregをタプルで返します。まず[bx+si]のみを実装します。

modrm (x:_) = (f mode rm, reg)
    where
        mode =  x `shiftR` 6
        reg  = (x `shiftR` 3) .&. 7
        rm   =  x             .&. 7
        f 0 0 = "[bx+si]"

これを使って逆アセンブラを実装します。

-- 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

リビジョン 18

r/m

r/mを増やして変化を確認します。

逆アセンブル結果
00000000  8901              mov [bx+di],ax
00000002  8902              mov [bp+si],ax
00000004  8903              mov [bp+di],ax
00000006  8904              mov [si],ax
00000008  8905              mov [di],ax
0000000A  89068907          mov [0x789],ax

89068907がくっ付いています。

即値によるアドレス指定

8906は mod = 00, r/m = 110 です。そのパターンを調べます。

逆アセンブル結果
00000000  88063412          mov [0x1234],al
00000004  89063412          mov [0x1234],ax
00000008  8A063412          mov al,[0x1234]
0000000C  8B063412          mov ax,[0x1234]

modrmを拡張します。

テスト
    , "88-8b mod=00,r/m=110 1" ~: disasm' "88063412" ~?= "mov [0x1234],al"
    , "88-8b mod=00,r/m=110 2" ~: disasm' "89063412" ~?= "mov [0x1234],ax"
    , "88-8b mod=00,r/m=110 3" ~: disasm' "8A063412" ~?= "mov al,[0x1234]"
    , "88-8b mod=00,r/m=110 4" ~: disasm' "8B063412" ~?= "mov ax,[0x1234]"
modrm (x:xs) = (f mode rm, reg)
    where
        mode =  x `shiftR` 6
        reg  = (x `shiftR` 3) .&. 7
        rm   =  x             .&. 7
        f 0 0 = "[bx+si]"
        f 0 6 = "[0x" ++ hex (fromLE 2 xs) ++ "]"

リビジョン 19

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

すべての組み合わせに対応させるため、仕様書からアドレス指定の組み合わせを調べて、アセンブリ言語でテストを書きます。

test.s
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

nasmでアセンブルしたものをndisasmで逆アセンブルします。

逆アセンブル結果
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

[bp]は機械語のパターンが異なるため除外します。(アドレス指定に割り当てられているため)

アドレスの組み合わせを定義してmodrmを拡張します。

テスト
    , "88-8b mod=00 1" ~: disasm' "8900" ~?= "mov [bx+si],ax"
    , "88-8b mod=00 2" ~: disasm' "8909" ~?= "mov [bx+di],cx"
    , "88-8b mod=00 3" ~: disasm' "8912" ~?= "mov [bp+si],dx"
    , "88-8b mod=00 4" ~: disasm' "891b" ~?= "mov [bp+di],bx"
    , "88-8b mod=00 5" ~: disasm' "8924" ~?= "mov [si],sp"
    , "88-8b mod=00 6" ~: disasm' "892d" ~?= "mov [di],bp"
    , "88-8b mod=00 7" ~: disasm' "893f" ~?= "mov [bx],di"
regad = ["bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"]

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 ++ "]"

リビジョン 20

ディスプレースメント

アドレスに対する差分をディスプレースメントと呼びます。[bp][bp+0]で表現します。

逆アセンブル結果
00000000  894001            mov [bx+si+0x1],ax
00000003  8949FF            mov [bx+di-0x1],cx
00000006  895202            mov [bp+si+0x2],dx
00000009  895BFE            mov [bp+di-0x2],bx
0000000C  896464            mov [si+0x64],sp
0000000F  896D9C            mov [di-0x64],bp
00000012  897600            mov [bp+0x0],si
00000015  897601            mov [bp+0x1],si
00000018  897F01            mov [bx+0x1],di

ディスプレースメントは符号付きです。符号の考え方は次の記事を参照してください。

符号付きで文字列化する関数を用意します。

テスト
    , "disp8 1" ~: disp8 0    ~?= "+0x0"
    , "disp8 2" ~: disp8 0x7f ~?= "+0x7f"
    , "disp8 3" ~: disp8 0x80 ~?= "-0x80"
    , "disp8 4" ~: disp8 0xff ~?= "-0x1"
disp8 x
    | x < 0x80  = "+0x" ++ hex x
    | otherwise = "-0x" ++ hex (0x100 - x)

リビジョン 21

modrmを拡張します。

テスト
    , "88-8b mod=01 1" ~: disasm' "894001" ~?= "mov [bx+si+0x1],ax"
    , "88-8b mod=01 2" ~: disasm' "8949FF" ~?= "mov [bx+di-0x1],cx"
    , "88-8b mod=01 3" ~: disasm' "895202" ~?= "mov [bp+si+0x2],dx"
    , "88-8b mod=01 4" ~: disasm' "895BFE" ~?= "mov [bp+di-0x2],bx"
    , "88-8b mod=01 5" ~: disasm' "896464" ~?= "mov [si+0x64],sp"
    , "88-8b mod=01 6" ~: disasm' "896D9C" ~?= "mov [di-0x64],bp"
    , "88-8b mod=01 7" ~: disasm' "897600" ~?= "mov [bp+0x0],si"
    , "88-8b mod=01 8" ~: disasm' "897601" ~?= "mov [bp+0x1],si"
    , "88-8b mod=01 9" ~: disasm' "897F01" ~?= "mov [bx+0x1],di"
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)

リビジョン 22

ファイルの分割

1ファイルが長くなって来たので分割します。

Leksahでの設定方法を説明します。Leksahについては以下を参照してください。

まずMain.hsの先頭にモジュール宣言を書いておきます。1ファイルだけのときは不要でしたが、ファイルを分割するときには必要となります。モジュール名はファイル名の拡張子を除いた部分と同じにします。

Main.hsの先頭
module Main where

Hex

  • File → New Module
    • Hex と入力 → [OK]

新規作成されたファイルの中身をすべて消します。

※ 中身を理解しないまま流用することによる問題を回避するための措置です。

ファイルの先頭にモジュール名を書いて、コードを移します。

Hex.hs
module Hex where

Mainから参照します。

Main.hsに追加
import Hex

リビジョン 23

DisAsm

逆アセンブラも同様に分割します。

  • File → New Module
    • DisAsm と入力 → [OK]

新規作成されたファイルの中身をすべて消します。

ファイルの先頭にモジュール名を書いて、コードを移します。

DisAsm.hs
module DisAsm where

Mainから参照します。

Main.hsに追加
import DisAsm

逆アセンブラのテストはMainに残したままにした方が、テストと実装を行ったり来たりするのに都合が良いでしょう。

リビジョン 24

練習

【問6】以下の手順でmodrmの実装を完成させてください。

  1. mod=10,11の機械語をバイナリエディタで作る。
  2. ndisasmで逆アセンブルしてアセンブリ言語を確認する。
  3. テストケースを作る。
  4. 逆アセンブラを実装する。

解答例

【問7】mov命令の2番目Immediate to Register/Memoryを実装してください。

解答例

【問8】mov命令の残りを実装してください。

解答例

【問9】disasmが命令の長さも返すように修正してください。想定される仕様をテストの修正として示します。仕様変更がdisasm'にまで及ぶとテストの修正が大変なため、与えられたバイナリの長さと比較する修正を提示します。

テストの修正
    [ "b8 1" ~: disasm [0xb8, 0, 0]       ~?= (3, "mov ax,0x0")
    , "b8 2" ~: disasm [0xb8, 0x34, 0x12] ~?= (3, "mov ax,0x1234")
disasm'の修正
disasm' hex
    | length bin == len = snd asm
    | otherwise         = "length? " ++ show len
    where
        bin = hexStrToList hex
        asm = disasm bin
        len = fst asm
  • showは引数を文字列に変換する関数です。

解答例

【問10】複数の命令を含んだバイナリを渡すと逆アセンブル結果をリストで返す関数を実装してください。具体的には以下のテストを通してください。

テスト
    , "disasms" ~: disasms [0xc6, 0x47, 1, 1, 0xb0, 1]
        ~?= [(4, "mov byte [bx+0x1],0x1"), (2, "mov al,0x1")]
    , "disasms'" ~: disasms' "C6470101B001"
        ~?= ["mov byte [bx+0x1],0x1", "mov al,0x1"]

解答例

【問11】逆アセンブル結果にアドレスやダンプを含めてndisasmと同じ出力にしてください。具体的には以下のテストを通してください。

テスト
    , "ndisasm" ~: ndisasm 0 [0xc6, 0x47, 1, 1]
        ~?= (4, "00000000  C6470101          mov byte [bx+0x1],0x1")
    , "ndisasms" ~: ndisasms 0 [0xc6, 0x47, 1, 1, 0xb0, 1]
        ~?= [ "00000000  C6470101          mov byte [bx+0x1],0x1"
            , "00000004  B001              mov al,0x1"
            ]
  • 第一引数は開始アドレスです。

解答例

ファイル読み込み

mainでコマンドライン引数を調べて、何も指定されていなければテスト、ファイルが指定されていれば読み込んで逆アセンブルするコードを示します。

依存パッケージにbytestringを追加してください。

importに追加
import System.Environment
import qualified Data.ByteString

qualifiedを指定すると使用する際に名前空間まで付ける必要があります(例: Data.ByteString.readFile)。元々あった関数と同名の関数(例: readFile)が衝突するのを回避しています。

mainを修正
main = do
    args <- getArgs
    if args == []
        then do
            runTestText (putTextToHandle stderr False)
                (TestList [testHex, testDisAsm])
            return ()
        else do
            bytes <- Data.ByteString.readFile $ args !! 0
            putStr $ unlines $ ndisasms 0
                [fromIntegral b | b <- Data.ByteString.unpack bytes]

<-はリスト内包表記の中か外かで意味が変わります。リスト内包表記の外で使われている<-はアクションから値を取り出す構文です。<-doを指定したブロックでしか使えません。詳細は次の記事を参照してください。

コマンドラインからファイルを指定して呼び出し、逆アセンブルできることを確認してください。

$ ./disasm test
00000000  C60012            mov byte [bx+si],0x12
00000003  C606123456        mov byte [0x3412],0x56
00000008  C6401234          mov byte [bx+si+0x12],0x34
0000000C  C680123456        mov byte [bx+si+0x3412],0x56
00000011  B012              mov al,0x12
00000013  C7001234          mov word [bx+si],0x3412
00000017  C70612345678      mov word [0x3412],0x7856
0000001D  C740123456        mov word [bx+si+0x12],0x5634
00000022  C78012345678      mov word [bx+si+0x3412],0x7856
00000028  B81234            mov ax,0x3412

リビジョン 32

練習

【問12】8086の全命令を実装してください。

※ 解答例はありません。