jprobe


jprobeはkprobeで実装されており、その時の引数が継承して関数をフックする。以下はカーネル関数do_sys_openをフックし、新規作成でファイル名がaaaの時、ファイル名をhogehogeにする。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/fs.h>

MODULE_DESCRIPTION("jprobe test");
MODULE_AUTHOR("y.kitamura");
MODULE_LICENSE("GPL");

static long jprobe_do_sys_open(int dfd, char __user *filename, 
                                         int flags, umode_t mode)
{
       if (flags & O_CREAT && !strcmp(filename, "aaa")) {
               printk("create %s file to hogehoge\n", filename);
               strcpy(filename, "hogehoge");
       }
       jprobe_return();
       return 0;
}

static struct jprobe jprobe_entry = {
       .entry                  = jprobe_do_sys_open,
       .kp = {
               .symbol_name    = "do_sys_open",
       },
};

static int __init jprobe_init(void)
{
       int ret;

       ret = register_jprobe(&jprobe_entry);
       if (ret < 0) {
               return -1;
       }
       return 0;
}

static void __exit jprobe_exit(void)
{
       unregister_jprobe(&jprobe_entry);
}

module_init(jprobe_init)
module_exit(jprobe_exit)
実行結果
[root@localhost lkm]# insmod jprobe_test.ko
[root@localhost lkm]# echo "data to aaa" > aaa
[root@localhost lkm]# cat hogehoge
data to aaa
[root@localhost lkm]# dmesg
  :
[  826.516923] create aaa file to hogehoge

補足

検証時は、if (flags & O_CREAT && !strcmp(filename, "aaa"))で、条件しておかないと、システム下で随時do_sys_openが呼ばれて、システムが不安定になるみたい。

なお、jprobeのjはjointのjじゃないかな?

実装?

register_jprobe()は、num=1でregister_jprobes()をコールする。arch_deref_entry_point()はjp->entr(サンプルのjprobe_do_sys_open)のアドレスを取得する。それが関数のエントリであるかどうか、kallsyms_lookup_size_offset()でチェックする。サンプルではlkmで定義した関数を設定しているが、カーネル内にある関数を設定してもいいみたい。

で、struct kprobe->pre_handlerにsetjmp_pre_handleを、struct kprobe->brea_handlerにlongjmp_break_handlerを設定して、register_kprobe()をコールしている。

post_handlerでなく、brea_handlerに設定しているところがポイント。post_handlerはプロブしたアドレスが実行した後に、呼び出される。brea_handlerは、ブレイクしてpre_handlerが呼び出され、プロブしたアドレスに戻る前に実行する。(と思う。)
int __kprobes register_jprobes(struct jprobe **jps, int num)
{
       struct jprobe *jp;
       int ret = 0, i;

       if (num <= 0)
               return -EINVAL;
       for (i = 0; i < num; i++) {
               unsigned long addr, offset;
               jp = jps[i];
               addr = arch_deref_entry_point(jp->entry);

               if (kallsyms_lookup_size_offset(addr, NULL, &offset) &&
                   offset == 0) {
                       jp->kp.pre_handler = setjmp_pre_handler;
                       jp->kp.break_handler = longjmp_break_handler;
                       ret = register_kprobe(&jp->kp);
               } else
                       ret = -EINVAL;

               if (ret < 0) {
                       if (i > 0)
                               unregister_jprobes(jps, i);
                       break;
               }
       }
       return ret;
}
setjmp_pre_handler()はCPU変数struct kprobe_ctlblk *kcbに、ブレイクした時に各レジスタおよびスタック内容を保存し、次に実行するアドレスをregs->ipに返している。
int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
       struct jprobe *jp = container_of(p, struct jprobe, kp);
       unsigned long addr;
       struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();

       kcb->jprobe_saved_regs = *regs;
       kcb->jprobe_saved_sp = stack_addr(regs);
       addr = (unsigned long)(kcb->jprobe_saved_sp);

       memcpy(kcb->jprobes_stack, (kprobe_opcode_t *)addr,
              MIN_STACK_SIZE(addr));
       regs->flags &= ~X86_EFLAGS_IF;
       trace_hardirqs_off();
       regs->ip = (unsigned long)(jp->entry);
       return 1;
}
longjmp_break_handler()でsetjmp_pre_handler()で設定しれレジスタ/スタックダンプを復帰してるっぽい。
int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
{
       struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
       u8 *addr = (u8 *) (regs->ip - 1);
       struct jprobe *jp = container_of(p, struct jprobe, kp);

       if ((addr > (u8 *) jprobe_return) &&
           (addr < (u8 *) jprobe_return_end)) {
               if (stack_addr(regs) != kcb->jprobe_saved_sp) {
                       struct pt_regs *saved_regs = &kcb->jprobe_saved_regs;
                       printk(KERN_ERR
                              "current sp %p does not match saved sp %p\n",
                              stack_addr(regs), kcb->jprobe_saved_sp);
                       printk(KERN_ERR "Saved registers for jprobe %p\n", jp);
                       show_registers(saved_regs);
                       printk(KERN_ERR "Current registers\n");
                       show_registers(regs);
                       BUG();
               }
               *regs = kcb->jprobe_saved_regs;
               memcpy((kprobe_opcode_t *)(kcb->jprobe_saved_sp),
                      kcb->jprobes_stack,
                      MIN_STACK_SIZE(kcb->jprobe_saved_sp));
               preempt_enable_no_resched();
               return 1;
       }
       return 0;
}





最終更新 2012/07/18 23:01:29 - north
(2012/07/18 17:00:41 作成)


検索

アクセス数
3575643
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。