Thread (1 message) 1 message, 1 author, 2019-11-15

Re: [PATCHv2 7/8] arm64: implement ftrace with regs

From: Mark Rutland <mark.rutland@arm.com>
Date: 2019-11-15 13:59:30
Also in: lkml

On Fri, Nov 15, 2019 at 07:05:39AM +0900, Itaru Kitayama wrote:
Is this feature avail even when building kernel with Clang?
Clang doesn't currently support -fpatchable-function-entry.

Nick (Cc'd) created an LLVM bugzilla ticket for that:

  https://bugs.llvm.org/show_bug.cgi?id=43912

... though it looks like that's having database issues at the moment.

Thanks,
Mark.
On Wed, Oct 30, 2019 at 2:01 Mark Rutland [off-list ref] wrote:

    From: Torsten Duwe [off-list ref]

    This patch implements FTRACE_WITH_REGS for arm64, which allows a traced
    function's arguments (and some other registers) to be captured into a
    struct pt_regs, allowing these to be inspected and/or modified. This is
    a building block for live-patching, where a function's arguments may be
    forwarded to another function. This is also necessary to enable ftrace
    and in-kernel pointer authentication at the same time, as it allows the
    LR value to be captured and adjusted prior to signing.

    Using GCC's -fpatchable-function-entry=N option, we can have the
    compiler insert a configurable number of NOPs between the function entry
    point and the usual prologue. This also ensures functions are AAPCS
    compliant (e.g. disabling inter-procedural register allocation).

    For example, with -fpatchable-function-entry=2, GCC 8.1.0 compiles the
    following:

    | unsigned long bar(void);
    |
    | unsigned long foo(void)
    | {
    |         return bar() + 1;
    | }

    ... to:

    | <foo>:
    |         nop
    |         nop
    |         stp     x29, x30, [sp, #-16]!
    |         mov     x29, sp
    |         bl      0 <bar>
    |         add     x0, x0, #0x1
    |         ldp     x29, x30, [sp], #16
    |         ret

    This patch builds the kernel with -fpatchable-function-entry=2,
    prefixing each function with two NOPs. To trace a function, we replace
    these NOPs with a sequence that saves the LR into a GPR, then calls an
    ftrace entry assembly function which saves this and other relevant
    registers:

    | mov   x9, x30
    | bl    <ftrace-entry>

    Since patchable functions are AAPCS compliant (and the kernel does not
    use x18 as a platform register), x9-x18 can be safely clobbered in the
    patched sequence and the ftrace entry code.

    There are now two ftrace entry functions, ftrace_regs_entry (which saves
    all GPRs), and ftrace_entry (which saves the bare minimum). A PLT is
    allocated for each within modules.

    Signed-off-by: Torsten Duwe [off-list ref]
    [Mark: rework asm, comments, PLTs, initialization, commit message]
    Signed-off-by: Mark Rutland [off-list ref]
    Reviewed-by: Ard Biesheuvel [off-list ref]
    Cc: AKASHI Takahiro [off-list ref]
    Cc: Amit Daniel Kachhap [off-list ref]
    Cc: Catalin Marinas [off-list ref]
    Cc: Josh Poimboeuf [off-list ref]
    Cc: Julien Thierry [off-list ref]
    Cc: Will Deacon [off-list ref]
    ---
     arch/arm64/Kconfig               |   2 +
     arch/arm64/Makefile              |   5 ++
     arch/arm64/include/asm/ftrace.h  |  23 +++++++
     arch/arm64/include/asm/module.h  |   2 +-
     arch/arm64/kernel/entry-ftrace.S | 140
    +++++++++++++++++++++++++++++++++++++--
     arch/arm64/kernel/ftrace.c       |  84 +++++++++++++++++++----
     arch/arm64/kernel/module-plts.c  |   3 +-
     arch/arm64/kernel/module.c       |  18 +++--
     8 files changed, 252 insertions(+), 25 deletions(-)

    diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
    index 950a56b71ff0..0ffb8596b8a1 100644
    --- a/arch/arm64/Kconfig
    +++ b/arch/arm64/Kconfig
    @@ -143,6 +143,8 @@ config ARM64
            select HAVE_DEBUG_KMEMLEAK
            select HAVE_DMA_CONTIGUOUS
            select HAVE_DYNAMIC_FTRACE
    +       select HAVE_DYNAMIC_FTRACE_WITH_REGS \
    +               if $(cc-option,-fpatchable-function-entry=2)
            select HAVE_EFFICIENT_UNALIGNED_ACCESS
            select HAVE_FAST_GUP
            select HAVE_FTRACE_MCOUNT_RECORD
    diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile
    index 2c0238ce0551..1fbe24d4fdb6 100644
    --- a/arch/arm64/Makefile
    +++ b/arch/arm64/Makefile
    @@ -95,6 +95,11 @@ ifeq ($(CONFIG_ARM64_MODULE_PLTS),y)
     KBUILD_LDS_MODULE      += $(srctree)/arch/arm64/kernel/module.lds
     endif

    +ifeq ($(CONFIG_DYNAMIC_FTRACE_WITH_REGS),y)
    +  KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
    +  CC_FLAGS_FTRACE := -fpatchable-function-entry=2
    +endif
    +
     # Default value
     head-y         := arch/arm64/kernel/head.o

    diff --git a/arch/arm64/include/asm/ftrace.h b/arch/arm64/include/asm/
    ftrace.h
    index d48667b04c41..91fa4baa1a93 100644
    --- a/arch/arm64/include/asm/ftrace.h
    +++ b/arch/arm64/include/asm/ftrace.h
    @@ -11,9 +11,20 @@
     #include <asm/insn.h>

     #define HAVE_FUNCTION_GRAPH_FP_TEST
    +
    +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
    +#define ARCH_SUPPORTS_FTRACE_OPS 1
    +#else
     #define MCOUNT_ADDR            ((unsigned long)_mcount)
    +#endif
    +
    +/* The BL at the callsite's adjusted rec->ip */
     #define MCOUNT_INSN_SIZE       AARCH64_INSN_SIZE

    +#define FTRACE_PLT_IDX         0
    +#define FTRACE_REGS_PLT_IDX    1
    +#define NR_FTRACE_PLTS         2
    +
     /*
      * Currently, gcc tends to save the link register after the local
    variables
      * on the stack. This causes the max stack tracer to report the function
    @@ -44,12 +55,24 @@ extern void return_to_handler(void);
     static inline unsigned long ftrace_call_adjust(unsigned long addr)
     {
            /*
    +        * Adjust addr to point at the BL in the callsite.
    +        * See ftrace_init_nop() for the callsite sequence.
    +        */
    +       if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
    +               return addr + AARCH64_INSN_SIZE;
    +       /*
             * addr is the address of the mcount call instruction.
             * recordmcount does the necessary offset calculation.
             */
            return addr;
     }

    +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
    +struct dyn_ftrace;
    +int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec);
    +#define ftrace_init_nop ftrace_init_nop
    +#endif
    +
     #define ftrace_return_address(n) return_address(n)

     /*
    diff --git a/arch/arm64/include/asm/module.h b/arch/arm64/include/asm/
    module.h
    index f80e13cbf8ec..1e93de68c044 100644
    --- a/arch/arm64/include/asm/module.h
    +++ b/arch/arm64/include/asm/module.h
    @@ -21,7 +21,7 @@ struct mod_arch_specific {
            struct mod_plt_sec      init;

            /* for CONFIG_DYNAMIC_FTRACE */
    -       struct plt_entry        *ftrace_trampoline;
    +       struct plt_entry        *ftrace_trampolines;
     };
     #endif

    diff --git a/arch/arm64/kernel/entry-ftrace.S b/arch/arm64/kernel/
    entry-ftrace.S
    index 33d003d80121..94720388957f 100644
    --- a/arch/arm64/kernel/entry-ftrace.S
    +++ b/arch/arm64/kernel/entry-ftrace.S
    @@ -7,10 +7,137 @@
      */

     #include <linux/linkage.h>
    +#include <asm/asm-offsets.h>
     #include <asm/assembler.h>
     #include <asm/ftrace.h>
     #include <asm/insn.h>

    +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
    +/*
    + * Due to -fpatchable-function-entry=2, the compiler has placed two NOPs
    before
    + * the regular function prologue. For an enabled callsite, ftrace_init_nop
    () and
    + * ftrace_make_call() have patched those NOPs to:
    + *
    + *     MOV     X9, LR
    + *     BL      <entry>
    + *
    + * ... where <entry> is either ftrace_caller or ftrace_regs_caller.
    + *
    + * Each instrumented function follows the AAPCS, so here x0-x8 and x19-x30
    are
    + * live, and x9-x18 are safe to clobber.
    + *
    + * We save the callsite's context into a pt_regs before invoking and
    ftrace
    + * callbacks. So that we can get a sensible backtrace, we create a stack
    record
    + * for the callsite and the ftrace entry assembly. This is not sufficient
    for
    + * reliable stacktrace: until we create the callsite stack record, its
    caller
    + * is missing from the LR and existing chain of frame records.
    + */
    +       .macro  ftrace_regs_entry, allregs=0
    +       /* Make room for pt_regs, plus a callee frame */
    +       sub     sp, sp, #(S_FRAME_SIZE + 16)
    +
    +       /* Save function arguments (and x9 for simplicity) */
    +       stp     x0, x1, [sp, #S_X0]
    +       stp     x2, x3, [sp, #S_X2]
    +       stp     x4, x5, [sp, #S_X4]
    +       stp     x6, x7, [sp, #S_X6]
    +       stp     x8, x9, [sp, #S_X8]
    +
    +       /* Optionally save the callee-saved registers, always save the FP *
    /
    +       .if \allregs == 1
    +       stp     x10, x11, [sp, #S_X10]
    +       stp     x12, x13, [sp, #S_X12]
    +       stp     x14, x15, [sp, #S_X14]
    +       stp     x16, x17, [sp, #S_X16]
    +       stp     x18, x19, [sp, #S_X18]
    +       stp     x20, x21, [sp, #S_X20]
    +       stp     x22, x23, [sp, #S_X22]
    +       stp     x24, x25, [sp, #S_X24]
    +       stp     x26, x27, [sp, #S_X26]
    +       stp     x28, x29, [sp, #S_X28]
    +       .else
    +       str     x29, [sp, #S_FP]
    +       .endif
    +
    +       /* Save the callsite's SP and LR */
    +       add     x10, sp, #(S_FRAME_SIZE + 16)
    +       stp     x9, x10, [sp, #S_LR]
    +
    +       /* Save the PC after the ftrace callsite */
    +       str     x30, [sp, #S_PC]
    +
    +       /* Create a frame record for the callsite above pt_regs */
    +       stp     x29, x9, [sp, #S_FRAME_SIZE]
    +       add     x29, sp, #S_FRAME_SIZE
    +
    +       /* Create our frame record within pt_regs. */
    +       stp     x29, x30, [sp, #S_STACKFRAME]
    +       add     x29, sp, #S_STACKFRAME
    +       .endm
    +
    +ENTRY(ftrace_regs_caller)
    +       ftrace_regs_entry       1
    +       b       ftrace_common
    +ENDPROC(ftrace_regs_caller)
    +
    +ENTRY(ftrace_caller)
    +       ftrace_regs_entry       0
    +       b       ftrace_common
    +ENDPROC(ftrace_caller)
    +
    +ENTRY(ftrace_common)
    +       sub     x0, x30, #AARCH64_INSN_SIZE     // ip (callsite's BL insn)
    +       mov     x1, x9                          // parent_ip (callsite's
    LR)
    +       ldr_l   x2, function_trace_op           // op
    +       mov     x3, sp                          // regs
    +
    +GLOBAL(ftrace_call)
    +       bl      ftrace_stub
    +
    +#ifdef CONFIG_FUNCTION_GRAPH_TRACER
    +GLOBAL(ftrace_graph_call)              // ftrace_graph_caller();
    +       nop                             // If enabled, this will be
    replaced
    +                                       // "b ftrace_graph_caller"
    +#endif
    +
    +/*
    + * At the callsite x0-x8 and x19-x30 were live. Any C code will have
    preserved
    + * x19-x29 per the AAPCS, and we created frame records upon entry, so we
    need
    + * to restore x0-x8, x29, and x30.
    + */
    +ftrace_common_return:
    +       /* Restore function arguments */
    +       ldp     x0, x1, [sp]
    +       ldp     x2, x3, [sp, #S_X2]
    +       ldp     x4, x5, [sp, #S_X4]
    +       ldp     x6, x7, [sp, #S_X6]
    +       ldr     x8, [sp, #S_X8]
    +
    +       /* Restore the callsite's FP, LR, PC */
    +       ldr     x29, [sp, #S_FP]
    +       ldr     x30, [sp, #S_LR]
    +       ldr     x9, [sp, #S_PC]
    +
    +       /* Restore the callsite's SP */
    +       add     sp, sp, #S_FRAME_SIZE + 16
    +
    +       ret     x9
    +ENDPROC(ftrace_common)
    +
    +#ifdef CONFIG_FUNCTION_GRAPH_TRACER
    +ENTRY(ftrace_graph_caller)
    +       ldr     x0, [sp, #S_PC]
    +       sub     x0, x0, #AARCH64_INSN_SIZE      // ip (callsite's BL insn)
    +       add     x1, sp, #S_LR                   // parent_ip (callsite's
    LR)
    +       ldr     x2, [sp, #S_FRAME_SIZE]         // parent fp (callsite's
    FP)
    +       bl      prepare_ftrace_return
    +       b       ftrace_common_return
    +ENDPROC(ftrace_graph_caller)
    +#else
    +#endif
    +
    +#else /* CONFIG_DYNAMIC_FTRACE_WITH_REGS */
    +
     /*
      * Gcc with -pg will put the following code in the beginning of each
    function:
      *      mov x0, x30
    @@ -160,11 +287,6 @@ GLOBAL(ftrace_graph_call)          //
    ftrace_graph_caller();

            mcount_exit
     ENDPROC(ftrace_caller)
    -#endif /* CONFIG_DYNAMIC_FTRACE */
    -
    -ENTRY(ftrace_stub)
    -       ret
    -ENDPROC(ftrace_stub)

     #ifdef CONFIG_FUNCTION_GRAPH_TRACER
     /*
    @@ -184,7 +306,15 @@ ENTRY(ftrace_graph_caller)

            mcount_exit
     ENDPROC(ftrace_graph_caller)
    +#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
    +#endif /* CONFIG_DYNAMIC_FTRACE */
    +#endif /* CONFIG_DYNAMIC_FTRACE_WITH_REGS */
    +
    +ENTRY(ftrace_stub)
    +       ret
    +ENDPROC(ftrace_stub)

    +#ifdef CONFIG_FUNCTION_GRAPH_TRACER
     /*
      * void return_to_handler(void)
      *
    diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
    index 822718eafdb4..aea652c33a38 100644
    --- a/arch/arm64/kernel/ftrace.c
    +++ b/arch/arm64/kernel/ftrace.c
    @@ -62,6 +62,19 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
            return ftrace_modify_code(pc, 0, new, false);
     }

    +#ifdef CONFIG_ARM64_MODULE_PLTS
    +static struct plt_entry *get_ftrace_plt(struct module *mod, unsigned long
    addr)
    +{
    +       struct plt_entry *plt = mod->arch.ftrace_trampolines;
    +
    +       if (addr == FTRACE_ADDR)
    +               return &plt[FTRACE_PLT_IDX];
    +       if (addr == FTRACE_REGS_ADDR && IS_ENABLED
    (CONFIG_FTRACE_WITH_REGS))
    +               return &plt[FTRACE_REGS_PLT_IDX];
    +       return NULL;
    +}
    +#endif
    +
     /*
      * Turn on the call to ftrace_caller() in instrumented function
      */
    @@ -74,19 +87,7 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned
    long addr)
            if (offset < -SZ_128M || offset >= SZ_128M) {
     #ifdef CONFIG_ARM64_MODULE_PLTS
                    struct module *mod;
    -
    -               /*
    -                * There is only one ftrace trampoline per module. For now,
    -                * this is not a problem since on arm64, all dynamic ftrace
    -                * invocations are routed via ftrace_caller(). This will
    need
    -                * to be revisited if support for multiple ftrace entry
    points
    -                * is added in the future, but for now, the pr_err() below
    -                * deals with a theoretical issue only.
    -                */
    -               if (addr != FTRACE_ADDR) {
    -                       pr_err("ftrace: far branches to multiple entry
    points unsupported inside a single module\n");
    -                       return -EINVAL;
    -               }
    +               struct plt_entry *plt;

                    /*
                     * On kernels that support module PLTs, the offset between
    the
    @@ -105,7 +106,13 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned
    long addr)
                    if (WARN_ON(!mod))
                            return -EINVAL;

    -               addr = (unsigned long)mod->arch.ftrace_trampoline;
    +               plt = get_ftrace_plt(mod, addr);
    +               if (!plt) {
    +                       pr_err("ftrace: no module PLT for %ps\n", (void *)
    addr);
    +                       return -EINVAL;
    +               }
    +
    +               addr = (unsigned long)plt;
     #else /* CONFIG_ARM64_MODULE_PLTS */
                    return -EINVAL;
     #endif /* CONFIG_ARM64_MODULE_PLTS */
    @@ -117,6 +124,55 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned
    long addr)
            return ftrace_modify_code(pc, old, new, true);
     }

    +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
    +int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
    +                       unsigned long addr)
    +{
    +       unsigned long pc = rec->ip;
    +       u32 old, new;
    +
    +       old = aarch64_insn_gen_branch_imm(pc, old_addr,
    +                                         AARCH64_INSN_BRANCH_LINK);
    +       new = aarch64_insn_gen_branch_imm(pc, addr,
    AARCH64_INSN_BRANCH_LINK);
    +
    +       return ftrace_modify_code(pc, old, new, true);
    +}
    +
    +/*
    + * The compiler has inserted two NOPs before the regular function
    prologue.
    + * All instrumented functions follow the AAPCS, so x0-x8 and x19-x30 are
    live,
    + * and x9-x18 are free for our use.
    + *
    + * At runtime we want to be able to swing a single NOP <-> BL to enable or
    + * disable the ftrace call. The BL requires us to save the original LR
    value,
    + * so here we insert a <MOV X9, LR> over the first NOP so the instructions
    + * before the regular prologue are:
    + *
    + * | Compiled | Disabled   | Enabled    |
    + * +----------+------------+------------+
    + * | NOP      | MOV X9, LR | MOV X9, LR |
    + * | NOP      | NOP        | BL <entry> |
    + *
    + * The LR value will be recovered by ftrace_regs_entry, and restored into
    LR
    + * before returning to the regular function prologue. When a function is
    not
    + * being traced, the MOV is not harmful given x9 is not live per the
    AAPCS.
    + *
    + * Note: ftrace_process_locs() has pre-adjusted rec->ip to be the address
    of
    + * the BL.
    + */
    +int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
    +{
    +       unsigned long pc = rec->ip - AARCH64_INSN_SIZE;
    +       u32 old, new;
    +
    +       old = aarch64_insn_gen_nop();
    +       new = aarch64_insn_gen_move_reg(AARCH64_INSN_REG_9,
    +                                       AARCH64_INSN_REG_LR,
    +                                       AARCH64_INSN_VARIANT_64BIT);
    +       return ftrace_modify_code(pc, old, new, true);
    +}
    +#endif
    +
     /*
      * Turn off the call to ftrace_caller() in instrumented function
      */
    diff --git a/arch/arm64/kernel/module-plts.c b/arch/arm64/kernel/
    module-plts.c
    index b182442b87a3..65b08a74aec6 100644
    --- a/arch/arm64/kernel/module-plts.c
    +++ b/arch/arm64/kernel/module-plts.c
    @@ -4,6 +4,7 @@
      */

     #include <linux/elf.h>
    +#include <linux/ftrace.h>
     #include <linux/kernel.h>
     #include <linux/module.h>
     #include <linux/sort.h>
    @@ -330,7 +331,7 @@ int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr
    *sechdrs,
                    tramp->sh_type = SHT_NOBITS;
                    tramp->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
                    tramp->sh_addralign = __alignof__(struct plt_entry);
    -               tramp->sh_size = sizeof(struct plt_entry);
    +               tramp->sh_size = NR_FTRACE_PLTS * sizeof(struct plt_entry);
            }

            return 0;
    diff --git a/arch/arm64/kernel/module.c b/arch/arm64/kernel/module.c
    index 5f5bc3b94da7..00d21a420c60 100644
    --- a/arch/arm64/kernel/module.c
    +++ b/arch/arm64/kernel/module.c
    @@ -486,21 +486,31 @@ static const Elf_Shdr *find_section(const Elf_Ehdr
    *hdr,
            return NULL;
     }

    +static inline void __init_plt(struct plt_entry *plt, unsigned long addr)
    +{
    +       *plt = get_plt_entry(addr, plt);
    +}
    +
     int module_init_ftrace_plt(const Elf_Ehdr *hdr,
                               const Elf_Shdr *sechdrs,
                               struct module *mod)
     {
     #if defined(CONFIG_ARM64_MODULE_PLTS) && defined(CONFIG_DYNAMIC_FTRACE)
            const Elf_Shdr *s;
    -       struct plt_entry *plt;
    +       struct plt_entry *plts;

            s = find_section(hdr, sechdrs, ".text.ftrace_trampoline");
            if (!s)
                    return -ENOEXEC;

    -       plt = (void *)s->sh_addr;
    -       *plt = get_plt_entry(FTRACE_ADDR, plt);
    -       mod->arch.ftrace_trampoline = plt;
    +       plts = (void *)s->sh_addr;
    +
    +       __init_plt(&plts[FTRACE_PLT_IDX], FTRACE_ADDR);
    +
    +       if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
    +               __init_plt(&plts[FTRACE_REGS_PLT_IDX], FTRACE_REGS_ADDR);
    +
    +       mod->arch.ftrace_trampolines = plts;
     #endif
            return 0;
     }
    --
    2.11.0


    _______________________________________________
    linux-arm-kernel mailing list
    linux-arm-kernel@lists.infradead.org
    http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help