Wiki
Clone wikiikebin / 8086 / hello
8086 > 1, 2, 2-1, 2-2, 2-3, 2-4, 2-5, 2-6, 2-7, 3, 4
この資料とサンプルコードはCC0(いわゆるパブリックドメイン)で提供します。
小さなバイナリを作って分析
- バイナリの対象は8086版UNIX V6
- まだOS本体は移植されていないため、インタプリタ上で仮想的に実行
ソース
write.s
! write(1, hello, 6); mov ax, #1 int 7 .data1 4 .data2 hello, 6 ! exit(0); mov ax, #0 int 7 .data1 1 .sect .data hello: .ascii "hello\n"
- ACKの文法
- アセンブリ言語の文法はアセンブラによって方言がある
!
はコメント
ax
はレジスタと呼ばれます。変数のようなものです。- 8086のアセンブリ言語
mov
は代入:mov ax, #0
≒ax = 0
int
は割り込み命令、7
は割り込み番号(システムコールは7固定)
システムコール
割り込みでカーネルを呼び出す。
.data1
でシステムコール番号を指定ax
は第一引数write
に渡す1
は標準出力
.data2
は追加の引数
UNIX V6のシステムコール定義: /usr/sys/ken/sysent.c
int sysent[] { 追加の引数の数, &処理関数, /* システムコール番号 = システムコール名 */ ... };
exit
: システムコール番号 1, 追加の引数の数 0write
: システムコール番号 4, 追加の引数の数 2sysent[] {
の間にイコールがないのはC言語が古いため(pre K&R)
実行
$ 8086v6-cc -.o write.s $ v6strip a.out $ 7run a.out hello
-.o
オプションはcrtやlibcをリンクしないオプション- バイナリを小さくするためC言語に必要なものを外した
gcc -nostdlib
に相当- 出力ファイルを指定する
-o
とは別なのに注意
逆アセンブル
バイナリを分析
$ 7run -d a.out 0000: b80100 mov ax, 0001 0003: cd07 int 7 0005: 04 ; sys write 0006: 1000 ; arg 0008: 0600 ; arg 000a: b80000 mov ax, 0000 000d: cd07 int 7 000f: 01 ; sys exit
- write.s →(アセンブル)→ a.out →(逆アセンブル)→ 上記結果
- アセンブル: アセンブリ言語 → バイナリ
- 逆アセンブル: バイナリ → アセンブリ言語
- 逆とは言っても完全に元に戻るわけではない
- 逆アセンブルでは対応するバイナリが確認できる
- 逆アセンブラの出力はACKの文法とは異なる
- 最終的にはこの文法を受け付けるアセンブラを提供予定
バイナリダンプ
バイナリを16進数で出力
$ hd a.out 000000 eb 0e 10 00 06 00 00 00 00 00 00 00 00 00 01 00 >................< 000010 b8 01 00 cd 07 04 10 00 06 00 b8 00 00 cd 07 01 >................< 000020 68 65 6c 6c 6f 0a >hello.< 000026
※ hd
が存在しない環境ではhexdump -C
を使用
メモリに読み込み
ヘッダはメモリに配置しないため、ファイルのオフセットとメモリのアドレスは16バイトずれる。
0000 b8 01 00 cd 07 04 10 00 06 00 b8 00 00 cd 07 01 ................ 0010 68 65 6c 6c 6f 0a hello.
- text(命令)+data
仕様
コード例はF#で示します。
- ヘッダ+text(命令)+data
- ヘッダは最初の16バイト
aout = System.IO.File.ReadAllBytes "a.out" h = aout.[0..15]
- ヘッダを2バイトずつ区切って、2番目がtextのサイズ、3番目がデータのサイズ。リトルエンディアン。
- 今回のサンプル:
tsize = 0x0010, dsize = 0x0006
tsize = (int h.[2]) ||| ((int h.[3]) <<< 8) dsize = (int h.[4]) ||| ((int h.[5]) <<< 8)
- ヘッダの後にtextがあり、その後にdataがある。連続した領域なので一気にメモリに読み込む。
- 「メモリに読み込み」はmemをダンプしたもの
mem = aout.[16..16+tsize+dsize]
課題 1
※ 制限時間内に作成できない場合、解答例に進んでください。
【30分】バイナリダンプするプログラムを作ってください。(hd a.out 相当)
- ファイルそのまま
- メモリに読み込んだもの
解答例
1-1.fsx
let aout = System.IO.File.ReadAllBytes "../../a.out" for i in 0 .. 0x10 .. aout.Length - 1 do printf "%04x " i for j in 0 .. 0xf do if i + j < aout.Length then printf "%02x " aout.[i + j] else printf " " for j in 0 .. 0xf do if i + j < aout.Length then let ch = int aout.[i + j] if 0x20 <= ch && ch <= 0x7e then printf "%c" (char ch) else printf "." printfn ""
1-2.fsx
let aout = (System.IO.File.ReadAllBytes "../../a.out").[16..] // 以下 1-1.fsx と同じ
課題 2
※ 制限時間内に作成できない場合、解答例に進んでください。
- 【1時間】逆アセンブラを作ってください。(7run -d a.out 相当)
- 【1時間】バイナリを実行してください。(7run a.out 相当)
- 以後、ここで作ったものをインタプリタと呼びます。
解答例
2-1.fsx
let aout = System.IO.File.ReadAllBytes "../../a.out" let read16 (a:byte[]) b = (int a.[b]) ||| ((int a.[b + 1]) <<< 8) let tsize = read16 aout 2 let dsize = read16 aout 4 let mem = aout.[16 .. 16 + tsize + dsize - 1] let mutable ip = 0 let show len dis = printf "%04x: " ip for j in 0 .. 5 do if j < len then printf "%02x" mem.[ip + j] else printf " " printfn "%s" dis ip <- ip + len while ip < tsize do match int mem.[ip] with | 0xb8 -> let n = read16 mem (ip + 1) show 3 (sprintf "mov ax, %04x" n) | 0xcd -> let n = int mem.[ip + 1] show 2 (sprintf "int %x" n) if n = 7 then match int mem.[ip] with | 1 -> show 1 "; sys exit" | 4 -> show 1 "; sys write" show 2 "; arg" show 2 "; arg" | _ -> show 1 "; ???" | _ -> show 1 "???"
2-2.fsx
let aout = System.IO.File.ReadAllBytes "../../a.out" let read16 (a:byte[]) b = (int a.[b]) ||| ((int a.[b + 1]) <<< 8) let tsize = read16 aout 2 let dsize = read16 aout 4 let mem = aout.[16 .. 16 + tsize + dsize - 1] let mutable ax, ip = 0, 0 while ip < tsize do match int mem.[ip] with | 0xb8 -> ax <- read16 mem (ip + 1) ip <- ip + 3 | 0xcd -> let n = int mem.[ip + 1] if n <> 7 then printfn "%04x: ??? int %x" ip n exit 1 match int mem.[ip + 2] with | 1 -> exit ax | 4 -> let arg1 = read16 mem (ip + 3) let arg2 = read16 mem (ip + 5) let bytes = mem.[arg1 .. arg1 + arg2 - 1] for b in bytes do printf "%c" (char b) ip <- ip + 7 | sc -> printfn "%04x: ??? syscall %d" ip sc exit 1 | b -> printfn "%04x: %02x ???" ip b exit 1
Updated