]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/x86/kernel/ftrace.c
Merge tag 'imx-clk-common-fixes' of git://git.pengutronix.de/git/imx/linux-2.6 into...
[karo-tx-linux.git] / arch / x86 / kernel / ftrace.c
index 32ff36596ab10d65d8d5050402eb8d24a5f3fb6f..c3a7cb4bf6e6f0f429495d8f4a9478ac9161c0d6 100644 (file)
@@ -100,7 +100,7 @@ static const unsigned char *ftrace_nop_replace(void)
 }
 
 static int
-ftrace_modify_code(unsigned long ip, unsigned const char *old_code,
+ftrace_modify_code_direct(unsigned long ip, unsigned const char *old_code,
                   unsigned const char *new_code)
 {
        unsigned char replaced[MCOUNT_INSN_SIZE];
@@ -141,7 +141,20 @@ int ftrace_make_nop(struct module *mod,
        old = ftrace_call_replace(ip, addr);
        new = ftrace_nop_replace();
 
-       return ftrace_modify_code(rec->ip, old, new);
+       /*
+        * On boot up, and when modules are loaded, the MCOUNT_ADDR
+        * is converted to a nop, and will never become MCOUNT_ADDR
+        * again. This code is either running before SMP (on boot up)
+        * or before the code will ever be executed (module load).
+        * We do not want to use the breakpoint version in this case,
+        * just modify the code directly.
+        */
+       if (addr == MCOUNT_ADDR)
+               return ftrace_modify_code_direct(rec->ip, old, new);
+
+       /* Normal cases use add_brk_on_nop */
+       WARN_ONCE(1, "invalid use of ftrace_make_nop");
+       return -EINVAL;
 }
 
 int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
@@ -152,9 +165,47 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
        old = ftrace_nop_replace();
        new = ftrace_call_replace(ip, addr);
 
-       return ftrace_modify_code(rec->ip, old, new);
+       /* Should only be called when module is loaded */
+       return ftrace_modify_code_direct(rec->ip, old, new);
 }
 
+/*
+ * The modifying_ftrace_code is used to tell the breakpoint
+ * handler to call ftrace_int3_handler(). If it fails to
+ * call this handler for a breakpoint added by ftrace, then
+ * the kernel may crash.
+ *
+ * As atomic_writes on x86 do not need a barrier, we do not
+ * need to add smp_mb()s for this to work. It is also considered
+ * that we can not read the modifying_ftrace_code before
+ * executing the breakpoint. That would be quite remarkable if
+ * it could do that. Here's the flow that is required:
+ *
+ *   CPU-0                          CPU-1
+ *
+ * atomic_inc(mfc);
+ * write int3s
+ *                             <trap-int3> // implicit (r)mb
+ *                             if (atomic_read(mfc))
+ *                                     call ftrace_int3_handler()
+ *
+ * Then when we are finished:
+ *
+ * atomic_dec(mfc);
+ *
+ * If we hit a breakpoint that was not set by ftrace, it does not
+ * matter if ftrace_int3_handler() is called or not. It will
+ * simply be ignored. But it is crucial that a ftrace nop/caller
+ * breakpoint is handled. No other user should ever place a
+ * breakpoint on an ftrace nop/caller location. It must only
+ * be done by this code.
+ */
+atomic_t modifying_ftrace_code __read_mostly;
+
+static int
+ftrace_modify_code(unsigned long ip, unsigned const char *old_code,
+                  unsigned const char *new_code);
+
 int ftrace_update_ftrace_func(ftrace_func_t func)
 {
        unsigned long ip = (unsigned long)(&ftrace_call);
@@ -163,13 +214,17 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
 
        memcpy(old, &ftrace_call, MCOUNT_INSN_SIZE);
        new = ftrace_call_replace(ip, (unsigned long)func);
+
+       /* See comment above by declaration of modifying_ftrace_code */
+       atomic_inc(&modifying_ftrace_code);
+
        ret = ftrace_modify_code(ip, old, new);
 
+       atomic_dec(&modifying_ftrace_code);
+
        return ret;
 }
 
-int modifying_ftrace_code __read_mostly;
-
 /*
  * A breakpoint was added to the code address we are about to
  * modify, and this is the handle that will just skip over it.
@@ -489,13 +544,46 @@ void ftrace_replace_code(int enable)
        }
 }
 
+static int
+ftrace_modify_code(unsigned long ip, unsigned const char *old_code,
+                  unsigned const char *new_code)
+{
+       int ret;
+
+       ret = add_break(ip, old_code);
+       if (ret)
+               goto out;
+
+       run_sync();
+
+       ret = add_update_code(ip, new_code);
+       if (ret)
+               goto fail_update;
+
+       run_sync();
+
+       ret = ftrace_write(ip, new_code, 1);
+       if (ret) {
+               ret = -EPERM;
+               goto out;
+       }
+       run_sync();
+ out:
+       return ret;
+
+ fail_update:
+       probe_kernel_write((void *)ip, &old_code[0], 1);
+       goto out;
+}
+
 void arch_ftrace_update_code(int command)
 {
-       modifying_ftrace_code++;
+       /* See comment above by declaration of modifying_ftrace_code */
+       atomic_inc(&modifying_ftrace_code);
 
        ftrace_modify_all_code(command);
 
-       modifying_ftrace_code--;
+       atomic_dec(&modifying_ftrace_code);
 }
 
 int __init ftrace_dyn_arch_init(void *data)