月別アーカイブ: 2019年2月

[Linux] kprobes

kernel や module をデバッグしたいと思ったときにいろんな方法がありますが、真っ先に思いつくのが printk() でメッセージや観測値を表示することだと思います。しかしいちいちビルドし直さなければならないこと、ソースを改変する必要があること、などあまりいい方法ではありません。

Linux にはデバッグのための手段がたくさん用意されています。kernel 内蔵の kdb/kgdb、kernel 動作の追跡を行う ftrace、プロセス動作に手を入れる ptrace、syscall やシグナルを監視する strace、実行状況や実行回数を監視する perf など。これらはプログラム動作を外から監視するものですが、動作そのものに割り込む機能として kprobes があります。

kprobes は特定の場所に breakpoint 命令を設定し、実行時に命令をフックして予め用意した関数を実行することができます。特定の場所の設定と関数は module として用意することで、kernel が起動した後でも module をロードして設定することができます。

kprobes は kernel config の以下を有効にします。使い方を見るために下の2行にてサンプルも有効にしておきます。

CONFIG_KPROBES=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_KPROBES=m

サンプルは samples/kprobes/kprobe_sample.c にあります。make modules でビルドすると、samples/kprobes/kprobe_sample.ko ができあがるので、以下のように割り込みたい関数ラベルを指定してロードします。

# insmod kprobe_sample.ko symbol=_do_fork
Planted kprobe at 0xffff000080102050

このサンプルでは、struct kprobe の .symbol に関数ラベルの文字列、.pre_handler に割り込む箇所を実行する前に実行したい関数、.post_handler に割り込む箇所を実行した後に実行したい関数をそれぞれ設定しています。この関数に表示したい情報や実行したい操作を呼び出すことで任意の場所に割り込んで操作することができます。割り込む箇所が関数ラベルから離れている場合は .offset に関数ラベルからのオフセットを記載します。

しかしながら、kprobes では任意の箇所に割り込むため、この割り込ませる関数はレジスタレベルで情報を取得することになります。引数の struct pt_regs はアーキテクチャ依存のレジスタセットを表しています。

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
	pr_info("<%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx\n",
		p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
	...
	/* A dump_stack() here will give a stack backtrace */
	return 0;
}

struct kprobes を設定したら、register_kprobe() で登録、unregister_kprobe() で登録を解除するだけです。module の probe 関数に必要なだけ登録すれば、複数箇所に割り込むことができます。

レジスタレベルで情報を扱うため、c言語レベルのデバッグにはやや不向きなところもありますが、ビルドが不要でどこにでも割り込めるため強力なデバッグツールと言えます。