From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752302AbdK3Xx1 (ORCPT ); Thu, 30 Nov 2017 18:53:27 -0500 Received: from mx0a-00082601.pphosted.com ([67.231.145.42]:34876 "EHLO mx0a-00082601.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750940AbdK3Xu2 (ORCPT ); Thu, 30 Nov 2017 18:50:28 -0500 Smtp-Origin-Hostprefix: devbig From: Song Liu Smtp-Origin-Hostname: devbig102.frc2.facebook.com To: , , , , , , CC: , Song Liu Smtp-Origin-Cluster: frc2c02 Subject: [PATCH v3 4/6] perf: implement pmu perf_uprobe Date: Thu, 30 Nov 2017 15:50:21 -0800 Message-ID: <20171130235023.1414663-7-songliubraving@fb.com> X-Mailer: git-send-email 2.9.5 In-Reply-To: <20171130235023.1414663-1-songliubraving@fb.com> References: <20171130235023.1414663-1-songliubraving@fb.com> X-FB-Internal: Safe MIME-Version: 1.0 Content-Type: text/plain X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,, definitions=2017-11-30_07:,, signatures=0 X-Proofpoint-Spam-Reason: safe X-FB-Internal: Safe Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This patch adds perf_uprobe support 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 Reviewed-by: Yonghong Song Reviewed-by: Josef Bacik --- include/linux/trace_events.h | 2 + kernel/events/core.c | 36 ++++++++++++++++- kernel/trace/trace_event_perf.c | 58 +++++++++++++++++++++++++++ kernel/trace/trace_probe.h | 4 ++ kernel/trace/trace_uprobe.c | 86 +++++++++++++++++++++++++++++++++++++---- 5 files changed, 177 insertions(+), 9 deletions(-) diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 51f748c9..9272fa6 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -496,6 +496,8 @@ extern int perf_trace_add(struct perf_event *event, int flags); extern void perf_trace_del(struct perf_event *event, int flags); extern int perf_kprobe_init(struct perf_event *event); extern void perf_kprobe_destroy(struct perf_event *event); +extern int perf_uprobe_init(struct perf_event *event); +extern void perf_uprobe_destroy(struct perf_event *event); extern int ftrace_profile_set_filter(struct perf_event *event, int event_id, char *filter_str); extern void ftrace_profile_free_filter(struct perf_event *event); diff --git a/kernel/events/core.c b/kernel/events/core.c index 49bbf46..fcdadb3 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -8013,10 +8013,43 @@ static int perf_kprobe_event_init(struct perf_event *event) return 0; } +static int perf_uprobe_event_init(struct perf_event *event); +static struct pmu perf_uprobe = { + .task_ctx_nr = perf_sw_context, + .event_init = perf_uprobe_event_init, + .add = perf_trace_add, + .del = perf_trace_del, + .start = perf_swevent_start, + .stop = perf_swevent_stop, + .read = perf_swevent_read, +}; + +static int perf_uprobe_event_init(struct perf_event *event) +{ + int err; + + if (event->attr.type != perf_uprobe.type) + return -ENOENT; + /* + * no branch sampling for probe events + */ + if (has_branch_stack(event)) + return -EOPNOTSUPP; + + err = perf_uprobe_init(event); + if (err) + return err; + + event->destroy = perf_uprobe_destroy; + + return 0; +} + static inline void perf_tp_register(void) { perf_pmu_register(&perf_tracepoint, "tracepoint", PERF_TYPE_TRACEPOINT); perf_pmu_register(&perf_kprobe, "kprobe", -1); + perf_pmu_register(&perf_uprobe, "uprobe", -1); } static void perf_event_free_filter(struct perf_event *event) @@ -8100,7 +8133,8 @@ static void perf_event_free_bpf_handler(struct perf_event *event) static inline bool perf_event_is_tracing(struct perf_event *event) { return event->attr.type == PERF_TYPE_TRACEPOINT || - strncmp(event->pmu->name, "kprobe", 6) == 0; + strncmp(event->pmu->name, "kprobe", 6) == 0 || + strncmp(event->pmu->name, "uprobe", 6) == 0; } static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd) diff --git a/kernel/trace/trace_event_perf.c b/kernel/trace/trace_event_perf.c index 7cf0d99..1b97ea2 100644 --- a/kernel/trace/trace_event_perf.c +++ b/kernel/trace/trace_event_perf.c @@ -272,6 +272,52 @@ int perf_kprobe_init(struct perf_event *p_event) #endif /* CONFIG_KPROBE_EVENTS */ } +int perf_uprobe_init(struct perf_event *p_event) +{ + int ret; + char *path = NULL; + struct trace_event_call *tp_event; + +#ifdef CONFIG_UPROBE_EVENTS + if (!p_event->attr.uprobe_path) + return -EINVAL; + path = kzalloc(PATH_MAX, GFP_KERNEL); + if (!path) + return -ENOMEM; + ret = strncpy_from_user( + path, u64_to_user_ptr(p_event->attr.uprobe_path), PATH_MAX); + if (ret < 0) + goto out; + if (path[0] == '\0') { + ret = -EINVAL; + goto out; + } + + tp_event = create_local_trace_uprobe( + path, p_event->attr.probe_offset, p_event->attr.config != 0); + if (IS_ERR(tp_event)) { + ret = PTR_ERR(tp_event); + goto out; + } + + /* + * 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); +out: + kfree(path); + return ret; +#else + return -EOPNOTSUPP; +#endif /* CONFIG_UPROBE_EVENTS */ +} + void perf_trace_destroy(struct perf_event *p_event) { mutex_lock(&event_mutex); @@ -290,6 +336,18 @@ void perf_kprobe_destroy(struct perf_event *p_event) #endif } +void perf_uprobe_destroy(struct perf_event *p_event) +{ + mutex_lock(&event_mutex); + perf_trace_event_close(p_event); + perf_trace_event_unreg(p_event); + mutex_unlock(&event_mutex); + +#ifdef CONFIG_UPROBE_EVENTS + destroy_local_trace_uprobe(p_event->tp_event); +#endif +} + int perf_trace_add(struct perf_event *p_event, int flags) { struct trace_event_call *tp_event = p_event->tp_event; 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 @@ -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