ftrace: pass module struct to arch dynamic ftrace functions
Steven Rostedt [Sat, 15 Nov 2008 00:21:19 +0000 (16:21 -0800)]
Impact: allow archs more flexibility on dynamic ftrace implementations

Dynamic ftrace has largly been developed on x86. Since x86 does not
have the same limitations as other architectures, the ftrace interaction
between the generic code and the architecture specific code was not
flexible enough to handle some of the issues that other architectures
have.

Most notably, module trampolines. Due to the limited branch distance
that archs make in calling kernel core code from modules, the module
load code must create a trampoline to jump to what will make the
larger jump into core kernel code.

The problem arises when this happens to a call to mcount. Ftrace checks
all code before modifying it and makes sure the current code is what
it expects. Right now, there is not enough information to handle modifying
module trampolines.

This patch changes the API between generic dynamic ftrace code and
the arch dependent code. There is now two functions for modifying code:

  ftrace_make_nop(mod, rec, addr) - convert the code at rec->ip into
       a nop, where the original text is calling addr. (mod is the
       module struct if called by module init)

  ftrace_make_caller(rec, addr) - convert the code rec->ip that should
       be a nop into a caller to addr.

The record "rec" now has a new field called "arch" where the architecture
can add any special attributes to each call site record.

Signed-off-by: Steven Rostedt <srostedt@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>

arch/x86/include/asm/ftrace.h
arch/x86/kernel/ftrace.c
include/linux/ftrace.h
kernel/module.c
kernel/trace/ftrace.c

index 9b6a1fa..2bb43b4 100644 (file)
@@ -17,6 +17,14 @@ static inline unsigned long ftrace_call_adjust(unsigned long addr)
         */
        return addr - 1;
 }
+
+#ifdef CONFIG_DYNAMIC_FTRACE
+
+struct dyn_arch_ftrace {
+       /* No extra data needed for x86 */
+};
+
+#endif /*  CONFIG_DYNAMIC_FTRACE */
 #endif /* __ASSEMBLY__ */
 #endif /* CONFIG_FUNCTION_TRACER */
 
index fe83273..762222a 100644 (file)
@@ -166,7 +166,7 @@ static int ftrace_calc_offset(long ip, long addr)
        return (int)(addr - ip);
 }
 
-unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr)
+static unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr)
 {
        static union ftrace_code_union calc;
 
@@ -311,12 +311,12 @@ do_ftrace_mod_code(unsigned long ip, void *new_code)
 
 static unsigned char ftrace_nop[MCOUNT_INSN_SIZE];
 
-unsigned char *ftrace_nop_replace(void)
+static unsigned char *ftrace_nop_replace(void)
 {
        return ftrace_nop;
 }
 
-int
+static int
 ftrace_modify_code(unsigned long ip, unsigned char *old_code,
                   unsigned char *new_code)
 {
@@ -349,6 +349,29 @@ ftrace_modify_code(unsigned long ip, unsigned char *old_code,
        return 0;
 }
 
+int ftrace_make_nop(struct module *mod,
+                   struct dyn_ftrace *rec, unsigned long addr)
+{
+       unsigned char *new, *old;
+       unsigned long ip = rec->ip;
+
+       old = ftrace_call_replace(ip, addr);
+       new = ftrace_nop_replace();
+
+       return ftrace_modify_code(rec->ip, old, new);
+}
+
+int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
+{
+       unsigned char *new, *old;
+       unsigned long ip = rec->ip;
+
+       old = ftrace_nop_replace();
+       new = ftrace_call_replace(ip, addr);
+
+       return ftrace_modify_code(rec->ip, old, new);
+}
+
 int ftrace_update_ftrace_func(ftrace_func_t func)
 {
        unsigned long ip = (unsigned long)(&ftrace_call);
index 4fbc4a8..166a207 100644 (file)
@@ -74,6 +74,9 @@ static inline void ftrace_start(void) { }
 #endif /* CONFIG_FUNCTION_TRACER */
 
 #ifdef CONFIG_DYNAMIC_FTRACE
+/* asm/ftrace.h must be defined for archs supporting dynamic ftrace */
+#include <asm/ftrace.h>
+
 enum {
        FTRACE_FL_FREE          = (1 << 0),
        FTRACE_FL_FAILED        = (1 << 1),
@@ -88,6 +91,7 @@ struct dyn_ftrace {
        struct list_head        list;
        unsigned long           ip; /* address of mcount call-site */
        unsigned long           flags;
+       struct dyn_arch_ftrace  arch;
 };
 
 int ftrace_force_update(void);
@@ -95,22 +99,40 @@ void ftrace_set_filter(unsigned char *buf, int len, int reset);
 
 /* defined in arch */
 extern int ftrace_ip_converted(unsigned long ip);
-extern unsigned char *ftrace_nop_replace(void);
-extern unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr);
 extern int ftrace_dyn_arch_init(void *data);
 extern int ftrace_update_ftrace_func(ftrace_func_t func);
 extern void ftrace_caller(void);
 extern void ftrace_call(void);
 extern void mcount_call(void);
 
-/* May be defined in arch */
-extern int ftrace_arch_read_dyn_info(char *buf, int size);
+/**
+ * ftrace_make_nop - convert code into top
+ * @mod: module structure if called by module load initialization
+ * @rec: the mcount call site record
+ * @addr: the address that the call site should be calling
+ *
+ * This is a very sensitive operation and great care needs
+ * to be taken by the arch.  The operation should carefully
+ * read the location, check to see if what is read is indeed
+ * what we expect it to be, and then on success of the compare,
+ * it should write to the location.
+ *
+ * The code segment at @rec->ip should be a caller to @addr
+ *
+ * Return must be:
+ *  0 on success
+ *  -EFAULT on error reading the location
+ *  -EINVAL on a failed compare of the contents
+ *  -EPERM  on error writing to the location
+ * Any other value will be considered a failure.
+ */
+extern int ftrace_make_nop(struct module *mod,
+                          struct dyn_ftrace *rec, unsigned long addr);
 
 /**
- * ftrace_modify_code - modify code segment
- * @ip: the address of the code segment
- * @old_code: the contents of what is expected to be there
- * @new_code: the code to patch in
+ * ftrace_make_call - convert a nop call site into a call to addr
+ * @rec: the mcount call site record
+ * @addr: the address that the call site should call
  *
  * This is a very sensitive operation and great care needs
  * to be taken by the arch.  The operation should carefully
@@ -118,6 +140,8 @@ extern int ftrace_arch_read_dyn_info(char *buf, int size);
  * what we expect it to be, and then on success of the compare,
  * it should write to the location.
  *
+ * The code segment at @rec->ip should be a nop
+ *
  * Return must be:
  *  0 on success
  *  -EFAULT on error reading the location
@@ -125,8 +149,11 @@ extern int ftrace_arch_read_dyn_info(char *buf, int size);
  *  -EPERM  on error writing to the location
  * Any other value will be considered a failure.
  */
-extern int ftrace_modify_code(unsigned long ip, unsigned char *old_code,
-                             unsigned char *new_code);
+extern int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr);
+
+
+/* May be defined in arch */
+extern int ftrace_arch_read_dyn_info(char *buf, int size);
 
 extern int skip_trace(unsigned long ip);
 
@@ -259,11 +286,13 @@ static inline void ftrace_dump(void) { }
 
 #ifdef CONFIG_FTRACE_MCOUNT_RECORD
 extern void ftrace_init(void);
-extern void ftrace_init_module(unsigned long *start, unsigned long *end);
+extern void ftrace_init_module(struct module *mod,
+                              unsigned long *start, unsigned long *end);
 #else
 static inline void ftrace_init(void) { }
 static inline void
-ftrace_init_module(unsigned long *start, unsigned long *end) { }
+ftrace_init_module(struct module *mod,
+                  unsigned long *start, unsigned long *end) { }
 #endif
 
 
index 1f4cc00..6979127 100644 (file)
@@ -2201,7 +2201,7 @@ static noinline struct module *load_module(void __user *umod,
        /* sechdrs[0].sh_size is always zero */
        mseg = section_objs(hdr, sechdrs, secstrings, "__mcount_loc",
                            sizeof(*mseg), &num_mcount);
-       ftrace_init_module(mseg, mseg + num_mcount);
+       ftrace_init_module(mod, mseg, mseg + num_mcount);
 
        err = module_finalize(hdr, sechdrs, mod);
        if (err < 0)
index 3940c71..e9a5fbf 100644 (file)
@@ -358,9 +358,7 @@ static void print_ip_ins(const char *fmt, unsigned char *p)
                printk(KERN_CONT "%s%02x", i ? ":" : "", p[i]);
 }
 
-static void ftrace_bug(int failed, unsigned long ip,
-                      unsigned char *expected,
-                      unsigned char *replace)
+static void ftrace_bug(int failed, unsigned long ip)
 {
        switch (failed) {
        case -EFAULT:
@@ -372,9 +370,7 @@ static void ftrace_bug(int failed, unsigned long ip,
                FTRACE_WARN_ON_ONCE(1);
                pr_info("ftrace failed to modify ");
                print_ip_sym(ip);
-               print_ip_ins(" expected: ", expected);
                print_ip_ins(" actual: ", (unsigned char *)ip);
-               print_ip_ins(" replace: ", replace);
                printk(KERN_CONT "\n");
                break;
        case -EPERM:
@@ -392,8 +388,7 @@ static void ftrace_bug(int failed, unsigned long ip,
 #define FTRACE_ADDR ((long)(ftrace_caller))
 
 static int
-__ftrace_replace_code(struct dyn_ftrace *rec,
-                     unsigned char *old, unsigned char *new, int enable)
+__ftrace_replace_code(struct dyn_ftrace *rec, int enable)
 {
        unsigned long ip, fl;
 
@@ -435,12 +430,10 @@ __ftrace_replace_code(struct dyn_ftrace *rec,
                 * otherwise enable it!
                 */
                if (fl & FTRACE_FL_ENABLED) {
-                       /* swap new and old */
-                       new = old;
-                       old = ftrace_call_replace(ip, FTRACE_ADDR);
+                       enable = 0;
                        rec->flags &= ~FTRACE_FL_ENABLED;
                } else {
-                       new = ftrace_call_replace(ip, FTRACE_ADDR);
+                       enable = 1;
                        rec->flags |= FTRACE_FL_ENABLED;
                }
        } else {
@@ -453,10 +446,7 @@ __ftrace_replace_code(struct dyn_ftrace *rec,
                        fl = rec->flags & (FTRACE_FL_NOTRACE | FTRACE_FL_ENABLED);
                        if (fl == FTRACE_FL_NOTRACE)
                                return 0;
-
-                       new = ftrace_call_replace(ip, FTRACE_ADDR);
-               } else
-                       old = ftrace_call_replace(ip, FTRACE_ADDR);
+               }
 
                if (enable) {
                        if (rec->flags & FTRACE_FL_ENABLED)
@@ -469,21 +459,18 @@ __ftrace_replace_code(struct dyn_ftrace *rec,
                }
        }
 
-       return ftrace_modify_code(ip, old, new);
+       if (enable)
+               return ftrace_make_call(rec, FTRACE_ADDR);
+       else
+               return ftrace_make_nop(NULL, rec, FTRACE_ADDR);
 }
 
 static void ftrace_replace_code(int enable)
 {
        int i, failed;
-       unsigned char *new = NULL, *old = NULL;
        struct dyn_ftrace *rec;
        struct ftrace_page *pg;
 
-       if (enable)
-               old = ftrace_nop_replace();
-       else
-               new = ftrace_nop_replace();
-
        for (pg = ftrace_pages_start; pg; pg = pg->next) {
                for (i = 0; i < pg->index; i++) {
                        rec = &pg->records[i];
@@ -504,34 +491,30 @@ static void ftrace_replace_code(int enable)
                                unfreeze_record(rec);
                        }
 
-                       failed = __ftrace_replace_code(rec, old, new, enable);
+                       failed = __ftrace_replace_code(rec, enable);
                        if (failed && (rec->flags & FTRACE_FL_CONVERTED)) {
                                rec->flags |= FTRACE_FL_FAILED;
                                if ((system_state == SYSTEM_BOOTING) ||
                                    !core_kernel_text(rec->ip)) {
                                        ftrace_free_rec(rec);
                                } else
-                                       ftrace_bug(failed, rec->ip, old, new);
+                                       ftrace_bug(failed, rec->ip);
                        }
                }
        }
 }
 
 static int
-ftrace_code_disable(struct dyn_ftrace *rec)
+ftrace_code_disable(struct module *mod, struct dyn_ftrace *rec)
 {
        unsigned long ip;
-       unsigned char *nop, *call;
        int ret;
 
        ip = rec->ip;
 
-       nop = ftrace_nop_replace();
-       call = ftrace_call_replace(ip, mcount_addr);
-
-       ret = ftrace_modify_code(ip, call, nop);
+       ret = ftrace_make_nop(mod, rec, mcount_addr);
        if (ret) {
-               ftrace_bug(ret, ip, call, nop);
+               ftrace_bug(ret, ip);
                rec->flags |= FTRACE_FL_FAILED;
                return 0;
        }
@@ -650,7 +633,7 @@ static cycle_t              ftrace_update_time;
 static unsigned long   ftrace_update_cnt;
 unsigned long          ftrace_update_tot_cnt;
 
-static int ftrace_update_code(void)
+static int ftrace_update_code(struct module *mod)
 {
        struct dyn_ftrace *p, *t;
        cycle_t start, stop;
@@ -667,7 +650,7 @@ static int ftrace_update_code(void)
                list_del_init(&p->list);
 
                /* convert record (i.e, patch mcount-call with NOP) */
-               if (ftrace_code_disable(p)) {
+               if (ftrace_code_disable(mod, p)) {
                        p->flags |= FTRACE_FL_CONVERTED;
                        ftrace_update_cnt++;
                } else
@@ -1309,7 +1292,8 @@ static __init int ftrace_init_debugfs(void)
 
 fs_initcall(ftrace_init_debugfs);
 
-static int ftrace_convert_nops(unsigned long *start,
+static int ftrace_convert_nops(struct module *mod,
+                              unsigned long *start,
                               unsigned long *end)
 {
        unsigned long *p;
@@ -1325,18 +1309,19 @@ static int ftrace_convert_nops(unsigned long *start,
 
        /* disable interrupts to prevent kstop machine */
        local_irq_save(flags);
-       ftrace_update_code();
+       ftrace_update_code(mod);
        local_irq_restore(flags);
        mutex_unlock(&ftrace_start_lock);
 
        return 0;
 }
 
-void ftrace_init_module(unsigned long *start, unsigned long *end)
+void ftrace_init_module(struct module *mod,
+                       unsigned long *start, unsigned long *end)
 {
        if (ftrace_disabled || start == end)
                return;
-       ftrace_convert_nops(start, end);
+       ftrace_convert_nops(mod, start, end);
 }
 
 extern unsigned long __start_mcount_loc[];
@@ -1366,7 +1351,8 @@ void __init ftrace_init(void)
 
        last_ftrace_enabled = ftrace_enabled = 1;
 
-       ret = ftrace_convert_nops(__start_mcount_loc,
+       ret = ftrace_convert_nops(NULL,
+                                 __start_mcount_loc,
                                  __stop_mcount_loc);
 
        return;