ftrace: use dynamic patching for updating mcount calls
authorSteven Rostedt <srostedt@redhat.com>
Mon, 12 May 2008 19:20:43 +0000 (21:20 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Fri, 23 May 2008 18:33:47 +0000 (20:33 +0200)
This patch replaces the indirect call to the mcount function
pointer with a direct call that will be patched by the
dynamic ftrace routines.

On boot up, the mcount function calls the ftace_stub function.
When the dynamic ftrace code is initialized, the ftrace_stub
is replaced with a call to the ftrace_record_ip, which records
the instruction pointers of the locations that call it.

Later, the ftraced daemon will call kstop_machine and patch all
the locations to nops.

When a ftrace is enabled, the original calls to mcount will now
be set top call ftrace_caller, which will do a direct call
to the registered ftrace function. This direct call is also patched
when the function that should be called is updated.

All patching is performed by a kstop_machine routine to prevent any
type of race conditions that is associated with modifying code
on the fly.

Signed-off-by: Steven Rostedt <srostedt@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
arch/x86/kernel/entry_32.S
arch/x86/kernel/entry_64.S
arch/x86/kernel/ftrace.c
include/linux/ftrace.h
kernel/trace/ftrace.c

index f47b9b5..e6517ce 100644 (file)
@@ -1110,10 +1110,50 @@ ENDPROC(xen_failsafe_callback)
 #endif /* CONFIG_XEN */
 
 #ifdef CONFIG_FTRACE
+#ifdef CONFIG_DYNAMIC_FTRACE
+
+ENTRY(mcount)
+       pushl %eax
+       pushl %ecx
+       pushl %edx
+       movl 0xc(%esp), %eax
+
+.globl mcount_call
+mcount_call:
+       call ftrace_stub
+
+       popl %edx
+       popl %ecx
+       popl %eax
+
+       ret
+END(mcount)
+
+ENTRY(ftrace_caller)
+       pushl %eax
+       pushl %ecx
+       pushl %edx
+       movl 0xc(%esp), %eax
+       movl 0x4(%ebp), %edx
+
+.globl ftrace_call
+ftrace_call:
+       call ftrace_stub
+
+       popl %edx
+       popl %ecx
+       popl %eax
+
+.globl ftrace_stub
+ftrace_stub:
+       ret
+END(ftrace_caller)
+
+#else /* ! CONFIG_DYNAMIC_FTRACE */
+
 ENTRY(mcount)
        cmpl $ftrace_stub, ftrace_trace_function
        jnz trace
-
 .globl ftrace_stub
 ftrace_stub:
        ret
@@ -1126,7 +1166,7 @@ trace:
        movl 0xc(%esp), %eax
        movl 0x4(%ebp), %edx
 
-       call   *ftrace_trace_function
+       call *ftrace_trace_function
 
        popl %edx
        popl %ecx
@@ -1134,7 +1174,8 @@ trace:
 
        jmp ftrace_stub
 END(mcount)
-#endif
+#endif /* CONFIG_DYNAMIC_FTRACE */
+#endif /* CONFIG_FTRACE */
 
 .section .rodata,"a"
 #include "syscall_table_32.S"
index f046e0c..fe25e5f 100644 (file)
        .code64
 
 #ifdef CONFIG_FTRACE
+#ifdef CONFIG_DYNAMIC_FTRACE
+ENTRY(mcount)
+
+       subq $0x38, %rsp
+       movq %rax, (%rsp)
+       movq %rcx, 8(%rsp)
+       movq %rdx, 16(%rsp)
+       movq %rsi, 24(%rsp)
+       movq %rdi, 32(%rsp)
+       movq %r8, 40(%rsp)
+       movq %r9, 48(%rsp)
+
+       movq 0x38(%rsp), %rdi
+
+.globl mcount_call
+mcount_call:
+       call ftrace_stub
+
+       movq 48(%rsp), %r9
+       movq 40(%rsp), %r8
+       movq 32(%rsp), %rdi
+       movq 24(%rsp), %rsi
+       movq 16(%rsp), %rdx
+       movq 8(%rsp), %rcx
+       movq (%rsp), %rax
+       addq $0x38, %rsp
+
+       retq
+END(mcount)
+
+ENTRY(ftrace_caller)
+
+       /* taken from glibc */
+       subq $0x38, %rsp
+       movq %rax, (%rsp)
+       movq %rcx, 8(%rsp)
+       movq %rdx, 16(%rsp)
+       movq %rsi, 24(%rsp)
+       movq %rdi, 32(%rsp)
+       movq %r8, 40(%rsp)
+       movq %r9, 48(%rsp)
+
+       movq 0x38(%rsp), %rdi
+       movq 8(%rbp), %rsi
+
+.globl ftrace_call
+ftrace_call:
+       call ftrace_stub
+
+       movq 48(%rsp), %r9
+       movq 40(%rsp), %r8
+       movq 32(%rsp), %rdi
+       movq 24(%rsp), %rsi
+       movq 16(%rsp), %rdx
+       movq 8(%rsp), %rcx
+       movq (%rsp), %rax
+       addq $0x38, %rsp
+
+.globl ftrace_stub
+ftrace_stub:
+       retq
+END(ftrace_caller)
+
+#else /* ! CONFIG_DYNAMIC_FTRACE */
 ENTRY(mcount)
        cmpq $ftrace_stub, ftrace_trace_function
        jnz trace
@@ -89,7 +153,8 @@ trace:
 
        jmp ftrace_stub
 END(mcount)
-#endif
+#endif /* CONFIG_DYNAMIC_FTRACE */
+#endif /* CONFIG_FTRACE */
 
 #ifndef CONFIG_PREEMPT
 #define retint_kernel retint_restore_args
index b69795e..9f44623 100644 (file)
@@ -109,10 +109,49 @@ ftrace_modify_code(unsigned long ip, unsigned char *old_code,
        return faulted;
 }
 
-int __init ftrace_dyn_arch_init(void)
+notrace int ftrace_update_ftrace_func(ftrace_func_t func)
+{
+       unsigned long ip = (unsigned long)(&ftrace_call);
+       unsigned char old[5], *new;
+       int ret;
+
+       ip += CALL_BACK;
+
+       memcpy(old, &ftrace_call, 5);
+       new = ftrace_call_replace(ip, (unsigned long)func);
+       ret = ftrace_modify_code(ip, old, new);
+
+       return ret;
+}
+
+notrace int ftrace_mcount_set(unsigned long *data)
+{
+       unsigned long ip = (long)(&mcount_call);
+       unsigned long *addr = data;
+       unsigned char old[5], *new;
+
+       /* ip is at the location, but modify code will subtact this */
+       ip += CALL_BACK;
+
+       /*
+        * Replace the mcount stub with a pointer to the
+        * ip recorder function.
+        */
+       memcpy(old, &mcount_call, 5);
+       new = ftrace_call_replace(ip, *addr);
+       *addr = ftrace_modify_code(ip, old, new);
+
+       return 0;
+}
+
+int __init ftrace_dyn_arch_init(void *data)
 {
        const unsigned char *const *noptable = find_nop_table();
 
+       /* This is running in kstop_machine */
+
+       ftrace_mcount_set(data);
+
        ftrace_nop = (unsigned long *)noptable[CALL_BACK];
 
        return 0;
index d509ad6..b0dd009 100644 (file)
@@ -56,9 +56,14 @@ struct dyn_ftrace {
 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);
+extern int ftrace_dyn_arch_init(void *data);
+extern int ftrace_mcount_set(unsigned long *data);
 extern int ftrace_modify_code(unsigned long ip, unsigned char *old_code,
                              unsigned char *new_code);
+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);
 #endif
 
 #ifdef CONFIG_FRAME_POINTER
index f6d9af3..88544f9 100644 (file)
 
 #include "trace.h"
 
-#ifdef CONFIG_DYNAMIC_FTRACE
-# define FTRACE_ENABLED_INIT 1
-#else
-# define FTRACE_ENABLED_INIT 0
-#endif
-
-int ftrace_enabled = FTRACE_ENABLED_INIT;
-static int last_ftrace_enabled = FTRACE_ENABLED_INIT;
+int ftrace_enabled;
+static int last_ftrace_enabled;
 
 static DEFINE_SPINLOCK(ftrace_lock);
 static DEFINE_MUTEX(ftrace_sysctl_lock);
@@ -149,6 +143,14 @@ static int notrace __unregister_ftrace_function(struct ftrace_ops *ops)
 
 #ifdef CONFIG_DYNAMIC_FTRACE
 
+enum {
+       FTRACE_ENABLE_CALLS             = (1 << 0),
+       FTRACE_DISABLE_CALLS            = (1 << 1),
+       FTRACE_UPDATE_TRACE_FUNC        = (1 << 2),
+       FTRACE_ENABLE_MCOUNT            = (1 << 3),
+       FTRACE_DISABLE_MCOUNT           = (1 << 4),
+};
+
 static struct hlist_head ftrace_hash[FTRACE_HASHSIZE];
 
 static DEFINE_PER_CPU(int, ftrace_shutdown_disable_cpu);
@@ -199,12 +201,8 @@ ftrace_add_hash(struct dyn_ftrace *node, unsigned long key)
        hlist_add_head(&node->node, &ftrace_hash[key]);
 }
 
-static notrace struct dyn_ftrace *ftrace_alloc_shutdown_node(unsigned long ip)
+static notrace struct dyn_ftrace *ftrace_alloc_dyn_node(unsigned long ip)
 {
-       /* If this was already converted, skip it */
-       if (ftrace_ip_converted(ip))
-               return NULL;
-
        if (ftrace_pages->index == ENTRIES_PER_PAGE) {
                if (!ftrace_pages->next)
                        return NULL;
@@ -215,7 +213,7 @@ static notrace struct dyn_ftrace *ftrace_alloc_shutdown_node(unsigned long ip)
 }
 
 static void notrace
-ftrace_record_ip(unsigned long ip, unsigned long parent_ip)
+ftrace_record_ip(unsigned long ip)
 {
        struct dyn_ftrace *node;
        unsigned long flags;
@@ -223,6 +221,9 @@ ftrace_record_ip(unsigned long ip, unsigned long parent_ip)
        int resched;
        int atomic;
 
+       if (!ftrace_enabled)
+               return;
+
        resched = need_resched();
        preempt_disable_notrace();
 
@@ -251,11 +252,12 @@ ftrace_record_ip(unsigned long ip, unsigned long parent_ip)
 
        /*
         * There's a slight race that the ftraced will update the
-        * hash and reset here. The arch alloc is responsible
-        * for seeing if the IP has already changed, and if
-        * it has, the alloc will fail.
+        * hash and reset here. If it is already converted, skip it.
         */
-       node = ftrace_alloc_shutdown_node(ip);
+       if (ftrace_ip_converted(ip))
+               goto out_unlock;
+
+       node = ftrace_alloc_dyn_node(ip);
        if (!node)
                goto out_unlock;
 
@@ -277,11 +279,7 @@ ftrace_record_ip(unsigned long ip, unsigned long parent_ip)
                preempt_enable_notrace();
 }
 
-static struct ftrace_ops ftrace_shutdown_ops __read_mostly =
-{
-       .func = ftrace_record_ip,
-};
-
+#define FTRACE_ADDR ((long)(&ftrace_caller))
 #define MCOUNT_ADDR ((long)(&mcount))
 
 static void notrace ftrace_replace_code(int saved)
@@ -309,9 +307,9 @@ static void notrace ftrace_replace_code(int saved)
                        ip = rec->ip;
 
                        if (saved)
-                               new = ftrace_call_replace(ip, MCOUNT_ADDR);
+                               new = ftrace_call_replace(ip, FTRACE_ADDR);
                        else
-                               old = ftrace_call_replace(ip, MCOUNT_ADDR);
+                               old = ftrace_call_replace(ip, FTRACE_ADDR);
 
                        failed = ftrace_modify_code(ip, old, new);
                        if (failed)
@@ -320,16 +318,6 @@ static void notrace ftrace_replace_code(int saved)
        }
 }
 
-static notrace void ftrace_startup_code(void)
-{
-       ftrace_replace_code(1);
-}
-
-static notrace void ftrace_shutdown_code(void)
-{
-       ftrace_replace_code(0);
-}
-
 static notrace void ftrace_shutdown_replenish(void)
 {
        if (ftrace_pages->next)
@@ -339,16 +327,8 @@ static notrace void ftrace_shutdown_replenish(void)
        ftrace_pages->next = (void *)get_zeroed_page(GFP_KERNEL);
 }
 
-static int notrace __ftrace_modify_code(void *data)
-{
-       void (*func)(void) = data;
-
-       func();
-       return 0;
-}
-
 static notrace void
-ftrace_code_disable(struct dyn_ftrace *rec, unsigned long addr)
+ftrace_code_disable(struct dyn_ftrace *rec)
 {
        unsigned long ip;
        unsigned char *nop, *call;
@@ -357,67 +337,113 @@ ftrace_code_disable(struct dyn_ftrace *rec, unsigned long addr)
        ip = rec->ip;
 
        nop = ftrace_nop_replace();
-       call = ftrace_call_replace(ip, addr);
+       call = ftrace_call_replace(ip, MCOUNT_ADDR);
 
        failed = ftrace_modify_code(ip, call, nop);
        if (failed)
                rec->flags |= FTRACE_FL_FAILED;
 }
 
-static void notrace ftrace_run_startup_code(void)
+static int notrace __ftrace_modify_code(void *data)
 {
-       stop_machine_run(__ftrace_modify_code, ftrace_startup_code, NR_CPUS);
+       unsigned long addr;
+       int *command = data;
+
+       if (*command & FTRACE_ENABLE_CALLS)
+               ftrace_replace_code(1);
+       else if (*command & FTRACE_DISABLE_CALLS)
+               ftrace_replace_code(0);
+
+       if (*command & FTRACE_UPDATE_TRACE_FUNC)
+               ftrace_update_ftrace_func(ftrace_trace_function);
+
+       if (*command & FTRACE_ENABLE_MCOUNT) {
+               addr = (unsigned long)ftrace_record_ip;
+               ftrace_mcount_set(&addr);
+       } else if (*command & FTRACE_DISABLE_MCOUNT) {
+               addr = (unsigned long)ftrace_stub;
+               ftrace_mcount_set(&addr);
+       }
+
+       return 0;
 }
 
-static void notrace ftrace_run_shutdown_code(void)
+static void notrace ftrace_run_update_code(int command)
 {
-       stop_machine_run(__ftrace_modify_code, ftrace_shutdown_code, NR_CPUS);
+       stop_machine_run(__ftrace_modify_code, &command, NR_CPUS);
 }
 
+static ftrace_func_t saved_ftrace_func;
+
 static void notrace ftrace_startup(void)
 {
+       int command = 0;
+
        mutex_lock(&ftraced_lock);
        ftraced_suspend++;
-       if (ftraced_suspend != 1)
+       if (ftraced_suspend == 1)
+               command |= FTRACE_ENABLE_CALLS;
+
+       if (saved_ftrace_func != ftrace_trace_function) {
+               saved_ftrace_func = ftrace_trace_function;
+               command |= FTRACE_UPDATE_TRACE_FUNC;
+       }
+
+       if (!command || !ftrace_enabled)
                goto out;
-       __unregister_ftrace_function(&ftrace_shutdown_ops);
 
-       if (ftrace_enabled)
-               ftrace_run_startup_code();
+       ftrace_run_update_code(command);
  out:
        mutex_unlock(&ftraced_lock);
 }
 
 static void notrace ftrace_shutdown(void)
 {
+       int command = 0;
+
        mutex_lock(&ftraced_lock);
        ftraced_suspend--;
-       if (ftraced_suspend)
-               goto out;
+       if (!ftraced_suspend)
+               command |= FTRACE_DISABLE_CALLS;
 
-       if (ftrace_enabled)
-               ftrace_run_shutdown_code();
+       if (saved_ftrace_func != ftrace_trace_function) {
+               saved_ftrace_func = ftrace_trace_function;
+               command |= FTRACE_UPDATE_TRACE_FUNC;
+       }
 
-       __register_ftrace_function(&ftrace_shutdown_ops);
+       if (!command || !ftrace_enabled)
+               goto out;
+
+       ftrace_run_update_code(command);
  out:
        mutex_unlock(&ftraced_lock);
 }
 
 static void notrace ftrace_startup_sysctl(void)
 {
+       int command = FTRACE_ENABLE_MCOUNT;
+
        mutex_lock(&ftraced_lock);
+       /* Force update next time */
+       saved_ftrace_func = NULL;
        /* ftraced_suspend is true if we want ftrace running */
        if (ftraced_suspend)
-               ftrace_run_startup_code();
+               command |= FTRACE_ENABLE_CALLS;
+
+       ftrace_run_update_code(command);
        mutex_unlock(&ftraced_lock);
 }
 
 static void notrace ftrace_shutdown_sysctl(void)
 {
+       int command = FTRACE_DISABLE_MCOUNT;
+
        mutex_lock(&ftraced_lock);
        /* ftraced_suspend is true if ftrace is running */
        if (ftraced_suspend)
-               ftrace_run_shutdown_code();
+               command |= FTRACE_DISABLE_CALLS;
+
+       ftrace_run_update_code(command);
        mutex_unlock(&ftraced_lock);
 }
 
@@ -430,11 +456,13 @@ static int notrace __ftrace_update_code(void *ignore)
        struct dyn_ftrace *p;
        struct hlist_head head;
        struct hlist_node *t;
+       int save_ftrace_enabled;
        cycle_t start, stop;
        int i;
 
-       /* Don't be calling ftrace ops now */
-       __unregister_ftrace_function(&ftrace_shutdown_ops);
+       /* Don't be recording funcs now */
+       save_ftrace_enabled = ftrace_enabled;
+       ftrace_enabled = 0;
 
        start = now(raw_smp_processor_id());
        ftrace_update_cnt = 0;
@@ -449,7 +477,7 @@ static int notrace __ftrace_update_code(void *ignore)
 
                /* all CPUS are stopped, we are safe to modify code */
                hlist_for_each_entry(p, t, &head, node) {
-                       ftrace_code_disable(p, MCOUNT_ADDR);
+                       ftrace_code_disable(p);
                        ftrace_update_cnt++;
                }
 
@@ -459,7 +487,7 @@ static int notrace __ftrace_update_code(void *ignore)
        ftrace_update_time = stop - start;
        ftrace_update_tot_cnt += ftrace_update_cnt;
 
-       __register_ftrace_function(&ftrace_shutdown_ops);
+       ftrace_enabled = save_ftrace_enabled;
 
        return 0;
 }
@@ -515,11 +543,6 @@ static int __init ftrace_dyn_table_alloc(void)
        struct ftrace_page *pg;
        int cnt;
        int i;
-       int ret;
-
-       ret = ftrace_dyn_arch_init();
-       if (ret)
-               return ret;
 
        /* allocate a few pages */
        ftrace_pages_start = (void *)get_zeroed_page(GFP_KERNEL);
@@ -557,11 +580,19 @@ static int __init ftrace_dyn_table_alloc(void)
        return 0;
 }
 
-static int __init notrace ftrace_shutdown_init(void)
+static int __init notrace ftrace_dynamic_init(void)
 {
        struct task_struct *p;
+       unsigned long addr;
        int ret;
 
+       addr = (unsigned long)ftrace_record_ip;
+       stop_machine_run(ftrace_dyn_arch_init, &addr, NR_CPUS);
+
+       /* ftrace_dyn_arch_init places the return code in addr */
+       if (addr)
+               return addr;
+
        ret = ftrace_dyn_table_alloc();
        if (ret)
                return ret;
@@ -570,12 +601,12 @@ static int __init notrace ftrace_shutdown_init(void)
        if (IS_ERR(p))
                return -1;
 
-       __register_ftrace_function(&ftrace_shutdown_ops);
+       last_ftrace_enabled = ftrace_enabled = 1;
 
        return 0;
 }
 
-core_initcall(ftrace_shutdown_init);
+core_initcall(ftrace_dynamic_init);
 #else
 # define ftrace_startup()        do { } while (0)
 # define ftrace_shutdown()       do { } while (0)
@@ -599,9 +630,8 @@ int register_ftrace_function(struct ftrace_ops *ops)
        int ret;
 
        mutex_lock(&ftrace_sysctl_lock);
-       ftrace_startup();
-
        ret = __register_ftrace_function(ops);
+       ftrace_startup();
        mutex_unlock(&ftrace_sysctl_lock);
 
        return ret;
@@ -619,10 +649,7 @@ int unregister_ftrace_function(struct ftrace_ops *ops)
 
        mutex_lock(&ftrace_sysctl_lock);
        ret = __unregister_ftrace_function(ops);
-
-       if (ftrace_list == &ftrace_list_end)
-               ftrace_shutdown();
-
+       ftrace_shutdown();
        mutex_unlock(&ftrace_sysctl_lock);
 
        return ret;