linux/arch/arm/kernel/ftrace.c
<<
>>
Prefs
   1/*
   2 * Dynamic function tracing support.
   3 *
   4 * Copyright (C) 2008 Abhishek Sagar <sagar.abhishek@gmail.com>
   5 * Copyright (C) 2010 Rabin Vincent <rabin@rab.in>
   6 *
   7 * For licencing details, see COPYING.
   8 *
   9 * Defines low-level handling of mcount calls when the kernel
  10 * is compiled with the -pg flag. When using dynamic ftrace, the
  11 * mcount call-sites get patched with NOP till they are enabled.
  12 * All code mutation routines here are called under stop_machine().
  13 */
  14
  15#include <linux/ftrace.h>
  16#include <linux/uaccess.h>
  17#include <linux/module.h>
  18#include <linux/stop_machine.h>
  19
  20#include <asm/cacheflush.h>
  21#include <asm/opcodes.h>
  22#include <asm/ftrace.h>
  23#include <asm/insn.h>
  24#include <asm/set_memory.h>
  25#include <asm/patch.h>
  26
  27#ifdef CONFIG_THUMB2_KERNEL
  28#define NOP             0xf85deb04      /* pop.w {lr} */
  29#else
  30#define NOP             0xe8bd4000      /* pop {lr} */
  31#endif
  32
  33#ifdef CONFIG_DYNAMIC_FTRACE
  34
  35static int __ftrace_modify_code(void *data)
  36{
  37        int *command = data;
  38
  39        ftrace_modify_all_code(*command);
  40
  41        return 0;
  42}
  43
  44void arch_ftrace_update_code(int command)
  45{
  46        stop_machine(__ftrace_modify_code, &command, NULL);
  47}
  48
  49static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
  50{
  51        return NOP;
  52}
  53
  54static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
  55{
  56        return addr;
  57}
  58
  59int ftrace_arch_code_modify_prepare(void)
  60{
  61        return 0;
  62}
  63
  64int ftrace_arch_code_modify_post_process(void)
  65{
  66        /* Make sure any TLB misses during machine stop are cleared. */
  67        flush_tlb_all();
  68        return 0;
  69}
  70
  71static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
  72{
  73        return arm_gen_branch_link(pc, addr);
  74}
  75
  76static int ftrace_modify_code(unsigned long pc, unsigned long old,
  77                              unsigned long new, bool validate)
  78{
  79        unsigned long replaced;
  80
  81        if (IS_ENABLED(CONFIG_THUMB2_KERNEL))
  82                old = __opcode_to_mem_thumb32(old);
  83        else
  84                old = __opcode_to_mem_arm(old);
  85
  86        if (validate) {
  87                if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE))
  88                        return -EFAULT;
  89
  90                if (replaced != old)
  91                        return -EINVAL;
  92        }
  93
  94        __patch_text((void *)pc, new);
  95
  96        return 0;
  97}
  98
  99int ftrace_update_ftrace_func(ftrace_func_t func)
 100{
 101        unsigned long pc;
 102        unsigned long new;
 103        int ret;
 104
 105        pc = (unsigned long)&ftrace_call;
 106        new = ftrace_call_replace(pc, (unsigned long)func);
 107
 108        ret = ftrace_modify_code(pc, 0, new, false);
 109
 110#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
 111        if (!ret) {
 112                pc = (unsigned long)&ftrace_regs_call;
 113                new = ftrace_call_replace(pc, (unsigned long)func);
 114
 115                ret = ftrace_modify_code(pc, 0, new, false);
 116        }
 117#endif
 118
 119        return ret;
 120}
 121
 122int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
 123{
 124        unsigned long new, old;
 125        unsigned long ip = rec->ip;
 126
 127        old = ftrace_nop_replace(rec);
 128
 129        new = ftrace_call_replace(ip, adjust_address(rec, addr));
 130
 131        return ftrace_modify_code(rec->ip, old, new, true);
 132}
 133
 134#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
 135
 136int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
 137                                unsigned long addr)
 138{
 139        unsigned long new, old;
 140        unsigned long ip = rec->ip;
 141
 142        old = ftrace_call_replace(ip, adjust_address(rec, old_addr));
 143
 144        new = ftrace_call_replace(ip, adjust_address(rec, addr));
 145
 146        return ftrace_modify_code(rec->ip, old, new, true);
 147}
 148
 149#endif
 150
 151int ftrace_make_nop(struct module *mod,
 152                    struct dyn_ftrace *rec, unsigned long addr)
 153{
 154        unsigned long ip = rec->ip;
 155        unsigned long old;
 156        unsigned long new;
 157        int ret;
 158
 159        old = ftrace_call_replace(ip, adjust_address(rec, addr));
 160        new = ftrace_nop_replace(rec);
 161        ret = ftrace_modify_code(ip, old, new, true);
 162
 163        return ret;
 164}
 165
 166int __init ftrace_dyn_arch_init(void)
 167{
 168        return 0;
 169}
 170#endif /* CONFIG_DYNAMIC_FTRACE */
 171
 172#ifdef CONFIG_FUNCTION_GRAPH_TRACER
 173void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
 174                           unsigned long frame_pointer)
 175{
 176        unsigned long return_hooker = (unsigned long) &return_to_handler;
 177        unsigned long old;
 178
 179        if (unlikely(atomic_read(&current->tracing_graph_pause)))
 180                return;
 181
 182        old = *parent;
 183        *parent = return_hooker;
 184
 185        if (function_graph_enter(old, self_addr, frame_pointer, NULL))
 186                *parent = old;
 187}
 188
 189#ifdef CONFIG_DYNAMIC_FTRACE
 190extern unsigned long ftrace_graph_call;
 191extern unsigned long ftrace_graph_call_old;
 192extern void ftrace_graph_caller_old(void);
 193extern unsigned long ftrace_graph_regs_call;
 194extern void ftrace_graph_regs_caller(void);
 195
 196static int __ftrace_modify_caller(unsigned long *callsite,
 197                                  void (*func) (void), bool enable)
 198{
 199        unsigned long caller_fn = (unsigned long) func;
 200        unsigned long pc = (unsigned long) callsite;
 201        unsigned long branch = arm_gen_branch(pc, caller_fn);
 202        unsigned long nop = 0xe1a00000; /* mov r0, r0 */
 203        unsigned long old = enable ? nop : branch;
 204        unsigned long new = enable ? branch : nop;
 205
 206        return ftrace_modify_code(pc, old, new, true);
 207}
 208
 209static int ftrace_modify_graph_caller(bool enable)
 210{
 211        int ret;
 212
 213        ret = __ftrace_modify_caller(&ftrace_graph_call,
 214                                     ftrace_graph_caller,
 215                                     enable);
 216
 217#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
 218        if (!ret)
 219                ret = __ftrace_modify_caller(&ftrace_graph_regs_call,
 220                                     ftrace_graph_regs_caller,
 221                                     enable);
 222#endif
 223
 224
 225        return ret;
 226}
 227
 228int ftrace_enable_ftrace_graph_caller(void)
 229{
 230        return ftrace_modify_graph_caller(true);
 231}
 232
 233int ftrace_disable_ftrace_graph_caller(void)
 234{
 235        return ftrace_modify_graph_caller(false);
 236}
 237#endif /* CONFIG_DYNAMIC_FTRACE */
 238#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
 239