Skip to content

Commit

Permalink
riscv: Using PATCHABLE_FUNCTION_ENTRY instead of MCOUNT
Browse files Browse the repository at this point in the history
This patch changes the current detour mechanism of dynamic ftrace
which has been discussed during LPC 2020 RISCV-MC [1].

Before the patch, we used mcount for detour:
<funca>:
	addi sp,sp,-16
	sd   ra,8(sp)
	sd   s0,0(sp)
	addi s0,sp,16
	mv   a5,ra
	mv   a0,a5
	auipc ra,0x0 -> nop
	jalr  -296(ra) <_mcount@plt> ->nop
	...

After the patch, we use nop call site area for detour:
<funca>:
	nop -> REG_S ra, -SZREG(sp)
	nop -> auipc ra, 0x?
	nop -> jalr ?(ra)
	nop -> REG_L ra, -SZREG(sp)
	...

The mcount mechanism is mixed with gcc function prologue which is
not very clear. The patchable function entry just put 16 bytes nop
before the front of the function prologue which could be filled
with a separated detour mechanism.

[1] https://www.linuxplumbersconf.org/event/7/contributions/807/

Signed-off-by: Guo Ren <guoren@linux.alibaba.com>
Signed-off-by: Palmer Dabbelt <palmerdabbelt@google.com>
  • Loading branch information
guoren83 authored and palmer-dabbelt committed Jan 14, 2021
1 parent 5ad84ad commit afc76b8
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 235 deletions.
2 changes: 2 additions & 0 deletions arch/riscv/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ OBJCOPYFLAGS := -O binary
LDFLAGS_vmlinux :=
ifeq ($(CONFIG_DYNAMIC_FTRACE),y)
LDFLAGS_vmlinux := --no-relax
KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
CC_FLAGS_FTRACE := -fpatchable-function-entry=8
endif

ifeq ($(CONFIG_64BIT)$(CONFIG_CMODEL_MEDLOW),yy)
Expand Down
95 changes: 50 additions & 45 deletions arch/riscv/kernel/ftrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,29 +72,56 @@ static int __ftrace_modify_call(unsigned long hook_pos, unsigned long target,
return 0;
}

/*
* Put 5 instructions with 16 bytes at the front of function within
* patchable function entry nops' area.
*
* 0: REG_S ra, -SZREG(sp)
* 1: auipc ra, 0x?
* 2: jalr -?(ra)
* 3: REG_L ra, -SZREG(sp)
*
* So the opcodes is:
* 0: 0xfe113c23 (sd)/0xfe112e23 (sw)
* 1: 0x???????? -> auipc
* 2: 0x???????? -> jalr
* 3: 0xff813083 (ld)/0xffc12083 (lw)
*/
#if __riscv_xlen == 64
#define INSN0 0xfe113c23
#define INSN3 0xff813083
#elif __riscv_xlen == 32
#define INSN0 0xfe112e23
#define INSN3 0xffc12083
#endif

#define FUNC_ENTRY_SIZE 16
#define FUNC_ENTRY_JMP 4

int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
int ret = ftrace_check_current_call(rec->ip, NULL);
unsigned int call[4] = {INSN0, 0, 0, INSN3};
unsigned long target = addr;
unsigned long caller = rec->ip + FUNC_ENTRY_JMP;

if (ret)
return ret;
call[1] = to_auipc_insn((unsigned int)(target - caller));
call[2] = to_jalr_insn((unsigned int)(target - caller));

return __ftrace_modify_call(rec->ip, addr, true);
if (patch_text_nosync((void *)rec->ip, call, FUNC_ENTRY_SIZE))
return -EPERM;

return 0;
}

int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
unsigned long addr)
{
unsigned int call[2];
int ret;
unsigned int nops[4] = {NOP4, NOP4, NOP4, NOP4};

make_call(rec->ip, addr, call);
ret = ftrace_check_current_call(rec->ip, call);

if (ret)
return ret;
if (patch_text_nosync((void *)rec->ip, nops, FUNC_ENTRY_SIZE))
return -EPERM;

return __ftrace_modify_call(rec->ip, addr, false);
return 0;
}


Expand Down Expand Up @@ -139,15 +166,16 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
unsigned long addr)
{
unsigned int call[2];
unsigned long caller = rec->ip + FUNC_ENTRY_JMP;
int ret;

make_call(rec->ip, old_addr, call);
ret = ftrace_check_current_call(rec->ip, call);
make_call(caller, old_addr, call);
ret = ftrace_check_current_call(caller, call);

if (ret)
return ret;

return __ftrace_modify_call(rec->ip, addr, true);
return __ftrace_modify_call(caller, addr, true);
}
#endif

Expand Down Expand Up @@ -176,53 +204,30 @@ void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,

#ifdef CONFIG_DYNAMIC_FTRACE
extern void ftrace_graph_call(void);
extern void ftrace_graph_regs_call(void);
int ftrace_enable_ftrace_graph_caller(void)
{
unsigned int call[2];
static int init_graph = 1;
int ret;

make_call(&ftrace_graph_call, &ftrace_stub, call);

/*
* When enabling graph tracer for the first time, ftrace_graph_call
* should contains a call to ftrace_stub. Once it has been disabled,
* the 8-bytes at the position becomes NOPs.
*/
if (init_graph) {
ret = ftrace_check_current_call((unsigned long)&ftrace_graph_call,
call);
init_graph = 0;
} else {
ret = ftrace_check_current_call((unsigned long)&ftrace_graph_call,
NULL);
}

ret = __ftrace_modify_call((unsigned long)&ftrace_graph_call,
(unsigned long)&prepare_ftrace_return, true);
if (ret)
return ret;

return __ftrace_modify_call((unsigned long)&ftrace_graph_call,
return __ftrace_modify_call((unsigned long)&ftrace_graph_regs_call,
(unsigned long)&prepare_ftrace_return, true);
}

int ftrace_disable_ftrace_graph_caller(void)
{
unsigned int call[2];
int ret;

make_call(&ftrace_graph_call, &prepare_ftrace_return, call);

/*
* This is to make sure that ftrace_enable_ftrace_graph_caller
* did the right thing.
*/
ret = ftrace_check_current_call((unsigned long)&ftrace_graph_call,
call);

ret = __ftrace_modify_call((unsigned long)&ftrace_graph_call,
(unsigned long)&prepare_ftrace_return, false);
if (ret)
return ret;

return __ftrace_modify_call((unsigned long)&ftrace_graph_call,
return __ftrace_modify_call((unsigned long)&ftrace_graph_regs_call,
(unsigned long)&prepare_ftrace_return, false);
}
#endif /* CONFIG_DYNAMIC_FTRACE */
Expand Down
Loading

0 comments on commit afc76b8

Please sign in to comment.