]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - kernel/trace/trace_kprobe.c
tracing: Fix clang -Wint-in-bool-context warnings in IF_ASSIGN macro
[linux.git] / kernel / trace / trace_kprobe.c
index 9d483ad9bb6c40b48963d7a1f5b11e28318f49ae..402dc3ce88d3ea2917954271f02dbcfde38b8f6f 100644 (file)
@@ -39,7 +39,7 @@ static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev);
 static int trace_kprobe_release(struct dyn_event *ev);
 static bool trace_kprobe_is_busy(struct dyn_event *ev);
 static bool trace_kprobe_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_kprobe_ops = {
        .create = trace_kprobe_create,
@@ -137,13 +137,36 @@ static bool trace_kprobe_is_busy(struct dyn_event *ev)
        return trace_probe_is_enabled(&tk->tp);
 }
 
+static bool trace_kprobe_match_command_head(struct trace_kprobe *tk,
+                                           int argc, const char **argv)
+{
+       char buf[MAX_ARGSTR_LEN + 1];
+
+       if (!argc)
+               return true;
+
+       if (!tk->symbol)
+               snprintf(buf, sizeof(buf), "0x%p", tk->rp.kp.addr);
+       else if (tk->rp.kp.offset)
+               snprintf(buf, sizeof(buf), "%s+%u",
+                        trace_kprobe_symbol(tk), tk->rp.kp.offset);
+       else
+               snprintf(buf, sizeof(buf), "%s", trace_kprobe_symbol(tk));
+       if (strcmp(buf, argv[0]))
+               return false;
+       argc--; argv++;
+
+       return trace_probe_match_command_args(&tk->tp, argc, argv);
+}
+
 static bool trace_kprobe_match(const char *system, const char *event,
-                              struct dyn_event *ev)
+                       int argc, const char **argv, struct dyn_event *ev)
 {
        struct trace_kprobe *tk = to_trace_kprobe(ev);
 
        return strcmp(trace_probe_name(&tk->tp), event) == 0 &&
-           (!system || strcmp(trace_probe_group_name(&tk->tp), system) == 0);
+           (!system || strcmp(trace_probe_group_name(&tk->tp), system) == 0) &&
+           trace_kprobe_match_command_head(tk, argc, argv);
 }
 
 static nokprobe_inline unsigned long trace_kprobe_nhit(struct trace_kprobe *tk)
@@ -180,20 +203,33 @@ unsigned long trace_kprobe_address(struct trace_kprobe *tk)
        return addr;
 }
 
+static nokprobe_inline struct trace_kprobe *
+trace_kprobe_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_kprobe, tp);
+}
+
 bool trace_kprobe_on_func_entry(struct trace_event_call *call)
 {
-       struct trace_kprobe *tk = (struct trace_kprobe *)call->data;
+       struct trace_kprobe *tk = trace_kprobe_primary_from_call(call);
 
-       return kprobe_on_func_entry(tk->rp.kp.addr,
+       return tk ? kprobe_on_func_entry(tk->rp.kp.addr,
                        tk->rp.kp.addr ? NULL : tk->rp.kp.symbol_name,
-                       tk->rp.kp.addr ? 0 : tk->rp.kp.offset);
+                       tk->rp.kp.addr ? 0 : tk->rp.kp.offset) : false;
 }
 
 bool trace_kprobe_error_injectable(struct trace_event_call *call)
 {
-       struct trace_kprobe *tk = (struct trace_kprobe *)call->data;
+       struct trace_kprobe *tk = trace_kprobe_primary_from_call(call);
 
-       return within_error_injection_list(trace_kprobe_address(tk));
+       return tk ? within_error_injection_list(trace_kprobe_address(tk)) :
+              false;
 }
 
 static int register_kprobe_event(struct trace_kprobe *tk);
@@ -291,32 +327,68 @@ static inline int __enable_trace_kprobe(struct trace_kprobe *tk)
        return ret;
 }
 
+static void __disable_trace_kprobe(struct trace_probe *tp)
+{
+       struct trace_probe *pos;
+       struct trace_kprobe *tk;
+
+       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
+               tk = container_of(pos, struct trace_kprobe, tp);
+               if (!trace_kprobe_is_registered(tk))
+                       continue;
+               if (trace_kprobe_is_return(tk))
+                       disable_kretprobe(&tk->rp);
+               else
+                       disable_kprobe(&tk->rp.kp);
+       }
+}
+
 /*
  * Enable trace_probe
  * if the file is NULL, enable "perf" handler, or enable "trace" handler.
  */
-static int
-enable_trace_kprobe(struct trace_kprobe *tk, struct trace_event_file *file)
+static int enable_trace_kprobe(struct trace_event_call *call,
+                               struct trace_event_file *file)
 {
-       bool enabled = trace_probe_is_enabled(&tk->tp);
+       struct trace_probe *pos, *tp;
+       struct trace_kprobe *tk;
+       bool enabled;
        int ret = 0;
 
+       tp = trace_probe_primary_from_call(call);
+       if (WARN_ON_ONCE(!tp))
+               return -ENODEV;
+       enabled = trace_probe_is_enabled(tp);
+
+       /* This also changes "enabled" state */
        if (file) {
-               ret = trace_probe_add_file(&tk->tp, file);
+               ret = trace_probe_add_file(tp, file);
                if (ret)
                        return ret;
        } else
-               trace_probe_set_flag(&tk->tp, TP_FLAG_PROFILE);
+               trace_probe_set_flag(tp, TP_FLAG_PROFILE);
 
        if (enabled)
                return 0;
 
-       ret = __enable_trace_kprobe(tk);
+       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
+               tk = container_of(pos, struct trace_kprobe, tp);
+               if (trace_kprobe_has_gone(tk))
+                       continue;
+               ret = __enable_trace_kprobe(tk);
+               if (ret)
+                       break;
+               enabled = true;
+       }
+
        if (ret) {
+               /* Failed to enable one of them. Roll back all */
+               if (enabled)
+                       __disable_trace_kprobe(tp);
                if (file)
-                       trace_probe_remove_file(&tk->tp, file);
+                       trace_probe_remove_file(tp, file);
                else
-                       trace_probe_clear_flag(&tk->tp, TP_FLAG_PROFILE);
+                       trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
        }
 
        return ret;
@@ -326,11 +398,14 @@ enable_trace_kprobe(struct trace_kprobe *tk, struct trace_event_file *file)
  * Disable trace_probe
  * if the file is NULL, disable "perf" handler, or disable "trace" handler.
  */
-static int
-disable_trace_kprobe(struct trace_kprobe *tk, struct trace_event_file *file)
+static int disable_trace_kprobe(struct trace_event_call *call,
+                               struct trace_event_file *file)
 {
-       struct trace_probe *tp = &tk->tp;
-       int ret = 0;
+       struct trace_probe *tp;
+
+       tp = trace_probe_primary_from_call(call);
+       if (WARN_ON_ONCE(!tp))
+               return -ENODEV;
 
        if (file) {
                if (!trace_probe_get_file_link(tp, file))
@@ -341,12 +416,8 @@ disable_trace_kprobe(struct trace_kprobe *tk, struct trace_event_file *file)
        } else
                trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
 
-       if (!trace_probe_is_enabled(tp) && trace_kprobe_is_registered(tk)) {
-               if (trace_kprobe_is_return(tk))
-                       disable_kretprobe(&tk->rp);
-               else
-                       disable_kprobe(&tk->rp.kp);
-       }
+       if (!trace_probe_is_enabled(tp))
+               __disable_trace_kprobe(tp);
 
  out:
        if (file)
@@ -358,7 +429,7 @@ disable_trace_kprobe(struct trace_kprobe *tk, struct trace_event_file *file)
                 */
                trace_probe_remove_file(tp, file);
 
-       return ret;
+       return 0;
 }
 
 #if defined(CONFIG_KPROBES_ON_FTRACE) && \
@@ -437,6 +508,10 @@ static void __unregister_trace_kprobe(struct trace_kprobe *tk)
 /* Unregister a trace_probe and probe_event */
 static int unregister_trace_kprobe(struct trace_kprobe *tk)
 {
+       /* If other probes are on the event, just unregister kprobe */
+       if (trace_probe_has_sibling(&tk->tp))
+               goto unreg;
+
        /* Enabled event can not be unregistered */
        if (trace_probe_is_enabled(&tk->tp))
                return -EBUSY;
@@ -445,12 +520,82 @@ static int unregister_trace_kprobe(struct trace_kprobe *tk)
        if (unregister_kprobe_event(tk))
                return -EBUSY;
 
+unreg:
        __unregister_trace_kprobe(tk);
        dyn_event_remove(&tk->devent);
+       trace_probe_unlink(&tk->tp);
 
        return 0;
 }
 
+static bool trace_kprobe_has_same_kprobe(struct trace_kprobe *orig,
+                                        struct trace_kprobe *comp)
+{
+       struct trace_probe_event *tpe = orig->tp.event;
+       struct trace_probe *pos;
+       int i;
+
+       list_for_each_entry(pos, &tpe->probes, list) {
+               orig = container_of(pos, struct trace_kprobe, tp);
+               if (strcmp(trace_kprobe_symbol(orig),
+                          trace_kprobe_symbol(comp)) ||
+                   trace_kprobe_offset(orig) != trace_kprobe_offset(comp))
+                       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))
+                               break;
+               }
+
+               if (i == orig->tp.nr_args)
+                       return true;
+       }
+
+       return false;
+}
+
+static int append_trace_kprobe(struct trace_kprobe *tk, struct trace_kprobe *to)
+{
+       int ret;
+
+       ret = trace_probe_compare_arg_type(&tk->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_kprobe_has_same_kprobe(to, tk)) {
+               trace_probe_log_set_index(0);
+               trace_probe_log_err(0, SAME_PROBE);
+               return -EEXIST;
+       }
+
+       /* Append to existing event */
+       ret = trace_probe_append(&tk->tp, &to->tp);
+       if (ret)
+               return ret;
+
+       /* Register k*probe */
+       ret = __register_trace_kprobe(tk);
+       if (ret == -ENOENT && !trace_kprobe_module_exist(tk)) {
+               pr_warn("This probe might be able to register after target module is loaded. Continue.\n");
+               ret = 0;
+       }
+
+       if (ret)
+               trace_probe_unlink(&tk->tp);
+       else
+               dyn_event_add(&tk->devent);
+
+       return ret;
+}
+
 /* Register a trace_probe and probe_event */
 static int register_trace_kprobe(struct trace_kprobe *tk)
 {
@@ -459,14 +604,17 @@ static int register_trace_kprobe(struct trace_kprobe *tk)
 
        mutex_lock(&event_mutex);
 
-       /* Delete old (same name) event if exist */
        old_tk = find_trace_kprobe(trace_probe_name(&tk->tp),
                                   trace_probe_group_name(&tk->tp));
        if (old_tk) {
-               ret = unregister_trace_kprobe(old_tk);
-               if (ret < 0)
-                       goto end;
-               free_trace_kprobe(old_tk);
+               if (trace_kprobe_is_return(tk) != trace_kprobe_is_return(old_tk)) {
+                       trace_probe_log_set_index(0);
+                       trace_probe_log_err(0, DIFF_PROBE_TYPE);
+                       ret = -EEXIST;
+               } else {
+                       ret = append_trace_kprobe(tk, old_tk);
+               }
+               goto end;
        }
 
        /* Register new event */
@@ -700,7 +848,7 @@ static int trace_kprobe_create(int argc, const char *argv[])
                        trace_probe_log_err(0, BAD_INSN_BNDRY);
                else if (ret == -ENOENT)
                        trace_probe_log_err(0, BAD_PROBE_ADDR);
-               else if (ret != -ENOMEM)
+               else if (ret != -ENOMEM && ret != -EEXIST)
                        trace_probe_log_err(0, FAIL_REG_PROBE);
                goto error;
        }
@@ -965,6 +1113,9 @@ process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, void *dest,
        case FETCH_OP_COMM:
                val = (unsigned long)current->comm;
                break;
+       case FETCH_OP_DATA:
+               val = (unsigned long)code->data;
+               break;
 #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
        case FETCH_OP_ARG:
                val = regs_get_kernel_argument(regs, code->param);
@@ -1089,7 +1240,10 @@ print_kprobe_event(struct trace_iterator *iter, int flags,
        struct trace_probe *tp;
 
        field = (struct kprobe_trace_entry_head *)iter->ent;
-       tp = container_of(event, struct trace_probe, call.event);
+       tp = trace_probe_primary_from_call(
+               container_of(event, struct trace_event_call, event));
+       if (WARN_ON_ONCE(!tp))
+               goto out;
 
        trace_seq_printf(s, "%s: (", trace_probe_name(tp));
 
@@ -1116,7 +1270,10 @@ print_kretprobe_event(struct trace_iterator *iter, int flags,
        struct trace_probe *tp;
 
        field = (struct kretprobe_trace_entry_head *)iter->ent;
-       tp = container_of(event, struct trace_probe, call.event);
+       tp = trace_probe_primary_from_call(
+               container_of(event, struct trace_event_call, event));
+       if (WARN_ON_ONCE(!tp))
+               goto out;
 
        trace_seq_printf(s, "%s: (", trace_probe_name(tp));
 
@@ -1145,23 +1302,31 @@ static int kprobe_event_define_fields(struct trace_event_call *event_call)
 {
        int ret;
        struct kprobe_trace_entry_head field;
-       struct trace_kprobe *tk = (struct trace_kprobe *)event_call->data;
+       struct trace_probe *tp;
+
+       tp = trace_probe_primary_from_call(event_call);
+       if (WARN_ON_ONCE(!tp))
+               return -ENOENT;
 
        DEFINE_FIELD(unsigned long, ip, FIELD_STRING_IP, 0);
 
-       return traceprobe_define_arg_fields(event_call, sizeof(field), &tk->tp);
+       return traceprobe_define_arg_fields(event_call, sizeof(field), tp);
 }
 
 static int kretprobe_event_define_fields(struct trace_event_call *event_call)
 {
        int ret;
        struct kretprobe_trace_entry_head field;
-       struct trace_kprobe *tk = (struct trace_kprobe *)event_call->data;
+       struct trace_probe *tp;
+
+       tp = trace_probe_primary_from_call(event_call);
+       if (WARN_ON_ONCE(!tp))
+               return -ENOENT;
 
        DEFINE_FIELD(unsigned long, func, FIELD_STRING_FUNC, 0);
        DEFINE_FIELD(unsigned long, ret_ip, FIELD_STRING_RETIP, 0);
 
-       return traceprobe_define_arg_fields(event_call, sizeof(field), &tk->tp);
+       return traceprobe_define_arg_fields(event_call, sizeof(field), tp);
 }
 
 #ifdef CONFIG_PERF_EVENTS
@@ -1289,20 +1454,19 @@ int bpf_get_kprobe_info(const struct perf_event *event, u32 *fd_type,
 static int kprobe_register(struct trace_event_call *event,
                           enum trace_reg type, void *data)
 {
-       struct trace_kprobe *tk = (struct trace_kprobe *)event->data;
        struct trace_event_file *file = data;
 
        switch (type) {
        case TRACE_REG_REGISTER:
-               return enable_trace_kprobe(tk, file);
+               return enable_trace_kprobe(event, file);
        case TRACE_REG_UNREGISTER:
-               return disable_trace_kprobe(tk, file);
+               return disable_trace_kprobe(event, file);
 
 #ifdef CONFIG_PERF_EVENTS
        case TRACE_REG_PERF_REGISTER:
-               return enable_trace_kprobe(tk, NULL);
+               return enable_trace_kprobe(event, NULL);
        case TRACE_REG_PERF_UNREGISTER:
-               return disable_trace_kprobe(tk, NULL);
+               return disable_trace_kprobe(event, NULL);
        case TRACE_REG_PERF_OPEN:
        case TRACE_REG_PERF_CLOSE:
        case TRACE_REG_PERF_ADD:
@@ -1369,7 +1533,6 @@ static inline void init_trace_event_call(struct trace_kprobe *tk)
 
        call->flags = TRACE_EVENT_FL_KPROBE;
        call->class->reg = kprobe_register;
-       call->data = tk;
 }
 
 static int register_kprobe_event(struct trace_kprobe *tk)
@@ -1432,7 +1595,9 @@ void destroy_local_trace_kprobe(struct trace_event_call *event_call)
 {
        struct trace_kprobe *tk;
 
-       tk = container_of(event_call, struct trace_kprobe, tp.call);
+       tk = trace_kprobe_primary_from_call(event_call);
+       if (unlikely(!tk))
+               return;
 
        if (trace_probe_is_enabled(&tk->tp)) {
                WARN_ON(1);
@@ -1577,7 +1742,8 @@ static __init int kprobe_trace_self_tests_init(void)
                                pr_warn("error on getting probe file.\n");
                                warn++;
                        } else
-                               enable_trace_kprobe(tk, file);
+                               enable_trace_kprobe(
+                                       trace_probe_event_call(&tk->tp), file);
                }
        }
 
@@ -1598,7 +1764,8 @@ static __init int kprobe_trace_self_tests_init(void)
                                pr_warn("error on getting probe file.\n");
                                warn++;
                        } else
-                               enable_trace_kprobe(tk, file);
+                               enable_trace_kprobe(
+                                       trace_probe_event_call(&tk->tp), file);
                }
        }
 
@@ -1631,7 +1798,8 @@ static __init int kprobe_trace_self_tests_init(void)
                        pr_warn("error on getting probe file.\n");
                        warn++;
                } else
-                       disable_trace_kprobe(tk, file);
+                       disable_trace_kprobe(
+                               trace_probe_event_call(&tk->tp), file);
        }
 
        tk = find_trace_kprobe("testprobe2", KPROBE_EVENT_SYSTEM);
@@ -1649,7 +1817,8 @@ static __init int kprobe_trace_self_tests_init(void)
                        pr_warn("error on getting probe file.\n");
                        warn++;
                } else
-                       disable_trace_kprobe(tk, file);
+                       disable_trace_kprobe(
+                               trace_probe_event_call(&tk->tp), file);
        }
 
        ret = trace_run_command("-:testprobe", create_or_delete_trace_kprobe);