diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c
index ed455b535..14733f519 100644
--- a/kernel/trace/fgraph.c
+++ b/kernel/trace/fgraph.c
@@ -1420,13 +1420,26 @@ int register_ftrace_graph(struct fgraph_ops *gops)
return ret;
}
-void unregister_ftrace_graph(struct fgraph_ops *gops)
+/**
+ * unregister_ftrace_graph_start - unpublish a function graph operation
+ * @gops: graph operation to remove
+ *
+ * Remove @gops from graph dispatch, release its array slot, and update the
+ * graph and subop state. Existing graph callbacks may still be using @gops
+ * after this returns.
+ *
+ * Return: true if @gops was unpublished. The caller must then invoke
+ * unregister_ftrace_graph_finish() exactly once before releasing @gops or its
+ * filter, even if the subop hash update failed.
+ */
+bool unregister_ftrace_graph_start(struct fgraph_ops *gops)
{
int command = 0;
+ bool removed = false;
if (WARN_ONCE(!(gops->ops.flags & FTRACE_OPS_FL_GRAPH),
"function graph ops unregistered without registering"))
- return;
+ return false;
guard(mutex)(&ftrace_lock);
@@ -1440,7 +1453,8 @@ void unregister_ftrace_graph(struct fgraph_ops *gops)
if (fgraph_lru_release_index(gops->idx) < 0)
goto out;
- fgraph_array[gops->idx] = &fgraph_stub;
+ WRITE_ONCE(fgraph_array[gops->idx], &fgraph_stub);
+ removed = true;
ftrace_graph_active--;
@@ -1460,7 +1474,32 @@ void unregister_ftrace_graph(struct fgraph_ops *gops)
unregister_pm_notifier(&ftrace_suspend_notifier);
unregister_trace_sched_switch(ftrace_graph_probe_sched_switch, NULL);
}
- gops->saved_func = NULL;
out:
gops->ops.flags &= ~FTRACE_OPS_FL_GRAPH;
+ return removed;
+}
+
+/**
+ * unregister_ftrace_graph_finish - wait for readers of an unpublished operation
+ * @gops: graph operation previously unpublished by unregister_ftrace_graph_start()
+ *
+ * Wait for existing graph readers, then clear the saved callback. This may
+ * sleep. The caller may release @gops and its filter after this returns.
+ */
+void unregister_ftrace_graph_finish(struct fgraph_ops *gops)
+{
+ /*
+ * The graph callbacks run with preemption disabled and may run while
+ * RCU is not watching. Wait for both kinds of readers before the
+ * caller can release the graph ops or its filter.
+ */
+ synchronize_rcu_tasks_rude();
+ synchronize_rcu_tasks();
+ gops->saved_func = NULL;
+}
+
+void unregister_ftrace_graph(struct fgraph_ops *gops)
+{
+ if (unregister_ftrace_graph_start(gops))
+ unregister_ftrace_graph_finish(gops);
}diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c
index da286619c..c53ad37a0 100644
--- a/kernel/trace/fprobe.c
+++ b/kernel/trace/fprobe.c
@@ -14,9 +14,11 @@
#include <linux/rhashtable.h>
#include <linux/slab.h>
#include <linux/sort.h>
+#include <linux/workqueue.h>
#include <asm/fprobe.h>
+#include "ftrace_internal.h"
#include "trace.h"
#define FPROBE_IP_HASH_BITS 8
@@ -279,6 +281,14 @@ static struct fgraph_ops fprobe_graph_ops = {
static int nr_fgraph_fprobes;
/* Is fprobe_graph_ops registered? */
static bool fprobe_graph_registered;
+static void fprobe_graph_cleanup_workfn(struct work_struct *work);
+static DECLARE_WORK(fprobe_graph_cleanup_work, fprobe_graph_cleanup_workfn);
+
+static void fprobe_graph_cleanup_workfn(struct work_struct *work)
+{
+ unregister_ftrace_graph_finish(&fprobe_graph_ops);
+ ftrace_free_filter(&fprobe_graph_ops.ops);
+}
/* Add @addrs to the ftrace filter and register fgraph if needed. */
static int fprobe_graph_add_ips(unsigned long *addrs, int num)@@ -286,6 +296,7 @@ static int fprobe_graph_add_ips(unsigned long *addrs, int num)
int ret;
lockdep_assert_held(&fprobe_mutex);
+ flush_work(&fprobe_graph_cleanup_work);
ret = ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 0, 0);
if (ret)
@@ -297,7 +308,7 @@ static int fprobe_graph_add_ips(unsigned long *addrs, int num)
ftrace_free_filter(&fprobe_graph_ops.ops);
return ret;
}
- fprobe_graph_registered = true;
+ WRITE_ONCE(fprobe_graph_registered, true);
}
return 0;
}
@@ -305,9 +316,9 @@ static int fprobe_graph_add_ips(unsigned long *addrs, int num)
static void __fprobe_graph_unregister(void)
{
if (fprobe_graph_registered) {
- unregister_ftrace_graph(&fprobe_graph_ops);
- ftrace_free_filter(&fprobe_graph_ops.ops);
- fprobe_graph_registered = false;
+ WRITE_ONCE(fprobe_graph_registered, false);
+ if (unregister_ftrace_graph_start(&fprobe_graph_ops))
+ schedule_work(&fprobe_graph_cleanup_work);
}
}
@@ -679,6 +690,8 @@ static void fprobe_return(struct ftrace_graph_ret *trace,
ret_ip = ftrace_regs_get_instruction_pointer(fregs);
guard(rcu_sched_notrace)();
+ if (!READ_ONCE(fprobe_graph_registered))
+ return;
curr = 0;
while (size_words > curr) {diff --git a/kernel/trace/ftrace_internal.h b/kernel/trace/ftrace_internal.h
index 3235470e6..abb7776a2 100644
--- a/kernel/trace/ftrace_internal.h
+++ b/kernel/trace/ftrace_internal.h
@@ -52,6 +52,8 @@ static inline int ftrace_shutdown_subops(struct ftrace_ops *ops, struct ftrace_o
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
extern int ftrace_graph_active;
+bool unregister_ftrace_graph_start(struct fgraph_ops *gops);
+void unregister_ftrace_graph_finish(struct fgraph_ops *gops);
# ifdef CONFIG_DYNAMIC_FTRACE
extern void fgraph_update_pid_func(void);
# else
--
2.56.0