| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * event probes | |
| * | |
| * Part of this code was copied from kernel/trace/trace_kprobe.c written by | |
| * Masami Hiramatsu <mhiramat@kernel.org> | |
| * | |
| * Copyright (C) 2021, VMware Inc, Steven Rostedt <rostedt@goodmis.org> | |
| * Copyright (C) 2021, VMware Inc, Tzvetomir Stoyanov tz.stoyanov@gmail.com> | |
| * | |
| */ | |
| #include <linux/cleanup.h> | |
| #include <linux/ftrace.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include "trace_dynevent.h" | |
| #include "trace_probe.h" | |
| #include "trace_probe_kernel.h" | |
| #include "trace_probe_tmpl.h" | |
| #define EPROBE_EVENT_SYSTEM "eprobes" | |
| struct trace_eprobe { | |
| /* tracepoint system */ | |
| const char *event_system; | |
| /* tracepoint event */ | |
| const char *event_name; | |
| /* filter string for the tracepoint */ | |
| char *filter_str; | |
| struct trace_event_call *event; | |
| struct dyn_event devent; | |
| struct trace_probe tp; | |
| }; | |
| struct eprobe_data { | |
| struct trace_event_file *file; | |
| struct trace_eprobe *ep; | |
| }; | |
| #define for_each_trace_eprobe_tp(ep, _tp) \ | |
| list_for_each_entry(ep, trace_probe_probe_list(_tp), tp.list) | |
| static int __trace_eprobe_create(int argc, const char *argv[]); | |
| static void trace_event_probe_cleanup(struct trace_eprobe *ep) | |
| { | |
| if (!ep) | |
| return; | |
| trace_probe_cleanup(&ep->tp); | |
| kfree(ep->event_name); | |
| kfree(ep->event_system); | |
| if (ep->event) | |
| trace_event_put_ref(ep->event); | |
| kfree(ep->filter_str); | |
| kfree(ep); | |
| } | |
| DEFINE_FREE(trace_event_probe_cleanup, struct trace_eprobe *, | |
| if (!IS_ERR_OR_NULL(_T)) trace_event_probe_cleanup(_T)) | |
| static struct trace_eprobe *to_trace_eprobe(struct dyn_event *ev) | |
| { | |
| return container_of(ev, struct trace_eprobe, devent); | |
| } | |
| static int eprobe_dyn_event_create(const char *raw_command) | |
| { | |
| return trace_probe_create(raw_command, __trace_eprobe_create); | |
| } | |
| static int eprobe_dyn_event_show(struct seq_file *m, struct dyn_event *ev) | |
| { | |
| struct trace_eprobe *ep = to_trace_eprobe(ev); | |
| int i; | |
| seq_printf(m, "e:%s/%s", trace_probe_group_name(&ep->tp), | |
| trace_probe_name(&ep->tp)); | |
| seq_printf(m, " %s.%s", ep->event_system, ep->event_name); | |
| for (i = 0; i < ep->tp.nr_args; i++) | |
| seq_printf(m, " %s=%s", ep->tp.args[i].name, ep->tp.args[i].comm); | |
| seq_putc(m, '\n'); | |
| return 0; | |
| } | |
| static int unregister_trace_eprobe(struct trace_eprobe *ep) | |
| { | |
| /* If other probes are on the event, just unregister eprobe */ | |
| if (trace_probe_has_sibling(&ep->tp)) | |
| goto unreg; | |
| /* Enabled event can not be unregistered */ | |
| if (trace_probe_is_enabled(&ep->tp)) | |
| return -EBUSY; | |
| /* Will fail if probe is being used by ftrace or perf */ | |
| if (trace_probe_unregister_event_call(&ep->tp)) | |
| return -EBUSY; | |
| unreg: | |
| dyn_event_remove(&ep->devent); | |
| trace_probe_unlink(&ep->tp); | |
| return 0; | |
| } | |
| static int eprobe_dyn_event_release(struct dyn_event *ev) | |
| { | |
| struct trace_eprobe *ep = to_trace_eprobe(ev); | |
| int ret = unregister_trace_eprobe(ep); | |
| if (!ret) | |
| trace_event_probe_cleanup(ep); | |
| return ret; | |
| } | |
| static bool eprobe_dyn_event_is_busy(struct dyn_event *ev) | |
| { | |
| struct trace_eprobe *ep = to_trace_eprobe(ev); | |
| return trace_probe_is_enabled(&ep->tp); | |
| } | |
| static bool eprobe_dyn_event_match(const char *system, const char *event, | |
| int argc, const char **argv, struct dyn_event *ev) | |
| { | |
| struct trace_eprobe *ep = to_trace_eprobe(ev); | |
| const char *slash; | |
| /* | |
| * We match the following: | |
| * event only - match all eprobes with event name | |
| * system and event only - match all system/event probes | |
| * system only - match all system probes | |
| * | |
| * The below has the above satisfied with more arguments: | |
| * | |
| * attached system/event - If the arg has the system and event | |
| * the probe is attached to, match | |
| * probes with the attachment. | |
| * | |
| * If any more args are given, then it requires a full match. | |
| */ | |
| /* | |
| * If system exists, but this probe is not part of that system | |
| * do not match. | |
| */ | |
| if (system && strcmp(trace_probe_group_name(&ep->tp), system) != 0) | |
| return false; | |
| /* Must match the event name */ | |
| if (event[0] != '\0' && strcmp(trace_probe_name(&ep->tp), event) != 0) | |
| return false; | |
| /* No arguments match all */ | |
| if (argc < 1) | |
| return true; | |
| /* First argument is the system/event the probe is attached to */ | |
| slash = strchr(argv[0], '/'); | |
| if (!slash) | |
| slash = strchr(argv[0], '.'); | |
| if (!slash) | |
| return false; | |
| if (strncmp(ep->event_system, argv[0], slash - argv[0]) || | |
| ep->event_system[slash - argv[0]] != '\0') | |
| return false; | |
| if (strcmp(ep->event_name, slash + 1)) | |
| return false; | |
| argc--; | |
| argv++; | |
| /* If there are no other args, then match */ | |
| if (argc < 1) | |
| return true; | |
| return trace_probe_match_command_args(&ep->tp, argc, argv); | |
| } | |
| static struct dyn_event_operations eprobe_dyn_event_ops = { | |
| .create = eprobe_dyn_event_create, | |
| .show = eprobe_dyn_event_show, | |
| .is_busy = eprobe_dyn_event_is_busy, | |
| .free = eprobe_dyn_event_release, | |
| .match = eprobe_dyn_event_match, | |
| }; | |
| static struct trace_eprobe *alloc_event_probe(const char *group, | |
| const char *this_event, | |
| struct trace_event_call *event, | |
| int nargs) | |
| { | |
| struct trace_eprobe *ep __free(trace_event_probe_cleanup) = NULL; | |
| const char *event_name; | |
| const char *sys_name; | |
| int ret; | |
| if (!event) | |
| return ERR_PTR(-ENODEV); | |
| sys_name = event->class->system; | |
| event_name = trace_event_name(event); | |
| ep = kzalloc_flex(*ep, tp.args, nargs); | |
| if (!ep) { | |
| trace_event_put_ref(event); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| ep->event = event; | |
| ep->event_name = kstrdup(event_name, GFP_KERNEL); | |
| if (!ep->event_name) | |
| return ERR_PTR(-ENOMEM); | |
| ep->event_system = kstrdup(sys_name, GFP_KERNEL); | |
| if (!ep->event_system) | |
| return ERR_PTR(-ENOMEM); | |
| ret = trace_probe_init(&ep->tp, this_event, group, false, nargs); | |
| if (ret < 0) | |
| return ERR_PTR(ret); | |
| dyn_event_init(&ep->devent, &eprobe_dyn_event_ops); | |
| return_ptr(ep); | |
| } | |
| static int eprobe_event_define_fields(struct trace_event_call *event_call) | |
| { | |
| struct eprobe_trace_entry_head field; | |
| struct trace_probe *tp; | |
| tp = trace_probe_primary_from_call(event_call); | |
| if (WARN_ON_ONCE(!tp)) | |
| return -ENOENT; | |
| return traceprobe_define_arg_fields(event_call, sizeof(field), tp); | |
| } | |
| static struct trace_event_fields eprobe_fields_array[] = { | |
| { .type = TRACE_FUNCTION_TYPE, | |
| .define_fields = eprobe_event_define_fields }, | |
| {} | |
| }; | |
| /* Event entry printers */ | |
| static enum print_line_t | |
| print_eprobe_event(struct trace_iterator *iter, int flags, | |
| struct trace_event *event) | |
| { | |
| struct eprobe_trace_entry_head *field; | |
| struct trace_event_call *pevent; | |
| struct trace_event *probed_event; | |
| struct trace_seq *s = &iter->seq; | |
| struct trace_eprobe *ep; | |
| struct trace_probe *tp; | |
| unsigned int type; | |
| field = (struct eprobe_trace_entry_head *)iter->ent; | |
| tp = trace_probe_primary_from_call( | |
| container_of(event, struct trace_event_call, event)); | |
| if (WARN_ON_ONCE(!tp)) | |
| goto out; | |
| ep = container_of(tp, struct trace_eprobe, tp); | |
| type = ep->event->event.type; | |
| trace_seq_printf(s, "%s: (", trace_probe_name(tp)); | |
| probed_event = ftrace_find_event(type); | |
| if (probed_event) { | |
| pevent = container_of(probed_event, struct trace_event_call, event); | |
| trace_seq_printf(s, "%s.%s", pevent->class->system, | |
| trace_event_name(pevent)); | |
| } else { | |
| trace_seq_printf(s, "%u", type); | |
| } | |
| trace_seq_putc(s, ')'); | |
| if (trace_probe_print_args(s, tp->args, tp->nr_args, | |
| (u8 *)&field[1], field) < 0) | |
| goto out; | |
| trace_seq_putc(s, '\n'); | |
| out: | |
| return trace_handle_return(s); | |
| } | |
| static nokprobe_inline unsigned long | |
| get_event_field(struct fetch_insn *code, void *rec) | |
| { | |
| struct ftrace_event_field *field = code->data; | |
| unsigned long val; | |
| void *addr; | |
| addr = rec + field->offset; | |
| if (is_string_field(field)) { | |
| switch (field->filter_type) { | |
| case FILTER_DYN_STRING: | |
| val = (unsigned long)(rec + (*(unsigned int *)addr & 0xffff)); | |
| break; | |
| case FILTER_RDYN_STRING: | |
| val = (unsigned long)(addr + (*(unsigned int *)addr & 0xffff)); | |
| break; | |
| case FILTER_STATIC_STRING: | |
| val = (unsigned long)addr; | |
| break; | |
| case FILTER_PTR_STRING: | |
| val = *(unsigned long *)addr; | |
| break; | |
| default: | |
| WARN_ON_ONCE(1); | |
| return 0; | |
| } | |
| return val; | |
| } | |
| switch (field->size) { | |
| case 1: | |
| if (field->is_signed) | |
| val = *(char *)addr; | |
| else | |
| val = *(unsigned char *)addr; | |
| break; | |
| case 2: | |
| if (field->is_signed) | |
| val = *(short *)addr; | |
| else | |
| val = *(unsigned short *)addr; | |
| break; | |
| case 4: | |
| if (field->is_signed) | |
| val = *(int *)addr; | |
| else | |
| val = *(unsigned int *)addr; | |
| break; | |
| default: | |
| if (field->size == sizeof(long)) { | |
| if (field->is_signed) | |
| val = *(long *)addr; | |
| else | |
| val = *(unsigned long *)addr; | |
| break; | |
| } | |
| /* This is an array, point to the addr itself */ | |
| val = (unsigned long)addr; | |
| break; | |
| } | |
| return val; | |
| } | |
| static int get_eprobe_size(struct trace_probe *tp, void *rec) | |
| { | |
| struct fetch_insn *code; | |
| struct probe_arg *arg; | |
| int i, len, ret = 0; | |
| for (i = 0; i < tp->nr_args; i++) { | |
| arg = tp->args + i; | |
| if (arg->dynamic) { | |
| unsigned long val; | |
| code = arg->code; | |
| retry: | |
| switch (code->op) { | |
| case FETCH_OP_TP_ARG: | |
| val = get_event_field(code, rec); | |
| break; | |
| case FETCH_NOP_SYMBOL: /* Ignore a place holder */ | |
| code++; | |
| goto retry; | |
| default: | |
| if (process_common_fetch_insn(code, &val) < 0) | |
| continue; | |
| } | |
| code++; | |
| len = process_fetch_insn_bottom(code, val, NULL, NULL); | |
| if (len > 0) | |
| ret += len; | |
| } | |
| } | |
| return ret; | |
| } | |
| /* Kprobe specific fetch functions */ | |
| /* Note that we don't verify it, since the code does not come from user space */ | |
| static int | |
| process_fetch_insn(struct fetch_insn *code, void *rec, void *edata, | |
| void *dest, void *base) | |
| { | |
| unsigned long val; | |
| int ret; | |
| retry: | |
| switch (code->op) { | |
| case FETCH_OP_TP_ARG: | |
| val = get_event_field(code, rec); | |
| break; | |
| case FETCH_NOP_SYMBOL: /* Ignore a place holder */ | |
| code++; | |
| goto retry; | |
| default: | |
| ret = process_common_fetch_insn(code, &val); | |
| if (ret < 0) | |
| return ret; | |
| } | |
| code++; | |
| return process_fetch_insn_bottom(code, val, dest, base); | |
| } | |
| NOKPROBE_SYMBOL(process_fetch_insn) | |
| /* eprobe handler */ | |
| static inline void | |
| __eprobe_trace_func(struct eprobe_data *edata, void *rec) | |
| { | |
| struct eprobe_trace_entry_head *entry; | |
| struct trace_event_call *call = trace_probe_event_call(&edata->ep->tp); | |
| struct trace_event_buffer fbuffer; | |
| int dsize; | |
| if (WARN_ON_ONCE(call != edata->file->event_call)) | |
| return; | |
| if (trace_trigger_soft_disabled(edata->file)) | |
| return; | |
| dsize = get_eprobe_size(&edata->ep->tp, rec); | |
| entry = trace_event_buffer_reserve(&fbuffer, edata->file, | |
| sizeof(*entry) + edata->ep->tp.size + dsize); | |
| if (!entry) | |
| return; | |
| entry = fbuffer.entry = ring_buffer_event_data(fbuffer.event); | |
| store_trace_args(&entry[1], &edata->ep->tp, rec, NULL, sizeof(*entry), dsize); | |
| trace_event_buffer_commit(&fbuffer); | |
| } | |
| /* | |
| * The event probe implementation uses event triggers to get access to | |
| * the event it is attached to, but is not an actual trigger. The below | |
| * functions are just stubs to fulfill what is needed to use the trigger | |
| * infrastructure. | |
| */ | |
| static int eprobe_trigger_init(struct event_trigger_data *data) | |
| { | |
| return 0; | |
| } | |
| static void eprobe_trigger_free(struct event_trigger_data *data) | |
| { | |
| } | |
| static int eprobe_trigger_print(struct seq_file *m, | |
| struct event_trigger_data *data) | |
| { | |
| /* Do not print eprobe event triggers */ | |
| return 0; | |
| } | |
| static void eprobe_trigger_func(struct event_trigger_data *data, | |
| struct trace_buffer *buffer, void *rec, | |
| struct ring_buffer_event *rbe) | |
| { | |
| struct eprobe_data *edata = data->private_data; | |
| if (unlikely(!rec)) | |
| return; | |
| __eprobe_trace_func(edata, rec); | |
| } | |
| static int eprobe_trigger_cmd_parse(struct event_command *cmd_ops, | |
| struct trace_event_file *file, | |
| char *glob, char *cmd, | |
| char *param_and_filter) | |
| { | |
| return -1; | |
| } | |
| static int eprobe_trigger_reg_func(char *glob, | |
| struct event_trigger_data *data, | |
| struct trace_event_file *file) | |
| { | |
| return -1; | |
| } | |
| static void eprobe_trigger_unreg_func(char *glob, | |
| struct event_trigger_data *data, | |
| struct trace_event_file *file) | |
| { | |
| } | |
| static struct event_command event_trigger_cmd = { | |
| .name = "eprobe", | |
| .trigger_type = ETT_EVENT_EPROBE, | |
| .flags = EVENT_CMD_FL_NEEDS_REC, | |
| .parse = eprobe_trigger_cmd_parse, | |
| .reg = eprobe_trigger_reg_func, | |
| .unreg = eprobe_trigger_unreg_func, | |
| .unreg_all = NULL, | |
| .set_filter = NULL, | |
| .trigger = eprobe_trigger_func, | |
| .print = eprobe_trigger_print, | |
| .init = eprobe_trigger_init, | |
| .free = eprobe_trigger_free, | |
| }; | |
| static struct event_trigger_data * | |
| new_eprobe_trigger(struct trace_eprobe *ep, struct trace_event_file *file) | |
| { | |
| struct event_trigger_data *trigger; | |
| struct event_filter *filter = NULL; | |
| struct eprobe_data *edata; | |
| int ret; | |
| edata = kzalloc_obj(*edata); | |
| trigger = kzalloc_obj(*trigger); | |
| if (!trigger || !edata) { | |
| ret = -ENOMEM; | |
| goto error; | |
| } | |
| trigger->flags = EVENT_TRIGGER_FL_PROBE; | |
| trigger->count = -1; | |
| /* | |
| * EVENT PROBE triggers are not registered as commands with | |
| * register_event_command(), as they are not controlled by the user | |
| * from the trigger file | |
| */ | |
| trigger->cmd_ops = &event_trigger_cmd; | |
| INIT_LIST_HEAD(&trigger->list); | |
| if (ep->filter_str) { | |
| ret = create_event_filter(file->tr, ep->event, | |
| ep->filter_str, false, &filter); | |
| if (ret) | |
| goto error; | |
| } | |
| RCU_INIT_POINTER(trigger->filter, filter); | |
| edata->file = file; | |
| edata->ep = ep; | |
| trigger->private_data = edata; | |
| return trigger; | |
| error: | |
| free_event_filter(filter); | |
| kfree(edata); | |
| kfree(trigger); | |
| return ERR_PTR(ret); | |
| } | |
| static int enable_eprobe(struct trace_eprobe *ep, | |
| struct trace_event_file *eprobe_file) | |
| { | |
| struct event_trigger_data *trigger; | |
| struct trace_event_file *file; | |
| struct trace_array *tr = eprobe_file->tr; | |
| file = find_event_file(tr, ep->event_system, ep->event_name); | |
| if (!file) | |
| return -ENOENT; | |
| trigger = new_eprobe_trigger(ep, eprobe_file); | |
| if (IS_ERR(trigger)) | |
| return PTR_ERR(trigger); | |
| list_add_tail_rcu(&trigger->list, &file->triggers); | |
| trace_event_trigger_enable_disable(file, 1); | |
| update_cond_flag(file); | |
| return 0; | |
| } | |
| static struct trace_event_functions eprobe_funcs = { | |
| .trace = print_eprobe_event | |
| }; | |
| static int disable_eprobe(struct trace_eprobe *ep, | |
| struct trace_array *tr) | |
| { | |
| struct event_trigger_data *trigger = NULL, *iter; | |
| struct trace_event_file *file; | |
| struct event_filter *filter; | |
| struct eprobe_data *edata; | |
| file = find_event_file(tr, ep->event_system, ep->event_name); | |
| if (!file) | |
| return -ENOENT; | |
| list_for_each_entry(iter, &file->triggers, list) { | |
| if (!(iter->flags & EVENT_TRIGGER_FL_PROBE)) | |
| continue; | |
| edata = iter->private_data; | |
| if (edata->ep == ep) { | |
| trigger = iter; | |
| break; | |
| } | |
| } | |
| if (!trigger) | |
| return -ENODEV; | |
| list_del_rcu(&trigger->list); | |
| trace_event_trigger_enable_disable(file, 0); | |
| update_cond_flag(file); | |
| /* Make sure nothing is using the edata or trigger */ | |
| tracepoint_synchronize_unregister(); | |
| filter = rcu_access_pointer(trigger->filter); | |
| if (filter) | |
| free_event_filter(filter); | |
| kfree(edata); | |
| kfree(trigger); | |
| return 0; | |
| } | |
| static int enable_trace_eprobe(struct trace_event_call *call, | |
| struct trace_event_file *file) | |
| { | |
| struct trace_probe *tp; | |
| struct trace_eprobe *ep; | |
| bool enabled; | |
| int ret = 0; | |
| int cnt = 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(tp, file); | |
| if (ret) | |
| return ret; | |
| } else | |
| trace_probe_set_flag(tp, TP_FLAG_PROFILE); | |
| if (enabled) | |
| return 0; | |
| for_each_trace_eprobe_tp(ep, tp) { | |
| ret = enable_eprobe(ep, file); | |
| if (ret) | |
| break; | |
| enabled = true; | |
| cnt++; | |
| } | |
| if (ret) { | |
| /* Failed to enable one of them. Roll back all */ | |
| if (enabled) { | |
| /* | |
| * It's a bug if one failed for something other than memory | |
| * not being available but another eprobe succeeded. | |
| */ | |
| WARN_ON_ONCE(ret != -ENOMEM); | |
| for_each_trace_eprobe_tp(ep, tp) { | |
| disable_eprobe(ep, file->tr); | |
| if (!--cnt) | |
| break; | |
| } | |
| } | |
| if (file) | |
| trace_probe_remove_file(tp, file); | |
| else | |
| trace_probe_clear_flag(tp, TP_FLAG_PROFILE); | |
| } | |
| return ret; | |
| } | |
| static int disable_trace_eprobe(struct trace_event_call *call, | |
| struct trace_event_file *file) | |
| { | |
| struct trace_probe *tp; | |
| struct trace_eprobe *ep; | |
| tp = trace_probe_primary_from_call(call); | |
| if (WARN_ON_ONCE(!tp)) | |
| return -ENODEV; | |
| if (file) { | |
| if (!trace_probe_get_file_link(tp, file)) | |
| return -ENOENT; | |
| if (!trace_probe_has_single_file(tp)) | |
| goto out; | |
| trace_probe_clear_flag(tp, TP_FLAG_TRACE); | |
| } else | |
| trace_probe_clear_flag(tp, TP_FLAG_PROFILE); | |
| if (!trace_probe_is_enabled(tp)) { | |
| for_each_trace_eprobe_tp(ep, tp) | |
| disable_eprobe(ep, file->tr); | |
| } | |
| out: | |
| if (file) | |
| /* | |
| * Synchronization is done in below function. For perf event, | |
| * file == NULL and perf_trace_event_unreg() calls | |
| * tracepoint_synchronize_unregister() to ensure synchronize | |
| * event. We don't need to care about it. | |
| */ | |
| trace_probe_remove_file(tp, file); | |
| return 0; | |
| } | |
| static int eprobe_register(struct trace_event_call *event, | |
| enum trace_reg type, void *data) | |
| { | |
| struct trace_event_file *file = data; | |
| switch (type) { | |
| case TRACE_REG_REGISTER: | |
| return enable_trace_eprobe(event, file); | |
| case TRACE_REG_UNREGISTER: | |
| return disable_trace_eprobe(event, file); | |
| #ifdef CONFIG_PERF_EVENTS | |
| case TRACE_REG_PERF_REGISTER: | |
| case TRACE_REG_PERF_UNREGISTER: | |
| case TRACE_REG_PERF_OPEN: | |
| case TRACE_REG_PERF_CLOSE: | |
| case TRACE_REG_PERF_ADD: | |
| case TRACE_REG_PERF_DEL: | |
| return 0; | |
| #endif | |
| } | |
| return 0; | |
| } | |
| static inline void init_trace_eprobe_call(struct trace_eprobe *ep) | |
| { | |
| struct trace_event_call *call = trace_probe_event_call(&ep->tp); | |
| call->flags = TRACE_EVENT_FL_EPROBE; | |
| call->event.funcs = &eprobe_funcs; | |
| call->class->fields_array = eprobe_fields_array; | |
| call->class->reg = eprobe_register; | |
| } | |
| static struct trace_event_call * | |
| find_and_get_event(const char *system, const char *event_name) | |
| { | |
| struct trace_event_call *tp_event; | |
| const char *name; | |
| list_for_each_entry(tp_event, &ftrace_events, list) { | |
| /* Skip other probes and ftrace events */ | |
| if (tp_event->flags & | |
| (TRACE_EVENT_FL_IGNORE_ENABLE | | |
| TRACE_EVENT_FL_KPROBE | | |
| TRACE_EVENT_FL_UPROBE | | |
| TRACE_EVENT_FL_EPROBE)) | |
| continue; | |
| if (!tp_event->class->system || | |
| strcmp(system, tp_event->class->system)) | |
| continue; | |
| name = trace_event_name(tp_event); | |
| if (!name || strcmp(event_name, name)) | |
| continue; | |
| if (!trace_event_try_get_ref(tp_event)) | |
| return NULL; | |
| return tp_event; | |
| } | |
| return NULL; | |
| } | |
| static int trace_eprobe_parse_filter(struct trace_eprobe *ep, int argc, const char *argv[]) | |
| { | |
| struct event_filter *dummy = NULL; | |
| int i, ret, len = 0; | |
| char *p; | |
| if (argc == 0) { | |
| trace_probe_log_err(0, NO_EP_FILTER); | |
| return -EINVAL; | |
| } | |
| /* Recover the filter string */ | |
| for (i = 0; i < argc; i++) | |
| len += strlen(argv[i]) + 1; | |
| ep->filter_str = kzalloc(len, GFP_KERNEL); | |
| if (!ep->filter_str) | |
| return -ENOMEM; | |
| p = ep->filter_str; | |
| for (i = 0; i < argc; i++) { | |
| if (i) | |
| ret = snprintf(p, len, " %s", argv[i]); | |
| else | |
| ret = snprintf(p, len, "%s", argv[i]); | |
| p += ret; | |
| len -= ret; | |
| } | |
| /* | |
| * Ensure the filter string can be parsed correctly. Note, this | |
| * filter string is for the original event, not for the eprobe. | |
| */ | |
| ret = create_event_filter(top_trace_array(), ep->event, ep->filter_str, | |
| true, &dummy); | |
| free_event_filter(dummy); | |
| if (ret) { | |
| kfree(ep->filter_str); | |
| ep->filter_str = NULL; | |
| } | |
| return ret; | |
| } | |
| static int __trace_eprobe_create(int argc, const char *argv[]) | |
| { | |
| /* | |
| * Argument syntax: | |
| * e[:[GRP/][ENAME]] SYSTEM.EVENT [FETCHARGS] [if FILTER] | |
| * Fetch args (no space): | |
| * <name>=$<field>[:TYPE] | |
| */ | |
| struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) = NULL; | |
| struct trace_eprobe *ep __free(trace_event_probe_cleanup) = NULL; | |
| const char *trlog __free(trace_probe_log_clear) = NULL; | |
| const char *event = NULL, *group = EPROBE_EVENT_SYSTEM; | |
| const char *sys_event = NULL, *sys_name = NULL; | |
| struct trace_event_call *event_call; | |
| char *buf1 __free(kfree) = NULL; | |
| char *buf2 __free(kfree) = NULL; | |
| char *gbuf __free(kfree) = NULL; | |
| int ret = 0, filter_idx = 0; | |
| int i, filter_cnt; | |
| if (argc < 2 || argv[0][0] != 'e') | |
| return -ECANCELED; | |
| trlog = trace_probe_log_init("event_probe", argc, argv); | |
| event = strchr(&argv[0][1], ':'); | |
| if (event) { | |
| gbuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL); | |
| if (!gbuf) | |
| return -ENOMEM; | |
| event++; | |
| ret = traceprobe_parse_event_name(&event, &group, gbuf, | |
| event - argv[0]); | |
| if (ret) | |
| return -EINVAL; | |
| } | |
| trace_probe_log_set_index(1); | |
| sys_event = argv[1]; | |
| buf2 = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL); | |
| if (!buf2) | |
| return -ENOMEM; | |
| ret = traceprobe_parse_event_name(&sys_event, &sys_name, buf2, 0); | |
| if (ret || !sys_event || !sys_name) { | |
| trace_probe_log_err(0, NO_EVENT_INFO); | |
| return -EINVAL; | |
| } | |
| if (!event) { | |
| buf1 = kstrdup(sys_event, GFP_KERNEL); | |
| if (!buf1) | |
| return -ENOMEM; | |
| event = buf1; | |
| } | |
| for (i = 2; i < argc; i++) { | |
| if (!strcmp(argv[i], "if")) { | |
| filter_idx = i + 1; | |
| filter_cnt = argc - filter_idx; | |
| argc = i; | |
| break; | |
| } | |
| } | |
| if (argc - 2 > MAX_TRACE_ARGS) { | |
| trace_probe_log_set_index(2); | |
| trace_probe_log_err(0, TOO_MANY_ARGS); | |
| return -E2BIG; | |
| } | |
| scoped_guard(mutex, &event_mutex) { | |
| event_call = find_and_get_event(sys_name, sys_event); | |
| ep = alloc_event_probe(group, event, event_call, argc - 2); | |
| } | |
| if (IS_ERR(ep)) { | |
| ret = PTR_ERR(ep); | |
| if (ret == -ENODEV) | |
| trace_probe_log_err(0, BAD_ATTACH_EVENT); | |
| /* This must return -ENOMEM or missing event, else there is a bug */ | |
| WARN_ON_ONCE(ret != -ENOMEM && ret != -ENODEV); | |
| return ret; | |
| } | |
| if (filter_idx) { | |
| trace_probe_log_set_index(filter_idx); | |
| ret = trace_eprobe_parse_filter(ep, filter_cnt, argv + filter_idx); | |
| if (ret) | |
| return -EINVAL; | |
| } else | |
| ep->filter_str = NULL; | |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
| if (!ctx) | |
| return -ENOMEM; | |
| ctx->event = ep->event; | |
| ctx->flags = TPARG_FL_KERNEL | TPARG_FL_TEVENT; | |
| argc -= 2; argv += 2; | |
| /* parse arguments */ | |
| for (i = 0; i < argc; i++) { | |
| trace_probe_log_set_index(i + 2); | |
| ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], ctx); | |
| /* Handle symbols "@" */ | |
| if (!ret) | |
| ret = traceprobe_update_arg(&ep->tp.args[i]); | |
| if (ret) | |
| return ret; | |
| } | |
| ret = traceprobe_set_print_fmt(&ep->tp, PROBE_PRINT_EVENT); | |
| if (ret < 0) | |
| return ret; | |
| init_trace_eprobe_call(ep); | |
| scoped_guard(mutex, &event_mutex) { | |
| ret = trace_probe_register_event_call(&ep->tp); | |
| if (ret) { | |
| if (ret == -EEXIST) { | |
| trace_probe_log_set_index(0); | |
| trace_probe_log_err(0, EVENT_EXIST); | |
| } | |
| return ret; | |
| } | |
| ret = dyn_event_add(&ep->devent, &ep->tp.event->call); | |
| if (ret < 0) { | |
| trace_probe_unregister_event_call(&ep->tp); | |
| return ret; | |
| } | |
| /* To avoid freeing registered eprobe event, clear ep. */ | |
| ep = NULL; | |
| } | |
| return ret; | |
| } | |
| /* | |
| * Register dynevent at core_initcall. This allows kernel to setup eprobe | |
| * events in postcore_initcall without tracefs. | |
| */ | |
| static __init int trace_events_eprobe_init_early(void) | |
| { | |
| int err = 0; | |
| err = dyn_event_register(&eprobe_dyn_event_ops); | |
| if (err) | |
| pr_warn("Could not register eprobe_dyn_event_ops\n"); | |
| return err; | |
| } | |
| core_initcall(trace_events_eprobe_init_early); |