Skip to content

Commit e71a4e1

Browse files
Ard Biesheuvelwildea01
authored andcommitted
arm64: ftrace: add support for far branches to dynamic ftrace
Currently, dynamic ftrace support in the arm64 kernel assumes that all core kernel code is within range of ordinary branch instructions that occur in module code, which is usually the case, but is no longer guaranteed now that we have support for module PLTs and address space randomization. Since on arm64, all patching of branch instructions involves function calls to the same entry point [ftrace_caller()], we can emit the modules with a trampoline that has unlimited range, and patch both the trampoline itself and the branch instruction to redirect the call via the trampoline. Signed-off-by: Ard Biesheuvel <[email protected]> [will: minor clarification to smp_wmb() comment] Signed-off-by: Will Deacon <[email protected]>
1 parent f8af0b3 commit e71a4e1

File tree

7 files changed

+84
-2
lines changed

7 files changed

+84
-2
lines changed

arch/arm64/Kconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ config RANDOMIZE_BASE
982982

983983
config RANDOMIZE_MODULE_REGION_FULL
984984
bool "Randomize the module region independently from the core kernel"
985-
depends on RANDOMIZE_BASE && !DYNAMIC_FTRACE
985+
depends on RANDOMIZE_BASE
986986
default y
987987
help
988988
Randomizes the location of the module region without considering the

arch/arm64/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ endif
7070

7171
ifeq ($(CONFIG_ARM64_MODULE_PLTS),y)
7272
KBUILD_LDFLAGS_MODULE += -T $(srctree)/arch/arm64/kernel/module.lds
73+
ifeq ($(CONFIG_DYNAMIC_FTRACE),y)
74+
KBUILD_LDFLAGS_MODULE += $(objtree)/arch/arm64/kernel/ftrace-mod.o
75+
endif
7376
endif
7477

7578
# Default value

arch/arm64/include/asm/module.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ struct mod_plt_sec {
3030
struct mod_arch_specific {
3131
struct mod_plt_sec core;
3232
struct mod_plt_sec init;
33+
34+
/* for CONFIG_DYNAMIC_FTRACE */
35+
void *ftrace_trampoline;
3336
};
3437
#endif
3538

arch/arm64/kernel/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ extra-y += $(head-y) vmlinux.lds
6262
ifeq ($(CONFIG_DEBUG_EFI),y)
6363
AFLAGS_head.o += -DVMLINUX_PATH="\"$(realpath $(objtree)/vmlinux)\""
6464
endif
65+
66+
# will be included by each individual module but not by the core kernel itself
67+
extra-$(CONFIG_DYNAMIC_FTRACE) += ftrace-mod.o

arch/arm64/kernel/ftrace-mod.S

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (C) 2017 Linaro Ltd <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License version 2 as
6+
* published by the Free Software Foundation.
7+
*/
8+
9+
#include <linux/linkage.h>
10+
#include <asm/assembler.h>
11+
12+
.section ".text.ftrace_trampoline", "ax"
13+
.align 3
14+
0: .quad 0
15+
__ftrace_trampoline:
16+
ldr x16, 0b
17+
br x16
18+
ENDPROC(__ftrace_trampoline)

arch/arm64/kernel/ftrace.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
*/
1111

1212
#include <linux/ftrace.h>
13+
#include <linux/module.h>
1314
#include <linux/swab.h>
1415
#include <linux/uaccess.h>
1516

1617
#include <asm/cacheflush.h>
18+
#include <asm/debug-monitors.h>
1719
#include <asm/ftrace.h>
1820
#include <asm/insn.h>
1921

@@ -69,8 +71,57 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
6971
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
7072
{
7173
unsigned long pc = rec->ip;
74+
long offset = (long)pc - (long)addr;
7275
u32 old, new;
7376

77+
if (IS_ENABLED(CONFIG_ARM64_MODULE_PLTS) &&
78+
(offset < -SZ_128M || offset >= SZ_128M)) {
79+
unsigned long *trampoline;
80+
struct module *mod;
81+
82+
/*
83+
* On kernels that support module PLTs, the offset between the
84+
* branch instruction and its target may legally exceed the
85+
* range of an ordinary relative 'bl' opcode. In this case, we
86+
* need to branch via a trampoline in the module.
87+
*
88+
* NOTE: __module_text_address() must be called with preemption
89+
* disabled, but we can rely on ftrace_lock to ensure that 'mod'
90+
* retains its validity throughout the remainder of this code.
91+
*/
92+
preempt_disable();
93+
mod = __module_text_address(pc);
94+
preempt_enable();
95+
96+
if (WARN_ON(!mod))
97+
return -EINVAL;
98+
99+
/*
100+
* There is only one ftrace trampoline per module. For now,
101+
* this is not a problem since on arm64, all dynamic ftrace
102+
* invocations are routed via ftrace_caller(). This will need
103+
* to be revisited if support for multiple ftrace entry points
104+
* is added in the future, but for now, the pr_err() below
105+
* deals with a theoretical issue only.
106+
*/
107+
trampoline = (unsigned long *)mod->arch.ftrace_trampoline;
108+
if (trampoline[0] != addr) {
109+
if (trampoline[0] != 0) {
110+
pr_err("ftrace: far branches to multiple entry points unsupported inside a single module\n");
111+
return -EINVAL;
112+
}
113+
114+
/* point the trampoline to our ftrace entry point */
115+
module_disable_ro(mod);
116+
trampoline[0] = addr;
117+
module_enable_ro(mod, true);
118+
119+
/* update trampoline before patching in the branch */
120+
smp_wmb();
121+
}
122+
addr = (unsigned long)&trampoline[1];
123+
}
124+
74125
old = aarch64_insn_gen_nop();
75126
new = aarch64_insn_gen_branch_imm(pc, addr, AARCH64_INSN_BRANCH_LINK);
76127

arch/arm64/kernel/module.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,12 @@ int module_finalize(const Elf_Ehdr *hdr,
420420
for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) {
421421
if (strcmp(".altinstructions", secstrs + s->sh_name) == 0) {
422422
apply_alternatives((void *)s->sh_addr, s->sh_size);
423-
return 0;
424423
}
424+
#ifdef CONFIG_ARM64_MODULE_PLTS
425+
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE) &&
426+
!strcmp(".text.ftrace_trampoline", secstrs + s->sh_name))
427+
me->arch.ftrace_trampoline = (void *)s->sh_addr;
428+
#endif
425429
}
426430

427431
return 0;

0 commit comments

Comments
 (0)