デバッガ上で実行されているか調べる(Plan9の場合)

Plan9では,UNIXのシグナルに相当するノート(note)って仕組みがある.基本的な考えはシグナル同じだけど,シグナル番号はなくて,文字列で識別する.分散環境を考えたら,結局文字列が一番ポータブルだったということか*1

ノートに対するハンドラは,atnotify(2)を使って設定する.第一引数でハンドラを指定し,第二引数が非0の場合はハンドラを登録,0の場合はシステムデフォルトに戻すことを指定する.そして,ハンドラでは,第二引数を調べることでどんなノートが発生したのか判定できる.例えば,DELETEでプログラムを止めた場合(SIGINTに相当)は,"Interrupt",kill(1)した場合は,"kill"という文字列が渡される.ハンドラが非0を返せば,プログラムは続行するが,0を返せば終了する.

#include <u.h>
#include <libc.h>

int 
handler(void *, char *msg)
{
	print("note: %s\n", msg);
	return 0;
}

void 
main(int argc, char **argv)
{
	atnotify(handler, 1);
	sleep(100 * 1000);
	exits(nil);
}

ノートを発行するには,postnote(2)を使う.UNIXのkillのように歴史的な理由に引きずられることなく,わかりやすい名前になっている.使用例として,Binary Hacks #92「Cのプログラムの中でブレークポイントを設定する」のデバッガ上で実行されているか調べるハックを真似てみる.

#include <u.h>
#include <libc.h>

static int in_acid = 1;

int 
handler(void *, char *msg)
{
	if (strncmp(msg, "sys: breakpoint", 15) == 0) {
		in_acid = 0;
		return 1;
	}
	return 0;
}

void 
main(int argc, char **argv)
{
	atnotify(handler, 1);
	postnote(PNPROC, getpid(), "sys: breakpoint");
	if (in_acid) {
		print("being debugged\n");
	} else {
		print("not being debugged\n");
	}
	exits(nil);
}

postnoteでは,getid(2)で自分のpidを取得して,自分宛に"sys: breakpoint"というノートを発行している.ちなみに,postnoteは,/proc//noteに"sys: breakpoint"をwriteするのと等価である*2
通常実行時は,ハンドラがブレークポイントを処理するけど,デバッガ上での実行時は,デバッガに取られてしまうので,ハンドラに処理が移らない.

cpu% chkdeb
not being debugged
cpu% 
cpu% acid chkdeb
chkdeb:386 plan 9 executable

/sys/lib/acid/port
/sys/lib/acid/386
acid: new()
7290795: system call	_main	SUBL	$0x48,SP
7290795: breakpoint	main+0x3	MOVL	$handler(SB),AX
acid: cont()
7290795: system call	pwrite+0x7	RET
acid: cont()
being debugged
<stdin>:4: (error) msg: pid=7290795 startstop: process exited
acid: 

*1:Text is the universal language

*2:killコマンドの挙動について,以前書いた.