structkprobe {
/*用于保存kprobe的全局hash表,以被探测的addr为key*/
structhlist_node hlist;
/* list of kprobes for multi-handler support */
/*当对同一个探测点存在多个探测函数时,所有的函数挂在这条链上*/
structlist_head list;
/*count the number of times this probe was temporarily disarmed */
unsigned longnmissed;
/* location of the probe point */
/*被探测的目标地址*/
kprobe_opcode_t *addr;
/* Allow user to indicate symbol name of the probe point */
/*symblo_name的存在,允许用户指定函数名而非确定的地址*/
constchar*symbol_name;
/* Offset into the symbol */
/*如果被探测点为函数内部某个指令,需要使用addr + offset的方式*/
unsigned intoffset;
/* Called before addr is executed. */
/*探测函数,在目标探测点执行之前调用*/
kprobe_pre_handler_t pre_handler;
/* Called after addr is executed, unless... */
/*探测函数,在目标探测点执行之后调用*/
kprobe_post_handler_t post_handler;
/*
* ... called if executing addr causes a fault (eg. page fault).
* Return 1 if it handled fault, otherwise kernel will see it.
*/
kprobe_fault_handler_t fault_handler;
/*
* ... called if breakpoint trap occurs in probe handler.
* Return 1 if it handled break, otherwise kernel will see it.
*/
kprobe_break_handler_t break_handler;
/*opcode 以及 ainsn 用于保存被替换的指令码*/
/* Saved opcode (which has been replaced with breakpoint) */
kprobe_opcode_t opcode;
/* copy of the original instruction */
structarch_specific_insn ainsn;
/*
* Indicates various status flags.
* Protected by kprobe_mutex after this kprobe is registered.
*/
u32 flags;
};
staticint__init init_kprobes(void)
{
inti,err =0;
....
/*kprobe_blacklist中保存的是kprobe实现的关键代码路径,这些函数不应该被kprobe探测*/
/*
* Lookup and populate the kprobe_blacklist.
*
* Unlike the kretprobe blacklist, we'll need to determine
* the range of addresses that belong to the said functions,
* since a kprobe need not necessarily be at the beginning
* of a function.
*/
for(kb =kprobe_blacklist;kb->name!=NULL;kb++){
kprobe_lookup_name(kb->name,addr);
if(!addr)
continue;
kb->start_addr =(unsigned long)addr;
symbol_name =kallsyms_lookup(kb->start_addr,
&size,&offset,&modname,namebuf);
if(!symbol_name)
kb->range =0;
else
kb->range =size;
}
....
if(!err)
/*注册通知链到die_notifier,用于接收int 3的异常信息*/
err =register_die_notifier(&kprobe_exceptions_nb);
....
}
其中的通知链:
12345
staticstructnotifier_block kprobe_exceptions_nb ={
.notifier_call =kprobe_exceptions_notify,
/*优先级最高,保证最先执行*/
.priority =0x7fffffff /* we need to be notified first */
};
int__kprobes kprobe_exceptions_notify(structnotifier_block *self,
unsigned longval,void*data)
{
structdie_args *args =data;
intret =NOTIFY_DONE;
if(args->regs &&user_mode_vm(args->regs))
returnret;
switch(val){
caseDIE_INT3:
/*对于kprobe,进入kprobe_handle*/
if(kprobe_handler(args->regs))
ret =NOTIFY_STOP;
break;
caseDIE_DEBUG:
if(post_kprobe_handler(args->regs)){
/*
* Reset the BS bit in dr6 (pointed by args->err) to
* denote completion of processing
*/
(*(unsigned long*)ERR_PTR(args->err))&=~DR_STEP;
ret =NOTIFY_STOP;
}
break;
caseDIE_GPF:
/*
* To be potentially processing a kprobe fault and to
* trust the result from kprobe_running(), we have
* be non-preemptible.
*/
if(!preemptible()&&kprobe_running()&&
kprobe_fault_handler(args->regs,args->trapnr))
ret =NOTIFY_STOP;
break;
default:
break;
}
returnret;
}
staticint__kprobes kprobe_handler(structpt_regs *regs)
{
kprobe_opcode_t *addr;
structkprobe *p;
structkprobe_ctlblk *kcb;
/*对于int 3中断,其被Intel定义为Trap,那么异常发生时EIP寄存器内指向的为异常指令的后一条指令*/
addr =(kprobe_opcode_t *)(regs->ip -sizeof(kprobe_opcode_t));
/*
* We don't want to be preempted for the entire
* duration of kprobe processing. We conditionally
* re-enable preemption at the end of this function,
* and also in reenter_kprobe() and setup_singlestep().
*/
preempt_disable();
kcb =get_kprobe_ctlblk();
/*获取addr对应的kprobe*/
p =get_kprobe(addr);
if(p){
/*如果异常的进入是由kprobe导致,则进入reenter_kprobe(jprobe需要,到时候分析)*/
if(kprobe_running()){
if(reenter_kprobe(p,regs,kcb))
return1;
}else{
set_current_kprobe(p,regs,kcb);
kcb->kprobe_status =KPROBE_HIT_ACTIVE;
/*
* If we have no pre-handler or it returned 0, we
* continue with normal processing. If we have a
* pre-handler and it returned non-zero, it prepped
* for calling the break_handler below on re-entry
* for jprobe processing, so get out doing nothing
* more here.
*/
/*执行在此地址上挂载的pre_handle函数*/
if(!p->pre_handler ||!p->pre_handler(p,regs))
/*设置单步调试模式,为post_handle函数的执行做准备*/
setup_singlestep(p,regs,kcb,0);
return1;
}
}elseif(*addr !=BREAKPOINT_INSTRUCTION){
/*
* The breakpoint instruction was removed right
* after we hit it. Another cpu has removed
* either a probepoint or a debugger breakpoint
* at this address. In either case, no further
* handling of this interrupt is appropriate.
* Back up over the (now missing) int3 and run
* the original instruction.
*/
regs->ip =(unsigned long)addr;
preempt_enable_no_resched();
return1;
}elseif(kprobe_running()){
p =__this_cpu_read(current_kprobe);
if(p->break_handler &&p->break_handler(p,regs)){
setup_singlestep(p,regs,kcb,0);
return1;
}
}/* else: not a kprobe fault; let the kernel handle it */
preempt_enable_no_resched();
return0;
}
staticvoid__kprobes setup_singlestep(structkprobe *p,structpt_regs *regs,
structkprobe_ctlblk *kcb,intreenter)
{
if(setup_detour_execution(p,regs,reenter))
return;
#if!defined(CONFIG_PREEMPT)
if(p->ainsn.boostable ==1 &&!p->post_handler){
/* Boost up -- we can execute copied instructions directly */
if(!reenter)
reset_current_kprobe();
/*
* Reentering boosted probe doesn't reset current_kprobe,
* nor set current_kprobe, because it doesn't use single
* stepping.
*/
regs->ip =(unsigned long)p->ainsn.insn;
preempt_enable_no_resched();
return;
}
#endif
/*jprobe*/
if(reenter){
save_previous_kprobe(kcb);
set_current_kprobe(p,regs,kcb);
kcb->kprobe_status =KPROBE_REENTER;
}else
kcb->kprobe_status =KPROBE_HIT_SS;
/* Prepare real single stepping */
/*准备单步模式,设置EFLAGS的TF标志位,清楚IF标志位(禁止中断)*/
clear_btf();
regs->flags|=X86_EFLAGS_TF;
regs->flags&=~X86_EFLAGS_IF;
/* single step inline if the instruction is an int3 */
if(p->opcode ==BREAKPOINT_INSTRUCTION)
regs->ip =(unsigned long)p->addr;
else
/*设置异常返回的指令为保存的被探测点的指令*/
regs->ip =(unsigned long)p->ainsn.insn;
}
dotraplinkage void__kprobes do_debug(structpt_regs *regs,longerror_code)
{
....
/*在do_debug中,以DIE_DEBUG再一次触发kprobe的通知链*/
if(notify_die(DIE_DEBUG,"debug",regs,PTR_ERR(&dr6),error_code,
SIGTRAP)==NOTIFY_STOP)
return;
....
return;
}
/*对于kprobe_exceptions_notify,其DIE_DEBUG处理流程*/
caseDIE_DEBUG:
if(post_kprobe_handler(args->regs)){
/*
* Reset the BS bit in dr6 (pointed by args->err) to
* denote completion of processing
*/
(*(unsigned long*)ERR_PTR(args->err))&=~DR_STEP;
ret =NOTIFY_STOP;
}
break;
staticint__kprobes post_kprobe_handler(structpt_regs *regs)
{
structkprobe *cur =kprobe_running();
structkprobe_ctlblk *kcb =get_kprobe_ctlblk();
if(!cur)
return0;
/*设置异常返回的EIP为下一条需要执行的指令*/
resume_execution(cur,regs,kcb);
/*恢复异常执行前的EFLAGS*/
regs->flags|=kcb->kprobe_saved_flags;
/*执行post_handler函数*/
if((kcb->kprobe_status !=KPROBE_REENTER)&&cur->post_handler){
kcb->kprobe_status =KPROBE_HIT_SSDONE;
cur->post_handler(cur,regs,0);
}
/* Restore back the original saved kprobes variables and continue. */
if(kcb->kprobe_status ==KPROBE_REENTER){
restore_previous_kprobe(kcb);
gotoout;
}
reset_current_kprobe();
out:
preempt_enable_no_resched();
/*
* if somebody else is singlestepping across a probe point, flags
* will have TF set, in which case, continue the remaining processing
* of do_debug, as if this is not a probe hit.
*/
if(regs->flags&X86_EFLAGS_TF)
return0;
return1;
}
#include <stdio.h>
int func(int a, int b)
{
return a / b;
}
int main()
{
int x = 10;
int y = 0;
printf("%d / %d = %d\n", x, y, func(x, y));
return 0;
}
addr2line如何找到的这一行呢。在可执行程序中都包含有调试信息, 其中很重要的一份数据就是程序源程序的行号和编译后的机器代码之间的对应关系Line Number Table。DWARF格式的Line Number Table是一种高度压缩的数据,存储的是表格前后两行的差值,在解析调试信息时,需要按照规则在内存里重建Line Number Table才能使用。
Line Number Table存储在可执行程序的.debug_line域,使用命令
1
$ readelf -w test1
可以输出DWARF的调试信息,其中有两行
12
Special opcode 146: advance Address by 10 to 0x4004fe and Line by 1 to 5
Special opcode 160: advance Address by 11 to 0x400509 and Line by 1 to 6