kprobeの割込ハンドラ
カーネル初期化時、early_trap_init()がコールされ、割り込み番号3のトラップのテーブルが設定される。int 3が発生すると、int3にジャンプし、そこからdo_int3がコールされる事になる。
int 3の処理は、die_chainのリスト管理としているため、int 3での割り込みを、kprobe以外で、新たな機能と、順次追加していくことが可能ということかな?そんな大それた事、億にひとつもありませんけど。
void __init early_trap_init(void)
{
set_intr_gate_ist(1, &debug, DEBUG_STACK);
set_system_intr_gate_ist(3, &int3, DEBUG_STACK);
set_intr_gate(14, &page_fault);
load_idt(&idt_descr);
}
ENTRY(int3)
RING0_INT_FRAME
pushl_cfi $-1 # mark this as an int
SAVE_ALL
TRACE_IRQS_OFF
xorl %edx,%edx # zero error code
movl %esp,%eax # pt_regs pointer
call do_int3
jmp ret_from_exception
CFI_ENDPROC
END(int3)
do_int3のnotify_die関数がkprobeの処理となる。以降は、デバッガーのようなユーザプロセスでint 3が発生した場合、そのプロセスにSIGTRAPを送信させる処理となる。
dotraplinkage void __kprobes do_int3(struct pt_regs *regs, long error_code)
{
:
if (notify_die(DIE_INT3, "int3", regs, error_code, 3, SIGTRAP)
== NOTIFY_STOP)
return;
debug_stack_usage_inc();
preempt_conditional_sti(regs);
do_trap(3, SIGTRAP, "int3", regs, error_code, NULL);
preempt_conditional_cli(regs);
debug_stack_usage_dec();
}
notify_dieからatomic_notifier_call_chainがコールされ、最終的にnotifier_call_chainがコールされる。そこでdie_chainに登録されている、notifier_blockのコールバックを順次コールする。
int notrace __kprobes notify_die(enum die_val val, const char *str,
struct pt_regs *regs, long err, int trap, int sig)
{
struct die_args args = {
.regs = regs,
.str = str,
.err = err,
.trapnr = trap,
.signr = sig,
};
return atomic_notifier_call_chain(&die_chain, val, &args);
}
notifier_call_chainの中でkprobeの処理が行われる。kprobeをリストしているstruct notifier_block **nlから、順にそのnb->notifier_callコールバック関数を呼び出すことで、kprobeのpre/postのハンドラをコールすることになる。
static int __kprobes notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
nb = rcu_dereference_raw(*nl);
while (nb && nr_to_call) {
next_nb = rcu_dereference_raw(nb->next);
#ifdef CONFIG_DEBUG_NOTIFIERS
if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
WARN(1, "Invalid notifier called!");
nb = next_nb;
continue;
}
#endif
ret = nb->notifier_call(nb, val, v);
if (nr_calls)
(*nr_calls)++;
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
kprobeモジュールの初期化の処理。register_die_notifierでkprobeの、割り込みが発生した時の情報を登録する。この時のハンドラはkprobe_exceptions_notifyとなる。
static struct notifier_block kprobe_exceptions_nb = {
.notifier_call = kprobe_exceptions_notify,
.priority = 0x7fffffff /* we need to be notified first */
};
static int __init init_kprobes(void)
{
:
:
if (!err)
err = register_die_notifier(&kprobe_exceptions_nb);
if (!err)
err = register_module_notifier(&kprobe_module_nb);
kprobes_initialized = (err == 0);
if (!err)
init_test_probes();
return err;
}
割り込みが発生した時のハンドラを、die_chainにリストしていく。
int register_die_notifier(struct notifier_block *nb)
{
vmalloc_sync_all();
return atomic_notifier_chain_register(&die_chain, nb);
}
kprobe_exceptions_notifyが割り込みが発生した時に、最終的によばれる、kprobeの実たる処理となる。DIE_INT3がpre_handore、DIE_DEBUGでpost_handerとコールされる。
int __kprobes kprobe_exceptions_notify(struct notifier_block *self,
unsigned long val, void *data)
{
struct die_args *args = data;
int ret = NOTIFY_DONE;
if (args->regs && user_mode_vm(args->regs))
return ret;
switch (val) {
case DIE_INT3:
if (kprobe_handler(args->regs))
ret = NOTIFY_STOP;
break;
case DIE_DEBUG:
if (post_kprobe_handler(args->regs)) {
(*(unsigned long *)ERR_PTR(args->err)) &= ~DR_STEP;
ret = NOTIFY_STOP;
}
break;
case DIE_GPF:
if (!preemptible() && kprobe_running() &&
kprobe_fault_handler(args->regs, args->trapnr))
ret = NOTIFY_STOP;
break;
default:
break;
}
return ret;
}
処理の流れはこんな感じみたい。(嘘もあるかもしれません。)int 3の処理は、die_chainのリスト管理としているため、int 3での割り込みを、kprobe以外で、新たな機能と、順次追加していくことが可能ということかな?そんな大それた事、億にひとつもありませんけど。





