7326c4e567
That thread "MIPS: Add option to pass return address location to _mcount" from "David Daney <ddaney@caviumnetworks.com>" have added a new option -mmcount-ra-address to gcc(4.5) for MIPS to transfer the location of the return address to _mcount. Benefit from this new feature, function graph tracer on MIPS will be easier and safer to hijack the return address of the kernel function, which will save some overhead and make the whole thing more reliable. In this patch, at first, try to enable the option -mmcount-ra-address in arch/mips/Makefile with cc-option, if gcc support it, it will be enabled, otherwise, no side effect. and then, we need to support this new option of gcc 4.5 and also support the old gcc versions. with _mcount in the old gcc versions, it's not easy to get the location of return address(tracing: add function graph tracer support for MIPS), so, we do it in a C function: ftrace_get_parent_addr(ftrace.c), but with -mmcount-ra-address, only several instructions need to get what we want, so, I put into asm(mcount.S). and also, as the $12(t0) is used by -mmcount-ra-address for transferring the localtion of return address to _mcount, we need to save it into the stack and restore it when enabled dynamic function tracer, 'Cause we have called "ftrace_call" before "ftrace_graph_caller", which may destroy $12(t0). (Thanks to David for providing that -mcount-ra-address and giving the idea of KBUILD_MCOUNT_RA_ADDRESS, both of them have made the whole thing more beautiful!) Signed-off-by: Wu Zhangjin <wuzhangjin@gmail.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Nicholas Mc Guire <der.herr@hofr.at> Cc: zhangfx@lemote.com Cc: Wu Zhangjin <wuzhangjin@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: linux-kernel@vger.kernel.org Cc: linux-mips@linux-mips.org Patchwork: http://patchwork.linux-mips.org/patch/681/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
276 lines
6.9 KiB
C
276 lines
6.9 KiB
C
/*
|
|
* Code for replacing ftrace calls with jumps.
|
|
*
|
|
* Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com>
|
|
* Copyright (C) 2009 DSLab, Lanzhou University, China
|
|
* Author: Wu Zhangjin <wuzj@lemote.com>
|
|
*
|
|
* Thanks goes to Steven Rostedt for writing the original x86 version.
|
|
*/
|
|
|
|
#include <linux/uaccess.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ftrace.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/asm.h>
|
|
#include <asm/asm-offsets.h>
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
|
#define JAL 0x0c000000 /* jump & link: ip --> ra, jump to target */
|
|
#define ADDR_MASK 0x03ffffff /* op_code|addr : 31...26|25 ....0 */
|
|
#define jump_insn_encode(op_code, addr) \
|
|
((unsigned int)((op_code) | (((addr) >> 2) & ADDR_MASK)))
|
|
|
|
static unsigned int ftrace_nop = 0x00000000;
|
|
|
|
static int ftrace_modify_code(unsigned long ip, unsigned int new_code)
|
|
{
|
|
int faulted;
|
|
|
|
/* *(unsigned int *)ip = new_code; */
|
|
safe_store_code(new_code, ip, faulted);
|
|
|
|
if (unlikely(faulted))
|
|
return -EFAULT;
|
|
|
|
flush_icache_range(ip, ip + 8);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lui_v1;
|
|
static int jal_mcount;
|
|
|
|
int ftrace_make_nop(struct module *mod,
|
|
struct dyn_ftrace *rec, unsigned long addr)
|
|
{
|
|
unsigned int new;
|
|
int faulted;
|
|
unsigned long ip = rec->ip;
|
|
|
|
/* We have compiled module with -mlong-calls, but compiled the kernel
|
|
* without it, we need to cope with them respectively. */
|
|
if (ip & 0x40000000) {
|
|
/* record it for ftrace_make_call */
|
|
if (lui_v1 == 0) {
|
|
/* lui_v1 = *(unsigned int *)ip; */
|
|
safe_load_code(lui_v1, ip, faulted);
|
|
|
|
if (unlikely(faulted))
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* lui v1, hi_16bit_of_mcount --> b 1f (0x10000004)
|
|
* addiu v1, v1, low_16bit_of_mcount
|
|
* move at, ra
|
|
* jalr v1
|
|
* nop
|
|
* 1f: (ip + 12)
|
|
*/
|
|
new = 0x10000004;
|
|
} else {
|
|
/* record/calculate it for ftrace_make_call */
|
|
if (jal_mcount == 0) {
|
|
/* We can record it directly like this:
|
|
* jal_mcount = *(unsigned int *)ip;
|
|
* Herein, jump over the first two nop instructions */
|
|
jal_mcount = jump_insn_encode(JAL, (MCOUNT_ADDR + 8));
|
|
}
|
|
|
|
/* move at, ra
|
|
* jalr v1 --> nop
|
|
*/
|
|
new = ftrace_nop;
|
|
}
|
|
return ftrace_modify_code(ip, new);
|
|
}
|
|
|
|
static int modified; /* initialized as 0 by default */
|
|
|
|
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
|
|
{
|
|
unsigned int new;
|
|
unsigned long ip = rec->ip;
|
|
|
|
/* We just need to remove the "b ftrace_stub" at the fist time! */
|
|
if (modified == 0) {
|
|
modified = 1;
|
|
ftrace_modify_code(addr, ftrace_nop);
|
|
}
|
|
/* ip, module: 0xc0000000, kernel: 0x80000000 */
|
|
new = (ip & 0x40000000) ? lui_v1 : jal_mcount;
|
|
|
|
return ftrace_modify_code(ip, new);
|
|
}
|
|
|
|
#define FTRACE_CALL_IP ((unsigned long)(&ftrace_call))
|
|
|
|
int ftrace_update_ftrace_func(ftrace_func_t func)
|
|
{
|
|
unsigned int new;
|
|
|
|
new = jump_insn_encode(JAL, (unsigned long)func);
|
|
|
|
return ftrace_modify_code(FTRACE_CALL_IP, new);
|
|
}
|
|
|
|
int __init ftrace_dyn_arch_init(void *data)
|
|
{
|
|
/* The return code is retured via data */
|
|
*(unsigned long *)data = 0;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_DYNAMIC_FTRACE */
|
|
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
|
extern void ftrace_graph_call(void);
|
|
#define JMP 0x08000000 /* jump to target directly */
|
|
#define CALL_FTRACE_GRAPH_CALLER \
|
|
jump_insn_encode(JMP, (unsigned long)(&ftrace_graph_caller))
|
|
#define FTRACE_GRAPH_CALL_IP ((unsigned long)(&ftrace_graph_call))
|
|
|
|
int ftrace_enable_ftrace_graph_caller(void)
|
|
{
|
|
return ftrace_modify_code(FTRACE_GRAPH_CALL_IP,
|
|
CALL_FTRACE_GRAPH_CALLER);
|
|
}
|
|
|
|
int ftrace_disable_ftrace_graph_caller(void)
|
|
{
|
|
return ftrace_modify_code(FTRACE_GRAPH_CALL_IP, ftrace_nop);
|
|
}
|
|
|
|
#endif /* !CONFIG_DYNAMIC_FTRACE */
|
|
|
|
#ifndef KBUILD_MCOUNT_RA_ADDRESS
|
|
#define S_RA_SP (0xafbf << 16) /* s{d,w} ra, offset(sp) */
|
|
#define S_R_SP (0xafb0 << 16) /* s{d,w} R, offset(sp) */
|
|
#define OFFSET_MASK 0xffff /* stack offset range: 0 ~ PT_SIZE */
|
|
|
|
unsigned long ftrace_get_parent_addr(unsigned long self_addr,
|
|
unsigned long parent,
|
|
unsigned long parent_addr,
|
|
unsigned long fp)
|
|
{
|
|
unsigned long sp, ip, ra;
|
|
unsigned int code;
|
|
int faulted;
|
|
|
|
/* in module or kernel? */
|
|
if (self_addr & 0x40000000) {
|
|
/* module: move to the instruction "lui v1, HI_16BIT_OF_MCOUNT" */
|
|
ip = self_addr - 20;
|
|
} else {
|
|
/* kernel: move to the instruction "move ra, at" */
|
|
ip = self_addr - 12;
|
|
}
|
|
|
|
/* search the text until finding the non-store instruction or "s{d,w}
|
|
* ra, offset(sp)" instruction */
|
|
do {
|
|
ip -= 4;
|
|
|
|
/* get the code at "ip": code = *(unsigned int *)ip; */
|
|
safe_load_code(code, ip, faulted);
|
|
|
|
if (unlikely(faulted))
|
|
return 0;
|
|
|
|
/* If we hit the non-store instruction before finding where the
|
|
* ra is stored, then this is a leaf function and it does not
|
|
* store the ra on the stack. */
|
|
if ((code & S_R_SP) != S_R_SP)
|
|
return parent_addr;
|
|
|
|
} while (((code & S_RA_SP) != S_RA_SP));
|
|
|
|
sp = fp + (code & OFFSET_MASK);
|
|
|
|
/* ra = *(unsigned long *)sp; */
|
|
safe_load_stack(ra, sp, faulted);
|
|
if (unlikely(faulted))
|
|
return 0;
|
|
|
|
if (ra == parent)
|
|
return sp;
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Hook the return address and push it in the stack of return addrs
|
|
* in current thread info.
|
|
*/
|
|
void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
|
|
unsigned long fp)
|
|
{
|
|
unsigned long old;
|
|
struct ftrace_graph_ent trace;
|
|
unsigned long return_hooker = (unsigned long)
|
|
&return_to_handler;
|
|
int faulted;
|
|
|
|
if (unlikely(atomic_read(¤t->tracing_graph_pause)))
|
|
return;
|
|
|
|
/* "parent" is the stack address saved the return address of the caller
|
|
* of _mcount.
|
|
*
|
|
* if the gcc < 4.5, a leaf function does not save the return address
|
|
* in the stack address, so, we "emulate" one in _mcount's stack space,
|
|
* and hijack it directly, but for a non-leaf function, it save the
|
|
* return address to the its own stack space, we can not hijack it
|
|
* directly, but need to find the real stack address,
|
|
* ftrace_get_parent_addr() does it!
|
|
*
|
|
* if gcc>= 4.5, with the new -mmcount-ra-address option, for a
|
|
* non-leaf function, the location of the return address will be saved
|
|
* to $12 for us, and for a leaf function, only put a zero into $12. we
|
|
* do it in ftrace_graph_caller of mcount.S.
|
|
*/
|
|
|
|
/* old = *parent; */
|
|
safe_load_stack(old, parent, faulted);
|
|
if (unlikely(faulted))
|
|
goto out;
|
|
#ifndef KBUILD_MCOUNT_RA_ADDRESS
|
|
parent = (unsigned long *)ftrace_get_parent_addr(self_addr, old,
|
|
(unsigned long)parent,
|
|
fp);
|
|
/* If fails when getting the stack address of the non-leaf function's
|
|
* ra, stop function graph tracer and return */
|
|
if (parent == 0)
|
|
goto out;
|
|
#endif
|
|
/* *parent = return_hooker; */
|
|
safe_store_stack(return_hooker, parent, faulted);
|
|
if (unlikely(faulted))
|
|
goto out;
|
|
|
|
if (ftrace_push_return_trace(old, self_addr, &trace.depth, fp) ==
|
|
-EBUSY) {
|
|
*parent = old;
|
|
return;
|
|
}
|
|
|
|
trace.func = self_addr;
|
|
|
|
/* Only trace if the calling function expects to */
|
|
if (!ftrace_graph_entry(&trace)) {
|
|
current->curr_ret_stack--;
|
|
*parent = old;
|
|
}
|
|
return;
|
|
out:
|
|
ftrace_graph_stop();
|
|
WARN_ON(1);
|
|
}
|
|
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|