|
@@ -80,9 +80,6 @@ static struct ftrace_ops ftrace_list_end __read_mostly = {
|
|
|
int ftrace_enabled __read_mostly;
|
|
|
static int last_ftrace_enabled;
|
|
|
|
|
|
-/* Quick disabling of function tracer. */
|
|
|
-int function_trace_stop __read_mostly;
|
|
|
-
|
|
|
/* Current function tracing op */
|
|
|
struct ftrace_ops *function_trace_op __read_mostly = &ftrace_list_end;
|
|
|
/* What to set function_trace_op to */
|
|
@@ -1042,6 +1039,8 @@ static struct pid * const ftrace_swapper_pid = &init_struct_pid;
|
|
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
|
|
|
+static struct ftrace_ops *removed_ops;
|
|
|
+
|
|
|
#ifndef CONFIG_FTRACE_MCOUNT_RECORD
|
|
|
# error Dynamic ftrace depends on MCOUNT_RECORD
|
|
|
#endif
|
|
@@ -1304,25 +1303,15 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
|
|
|
struct ftrace_hash *new_hash;
|
|
|
int size = src->count;
|
|
|
int bits = 0;
|
|
|
- int ret;
|
|
|
int i;
|
|
|
|
|
|
- /*
|
|
|
- * Remove the current set, update the hash and add
|
|
|
- * them back.
|
|
|
- */
|
|
|
- ftrace_hash_rec_disable(ops, enable);
|
|
|
-
|
|
|
/*
|
|
|
* If the new source is empty, just free dst and assign it
|
|
|
* the empty_hash.
|
|
|
*/
|
|
|
if (!src->count) {
|
|
|
- free_ftrace_hash_rcu(*dst);
|
|
|
- rcu_assign_pointer(*dst, EMPTY_HASH);
|
|
|
- /* still need to update the function records */
|
|
|
- ret = 0;
|
|
|
- goto out;
|
|
|
+ new_hash = EMPTY_HASH;
|
|
|
+ goto update;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1335,10 +1324,9 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
|
|
|
if (bits > FTRACE_HASH_MAX_BITS)
|
|
|
bits = FTRACE_HASH_MAX_BITS;
|
|
|
|
|
|
- ret = -ENOMEM;
|
|
|
new_hash = alloc_ftrace_hash(bits);
|
|
|
if (!new_hash)
|
|
|
- goto out;
|
|
|
+ return -ENOMEM;
|
|
|
|
|
|
size = 1 << src->size_bits;
|
|
|
for (i = 0; i < size; i++) {
|
|
@@ -1349,20 +1337,20 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+update:
|
|
|
+ /*
|
|
|
+ * Remove the current set, update the hash and add
|
|
|
+ * them back.
|
|
|
+ */
|
|
|
+ ftrace_hash_rec_disable(ops, enable);
|
|
|
+
|
|
|
old_hash = *dst;
|
|
|
rcu_assign_pointer(*dst, new_hash);
|
|
|
free_ftrace_hash_rcu(old_hash);
|
|
|
|
|
|
- ret = 0;
|
|
|
- out:
|
|
|
- /*
|
|
|
- * Enable regardless of ret:
|
|
|
- * On success, we enable the new hash.
|
|
|
- * On failure, we re-enable the original hash.
|
|
|
- */
|
|
|
ftrace_hash_rec_enable(ops, enable);
|
|
|
|
|
|
- return ret;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1492,6 +1480,53 @@ int ftrace_text_reserved(const void *start, const void *end)
|
|
|
return (int)!!ret;
|
|
|
}
|
|
|
|
|
|
+/* Test if ops registered to this rec needs regs */
|
|
|
+static bool test_rec_ops_needs_regs(struct dyn_ftrace *rec)
|
|
|
+{
|
|
|
+ struct ftrace_ops *ops;
|
|
|
+ bool keep_regs = false;
|
|
|
+
|
|
|
+ for (ops = ftrace_ops_list;
|
|
|
+ ops != &ftrace_list_end; ops = ops->next) {
|
|
|
+ /* pass rec in as regs to have non-NULL val */
|
|
|
+ if (ftrace_ops_test(ops, rec->ip, rec)) {
|
|
|
+ if (ops->flags & FTRACE_OPS_FL_SAVE_REGS) {
|
|
|
+ keep_regs = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return keep_regs;
|
|
|
+}
|
|
|
+
|
|
|
+static void ftrace_remove_tramp(struct ftrace_ops *ops,
|
|
|
+ struct dyn_ftrace *rec)
|
|
|
+{
|
|
|
+ struct ftrace_func_entry *entry;
|
|
|
+
|
|
|
+ entry = ftrace_lookup_ip(ops->tramp_hash, rec->ip);
|
|
|
+ if (!entry)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The tramp_hash entry will be removed at time
|
|
|
+ * of update.
|
|
|
+ */
|
|
|
+ ops->nr_trampolines--;
|
|
|
+ rec->flags &= ~FTRACE_FL_TRAMP;
|
|
|
+}
|
|
|
+
|
|
|
+static void ftrace_clear_tramps(struct dyn_ftrace *rec)
|
|
|
+{
|
|
|
+ struct ftrace_ops *op;
|
|
|
+
|
|
|
+ do_for_each_ftrace_op(op, ftrace_ops_list) {
|
|
|
+ if (op->nr_trampolines)
|
|
|
+ ftrace_remove_tramp(op, rec);
|
|
|
+ } while_for_each_ftrace_op(op);
|
|
|
+}
|
|
|
+
|
|
|
static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
|
|
|
int filter_hash,
|
|
|
bool inc)
|
|
@@ -1572,8 +1607,30 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
|
|
|
|
|
|
if (inc) {
|
|
|
rec->flags++;
|
|
|
- if (FTRACE_WARN_ON((rec->flags & ~FTRACE_FL_MASK) == FTRACE_REF_MAX))
|
|
|
+ if (FTRACE_WARN_ON(ftrace_rec_count(rec) == FTRACE_REF_MAX))
|
|
|
return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If there's only a single callback registered to a
|
|
|
+ * function, and the ops has a trampoline registered
|
|
|
+ * for it, then we can call it directly.
|
|
|
+ */
|
|
|
+ if (ftrace_rec_count(rec) == 1 && ops->trampoline) {
|
|
|
+ rec->flags |= FTRACE_FL_TRAMP;
|
|
|
+ ops->nr_trampolines++;
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * If we are adding another function callback
|
|
|
+ * to this function, and the previous had a
|
|
|
+ * trampoline used, then we need to go back to
|
|
|
+ * the default trampoline.
|
|
|
+ */
|
|
|
+ rec->flags &= ~FTRACE_FL_TRAMP;
|
|
|
+
|
|
|
+ /* remove trampolines from any ops for this rec */
|
|
|
+ ftrace_clear_tramps(rec);
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* If any ops wants regs saved for this function
|
|
|
* then all ops will get saved regs.
|
|
@@ -1581,9 +1638,30 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
|
|
|
if (ops->flags & FTRACE_OPS_FL_SAVE_REGS)
|
|
|
rec->flags |= FTRACE_FL_REGS;
|
|
|
} else {
|
|
|
- if (FTRACE_WARN_ON((rec->flags & ~FTRACE_FL_MASK) == 0))
|
|
|
+ if (FTRACE_WARN_ON(ftrace_rec_count(rec) == 0))
|
|
|
return;
|
|
|
rec->flags--;
|
|
|
+
|
|
|
+ if (ops->trampoline && !ftrace_rec_count(rec))
|
|
|
+ ftrace_remove_tramp(ops, rec);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If the rec had REGS enabled and the ops that is
|
|
|
+ * being removed had REGS set, then see if there is
|
|
|
+ * still any ops for this record that wants regs.
|
|
|
+ * If not, we can stop recording them.
|
|
|
+ */
|
|
|
+ if (ftrace_rec_count(rec) > 0 &&
|
|
|
+ rec->flags & FTRACE_FL_REGS &&
|
|
|
+ ops->flags & FTRACE_OPS_FL_SAVE_REGS) {
|
|
|
+ if (!test_rec_ops_needs_regs(rec))
|
|
|
+ rec->flags &= ~FTRACE_FL_REGS;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * flags will be cleared in ftrace_check_record()
|
|
|
+ * if rec count is zero.
|
|
|
+ */
|
|
|
}
|
|
|
count++;
|
|
|
/* Shortcut, if we handled all records, we are done. */
|
|
@@ -1668,17 +1746,23 @@ static int ftrace_check_record(struct dyn_ftrace *rec, int enable, int update)
|
|
|
* If we are disabling calls, then disable all records that
|
|
|
* are enabled.
|
|
|
*/
|
|
|
- if (enable && (rec->flags & ~FTRACE_FL_MASK))
|
|
|
+ if (enable && ftrace_rec_count(rec))
|
|
|
flag = FTRACE_FL_ENABLED;
|
|
|
|
|
|
/*
|
|
|
- * If enabling and the REGS flag does not match the REGS_EN, then
|
|
|
- * do not ignore this record. Set flags to fail the compare against
|
|
|
- * ENABLED.
|
|
|
+ * If enabling and the REGS flag does not match the REGS_EN, or
|
|
|
+ * the TRAMP flag doesn't match the TRAMP_EN, then do not ignore
|
|
|
+ * this record. Set flags to fail the compare against ENABLED.
|
|
|
*/
|
|
|
- if (flag &&
|
|
|
- (!(rec->flags & FTRACE_FL_REGS) != !(rec->flags & FTRACE_FL_REGS_EN)))
|
|
|
- flag |= FTRACE_FL_REGS;
|
|
|
+ if (flag) {
|
|
|
+ if (!(rec->flags & FTRACE_FL_REGS) !=
|
|
|
+ !(rec->flags & FTRACE_FL_REGS_EN))
|
|
|
+ flag |= FTRACE_FL_REGS;
|
|
|
+
|
|
|
+ if (!(rec->flags & FTRACE_FL_TRAMP) !=
|
|
|
+ !(rec->flags & FTRACE_FL_TRAMP_EN))
|
|
|
+ flag |= FTRACE_FL_TRAMP;
|
|
|
+ }
|
|
|
|
|
|
/* If the state of this record hasn't changed, then do nothing */
|
|
|
if ((rec->flags & FTRACE_FL_ENABLED) == flag)
|
|
@@ -1696,6 +1780,12 @@ static int ftrace_check_record(struct dyn_ftrace *rec, int enable, int update)
|
|
|
else
|
|
|
rec->flags &= ~FTRACE_FL_REGS_EN;
|
|
|
}
|
|
|
+ if (flag & FTRACE_FL_TRAMP) {
|
|
|
+ if (rec->flags & FTRACE_FL_TRAMP)
|
|
|
+ rec->flags |= FTRACE_FL_TRAMP_EN;
|
|
|
+ else
|
|
|
+ rec->flags &= ~FTRACE_FL_TRAMP_EN;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1704,7 +1794,7 @@ static int ftrace_check_record(struct dyn_ftrace *rec, int enable, int update)
|
|
|
* Otherwise,
|
|
|
* return UPDATE_MODIFY_CALL to tell the caller to convert
|
|
|
* from the save regs, to a non-save regs function or
|
|
|
- * vice versa.
|
|
|
+ * vice versa, or from a trampoline call.
|
|
|
*/
|
|
|
if (flag & FTRACE_FL_ENABLED)
|
|
|
return FTRACE_UPDATE_MAKE_CALL;
|
|
@@ -1714,7 +1804,7 @@ static int ftrace_check_record(struct dyn_ftrace *rec, int enable, int update)
|
|
|
|
|
|
if (update) {
|
|
|
/* If there's no more users, clear all flags */
|
|
|
- if (!(rec->flags & ~FTRACE_FL_MASK))
|
|
|
+ if (!ftrace_rec_count(rec))
|
|
|
rec->flags = 0;
|
|
|
else
|
|
|
/* Just disable the record (keep REGS state) */
|
|
@@ -1751,6 +1841,43 @@ int ftrace_test_record(struct dyn_ftrace *rec, int enable)
|
|
|
return ftrace_check_record(rec, enable, 0);
|
|
|
}
|
|
|
|
|
|
+static struct ftrace_ops *
|
|
|
+ftrace_find_tramp_ops_curr(struct dyn_ftrace *rec)
|
|
|
+{
|
|
|
+ struct ftrace_ops *op;
|
|
|
+
|
|
|
+ /* Removed ops need to be tested first */
|
|
|
+ if (removed_ops && removed_ops->tramp_hash) {
|
|
|
+ if (ftrace_lookup_ip(removed_ops->tramp_hash, rec->ip))
|
|
|
+ return removed_ops;
|
|
|
+ }
|
|
|
+
|
|
|
+ do_for_each_ftrace_op(op, ftrace_ops_list) {
|
|
|
+ if (!op->tramp_hash)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (ftrace_lookup_ip(op->tramp_hash, rec->ip))
|
|
|
+ return op;
|
|
|
+
|
|
|
+ } while_for_each_ftrace_op(op);
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static struct ftrace_ops *
|
|
|
+ftrace_find_tramp_ops_new(struct dyn_ftrace *rec)
|
|
|
+{
|
|
|
+ struct ftrace_ops *op;
|
|
|
+
|
|
|
+ do_for_each_ftrace_op(op, ftrace_ops_list) {
|
|
|
+ /* pass rec in as regs to have non-NULL val */
|
|
|
+ if (ftrace_ops_test(op, rec->ip, rec))
|
|
|
+ return op;
|
|
|
+ } while_for_each_ftrace_op(op);
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ftrace_get_addr_new - Get the call address to set to
|
|
|
* @rec: The ftrace record descriptor
|
|
@@ -1763,6 +1890,20 @@ int ftrace_test_record(struct dyn_ftrace *rec, int enable)
|
|
|
*/
|
|
|
unsigned long ftrace_get_addr_new(struct dyn_ftrace *rec)
|
|
|
{
|
|
|
+ struct ftrace_ops *ops;
|
|
|
+
|
|
|
+ /* Trampolines take precedence over regs */
|
|
|
+ if (rec->flags & FTRACE_FL_TRAMP) {
|
|
|
+ ops = ftrace_find_tramp_ops_new(rec);
|
|
|
+ if (FTRACE_WARN_ON(!ops || !ops->trampoline)) {
|
|
|
+ pr_warning("Bad trampoline accounting at: %p (%pS)\n",
|
|
|
+ (void *)rec->ip, (void *)rec->ip);
|
|
|
+ /* Ftrace is shutting down, return anything */
|
|
|
+ return (unsigned long)FTRACE_ADDR;
|
|
|
+ }
|
|
|
+ return ops->trampoline;
|
|
|
+ }
|
|
|
+
|
|
|
if (rec->flags & FTRACE_FL_REGS)
|
|
|
return (unsigned long)FTRACE_REGS_ADDR;
|
|
|
else
|
|
@@ -1781,6 +1922,20 @@ unsigned long ftrace_get_addr_new(struct dyn_ftrace *rec)
|
|
|
*/
|
|
|
unsigned long ftrace_get_addr_curr(struct dyn_ftrace *rec)
|
|
|
{
|
|
|
+ struct ftrace_ops *ops;
|
|
|
+
|
|
|
+ /* Trampolines take precedence over regs */
|
|
|
+ if (rec->flags & FTRACE_FL_TRAMP_EN) {
|
|
|
+ ops = ftrace_find_tramp_ops_curr(rec);
|
|
|
+ if (FTRACE_WARN_ON(!ops)) {
|
|
|
+ pr_warning("Bad trampoline accounting at: %p (%pS)\n",
|
|
|
+ (void *)rec->ip, (void *)rec->ip);
|
|
|
+ /* Ftrace is shutting down, return anything */
|
|
|
+ return (unsigned long)FTRACE_ADDR;
|
|
|
+ }
|
|
|
+ return ops->trampoline;
|
|
|
+ }
|
|
|
+
|
|
|
if (rec->flags & FTRACE_FL_REGS_EN)
|
|
|
return (unsigned long)FTRACE_REGS_ADDR;
|
|
|
else
|
|
@@ -2023,6 +2178,89 @@ void __weak arch_ftrace_update_code(int command)
|
|
|
ftrace_run_stop_machine(command);
|
|
|
}
|
|
|
|
|
|
+static int ftrace_save_ops_tramp_hash(struct ftrace_ops *ops)
|
|
|
+{
|
|
|
+ struct ftrace_page *pg;
|
|
|
+ struct dyn_ftrace *rec;
|
|
|
+ int size, bits;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ size = ops->nr_trampolines;
|
|
|
+ bits = 0;
|
|
|
+ /*
|
|
|
+ * Make the hash size about 1/2 the # found
|
|
|
+ */
|
|
|
+ for (size /= 2; size; size >>= 1)
|
|
|
+ bits++;
|
|
|
+
|
|
|
+ ops->tramp_hash = alloc_ftrace_hash(bits);
|
|
|
+ /*
|
|
|
+ * TODO: a failed allocation is going to screw up
|
|
|
+ * the accounting of what needs to be modified
|
|
|
+ * and not. For now, we kill ftrace if we fail
|
|
|
+ * to allocate here. But there are ways around this,
|
|
|
+ * but that will take a little more work.
|
|
|
+ */
|
|
|
+ if (!ops->tramp_hash)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ do_for_each_ftrace_rec(pg, rec) {
|
|
|
+ if (ftrace_rec_count(rec) == 1 &&
|
|
|
+ ftrace_ops_test(ops, rec->ip, rec)) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If another ops adds to a rec, the rec will
|
|
|
+ * lose its trampoline and never get it back
|
|
|
+ * until all ops are off of it.
|
|
|
+ */
|
|
|
+ if (!(rec->flags & FTRACE_FL_TRAMP))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* This record had better have a trampoline */
|
|
|
+ if (FTRACE_WARN_ON(!(rec->flags & FTRACE_FL_TRAMP_EN)))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ ret = add_hash_entry(ops->tramp_hash, rec->ip);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ } while_for_each_ftrace_rec();
|
|
|
+
|
|
|
+ /* The number of recs in the hash must match nr_trampolines */
|
|
|
+ FTRACE_WARN_ON(ops->tramp_hash->count != ops->nr_trampolines);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_save_tramp_hashes(void)
|
|
|
+{
|
|
|
+ struct ftrace_ops *op;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now that any trampoline is being used, we need to save the
|
|
|
+ * hashes for the ops that have them. This allows the mapping
|
|
|
+ * back from the record to the ops that has the trampoline to
|
|
|
+ * know what code is being replaced. Modifying code must always
|
|
|
+ * verify what it is changing.
|
|
|
+ */
|
|
|
+ do_for_each_ftrace_op(op, ftrace_ops_list) {
|
|
|
+
|
|
|
+ /* The tramp_hash is recreated each time. */
|
|
|
+ free_ftrace_hash(op->tramp_hash);
|
|
|
+ op->tramp_hash = NULL;
|
|
|
+
|
|
|
+ if (op->nr_trampolines) {
|
|
|
+ ret = ftrace_save_ops_tramp_hash(op);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ } while_for_each_ftrace_op(op);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static void ftrace_run_update_code(int command)
|
|
|
{
|
|
|
int ret;
|
|
@@ -2031,11 +2269,6 @@ static void ftrace_run_update_code(int command)
|
|
|
FTRACE_WARN_ON(ret);
|
|
|
if (ret)
|
|
|
return;
|
|
|
- /*
|
|
|
- * Do not call function tracer while we update the code.
|
|
|
- * We are in stop machine.
|
|
|
- */
|
|
|
- function_trace_stop++;
|
|
|
|
|
|
/*
|
|
|
* By default we use stop_machine() to modify the code.
|
|
@@ -2045,15 +2278,15 @@ static void ftrace_run_update_code(int command)
|
|
|
*/
|
|
|
arch_ftrace_update_code(command);
|
|
|
|
|
|
- function_trace_stop--;
|
|
|
-
|
|
|
ret = ftrace_arch_code_modify_post_process();
|
|
|
FTRACE_WARN_ON(ret);
|
|
|
+
|
|
|
+ ret = ftrace_save_tramp_hashes();
|
|
|
+ FTRACE_WARN_ON(ret);
|
|
|
}
|
|
|
|
|
|
static ftrace_func_t saved_ftrace_func;
|
|
|
static int ftrace_start_up;
|
|
|
-static int global_start_up;
|
|
|
|
|
|
static void control_ops_free(struct ftrace_ops *ops)
|
|
|
{
|
|
@@ -2117,8 +2350,7 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command)
|
|
|
|
|
|
ftrace_hash_rec_disable(ops, 1);
|
|
|
|
|
|
- if (!global_start_up)
|
|
|
- ops->flags &= ~FTRACE_OPS_FL_ENABLED;
|
|
|
+ ops->flags &= ~FTRACE_OPS_FL_ENABLED;
|
|
|
|
|
|
command |= FTRACE_UPDATE_CALLS;
|
|
|
|
|
@@ -2139,8 +2371,16 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * If the ops uses a trampoline, then it needs to be
|
|
|
+ * tested first on update.
|
|
|
+ */
|
|
|
+ removed_ops = ops;
|
|
|
+
|
|
|
ftrace_run_update_code(command);
|
|
|
|
|
|
+ removed_ops = NULL;
|
|
|
+
|
|
|
/*
|
|
|
* Dynamic ops may be freed, we must make sure that all
|
|
|
* callers are done before leaving this function.
|
|
@@ -2398,7 +2638,8 @@ ftrace_allocate_pages(unsigned long num_to_init)
|
|
|
return start_pg;
|
|
|
|
|
|
free_pages:
|
|
|
- while (start_pg) {
|
|
|
+ pg = start_pg;
|
|
|
+ while (pg) {
|
|
|
order = get_count_order(pg->size / ENTRIES_PER_PAGE);
|
|
|
free_pages((unsigned long)pg->records, order);
|
|
|
start_pg = pg->next;
|
|
@@ -2595,8 +2836,10 @@ static void *t_start(struct seq_file *m, loff_t *pos)
|
|
|
* off, we can short cut and just print out that all
|
|
|
* functions are enabled.
|
|
|
*/
|
|
|
- if (iter->flags & FTRACE_ITER_FILTER &&
|
|
|
- ftrace_hash_empty(ops->filter_hash)) {
|
|
|
+ if ((iter->flags & FTRACE_ITER_FILTER &&
|
|
|
+ ftrace_hash_empty(ops->filter_hash)) ||
|
|
|
+ (iter->flags & FTRACE_ITER_NOTRACE &&
|
|
|
+ ftrace_hash_empty(ops->notrace_hash))) {
|
|
|
if (*pos > 0)
|
|
|
return t_hash_start(m, pos);
|
|
|
iter->flags |= FTRACE_ITER_PRINTALL;
|
|
@@ -2641,7 +2884,10 @@ static int t_show(struct seq_file *m, void *v)
|
|
|
return t_hash_show(m, iter);
|
|
|
|
|
|
if (iter->flags & FTRACE_ITER_PRINTALL) {
|
|
|
- seq_printf(m, "#### all functions enabled ####\n");
|
|
|
+ if (iter->flags & FTRACE_ITER_NOTRACE)
|
|
|
+ seq_printf(m, "#### no functions disabled ####\n");
|
|
|
+ else
|
|
|
+ seq_printf(m, "#### all functions enabled ####\n");
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -2651,10 +2897,22 @@ static int t_show(struct seq_file *m, void *v)
|
|
|
return 0;
|
|
|
|
|
|
seq_printf(m, "%ps", (void *)rec->ip);
|
|
|
- if (iter->flags & FTRACE_ITER_ENABLED)
|
|
|
+ if (iter->flags & FTRACE_ITER_ENABLED) {
|
|
|
seq_printf(m, " (%ld)%s",
|
|
|
- rec->flags & ~FTRACE_FL_MASK,
|
|
|
- rec->flags & FTRACE_FL_REGS ? " R" : "");
|
|
|
+ ftrace_rec_count(rec),
|
|
|
+ rec->flags & FTRACE_FL_REGS ? " R" : " ");
|
|
|
+ if (rec->flags & FTRACE_FL_TRAMP_EN) {
|
|
|
+ struct ftrace_ops *ops;
|
|
|
+
|
|
|
+ ops = ftrace_find_tramp_ops_curr(rec);
|
|
|
+ if (ops && ops->trampoline)
|
|
|
+ seq_printf(m, "\ttramp: %pS",
|
|
|
+ (void *)ops->trampoline);
|
|
|
+ else
|
|
|
+ seq_printf(m, "\ttramp: ERROR!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
seq_printf(m, "\n");
|
|
|
|
|
|
return 0;
|
|
@@ -2702,13 +2960,6 @@ ftrace_enabled_open(struct inode *inode, struct file *file)
|
|
|
return iter ? 0 : -ENOMEM;
|
|
|
}
|
|
|
|
|
|
-static void ftrace_filter_reset(struct ftrace_hash *hash)
|
|
|
-{
|
|
|
- mutex_lock(&ftrace_lock);
|
|
|
- ftrace_hash_clear(hash);
|
|
|
- mutex_unlock(&ftrace_lock);
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* ftrace_regex_open - initialize function tracer filter files
|
|
|
* @ops: The ftrace_ops that hold the hash filters
|
|
@@ -2758,7 +3009,13 @@ ftrace_regex_open(struct ftrace_ops *ops, int flag,
|
|
|
hash = ops->filter_hash;
|
|
|
|
|
|
if (file->f_mode & FMODE_WRITE) {
|
|
|
- iter->hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, hash);
|
|
|
+ const int size_bits = FTRACE_HASH_DEFAULT_BITS;
|
|
|
+
|
|
|
+ if (file->f_flags & O_TRUNC)
|
|
|
+ iter->hash = alloc_ftrace_hash(size_bits);
|
|
|
+ else
|
|
|
+ iter->hash = alloc_and_copy_ftrace_hash(size_bits, hash);
|
|
|
+
|
|
|
if (!iter->hash) {
|
|
|
trace_parser_put(&iter->parser);
|
|
|
kfree(iter);
|
|
@@ -2767,10 +3024,6 @@ ftrace_regex_open(struct ftrace_ops *ops, int flag,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if ((file->f_mode & FMODE_WRITE) &&
|
|
|
- (file->f_flags & O_TRUNC))
|
|
|
- ftrace_filter_reset(iter->hash);
|
|
|
-
|
|
|
if (file->f_mode & FMODE_READ) {
|
|
|
iter->pg = ftrace_pages_start;
|
|
|
|
|
@@ -3471,14 +3724,16 @@ ftrace_set_hash(struct ftrace_ops *ops, unsigned char *buf, int len,
|
|
|
else
|
|
|
orig_hash = &ops->notrace_hash;
|
|
|
|
|
|
- hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, *orig_hash);
|
|
|
+ if (reset)
|
|
|
+ hash = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
|
|
|
+ else
|
|
|
+ hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, *orig_hash);
|
|
|
+
|
|
|
if (!hash) {
|
|
|
ret = -ENOMEM;
|
|
|
goto out_regex_unlock;
|
|
|
}
|
|
|
|
|
|
- if (reset)
|
|
|
- ftrace_filter_reset(hash);
|
|
|
if (buf && !ftrace_match_records(hash, buf, len)) {
|
|
|
ret = -EINVAL;
|
|
|
goto out_regex_unlock;
|
|
@@ -3630,6 +3885,7 @@ __setup("ftrace_filter=", set_ftrace_filter);
|
|
|
|
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
|
static char ftrace_graph_buf[FTRACE_FILTER_SIZE] __initdata;
|
|
|
+static char ftrace_graph_notrace_buf[FTRACE_FILTER_SIZE] __initdata;
|
|
|
static int ftrace_set_func(unsigned long *array, int *idx, int size, char *buffer);
|
|
|
|
|
|
static int __init set_graph_function(char *str)
|
|
@@ -3639,16 +3895,29 @@ static int __init set_graph_function(char *str)
|
|
|
}
|
|
|
__setup("ftrace_graph_filter=", set_graph_function);
|
|
|
|
|
|
-static void __init set_ftrace_early_graph(char *buf)
|
|
|
+static int __init set_graph_notrace_function(char *str)
|
|
|
+{
|
|
|
+ strlcpy(ftrace_graph_notrace_buf, str, FTRACE_FILTER_SIZE);
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+__setup("ftrace_graph_notrace=", set_graph_notrace_function);
|
|
|
+
|
|
|
+static void __init set_ftrace_early_graph(char *buf, int enable)
|
|
|
{
|
|
|
int ret;
|
|
|
char *func;
|
|
|
+ unsigned long *table = ftrace_graph_funcs;
|
|
|
+ int *count = &ftrace_graph_count;
|
|
|
+
|
|
|
+ if (!enable) {
|
|
|
+ table = ftrace_graph_notrace_funcs;
|
|
|
+ count = &ftrace_graph_notrace_count;
|
|
|
+ }
|
|
|
|
|
|
while (buf) {
|
|
|
func = strsep(&buf, ",");
|
|
|
/* we allow only one expression at a time */
|
|
|
- ret = ftrace_set_func(ftrace_graph_funcs, &ftrace_graph_count,
|
|
|
- FTRACE_GRAPH_MAX_FUNCS, func);
|
|
|
+ ret = ftrace_set_func(table, count, FTRACE_GRAPH_MAX_FUNCS, func);
|
|
|
if (ret)
|
|
|
printk(KERN_DEBUG "ftrace: function %s not "
|
|
|
"traceable\n", func);
|
|
@@ -3677,7 +3946,9 @@ static void __init set_ftrace_early_filters(void)
|
|
|
ftrace_set_early_filter(&global_ops, ftrace_notrace_buf, 0);
|
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
|
if (ftrace_graph_buf[0])
|
|
|
- set_ftrace_early_graph(ftrace_graph_buf);
|
|
|
+ set_ftrace_early_graph(ftrace_graph_buf, 1);
|
|
|
+ if (ftrace_graph_notrace_buf[0])
|
|
|
+ set_ftrace_early_graph(ftrace_graph_notrace_buf, 0);
|
|
|
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
|
|
}
|
|
|
|
|
@@ -3819,7 +4090,12 @@ static int g_show(struct seq_file *m, void *v)
|
|
|
return 0;
|
|
|
|
|
|
if (ptr == (unsigned long *)1) {
|
|
|
- seq_printf(m, "#### all functions enabled ####\n");
|
|
|
+ struct ftrace_graph_data *fgd = m->private;
|
|
|
+
|
|
|
+ if (fgd->table == ftrace_graph_funcs)
|
|
|
+ seq_printf(m, "#### all functions enabled ####\n");
|
|
|
+ else
|
|
|
+ seq_printf(m, "#### no functions disabled ####\n");
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -4447,9 +4723,6 @@ __ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
|
|
|
struct ftrace_ops *op;
|
|
|
int bit;
|
|
|
|
|
|
- if (function_trace_stop)
|
|
|
- return;
|
|
|
-
|
|
|
bit = trace_test_and_set_recursion(TRACE_LIST_START, TRACE_LIST_MAX);
|
|
|
if (bit < 0)
|
|
|
return;
|
|
@@ -4461,9 +4734,8 @@ __ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
|
|
|
preempt_disable_notrace();
|
|
|
do_for_each_ftrace_op(op, ftrace_ops_list) {
|
|
|
if (ftrace_ops_test(op, ip, regs)) {
|
|
|
- if (WARN_ON(!op->func)) {
|
|
|
- function_trace_stop = 1;
|
|
|
- printk("op=%p %pS\n", op, op);
|
|
|
+ if (FTRACE_WARN_ON(!op->func)) {
|
|
|
+ pr_warn("op=%p %pS\n", op, op);
|
|
|
goto out;
|
|
|
}
|
|
|
op->func(ip, parent_ip, op, regs);
|
|
@@ -5084,6 +5356,12 @@ int register_ftrace_graph(trace_func_graph_ret_t retfunc,
|
|
|
/* Function graph doesn't use the .func field of global_ops */
|
|
|
global_ops.flags |= FTRACE_OPS_FL_STUB;
|
|
|
|
|
|
+#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
+ /* Optimize function graph calling (if implemented by arch) */
|
|
|
+ if (FTRACE_GRAPH_TRAMP_ADDR != 0)
|
|
|
+ global_ops.trampoline = FTRACE_GRAPH_TRAMP_ADDR;
|
|
|
+#endif
|
|
|
+
|
|
|
ret = ftrace_startup(&global_ops, FTRACE_START_FUNC_RET);
|
|
|
|
|
|
out:
|
|
@@ -5104,6 +5382,10 @@ void unregister_ftrace_graph(void)
|
|
|
__ftrace_graph_entry = ftrace_graph_entry_stub;
|
|
|
ftrace_shutdown(&global_ops, FTRACE_STOP_FUNC_RET);
|
|
|
global_ops.flags &= ~FTRACE_OPS_FL_STUB;
|
|
|
+#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
+ if (FTRACE_GRAPH_TRAMP_ADDR != 0)
|
|
|
+ global_ops.trampoline = 0;
|
|
|
+#endif
|
|
|
unregister_pm_notifier(&ftrace_suspend_notifier);
|
|
|
unregister_trace_sched_switch(ftrace_graph_probe_sched_switch, NULL);
|
|
|
|
|
@@ -5183,9 +5465,4 @@ void ftrace_graph_exit_task(struct task_struct *t)
|
|
|
|
|
|
kfree(ret_stack);
|
|
|
}
|
|
|
-
|
|
|
-void ftrace_graph_stop(void)
|
|
|
-{
|
|
|
- ftrace_stop();
|
|
|
-}
|
|
|
#endif
|