acidまめ知識

acidはacmeから使う

gdb豆知識」で、「gdbemacsから使うのが正しい姿」説が語られている。Plan9の場合、特別な連携がなされているわけじゃないけど、ここでは「acidをacmeから使うと便利」という話を書こうと思う。具体的には、スタックトレースすると、次のようにアドレスと一緒にソースのファイル名と行番号が表示される。

acid: stk()
pread()+0x7 /sys/src/libc/9syscall/pread.s:5
read(fd=0x4,buf=0x2f428,n=0x200)+0x2f /sys/src/libc/9sys/read.c:7
Read(fd=0x4,buf=0x2f428,cnt=0x200)+0x1f /sys/src/cmd/rc/plan9.c:500
emptybuf(f=0x2f418)+0x2a /sys/src/cmd/rc/io.c:250
rchr(b=0x2f418)+0x19 /sys/src/cmd/rc/io.c:71
getnext()+0x54 /sys/src/cmd/rc/lex.c:68
nextc()+0x10 /sys/src/cmd/rc/lex.c:35
yylex()+0x8 /sys/src/cmd/rc/lex.c:179
yylex1()+0x8 /sys/src/cmd/rc/syn.y:224
yyparse()+0xa7e /sys/src/cmd/rc/syn.y:316
Xrdcmds()+0x75 /sys/src/cmd/rc/exec.c:838
main(argc=0x1,argv=0x7fffefb4)+0x349 /sys/src/cmd/rc/exec.c:184
_main+0x31 /sys/src/libc/386/main9.s:16

ここでどれでも好きなファイル名を右クリックすれば、該当する箇所のソースコードが表示される。下のスクリーンショットacmeのウィンドウで左のカラムでacidを実行しており、右のカラムに対応するソースコードが表示されている。これだけのためでもacidをacmeから使う方が便利。逆に素で使う利点は思い浮かばない*1


コンパイル時のオプション

kenccには-gオプションなんてないので、気にする必要はない。問答無用でデバッグ情報が付いて来る。

acidの起動方法

acmeから起動する場合は、winコマンドでシェルを起動して*2、acidを実行する。

$ acid /bin/ls
/bin/ls:386 plan 9 executable

/sys/lib/acid/port
/sys/lib/acid/386

実行の開始

コマンドライン引数をprogargs変数に設定し、new()でプロセスを起動する。new()はmain関数の先頭で勝手に止まる。

acid: progargs="/"
acid: new()
3508350: system call	_main	SUBL	$0x48,SP
3508350: breakpoint	main+0x3	MOVL	$bin(SB),AX

ブレークポイントの設定

bpset()でブレークポイントを設定し、bpdel()で削除する。bptab()でブレークポイント一覧を表示する。

acid: bpset(ls)
acid: bptab()
	0x000012c7 ls  SUBL	$0x28,SP

実行の再開はcont()。ブレークポイントまで実行が進む。

acid: cont()
3508350: breakpoint	ls	SUBL	$0x28,SP

PCが指しているアドレスのソースコードを見たければsrc()。

acid: src(*PC)
/sys/src/cmd/ls.c:87
 82		output();
 83		exits(errs? "errors" : 0);
 84	}
 85	
 86	int
>87	ls(char *s, int multi)
 88	{
 89		int fd;
 90		long i, n;
 91		char *p;
 92		Dir *db;

regs()でレジスタの値がわかる

acid: regs()
PC	0x00001023 main+0x3  /sys/src/cmd/ls.c:53
SP	0x7fffef40 ECODE 0x80100429 EFLAG 0x00000202
CS	0x00000023 DS	 0x0000001b SS	0x0000001b
GS	0x0000001b FS	 0x0000001b ES	0x0000001b
TRAP	0x00000003 exception 3
AX	0x7fffefb4 BX	0x7fffbce5 CX	0x7fffcd6c DX	0x00000004
DI	0x00033130 SI	0x00000000 BP	0x7fffbc58

ところで、newやregsの後の括弧は何だ?関数っぽいぞと思った人はするどい。acidは単なるデバッガにとどまらない、言語処理系だとも言える。new()やregs()、スタックトレースのstk()など、多くの機能はacid本体に組み込まれているわけではなく、外部ライブラリ(スクリプト)として実現されている。この辺の話は「acid(1)」や「trussライブラリ」を参照されたい。

ちなみにPlan9では、レジスタはデータセグメントの特定のアドレスにマッピングされている。例えば、PC(プログラムカウンタ)は、0x38にマッピングされている。そのためgdbのようにレジスタを参照するときに$をプレフィックスする必要はなく、他のシンボルと同様に扱える(変数の参照も同様に$をプレフィックする必要がない。acidのシンタックスはシェルではなくCっぽい)。アドレスxに格納された値を参照するときは*xと書く。

acid: PC
0x00000038
acid: *PC
0x00001023

変数のフォーマット

もちろん変数のフォーマットを指定しても表示できる。例えば、関数mainの先頭の命令を調べることを考えてみよう。正確には*fmt(x, 'i')と書くのだけど、x\iがシンタックスシュガーになっているので、通常はこちらを使えばよい。

acid: main
0x00001020
acid: *fmt(main, 'i')
SUBL	$0x24,SP
acid: main\i
SUBL	$0x24,SP

あと使うのは、こんなところ?

acid: print("hex=", 65\x, " dec=", 65\d, " oct=", 65\o, " char=", 65\c)
hex=0x0041 dec=65 oct=000000000101 char=A

対応しているフォーマットは次の通り。

8-bit integer: c (char), C (char or decimal), b (unsigned hex)
16-bit integer: r (Rune), x (hex), d (decimal), u (unsigned), o (octal), q (signed octal)
32-bit integer: B (binary), X (hex), D (decimal), U (unsigned), O (octal), Q (signed octal)
64-bit integer: V (decimal), Y (unsigned hex), Z (unsigned)
address-sized integer (pointer): a or A (symbol), W (hex)
string: s (NUL-terminated chars), R (NUL-terminated Runes)
machine instruction: i (Plan 9 syntax), I (alternate syntax)
floating point: f, g (32-bit), F, G (64-bit), 3, 8 (80-bit)

スタックの調査

gdb(adbでもidbでもいいけど)だと、frameやup、downなんかを使ってスタックフレームを巻き戻り、変数の値を調べるけど、acidに同等の機能は用意されていない。

では、どうしているかというと、lstk()でスタック上のローカル変数の値を表示することができる。また、*func:varと書けばスタックフレームの最内func内の変数varの値を参照できる。これはデバッグスタイルに関わるので、かなり戸惑う。慣れの問題なのかなぁ?

acid: lstk()
ls(s=0xda95,multi=0x0)+0x0 /sys/src/cmd/ls.c:87
	db=0x4d
	fd=0x0
	n=0x51
main(argv=0x7fffefb8,argc=0x0)+0xb9 /sys/src/cmd/ls.c:79
	_argc=0x0
	_args=0x0
	i=0x0
_main+0x31 /sys/src/libc/386/main9.s:16

終了

終了はdeleteじゃなくてctrl-d。これを忘れるとシェルに戻れなくなって困る。

(おまけ)使ったことのないコマンド

普段使うコマンドは決まっていて、大半のコマンドは存在すら知らない。

*1:ちなみにgdbの場合、(Emacsから実行しなくても)ctrl-a/Aで該当部分のソースコードを参照できることを知っておくと役に立つかもしれない。

*2:ウィンドウのどこでもいいのでwinと打ち込んで、そこを中央クリック。