acidまめ知識
acidはacmeから使う
「gdb豆知識」で、「gdbをemacsから使うのが正しい姿」説が語られている。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。
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。これを忘れるとシェルに戻れなくなって困る。
(おまけ)使ったことのないコマンド
普段使うコマンドは決まっていて、大半のコマンドは存在すら知らない。