gdb hacks - ハードウェアのデバッグ機能を使う (後編)

| | コメント(0) | トラックバック(0)

gdb hacks 第 6 回。前回は IA-32 で gdb がデバッグレジスタをどのように活用しているかを見ました。今回はデバッグレジスタをプロセスが自分自身で利用する方法について考えてみます。

IA-32 のデバッグレジスタ DR0-DR7 へのアクセスは、リアルモードまたは特権順位 0 のプロテクトモードでしか許されていません。Linux では ptrace システムコールを使ってカーネルに読み書きしてもらうことになります。

しかしながらプロセスは自分自身に対して ptrace することはできません。そこで、自分のデバッグレジスタにアクセスしたい場合は fork を使って子プロセスに ptrace をしてもらいます。

とりあえずデバッグレジスタに値を設定する関数を書いてみます (エラーチェックなどはいいかげんですので注意してください)。

#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void
set_debugregs(unsigned long *v)
{
	if (!fork()) {
		int i, *p = ((struct user *)0)->u_debugreg;
		pid_t ppid = getppid();
		ptrace(PTRACE_ATTACH, ppid, NULL, NULL);
		waitpid(ppid, NULL, 0);
		for (i = 0; i < 8; i++, p++, v++) {
			if (i == 4 || i == 5)
				continue;
			if (ptrace(PTRACE_POKEUSER, ppid, p, *v) < 0)
				fprintf(stderr,
					"ptrace failed: dr%d = %08lx\n", i, *v);
		}
		ptrace(PTRACE_DETACH, ppid, NULL, NULL);
		exit(0);
	}
	wait(NULL);
}

このように値をセットする関数はわりと簡単に書けますが、逆に値を読み出したい場合は、子プロセスが行った ptrace の結果を得るためになんらかのプロセス間通信を行う必要があるでしょう。

以下はテストプログラムです。

#include <signal.h>
#include <asm/ucontext.h>

#define TRY(x) do { fputs("Trying " #x "\n", stderr); x; } while (0)

static void
trap_handler(int n, siginfo_t *si, struct ucontext *uc)
{
	fprintf(stderr, "  Trapped at 0x%08lx\n", uc->uc_mcontext.eip);
	uc->uc_mcontext.eflags |= 1<<16; /* Set RF; helpless */
}

int tmp, data0, data1;
void func(void) {}

int
main(void)
{
	unsigned long regs[] = {
		(unsigned long)&data0,
		(unsigned long)&data1,
		(unsigned long)func,
		0, /* unused */
		0, /* reserved */
		0, /* reserved */
		0, /* cant read */
		0x00fd013f, /* Trap conditions
				0: write, 1: read/write, 2: exec, 3: unused */
	};

	struct sigaction sa = {
		.sa_sigaction = (void *)trap_handler,
		.sa_flags = SA_RESTART | SA_SIGINFO,
	};

	sigemptyset(&sa.sa_mask);
	sigaction(SIGTRAP, &sa, NULL);
	set_debugregs(regs);

	TRY(tmp = data0);
	TRY(tmp = data1);
	TRY(data0 = 1);
	TRY(data1 = 1);
	TRY(func());

	return 0;
}

上記コードをまとめたファイル debugregs.c を用意してあります。これをコンパイルして実行すると以下のようになります (無限ループになりますので ^C で止めてください)。

% gcc -g -Wall -O2 debugregs.c
% ./a.out
Trying tmp = data0
Trying tmp = data1
  Trapped at 0x080487f5
Trying data0 = 1
  Trapped at 0x0804882f
Trying data1 = 1
  Trapped at 0x0804885b
Trying func()
  Trapped at 0x080486f0
  Trapped at 0x080486f0
  Trapped at 0x080486f0
...

データアクセスをテストする最初の 4 つの TRY() は意図した通りに動作しているようです。命令実行をテストする最後の TRY() で無限ループになるのはなぜでしょうか。

これはシグナルハンドラから復帰後、デバッグ例外を起こした同じ命令から実行再開しようとするためです。本来ならシグナルハンドラでステータスレジスタ (DR6) を調べ、設定を調整してから実行再開すべきなのですが、手を抜いているのでこうなります。

また EFLAGS の RF というビットをセットしておくと、一回だけ命令実行によるデバッグ例外の発生を抑止するという機能があり、これを使えばデバッグレジスタを書き換えることなく実行継続できるのですが、残念ながら Linux ではこれを活用する手段がないようです。

さてこのようにデバッグレジスタを使えば、一般的なページ単位のメモリ保護機構には不可能な、微細なメモリスポットのアクセスも感知できることがわかりました。設定できるのは最大 4 ワードというごく少量のメモリではありますが、うまく使えばなにかの役には立つでしょう。

つづく。

参考文献:

カテゴリ

トラックバック(0)

このブログ記事を参照しているブログ一覧: gdb hacks - ハードウェアのデバッグ機能を使う (後編)

このブログ記事に対するトラックバックURL: http://www.keshi.org/mt/mt-tb.cgi/37

コメントする

このブログ記事について

このページは、yaegashiが2006年3月12日 17:23に書いたブログ記事です。

ひとつ前のブログ記事は「gdb hacks - ハードウェアのデバッグ機能を使う (前編)」です。

次のブログ記事は「mod_authz_path」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.0