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
The three types of buffering available are unbuffered, block buffered, and line buffered. When an output stream is unbuffered, information appears on the destination file or terminal as soon as written; when it is block buffered many characters are saved up and written as a block; when it is line buffered characters are saved up until a newline is output or input is read from any stream attached to a terminal device (typically stdin). The function fflush(3) may be used to force the block out early. (See fclose(3).) Normally all files are block buffered. When the first I/O operation occurs on a file, malloc(3) is called, and a buffer is obtained. If a stream refers to a terminal (as stdout normally does) it is line buffered. The standard error
stream stderr is always unbuffered by default.
这个函数应该必须在如何输出被写到该文件之前调用。一般放在main里靠前面的语句!但是setbuf有个经典的错误,man手册上也提到了,c陷阱和缺陷上也提到了
You must make sure that both buf and the space it points to still exist by the time stream is closed, which also happens at program termination. For example, the following is illegal:
The function fflush forces a write of all user-space buffered data for the given output or update stream via the stream underlying write function. The open status of the stream is unaffected. If the stream argument is NULL, fflush flushes all open output streams.
但是fflush仅仅刷新C库里的缓冲。其他的一些数据的刷新需要调用fsync或者sync!
Note that fflush() only flushes the user space buffers provided by the C library. To ensure that the data is physically stored on disk the kernel buffers must be flushed too, e.g. with sync(2) or fsync(2).
fsync()和sync()
fsync和sync最终将缓冲的数据更新到文件里。
12
#include <unistd.h>
int fsync(int fd);
fsync copies all in-core parts of a file to disk, and waits until the device reports that all parts are on stable storage. It also updates metadata stat information. It does not necessarily ensure that the entry in the directory containing the file has also reached disk. For that an explicit fsync on the file descriptor of the directory is also needed.