안녕하세요~ 이우영 입니다.

 

요즘 비가 많이 와서 우중충 하지만 열심히 합시다!

 

오늘은 kprobes 에 대해서 조금더 자세하게 알아 보도록 하겠습니다.

 

include/linux/kprobes.h 에 보면


struct kprobe {

struct hlist_node  hlist;

struct list_head list;

unsigned int  mod_refcubted;

unsigned long  nmissed;

kprobe_opcode_t  *addr;

char *symbol_name;

unsigned int  offset;

kprobe_pre_handler_t  pre_handler;

kprobe_post_handler_t  post_handler;

kprobe_fault_handler_t  fault_handler;

kprobe_break_handler_t  break_handler;

kprobe_opcode_t  opcode;

struct arch_specific_insn  ainsn;

};


kprobe 구조체에 필요한 정보들을 입력하고 register_kprobe 나 register_jprobe 로 등록하면 동작하게 됩니다.


전에 위치를 지정하는 방법이 2가지 있다고 설명을 했습니다.


kprobe_opcode_t  *addr;

char *symbol_name;


위 두 변수가 2가지 방법입니다.


addr에는 커널함수의 주소를 직접 입력해서 사용하고


symbol_name 는 커널 함수의 이름을 등록해서 사용합니다.


꼭 2가지 중 한가지 변수만을 사용해야 합니다.


나머지 부분은 예제를 보면서 간단하게 설명하겠습니다.


커널에 sample/kprobes 에 보면 kprobe,jprobe 의 예제가 들어있습니다.


예제는 x86 과 powerpc 에 맞게 만들어져 있습니다. 

보기 편하게 일단 x86에 관해서만 보겠습니다.


kprobe_example.c


#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/kprobes.h>


/* For each probe you need to allocate a kprobe structure */

static struct kprobe kp = {

.symbol_name = "do_fork",

};


/* kprobe pre_handler: called just before the probed instruction is executed */

static int handler_pre(struct kprobe *p, struct pt_regs *regs)

{

printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx,"

" flags = 0x%lx\n",

p->addr, regs->ip, regs->flags);


/* A dump_stack() here will give a stack backtrace */

return 0;

}


/* kprobe post_handler: called after the probed instruction is executed */

static void handler_post(struct kprobe *p, struct pt_regs *regs,

unsigned long flags)

{

printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n",

p->addr, regs->flags);


}


/*

 * fault_handler: this is called if an exception is generated for any

 * instruction within the pre- or post-handler, or when Kprobes

 * single-steps the probed instruction.

 */

static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)

{

printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn",

p->addr, trapnr);

/* Return 0 because we don't handle the fault. */

return 0;

}


static int __init kprobe_init(void)

{

int ret;

kp.pre_handler = handler_pre;

kp.post_handler = handler_post;

kp.fault_handler = handler_fault;


ret = register_kprobe(&kp);

if (ret < 0) {

printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);

return ret;

}

printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);

return 0;

}


static void __exit kprobe_exit(void)

{

unregister_kprobe(&kp);

printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);

}


module_init(kprobe_init)

module_exit(kprobe_exit)

MODULE_LICENSE("GPL");


차근차근 확인해 보도록 하겠습니다.


우선 kprobe 구조체를 만들어 주었습니다.


static struct kprobe kp = {

.symbol_name = "do_fork",

};


그리고 커널 함수를 등록해줍니다. 여기서는 symbol_name을 이용해서 등록을 해주내요


다음으로 모듈을 등록 해주는 kprobe_init() 를 보면 


3가지 핸들러를 입력해 줍니다.


pre, post, fault 단어를 보시면 알겠지만


do_fork 함수가 호출 되기 전, 후, 예외 발생 시 핸들러 가 호출 됩니다.


이 핸들러들을 통해서 함수가 호출 되기 전,후 변화등을 살펴 볼 수 있겠죠?


하지만 do_fork 함수가 받은 인자 값들에 관해서는 알수가 없습니다.


그래서 jprobe를 사용합니다.


저번 시간에 실습과 같이 jprobe는 지정한 함수와 같은 인자 값을 받는 함수를 만들어


바꿔치기 해버립니다. 그럼 받은 인자들을 우리가 원하는데로 볼 수 있습니다.


사용하기에 따라서 여러가지를 할 수 있습니다.


struct jprobe {

struct kprobe kp;

kprobe_opcode_t *entry;

};


jprobe는 kprobe 구조체와 entry 변수를 가지고 있습니다.


entry에 우리가 만든 함수를 등록해주면 do_fork 함수가 호출될 때 먼저 실행되고 do_fork 함수가 실행 하게 됩니다.


참 사용하기 쉽게 만들어 두었죠? 


예제 코드를 확인해 보시면 조금더 이해가 쉬워 질꺼 같아 간단하게 글을 써봤습니다.


쓰다보니 별로 쉬워진거 같진 않내요 ㅎㅎ.;


조금더 자세하게 알고 싶으신 분은 


http://www.chunghwan.com/tag/jprobes/?lang=ko 에 가보시면


그림과 함께 자세하게 설명이 되어있습니다.


오늘은 그림 한장 없어서 지겨운 글이 되었을꺼 같내요(준비가 부족해서..)


다음에는 조금 더 성실한 글을 써보도록 하겠습니다.


그럼 다음에 또 만나요~