Chinaunix首页 | 论坛 | 博客
  • 博客访问: 752094
  • 博文数量: 79
  • 博客积分: 2671
  • 博客等级: 少校
  • 技术积分: 1247
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-02 15:26
个人简介

宅男

文章分类

全部博文(79)

文章存档

2017年(11)

2016年(12)

2015年(6)

2012年(10)

2011年(33)

2010年(7)

分类: Android平台

2016-01-26 14:08:14

前段时间在调试一个ftrace function graph相关的bug,把这方面的代码仔细研究了一遍。关于ftrace是什么,不再赘述。
  1. 730ifdef CONFIG_FUNCTION_TRACER
  2. 731ifdef CONFIG_HAVE_FENTRY
  3. 732CC_USING_FENTRY    := $(call cc-option, -mfentry -DCC_USING_FENTRY)
  4. 733endif
  5. 734KBUILD_CFLAGS    += -pg $(CC_USING_FENTRY)
  6. 735KBUILD_AFLAGS    += $(CC_USING_FENTRY)
  7. 736ifdef CONFIG_DYNAMIC_FTRACE
  8. 737    ifdef CONFIG_HAVE_C_RECORDMCOUNT
  9. 738        BUILD_C_RECORDMCOUNT := y
  10. 739        export BUILD_C_RECORDMCOUNT
  11. 740    endif
  12. 741endif
  13. 742endif
编译的时候加入-pg选项。根据GCC文档的描述。
  1. -pg Generate extra code to write profile information suitable for the analysis program
  2. gprof. You must use this option when compiling the source files you want
  3. data about, and you must also use it when linking.

在同时打开Enable CONFIG_FUNCTION_TRACER和CONFIG_FUNCTION_GRAPH_TRACER。编译器会在每一个函数的入口插入一个ftrace_caller的函数。下面是内核函数cpu_down的反汇编代码

  1. ______________addr/line|code_____|label_______|mnemonic________________|comment
  2.                        |
  3.                        |
  4.                     369|
  5.    NSX:FFFFFFC000C03BE0|A9BD7BFD cpu_down: stp x29,x30,[SP,#-0x30]! ; x29,x30,[SP,#-48]!
  6.    NSX:FFFFFFC000C03BE4|910003FD mov x29,SP
  7.    NSX:FFFFFFC000C03BE8|F9000BF4 str x20,[SP,#0x10] ; x20,[SP,#16]
  8.                        |
  9.                        |
  10.                     369|
  11.    NSX:FFFFFFC000C03BEC|2A0003F4 mov w20,w0
  12.    NSX:FFFFFFC000C03BF0|AA1E03E0 mov x0,x30
  13.    NSX:FFFFFFC000C03BF4|97D22D53 bl 0xFFFFFFC00008F140 ; ftrace_caller
  14.                        |
  15.                        |
  16.                     372|
  17.    NSX:FFFFFFC000C03BF8|97D27F37 bl 0xFFFFFFC0000A38D4 ; cpu_maps_update_begin
  18.                        |
  19.                     374|
  20.    NSX:FFFFFFC000C03BFC|900051A0 adrp x0,0xFFFFFFC001637000
  21.    NSX:FFFFFFC000C03C00|B946D801 ldr w1,[x0,#0x6D8] ; w1,[x0,#1752]
  22.                     375|
  23.    NSX:FFFFFFC000C03C04|128001E0 movn w0,#0x0F ; w0,#15
  24.                        |
  25.                     374|
在了解ftrace_caller之前,有必要了解一下编译器的关于函数调用的一些规则。
ARM64的函数调用栈特点如下:堆栈静态开辟、堆栈低地址方向生长、非叶子函数入口处会开辟堆栈并保存返回地址、函数调用指令执行时LR寄存器会保存被调用函数的返回地址、函数栈内严格遵循load、store的方式存储及访问堆栈
http://blog.csdn.net/dxq136363/article/details/37993039
如果这段文字描述还不够形象的话,请参照arch/arm64/kernel/entry-ftrace.S的这段注释 
再次回到ftrace_caller函数中,在symbol ftrace_graph_call的地方原来是一条nop指令,当我们通过echo function_graph > /sys/kernel/debug/tracing/current_tracer来使能function_graph的时候,这条指令会被替换掉。
  1. #ifdef CONFIG_DYNAMIC_FTRACE
  2. /*
  3.  * Turn on/off the call to ftrace_graph_caller() in ftrace_caller()
  4.  * depending on @enable.
  5.  */
  6. static int ftrace_modify_graph_caller(bool enable)
  7. {
  8.         unsigned long pc = (unsigned long)&ftrace_graph_call;
  9.         u32 branch, nop;
  10.         
  11.         branch = aarch64_insn_gen_branch_imm(pc,
  12.                         (unsigned long)ftrace_graph_caller, false);
  13.         nop = aarch64_insn_gen_nop();

  14.         if (enable)
  15.                 return ftrace_modify_code(pc, nop, branch, true);
  16.         else
  17.                 return ftrace_modify_code(pc, branch, nop, true);
  18. }
这是一个跟架构相关的C代码,因为每种架构的指令会不同。当我们echo function_graph的时候,ftrace_modify_graph_caller会将这条nop指向替换成一条"b ftrace_graph_call"指令,注意这是不保存LR的无条件跳转。
btw。使能function_graph的时候需要disable 掉CONFIG_STRICT_MEMORY_RWX和“KERNEL_TEXT_RDONLY“这样,才会允许代码被动态修改。

  1. #ifdef CONFIG_FUNCTION_GRAPH_TRACER
  2. /*
  3.  * void ftrace_graph_caller(void)
  4.  *
  5.  * Called from _mcount() or ftrace_caller() when function_graph tracer is
  6.  * selected.
  7.  * This function w/ prepare_ftrace_return() fakes link register's value on
  8.  * the call stack in order to intercept instrumented function's return path
  9.  * and run return_to_handler() later on its exit.
  10.  */
  11. ENTRY(ftrace_graph_caller)
  12.         mcount_get_lr_addr x0 // pointer to function's saved lr
  13.         mcount_get_pc x1 // function's pc
  14.         mcount_get_parent_fp x2 // parent's fp
  15.         bl prepare_ftrace_return // prepare_ftrace_return(&lr, pc, fp)

  16.         mcount_exit
  17. ENDPROC(ftrace_graph_caller)

上图以cpu_down为例,简单的画出了栈中的FP及LR的关系图。
所以当调用prepare_ftrace_return函数式,其参数
X0 = address of LR in cpu_subsys_offline
X1 =  PC of cpu_down
X2 = fp in in cpu_subsys_offline

void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
  1.                            unsigned long frame_pointer)
  2. {
  3.         unsigned long return_hooker = (unsigned long)&return_to_handler;
  4.         unsigned long old;
  5.         struct ftrace_graph_ent trace;
  6.         int err;

  7.         if (unlikely(atomic_read(&current->tracing_graph_pause)))
  8.                 return;

  9.         /*
  10.          * Note:
  11.          * No protection against faulting at *parent, which may be seen
  12.          * on other archs. It's unlikely on AArch64.
  13.          */
  14.         old = *parent; //保存原始的LR值
  15.         *parent = return_hooker; //根据上面的分析,parent保存的是address of LR in cpu_subsys_offline.这边进行指针的赋值操作,修改LR。
  16.         
  17.         trace.func = self_addr;
  18.         trace.depth = current->curr_ret_stack + 1;

  19.         /* Only trace if the calling function expects to */
  20.         if (!ftrace_graph_entry(&trace)) {
  21.                 *parent = old;
  22.                 return;
  23.         }

  24.         err = ftrace_push_return_trace(old, self_addr, &trace.depth,
  25.                                        frame_pointer);//保存ftrace function graph的相关信息到task_struct结构体中,这些信息包含函数的调用关系以及时间戳
  26.         if (err == -EBUSY) {
  27.                 *parent = old;
  28.                 return;
  29.         }
  30. }


  1. /* Add a function return address to the trace stack on thread info.*/
    int
    ftrace_push_return_trace(unsigned long ret, unsigned long func, int *depth,
                             unsigned long frame_pointer)
    {
            unsigned long long calltime;
            int index;


            if (!current->ret_stack)
                    return -EBUSY;


            /*
             * We must make sure the ret_stack is tested before we read
             * anything else.
             */
            smp_rmb();


            /* The return trace stack is full */
            if (current->curr_ret_stack == FTRACE_RETFUNC_DEPTH - 1) {
                    atomic_inc(¤t->trace_overrun);
                    return -EBUSY;
            }


            calltime = trace_clock_local();


            index = ++current->curr_ret_stack;//return stack,基于stack的操作。即后入的先出。
            barrier();
            current->ret_stack[index].ret = ret;//返回LR
            current->ret_stack[index].func = func;
            current->ret_stack[index].calltime = calltime;
            current->ret_stack[index].subtime = 0;
            current->ret_stack[index].fp = frame_pointer;
            *depth = index;


            return 0;
    }
    prepare_ftrace_return这个函数,从名字上就可以看出,其主要作用是修改函数的返回地址。
当执行完prepare_ftrace_return 后,通过mcount_exit来退栈,返回到cpu_down函数中继续执行cpu_down的主体。
当CPU_down执行完成之后,通过ret返回时,并不是直接返回到调用cpu_down的cpu_sysfs_offline函数中去。而是跳转到了return_to_handler函数中去。
  1. ENTRY(return_to_handler)
  2.         str x0, [sp, #-16]! //注意此时cpu_down函数已经执行完毕,其返回值保存在X0中。
  3. 当然也会出现返回值是一个结构体的情况,比如 
  4.         mov x0, x29 // parent's fp
  5.         bl ftrace_return_to_handler// addr = ftrace_return_to_hander(fp);
  6.         mov x30, x0 // restore the original return address
  7.         ldr x0, [sp], #16
  8.         ret
  9. END(return_to_handler)
ftrace_return_to_handler同样是一个C函数。
  1. unsigned long ftrace_return_to_handler(unsigned long frame_pointer)
  2. {
  3.         struct ftrace_graph_ret trace;
  4.         unsigned long ret;

  5.         ftrace_pop_return_trace(&trace, &ret, frame_pointer);//注意这边的ret返回值,是原始的LR
  6.         trace.rettime = trace_clock_local();//记录函数退出时间。
  7.         barrier();
  8.         current->curr_ret_stack--;

  9.         /*
  10.          * The trace should run after decrementing the ret counter
  11.          * in case an interrupt were to come in. We don want to
  12.          * lose the interrupt if max_depth is set.
             */

  13.         ftrace_graph_return(&trace);
            
            if (unlikely(!ret)) {
                    ftrace_graph_stop();
                    WARN_ON(1);
                    /* Might as well panic. What else to do? */
                    ret = (unsigned long)panic;
            }


            return ret;

再次回到return_to_handler函数。
  1. ENTRY(return_to_handler)
  2.         str x0, [sp, #-16]!
  3.         mov x0, x29 // parent's fp
  4.         bl ftrace_return_to_handler// addr = ftrace_return_to_hander(fp);
  5.         mov x30, x0 // restore the original return address
  6. //此时X0中保存的是原始的LR。 这原始LR付给LR寄存器。这样ret返回时,饶了一圈又返回到cpu_sysfs_offline调用cpu_down的下一条指令了。
  7.         ldr x0, [sp], #16 //将压入的cpu_down的返回值退栈。
  8.         ret
  9. END(return_to_handler)
从整个流程上看,ftrace function graph通过动态修改指令,并修改函数调用时压入stack的LR。来做到对于函数的追踪。
相当于在函数的入口和return的时候,插入函数来记录函数调用关系及运行时间。其中涉及到了很多的汇编函数,读起来
比较拗口。
阅读(6870) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~