From 149b9a9d86d7351ee9a44ce391884cc12e922124 Mon Sep 17 00:00:00 2001 From: Yannick Brosseau Date: Mon, 12 Sep 2016 15:00:12 -0400 Subject: [PATCH] Add uprobes support The added support is basic. It create an event with no data associated to the file path + offset specified. Signed-off-by: Yannick Brosseau Signed-off-by: Francis Deslauriers Signed-off-by: Mathieu Desnoyers --- Makefile | 3 +- lttng-abi.c | 1 - lttng-abi.h | 7 ++ lttng-events.c | 22 +++++ lttng-events.h | 34 ++++++++ probes/lttng-uprobes.c | 193 +++++++++++++++++++++++++++++++++++++++++ wrapper/uprobes.h | 87 +++++++++++++++++++ 7 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 probes/lttng-uprobes.c create mode 100644 wrapper/uprobes.h diff --git a/Makefile b/Makefile index ae2b1953..463b3141 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,8 @@ ifneq ($(KERNELRELEASE),) lttng-filter-specialize.o \ lttng-filter-validator.o \ probes/lttng-probe-user.o \ - lttng-tp-mempool.o + lttng-tp-mempool.o \ + probes/lttng-uprobes.o ifneq ($(CONFIG_HAVE_SYSCALL_TRACEPOINTS),) lttng-tracer-objs += lttng-syscalls.o diff --git a/lttng-abi.c b/lttng-abi.c index c535cb95..32fa0bfa 100644 --- a/lttng-abi.c +++ b/lttng-abi.c @@ -1306,7 +1306,6 @@ old_ctx_end: default: return -ENOIOCTLCMD; } - } /** diff --git a/lttng-abi.h b/lttng-abi.h index f0fe8efb..24877dc8 100644 --- a/lttng-abi.h +++ b/lttng-abi.h @@ -28,6 +28,7 @@ enum lttng_kernel_instrumentation { LTTNG_KERNEL_KRETPROBE = 3, LTTNG_KERNEL_NOOP = 4, /* not hooked */ LTTNG_KERNEL_SYSCALL = 5, + LTTNG_KERNEL_UPROBE = 6, }; /* @@ -73,6 +74,11 @@ struct lttng_kernel_function_tracer { char symbol_name[LTTNG_KERNEL_SYM_NAME_LEN]; } __attribute__((packed)); +struct lttng_kernel_uprobe { + char path[LTTNG_KERNEL_SYM_NAME_LEN]; + uint64_t offset; +} __attribute__((packed)); + /* * For syscall tracing, name = "*" means "enable all". */ @@ -88,6 +94,7 @@ struct lttng_kernel_event { struct lttng_kernel_kretprobe kretprobe; struct lttng_kernel_kprobe kprobe; struct lttng_kernel_function_tracer ftrace; + struct lttng_kernel_uprobe uprobe; char padding[LTTNG_KERNEL_EVENT_PADDING2]; } u; } __attribute__((packed)); diff --git a/lttng-events.c b/lttng-events.c index 48e17601..636a1891 100644 --- a/lttng-events.c +++ b/lttng-events.c @@ -395,6 +395,7 @@ int lttng_event_enable(struct lttng_event *event) break; case LTTNG_KERNEL_KPROBE: case LTTNG_KERNEL_FUNCTION: + case LTTNG_KERNEL_UPROBE: case LTTNG_KERNEL_NOOP: WRITE_ONCE(event->enabled, 1); break; @@ -430,6 +431,7 @@ int lttng_event_disable(struct lttng_event *event) break; case LTTNG_KERNEL_KPROBE: case LTTNG_KERNEL_FUNCTION: + case LTTNG_KERNEL_UPROBE: case LTTNG_KERNEL_NOOP: WRITE_ONCE(event->enabled, 0); break; @@ -576,6 +578,7 @@ struct lttng_event *_lttng_event_create(struct lttng_channel *chan, event_name = event_desc->name; break; case LTTNG_KERNEL_KPROBE: + case LTTNG_KERNEL_UPROBE: case LTTNG_KERNEL_KRETPROBE: case LTTNG_KERNEL_FUNCTION: case LTTNG_KERNEL_NOOP: @@ -739,6 +742,17 @@ struct lttng_event *_lttng_event_create(struct lttng_channel *chan, goto register_error; } break; + case LTTNG_KERNEL_UPROBE: + + ret = lttng_uprobes_register(event_param->name, + event_param->u.uprobe.path, + event_param->u.uprobe.offset, + event); + if (ret) + goto register_error; + ret = try_module_get(event->desc->owner); + WARN_ON_ONCE(!ret); + break; default: WARN_ON_ONCE(1); ret = -EINVAL; @@ -850,6 +864,10 @@ int _lttng_event_unregister(struct lttng_event *event) case LTTNG_KERNEL_NOOP: ret = 0; break; + case LTTNG_KERNEL_UPROBE: + lttng_uprobes_unregister(event); + ret = 0; + break; default: WARN_ON_ONCE(1); } @@ -883,6 +901,10 @@ void _lttng_event_destroy(struct lttng_event *event) case LTTNG_KERNEL_NOOP: case LTTNG_KERNEL_SYSCALL: break; + case LTTNG_KERNEL_UPROBE: + module_put(event->desc->owner); + lttng_uprobes_destroy_private(event); + break; default: WARN_ON_ONCE(1); } diff --git a/lttng-events.h b/lttng-events.h index 8b0803e4..4093bd8a 100644 --- a/lttng-events.h +++ b/lttng-events.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -309,6 +310,11 @@ struct lttng_event { struct { char *symbol_name; } ftrace; + struct { + struct uprobe_consumer up_consumer; + struct inode *inode; + loff_t offset; + } uprobe; } u; struct list_head list; /* Event list in session */ unsigned int metadata_dumped:1; @@ -771,6 +777,34 @@ void lttng_kprobes_destroy_private(struct lttng_event *event) } #endif +#ifdef CONFIG_UPROBES +int lttng_uprobes_register(const char *name, + const char *path, + uint64_t offset, + struct lttng_event *event); +void lttng_uprobes_unregister(struct lttng_event *event); +void lttng_uprobes_destroy_private(struct lttng_event *event); +#else +static inline +int lttng_uprobes_register(const char *name, + const char *path, + uint64_t offset, + struct lttng_event *event) +{ + return -ENOSYS; +} + +static inline +void lttng_uprobes_unregister(struct lttng_event *event) +{ +} + +static inline +void lttng_uprobes_destroy_private(struct lttng_event *event) +{ +} +#endif + #ifdef CONFIG_KRETPROBES int lttng_kretprobes_register(const char *name, const char *symbol_name, diff --git a/probes/lttng-uprobes.c b/probes/lttng-uprobes.c new file mode 100644 index 00000000..09069920 --- /dev/null +++ b/probes/lttng-uprobes.c @@ -0,0 +1,193 @@ +/* + * probes/lttng-uprobes.c + * + * LTTng uprobes integration module. + * + * Copyright (C) 2013 Yannick Brosseau + * Copyright (C) 2009-2012 Mathieu Desnoyers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; only + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static +int lttng_uprobes_handler_pre(struct uprobe_consumer *uc, struct pt_regs *regs) +{ + struct lttng_event *event = + container_of(uc, struct lttng_event, u.uprobe.up_consumer); + struct lttng_probe_ctx lttng_probe_ctx = { + .event = event, + .interruptible = !lttng_regs_irqs_disabled(regs), + }; + struct lttng_channel *chan = event->chan; + struct lib_ring_buffer_ctx ctx; + int ret; + + struct { + unsigned long ip; + } payload; + + if (unlikely(!ACCESS_ONCE(chan->session->active))) + return 0; + if (unlikely(!ACCESS_ONCE(chan->enabled))) + return 0; + if (unlikely(!ACCESS_ONCE(event->enabled))) + return 0; + + lib_ring_buffer_ctx_init(&ctx, chan->chan, <tng_probe_ctx, + sizeof(payload), lttng_alignof(payload), -1); + + ret = chan->ops->event_reserve(&ctx, event->id); + if (ret < 0) + return 0; + + /* Event payload. */ + payload.ip = regs->ip; + lib_ring_buffer_align_ctx(&ctx, lttng_alignof(payload)); + chan->ops->event_write(&ctx, &payload, sizeof(payload)); + chan->ops->event_commit(&ctx); + return 0; +} + +/* + * Create event description. + */ +static +int lttng_create_uprobe_event(const char *name, struct lttng_event *event) +{ + struct lttng_event_desc *desc; + struct lttng_event_field *fields; + int ret; + + desc = kzalloc(sizeof(*event->desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + desc->name = kstrdup(name, GFP_KERNEL); + if (!desc->name) { + ret = -ENOMEM; + goto error_str; + } + + desc->nr_fields = 1; + desc->fields = fields = + kzalloc(1 * sizeof(struct lttng_event_field), GFP_KERNEL); + + if (!desc->fields) { + ret = -ENOMEM; + goto error_fields; + } + fields[0].name = "ip"; + fields[0].type.atype = atype_integer; + fields[0].type.u.basic.integer.size = sizeof(unsigned long) * CHAR_BIT; + fields[0].type.u.basic.integer.alignment = lttng_alignof(unsigned long) * CHAR_BIT; + fields[0].type.u.basic.integer.signedness = lttng_is_signed_type(unsigned long); + fields[0].type.u.basic.integer.reverse_byte_order = 0; + fields[0].type.u.basic.integer.base = 16; + fields[0].type.u.basic.integer.encoding = lttng_encode_none; + + desc->owner = THIS_MODULE; + event->desc = desc; + + return 0; + +error_fields: + kfree(desc->name); +error_str: + kfree(desc); + return ret; +} + +int lttng_uprobes_register(const char *name, + const char *path_name, + uint64_t offset, + struct lttng_event *event) +{ + int ret; + + /* Shoudl we fail if the path is empty, it should be checked before */ + if (path_name[0] == '\0') + path_name = NULL; + + ret = lttng_create_uprobe_event(name, event); + if (ret) + goto error; + + memset(&event->u.uprobe.up_consumer, 0, + sizeof(event->u.uprobe.up_consumer)); + + event->u.uprobe.up_consumer.handler = lttng_uprobes_handler_pre; + if (path_name) { + struct path path; + ret = kern_path(path_name, LOOKUP_FOLLOW, &path); + if (ret) + goto path_error; + + event->u.uprobe.inode = igrab(path.dentry->d_inode); + } + event->u.uprobe.offset = offset; + + /* Ensure the memory we just allocated don't trigger page faults. */ + wrapper_vmalloc_sync_all(); + printk(KERN_WARNING "Registering probe on inode %lu and offset %llu\n", event->u.uprobe.inode->i_ino, event->u.uprobe.offset); + ret = wrapper_uprobe_register(event->u.uprobe.inode, + event->u.uprobe.offset, + &event->u.uprobe.up_consumer); + if (ret) { + printk(KERN_WARNING "Error registering probe on inode %lu " + "and offset %llu\n", event->u.uprobe.inode->i_ino, + event->u.uprobe.offset); + goto register_error; + } + return 0; + +register_error: + iput(event->u.uprobe.inode); +path_error: + kfree(event->desc->name); + kfree(event->desc); +error: + return ret; +} +EXPORT_SYMBOL_GPL(lttng_uprobes_register); + +void lttng_uprobes_unregister(struct lttng_event *event) +{ + wrapper_uprobe_unregister(event->u.uprobe.inode, + event->u.uprobe.offset, + &event->u.uprobe.up_consumer); +} +EXPORT_SYMBOL_GPL(lttng_uprobes_unregister); + +void lttng_uprobes_destroy_private(struct lttng_event *event) +{ + iput(event->u.uprobe.inode); + kfree(event->desc->name); + kfree(event->desc); +} +EXPORT_SYMBOL_GPL(lttng_uprobes_destroy_private); + +MODULE_LICENSE("GPL and additional rights"); +MODULE_AUTHOR("Yannick Brosseau"); +MODULE_DESCRIPTION("Linux Trace Toolkit Uprobes Support"); diff --git a/wrapper/uprobes.h b/wrapper/uprobes.h new file mode 100644 index 00000000..e38a5415 --- /dev/null +++ b/wrapper/uprobes.h @@ -0,0 +1,87 @@ +#ifndef _LTTNG_WRAPPER_UPROBES_H +#define _LTTNG_WRAPPER_UPROBES_H + +/* + * wrapper/uprobes.h + * + * wrapper around uprobes. Using KALLSYMS to get its address when + * available, else we need to have a kernel that exports this function to GPL + * modules. + * + * Copyright (C) 2013 Yannick Brosseau + * Copyright (C) 2017 Francis Deslauriers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; only + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) +#include + +/* Use kallsym lookup for version before 3.9. */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0)) + +static inline +int wrapper_uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) +{ + return uprobe_register(inode, offset, uc); +} + +static inline +void wrapper_uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) +{ + uprobe_unregister(inode, offset, uc); +} + +#else /* Version < 3.9, use kallsym lookup. */ +#include "kallsyms.h" + +static inline +int wrapper_uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) +{ + int (*uprobe_register_sym)(struct inode *inode, loff_t offset, struct uprobe_consumer *uc); + + uprobe_register_sym = (void *) kallsyms_lookup_funcptr("uprobe_register"); + + if (uprobe_register_sym) { + return uprobe_register_sym(inode, offset, uc); + } else { + printk(KERN_WARNING "LTTng: uprobe_register symbol lookup failed.\n"); + return -EINVAL; + } +} + +static inline +void wrapper_uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) +{ + int (*uprobe_unregister_sym)(struct inode *inode, loff_t offset, struct uprobe_consumer *uc); + + uprobe_unregister_sym = (void *) kallsyms_lookup_funcptr("uprobe_unregister"); + + if (uprobe_unregister_sym) { + uprobe_unregister_sym(inode, offset, uc); + } else { + printk(KERN_WARNING "LTTng: uprobe_unregister symbol lookup failed.\n"); + WARN_ON(1); + } +} +#endif +#else +/* Version < 3.5, before uprobe was added. */ +struct uprobe_consumer {}; + +#endif +#endif -- 2.34.1