kprobeの割込ハンドラ
Rev.1を表示中。最新版はこちら。
カーネル初期化時、early_trap_init()がコールされ、割り込み番号3のトラップのテーブルが設定される。int 3が発生すると、int3にジャンプし、そこからdo_int3がコールされる事になる。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以外で、新たな機能と、順次追加していくことが可能ということかな?