From 7371f44c2c8ea6e60c0457671af008dd018c0b5f Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Thu, 28 Jul 2011 18:41:03 -0400 Subject: [PATCH] Add kretprobe support Signed-off-by: Mathieu Desnoyers --- ltt-debugfs-abi.c | 3 + ltt-debugfs-abi.h | 9 ++ ltt-events.c | 51 +++++++ ltt-events.h | 38 ++++++ probes/Makefile | 5 + probes/lttng-kretprobes.c | 277 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 383 insertions(+) create mode 100644 probes/lttng-kretprobes.c diff --git a/ltt-debugfs-abi.c b/ltt-debugfs-abi.c index fe5f5b1b..a5f8e7ac 100644 --- a/ltt-debugfs-abi.c +++ b/ltt-debugfs-abi.c @@ -488,6 +488,9 @@ int lttng_abi_create_event(struct file *channel_file, return -EFAULT; event_param.name[LTTNG_SYM_NAME_LEN - 1] = '\0'; switch (event_param.instrumentation) { + case LTTNG_KERNEL_KRETPROBE: + event_param.u.kretprobe.symbol_name[LTTNG_SYM_NAME_LEN - 1] = '\0'; + break; case LTTNG_KERNEL_KPROBE: event_param.u.kprobe.symbol_name[LTTNG_SYM_NAME_LEN - 1] = '\0'; break; diff --git a/ltt-debugfs-abi.h b/ltt-debugfs-abi.h index e5ddcfa0..3b8e72ea 100644 --- a/ltt-debugfs-abi.h +++ b/ltt-debugfs-abi.h @@ -19,6 +19,7 @@ enum lttng_kernel_instrumentation { LTTNG_KERNEL_TRACEPOINT = 0, LTTNG_KERNEL_KPROBE = 1, LTTNG_KERNEL_FUNCTION = 2, + LTTNG_KERNEL_KRETPROBE = 3, }; /* @@ -42,6 +43,13 @@ struct lttng_kernel_channel { enum lttng_kernel_output output; /* splice, mmap */ }; +struct lttng_kernel_kretprobe { + uint64_t addr; + + uint64_t offset; + char symbol_name[LTTNG_SYM_NAME_LEN]; +}; + /* * Either addr is used, or symbol_name and offset. */ @@ -61,6 +69,7 @@ struct lttng_kernel_event { enum lttng_kernel_instrumentation instrumentation; /* Per instrumentation type configuration */ union { + struct lttng_kernel_kretprobe kretprobe; struct lttng_kernel_kprobe kprobe; struct lttng_kernel_function_tracer ftrace; } u; diff --git a/ltt-events.c b/ltt-events.c index 3532dee5..9c4ba27b 100644 --- a/ltt-events.c +++ b/ltt-events.c @@ -309,6 +309,49 @@ struct ltt_event *ltt_event_create(struct ltt_channel *chan, ret = try_module_get(event->desc->owner); WARN_ON_ONCE(!ret); break; + case LTTNG_KERNEL_KRETPROBE: + { + struct ltt_event *event_return; + + /* kretprobe defines 2 events */ + event_return = + kmem_cache_zalloc(event_cache, GFP_KERNEL); + if (!event_return) + goto register_error; + event_return->chan = chan; + event_return->filter = filter; + event_return->id = chan->free_event_id++; + event_return->enabled = 1; + event_return->instrumentation = event_param->instrumentation; + /* + * Populate ltt_event structure before kretprobe registration. + */ + smp_wmb(); + ret = lttng_kretprobes_register(event_param->name, + event_param->u.kretprobe.symbol_name, + event_param->u.kretprobe.offset, + event_param->u.kretprobe.addr, + event, event_return); + if (ret) { + kmem_cache_free(event_cache, event_return); + goto register_error; + } + /* Take 2 refs on the module: one per event. */ + ret = try_module_get(event->desc->owner); + WARN_ON_ONCE(!ret); + ret = try_module_get(event->desc->owner); + WARN_ON_ONCE(!ret); + ret = _ltt_event_metadata_statedump(chan->session, chan, + event_return); + if (ret) { + kmem_cache_free(event_cache, event_return); + module_put(event->desc->owner); + module_put(event->desc->owner); + goto statedump_error; + } + list_add(&event_return->list, &chan->session->events); + break; + } case LTTNG_KERNEL_FUNCTION: ret = lttng_ftrace_register(event_param->name, event_param->u.ftrace.symbol_name, @@ -361,6 +404,10 @@ int _ltt_event_unregister(struct ltt_event *event) lttng_kprobes_unregister(event); ret = 0; break; + case LTTNG_KERNEL_KRETPROBE: + lttng_kretprobes_unregister(event); + ret = 0; + break; case LTTNG_KERNEL_FUNCTION: lttng_ftrace_unregister(event); ret = 0; @@ -385,6 +432,10 @@ void _ltt_event_destroy(struct ltt_event *event) module_put(event->desc->owner); lttng_kprobes_destroy_private(event); break; + case LTTNG_KERNEL_KRETPROBE: + module_put(event->desc->owner); + lttng_kretprobes_destroy_private(event); + break; case LTTNG_KERNEL_FUNCTION: module_put(event->desc->owner); lttng_ftrace_destroy_private(event); diff --git a/ltt-events.h b/ltt-events.h index 31a50ca5..6cc4ea3f 100644 --- a/ltt-events.h +++ b/ltt-events.h @@ -163,6 +163,8 @@ struct lttng_probe_desc { struct list_head head; /* chain registered probes */ }; +struct lttng_krp; /* Kretprobe handling */ + /* * ltt_event structure is referred to by the tracing fast path. It must be * kept small. @@ -180,6 +182,10 @@ struct ltt_event { struct kprobe kp; char *symbol_name; } kprobe; + struct { + struct lttng_krp *lttng_krp; + char *symbol_name; + } kretprobe; struct { char *symbol_name; } ftrace; @@ -351,6 +357,38 @@ void lttng_kprobes_destroy_private(struct ltt_event *event) } #endif +#ifdef CONFIG_KRETPROBES +int lttng_kretprobes_register(const char *name, + const char *symbol_name, + uint64_t offset, + uint64_t addr, + struct ltt_event *event_entry, + struct ltt_event *event_exit); +void lttng_kretprobes_unregister(struct ltt_event *event); +void lttng_kretprobes_destroy_private(struct ltt_event *event); +#else +static inline +int lttng_kretprobes_register(const char *name, + const char *symbol_name, + uint64_t offset, + uint64_t addr, + struct ltt_event *event_entry, + struct ltt_event *event_exit) +{ + return -ENOSYS; +} + +static inline +void lttng_kretprobes_unregister(struct ltt_event *event) +{ +} + +static inline +void lttng_kretprobes_destroy_private(struct ltt_event *event) +{ +} +#endif + #ifdef CONFIG_DYNAMIC_FTRACE int lttng_ftrace_register(const char *name, const char *symbol_name, diff --git a/probes/Makefile b/probes/Makefile index 8e6994a0..8b02d5f5 100644 --- a/probes/Makefile +++ b/probes/Makefile @@ -31,6 +31,11 @@ ifneq ($(CONFIG_KPROBES),) obj-m += lttng-kprobes.o endif + +ifneq ($(CONFIG_KRETPROBES),) +obj-m += lttng-kretprobes.o +endif + ifneq ($(CONFIG_DYNAMIC_FTRACE),) obj-m += lttng-ftrace.o endif diff --git a/probes/lttng-kretprobes.c b/probes/lttng-kretprobes.c new file mode 100644 index 00000000..60bafc04 --- /dev/null +++ b/probes/lttng-kretprobes.c @@ -0,0 +1,277 @@ +/* + * (C) Copyright 2009-2011 - + * Mathieu Desnoyers + * + * LTTng kretprobes integration module. + * + * Dual LGPL v2.1/GPL v2 license. + */ + +#include +#include +#include +#include +#include "../ltt-events.h" +#include "../wrapper/ringbuffer/frontend_types.h" +#include "../wrapper/vmalloc.h" +#include "../ltt-tracer.h" + +enum lttng_kretprobe_type { + EVENT_ENTRY = 0, + EVENT_RETURN = 1, +}; + +struct lttng_krp { + struct kretprobe krp; + struct ltt_event *event[2]; /* ENTRY and RETURN */ + struct kref kref_register; + struct kref kref_alloc; +}; + +static +int _lttng_kretprobes_handler(struct kretprobe_instance *krpi, + struct pt_regs *regs, + enum lttng_kretprobe_type type) +{ + struct lttng_krp *lttng_krp = + container_of(krpi->rp, struct lttng_krp, krp); + struct ltt_event *event = + lttng_krp->event[type]; + struct ltt_channel *chan = event->chan; + struct lib_ring_buffer_ctx ctx; + int ret; + struct { + unsigned long ip; + unsigned long parent_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; + + payload.ip = (unsigned long) krpi->rp->kp.addr; + payload.parent_ip = (unsigned long) krpi->ret_addr; + + lib_ring_buffer_ctx_init(&ctx, chan->chan, event, sizeof(payload), + ltt_alignof(payload), -1); + ret = chan->ops->event_reserve(&ctx, event->id); + if (ret < 0) + return 0; + lib_ring_buffer_align_ctx(&ctx, ltt_alignof(payload)); + chan->ops->event_write(&ctx, &payload, sizeof(payload)); + chan->ops->event_commit(&ctx); + return 0; +} + +static +int lttng_kretprobes_handler_entry(struct kretprobe_instance *krpi, + struct pt_regs *regs) +{ + return _lttng_kretprobes_handler(krpi, regs, EVENT_ENTRY); +} + +static +int lttng_kretprobes_handler_return(struct kretprobe_instance *krpi, + struct pt_regs *regs) +{ + return _lttng_kretprobes_handler(krpi, regs, EVENT_RETURN); +} + +/* + * Create event description + */ +static +int lttng_create_kprobe_event(const char *name, struct ltt_event *event, + enum lttng_kretprobe_type type) +{ + struct lttng_event_field *fields; + struct lttng_event_desc *desc; + int ret; + char *alloc_name; + size_t name_len; + const char *suffix = NULL; + + desc = kzalloc(sizeof(*event->desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + name_len = strlen(name); + switch (type) { + case EVENT_ENTRY: + suffix = "_entry"; + break; + case EVENT_RETURN: + suffix = "_return"; + break; + } + name_len += strlen(suffix); + alloc_name = kmalloc(name_len + 1, GFP_KERNEL); + if (!alloc_name) { + ret = -ENOMEM; + goto error_str; + } + strcpy(alloc_name, name); + strcat(alloc_name, suffix); + desc->name = alloc_name; + desc->nr_fields = 2; + desc->fields = fields = + kzalloc(2 * 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 = ltt_alignof(unsigned long) * CHAR_BIT; + fields[0].type.u.basic.integer.signedness = 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; + + fields[1].name = "parent_ip"; + fields[1].type.atype = atype_integer; + fields[1].type.u.basic.integer.size = sizeof(unsigned long) * CHAR_BIT; + fields[1].type.u.basic.integer.alignment = ltt_alignof(unsigned long) * CHAR_BIT; + fields[1].type.u.basic.integer.signedness = is_signed_type(unsigned long); + fields[1].type.u.basic.integer.reverse_byte_order = 0; + fields[1].type.u.basic.integer.base = 16; + fields[1].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_kretprobes_register(const char *name, + const char *symbol_name, + uint64_t offset, + uint64_t addr, + struct ltt_event *event_entry, + struct ltt_event *event_return) +{ + int ret; + struct lttng_krp *lttng_krp; + + /* Kprobes expects a NULL symbol name if unused */ + if (symbol_name[0] == '\0') + symbol_name = NULL; + + ret = lttng_create_kprobe_event(name, event_entry, EVENT_ENTRY); + if (ret) + goto error; + ret = lttng_create_kprobe_event(name, event_return, EVENT_RETURN); + if (ret) + goto event_return_error; + lttng_krp = kzalloc(sizeof(*lttng_krp), GFP_KERNEL); + if (!lttng_krp) + goto krp_error; + lttng_krp->krp.entry_handler = lttng_kretprobes_handler_entry; + lttng_krp->krp.handler = lttng_kretprobes_handler_return; + if (symbol_name) { + char *alloc_symbol; + + alloc_symbol = kstrdup(symbol_name, GFP_KERNEL); + if (!alloc_symbol) { + ret = -ENOMEM; + goto name_error; + } + lttng_krp->krp.kp.symbol_name = + alloc_symbol; + event_entry->u.kretprobe.symbol_name = + alloc_symbol; + event_return->u.kretprobe.symbol_name = + alloc_symbol; + } + lttng_krp->krp.kp.offset = offset; + lttng_krp->krp.kp.addr = (void *) addr; + + /* Allow probe handler to find event structures */ + lttng_krp->event[EVENT_ENTRY] = event_entry; + lttng_krp->event[EVENT_RETURN] = event_return; + event_entry->u.kretprobe.lttng_krp = lttng_krp; + event_return->u.kretprobe.lttng_krp = lttng_krp; + + /* + * Both events must be unregistered before the kretprobe is + * unregistered. Same for memory allocation. + */ + kref_init(<tng_krp->kref_alloc); + kref_get(<tng_krp->kref_alloc); /* inc refcount to 2 */ + kref_init(<tng_krp->kref_register); + kref_get(<tng_krp->kref_register); /* inc refcount to 2 */ + + /* + * Ensure the memory we just allocated don't trigger page faults. + * Well.. kprobes itself puts the page fault handler on the blacklist, + * but we can never be too careful. + */ + wrapper_vmalloc_sync_all(); + + ret = register_kretprobe(<tng_krp->krp); + if (ret) + goto register_error; + return 0; + +register_error: + kfree(lttng_krp->krp.kp.symbol_name); +name_error: + kfree(lttng_krp); +krp_error: + kfree(event_return->desc->fields); + kfree(event_return->desc->name); + kfree(event_return->desc); +event_return_error: + kfree(event_entry->desc->fields); + kfree(event_entry->desc->name); + kfree(event_entry->desc); +error: + return ret; +} +EXPORT_SYMBOL_GPL(lttng_kretprobes_register); + +static +void _lttng_kretprobes_unregister_release(struct kref *kref) +{ + struct lttng_krp *lttng_krp = + container_of(kref, struct lttng_krp, kref_register); + unregister_kretprobe(<tng_krp->krp); +} + +void lttng_kretprobes_unregister(struct ltt_event *event) +{ + kref_put(&event->u.kretprobe.lttng_krp->kref_register, + _lttng_kretprobes_unregister_release); +} +EXPORT_SYMBOL_GPL(lttng_kretprobes_unregister); + +static +void _lttng_kretprobes_release(struct kref *kref) +{ + struct lttng_krp *lttng_krp = + container_of(kref, struct lttng_krp, kref_alloc); + kfree(lttng_krp->krp.kp.symbol_name); +} + +void lttng_kretprobes_destroy_private(struct ltt_event *event) +{ + kfree(event->desc->fields); + kfree(event->desc->name); + kfree(event->desc); + kref_put(&event->u.kretprobe.lttng_krp->kref_alloc, + _lttng_kretprobes_release); +} +EXPORT_SYMBOL_GPL(lttng_kretprobes_destroy_private); + +MODULE_LICENSE("GPL and additional rights"); +MODULE_AUTHOR("Mathieu Desnoyers"); +MODULE_DESCRIPTION("Linux Trace Toolkit Kretprobes Support"); -- 2.34.1