|
@@ -0,0 +1,335 @@
|
|
|
+/*
|
|
|
+ * Copyright (C) 2008 Pekka Enberg, Eduard - Gabriel Munteanu
|
|
|
+ *
|
|
|
+ * This file is released under GPL version 2.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/debugfs.h>
|
|
|
+#include <linux/relay.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/marker.h>
|
|
|
+#include <linux/gfp.h>
|
|
|
+#include <linux/kmemtrace.h>
|
|
|
+
|
|
|
+#define KMEMTRACE_SUBBUF_SIZE 524288
|
|
|
+#define KMEMTRACE_DEF_N_SUBBUFS 20
|
|
|
+
|
|
|
+static struct rchan *kmemtrace_chan;
|
|
|
+static u32 kmemtrace_buf_overruns;
|
|
|
+
|
|
|
+static unsigned int kmemtrace_n_subbufs;
|
|
|
+#ifdef CONFIG_KMEMTRACE_DEFAULT_ENABLED
|
|
|
+static unsigned int kmemtrace_enabled = 1;
|
|
|
+#else
|
|
|
+static unsigned int kmemtrace_enabled = 0;
|
|
|
+#endif
|
|
|
+
|
|
|
+/*
|
|
|
+ * The sequence number is used for reordering kmemtrace packets
|
|
|
+ * in userspace, since they are logged as per-CPU data.
|
|
|
+ *
|
|
|
+ * atomic_t should always be a 32-bit signed integer. Wraparound is not
|
|
|
+ * likely to occur, but userspace can deal with it by expecting a certain
|
|
|
+ * sequence number in the next packet that will be read.
|
|
|
+ */
|
|
|
+static atomic_t kmemtrace_seq_num;
|
|
|
+
|
|
|
+#define KMEMTRACE_ABI_VERSION 1
|
|
|
+
|
|
|
+static u32 kmemtrace_abi_version __read_mostly = KMEMTRACE_ABI_VERSION;
|
|
|
+
|
|
|
+enum kmemtrace_event_id {
|
|
|
+ KMEMTRACE_EVENT_ALLOC = 0,
|
|
|
+ KMEMTRACE_EVENT_FREE,
|
|
|
+};
|
|
|
+
|
|
|
+struct kmemtrace_event {
|
|
|
+ u8 event_id;
|
|
|
+ u8 type_id;
|
|
|
+ u16 event_size;
|
|
|
+ s32 seq_num;
|
|
|
+ u64 call_site;
|
|
|
+ u64 ptr;
|
|
|
+} __attribute__ ((__packed__));
|
|
|
+
|
|
|
+struct kmemtrace_stats_alloc {
|
|
|
+ u64 bytes_req;
|
|
|
+ u64 bytes_alloc;
|
|
|
+ u32 gfp_flags;
|
|
|
+ s32 numa_node;
|
|
|
+} __attribute__ ((__packed__));
|
|
|
+
|
|
|
+static void kmemtrace_probe_alloc(void *probe_data, void *call_data,
|
|
|
+ const char *format, va_list *args)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ struct kmemtrace_event *ev;
|
|
|
+ struct kmemtrace_stats_alloc *stats;
|
|
|
+ void *buf;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ buf = relay_reserve(kmemtrace_chan,
|
|
|
+ sizeof(struct kmemtrace_event) +
|
|
|
+ sizeof(struct kmemtrace_stats_alloc));
|
|
|
+ if (!buf)
|
|
|
+ goto failed;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Don't convert this to use structure initializers,
|
|
|
+ * C99 does not guarantee the rvalues evaluation order.
|
|
|
+ */
|
|
|
+
|
|
|
+ ev = buf;
|
|
|
+ ev->event_id = KMEMTRACE_EVENT_ALLOC;
|
|
|
+ ev->type_id = va_arg(*args, int);
|
|
|
+ ev->event_size = sizeof(struct kmemtrace_event) +
|
|
|
+ sizeof(struct kmemtrace_stats_alloc);
|
|
|
+ ev->seq_num = atomic_add_return(1, &kmemtrace_seq_num);
|
|
|
+ ev->call_site = va_arg(*args, unsigned long);
|
|
|
+ ev->ptr = va_arg(*args, unsigned long);
|
|
|
+
|
|
|
+ stats = buf + sizeof(struct kmemtrace_event);
|
|
|
+ stats->bytes_req = va_arg(*args, unsigned long);
|
|
|
+ stats->bytes_alloc = va_arg(*args, unsigned long);
|
|
|
+ stats->gfp_flags = va_arg(*args, unsigned long);
|
|
|
+ stats->numa_node = va_arg(*args, int);
|
|
|
+
|
|
|
+failed:
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
+
|
|
|
+static void kmemtrace_probe_free(void *probe_data, void *call_data,
|
|
|
+ const char *format, va_list *args)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ struct kmemtrace_event *ev;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ ev = relay_reserve(kmemtrace_chan, sizeof(struct kmemtrace_event));
|
|
|
+ if (!ev)
|
|
|
+ goto failed;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Don't convert this to use structure initializers,
|
|
|
+ * C99 does not guarantee the rvalues evaluation order.
|
|
|
+ */
|
|
|
+ ev->event_id = KMEMTRACE_EVENT_FREE;
|
|
|
+ ev->type_id = va_arg(*args, int);
|
|
|
+ ev->event_size = sizeof(struct kmemtrace_event);
|
|
|
+ ev->seq_num = atomic_add_return(1, &kmemtrace_seq_num);
|
|
|
+ ev->call_site = va_arg(*args, unsigned long);
|
|
|
+ ev->ptr = va_arg(*args, unsigned long);
|
|
|
+
|
|
|
+failed:
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
+
|
|
|
+static struct dentry *
|
|
|
+kmemtrace_create_buf_file(const char *filename, struct dentry *parent,
|
|
|
+ int mode, struct rchan_buf *buf, int *is_global)
|
|
|
+{
|
|
|
+ return debugfs_create_file(filename, mode, parent, buf,
|
|
|
+ &relay_file_operations);
|
|
|
+}
|
|
|
+
|
|
|
+static int kmemtrace_remove_buf_file(struct dentry *dentry)
|
|
|
+{
|
|
|
+ debugfs_remove(dentry);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kmemtrace_subbuf_start(struct rchan_buf *buf,
|
|
|
+ void *subbuf,
|
|
|
+ void *prev_subbuf,
|
|
|
+ size_t prev_padding)
|
|
|
+{
|
|
|
+ if (relay_buf_full(buf)) {
|
|
|
+ /*
|
|
|
+ * We know it's not SMP-safe, but neither
|
|
|
+ * debugfs_create_u32() is.
|
|
|
+ */
|
|
|
+ kmemtrace_buf_overruns++;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+static struct rchan_callbacks relay_callbacks = {
|
|
|
+ .create_buf_file = kmemtrace_create_buf_file,
|
|
|
+ .remove_buf_file = kmemtrace_remove_buf_file,
|
|
|
+ .subbuf_start = kmemtrace_subbuf_start,
|
|
|
+};
|
|
|
+
|
|
|
+static struct dentry *kmemtrace_dir;
|
|
|
+static struct dentry *kmemtrace_overruns_dentry;
|
|
|
+static struct dentry *kmemtrace_abi_version_dentry;
|
|
|
+
|
|
|
+static struct dentry *kmemtrace_enabled_dentry;
|
|
|
+
|
|
|
+static int kmemtrace_start_probes(void)
|
|
|
+{
|
|
|
+ int err;
|
|
|
+
|
|
|
+ err = marker_probe_register("kmemtrace_alloc", "type_id %d "
|
|
|
+ "call_site %lu ptr %lu "
|
|
|
+ "bytes_req %lu bytes_alloc %lu "
|
|
|
+ "gfp_flags %lu node %d",
|
|
|
+ kmemtrace_probe_alloc, NULL);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ err = marker_probe_register("kmemtrace_free", "type_id %d "
|
|
|
+ "call_site %lu ptr %lu",
|
|
|
+ kmemtrace_probe_free, NULL);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static void kmemtrace_stop_probes(void)
|
|
|
+{
|
|
|
+ marker_probe_unregister("kmemtrace_alloc",
|
|
|
+ kmemtrace_probe_alloc, NULL);
|
|
|
+ marker_probe_unregister("kmemtrace_free",
|
|
|
+ kmemtrace_probe_free, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static int kmemtrace_enabled_get(void *data, u64 *val)
|
|
|
+{
|
|
|
+ *val = *((int *) data);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kmemtrace_enabled_set(void *data, u64 val)
|
|
|
+{
|
|
|
+ u64 old_val = kmemtrace_enabled;
|
|
|
+
|
|
|
+ *((int *) data) = !!val;
|
|
|
+
|
|
|
+ if (old_val == val)
|
|
|
+ return 0;
|
|
|
+ if (val)
|
|
|
+ kmemtrace_start_probes();
|
|
|
+ else
|
|
|
+ kmemtrace_stop_probes();
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+DEFINE_SIMPLE_ATTRIBUTE(kmemtrace_enabled_fops,
|
|
|
+ kmemtrace_enabled_get,
|
|
|
+ kmemtrace_enabled_set, "%llu\n");
|
|
|
+
|
|
|
+static void kmemtrace_cleanup(void)
|
|
|
+{
|
|
|
+ if (kmemtrace_enabled_dentry)
|
|
|
+ debugfs_remove(kmemtrace_enabled_dentry);
|
|
|
+
|
|
|
+ kmemtrace_stop_probes();
|
|
|
+
|
|
|
+ if (kmemtrace_abi_version_dentry)
|
|
|
+ debugfs_remove(kmemtrace_abi_version_dentry);
|
|
|
+ if (kmemtrace_overruns_dentry)
|
|
|
+ debugfs_remove(kmemtrace_overruns_dentry);
|
|
|
+
|
|
|
+ relay_close(kmemtrace_chan);
|
|
|
+ kmemtrace_chan = NULL;
|
|
|
+
|
|
|
+ if (kmemtrace_dir)
|
|
|
+ debugfs_remove(kmemtrace_dir);
|
|
|
+}
|
|
|
+
|
|
|
+static int __init kmemtrace_setup_late(void)
|
|
|
+{
|
|
|
+ if (!kmemtrace_chan)
|
|
|
+ goto failed;
|
|
|
+
|
|
|
+ kmemtrace_dir = debugfs_create_dir("kmemtrace", NULL);
|
|
|
+ if (!kmemtrace_dir)
|
|
|
+ goto cleanup;
|
|
|
+
|
|
|
+ kmemtrace_abi_version_dentry =
|
|
|
+ debugfs_create_u32("abi_version", S_IRUSR,
|
|
|
+ kmemtrace_dir, &kmemtrace_abi_version);
|
|
|
+ kmemtrace_overruns_dentry =
|
|
|
+ debugfs_create_u32("total_overruns", S_IRUSR,
|
|
|
+ kmemtrace_dir, &kmemtrace_buf_overruns);
|
|
|
+ if (!kmemtrace_overruns_dentry || !kmemtrace_abi_version_dentry)
|
|
|
+ goto cleanup;
|
|
|
+
|
|
|
+ kmemtrace_enabled_dentry =
|
|
|
+ debugfs_create_file("enabled", S_IRUSR | S_IWUSR,
|
|
|
+ kmemtrace_dir, &kmemtrace_enabled,
|
|
|
+ &kmemtrace_enabled_fops);
|
|
|
+ if (!kmemtrace_enabled_dentry)
|
|
|
+ goto cleanup;
|
|
|
+
|
|
|
+ if (relay_late_setup_files(kmemtrace_chan, "cpu", kmemtrace_dir))
|
|
|
+ goto cleanup;
|
|
|
+
|
|
|
+ printk(KERN_INFO "kmemtrace: fully up.\n");
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+cleanup:
|
|
|
+ kmemtrace_cleanup();
|
|
|
+failed:
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+late_initcall(kmemtrace_setup_late);
|
|
|
+
|
|
|
+static int __init kmemtrace_set_boot_enabled(char *str)
|
|
|
+{
|
|
|
+ if (!str)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!strcmp(str, "yes"))
|
|
|
+ kmemtrace_enabled = 1;
|
|
|
+ else if (!strcmp(str, "no"))
|
|
|
+ kmemtrace_enabled = 0;
|
|
|
+ else
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+early_param("kmemtrace.enable", kmemtrace_set_boot_enabled);
|
|
|
+
|
|
|
+static int __init kmemtrace_set_subbufs(char *str)
|
|
|
+{
|
|
|
+ get_option(&str, &kmemtrace_n_subbufs);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+early_param("kmemtrace.subbufs", kmemtrace_set_subbufs);
|
|
|
+
|
|
|
+void kmemtrace_init(void)
|
|
|
+{
|
|
|
+ if (!kmemtrace_enabled)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!kmemtrace_n_subbufs)
|
|
|
+ kmemtrace_n_subbufs = KMEMTRACE_DEF_N_SUBBUFS;
|
|
|
+
|
|
|
+ kmemtrace_chan = relay_open(NULL, NULL, KMEMTRACE_SUBBUF_SIZE,
|
|
|
+ kmemtrace_n_subbufs, &relay_callbacks,
|
|
|
+ NULL);
|
|
|
+ if (unlikely(!kmemtrace_chan)) {
|
|
|
+ printk(KERN_ERR "kmemtrace: could not open relay channel.\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (unlikely(kmemtrace_start_probes()))
|
|
|
+ goto probe_fail;
|
|
|
+
|
|
|
+ printk(KERN_INFO "kmemtrace: early init successful.\n");
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+probe_fail:
|
|
|
+ printk(KERN_ERR "kmemtrace: could not register marker probes!\n");
|
|
|
+ kmemtrace_cleanup();
|
|
|
+}
|
|
|
+
|