Skip to content

Commit dbf236b

Browse files
mhiramatKernel Patches Daemon
authored and
Kernel Patches Daemon
committed
ARM: rethook: Replace kretprobe trampoline with rethook
Replace the kretprob's trampoline code with the rethook on arm. This also enables rethook support on arm. Most of the code has been copied from kretprobe on arm. The significant difference is that the rethook on mcount (ftrace) support is added. If the rethook is called from the kprobes for kretprobe, there is no problem to replace the LR register with trampoline address because the LR register will be saved after kprobe probed. However, the mcount call will be placed right after making a stack frame for the function. This means we have to decode the stackframe to find where the LR register is saved. With the CONFIG_FRAME_POINTER, the frame pointer (FP register) is used. Without that, rethook has to unwind one stack frame to find it. Signed-off-by: Masami Hiramatsu <[email protected]>
1 parent 0a43a52 commit dbf236b

File tree

6 files changed

+139
-70
lines changed

6 files changed

+139
-70
lines changed

arch/arm/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ config ARM
110110
select HAVE_MOD_ARCH_SPECIFIC
111111
select HAVE_NMI
112112
select HAVE_OPTPROBES if !THUMB2_KERNEL
113+
select HAVE_RETHOOK
113114
select HAVE_PERF_EVENTS
114115
select HAVE_PERF_REGS
115116
select HAVE_PERF_USER_STACK_DUMP

arch/arm/include/asm/stacktrace.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ struct stackframe {
1717

1818
/* address of the LR value on the stack */
1919
unsigned long *lr_addr;
20-
#ifdef CONFIG_KRETPROBES
20+
21+
#if defined(CONFIG_RETHOOK)
2122
struct llist_node *kr_cur;
2223
struct task_struct *tsk;
2324
#endif
@@ -30,7 +31,7 @@ void arm_get_current_stackframe(struct pt_regs *regs, struct stackframe *frame)
3031
frame->sp = regs->ARM_sp;
3132
frame->lr = regs->ARM_lr;
3233
frame->pc = regs->ARM_pc;
33-
#ifdef CONFIG_KRETPROBES
34+
#if defined(CONFIG_RETHOOK)
3435
frame->kr_cur = NULL;
3536
frame->tsk = current;
3637
#endif

arch/arm/kernel/stacktrace.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: GPL-2.0-only
22
#include <linux/export.h>
33
#include <linux/kprobes.h>
4+
#include <linux/rethook.h>
45
#include <linux/sched.h>
56
#include <linux/sched/debug.h>
67
#include <linux/stacktrace.h>
@@ -66,10 +67,10 @@ int notrace unwind_frame(struct stackframe *frame)
6667
frame->sp = *(unsigned long *)(fp - 8);
6768
frame->pc = *(unsigned long *)(fp - 4);
6869
#endif
69-
#ifdef CONFIG_KRETPROBES
70-
if (is_kretprobe_trampoline(frame->pc))
71-
frame->pc = kretprobe_find_ret_addr(frame->tsk,
72-
(void *)frame->fp, &frame->kr_cur);
70+
#ifdef CONFIG_RETHOOK
71+
if (is_rethook_trampoline(frame->pc))
72+
frame->pc = rethook_find_ret_addr(frame->tsk, frame->fp,
73+
&frame->kr_cur);
7374
#endif
7475

7576
return 0;
@@ -163,7 +164,7 @@ static noinline void __save_stack_trace(struct task_struct *tsk,
163164
here:
164165
frame.pc = (unsigned long)&&here;
165166
}
166-
#ifdef CONFIG_KRETPROBES
167+
#ifdef CONFIG_RETHOOK
167168
frame.kr_cur = NULL;
168169
frame.tsk = tsk;
169170
#endif
@@ -184,7 +185,7 @@ void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace)
184185
frame.sp = regs->ARM_sp;
185186
frame.lr = regs->ARM_lr;
186187
frame.pc = regs->ARM_pc;
187-
#ifdef CONFIG_KRETPROBES
188+
#ifdef CONFIG_RETHOOK
188189
frame.kr_cur = NULL;
189190
frame.tsk = current;
190191
#endif

arch/arm/probes/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ obj-$(CONFIG_KPROBES) += decode-thumb.o
66
else
77
obj-$(CONFIG_KPROBES) += decode-arm.o
88
endif
9+
obj-$(CONFIG_RETHOOK) += rethook.o

arch/arm/probes/kprobes/core.c

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -365,68 +365,6 @@ int __kprobes kprobe_exceptions_notify(struct notifier_block *self,
365365
return NOTIFY_DONE;
366366
}
367367

368-
/*
369-
* When a retprobed function returns, trampoline_handler() is called,
370-
* calling the kretprobe's handler. We construct a struct pt_regs to
371-
* give a view of registers r0-r11, sp, lr, and pc to the user
372-
* return-handler. This is not a complete pt_regs structure, but that
373-
* should be enough for stacktrace from the return handler with or
374-
* without pt_regs.
375-
*/
376-
void __naked __kprobes __kretprobe_trampoline(void)
377-
{
378-
__asm__ __volatile__ (
379-
#ifdef CONFIG_FRAME_POINTER
380-
"ldr lr, =__kretprobe_trampoline \n\t"
381-
/* __kretprobe_trampoline makes a framepointer on pt_regs. */
382-
#ifdef CONFIG_CC_IS_CLANG
383-
"stmdb sp, {sp, lr, pc} \n\t"
384-
"sub sp, sp, #12 \n\t"
385-
/* In clang case, pt_regs->ip = lr. */
386-
"stmdb sp!, {r0 - r11, lr} \n\t"
387-
/* fp points regs->r11 (fp) */
388-
"add fp, sp, #44 \n\t"
389-
#else /* !CONFIG_CC_IS_CLANG */
390-
/* In gcc case, pt_regs->ip = fp. */
391-
"stmdb sp, {fp, sp, lr, pc} \n\t"
392-
"sub sp, sp, #16 \n\t"
393-
"stmdb sp!, {r0 - r11} \n\t"
394-
/* fp points regs->r15 (pc) */
395-
"add fp, sp, #60 \n\t"
396-
#endif /* CONFIG_CC_IS_CLANG */
397-
#else /* !CONFIG_FRAME_POINTER */
398-
"sub sp, sp, #16 \n\t"
399-
"stmdb sp!, {r0 - r11} \n\t"
400-
#endif /* CONFIG_FRAME_POINTER */
401-
"mov r0, sp \n\t"
402-
"bl trampoline_handler \n\t"
403-
"mov lr, r0 \n\t"
404-
"ldmia sp!, {r0 - r11} \n\t"
405-
"add sp, sp, #16 \n\t"
406-
#ifdef CONFIG_THUMB2_KERNEL
407-
"bx lr \n\t"
408-
#else
409-
"mov pc, lr \n\t"
410-
#endif
411-
: : : "memory");
412-
}
413-
414-
/* Called from __kretprobe_trampoline */
415-
static __used __kprobes void *trampoline_handler(struct pt_regs *regs)
416-
{
417-
return (void *)kretprobe_trampoline_handler(regs, (void *)regs->ARM_fp);
418-
}
419-
420-
void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
421-
struct pt_regs *regs)
422-
{
423-
ri->ret_addr = (kprobe_opcode_t *)regs->ARM_lr;
424-
ri->fp = (void *)regs->ARM_fp;
425-
426-
/* Replace the return addr with trampoline addr. */
427-
regs->ARM_lr = (unsigned long)&__kretprobe_trampoline;
428-
}
429-
430368
int __kprobes arch_trampoline_kprobe(struct kprobe *p)
431369
{
432370
return 0;

arch/arm/probes/rethook.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* arm implementation of rethook. Mostly copied from arch/arm/probes/kprobes/core.c
4+
*/
5+
6+
#include <linux/kprobes.h>
7+
#include <linux/rethook.h>
8+
9+
#include <asm/stacktrace.h>
10+
11+
/* Called from arch_rethook_trampoline */
12+
static __used notrace unsigned long arch_rethook_trampoline_callback(struct pt_regs *regs)
13+
{
14+
return rethook_trampoline_handler(regs, regs->ARM_fp);
15+
}
16+
NOKPROBE_SYMBOL(arch_rethook_trampoline_callback);
17+
18+
/*
19+
* When a rethook'ed function returns, it returns to arch_rethook_trampoline
20+
* which calls rethook callback. We construct a struct pt_regs to
21+
* give a view of registers r0-r11, sp, lr, and pc to the user
22+
* return-handler. This is not a complete pt_regs structure, but that
23+
* should be enough for stacktrace from the return handler with or
24+
* without pt_regs.
25+
*/
26+
asm(
27+
".text\n"
28+
".global arch_rethook_trampoline\n"
29+
".type arch_rethook_trampoline, %function\n"
30+
"arch_rethook_trampoline:\n"
31+
#ifdef CONFIG_FRAME_POINTER
32+
"adr lr, . \n\t"
33+
/* this makes a framepointer on pt_regs. */
34+
#ifdef CONFIG_CC_IS_CLANG
35+
"stmdb sp, {sp, lr, pc} \n\t"
36+
"sub sp, sp, #12 \n\t"
37+
/* In clang case, pt_regs->ip = lr. */
38+
"stmdb sp!, {r0 - r11, lr} \n\t"
39+
/* fp points regs->r11 (fp) */
40+
"add fp, sp, #44 \n\t"
41+
#else /* !CONFIG_CC_IS_CLANG */
42+
/* In gcc case, pt_regs->ip = fp. */
43+
"stmdb sp, {fp, sp, lr, pc} \n\t"
44+
"sub sp, sp, #16 \n\t"
45+
"stmdb sp!, {r0 - r11} \n\t"
46+
/* fp points regs->r15 (pc) */
47+
"add fp, sp, #60 \n\t"
48+
#endif /* CONFIG_CC_IS_CLANG */
49+
#else /* !CONFIG_FRAME_POINTER */
50+
"sub sp, sp, #16 \n\t"
51+
"stmdb sp!, {r0 - r11} \n\t"
52+
#endif /* CONFIG_FRAME_POINTER */
53+
"mov r0, sp \n\t"
54+
"bl arch_rethook_trampoline_callback \n\t"
55+
"mov lr, r0 \n\t"
56+
"ldmia sp!, {r0 - r11} \n\t"
57+
"add sp, sp, #16 \n\t"
58+
#ifdef CONFIG_THUMB2_KERNEL
59+
"bx lr \n\t"
60+
#else
61+
"mov pc, lr \n\t"
62+
#endif
63+
".size arch_rethook_trampoline, .-arch_rethook_trampoline\n"
64+
);
65+
NOKPROBE_SYMBOL(arch_rethook_trampoline);
66+
67+
/*
68+
* At the entry of function with mcount, if the FRAME_POINTER is enabled,
69+
* the stack and registers are prepared for the mcount function as below.
70+
*
71+
* mov ip, sp
72+
* push {fp, ip, lr, pc}
73+
* sub fp, ip, #4 ; FP[0] = PC, FP[-4] = LR, and FP[-12] = call-site FP.
74+
* push {lr}
75+
* bl <__gnu_mcount_nc> ; call ftrace
76+
*
77+
* And when returning from the function, call-site FP, SP and PC are restored
78+
* from stack as below;
79+
*
80+
* ldm sp, {fp, sp, pc}
81+
*
82+
* Thus, if the arch_rethook_prepare() is called from real function entry,
83+
* it must change the LR and save FP in pt_regs. But if it is called via
84+
* mcount context (ftrace), it must change the LR on stack, which is next
85+
* to the PC (= FP[-4]), and save the FP value at FP[-12].
86+
*
87+
* If the FRAME_POINTER is disabled, we have to use arm unwinder to find where
88+
* the LR is stored.
89+
*/
90+
int notrace arch_rethook_prepare(struct rethook_node *rh, struct pt_regs *regs, bool mcount)
91+
{
92+
unsigned long *lr_addr;
93+
int ret;
94+
95+
if (mcount) {
96+
/* Clang + mcount case is not supported yet. */
97+
if (IS_ENABLED(CONFIG_CC_IS_CLANG))
98+
return -EOPNOTSUPP;
99+
if (IS_ENABLED(CONFIG_FRAME_POINTER)) {
100+
lr_addr = (unsigned long *)(regs->ARM_fp - 4);
101+
rh->frame = *(unsigned long *)(regs->ARM_fp - 12);
102+
} else {
103+
struct stackframe frame;
104+
105+
arm_get_current_stackframe(regs, &frame);
106+
ret = unwind_frame(&frame);
107+
if (ret < 0)
108+
return -EINVAL;
109+
110+
if (frame.lr_addr)
111+
lr_addr = frame.lr_addr;
112+
else
113+
lr_addr = &regs->ARM_lr;
114+
rh->frame = regs->ARM_fp;
115+
}
116+
} else {
117+
lr_addr = &regs->ARM_lr;
118+
rh->frame = regs->ARM_fp;
119+
}
120+
121+
/* Replace the return addr with trampoline addr. */
122+
rh->ret_addr = *lr_addr;
123+
*lr_addr = (unsigned long)arch_rethook_trampoline;
124+
125+
return 0;
126+
}
127+
NOKPROBE_SYMBOL(arch_rethook_prepare);

0 commit comments

Comments
 (0)