Add kretprobe support
authorMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Thu, 28 Jul 2011 22:41:03 +0000 (18:41 -0400)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Thu, 28 Jul 2011 22:41:03 +0000 (18:41 -0400)
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
ltt-debugfs-abi.c
ltt-debugfs-abi.h
ltt-events.c
ltt-events.h
probes/Makefile
probes/lttng-kretprobes.c [new file with mode: 0644]

index fe5f5b1b354310ea0c083cb53c3504aae7c875c9..a5f8e7ac88f47227cf6c00b308d0275140964444 100644 (file)
@@ -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;
index e5ddcfa0965ded2110b8c2bfa12aa04c6c763272..3b8e72ea9c8544b0d55165a7311ed25e7ddbb834 100644 (file)
@@ -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;
index 3532dee5a1c8d46e246fa14c8e6c07a1257f26c7..9c4ba27bc2300012ebbfa1a631451e01d0f3db18 100644 (file)
@@ -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);
index 31a50ca5f34968d0705e22885d30d54289d0db51..6cc4ea3f6fed01bbcc9309dbfcdfbfb33400a21e 100644 (file)
@@ -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,
index 8e6994a0ffe8a7b5f3bac813a5161e6fd2c30450..8b02d5f5b5eaa3adcf43d657bfef940627ed301c 100644 (file)
@@ -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 (file)
index 0000000..60bafc0
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * (C) Copyright       2009-2011 -
+ *             Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * LTTng kretprobes integration module.
+ *
+ * Dual LGPL v2.1/GPL v2 license.
+ */
+
+#include <linux/module.h>
+#include <linux/kprobes.h>
+#include <linux/slab.h>
+#include <linux/kref.h>
+#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(&lttng_krp->kref_alloc);
+       kref_get(&lttng_krp->kref_alloc);       /* inc refcount to 2 */
+       kref_init(&lttng_krp->kref_register);
+       kref_get(&lttng_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(&lttng_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(&lttng_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");
This page took 0.032688 seconds and 4 git commands to generate.