perf probe: Check kprobes blacklist when adding new events
authorMasami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Thu, 19 Feb 2015 14:31:13 +0000 (23:31 +0900)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Thu, 26 Feb 2015 14:59:05 +0000 (11:59 -0300)
Recent linux kernel provides a blacklist of the functions which can not
be probed. perf probe can now check this blacklist before setting new
events and indicate better error message for users.

Without this patch,
  ----
  # perf probe --add vmalloc_fault
  Added new event:
  Failed to write event: Invalid argument
    Error: Failed to add events.
  ----
With this patch
  ----
  # perf probe --add vmalloc_fault
  Added new event:
  Warning: Skipped probing on blacklisted function: vmalloc_fault
  ----

Reported-by: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20150219143113.14434.5387.stgit@localhost.localdomain
Signed-off-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/util/probe-event.c

index 9dfbed9..662d454 100644 (file)
@@ -1903,6 +1903,95 @@ static struct strlist *get_probe_trace_command_rawlist(int fd)
        return sl;
 }
 
+struct kprobe_blacklist_node {
+       struct list_head list;
+       unsigned long start;
+       unsigned long end;
+       char *symbol;
+};
+
+static void kprobe_blacklist__delete(struct list_head *blacklist)
+{
+       struct kprobe_blacklist_node *node;
+
+       while (!list_empty(blacklist)) {
+               node = list_first_entry(blacklist,
+                                       struct kprobe_blacklist_node, list);
+               list_del(&node->list);
+               free(node->symbol);
+               free(node);
+       }
+}
+
+static int kprobe_blacklist__load(struct list_head *blacklist)
+{
+       struct kprobe_blacklist_node *node;
+       const char *__debugfs = debugfs_find_mountpoint();
+       char buf[PATH_MAX], *p;
+       FILE *fp;
+       int ret;
+
+       if (__debugfs == NULL)
+               return -ENOTSUP;
+
+       ret = e_snprintf(buf, PATH_MAX, "%s/kprobes/blacklist", __debugfs);
+       if (ret < 0)
+               return ret;
+
+       fp = fopen(buf, "r");
+       if (!fp)
+               return -errno;
+
+       ret = 0;
+       while (fgets(buf, PATH_MAX, fp)) {
+               node = zalloc(sizeof(*node));
+               if (!node) {
+                       ret = -ENOMEM;
+                       break;
+               }
+               INIT_LIST_HEAD(&node->list);
+               list_add_tail(&node->list, blacklist);
+               if (sscanf(buf, "0x%lx-0x%lx", &node->start, &node->end) != 2) {
+                       ret = -EINVAL;
+                       break;
+               }
+               p = strchr(buf, '\t');
+               if (p) {
+                       p++;
+                       if (p[strlen(p) - 1] == '\n')
+                               p[strlen(p) - 1] = '\0';
+               } else
+                       p = (char *)"unknown";
+               node->symbol = strdup(p);
+               if (!node->symbol) {
+                       ret = -ENOMEM;
+                       break;
+               }
+               pr_debug2("Blacklist: 0x%lx-0x%lx, %s\n",
+                         node->start, node->end, node->symbol);
+               ret++;
+       }
+       if (ret < 0)
+               kprobe_blacklist__delete(blacklist);
+       fclose(fp);
+
+       return ret;
+}
+
+static struct kprobe_blacklist_node *
+kprobe_blacklist__find_by_address(struct list_head *blacklist,
+                                 unsigned long address)
+{
+       struct kprobe_blacklist_node *node;
+
+       list_for_each_entry(node, blacklist, list) {
+               if (node->start <= address && address <= node->end)
+                       return node;
+       }
+
+       return NULL;
+}
+
 /* Show an event */
 static int show_perf_probe_event(struct perf_probe_event *pev,
                                 const char *module)
@@ -2117,6 +2206,8 @@ static int __add_probe_trace_events(struct perf_probe_event *pev,
        char buf[64];
        const char *event, *group;
        struct strlist *namelist;
+       LIST_HEAD(blacklist);
+       struct kprobe_blacklist_node *node;
 
        if (pev->uprobes)
                fd = open_uprobe_events(true);
@@ -2134,11 +2225,25 @@ static int __add_probe_trace_events(struct perf_probe_event *pev,
                pr_debug("Failed to get current event list.\n");
                return -EIO;
        }
+       /* Get kprobe blacklist if exists */
+       if (!pev->uprobes) {
+               ret = kprobe_blacklist__load(&blacklist);
+               if (ret < 0)
+                       pr_debug("No kprobe blacklist support, ignored\n");
+       }
 
        ret = 0;
        pr_info("Added new event%s\n", (ntevs > 1) ? "s:" : ":");
        for (i = 0; i < ntevs; i++) {
                tev = &tevs[i];
+               /* Ensure that the address is NOT blacklisted */
+               node = kprobe_blacklist__find_by_address(&blacklist,
+                                                        tev->point.address);
+               if (node) {
+                       pr_warning("Warning: Skipped probing on blacklisted function: %s\n", node->symbol);
+                       continue;
+               }
+
                if (pev->event)
                        event = pev->event;
                else
@@ -2189,13 +2294,15 @@ static int __add_probe_trace_events(struct perf_probe_event *pev,
                allow_suffix = true;
        }
 
-       if (ret >= 0) {
+       /* Note that it is possible to skip all events because of blacklist */
+       if (ret >= 0 && tev->event) {
                /* Show how to use the event. */
                pr_info("\nYou can now use it in all perf tools, such as:\n\n");
                pr_info("\tperf record -e %s:%s -aR sleep 1\n\n", tev->group,
                         tev->event);
        }
 
+       kprobe_blacklist__delete(&blacklist);
        strlist__delete(namelist);
        close(fd);
        return ret;