ftrace: Clear REGS_EN and TRAMP_EN flags on disabling record via sysctl
authorSteven Rostedt (Red Hat) <rostedt@goodmis.org>
Thu, 5 Mar 2015 04:10:28 +0000 (23:10 -0500)
committerSteven Rostedt <rostedt@goodmis.org>
Mon, 9 Mar 2015 14:46:00 +0000 (10:46 -0400)
When /proc/sys/kernel/ftrace_enabled is set to zero, all function
tracing is disabled. But the records that represent the functions
still hold information about the ftrace_ops that are hooked to them.

ftrace_ops may request "REGS" (have a full set of pt_regs passed to
the callback), or "TRAMP" (the ops has its own trampoline to use).
When the record is updated to represent the state of the ops hooked
to it, it sets "REGS_EN" and/or "TRAMP_EN" to state that the callback
points to the correct trampoline (REGS has its own trampoline).

When ftrace_enabled is set to zero, all ftrace locations are a nop,
so they do not point to any trampoline. But the _EN flags are still
set. This can cause the accounting to go wrong when ftrace_enabled
is cleared and an ops that has a trampoline is registered or unregistered.

For example, the following will cause ftrace to crash:

 # echo function_graph > /sys/kernel/debug/tracing/current_tracer
 # echo 0 > /proc/sys/kernel/ftrace_enabled
 # echo nop > /sys/kernel/debug/tracing/current_tracer
 # echo 1 > /proc/sys/kernel/ftrace_enabled
 # echo function_graph > /sys/kernel/debug/tracing/current_tracer

As function_graph uses a trampoline, when ftrace_enabled is set to zero
the updates to the record are not done. When enabling function_graph
again, the record will still have the TRAMP_EN flag set, and it will
look for an op that has a trampoline other than the function_graph
ops, and fail to find one.

Cc: stable@vger.kernel.org # 3.17+
Reported-by: Pratyush Anand <panand@redhat.com>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
kernel/trace/ftrace.c

index 45e5cb1..14947e0 100644 (file)
@@ -2041,8 +2041,12 @@ static int ftrace_check_record(struct dyn_ftrace *rec, int enable, int update)
                if (!ftrace_rec_count(rec))
                        rec->flags = 0;
                else
-                       /* Just disable the record (keep REGS state) */
-                       rec->flags &= ~FTRACE_FL_ENABLED;
+                       /*
+                        * Just disable the record, but keep the ops TRAMP
+                        * and REGS states. The _EN flags must be disabled though.
+                        */
+                       rec->flags &= ~(FTRACE_FL_ENABLED | FTRACE_FL_TRAMP_EN |
+                                       FTRACE_FL_REGS_EN);
        }
 
        return FTRACE_UPDATE_MAKE_NOP;