call_chain & dump_trace

调用链是剖析工具中常备的一种显示方式,可以为用户呈现明确的函数调用关系,在perf中,可以根据调用链分析主函数的sample分布到了哪些子函数中。在内核调试中,根据调用链可以得到出错函数的上层调用者是谁。

调用链的实现其实很简单,就是遍历函数栈,在x86中,bp寄存器指向的内存位置存放的是旧栈帧基地址,这个位置之上(高地址处)是函数返回地址,因此在函数返回时pop %ebp,就是把旧栈帧地址置放到bp寄存器中,然后pop %eip,会把函数返回地址放到程序计数器中。在进入一个新函数之前,call func,会把函数返回地址放到填入栈,并跳转到func,这时就进入了一个新栈帧,push %ebp,把函数调用者的栈帧基地址压栈,mov %esp, %ebp,这就让bp指向了新栈帧的底部,下面sp就可以动态变化改动栈空间大小了。

这么看来在新旧两个栈的边界处分别是指令返回地址和栈返回地址,旧栈底部是函数返回指令地址,新栈顶部是栈返回地址。因此在内核中定义栈帧:

[cpp]  view plain copy
  1. /* The form of the top of the frame on the stack */  
  2. struct stack_frame {  
  3.     struct stack_frame *next_frame;  
  4.     unsigned long return_address;  
  5. };  

由于栈的增长是由高到低,所以高地址就是函数指令返回地址return_address,低地址就是栈返回地址next_frameregs->bp指向当前栈的底部,也就是这个结构体的起始地址,如果从regs->bp处读取一个stack_frame结构体,那么就可以得到上下栈帧边界处信息,包括,函数返回指令地址以及下一个栈帧地址bp。

[cpp]  view plain copy
  1. static struct perf_callchain_entry *perf_callchain(struct pt_regs *regs)  
  2. {  
  3.     int rctx;  
  4.     struct perf_callchain_entry *entry;  
  5.   
  6.     entry = get_callchain_entry(&rctx);  
  7.     if (rctx == -1) return NULL;  
  8.     if (!entry) goto exit_put;  
  9.   
  10.     entry->nr = 0;  
  11.     if (!user_mode(regs)) {  
  12.         perf_callchain_store(entry, PERF_CONTEXT_KERNEL);  
  13.         perf_callchain_kernel(entry, regs);  
  14.         if (current->mm)  //有用户调用
  15.             regs = task_pt_regs(current);  
  16.         else  
  17.             regs = NULL;  
  18.     }  
  19.     if (regs) {  
  20.         perf_callchain_store(entry, PERF_CONTEXT_USER);  
  21.         perf_callchain_user(entry, regs);  
  22.     }  
  23. exit_put:  
  24.     put_callchain_entry(rctx);  
  25.     return entry;  
  26. }  

在perf_callchain()中,只需要一个pt_regs *参数,判定指令地址是否处于用户态user_mode(regs) => return !!(regs->cs & 3),对于内核态地址,要遍历内核态栈,perf_callchain_kernel()实际就是dump_trace(),x86_64有三种内核栈:process stackinterrupt stacksevere exception (double fault, nmi, stack fault, debug, mce) hardware stack

[cpp]  view plain copy
  1. /* 
  2.  * x86-64 can have up to three kernel stacks: 
  3.  * process stack 
  4.  * interrupt stack 
  5.  * severe exception (double fault, nmi, stack fault, debug, mce) hardware stack 
  6.  */  
  7. void dump_trace(struct task_struct *task, struct pt_regs *regs,  
  8.         unsigned long *stack, unsigned long bp,  
  9.         const struct stacktrace_ops *ops, void *data)  
  10. {  
  11.     const unsigned cpu = get_cpu();  
  12.     unsigned long *irq_stack_end = (unsigned long *)per_cpu(irq_stack_ptr, cpu);  
  13.     unsigned used = 0;  
  14.     struct thread_info *tinfo;  
  15.     int graph = 0;  
  16.     unsigned long dummy;  
  17.   
  18.     if (!task)  task = current;  
  19.   
  20.     if (!stack) {  
  21.         stack = &dummy;  
  22.         if (task && task != current)  
  23.             stack = (unsigned long *)task->thread.sp;  
  24.     }  
  25.   
  26.     if (!bp)        bp = stack_frame(task, regs);  
  27.     /* 
  28.      * Print function call entries in all stacks, starting at the 
  29.      * current stack address. If the stacks consist of nested exceptions 
  30.      */  
  31.     tinfo = task_thread_info(task);  
  32.     for (;;) {  
  33.         char *id;  
  34.         unsigned long *estack_end;  
  35.         estack_end = in_exception_stack(cpu, (unsigned long)stack, &used, &id);  
  36.   
  37.         if (estack_end) {  
  38.             if (ops->stack(data, id) < 0) break;  
  39.             bp = ops->walk_stack(tinfo, stack, bp, ops, data, estack_end, &graph);  
  40.             ops->stack(data, "<EOE>");  
  41.   
  42.             /* We link to the next stack via the second-to-last pointer (index -2 to end) in the exception stack: */  
  43.             stack = (unsigned long *) estack_end[-2];  
  44.             continue;  
  45.         }  
  46.         if (irq_stack_end) {  
  47.             unsigned long *irq_stack;  
  48.             irq_stack = irq_stack_end - (IRQ_STACK_SIZE - 64) / sizeof(*irq_stack);  
  49.   
  50.             if (in_irq_stack(stack, irq_stack, irq_stack_end)) {  
  51.                 if (ops->stack(data, "IRQ") < 0)  break;  
  52.                 bp = ops->walk_stack(tinfo, stack, bp, ops, data, irq_stack_end, &graph);  
  53.                 /* 
  54.                  * We link to the next stack (which would be the process 
  55.                  * stack normally) the last pointer (index -1 to end) in the IRQ stack: 
  56.                  */  
  57.                 stack = (unsigned long *) (irq_stack_end[-1]);  
  58.                 bp = fixup_bp_irq_link(bp, stack, irq_stack, irq_stack_end);  
  59.                 irq_stack_end = NULL;  
  60.                 ops->stack(data, "EOI");  
  61.                 continue;  
  62.             }  
  63.         }  
  64.         break;  
  65.     }  
  66.   
  67.     /* This handles the process stack: */  
  68.     bp = ops->walk_stack(tinfo, stack, bp, ops, data, NULL, &graph);  
  69.     put_cpu();  
  70. }  

其中ops->walk_stack()就是遍历内核栈操作,在perf_event中是调用print_context_stack_bp()实现的,这个递归调用直至跳出内核地址空间

[cpp]  view plain copy
  1. unsigned long  
  2. print_context_stack_bp(struct thread_info *tinfo,  
  3.                unsigned long *stack, unsigned long bp,  
  4.                const struct stacktrace_ops *ops, void *data,  
  5.                unsigned long *end, int *graph)  
  6. {  
  7.     struct stack_frame *frame = (struct stack_frame *)bp;  
  8.     unsigned long *ret_addr = &frame->return_address;  
  9.   
  10.     while (valid_stack_ptr(tinfo, ret_addr, sizeof(*ret_addr), end)) {  
  11.         unsigned long addr = *ret_addr;  
  12.   
  13.         if (!__kernel_text_address(addr))  
  14.             break;  
  15.   
  16.         ops->address(data, addr, 1);  
  17.         frame = frame->next_frame;  
  18.         ret_addr = &frame->return_address;  
  19.         print_ftrace_graph_addr(addr, data, ops, tinfo, graph);  
  20.     }  
  21.   
  22.     return (unsigned long)frame;  
  23. }  

在遍历完内核栈后,判断当前进程是否是内核线程(内核线程的task->mm == NULL),对于一般进程,通过系统调用或发生异常或中断进入内核栈,这时

[cpp]  view plain copy
  1. #define task_pt_regs(tsk)   ((struct pt_regs *)(tsk)->thread.sp0 - 1)  

然后递归打印打印用户态栈信息perf_callchain_user(),这里copy_from_user_nmi(&frame, fp, sizeof(frame));就从bp寄存器指向内存地址处拷贝一个stack_frame信息,据此递归用户态栈,直至fp < regs->sp???

[cpp]  view plain copy
  1. void  
  2. perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs)  
  3. {  
  4.     struct stack_frame frame;  
  5.     const void __user *fp;  
  6.   
  7.     fp = (void __user *)regs->bp;  
  8.     perf_callchain_store(entry, regs->ip);  
  9.   
  10.     while (entry->nr < PERF_MAX_STACK_DEPTH) {  
  11.         unsigned long bytes;  
  12.         frame.next_frame = NULL;  
  13.         frame.return_address = 0;  
  14.   
  15.         bytes = copy_from_user_nmi(&frame, fp, sizeof(frame));  
  16.         if (bytes != sizeof(frame))  
  17.             break;  
  18.   
  19.         if ((unsigned long)fp < regs->sp)  
  20.             break;  
  21.   
  22.         perf_callchain_store(entry, frame.return_address);  
  23.         fp = frame.next_frame;  
  24.     }  
  25. }  

 

你可能感兴趣的:(call_chain & dump_trace)