]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - kernel/trace/trace_uprobe.c
Merge tag 'backlight-next-5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee...
[linux.git] / kernel / trace / trace_uprobe.c
index 1ceedb9146b114e6225a7662044c6b00c4cba136..34dd6d0016a3b73080617bdcacb08e2451c6b67b 100644 (file)
@@ -44,7 +44,7 @@ static int trace_uprobe_show(struct seq_file *m, struct dyn_event *ev);
 static int trace_uprobe_release(struct dyn_event *ev);
 static bool trace_uprobe_is_busy(struct dyn_event *ev);
 static bool trace_uprobe_match(const char *system, const char *event,
-                              struct dyn_event *ev);
+                       int argc, const char **argv, struct dyn_event *ev);
 
 static struct dyn_event_operations trace_uprobe_ops = {
        .create = trace_uprobe_create,
@@ -248,6 +248,9 @@ process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, void *dest,
        case FETCH_OP_COMM:
                val = FETCH_TOKEN_COMM;
                break;
+       case FETCH_OP_DATA:
+               val = (unsigned long)code->data;
+               break;
        case FETCH_OP_FOFFS:
                val = translate_user_vaddr(code->immediate);
                break;
@@ -284,13 +287,54 @@ static bool trace_uprobe_is_busy(struct dyn_event *ev)
        return trace_probe_is_enabled(&tu->tp);
 }
 
+static bool trace_uprobe_match_command_head(struct trace_uprobe *tu,
+                                           int argc, const char **argv)
+{
+       char buf[MAX_ARGSTR_LEN + 1];
+       int len;
+
+       if (!argc)
+               return true;
+
+       len = strlen(tu->filename);
+       if (strncmp(tu->filename, argv[0], len) || argv[0][len] != ':')
+               return false;
+
+       if (tu->ref_ctr_offset == 0)
+               snprintf(buf, sizeof(buf), "0x%0*lx",
+                               (int)(sizeof(void *) * 2), tu->offset);
+       else
+               snprintf(buf, sizeof(buf), "0x%0*lx(0x%lx)",
+                               (int)(sizeof(void *) * 2), tu->offset,
+                               tu->ref_ctr_offset);
+       if (strcmp(buf, &argv[0][len + 1]))
+               return false;
+
+       argc--; argv++;
+
+       return trace_probe_match_command_args(&tu->tp, argc, argv);
+}
+
 static bool trace_uprobe_match(const char *system, const char *event,
-                              struct dyn_event *ev)
+                       int argc, const char **argv, struct dyn_event *ev)
 {
        struct trace_uprobe *tu = to_trace_uprobe(ev);
 
        return strcmp(trace_probe_name(&tu->tp), event) == 0 &&
-           (!system || strcmp(trace_probe_group_name(&tu->tp), system) == 0);
+          (!system || strcmp(trace_probe_group_name(&tu->tp), system) == 0) &&
+          trace_uprobe_match_command_head(tu, argc, argv);
+}
+
+static nokprobe_inline struct trace_uprobe *
+trace_uprobe_primary_from_call(struct trace_event_call *call)
+{
+       struct trace_probe *tp;
+
+       tp = trace_probe_primary_from_call(call);
+       if (WARN_ON_ONCE(!tp))
+               return NULL;
+
+       return container_of(tp, struct trace_uprobe, tp);
 }
 
 /*
@@ -352,15 +396,75 @@ static int unregister_trace_uprobe(struct trace_uprobe *tu)
 {
        int ret;
 
+       if (trace_probe_has_sibling(&tu->tp))
+               goto unreg;
+
        ret = unregister_uprobe_event(tu);
        if (ret)
                return ret;
 
+unreg:
        dyn_event_remove(&tu->devent);
+       trace_probe_unlink(&tu->tp);
        free_trace_uprobe(tu);
        return 0;
 }
 
+static bool trace_uprobe_has_same_uprobe(struct trace_uprobe *orig,
+                                        struct trace_uprobe *comp)
+{
+       struct trace_probe_event *tpe = orig->tp.event;
+       struct trace_probe *pos;
+       struct inode *comp_inode = d_real_inode(comp->path.dentry);
+       int i;
+
+       list_for_each_entry(pos, &tpe->probes, list) {
+               orig = container_of(pos, struct trace_uprobe, tp);
+               if (comp_inode != d_real_inode(orig->path.dentry) ||
+                   comp->offset != orig->offset)
+                       continue;
+
+               /*
+                * trace_probe_compare_arg_type() ensured that nr_args and
+                * each argument name and type are same. Let's compare comm.
+                */
+               for (i = 0; i < orig->tp.nr_args; i++) {
+                       if (strcmp(orig->tp.args[i].comm,
+                                  comp->tp.args[i].comm))
+                               continue;
+               }
+
+               return true;
+       }
+
+       return false;
+}
+
+static int append_trace_uprobe(struct trace_uprobe *tu, struct trace_uprobe *to)
+{
+       int ret;
+
+       ret = trace_probe_compare_arg_type(&tu->tp, &to->tp);
+       if (ret) {
+               /* Note that argument starts index = 2 */
+               trace_probe_log_set_index(ret + 1);
+               trace_probe_log_err(0, DIFF_ARG_TYPE);
+               return -EEXIST;
+       }
+       if (trace_uprobe_has_same_uprobe(to, tu)) {
+               trace_probe_log_set_index(0);
+               trace_probe_log_err(0, SAME_PROBE);
+               return -EEXIST;
+       }
+
+       /* Append to existing event */
+       ret = trace_probe_append(&tu->tp, &to->tp);
+       if (!ret)
+               dyn_event_add(&tu->devent);
+
+       return ret;
+}
+
 /*
  * Uprobe with multiple reference counter is not allowed. i.e.
  * If inode and offset matches, reference counter offset *must*
@@ -370,25 +474,21 @@ static int unregister_trace_uprobe(struct trace_uprobe *tu)
  * as the new one does not conflict with any other existing
  * ones.
  */
-static struct trace_uprobe *find_old_trace_uprobe(struct trace_uprobe *new)
+static int validate_ref_ctr_offset(struct trace_uprobe *new)
 {
        struct dyn_event *pos;
-       struct trace_uprobe *tmp, *old = NULL;
+       struct trace_uprobe *tmp;
        struct inode *new_inode = d_real_inode(new->path.dentry);
 
-       old = find_probe_event(trace_probe_name(&new->tp),
-                               trace_probe_group_name(&new->tp));
-
        for_each_trace_uprobe(tmp, pos) {
-               if ((old ? old != tmp : true) &&
-                   new_inode == d_real_inode(tmp->path.dentry) &&
+               if (new_inode == d_real_inode(tmp->path.dentry) &&
                    new->offset == tmp->offset &&
                    new->ref_ctr_offset != tmp->ref_ctr_offset) {
                        pr_warn("Reference counter offset mismatch.");
-                       return ERR_PTR(-EINVAL);
+                       return -EINVAL;
                }
        }
-       return old;
+       return 0;
 }
 
 /* Register a trace_uprobe and probe_event */
@@ -399,18 +499,22 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
 
        mutex_lock(&event_mutex);
 
-       /* register as an event */
-       old_tu = find_old_trace_uprobe(tu);
-       if (IS_ERR(old_tu)) {
-               ret = PTR_ERR(old_tu);
+       ret = validate_ref_ctr_offset(tu);
+       if (ret)
                goto end;
-       }
 
+       /* register as an event */
+       old_tu = find_probe_event(trace_probe_name(&tu->tp),
+                                 trace_probe_group_name(&tu->tp));
        if (old_tu) {
-               /* delete old event */
-               ret = unregister_trace_uprobe(old_tu);
-               if (ret)
-                       goto end;
+               if (is_ret_probe(tu) != is_ret_probe(old_tu)) {
+                       trace_probe_log_set_index(0);
+                       trace_probe_log_err(0, DIFF_PROBE_TYPE);
+                       ret = -EEXIST;
+               } else {
+                       ret = append_trace_uprobe(tu, old_tu);
+               }
+               goto end;
        }
 
        ret = register_uprobe_event(tu);
@@ -897,7 +1001,10 @@ print_uprobe_event(struct trace_iterator *iter, int flags, struct trace_event *e
        u8 *data;
 
        entry = (struct uprobe_trace_entry_head *)iter->ent;
-       tu = container_of(event, struct trace_uprobe, tp.call.event);
+       tu = trace_uprobe_primary_from_call(
+               container_of(event, struct trace_event_call, event));
+       if (unlikely(!tu))
+               goto out;
 
        if (is_ret_probe(tu)) {
                trace_seq_printf(s, "%s: (0x%lx <- 0x%lx)",
@@ -924,27 +1031,71 @@ typedef bool (*filter_func_t)(struct uprobe_consumer *self,
                                enum uprobe_filter_ctx ctx,
                                struct mm_struct *mm);
 
-static int
-probe_event_enable(struct trace_uprobe *tu, struct trace_event_file *file,
-                  filter_func_t filter)
+static int trace_uprobe_enable(struct trace_uprobe *tu, filter_func_t filter)
 {
-       bool enabled = trace_probe_is_enabled(&tu->tp);
        int ret;
 
+       tu->consumer.filter = filter;
+       tu->inode = d_real_inode(tu->path.dentry);
+
+       if (tu->ref_ctr_offset)
+               ret = uprobe_register_refctr(tu->inode, tu->offset,
+                               tu->ref_ctr_offset, &tu->consumer);
+       else
+               ret = uprobe_register(tu->inode, tu->offset, &tu->consumer);
+
+       if (ret)
+               tu->inode = NULL;
+
+       return ret;
+}
+
+static void __probe_event_disable(struct trace_probe *tp)
+{
+       struct trace_probe *pos;
+       struct trace_uprobe *tu;
+
+       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
+               tu = container_of(pos, struct trace_uprobe, tp);
+               if (!tu->inode)
+                       continue;
+
+               WARN_ON(!uprobe_filter_is_empty(&tu->filter));
+
+               uprobe_unregister(tu->inode, tu->offset, &tu->consumer);
+               tu->inode = NULL;
+       }
+}
+
+static int probe_event_enable(struct trace_event_call *call,
+                       struct trace_event_file *file, filter_func_t filter)
+{
+       struct trace_probe *pos, *tp;
+       struct trace_uprobe *tu;
+       bool enabled;
+       int ret;
+
+       tp = trace_probe_primary_from_call(call);
+       if (WARN_ON_ONCE(!tp))
+               return -ENODEV;
+       enabled = trace_probe_is_enabled(tp);
+
+       /* This may also change "enabled" state */
        if (file) {
-               if (trace_probe_test_flag(&tu->tp, TP_FLAG_PROFILE))
+               if (trace_probe_test_flag(tp, TP_FLAG_PROFILE))
                        return -EINTR;
 
-               ret = trace_probe_add_file(&tu->tp, file);
+               ret = trace_probe_add_file(tp, file);
                if (ret < 0)
                        return ret;
        } else {
-               if (trace_probe_test_flag(&tu->tp, TP_FLAG_TRACE))
+               if (trace_probe_test_flag(tp, TP_FLAG_TRACE))
                        return -EINTR;
 
-               trace_probe_set_flag(&tu->tp, TP_FLAG_PROFILE);
+               trace_probe_set_flag(tp, TP_FLAG_PROFILE);
        }
 
+       tu = container_of(tp, struct trace_uprobe, tp);
        WARN_ON(!uprobe_filter_is_empty(&tu->filter));
 
        if (enabled)
@@ -954,18 +1105,15 @@ probe_event_enable(struct trace_uprobe *tu, struct trace_event_file *file,
        if (ret)
                goto err_flags;
 
-       tu->consumer.filter = filter;
-       tu->inode = d_real_inode(tu->path.dentry);
-       if (tu->ref_ctr_offset) {
-               ret = uprobe_register_refctr(tu->inode, tu->offset,
-                               tu->ref_ctr_offset, &tu->consumer);
-       } else {
-               ret = uprobe_register(tu->inode, tu->offset, &tu->consumer);
+       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
+               tu = container_of(pos, struct trace_uprobe, tp);
+               ret = trace_uprobe_enable(tu, filter);
+               if (ret) {
+                       __probe_event_disable(tp);
+                       goto err_buffer;
+               }
        }
 
-       if (ret)
-               goto err_buffer;
-
        return 0;
 
  err_buffer:
@@ -973,33 +1121,35 @@ probe_event_enable(struct trace_uprobe *tu, struct trace_event_file *file,
 
  err_flags:
        if (file)
-               trace_probe_remove_file(&tu->tp, file);
+               trace_probe_remove_file(tp, file);
        else
-               trace_probe_clear_flag(&tu->tp, TP_FLAG_PROFILE);
+               trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
 
        return ret;
 }
 
-static void
-probe_event_disable(struct trace_uprobe *tu, struct trace_event_file *file)
+static void probe_event_disable(struct trace_event_call *call,
+                               struct trace_event_file *file)
 {
-       if (!trace_probe_is_enabled(&tu->tp))
+       struct trace_probe *tp;
+
+       tp = trace_probe_primary_from_call(call);
+       if (WARN_ON_ONCE(!tp))
+               return;
+
+       if (!trace_probe_is_enabled(tp))
                return;
 
        if (file) {
-               if (trace_probe_remove_file(&tu->tp, file) < 0)
+               if (trace_probe_remove_file(tp, file) < 0)
                        return;
 
-               if (trace_probe_is_enabled(&tu->tp))
+               if (trace_probe_is_enabled(tp))
                        return;
        } else
-               trace_probe_clear_flag(&tu->tp, TP_FLAG_PROFILE);
-
-       WARN_ON(!uprobe_filter_is_empty(&tu->filter));
-
-       uprobe_unregister(tu->inode, tu->offset, &tu->consumer);
-       tu->inode = NULL;
+               trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
 
+       __probe_event_disable(tp);
        uprobe_buffer_disable();
 }
 
@@ -1007,7 +1157,11 @@ static int uprobe_event_define_fields(struct trace_event_call *event_call)
 {
        int ret, size;
        struct uprobe_trace_entry_head field;
-       struct trace_uprobe *tu = event_call->data;
+       struct trace_uprobe *tu;
+
+       tu = trace_uprobe_primary_from_call(event_call);
+       if (unlikely(!tu))
+               return -ENODEV;
 
        if (is_ret_probe(tu)) {
                DEFINE_FIELD(unsigned long, vaddr[0], FIELD_STRING_FUNC, 0);
@@ -1100,6 +1254,27 @@ static int uprobe_perf_open(struct trace_uprobe *tu, struct perf_event *event)
        return err;
 }
 
+static int uprobe_perf_multi_call(struct trace_event_call *call,
+                                 struct perf_event *event,
+               int (*op)(struct trace_uprobe *tu, struct perf_event *event))
+{
+       struct trace_probe *pos, *tp;
+       struct trace_uprobe *tu;
+       int ret = 0;
+
+       tp = trace_probe_primary_from_call(call);
+       if (WARN_ON_ONCE(!tp))
+               return -ENODEV;
+
+       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
+               tu = container_of(pos, struct trace_uprobe, tp);
+               ret = op(tu, event);
+               if (ret)
+                       break;
+       }
+
+       return ret;
+}
 static bool uprobe_perf_filter(struct uprobe_consumer *uc,
                                enum uprobe_filter_ctx ctx, struct mm_struct *mm)
 {
@@ -1213,30 +1388,29 @@ static int
 trace_uprobe_register(struct trace_event_call *event, enum trace_reg type,
                      void *data)
 {
-       struct trace_uprobe *tu = event->data;
        struct trace_event_file *file = data;
 
        switch (type) {
        case TRACE_REG_REGISTER:
-               return probe_event_enable(tu, file, NULL);
+               return probe_event_enable(event, file, NULL);
 
        case TRACE_REG_UNREGISTER:
-               probe_event_disable(tu, file);
+               probe_event_disable(event, file);
                return 0;
 
 #ifdef CONFIG_PERF_EVENTS
        case TRACE_REG_PERF_REGISTER:
-               return probe_event_enable(tu, NULL, uprobe_perf_filter);
+               return probe_event_enable(event, NULL, uprobe_perf_filter);
 
        case TRACE_REG_PERF_UNREGISTER:
-               probe_event_disable(tu, NULL);
+               probe_event_disable(event, NULL);
                return 0;
 
        case TRACE_REG_PERF_OPEN:
-               return uprobe_perf_open(tu, data);
+               return uprobe_perf_multi_call(event, data, uprobe_perf_open);
 
        case TRACE_REG_PERF_CLOSE:
-               return uprobe_perf_close(tu, data);
+               return uprobe_perf_multi_call(event, data, uprobe_perf_close);
 
 #endif
        default:
@@ -1330,7 +1504,6 @@ static inline void init_trace_event_call(struct trace_uprobe *tu)
 
        call->flags = TRACE_EVENT_FL_UPROBE | TRACE_EVENT_FL_CAP_ANY;
        call->class->reg = trace_uprobe_register;
-       call->data = tu;
 }
 
 static int register_uprobe_event(struct trace_uprobe *tu)
@@ -1399,7 +1572,7 @@ void destroy_local_trace_uprobe(struct trace_event_call *event_call)
 {
        struct trace_uprobe *tu;
 
-       tu = container_of(event_call, struct trace_uprobe, tp.call);
+       tu = trace_uprobe_primary_from_call(event_call);
 
        free_trace_uprobe(tu);
 }