|
@@ -1358,6 +1358,9 @@ ftrace_hash_rec_disable_modify(struct ftrace_ops *ops, int filter_hash);
|
|
|
static void
|
|
|
ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash);
|
|
|
|
|
|
+static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
|
|
|
+ struct ftrace_hash *new_hash);
|
|
|
+
|
|
|
static int
|
|
|
ftrace_hash_move(struct ftrace_ops *ops, int enable,
|
|
|
struct ftrace_hash **dst, struct ftrace_hash *src)
|
|
@@ -1368,8 +1371,13 @@ 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;
|
|
|
|
|
|
+ /* Reject setting notrace hash on IPMODIFY ftrace_ops */
|
|
|
+ if (ops->flags & FTRACE_OPS_FL_IPMODIFY && !enable)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
/*
|
|
|
* If the new source is empty, just free dst and assign it
|
|
|
* the empty_hash.
|
|
@@ -1403,6 +1411,16 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
|
|
|
}
|
|
|
|
|
|
update:
|
|
|
+ /* Make sure this can be applied if it is IPMODIFY ftrace_ops */
|
|
|
+ if (enable) {
|
|
|
+ /* IPMODIFY should be updated only when filter_hash updating */
|
|
|
+ ret = ftrace_hash_ipmodify_update(ops, new_hash);
|
|
|
+ if (ret < 0) {
|
|
|
+ free_ftrace_hash(new_hash);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Remove the current set, update the hash and add
|
|
|
* them back.
|
|
@@ -1767,6 +1785,114 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops,
|
|
|
ftrace_hash_rec_update_modify(ops, filter_hash, 1);
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Try to update IPMODIFY flag on each ftrace_rec. Return 0 if it is OK
|
|
|
+ * or no-needed to update, -EBUSY if it detects a conflict of the flag
|
|
|
+ * on a ftrace_rec, and -EINVAL if the new_hash tries to trace all recs.
|
|
|
+ * Note that old_hash and new_hash has below meanings
|
|
|
+ * - If the hash is NULL, it hits all recs (if IPMODIFY is set, this is rejected)
|
|
|
+ * - If the hash is EMPTY_HASH, it hits nothing
|
|
|
+ * - Anything else hits the recs which match the hash entries.
|
|
|
+ */
|
|
|
+static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
|
|
|
+ struct ftrace_hash *old_hash,
|
|
|
+ struct ftrace_hash *new_hash)
|
|
|
+{
|
|
|
+ struct ftrace_page *pg;
|
|
|
+ struct dyn_ftrace *rec, *end = NULL;
|
|
|
+ int in_old, in_new;
|
|
|
+
|
|
|
+ /* Only update if the ops has been registered */
|
|
|
+ if (!(ops->flags & FTRACE_OPS_FL_ENABLED))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (!(ops->flags & FTRACE_OPS_FL_IPMODIFY))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Since the IPMODIFY is a very address sensitive action, we do not
|
|
|
+ * allow ftrace_ops to set all functions to new hash.
|
|
|
+ */
|
|
|
+ if (!new_hash || !old_hash)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Update rec->flags */
|
|
|
+ do_for_each_ftrace_rec(pg, rec) {
|
|
|
+ /* We need to update only differences of filter_hash */
|
|
|
+ in_old = !!ftrace_lookup_ip(old_hash, rec->ip);
|
|
|
+ in_new = !!ftrace_lookup_ip(new_hash, rec->ip);
|
|
|
+ if (in_old == in_new)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (in_new) {
|
|
|
+ /* New entries must ensure no others are using it */
|
|
|
+ if (rec->flags & FTRACE_FL_IPMODIFY)
|
|
|
+ goto rollback;
|
|
|
+ rec->flags |= FTRACE_FL_IPMODIFY;
|
|
|
+ } else /* Removed entry */
|
|
|
+ rec->flags &= ~FTRACE_FL_IPMODIFY;
|
|
|
+ } while_for_each_ftrace_rec();
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+rollback:
|
|
|
+ end = rec;
|
|
|
+
|
|
|
+ /* Roll back what we did above */
|
|
|
+ do_for_each_ftrace_rec(pg, rec) {
|
|
|
+ if (rec == end)
|
|
|
+ goto err_out;
|
|
|
+
|
|
|
+ in_old = !!ftrace_lookup_ip(old_hash, rec->ip);
|
|
|
+ in_new = !!ftrace_lookup_ip(new_hash, rec->ip);
|
|
|
+ if (in_old == in_new)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (in_new)
|
|
|
+ rec->flags &= ~FTRACE_FL_IPMODIFY;
|
|
|
+ else
|
|
|
+ rec->flags |= FTRACE_FL_IPMODIFY;
|
|
|
+ } while_for_each_ftrace_rec();
|
|
|
+
|
|
|
+err_out:
|
|
|
+ return -EBUSY;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_hash_ipmodify_enable(struct ftrace_ops *ops)
|
|
|
+{
|
|
|
+ struct ftrace_hash *hash = ops->func_hash->filter_hash;
|
|
|
+
|
|
|
+ if (ftrace_hash_empty(hash))
|
|
|
+ hash = NULL;
|
|
|
+
|
|
|
+ return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash);
|
|
|
+}
|
|
|
+
|
|
|
+/* Disabling always succeeds */
|
|
|
+static void ftrace_hash_ipmodify_disable(struct ftrace_ops *ops)
|
|
|
+{
|
|
|
+ struct ftrace_hash *hash = ops->func_hash->filter_hash;
|
|
|
+
|
|
|
+ if (ftrace_hash_empty(hash))
|
|
|
+ hash = NULL;
|
|
|
+
|
|
|
+ __ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH);
|
|
|
+}
|
|
|
+
|
|
|
+static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
|
|
|
+ struct ftrace_hash *new_hash)
|
|
|
+{
|
|
|
+ struct ftrace_hash *old_hash = ops->func_hash->filter_hash;
|
|
|
+
|
|
|
+ if (ftrace_hash_empty(old_hash))
|
|
|
+ old_hash = NULL;
|
|
|
+
|
|
|
+ if (ftrace_hash_empty(new_hash))
|
|
|
+ new_hash = NULL;
|
|
|
+
|
|
|
+ return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash);
|
|
|
+}
|
|
|
+
|
|
|
static void print_ip_ins(const char *fmt, unsigned char *p)
|
|
|
{
|
|
|
int i;
|
|
@@ -2436,6 +2562,15 @@ static int ftrace_startup(struct ftrace_ops *ops, int command)
|
|
|
*/
|
|
|
ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING;
|
|
|
|
|
|
+ ret = ftrace_hash_ipmodify_enable(ops);
|
|
|
+ if (ret < 0) {
|
|
|
+ /* Rollback registration process */
|
|
|
+ __unregister_ftrace_function(ops);
|
|
|
+ ftrace_start_up--;
|
|
|
+ ops->flags &= ~FTRACE_OPS_FL_ENABLED;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
ftrace_hash_rec_enable(ops, 1);
|
|
|
|
|
|
ftrace_startup_enable(command);
|
|
@@ -2464,6 +2599,8 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command)
|
|
|
*/
|
|
|
WARN_ON_ONCE(ftrace_start_up < 0);
|
|
|
|
|
|
+ /* Disabling ipmodify never fails */
|
|
|
+ ftrace_hash_ipmodify_disable(ops);
|
|
|
ftrace_hash_rec_disable(ops, 1);
|
|
|
|
|
|
ops->flags &= ~FTRACE_OPS_FL_ENABLED;
|
|
@@ -3058,9 +3195,10 @@ static int t_show(struct seq_file *m, void *v)
|
|
|
if (iter->flags & FTRACE_ITER_ENABLED) {
|
|
|
struct ftrace_ops *ops = NULL;
|
|
|
|
|
|
- seq_printf(m, " (%ld)%s",
|
|
|
+ seq_printf(m, " (%ld)%s%s",
|
|
|
ftrace_rec_count(rec),
|
|
|
- rec->flags & FTRACE_FL_REGS ? " R" : " ");
|
|
|
+ rec->flags & FTRACE_FL_REGS ? " R" : " ",
|
|
|
+ rec->flags & FTRACE_FL_IPMODIFY ? " I" : " ");
|
|
|
if (rec->flags & FTRACE_FL_TRAMP_EN) {
|
|
|
ops = ftrace_find_tramp_ops_any(rec);
|
|
|
if (ops)
|