[PATCH 2/2] arm64: ftrace: add support for far branches to dynamic ftrace
Ard Biesheuvel
ard.biesheuvel at linaro.org
Mon Apr 10 10:22:24 EDT 2017
On 10 April 2017 at 15:13, Ard Biesheuvel <ard.biesheuvel at linaro.org> wrote:
> Currently, dynamic ftrace support in the arm64 kernel assumes that all
> core kernel code is within range of ordinary branch instructions 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 all patching of branch instructions involves function calls to
> ftrace_caller(), we can emit the modules with a trampoline that has
> unlimited range, and patch the branch instruction to call the trampoline
> if ftrace_caller() itself is out of range.
>
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel at linaro.org>
> ---
> arch/arm64/Kconfig | 2 +-
> arch/arm64/Makefile | 3 ++
> arch/arm64/include/asm/module.h | 3 ++
> arch/arm64/kernel/ftrace.c | 37 ++++++++++++++++++--
> arch/arm64/kernel/module-plts.c | 10 ++++++
> arch/arm64/lib/Makefile | 2 ++
> arch/arm64/lib/ftrace-mod.S | 25 +++++++++++++
> 7 files changed, 78 insertions(+), 4 deletions(-)
>
> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index e7f043efff41..31af7ea72072 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -982,7 +982,7 @@ config RANDOMIZE_BASE
>
> config RANDOMIZE_MODULE_REGION_FULL
> bool "Randomize the module region independently from the core kernel"
> - depends on RANDOMIZE_BASE && !DYNAMIC_FTRACE
> + depends on RANDOMIZE_BASE
> default y
> help
> Randomizes the location of the module region without considering the
> diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile
> index b9a4a934ca05..01498eab5ada 100644
> --- a/arch/arm64/Makefile
> +++ b/arch/arm64/Makefile
> @@ -68,6 +68,9 @@ endif
>
> ifeq ($(CONFIG_ARM64_MODULE_PLTS),y)
> KBUILD_LDFLAGS_MODULE += -T $(srctree)/arch/arm64/kernel/module.lds
> +ifeq ($(CONFIG_DYNAMIC_FTRACE),y)
> +KBUILD_LDFLAGS_MODULE += $(objtree)/arch/arm64/lib/ftrace-mod.o
> +endif
> endif
>
> # Default value
> diff --git a/arch/arm64/include/asm/module.h b/arch/arm64/include/asm/module.h
> index e12af6754634..2ff2b7782957 100644
> --- a/arch/arm64/include/asm/module.h
> +++ b/arch/arm64/include/asm/module.h
> @@ -25,6 +25,9 @@ struct mod_arch_specific {
> struct elf64_shdr *plt;
> int plt_num_entries;
> int plt_max_entries;
> +
> + /* for CONFIG_DYNAMIC_FTRACE */
> + struct elf64_shdr *ftrace_trampoline;
> };
> #endif
>
> diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
> index a8db6857cad6..8f5a7dc36c1b 100644
> --- a/arch/arm64/kernel/ftrace.c
> +++ b/arch/arm64/kernel/ftrace.c
> @@ -9,11 +9,14 @@
> * published by the Free Software Foundation.
> */
>
> +#include <linux/elf.h>
> #include <linux/ftrace.h>
> +#include <linux/module.h>
> #include <linux/swab.h>
> #include <linux/uaccess.h>
>
> #include <asm/cacheflush.h>
> +#include <asm/debug-monitors.h>
> #include <asm/ftrace.h>
> #include <asm/insn.h>
>
> @@ -63,6 +66,35 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
> return ftrace_modify_code(pc, 0, new, false);
> }
>
> +#ifdef CONFIG_ARM64_MODULE_PLTS
> +EXPORT_SYMBOL_GPL(ftrace_caller);
> +#endif
> +
> +static u32 __ftrace_gen_branch(unsigned long pc, unsigned long addr)
> +{
> + long offset = (long)pc - (long)addr;
> + struct module *mod;
> +
> + if (IS_ENABLED(CONFIG_ARM64_MODULE_PLTS) &&
> + addr == (unsigned long)&ftrace_caller &&
> + unlikely(offset < -SZ_128M || offset >= SZ_128M)) {
> +
> + /*
> + * On kernels that support module PLTs, the offset between the
> + * call and its target may legally exceed the range of an
> + * ordinary branch instruction. In this case, we need to branch
> + * via a trampoline in the module.
> + */
> + mod = __module_address(pc);
> + if (WARN_ON(!mod))
> + return AARCH64_BREAK_FAULT;
> +
> + addr = (unsigned long)mod->arch.ftrace_trampoline->sh_addr;
> + }
> +
> + return aarch64_insn_gen_branch_imm(pc, addr, AARCH64_INSN_BRANCH_LINK);
> +}
> +
> /*
> * Turn on the call to ftrace_caller() in instrumented function
> */
> @@ -72,7 +104,7 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
> u32 old, new;
>
> old = aarch64_insn_gen_nop();
> - new = aarch64_insn_gen_branch_imm(pc, addr, AARCH64_INSN_BRANCH_LINK);
> + new = __ftrace_gen_branch(pc, addr);
>
> return ftrace_modify_code(pc, old, new, true);
> }
> @@ -87,8 +119,7 @@ int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
> u32 old = 0, new;
>
> if (!IS_ENABLED(CONFIG_ARM64_MODULE_PLTS))
> - old = aarch64_insn_gen_branch_imm(pc, addr,
> - AARCH64_INSN_BRANCH_LINK);
> + old = __ftrace_gen_branch(pc, addr);
> new = aarch64_insn_gen_nop();
>
> return ftrace_modify_code(pc, old, new,
> diff --git a/arch/arm64/kernel/module-plts.c b/arch/arm64/kernel/module-plts.c
> index 1ce90d8450ae..859c7170e69a 100644
> --- a/arch/arm64/kernel/module-plts.c
> +++ b/arch/arm64/kernel/module-plts.c
> @@ -162,6 +162,10 @@ int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
> mod->arch.plt = sechdrs + i;
> else if (sechdrs[i].sh_type == SHT_SYMTAB)
> syms = (Elf64_Sym *)sechdrs[i].sh_addr;
> + else if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE) &&
> + strcmp(".text.ftrace_trampoline",
> + secstrings + sechdrs[i].sh_name) == 0)
> + mod->arch.ftrace_trampoline = sechdrs + i;
> }
>
> if (!mod->arch.plt) {
> @@ -173,6 +177,12 @@ int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
> return -ENOEXEC;
> }
>
> + if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE) && !mod->arch.ftrace_trampoline) {
> + pr_err("%s: module ftrace trampoline section missing\n",
> + mod->name);
> + return -ENOEXEC;
> + }
> +
> for (i = 0; i < ehdr->e_shnum; i++) {
> Elf64_Rela *rels = (void *)ehdr + sechdrs[i].sh_offset;
> int numrels = sechdrs[i].sh_size / sizeof(Elf64_Rela);
> diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile
> index c86b7909ef31..b01dcfa9c002 100644
> --- a/arch/arm64/lib/Makefile
> +++ b/arch/arm64/lib/Makefile
> @@ -4,6 +4,8 @@ lib-y := bitops.o clear_user.o delay.o copy_from_user.o \
> memcmp.o strcmp.o strncmp.o strlen.o strnlen.o \
> strchr.o strrchr.o
>
> +lib-$(CONFIG_DYNAMIC_FTRACE) += ftrace-mod.o
> +
> # Tell the compiler to treat all general purpose registers (with the
> # exception of the IP registers, which are already handled by the caller
> # in case of a PLT) as callee-saved, which allows for efficient runtime
> diff --git a/arch/arm64/lib/ftrace-mod.S b/arch/arm64/lib/ftrace-mod.S
> new file mode 100644
> index 000000000000..ce15b9948851
> --- /dev/null
> +++ b/arch/arm64/lib/ftrace-mod.S
> @@ -0,0 +1,25 @@
> +/*
> + * Copyright (C) 2017 Linaro Ltd <ard.biesheuvel at linaro.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/linkage.h>
> +#include <asm/assembler.h>
> +
> + .section ".text.ftrace_trampoline", "ax"
> +ENTRY(__ftrace_trampoline)
> + stp x29, x30, [sp, #-16]!
> + mov x29, sp
> +
> + movz x30, #:abs_g3:ftrace_caller
> + movk x30, #:abs_g2_nc:ftrace_caller
> + movk x30, #:abs_g1_nc:ftrace_caller
> + movk x30, #:abs_g0_nc:ftrace_caller
> + blr x30
> +
> + ldp x29, x30, [sp], #16
> + ret
> +ENDPROC(__ftrace_trampoline)
I suppose the additional stack frame interferes with correct operation
of ftrace() here, so we should probably do this:
ENTRY(__ftrace_trampoline)
movz x16, #:abs_g3:ftrace_caller
movk x16, #:abs_g2_nc:ftrace_caller
movk x16, #:abs_g1_nc:ftrace_caller
movk x16, #:abs_g0_nc:ftrace_caller
blr x16
ENDPROC(__ftrace_trampoline)
instead.
More information about the linux-arm-kernel
mailing list