forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
959 lines
22 KiB
959 lines
22 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* event probes |
|
* |
|
* Part of this code was copied from kernel/trace/trace_kprobe.c written by |
|
* Masami Hiramatsu <[email protected]> |
|
* |
|
* Copyright (C) 2021, VMware Inc, Steven Rostedt <[email protected]> |
|
* Copyright (C) 2021, VMware Inc, Tzvetomir Stoyanov [email protected]> |
|
* |
|
*/ |
|
#include <linux/module.h> |
|
#include <linux/mutex.h> |
|
#include <linux/ftrace.h> |
|
|
|
#include "trace_dynevent.h" |
|
#include "trace_probe.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; |
|
|
|
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; |
|
}; |
|
|
|
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); |
|
} |
|
|
|
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 |
|
* |
|
* 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 (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])) |
|
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; |
|
const char *event_name; |
|
const char *sys_name; |
|
int ret = -ENOMEM; |
|
|
|
if (!event) |
|
return ERR_PTR(-ENODEV); |
|
|
|
sys_name = event->class->system; |
|
event_name = trace_event_name(event); |
|
|
|
ep = kzalloc(struct_size(ep, tp.args, nargs), GFP_KERNEL); |
|
if (!ep) { |
|
trace_event_put_ref(event); |
|
goto error; |
|
} |
|
ep->event = event; |
|
ep->event_name = kstrdup(event_name, GFP_KERNEL); |
|
if (!ep->event_name) |
|
goto error; |
|
ep->event_system = kstrdup(sys_name, GFP_KERNEL); |
|
if (!ep->event_system) |
|
goto error; |
|
|
|
ret = trace_probe_init(&ep->tp, this_event, group, false); |
|
if (ret < 0) |
|
goto error; |
|
|
|
dyn_event_init(&ep->devent, &eprobe_dyn_event_ops); |
|
return ep; |
|
error: |
|
trace_event_probe_cleanup(ep); |
|
return ERR_PTR(ret); |
|
} |
|
|
|
static int trace_eprobe_tp_arg_update(struct trace_eprobe *ep, int i) |
|
{ |
|
struct probe_arg *parg = &ep->tp.args[i]; |
|
struct ftrace_event_field *field; |
|
struct list_head *head; |
|
|
|
head = trace_get_fields(ep->event); |
|
list_for_each_entry(field, head, link) { |
|
if (!strcmp(parg->code->data, field->name)) { |
|
kfree(parg->code->data); |
|
parg->code->data = field; |
|
return 0; |
|
} |
|
} |
|
kfree(parg->code->data); |
|
parg->code->data = NULL; |
|
return -ENOENT; |
|
} |
|
|
|
static int eprobe_event_define_fields(struct trace_event_call *event_call) |
|
{ |
|
int ret; |
|
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; |
|
|
|
DEFINE_FIELD(unsigned int, type, FIELD_STRING_TYPE, 0); |
|
|
|
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_probe *tp; |
|
|
|
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; |
|
|
|
trace_seq_printf(s, "%s: (", trace_probe_name(tp)); |
|
|
|
probed_event = ftrace_find_event(field->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", field->type); |
|
} |
|
|
|
trace_seq_putc(s, ')'); |
|
|
|
if (print_probe_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 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; |
|
|
|
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->is_signed) |
|
val = *(long *)addr; |
|
else |
|
val = *(unsigned long *)addr; |
|
break; |
|
} |
|
return val; |
|
} |
|
|
|
static int get_eprobe_size(struct trace_probe *tp, void *rec) |
|
{ |
|
struct probe_arg *arg; |
|
int i, len, ret = 0; |
|
|
|
for (i = 0; i < tp->nr_args; i++) { |
|
arg = tp->args + i; |
|
if (unlikely(arg->dynamic)) { |
|
unsigned long val; |
|
|
|
val = get_event_field(arg->code, rec); |
|
len = process_fetch_insn_bottom(arg->code + 1, 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 *dest, |
|
void *base) |
|
{ |
|
unsigned long val; |
|
|
|
val = get_event_field(code, rec); |
|
return process_fetch_insn_bottom(code + 1, val, dest, base); |
|
} |
|
NOKPROBE_SYMBOL(process_fetch_insn) |
|
|
|
/* Return the length of string -- including null terminal byte */ |
|
static nokprobe_inline int |
|
fetch_store_strlen_user(unsigned long addr) |
|
{ |
|
const void __user *uaddr = (__force const void __user *)addr; |
|
|
|
return strnlen_user_nofault(uaddr, MAX_STRING_SIZE); |
|
} |
|
|
|
/* Return the length of string -- including null terminal byte */ |
|
static nokprobe_inline int |
|
fetch_store_strlen(unsigned long addr) |
|
{ |
|
int ret, len = 0; |
|
u8 c; |
|
|
|
#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE |
|
if (addr < TASK_SIZE) |
|
return fetch_store_strlen_user(addr); |
|
#endif |
|
|
|
do { |
|
ret = copy_from_kernel_nofault(&c, (u8 *)addr + len, 1); |
|
len++; |
|
} while (c && ret == 0 && len < MAX_STRING_SIZE); |
|
|
|
return (ret < 0) ? ret : len; |
|
} |
|
|
|
/* |
|
* Fetch a null-terminated string from user. Caller MUST set *(u32 *)buf |
|
* with max length and relative data location. |
|
*/ |
|
static nokprobe_inline int |
|
fetch_store_string_user(unsigned long addr, void *dest, void *base) |
|
{ |
|
const void __user *uaddr = (__force const void __user *)addr; |
|
int maxlen = get_loc_len(*(u32 *)dest); |
|
void *__dest; |
|
long ret; |
|
|
|
if (unlikely(!maxlen)) |
|
return -ENOMEM; |
|
|
|
__dest = get_loc_data(dest, base); |
|
|
|
ret = strncpy_from_user_nofault(__dest, uaddr, maxlen); |
|
if (ret >= 0) |
|
*(u32 *)dest = make_data_loc(ret, __dest - base); |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Fetch a null-terminated string. Caller MUST set *(u32 *)buf with max |
|
* length and relative data location. |
|
*/ |
|
static nokprobe_inline int |
|
fetch_store_string(unsigned long addr, void *dest, void *base) |
|
{ |
|
int maxlen = get_loc_len(*(u32 *)dest); |
|
void *__dest; |
|
long ret; |
|
|
|
#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE |
|
if ((unsigned long)addr < TASK_SIZE) |
|
return fetch_store_string_user(addr, dest, base); |
|
#endif |
|
|
|
if (unlikely(!maxlen)) |
|
return -ENOMEM; |
|
|
|
__dest = get_loc_data(dest, base); |
|
|
|
/* |
|
* Try to get string again, since the string can be changed while |
|
* probing. |
|
*/ |
|
ret = strncpy_from_kernel_nofault(__dest, (void *)addr, maxlen); |
|
if (ret >= 0) |
|
*(u32 *)dest = make_data_loc(ret, __dest - base); |
|
|
|
return ret; |
|
} |
|
|
|
static nokprobe_inline int |
|
probe_mem_read_user(void *dest, void *src, size_t size) |
|
{ |
|
const void __user *uaddr = (__force const void __user *)src; |
|
|
|
return copy_from_user_nofault(dest, uaddr, size); |
|
} |
|
|
|
static nokprobe_inline int |
|
probe_mem_read(void *dest, void *src, size_t size) |
|
{ |
|
#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE |
|
if ((unsigned long)src < TASK_SIZE) |
|
return probe_mem_read_user(dest, src, size); |
|
#endif |
|
return copy_from_kernel_nofault(dest, src, size); |
|
} |
|
|
|
/* 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; |
|
|
|
fbuffer.trace_ctx = tracing_gen_ctx(); |
|
fbuffer.trace_file = edata->file; |
|
|
|
dsize = get_eprobe_size(&edata->ep->tp, rec); |
|
fbuffer.regs = NULL; |
|
|
|
fbuffer.event = |
|
trace_event_buffer_lock_reserve(&fbuffer.buffer, edata->file, |
|
call->event.type, |
|
sizeof(*entry) + edata->ep->tp.size + dsize, |
|
fbuffer.trace_ctx); |
|
if (!fbuffer.event) |
|
return; |
|
|
|
entry = fbuffer.entry = ring_buffer_event_data(fbuffer.event); |
|
if (edata->ep->event) |
|
entry->type = edata->ep->event->event.type; |
|
else |
|
entry->type = 0; |
|
store_trace_args(&entry[1], &edata->ep->tp, rec, 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_ops *ops, |
|
struct event_trigger_data *data) |
|
{ |
|
return 0; |
|
} |
|
|
|
static void eprobe_trigger_free(struct event_trigger_ops *ops, |
|
struct event_trigger_data *data) |
|
{ |
|
|
|
} |
|
|
|
static int eprobe_trigger_print(struct seq_file *m, |
|
struct event_trigger_ops *ops, |
|
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; |
|
|
|
__eprobe_trace_func(edata, rec); |
|
} |
|
|
|
static struct event_trigger_ops eprobe_trigger_ops = { |
|
.func = eprobe_trigger_func, |
|
.print = eprobe_trigger_print, |
|
.init = eprobe_trigger_init, |
|
.free = eprobe_trigger_free, |
|
}; |
|
|
|
static int eprobe_trigger_cmd_func(struct event_command *cmd_ops, |
|
struct trace_event_file *file, |
|
char *glob, char *cmd, char *param) |
|
{ |
|
return -1; |
|
} |
|
|
|
static int eprobe_trigger_reg_func(char *glob, struct event_trigger_ops *ops, |
|
struct event_trigger_data *data, |
|
struct trace_event_file *file) |
|
{ |
|
return -1; |
|
} |
|
|
|
static void eprobe_trigger_unreg_func(char *glob, struct event_trigger_ops *ops, |
|
struct event_trigger_data *data, |
|
struct trace_event_file *file) |
|
{ |
|
|
|
} |
|
|
|
static struct event_trigger_ops *eprobe_trigger_get_ops(char *cmd, |
|
char *param) |
|
{ |
|
return &eprobe_trigger_ops; |
|
} |
|
|
|
static struct event_command event_trigger_cmd = { |
|
.name = "eprobe", |
|
.trigger_type = ETT_EVENT_EPROBE, |
|
.flags = EVENT_CMD_FL_NEEDS_REC, |
|
.func = eprobe_trigger_cmd_func, |
|
.reg = eprobe_trigger_reg_func, |
|
.unreg = eprobe_trigger_unreg_func, |
|
.unreg_all = NULL, |
|
.get_trigger_ops = eprobe_trigger_get_ops, |
|
.set_filter = NULL, |
|
}; |
|
|
|
static struct event_trigger_data * |
|
new_eprobe_trigger(struct trace_eprobe *ep, struct trace_event_file *file) |
|
{ |
|
struct event_trigger_data *trigger; |
|
struct eprobe_data *edata; |
|
|
|
edata = kzalloc(sizeof(*edata), GFP_KERNEL); |
|
trigger = kzalloc(sizeof(*trigger), GFP_KERNEL); |
|
if (!trigger || !edata) { |
|
kfree(edata); |
|
kfree(trigger); |
|
return ERR_PTR(-ENOMEM); |
|
} |
|
|
|
trigger->flags = EVENT_TRIGGER_FL_PROBE; |
|
trigger->count = -1; |
|
trigger->ops = &eprobe_trigger_ops; |
|
|
|
/* |
|
* 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); |
|
RCU_INIT_POINTER(trigger->filter, NULL); |
|
|
|
edata->file = file; |
|
edata->ep = ep; |
|
trigger->private_data = edata; |
|
|
|
return trigger; |
|
} |
|
|
|
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; |
|
struct trace_event_file *file; |
|
struct eprobe_data *edata; |
|
|
|
file = find_event_file(tr, ep->event_system, ep->event_name); |
|
if (!file) |
|
return -ENOENT; |
|
|
|
list_for_each_entry(trigger, &file->triggers, list) { |
|
if (!(trigger->flags & EVENT_TRIGGER_FL_PROBE)) |
|
continue; |
|
edata = trigger->private_data; |
|
if (edata->ep == ep) |
|
break; |
|
} |
|
if (list_entry_is_head(trigger, &file->triggers, list)) |
|
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(); |
|
|
|
kfree(edata); |
|
kfree(trigger); |
|
|
|
return 0; |
|
} |
|
|
|
static int enable_trace_eprobe(struct trace_event_call *call, |
|
struct trace_event_file *file) |
|
{ |
|
struct trace_probe *pos, *tp; |
|
struct trace_eprobe *ep; |
|
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(tp, file); |
|
if (ret) |
|
return ret; |
|
} else |
|
trace_probe_set_flag(tp, TP_FLAG_PROFILE); |
|
|
|
if (enabled) |
|
return 0; |
|
|
|
list_for_each_entry(pos, trace_probe_probe_list(tp), list) { |
|
ep = container_of(pos, struct trace_eprobe, tp); |
|
ret = enable_eprobe(ep, file); |
|
if (ret) |
|
break; |
|
enabled = true; |
|
} |
|
|
|
if (ret) { |
|
/* Failed to enable one of them. Roll back all */ |
|
if (enabled) |
|
disable_eprobe(ep, file->tr); |
|
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 *pos, *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)) { |
|
list_for_each_entry(pos, trace_probe_probe_list(tp), list) { |
|
ep = container_of(pos, struct trace_eprobe, 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; |
|
break; |
|
} |
|
return tp_event; |
|
break; |
|
} |
|
return NULL; |
|
} |
|
|
|
static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[], int i) |
|
{ |
|
unsigned int flags = TPARG_FL_KERNEL | TPARG_FL_TPOINT; |
|
int ret; |
|
|
|
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], flags); |
|
if (ret) |
|
return ret; |
|
|
|
if (ep->tp.args[i].code->op == FETCH_OP_TP_ARG) |
|
ret = trace_eprobe_tp_arg_update(ep, i); |
|
|
|
return ret; |
|
} |
|
|
|
static int __trace_eprobe_create(int argc, const char *argv[]) |
|
{ |
|
/* |
|
* Argument syntax: |
|
* e[:[GRP/]ENAME] SYSTEM.EVENT [FETCHARGS] |
|
* Fetch args: |
|
* <name>=$<field>[:TYPE] |
|
*/ |
|
const char *event = NULL, *group = EPROBE_EVENT_SYSTEM; |
|
const char *sys_event = NULL, *sys_name = NULL; |
|
struct trace_event_call *event_call; |
|
struct trace_eprobe *ep = NULL; |
|
char buf1[MAX_EVENT_NAME_LEN]; |
|
char buf2[MAX_EVENT_NAME_LEN]; |
|
int ret = 0; |
|
int i; |
|
|
|
if (argc < 2 || argv[0][0] != 'e') |
|
return -ECANCELED; |
|
|
|
trace_probe_log_init("event_probe", argc, argv); |
|
|
|
event = strchr(&argv[0][1], ':'); |
|
if (event) { |
|
event++; |
|
ret = traceprobe_parse_event_name(&event, &group, buf1, |
|
event - argv[0]); |
|
if (ret) |
|
goto parse_error; |
|
} else { |
|
strscpy(buf1, argv[1], MAX_EVENT_NAME_LEN); |
|
sanitize_event_name(buf1); |
|
event = buf1; |
|
} |
|
if (!is_good_name(event) || !is_good_name(group)) |
|
goto parse_error; |
|
|
|
sys_event = argv[1]; |
|
ret = traceprobe_parse_event_name(&sys_event, &sys_name, buf2, |
|
sys_event - argv[1]); |
|
if (ret || !sys_name) |
|
goto parse_error; |
|
if (!is_good_name(sys_event) || !is_good_name(sys_name)) |
|
goto parse_error; |
|
|
|
mutex_lock(&event_mutex); |
|
event_call = find_and_get_event(sys_name, sys_event); |
|
ep = alloc_event_probe(group, event, event_call, argc - 2); |
|
mutex_unlock(&event_mutex); |
|
|
|
if (IS_ERR(ep)) { |
|
ret = PTR_ERR(ep); |
|
/* This must return -ENOMEM, else there is a bug */ |
|
WARN_ON_ONCE(ret != -ENOMEM); |
|
ep = NULL; |
|
goto error; |
|
} |
|
|
|
argc -= 2; argv += 2; |
|
/* parse arguments */ |
|
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) { |
|
trace_probe_log_set_index(i + 2); |
|
ret = trace_eprobe_tp_update_arg(ep, argv, i); |
|
if (ret) |
|
goto error; |
|
} |
|
ret = traceprobe_set_print_fmt(&ep->tp, PROBE_PRINT_EVENT); |
|
if (ret < 0) |
|
goto error; |
|
init_trace_eprobe_call(ep); |
|
mutex_lock(&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); |
|
} |
|
mutex_unlock(&event_mutex); |
|
goto error; |
|
} |
|
ret = dyn_event_add(&ep->devent, &ep->tp.event->call); |
|
mutex_unlock(&event_mutex); |
|
return ret; |
|
parse_error: |
|
ret = -EINVAL; |
|
error: |
|
trace_event_probe_cleanup(ep); |
|
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);
|
|
|