Wiki

Clone wiki

ikebin / pdp11 / hello

pdp11 > 1, 2, 2-1, 2-2, 2-3, 2-4, 2-5, 2-6, 2-7, 3, 4

CC0 この資料とサンプルコードはCC0(いわゆるパブリックドメイン)で提供します。


小さなバイナリを作って分析

  • バイナリの対象はPDP-11版UNIX V6
  • インタプリタ上で仮想的に実行

ソース

write.s

/ write(1, hello, 6);
mov $1, r0
sys write
hello
6

/ exit(0);
mov $0, r0
sys exit

.data
hello: <hello\n>
  • アセンブリ言語の文法
    • アセンブリ言語の文法はアセンブラによって方言がある
    • / はコメント
  • r0 はレジスタと呼ばれます。変数のようなものです。
  • PDP-11のアセンブリ言語
    • mov は代入: mov $0, r0r0 = 0
    • sys はシステムコール命令

システムコール

割り込みでカーネルを呼び出す。

  • sys の後にシステムコール名を書く: write, exit など
  • r0 は第一引数
    • write に渡す 1 は標準出力
  • sys の次の行には追加の引数を書く

UNIX V6のシステムコール定義: /usr/sys/ken/sysent.c

int sysent[]
{
    追加の引数の数, &処理関数, /* システムコール番号 = システムコール名 */
    ...
};
  • exit: システムコール番号 1, 追加の引数の数 0
  • write: システムコール番号 4, 追加の引数の数 2
  • sysent[] { の間にイコールがないのはC言語が古いため(pre K&R)

実行

$ v6as write.s
$ v6strip a.out
$ 7run a.out
hello

逆アセンブル

バイナリを分析

$ 7run -d a.out
0000: 15c0 0001       mov $1, r0
0004: 8904            sys 4 ; write
0006: 0010            ; arg
0008: 0006            ; arg
000a: 15c0 0000       mov $0, r0
000e: 8901            sys 1 ; exit
  • write.s →(アセンブル)→ a.out →(逆アセンブル)→ 上記結果
    • アセンブル: アセンブリ言語 → バイナリ
    • 逆アセンブル: バイナリ → アセンブリ言語
  • 逆とは言っても完全に元に戻るわけではない
    • 逆アセンブルでは対応するバイナリが確認できる
  • 逆アセンブラの出力はアセンブリ言語の文法とは異なる
    • アセンブリ言語は8進数
    • 逆アセンブラは16進数

バイナリダンプ

バイナリを16進数で出力

$ hd a.out
000000 07 01 10 00 06 00 00 00 00 00 00 00 00 00 01 00  >................<
000010 c0 15 01 00 04 89 10 00 06 00 c0 15 00 00 01 89  >................<
000020 68 65 6c 6c 6f 0a                                >hello.<
000026

メモリに読み込み

ヘッダはメモリに配置しないため、ファイルのオフセットとメモリのアドレスは16バイトずれる。

0000 c0 15 01 00 04 89 10 00 06 00 c0 15 00 00 01 89 ................
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. ファイルそのまま
  2. メモリに読み込んだもの

解答例

1-1.fsx

let aout = System.IO.File.ReadAllBytes "../../a.out"
for i in 0 .. 16 .. aout.Length - 1 do
    printf "%06x " i
    let sw = new System.IO.StringWriter()
    for j in 0 .. 15 do
        if i + j < aout.Length then
            let b = int aout.[i + j]
            printf "%02x " b
            let ch = if b < 32 || b > 127 then '.' else char b
            sw.Write ch
        else
            printf "   "
    let asc = sw.ToString()
    printfn "%s" asc

1-2.fsx

let aout = (System.IO.File.ReadAllBytes "../../a.out").[16..]
// 以下 1-1.fsx と同じ

課題 2

※ 制限時間内に作成できない場合、解答例に進んでください。

  1. 【1時間】逆アセンブラを作ってください。(7run -d a.out 相当)
  2. 【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 pc = 0
let show len dis =
    let words = [ for i in pc .. 2 .. pc + len - 1 -> sprintf "%04x" (read16 mem i) ]
    printfn "%04x: %-14s  %s" pc (String.concat " " words) dis
    pc <- pc + len
while pc < tsize do
    match read16 mem pc with
    | 0x15c0 ->
        let n = read16 mem (pc + 2)
        show 4 (sprintf "mov $%x, r0" n)
    | 0x8901 ->
        show 2 "sys 1 ; exit"
    | 0x8904 ->
        show 2 "sys 4 ; write"
        show 2 "; arg"
        show 2 "; arg"
    | _ ->
        show 2 "???"

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 r0, pc = 0, 0
while pc < tsize do
    match read16 mem pc with
    | 0x15c0 ->
        r0 <- read16 mem (pc + 2)
        pc <- pc + 4
    | 0x8901 ->
        exit r0
    | 0x8904 ->
        let arg1 = read16 mem (pc + 2)
        let arg2 = read16 mem (pc + 4)
        let bytes = mem.[arg1 .. arg1 + arg2 - 1]
        printf "%s" (System.Text.Encoding.ASCII.GetString bytes)
        pc <- pc + 6
    | w ->
        printfn "%04x: %04x ???" pc w
        exit 1

pdp11 > 1, 2, 2-1, 2-2, 2-3, 2-4, 2-5, 2-6, 2-7, 3, 4

Updated