Skip to content

Commit 2aa6ac0

Browse files
FlorentRevestwilldeacon
authored andcommitted
arm64: ftrace: Add direct call support
This builds up on the CALL_OPS work which extends the ftrace patchsite on arm64 with an ops pointer usable by the ftrace trampoline. This ops pointer is valid at all time. Indeed, it is either pointing to ftrace_list_ops or to the single ops which should be called from that patchsite. There are a few cases to distinguish: - If a direct call ops is the only one tracing a function: - If the direct called trampoline is within the reach of a BL instruction -> the ftrace patchsite jumps to the trampoline - Else -> the ftrace patchsite jumps to the ftrace_caller trampoline which reads the ops pointer in the patchsite and jumps to the direct call address stored in the ops - Else -> the ftrace patchsite jumps to the ftrace_caller trampoline and its ops literal points to ftrace_list_ops so it iterates over all registered ftrace ops, including the direct call ops and calls its call_direct_funcs handler which stores the direct called trampoline's address in the ftrace_regs and the ftrace_caller trampoline will return to that address instead of returning to the traced function Signed-off-by: Florent Revest <[email protected]> Co-developed-by: Mark Rutland <[email protected]> Signed-off-by: Mark Rutland <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Will Deacon <[email protected]>
1 parent f89b30b commit 2aa6ac0

File tree

5 files changed

+138
-20
lines changed

5 files changed

+138
-20
lines changed

arch/arm64/Kconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ config ARM64
185185
select HAVE_DEBUG_KMEMLEAK
186186
select HAVE_DMA_CONTIGUOUS
187187
select HAVE_DYNAMIC_FTRACE
188+
select HAVE_DYNAMIC_FTRACE_WITH_ARGS \
189+
if $(cc-option,-fpatchable-function-entry=2)
190+
select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS \
191+
if DYNAMIC_FTRACE_WITH_ARGS && DYNAMIC_FTRACE_WITH_CALL_OPS
188192
select HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS \
189193
if (DYNAMIC_FTRACE_WITH_ARGS && !CFI_CLANG && \
190194
!CC_OPTIMIZE_FOR_SIZE)

arch/arm64/include/asm/ftrace.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,19 @@ struct ftrace_ops;
7070

7171
#define arch_ftrace_get_regs(regs) NULL
7272

73+
/*
74+
* Note: sizeof(struct ftrace_regs) must be a multiple of 16 to ensure correct
75+
* stack alignment
76+
*/
7377
struct ftrace_regs {
7478
/* x0 - x8 */
7579
unsigned long regs[9];
80+
81+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
82+
unsigned long direct_tramp;
83+
#else
7684
unsigned long __unused;
85+
#endif
7786

7887
unsigned long fp;
7988
unsigned long lr;
@@ -136,6 +145,19 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec);
136145
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
137146
struct ftrace_ops *op, struct ftrace_regs *fregs);
138147
#define ftrace_graph_func ftrace_graph_func
148+
149+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
150+
static inline void arch_ftrace_set_direct_caller(struct ftrace_regs *fregs,
151+
unsigned long addr)
152+
{
153+
/*
154+
* The ftrace trampoline will return to this address instead of the
155+
* instrumented function.
156+
*/
157+
fregs->direct_tramp = addr;
158+
}
159+
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
160+
139161
#endif
140162

141163
#define ftrace_return_address(n) return_address(n)

arch/arm64/kernel/asm-offsets.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ int main(void)
9393
DEFINE(FREGS_LR, offsetof(struct ftrace_regs, lr));
9494
DEFINE(FREGS_SP, offsetof(struct ftrace_regs, sp));
9595
DEFINE(FREGS_PC, offsetof(struct ftrace_regs, pc));
96+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
97+
DEFINE(FREGS_DIRECT_TRAMP, offsetof(struct ftrace_regs, direct_tramp));
98+
#endif
9699
DEFINE(FREGS_SIZE, sizeof(struct ftrace_regs));
97100
BLANK();
98101
#endif
@@ -197,6 +200,9 @@ int main(void)
197200
#endif
198201
#ifdef CONFIG_FUNCTION_TRACER
199202
DEFINE(FTRACE_OPS_FUNC, offsetof(struct ftrace_ops, func));
203+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
204+
DEFINE(FTRACE_OPS_DIRECT_CALL, offsetof(struct ftrace_ops, direct_call));
205+
#endif
200206
#endif
201207
return 0;
202208
}

arch/arm64/kernel/entry-ftrace.S

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@
3636
SYM_CODE_START(ftrace_caller)
3737
bti c
3838

39+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
40+
/*
41+
* The literal pointer to the ops is at an 8-byte aligned boundary
42+
* which is either 12 or 16 bytes before the BL instruction in the call
43+
* site. See ftrace_call_adjust() for details.
44+
*
45+
* Therefore here the LR points at `literal + 16` or `literal + 20`,
46+
* and we can find the address of the literal in either case by
47+
* aligning to an 8-byte boundary and subtracting 16. We do the
48+
* alignment first as this allows us to fold the subtraction into the
49+
* LDR.
50+
*/
51+
bic x11, x30, 0x7
52+
ldr x11, [x11, #-(4 * AARCH64_INSN_SIZE)] // op
53+
54+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
55+
/*
56+
* If the op has a direct call, handle it immediately without
57+
* saving/restoring registers.
58+
*/
59+
ldr x17, [x11, #FTRACE_OPS_DIRECT_CALL] // op->direct_call
60+
cbnz x17, ftrace_caller_direct
61+
#endif
62+
#endif
63+
3964
/* Save original SP */
4065
mov x10, sp
4166

@@ -49,6 +74,10 @@ SYM_CODE_START(ftrace_caller)
4974
stp x6, x7, [sp, #FREGS_X6]
5075
str x8, [sp, #FREGS_X8]
5176

77+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
78+
str xzr, [sp, #FREGS_DIRECT_TRAMP]
79+
#endif
80+
5281
/* Save the callsite's FP, LR, SP */
5382
str x29, [sp, #FREGS_FP]
5483
str x9, [sp, #FREGS_LR]
@@ -71,20 +100,7 @@ SYM_CODE_START(ftrace_caller)
71100
mov x3, sp // regs
72101

73102
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
74-
/*
75-
* The literal pointer to the ops is at an 8-byte aligned boundary
76-
* which is either 12 or 16 bytes before the BL instruction in the call
77-
* site. See ftrace_call_adjust() for details.
78-
*
79-
* Therefore here the LR points at `literal + 16` or `literal + 20`,
80-
* and we can find the address of the literal in either case by
81-
* aligning to an 8-byte boundary and subtracting 16. We do the
82-
* alignment first as this allows us to fold the subtraction into the
83-
* LDR.
84-
*/
85-
bic x2, x30, 0x7
86-
ldr x2, [x2, #-16] // op
87-
103+
mov x2, x11 // op
88104
ldr x4, [x2, #FTRACE_OPS_FUNC] // op->func
89105
blr x4 // op->func(ip, parent_ip, op, regs)
90106

@@ -107,17 +123,61 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
107123
ldp x6, x7, [sp, #FREGS_X6]
108124
ldr x8, [sp, #FREGS_X8]
109125

110-
/* Restore the callsite's FP, LR, PC */
126+
/* Restore the callsite's FP */
111127
ldr x29, [sp, #FREGS_FP]
128+
129+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
130+
ldr x17, [sp, #FREGS_DIRECT_TRAMP]
131+
cbnz x17, ftrace_caller_direct_late
132+
#endif
133+
134+
/* Restore the callsite's LR and PC */
112135
ldr x30, [sp, #FREGS_LR]
113136
ldr x9, [sp, #FREGS_PC]
114137

115138
/* Restore the callsite's SP */
116139
add sp, sp, #FREGS_SIZE + 32
117140

118141
ret x9
142+
143+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
144+
SYM_INNER_LABEL(ftrace_caller_direct_late, SYM_L_LOCAL)
145+
/*
146+
* Head to a direct trampoline in x17 after having run other tracers.
147+
* The ftrace_regs are live, and x0-x8 and FP have been restored. The
148+
* LR, PC, and SP have not been restored.
149+
*/
150+
151+
/*
152+
* Restore the callsite's LR and PC matching the trampoline calling
153+
* convention.
154+
*/
155+
ldr x9, [sp, #FREGS_LR]
156+
ldr x30, [sp, #FREGS_PC]
157+
158+
/* Restore the callsite's SP */
159+
add sp, sp, #FREGS_SIZE + 32
160+
161+
SYM_INNER_LABEL(ftrace_caller_direct, SYM_L_LOCAL)
162+
/*
163+
* Head to a direct trampoline in x17.
164+
*
165+
* We use `BR X17` as this can safely land on a `BTI C` or `PACIASP` in
166+
* the trampoline, and will not unbalance any return stack.
167+
*/
168+
br x17
169+
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
119170
SYM_CODE_END(ftrace_caller)
120171

172+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
173+
SYM_CODE_START(ftrace_stub_direct_tramp)
174+
bti c
175+
mov x10, x30
176+
mov x30, x9
177+
ret x10
178+
SYM_CODE_END(ftrace_stub_direct_tramp)
179+
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
180+
121181
#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
122182

123183
/*

arch/arm64/kernel/ftrace.c

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ static struct plt_entry *get_ftrace_plt(struct module *mod, unsigned long addr)
206206
return NULL;
207207
}
208208

209+
static bool reachable_by_bl(unsigned long addr, unsigned long pc)
210+
{
211+
long offset = (long)addr - (long)pc;
212+
213+
return offset >= -SZ_128M && offset < SZ_128M;
214+
}
215+
209216
/*
210217
* Find the address the callsite must branch to in order to reach '*addr'.
211218
*
@@ -220,14 +227,21 @@ static bool ftrace_find_callable_addr(struct dyn_ftrace *rec,
220227
unsigned long *addr)
221228
{
222229
unsigned long pc = rec->ip;
223-
long offset = (long)*addr - (long)pc;
224230
struct plt_entry *plt;
225231

232+
/*
233+
* If a custom trampoline is unreachable, rely on the ftrace_caller
234+
* trampoline which knows how to indirectly reach that trampoline
235+
* through ops->direct_call.
236+
*/
237+
if (*addr != FTRACE_ADDR && !reachable_by_bl(*addr, pc))
238+
*addr = FTRACE_ADDR;
239+
226240
/*
227241
* When the target is within range of the 'BL' instruction, use 'addr'
228242
* as-is and branch to that directly.
229243
*/
230-
if (offset >= -SZ_128M && offset < SZ_128M)
244+
if (reachable_by_bl(*addr, pc))
231245
return true;
232246

233247
/*
@@ -330,12 +344,24 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
330344
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
331345
unsigned long addr)
332346
{
333-
if (WARN_ON_ONCE(old_addr != (unsigned long)ftrace_caller))
347+
unsigned long pc = rec->ip;
348+
u32 old, new;
349+
int ret;
350+
351+
ret = ftrace_rec_set_ops(rec, arm64_rec_get_ops(rec));
352+
if (ret)
353+
return ret;
354+
355+
if (!ftrace_find_callable_addr(rec, NULL, &old_addr))
334356
return -EINVAL;
335-
if (WARN_ON_ONCE(addr != (unsigned long)ftrace_caller))
357+
if (!ftrace_find_callable_addr(rec, NULL, &addr))
336358
return -EINVAL;
337359

338-
return ftrace_rec_update_ops(rec);
360+
old = aarch64_insn_gen_branch_imm(pc, old_addr,
361+
AARCH64_INSN_BRANCH_LINK);
362+
new = aarch64_insn_gen_branch_imm(pc, addr, AARCH64_INSN_BRANCH_LINK);
363+
364+
return ftrace_modify_code(pc, old, new, true);
339365
}
340366
#endif
341367

0 commit comments

Comments
 (0)