池袋バイナリ勉強会(5)に参加しました

10月21日(日)に開催された池袋バイナリ勉強会(5)に参加しました。PDP-11の実行ファイル(a.out形式)の解析に取り組んでいます。

以下、取り組んだ内容のメモです(書かれている内容には間違いがあるかも知れません)。

前回までの復習

前回までの勉強会で学んだことの復習をしました。

write.sファイルからa.outを作成します。

mov $1, r0
sys write
hello
6

mov $0, r0
sys exit

.data
hello: <hello\n>

v6asでa.outが作られます(アセンブル、リンク)。v6runでa.outを実行します。実行するとhelloと出力されます。

$ v6as write.s
$ v6run a.out
hello

v6stripでシンボル情報を削除します。

$ v6strip a.out

pdp11-aout-objdump -d a.outで逆アセンブルします(v6stripでa.outに含まれるシンボル情報を削除しないと正しく逆アセンブルできません)。

~/etc/pdp-11 $ pdp11-aout-objdump -d a.out

a.out:     ファイル形式 a.out-pdp11


セクション .text の逆アセンブル:

00000000 <.text>:
   0:	15c0 0001      	mov	$1, r0
   4:	8904           	sys	4
   6:	0010           	.word	20
   8:	0006           	rtt
   a:	15c0 0000      	mov	$0, r0
   e:	8901           	sys	1
  • 定義されていない命令は、.word として表示されるようです。
  • .word 20の20は8進表記です。

アセンブル/実行を行うスクリプトの作成

先ほどのwrite.sから出力されたa.outに対して、pdp11-aout-objdumpを実行すると逆アセンブル結果が出力されますが、pdp11-aout-objdumpを使わずに、逆アセンブルを行うスクリプトの作成に取り組みました(write.sで使用している命令のみ)。

a.outをバイナリエディタで開いて、pdp11-aout-objdumpでの逆アセンブル結果と見比べます。

$ xxd a.out
0000000: 0701 1000 0600 0000 0000 0000 0000 0100  ................
0000010: c015 0100 0489 1000 0600 c015 0000 0189  ................
0000020: 6865 6c6c 6f0a                           hello.

a.outの先頭16バイトはヘッダになっています。ここにテキストサイズ、データサイズ、シンボル情報のサイズなどが書き込まれているようです。17バイト目から命令が始まっています。

アセンブルすることが出来たら、次はa.outを実行するスクリプト("hello"と出力するスクリプト)を作成しました。実際には、mov,sysの正しい振る舞いは理解していないので、それっぽく動作するスクリプトを書いただけです。

v6nmでシンボルテーブル出力

v6nmでシンボルテーブルが出力されます。

$ v6as write.s
$ v6nm a.out
000020d hello

v6stripでシンボルテーブルを削除してから、v6nmを再度実行すると、今度は「no name list」を表示されます。

$ v6strip a.out
$ v6nm a.out
no name list

strip前後のa.outを見比べてみます。

$ v6as write.s
$ xxd a.out
0000000: 0701 1000 0600 0000 0c00 0000 0000 0000  ................
0000010: c015 0100 0489 1000 0600 c015 0000 0189  ................
0000020: 6865 6c6c 6f0a 0000 0000 0000 0400 0000  hello...........
0000030: 0000 0000 0000 0000 0000 0000 6865 6c6c  ............hell
0000040: 6f00 0000 0300 1000                      o.......
$ v6strip a.out
$ xxd a.out
0000000: 0701 1000 0600 0000 0000 0000 0000 0100  ................
0000010: c015 0100 0489 1000 0600 c015 0000 0189  ................
0000020: 6865 6c6c 6f0a                           hello.                                   

strip前のa.outの9, 10バイト目には0c00とありますが、リトルエンディアンなのでひっくり返して000c(10進数で12)がシンボルテーブルのサイズとなります。strip後ではこの部分の値が0になっているのが確認出来ます。v6strip実行すると、a.outのファイルサイズが72バイトから38バイトになり、34バイト削られていますから、v6stripはシンボルテーブル以外にも削っている情報があるということでしょうか(よく分かっていません)。

ちなみに、オフセット0000020には、helloっぽいものがありますが、先ほどv6nmにて表示されたものがこれに対応しているような感じです。

v6arでlibc.aを展開

v6arでlibc.aを展開すると、.oファイルが出てきます。

$ ls
libc.a
$ v6ar x libc.a
$ ls
abort.o     dup.o       getpid.o    mcount.o    ptrace.o    stat.o
abs.o       errlst.o    getpw.o     mdate.o     putc.o      stime.o
alloc.o     execl.o     getuid.o    mknod.o     putchr.o    stty.o
atof.o      execv.o     gtty.o      mon.o       qsort.o     sync.o
atoi.o      exit.o      hmul.o      mount.o     read.o      time.o
cerror.o    ffltpr.o    kill.o      nargs.o     reset.o     times.o
chdir.o     fltpr.o     ladd.o      nice.o      rin.o       umount.o
chmod.o     fork.o      ldfps.o     nlist.o     sbrk.o      unlink.o
chown.o     fstat.o     libc.a      open.o      seek.o      wait.o
close.o     getc.o      link.o      perror.o    setgid.o    write.o
creat.o     getchr.o    locv.o      pipe.o      setuid.o
csv.o       getcsw.o    ltod.o      printf.o    signal.o
ctime.o     getgid.o    makdir.o    prof.o      sleep.o

v6cc -Sで*.s出力

v6cc -SでCプログラムからアセンブリコードを出力できます。

$ cat wr.c
main() {
  write(1, "hello\n", 6);
}
$ v6cc -S wr.c
$ cat wr.s
.globl	_main
.text
_main:
~~main:
jsr	r5,csv
mov	$6,(sp)
mov	$L2,-(sp)
mov	$1,-(sp)
jsr	pc,*$_write
cmp	(sp)+,(sp)+
L1:jmp	cret
.globl
.data
L2:.byte 150,145,154,154,157,12,0

write()の第1引数の整数

  • 0を渡すと、stdin
  • 1を渡すと、stdout
  • 2を渡すと、stderr
$ cat wr.c
main() {
  write(1, "hello\n", 6);
}
$ v6cc wr.c
$ v6run a.out
hello

a.outにはcrt0.sがそのまま含まれている

main() {}

test.cをコンパイルして逆アセンブルした結果を見ると、v6src/s4/crt0.sのコードがそのまま含まれているのが確認できます。

/ C runtime startoff

.globl	savr5
.globl	_exit

.globl	_main

start:
	setd
	mov	sp,r0
	mov	(r0),-(sp)
	tst	(r0)+
	mov	r0,2(sp)
	jsr	pc,_main
	mov	r0,(sp)
	jsr	pc,*$_exit
	sys	exit

.bss
savr5:	.=.+2

以下がa.outの逆アセンブル結果です。

$ pdp11-aout-objdump -d a.out

a.out:     ファイル形式 a.out-pdp11


セクション .text の逆アセンブル:

00000000 <.text>:
   0:	f009           	setd
   ↓↓↓↓↓↓ crt0.s ↓↓↓↓↓↓
   2:	1180           	mov	sp, r0
   4:	1226           	mov	(r0), -(sp)
   6:	0bd0           	tst	(r0)+
   8:	1036 0002      	mov	r0, 2(sp)
   c:	09f7 0008      	jsr	pc, 0x18
  10:	100e           	mov	r0, (sp)
  12:	09df 0020      	jsr	pc, *$40
  16:	8901           	sys	1
  ↑↑↑↑↑↑ crt0.s ↑↑↑↑↑↑
  18:	0977 000e      	jsr	r5, 0x2a
  1c:	0077 0018      	jmp	0x38
  20:	1166           	mov	r5, -(sp)
  22:	1185           	mov	sp, r5
  24:	1d40 0004      	mov	4(r5), r0
  28:	8901           	sys	1
  2a:	1140           	mov	r5, r0
  2c:	1185           	mov	sp, r5
  2e:	1126           	mov	r4, -(sp)
  30:	10e6           	mov	r3, -(sp)
  32:	10a6           	mov	r2, -(sp)
  34:	0be6           	tst	-(sp)
  36:	0048           	jmp	(r0)
  38:	1141           	mov	r5, r1
  3a:	1844           	mov	-(r1), r4
  3c:	1843           	mov	-(r1), r3
  3e:	1842           	mov	-(r1), r2
  40:	1146           	mov	r5, sp
  42:	1585           	mov	(sp)+, r5
  44:	0087           	rts	pc

その他、学んだこと

  • mov命令でのPC(プログラムカウンタ)について学びました(ここに書けるほどには理解していませんが……)。

  • 以下の2冊の本を紹介していただきました。

Linkers & Loaders

Linkers & Loaders

Lions’ Commentary on UNIX (Ascii books)

Lions’ Commentary on UNIX (Ascii books)

Lions’ Commentary on UNIX (Ascii books)』は、さっそく購入しました。