前段时间在调试一个ftrace function graph相关的bug,把这方面的代码仔细研究了一遍。关于ftrace是什么,不再赘述。
-
730ifdef CONFIG_FUNCTION_TRACER
-
731ifdef CONFIG_HAVE_FENTRY
-
732CC_USING_FENTRY := $(call cc-option, -mfentry -DCC_USING_FENTRY)
-
733endif
-
734KBUILD_CFLAGS += -pg $(CC_USING_FENTRY)
-
735KBUILD_AFLAGS += $(CC_USING_FENTRY)
-
736ifdef CONFIG_DYNAMIC_FTRACE
-
737 ifdef CONFIG_HAVE_C_RECORDMCOUNT
-
738 BUILD_C_RECORDMCOUNT := y
-
739 export BUILD_C_RECORDMCOUNT
-
740 endif
-
741endif
-
742endif
编译的时候加入-pg选项。根据GCC文档的描述。
-
-pg Generate extra code to write profile information suitable for the analysis program
-
gprof. You must use this option when compiling the source files you want
-
data about, and you must also use it when linking.
在同时打开Enable
CONFIG_FUNCTION_TRACER和CONFIG_FUNCTION_GRAPH_TRACER。编译器会在每一个函数的入口插入一个ftrace_caller的函数。下面是内核函数cpu_down的反汇编代码
-
______________addr/line|code_____|label_______|mnemonic________________|comment
-
|
-
|
-
369|
-
NSX:FFFFFFC000C03BE0|A9BD7BFD cpu_down: stp x29,x30,[SP,#-0x30]! ; x29,x30,[SP,#-48]!
-
NSX:FFFFFFC000C03BE4|910003FD mov x29,SP
-
NSX:FFFFFFC000C03BE8|F9000BF4 str x20,[SP,#0x10] ; x20,[SP,#16]
-
|
-
|
-
369|
-
NSX:FFFFFFC000C03BEC|2A0003F4 mov w20,w0
-
NSX:FFFFFFC000C03BF0|AA1E03E0 mov x0,x30
-
NSX:FFFFFFC000C03BF4|97D22D53 bl 0xFFFFFFC00008F140 ; ftrace_caller
-
|
-
|
-
372|
-
NSX:FFFFFFC000C03BF8|97D27F37 bl 0xFFFFFFC0000A38D4 ; cpu_maps_update_begin
-
|
-
374|
-
NSX:FFFFFFC000C03BFC|900051A0 adrp x0,0xFFFFFFC001637000
-
NSX:FFFFFFC000C03C00|B946D801 ldr w1,[x0,#0x6D8] ; w1,[x0,#1752]
-
375|
-
NSX:FFFFFFC000C03C04|128001E0 movn w0,#0x0F ; w0,#15
-
|
-
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的时候,这条指令会被替换掉。
-
#ifdef CONFIG_DYNAMIC_FTRACE
-
/*
-
* Turn on/off the call to ftrace_graph_caller() in ftrace_caller()
-
* depending on @enable.
-
*/
-
static int ftrace_modify_graph_caller(bool enable)
-
{
-
unsigned long pc = (unsigned long)&ftrace_graph_call;
-
u32 branch, nop;
-
-
branch = aarch64_insn_gen_branch_imm(pc,
-
(unsigned long)ftrace_graph_caller, false);
-
nop = aarch64_insn_gen_nop();
-
-
if (enable)
-
return ftrace_modify_code(pc, nop, branch, true);
-
else
-
return ftrace_modify_code(pc, branch, nop, true);
-
}
这是一个跟架构相关的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“这样,才会允许代码被动态修改。
-
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
-
/*
-
* void ftrace_graph_caller(void)
-
*
-
* Called from _mcount() or ftrace_caller() when function_graph tracer is
-
* selected.
-
* This function w/ prepare_ftrace_return() fakes link register's value on
-
* the call stack in order to intercept instrumented function's return path
-
* and run return_to_handler() later on its exit.
-
*/
-
ENTRY(ftrace_graph_caller)
-
mcount_get_lr_addr x0 // pointer to function's saved lr
-
mcount_get_pc x1 // function's pc
-
mcount_get_parent_fp x2 // parent's fp
-
bl prepare_ftrace_return // prepare_ftrace_return(&lr, pc, fp)
-
-
mcount_exit
-
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,
-
unsigned long frame_pointer)
-
{
-
unsigned long return_hooker = (unsigned long)&return_to_handler;
-
unsigned long old;
-
struct ftrace_graph_ent trace;
-
int err;
-
-
if (unlikely(atomic_read(¤t->tracing_graph_pause)))
-
return;
-
-
/*
-
* Note:
-
* No protection against faulting at *parent, which may be seen
-
* on other archs. It's unlikely on AArch64.
-
*/
-
old = *parent; //保存原始的LR值
-
*parent = return_hooker; //根据上面的分析,parent保存的是address of LR in cpu_subsys_offline.这边进行指针的赋值操作,修改LR。
-
-
trace.func = self_addr;
-
trace.depth = current->curr_ret_stack + 1;
-
-
/* Only trace if the calling function expects to */
-
if (!ftrace_graph_entry(&trace)) {
-
*parent = old;
-
return;
-
}
-
-
err = ftrace_push_return_trace(old, self_addr, &trace.depth,
-
frame_pointer);//保存ftrace function graph的相关信息到task_struct结构体中,这些信息包含函数的调用关系以及时间戳
-
if (err == -EBUSY) {
-
*parent = old;
-
return;
-
}
-
}
-
/* 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函数中去。
-
ENTRY(return_to_handler)
-
str x0, [sp, #-16]! //注意此时cpu_down函数已经执行完毕,其返回值保存在X0中。
-
当然也会出现返回值是一个结构体的情况,比如
-
mov x0, x29 // parent's fp
-
bl ftrace_return_to_handler// addr = ftrace_return_to_hander(fp);
-
mov x30, x0 // restore the original return address
-
ldr x0, [sp], #16
-
ret
-
END(return_to_handler)
ftrace_return_to_handler同样是一个C函数。
-
unsigned long ftrace_return_to_handler(unsigned long frame_pointer)
-
{
-
struct ftrace_graph_ret trace;
-
unsigned long ret;
-
-
ftrace_pop_return_trace(&trace, &ret, frame_pointer);//注意这边的ret返回值,是原始的LR
-
trace.rettime = trace_clock_local();//记录函数退出时间。
-
barrier();
-
current->curr_ret_stack--;
-
-
/*
-
* The trace should run after decrementing the ret counter
-
* in case an interrupt were to come in. We don want to
-
* lose the interrupt if max_depth is set.
*/
-
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函数。
-
ENTRY(return_to_handler)
-
str x0, [sp, #-16]!
-
mov x0, x29 // parent's fp
-
bl ftrace_return_to_handler// addr = ftrace_return_to_hander(fp);
-
mov x30, x0 // restore the original return address
-
//此时X0中保存的是原始的LR。 这原始LR付给LR寄存器。这样ret返回时,饶了一圈又返回到cpu_sysfs_offline调用cpu_down的下一条指令了。
-
ldr x0, [sp], #16 //将压入的cpu_down的返回值退栈。
-
ret
-
END(return_to_handler)
从整个流程上看,ftrace function graph通过动态修改指令,并修改函数调用时压入stack的LR。来做到对于函数的追踪。
相当于在函数的入口和return的时候,插入函数来记录函数调用关系及运行时间。其中涉及到了很多的汇编函数,读起来
比较拗口。
阅读(6786) | 评论(0) | 转发(0) |