powerpc/ftrace: Add support for -mprofile-kernel ftrace ABI

The gcc switch -mprofile-kernel defines a new ABI for calling _mcount()
very early in the function with minimal overhead.

Although mprofile-kernel has been available since GCC 3.4, there were
bugs which were only fixed recently. Currently it is known to work in
GCC 4.9, 5 and 6.

Additionally there are two possible code sequences generated by the
flag, the first uses mflr/std/bl and the second is optimised to omit the
std. Currently only gcc 6 has the optimised sequence. This patch
supports both sequences.

Initial work started by Vojtech Pavlik, used with permission.

Key changes:
 - rework _mcount() to work for both the old and new ABIs.
 - implement new versions of ftrace_caller() and ftrace_graph_caller()
   which deal with the new ABI.
 - updates to __ftrace_make_nop() to recognise the new mcount calling
   sequence.
 - updates to __ftrace_make_call() to recognise the nop'ed sequence.
 - implement ftrace_modify_call().
 - updates to the module loader to surpress the toc save in the module
   stub when calling mcount with the new ABI.

Reviewed-by: Balbir Singh <bsingharora@gmail.com>
Signed-off-by: Torsten Duwe <duwe@suse.de>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
Torsten Duwe
2016-03-03 15:26:59 +11:00
committed by Michael Ellerman
parent 9a7841ae8d
commit 153086644f
5 changed files with 325 additions and 21 deletions

View File

@@ -61,8 +61,11 @@ ftrace_modify_code(unsigned long ip, unsigned int old, unsigned int new)
return -EFAULT;
/* Make sure it is what we expect it to be */
if (replaced != old)
if (replaced != old) {
pr_err("%p: replaced (%#x) != old (%#x)",
(void *)ip, replaced, old);
return -EINVAL;
}
/* replace the text with the new text */
if (patch_instruction((unsigned int *)ip, new))
@@ -108,11 +111,13 @@ __ftrace_make_nop(struct module *mod,
{
unsigned long entry, ptr, tramp;
unsigned long ip = rec->ip;
unsigned int op;
unsigned int op, pop;
/* read where this goes */
if (probe_kernel_read(&op, (void *)ip, sizeof(int)))
if (probe_kernel_read(&op, (void *)ip, sizeof(int))) {
pr_err("Fetching opcode failed.\n");
return -EFAULT;
}
/* Make sure that that this is still a 24bit jump */
if (!is_bl_op(op)) {
@@ -152,10 +157,42 @@ __ftrace_make_nop(struct module *mod,
*
* Use a b +8 to jump over the load.
*/
op = 0x48000008; /* b +8 */
if (patch_instruction((unsigned int *)ip, op))
pop = PPC_INST_BRANCH | 8; /* b +8 */
/*
* Check what is in the next instruction. We can see ld r2,40(r1), but
* on first pass after boot we will see mflr r0.
*/
if (probe_kernel_read(&op, (void *)(ip+4), MCOUNT_INSN_SIZE)) {
pr_err("Fetching op failed.\n");
return -EFAULT;
}
if (op != PPC_INST_LD_TOC) {
unsigned int inst;
if (probe_kernel_read(&inst, (void *)(ip - 4), 4)) {
pr_err("Fetching instruction at %lx failed.\n", ip - 4);
return -EFAULT;
}
/* We expect either a mlfr r0, or a std r0, LRSAVE(r1) */
if (inst != PPC_INST_MFLR && inst != PPC_INST_STD_LR) {
pr_err("Unexpected instructions around bl _mcount\n"
"when enabling dynamic ftrace!\t"
"(%08x,bl,%08x)\n", inst, op);
return -EINVAL;
}
/* When using -mkernel_profile there is no load to jump over */
pop = PPC_INST_NOP;
}
if (patch_instruction((unsigned int *)ip, pop)) {
pr_err("Patching NOP failed.\n");
return -EPERM;
}
return 0;
}
@@ -281,6 +318,39 @@ int ftrace_make_nop(struct module *mod,
#ifdef CONFIG_MODULES
#ifdef CONFIG_PPC64
/*
* Examine the existing instructions for __ftrace_make_call.
* They should effectively be a NOP, and follow formal constraints,
* depending on the ABI. Return false if they don't.
*/
#ifndef CC_USING_MPROFILE_KERNEL
static int
expected_nop_sequence(void *ip, unsigned int op0, unsigned int op1)
{
/*
* We expect to see:
*
* b +8
* ld r2,XX(r1)
*
* The load offset is different depending on the ABI. For simplicity
* just mask it out when doing the compare.
*/
if ((op0 != 0x48000008) || ((op1 & 0xffff0000) != 0xe8410000))
return 0;
return 1;
}
#else
static int
expected_nop_sequence(void *ip, unsigned int op0, unsigned int op1)
{
/* look for patched "NOP" on ppc64 with -mprofile-kernel */
if (op0 != PPC_INST_NOP)
return 0;
return 1;
}
#endif
static int
__ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
@@ -291,17 +361,9 @@ __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
if (probe_kernel_read(op, ip, sizeof(op)))
return -EFAULT;
/*
* We expect to see:
*
* b +8
* ld r2,XX(r1)
*
* The load offset is different depending on the ABI. For simplicity
* just mask it out when doing the compare.
*/
if ((op[0] != 0x48000008) || ((op[1] & 0xffff0000) != 0xe8410000)) {
pr_err("Unexpected call sequence: %x %x\n", op[0], op[1]);
if (!expected_nop_sequence(ip, op[0], op[1])) {
pr_err("Unexpected call sequence at %p: %x %x\n",
ip, op[0], op[1]);
return -EINVAL;
}
@@ -324,7 +386,16 @@ __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
return 0;
}
#else
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
unsigned long addr)
{
return ftrace_make_call(rec, addr);
}
#endif
#else /* !CONFIG_PPC64: */
static int
__ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{