This patch adds uprobe support to perf_probe with similar pattern as previous patch (for kprobe).
Two functions, create_local_trace_uprobe() and destroy_local_trace_uprobe(), are created so a uprobe can be created and attached to the file descriptor created by perf_event_open(). Signed-off-by: Song Liu <songliubrav...@fb.com> Reviewed-by: Yonghong Song <y...@fb.com> Reviewed-by: Josef Bacik <jba...@fb.com> --- kernel/trace/trace_event_perf.c | 48 +++++++++++++++++++++- kernel/trace/trace_probe.h | 4 ++ kernel/trace/trace_uprobe.c | 90 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/kernel/trace/trace_event_perf.c b/kernel/trace/trace_event_perf.c index bf9b99b..4e4de84 100644 --- a/kernel/trace/trace_event_perf.c +++ b/kernel/trace/trace_event_perf.c @@ -256,6 +256,39 @@ static int perf_probe_create_kprobe(struct perf_event *p_event, } #endif /* CONFIG_KPROBE_EVENTS */ +#ifdef CONFIG_UPROBE_EVENTS +static int perf_probe_create_uprobe(struct perf_event *p_event, + struct probe_desc *pd, char *name) +{ + struct trace_event_call *tp_event; + int ret; + + if (!name) + return -EINVAL; + tp_event = create_local_trace_uprobe( + name, pd->offset, p_event->attr.is_return); + if (IS_ERR(tp_event)) + return PTR_ERR(tp_event); + /* + * local trace_uprobe need to hold event_mutex to call + * uprobe_buffer_enable() and uprobe_buffer_disable(). + * event_mutex is not required for local trace_kprobes. + */ + mutex_lock(&event_mutex); + ret = perf_trace_event_init(tp_event, p_event); + if (ret) + destroy_local_trace_uprobe(tp_event); + mutex_unlock(&event_mutex); + return ret; +} +#else +static int perf_probe_create_uprobe(struct perf_event *p_event, + struct probe_desc *pd, char *name) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_KPROBE_EVENTS */ + int perf_probe_init(struct perf_event *p_event) { struct probe_desc pd; @@ -292,7 +325,7 @@ int perf_probe_init(struct perf_event *p_event) if (!p_event->attr.is_uprobe) ret = perf_probe_create_kprobe(p_event, &pd, name); else - ret = -EOPNOTSUPP; + ret = perf_probe_create_uprobe(p_event, &pd, name); out: kfree(name); return ret; @@ -308,13 +341,26 @@ void perf_trace_destroy(struct perf_event *p_event) void perf_probe_destroy(struct perf_event *p_event) { + /* + * local trace_uprobe need to hold event_mutex to call + * uprobe_buffer_enable() and uprobe_buffer_disable(). + * event_mutex is not required for local trace_kprobes. + */ + if (p_event->attr.is_uprobe) + mutex_lock(&event_mutex); perf_trace_event_close(p_event); perf_trace_event_unreg(p_event); + if (p_event->attr.is_uprobe) + mutex_unlock(&event_mutex); if (!p_event->attr.is_uprobe) { #ifdef CONFIG_KPROBE_EVENTS destroy_local_trace_kprobe(p_event->tp_event); #endif + } else { +#ifdef CONFIG_UPROBE_EVENTS + destroy_local_trace_uprobe(p_event->tp_event); +#endif } } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 910ae1b..86b5925 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -417,4 +417,8 @@ extern struct trace_event_call * create_local_trace_kprobe(char *func, void *addr, unsigned long offs, bool is_return); extern void destroy_local_trace_kprobe(struct trace_event_call *event_call); + +extern struct trace_event_call * +create_local_trace_uprobe(char *name, unsigned long offs, bool is_return); +extern void destroy_local_trace_uprobe(struct trace_event_call *event_call); #endif diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index 4525e02..4d805d2 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -31,8 +31,8 @@ #define UPROBE_EVENT_SYSTEM "uprobes" struct uprobe_trace_entry_head { - struct trace_entry ent; - unsigned long vaddr[]; + struct trace_entry ent; + unsigned long vaddr[]; }; #define SIZEOF_TRACE_ENTRY(is_return) \ @@ -1293,16 +1293,25 @@ static struct trace_event_functions uprobe_funcs = { .trace = print_uprobe_event }; -static int register_uprobe_event(struct trace_uprobe *tu) +static inline void init_trace_event_call(struct trace_uprobe *tu, + struct trace_event_call *call) { - struct trace_event_call *call = &tu->tp.call; - int ret; - - /* Initialize trace_event_call */ INIT_LIST_HEAD(&call->class->fields); call->event.funcs = &uprobe_funcs; call->class->define_fields = uprobe_event_define_fields; + call->flags = TRACE_EVENT_FL_UPROBE; + call->class->reg = trace_uprobe_register; + call->data = tu; +} + +static int register_uprobe_event(struct trace_uprobe *tu) +{ + struct trace_event_call *call = &tu->tp.call; + int ret = 0; + + init_trace_event_call(tu, call); + if (set_print_fmt(&tu->tp, is_ret_probe(tu)) < 0) return -ENOMEM; @@ -1312,9 +1321,6 @@ static int register_uprobe_event(struct trace_uprobe *tu) return -ENODEV; } - call->flags = TRACE_EVENT_FL_UPROBE; - call->class->reg = trace_uprobe_register; - call->data = tu; ret = trace_add_event_call(call); if (ret) { @@ -1340,6 +1346,70 @@ static int unregister_uprobe_event(struct trace_uprobe *tu) return 0; } +#ifdef CONFIG_PERF_EVENTS +struct trace_event_call * +create_local_trace_uprobe(char *name, unsigned long offs, bool is_return) +{ + struct trace_uprobe *tu; + struct inode *inode; + struct path path; + int ret; + + ret = kern_path(name, LOOKUP_FOLLOW, &path); + if (ret) + return ERR_PTR(ret); + + inode = igrab(d_inode(path.dentry)); + path_put(&path); + + if (!inode || !S_ISREG(inode->i_mode)) { + iput(inode); + return ERR_PTR(-EINVAL); + } + + /* + * local trace_kprobes are not added to probe_list, so they are never + * searched in find_trace_kprobe(). Therefore, there is no concern of + * duplicated name "DUMMY_EVENT" here. + */ + tu = alloc_trace_uprobe(UPROBE_EVENT_SYSTEM, "DUMMY_EVENT", 0, + is_return); + + if (IS_ERR(tu)) { + pr_info("Failed to allocate trace_uprobe.(%d)\n", + (int)PTR_ERR(tu)); + return ERR_CAST(tu); + } + + tu->offset = offs; + tu->inode = inode; + tu->filename = kstrdup(name, GFP_KERNEL); + init_trace_event_call(tu, &tu->tp.call); + + if (set_print_fmt(&tu->tp, is_ret_probe(tu)) < 0) { + ret = -ENOMEM; + goto error; + } + + return &tu->tp.call; +error: + free_trace_uprobe(tu); + return ERR_PTR(ret); +} + +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); + + kfree(tu->tp.call.print_fmt); + tu->tp.call.print_fmt = NULL; + + free_trace_uprobe(tu); +} +#endif /* CONFIG_PERF_EVENTS */ + /* Make a trace interface for controling probe points */ static __init int init_uprobe_trace(void) { -- 2.9.5