Edited at

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

More than 3 years have passed since last update.

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の全命令を実装してください。

※ 解答例はありません。