AcidはPlan 9またはPlan9portで使えるデバッガです。おそらくAcidの用途で最も多いのは、suicide*1したプロセスをアタッチして、stk()
やlstk()
を使って落ちた様子をみてCtl-dで抜ける、ような使い方だと思いますが、Acidはこれ自体がシェルとCの中間みたいなプログラミング言語になっていて、やれることは非常に多いです。が、そこまで書くのは大変なので、今回の記事ではブレークポイントで止めて値を眺めるまでの基本的な使い方を書きました。
使い方
acidコマンドの引数にデバッグしたいファイルを渡すと、まだプロセスが作られていない状態でデバッガが起動します。acid:というプロンプトが表示されるので、引数が必要であればprogargsにスペース区切りで設定してプロセスを作成しましょう。
% acid /bin/git
/bin/git: 386 plan 9 executable
/sys/lib/acid/port
/sys/lib/acid/386
acid: progargs = "stash save Debugging"
acid: new()
1415: system call _main SUBL $0xc,SP
1415: breakpoint main+0x3 CALL trace2_initialize_clock(SB)
acid: cont()
acidはプロセスIDを与えると直接そのプロセスをアタッチします。この場合は、プロセスはすでに動いているためnew()
でプロセスを作成する必要はありません。
% acid <プロセスID>
/bin/git: 386 plan 9 executable
/sys/lib/acid/port
/sys/lib/acid/386
acid: stk()
Acidの変数
変数はプログラムのシンボルテーブルで扱うものがそのまま参照できます*2。具体的に書いた方が分かりやすいと思うので、以下のプログラムを説明のために使います。
#include <u.h>
#include <libc.h>
typedef struct Addr Addr;
struct Addr {
char *host;
int port;
};
char *network = "tcp";
void
main(void)
{
Addr a;
a.host = "localhost";
a.port = 9000;
print("%s!%s!%d\n", network, a.host, a.port);
exits(nil);
}
Acidはシンボルテーブルを参照します。このため、デバッグ対象プログラムの変数を参照するには、ローカル変数の場合はfunc:name
のように関数名と変数名を:で繋げてアクセスします。グローバル変数ならname
のように変数名だけでアクセス可能です。
acid: network
0x00006010
ただし、Acidから参照した変数はそのアドレスを表します。Cで言うと(void*)&network
のような扱いです。単項*
演算子を使うと、networkが指しているメモリの値を取り出すことができます。ソースコード上では*network
はC文字列へのポインタですが、AcidではCの型情報が失われているので、ただの整数です。
acid: *network
0x00006850
Acidでは、Cの型とは別に変数のフォーマットが存在します。現在変数が持っているフォーマットはwhatisで調べたり、fmtで変更したりできます。そして*
演算子がメモリから取り出す値のサイズはAcidの変数が持つフォーマットに依存します。例えばs(文字列)なら\0
まで読み込みますし、D(4バイト整数)なら4バイト読みます。
それではnetwork変数が指している文字列を表示してみましょう。
acid: whatis network
integer variable format X
acid: x = fmt(*network, 's')
acid: x = *network\s
acid: whatis x
integer variable format s
acid: s = *x
string variable
acid: s
tcp
acid: *(*network\s)
tcp
フォーマットを変更すれば、整数や浮動小数なども読み取れます。*
と似たような@
演算子もあるようです。利用可能なすべてのフォーマットはAcid Manualを参照してください。
構造体などのメンバ変数
構造体などの場合、Acidは型情報を持っていないのでメンバ変数を名前で参照できません。もちろん変数の先頭アドレスからオフセット分だけ加算すればアクセス可能ですが、アラインメントなどを考慮すると非常に面倒です。8c -a
オプションを使うと、Acidで利用できる型を生成してくれるので、これを使うといいでしょう。他のコンパイラではDWARFとして埋め込まれているような情報も、Plan 9では人が読み書きできるようなテキストファイルとして分けて扱います。
% 8c -an main.c
-a
オプションが生成したファイルには、以下のようにCの構造体や共用体と同じ名前の型や関数が含まれます。
sizeofAddr = 8;
aggr Addr
{
'X' 0 host;
'D' 4 port;
};
defn
Addr(addr) {
complex Addr addr;
print(" host ", addr.host\X, "\n");
print(" port ", addr.port, "\n");
};
これらのファイルは、acid -l
オプションやinclude(string)
などで読み込んで使います。
% acid -l ./main.acid 8.out
acid: x = (Addr)main:a
acid: x
host 0x00006854
port 9000
acid: *(x.host)
localhost
acid: Addr(main:a)
host 0x00006854
port 9000
ブレークポイントの設定はbpset(address)
やbpdel(address)
関数で行います。acidはシンボルテーブルを参照するので、address引数には関数名をそのまま渡してもいいし、filepc(where)
関数でファイル名と行番号から明示的に与えることもできます。また、現在設定中のブレークポイントが知りたければbptab()
関数が使えます。
acid: bpset(strbuf_vinsertf)
acid: bpset(filepc("strbuf.c:262"))
acid: bpset(filepc("strbuf.c:274"))
acid: bptab()
0x00236588 strbuf_vinsertf SUBL $0x20,SP
0x002365ad strbuf_vinsertf+0x25 MOVL 0x8(BP),CX
0x002366b4 strbuf_vinsertf+0x12c MOVL 0x8(SI),CX
acid: cont()
ブレーク中の操作
ブレークポイントで停止すると、プロセスはStopped*3状態に遷移して、acidが入力を受け付けるようになります。
停止している場所を調べるためにソースコードの参照ができます。
acid: src(addr)
acid: line(addr)
acid: asm(addr)
PCレジスタに現在のプログラムカウンタが設定されているので、これを使うのが便利でしょう。
acid: src(*PC)
/tmp/x/git/strbuf.c:274
269 strbuf_grow(sb, len);
270 memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos);
271
272 save = sb->buf[pos + len];
273 len2 = vsnprintf(sb->buf + pos, len + 1, fmt, ap);
>274 sb->buf[pos + len] = save;
275 if (len2 != len)
276 BUG("your vsnprintf is broken (returns inconsistent lengths)");
277 strbuf_setlen(sb, sb->len + len);
278 }
279
演算もできるので現在位置から少し前も見られます。
acid: asm(*PC-10)
strbuf_vinsertf+0x122 0x002366aa MOVL sb+0x0(FP),SI
strbuf_vinsertf+0x126 0x002366ae MOVL len+0x1c(SP),BP
strbuf_vinsertf+0x12a 0x002366b2 MOVL AX,BX
strbuf_vinsertf+0x12c 0x002366b4 MOVL 0x8(SI),CX
strbuf_vinsertf+0x12d 0x002366b5 DECL SI
strbuf_vinsertf+0x12e 0x002366b6 ORB CL,0xa048dea(CX)
strbuf_vinsertf+0x134 0x002366bc ADDL pos+0x4(FP),AX
strbuf_vinsertf+0x138 0x002366c0 MOVBSX save+0x17(SP),CX
strbuf_vinsertf+0x13d 0x002366c5 MOVB CL,0x0(AX)
...
レジスタに保存されている値はregs()
などで表示できます。
acid: regs()
PC 0x002366b4 strbuf_vinsertf+0x12c /tmp/x/git/strbuf.c:274
SP 0xdfffe884 ECODE 0xf01006d6 EFLAG 0x00000692
CS 0x00000023 DS 0x0000001b SS 0x0000001b
GS 0x0000001b FS 0x0000001b ES 0x0000001b
TRAP 0x00000003 breakpoint
AX 0x0000000b BX 0x0000000b CX 0x0074571b DX 0x004ebd7c
DI 0xffffffff SI 0xdfffeabc BP 0x00000016
他に、汎用レジスタや浮動小数点レジスタだけ表示する関数もありますが、通常はregs()
があれば十分でしょう。
stk()
またはlstk()
関数でスタックトレースを取得できます。lstk()
はstk()
と似ていますが、ローカル変数の値も含めて出力されます。
ステップ
cont()
またはstep()
関数で処理を再開できます。
acid: step()
acid: cont()
マルチプロセス
デバッグ中のプログラムがfork(2)した場合、新しいプロセスはStopped状態になって停止します。そのプロセスをデバッグしたい場合は
% acid <新しいプロセスのPID>
でアタッチしてcont()
すればデバッグ可能ですし、処理を進めたいだけなら
% echo start >/proc/<新しいプロセスのPID>/ctl
で再開できます。プロセス操作の詳細はproc(3)を読んで下さい。
または、acid:プロンプトで待ち受けている状態であれば、以下の関数が使えるかもしれません。
acid: procs()
acid: setproc(pid)
acid: start(pid)
ライブラリ
acidに-l library
オプションを与えると/sys/lib/acid以下*4のライブラリを読み込みます。特にtrussは、実際に発行されたシステムコールを確認できるので便利です。
% acid -l truss /bin/git
acid: progargs = "xx"
acid: new()
acid: truss()
参考情報