// samples/kprobes/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 */staticstructkprobekp={.symbol_name="_do_fork",};/* kprobe pre_handler: called just before the probed instruction is executed */staticinthandler_pre(structkprobe*p,structpt_regs*regs){#ifdef CONFIG_X86
printk(KERN_INFO"pre_handler: p->addr = 0x%p, ip = %lx,"" flags = 0x%lx\n",p->addr,regs->ip,regs->flags);#endif
#ifdef CONFIG_PPC
printk(KERN_INFO"pre_handler: p->addr = 0x%p, nip = 0x%lx,"" msr = 0x%lx\n",p->addr,regs->nip,regs->msr);#endif
#ifdef CONFIG_MIPS
printk(KERN_INFO"pre_handler: p->addr = 0x%p, epc = 0x%lx,"" status = 0x%lx\n",p->addr,regs->cp0_epc,regs->cp0_status);#endif
#ifdef CONFIG_TILEGX
printk(KERN_INFO"pre_handler: p->addr = 0x%p, pc = 0x%lx,"" ex1 = 0x%lx\n",p->addr,regs->pc,regs->ex1);#endif
/* A dump_stack() here will give a stack backtrace */return0;}/* kprobe post_handler: called after the probed instruction is executed */staticvoidhandler_post(structkprobe*p,structpt_regs*regs,unsignedlongflags){#ifdef CONFIG_X86
printk(KERN_INFO"post_handler: p->addr = 0x%p, flags = 0x%lx\n",p->addr,regs->flags);#endif
#ifdef CONFIG_PPC
printk(KERN_INFO"post_handler: p->addr = 0x%p, msr = 0x%lx\n",p->addr,regs->msr);#endif
#ifdef CONFIG_MIPS
printk(KERN_INFO"post_handler: p->addr = 0x%p, status = 0x%lx\n",p->addr,regs->cp0_status);#endif
#ifdef CONFIG_TILEGX
printk(KERN_INFO"post_handler: p->addr = 0x%p, ex1 = 0x%lx\n",p->addr,regs->ex1);#endif
}/*
* 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.
*/staticinthandler_fault(structkprobe*p,structpt_regs*regs,inttrapnr){printk(KERN_INFO"fault_handler: p->addr = 0x%p, trap #%dn",p->addr,trapnr);/* Return 0 because we don't handle the fault. */return0;}staticint__initkprobe_init(void){intret;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);returnret;}printk(KERN_INFO"Planted kprobe at %p\n",kp.addr);return0;}staticvoid__exitkprobe_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");// include/linux/kprobes.h
structkprobe{// 所有注册过的kprobe都会加入到kprobe_table哈希表中,hlist指向哈希表的位置
structhlist_nodehlist;/* list of kprobes for multi-handler support */structlist_headlist;/*count the number of times this probe was temporarily disarmed */unsignedlongnmissed;/* location of the probe point */kprobe_opcode_t*addr;/* Allow user to indicate symbol name of the probe point */// 地址和name不能同时出现,之前提过kprobe可以hook函数和地址
constchar*symbol_name;/* Offset into the symbol */unsignedintoffset;/* Called before addr is executed. */// 在单步执行原始指令前被调用
kprobe_pre_handler_tpre_handler;/* Called after addr is executed, unless... */// 在单步执行原始指令后被调用
kprobe_post_handler_tpost_handler;/* Saved opcode (which has been replaced with breakpoint) */kprobe_opcode_topcode;// 保存平台相关的被探测指令和下一条指令
/* copy of the original instruction */structarch_specific_insnainsn;/*
* Indicates various status flags.
* Protected by kprobe_mutex after this kprobe is registered.
*/u32flags;};
staticint__initinit_kprobes(void){inti,err=0;/* FIXME allocate the probe table, currently defined statically *//* initialize all list heads *///1. 初始化哈希表节点, 保存已注册的kprobe实例
for(i=0;i<KPROBE_TABLE_SIZE;i++){INIT_HLIST_HEAD(&kprobe_table[i]);......}......//2. 初始化kprobe黑名单(非__krpobe属性又不能被kprobe的函数)
if(kretprobe_blacklist_size){/* lookup the function address from its name */for(i=0;kretprobe_blacklist[i].name!=NULL;i++){kretprobe_blacklist[i].addr=kprobe_lookup_name(kretprobe_blacklist[i].name,0);.....}}......// 3. 架构相关的初始化,调用两个函数arm_kprobe_decode_init与register_undef_hook
err=arch_init_kprobes();if(!err)// 4. 注册die通知链
err=register_die_notifier(&kprobe_exceptions_nb);if(!err)// 5. 注册模块通知链
err=register_module_notifier(&kprobe_module_nb);kprobes_initialized=(err==0);if(!err)init_test_probes();returnerr;}// arch/arm/probes/kprobes/core.c
int__initarch_init_kprobes(){return0;}
staticstructnotifier_blockkprobe_exceptions_nb={.notifier_call=kprobe_exceptions_notify,.priority=0x7fffffff/* we need to be notified first */};int__kprobeskprobe_exceptions_notify(structnotifier_block*self,unsignedlongval,void*data){structdie_args*args=data;unsignedlongaddr=args->err;intret=NOTIFY_DONE;switch(val){caseDIE_IERR:if(arc_kprobe_handler(addr,args->regs))returnNOTIFY_STOP;break;caseDIE_TRAP:if(arc_post_kprobe_handler(addr,args->regs))returnNOTIFY_STOP;break;default:break;}returnret;}
staticstructnotifier_blockkprobe_module_nb={.notifier_call=kprobes_module_callback,.priority=0};/* Module notifier call back, checking kprobes on the module */staticintkprobes_module_callback(structnotifier_block*nb,unsignedlongval,void*data){structmodule*mod=data;structhlist_head*head;structkprobe*p;unsignedinti;intcheckcore=(val==MODULE_STATE_GOING);if(val!=MODULE_STATE_GOING&&val!=MODULE_STATE_LIVE)returnNOTIFY_DONE;/*
* When MODULE_STATE_GOING was notified, both of module .text and
* .init.text sections would be freed. When MODULE_STATE_LIVE was
* notified, only .init.text section would be freed. We need to
* disable kprobes which have been inserted in the sections.
*/mutex_lock(&kprobe_mutex);for(i=0;i<KPROBE_TABLE_SIZE;i++){head=&kprobe_table[i];hlist_for_each_entry_rcu(p,head,hlist)if(within_module_init((unsignedlong)p->addr,mod)||(checkcore&&within_module_core((unsignedlong)p->addr,mod))){/*
* The vaddr this probe is installed will soon
* be vfreed buy not synced to disk. Hence,
* disarming the breakpoint isn't needed.
*
* Note, this will also move any optimized probes
* that are pending to be removed from their
* corresponding lists to the freeing_list and
* will not be touched by the delayed
* kprobe_optimizer work handler.
*/kill_kprobe(p);}}mutex_unlock(&kprobe_mutex);returnNOTIFY_DONE;}
/* Check passed kprobe is valid and return kprobe in kprobe_table. */staticstructkprobe*__get_valid_kprobe(structkprobe*p){structkprobe*ap,*list_p;ap=get_kprobe(p->addr);if(unlikely(!ap))returnNULL;if(p!=ap){list_for_each_entry_rcu(list_p,&ap->list,list)if(list_p==p)/* kprobe p is a valid probe */gotovalid;returnNULL;}valid:returnap;}
el1_dbg:/*
* Debug exception handling
*/cmpx24,#ESR_ELx_EC_BRK64// if BRK64
cincx24,x24,eq// set bit '0'
tbzx24,#0,el1_inv// EL1 only
mrsx0,far_el1movx2,sp// struct pt_regs
bldo_debug_exceptionget_thread_infox20// top of stack
ldrw4,[x20,#TI_CPU_EXCP]subw4,w4,#0x1strw4,[x20,#TI_CPU_EXCP]kernel_exit1
asmlinkageint__exceptiondo_debug_exception(unsignedlongaddr,unsignedintesr,structpt_regs*regs){// 解析得到debug_fault_info的处理函数
conststructfault_info*inf=debug_fault_info+DBG_ESR_EVT(esr);unsignedlongpc=instruction_pointer(regs);structsiginfoinfo;intrv;/*
* Tell lockdep we disabled irqs in entry.S. Do nothing if they were
* already disabled to preserve the last enabled/disabled addresses.
*/if(interrupts_enabled(regs))trace_hardirqs_off();if(user_mode(regs)&&!is_ttbr0_addr(pc))arm64_apply_bp_hardening();// 函数调用
if(!inf->fn(addr,esr,regs)){rv=1;}else{pr_alert("Unhandled debug exception: %s (0x%08x) at 0x%016lx\n",inf->name,esr,addr);info.si_signo=inf->sig;info.si_errno=0;info.si_code=inf->code;info.si_addr=(void__user*)addr;arm64_notify_die("",regs,&info,0);rv=0;}if(interrupts_enabled(regs))trace_hardirqs_on();returnrv;}
// arch/arm64/kernel/debug-monitors.c
staticintbrk_handler(unsignedlongaddr,unsignedintesr,structpt_regs*regs){boolhandler_found=false;#ifdef CONFIG_KPROBES
if((esr&BRK64_ESR_MASK)==BRK64_ESR_KPROBES){if(kprobe_breakpoint_handler(regs,esr)==DBG_HOOK_HANDLED)handler_found=true;}#endif
// 调用断点hook
if(!handler_found&&call_break_hook(regs,esr)==DBG_HOOK_HANDLED)handler_found=true;if(!handler_found&&user_mode(regs)){send_user_sigtrap(TRAP_BRKPT);}elseif(!handler_found){pr_warn("Unexpected kernel BRK exception at EL1\n");return-EFAULT;}return0;}int__kprobeskprobe_breakpoint_handler(structpt_regs*regs,unsignedintesr){if(user_mode(regs))returnDBG_HOOK_ERROR;kprobe_handler(regs);returnDBG_HOOK_HANDLED;}// arch/arm64/kernel/probes/kprobes.c
staticvoid__kprobeskprobe_handler(structpt_regs*regs){structkprobe*p,*cur_kprobe;structkprobe_ctlblk*kcb;unsignedlongaddr=instruction_pointer(regs);kcb=get_kprobe_ctlblk();cur_kprobe=kprobe_running();// 获取kprobe实例
p=get_kprobe((kprobe_opcode_t*)addr);if(p){if(cur_kprobe){if(reenter_kprobe(p,regs,kcb))return;}else{/* Probe hit */set_current_kprobe(p);kcb->kprobe_status=KPROBE_HIT_ACTIVE;// 调用pre_handler
if(!p->pre_handler||!p->pre_handler(p,regs)){// 进入单步调试异常
setup_singlestep(p,regs,kcb,0);return;}}}elseif((le32_to_cpu(*(kprobe_opcode_t*)addr)==BRK64_OPCODE_KPROBES)&&cur_kprobe){/* We probably hit a jprobe. Call its break handler. */if(cur_kprobe->break_handler&&cur_kprobe->break_handler(cur_kprobe,regs)){setup_singlestep(cur_kprobe,regs,kcb,0);return;}}}staticvoid__kprobessetup_singlestep(structkprobe*p,structpt_regs*regs,structkprobe_ctlblk*kcb,intreenter){unsignedlongslot;if(reenter){save_previous_kprobe(kcb);set_current_kprobe(p);kcb->kprobe_status=KPROBE_REENTER;}else{kcb->kprobe_status=KPROBE_HIT_SS;}if(p->ainsn.api.insn){/* prepare for single stepping */slot=(unsignedlong)p->ainsn.api.insn;set_ss_context(kcb,slot);/* mark pending ss */spsr_set_debug_flag(regs,0);/* IRQs and single stepping do not mix well. */kprobes_save_local_irqflag(kcb,regs);// 设置单步调试状态
kernel_enable_single_step(regs);// 设置regs->pc 为opcode,这样从BRK exception退出后就会执行opcode
instruction_pointer_set(regs,slot);}else{/* insn simulation */arch_simulate_insn(p,regs);}}
// arch/arm64/kernel/debug-monitors.c
staticintsingle_step_handler(unsignedlongaddr,unsignedintesr,structpt_regs*regs){boolhandler_found=false;/*
* If we are stepping a pending breakpoint, call the hw_breakpoint
* handler first.
*/if(!reinstall_suspended_bps(regs))return0;#ifdef CONFIG_KPROBES
if(kprobe_single_step_handler(regs,esr)==DBG_HOOK_HANDLED)handler_found=true;#endif
if(!handler_found&&call_step_hook(regs,esr)==DBG_HOOK_HANDLED)handler_found=true;if(!handler_found&&user_mode(regs)){send_user_sigtrap(TRAP_TRACE);/*
* ptrace will disable single step unless explicitly
* asked to re-enable it. For other clients, it makes
* sense to leave it enabled (i.e. rewind the controls
* to the active-not-pending state).
*/user_rewind_single_step(current);}elseif(!handler_found){pr_warn("Unexpected kernel single-step exception at EL1\n");/*
* Re-enable stepping since we know that we will be
* returning to regs.
*/set_regs_spsr_ss(regs);}return0;}int__kprobeskprobe_single_step_handler(structpt_regs*regs,unsignedintesr){structkprobe_ctlblk*kcb=get_kprobe_ctlblk();intretval;if(user_mode(regs))returnDBG_HOOK_ERROR;/* return error if this is not our step */retval=kprobe_ss_hit(kcb,instruction_pointer(regs));if(retval==DBG_HOOK_HANDLED){kprobes_restore_local_irqflag(kcb,regs);kernel_disable_single_step();post_kprobe_handler(kcb,regs);}returnretval;}staticvoid__kprobespost_kprobe_handler(structkprobe_ctlblk*kcb,structpt_regs*regs){structkprobe*cur=kprobe_running();if(!cur)return;/* return addr restore if non-branching insn */if(cur->ainsn.api.restore!=0)// 设置pc指令为opcode的下条指令
instruction_pointer_set(regs,cur->ainsn.api.restore);/* restore back original saved kprobe variables and continue */if(kcb->kprobe_status==KPROBE_REENTER){restore_previous_kprobe(kcb);return;}/* call post handler */kcb->kprobe_status=KPROBE_HIT_SSDONE;if(cur->post_handler){/* post_handler can hit breakpoint and single step
* again, so we enable D-flag for recursive exception.
*/// 执行post_handler
cur->post_handler(cur,regs,0);}reset_current_kprobe();}