Commit bce0b6c5 authored by Steven Rostedt (Red Hat)'s avatar Steven Rostedt (Red Hat) Committed by Steven Rostedt

ftrace: Fix up trampoline accounting with looping on hash ops

Now that a ftrace_hash can be shared by multiple ftrace_ops, they can dec
the rec->flags by more than once (one per those that share the ftrace_hash).
This means that the tramp_hash may not have a hash item when it was added.

For example, if two ftrace_ops share a hash for a ftrace record, and the
first ops has a trampoline, when it adds itself it will set the rec->flags
TRAMP flag and increments its nr_trampolines counter. When the second ops
is added, it must clear that tramp flag but also decrement the other ops
that shares its hash. As the update to the function callbacks has not yet
been performed, the other ops will not have the tramp hash set yet and it
can not be used to know to decrement its nr_trampolines.

Luckily, the tramp_hash does not need to be used. As the ftrace_mutex is
held, a ops with a trampoline to a record during an update of another ops
that shares the record will have its func_hash pointing to it. Since a
trampoline can only be set for a record if only one ops is attached to it,
we can just check if the record has a trampoline (the FTRACE_FL_TRAMP flag
is set) and then find the ops that has this record in its hashes.

Also added some output to help debug when things go wrong.

Cc: stable@vger.kernel.org # 3.16+ (apply after 3.17-rc4 is out)
Signed-off-by: default avatarSteven Rostedt <rostedt@goodmis.org>
parent 84261912
...@@ -1507,25 +1507,38 @@ static bool test_rec_ops_needs_regs(struct dyn_ftrace *rec) ...@@ -1507,25 +1507,38 @@ static bool test_rec_ops_needs_regs(struct dyn_ftrace *rec)
static void ftrace_remove_tramp(struct ftrace_ops *ops, static void ftrace_remove_tramp(struct ftrace_ops *ops,
struct dyn_ftrace *rec) struct dyn_ftrace *rec)
{ {
struct ftrace_func_entry *entry; /* If TRAMP is not set, no ops should have a trampoline for this */
if (!(rec->flags & FTRACE_FL_TRAMP))
entry = ftrace_lookup_ip(ops->tramp_hash, rec->ip);
if (!entry)
return; return;
rec->flags &= ~FTRACE_FL_TRAMP;
if ((!ftrace_hash_empty(ops->func_hash->filter_hash) &&
!ftrace_lookup_ip(ops->func_hash->filter_hash, rec->ip)) ||
ftrace_lookup_ip(ops->func_hash->notrace_hash, rec->ip))
return;
/* /*
* The tramp_hash entry will be removed at time * The tramp_hash entry will be removed at time
* of update. * of update.
*/ */
ops->nr_trampolines--; ops->nr_trampolines--;
rec->flags &= ~FTRACE_FL_TRAMP;
} }
static void ftrace_clear_tramps(struct dyn_ftrace *rec) static void ftrace_clear_tramps(struct dyn_ftrace *rec, struct ftrace_ops *ops)
{ {
struct ftrace_ops *op; struct ftrace_ops *op;
/* If TRAMP is not set, no ops should have a trampoline for this */
if (!(rec->flags & FTRACE_FL_TRAMP))
return;
do_for_each_ftrace_op(op, ftrace_ops_list) { do_for_each_ftrace_op(op, ftrace_ops_list) {
/*
* This function is called to clear other tramps
* not the one that is being updated.
*/
if (op == ops)
continue;
if (op->nr_trampolines) if (op->nr_trampolines)
ftrace_remove_tramp(op, rec); ftrace_remove_tramp(op, rec);
} while_for_each_ftrace_op(op); } while_for_each_ftrace_op(op);
...@@ -1626,13 +1639,10 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops, ...@@ -1626,13 +1639,10 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
/* /*
* If we are adding another function callback * If we are adding another function callback
* to this function, and the previous had a * to this function, and the previous had a
* trampoline used, then we need to go back to * custom trampoline in use, then we need to go
* the default trampoline. * back to the default trampoline.
*/ */
rec->flags &= ~FTRACE_FL_TRAMP; ftrace_clear_tramps(rec, ops);
/* remove trampolines from any ops for this rec */
ftrace_clear_tramps(rec);
} }
/* /*
...@@ -1935,8 +1945,8 @@ unsigned long ftrace_get_addr_new(struct dyn_ftrace *rec) ...@@ -1935,8 +1945,8 @@ unsigned long ftrace_get_addr_new(struct dyn_ftrace *rec)
if (rec->flags & FTRACE_FL_TRAMP) { if (rec->flags & FTRACE_FL_TRAMP) {
ops = ftrace_find_tramp_ops_new(rec); ops = ftrace_find_tramp_ops_new(rec);
if (FTRACE_WARN_ON(!ops || !ops->trampoline)) { if (FTRACE_WARN_ON(!ops || !ops->trampoline)) {
pr_warning("Bad trampoline accounting at: %p (%pS)\n", pr_warn("Bad trampoline accounting at: %p (%pS) (%lx)\n",
(void *)rec->ip, (void *)rec->ip); (void *)rec->ip, (void *)rec->ip, rec->flags);
/* Ftrace is shutting down, return anything */ /* Ftrace is shutting down, return anything */
return (unsigned long)FTRACE_ADDR; return (unsigned long)FTRACE_ADDR;
} }
...@@ -2266,7 +2276,10 @@ static int ftrace_save_ops_tramp_hash(struct ftrace_ops *ops) ...@@ -2266,7 +2276,10 @@ static int ftrace_save_ops_tramp_hash(struct ftrace_ops *ops)
} while_for_each_ftrace_rec(); } while_for_each_ftrace_rec();
/* The number of recs in the hash must match nr_trampolines */ /* The number of recs in the hash must match nr_trampolines */
FTRACE_WARN_ON(ops->tramp_hash->count != ops->nr_trampolines); if (FTRACE_WARN_ON(ops->tramp_hash->count != ops->nr_trampolines))
pr_warn("count=%ld trampolines=%d\n",
ops->tramp_hash->count,
ops->nr_trampolines);
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment