perf symbols: Fix inverted logic for showing kallsyms as the source of symbols
[safe/jmp/linux-2.6] / tools / perf / util / symbol.c
index da2f07f..1270cf8 100644 (file)
@@ -161,7 +161,7 @@ static size_t symbol__fprintf(struct symbol *self, FILE *fp)
                       self->start, self->end, self->name);
 }
 
-static void dso__set_long_name(struct dso *self, char *name)
+void dso__set_long_name(struct dso *self, char *name)
 {
        if (name == NULL)
                return;
@@ -176,7 +176,7 @@ static void dso__set_basename(struct dso *self)
 
 struct dso *dso__new(const char *name)
 {
-       struct dso *self = malloc(sizeof(*self) + strlen(name) + 1);
+       struct dso *self = zalloc(sizeof(*self) + strlen(name) + 1);
 
        if (self != NULL) {
                int i;
@@ -345,10 +345,10 @@ void dso__sort_by_name(struct dso *self, enum map_type type)
                                     &self->symbols[type]);
 }
 
-int build_id__sprintf(u8 *self, int len, char *bf)
+int build_id__sprintf(const u8 *self, int len, char *bf)
 {
        char *bid = bf;
-       u8 *raw = self;
+       const u8 *raw = self;
        int i;
 
        for (i = 0; i < len; ++i) {
@@ -383,13 +383,14 @@ size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp)
        return ret;
 }
 
-int kallsyms__parse(void *arg, int (*process_symbol)(void *arg, const char *name,
+int kallsyms__parse(const char *filename, void *arg,
+                   int (*process_symbol)(void *arg, const char *name,
                                                     char type, u64 start))
 {
        char *line = NULL;
        size_t n;
        int err = 0;
-       FILE *file = fopen("/proc/kallsyms", "r");
+       FILE *file = fopen(filename, "r");
 
        if (file == NULL)
                goto out_failure;
@@ -466,10 +467,11 @@ static int map__process_kallsym_symbol(void *arg, const char *name,
  * so that we can in the next step set the symbol ->end address and then
  * call kernel_maps__split_kallsyms.
  */
-static int dso__load_all_kallsyms(struct dso *self, struct map *map)
+static int dso__load_all_kallsyms(struct dso *self, const char *filename,
+                                 struct map *map)
 {
        struct process_kallsyms_args args = { .map = map, .dso = self, };
-       return kallsyms__parse(&args, map__process_kallsym_symbol);
+       return kallsyms__parse(filename, &args, map__process_kallsym_symbol);
 }
 
 /*
@@ -500,13 +502,17 @@ static int dso__split_kallsyms(struct dso *self, struct map *map,
 
                        *module++ = '\0';
 
-                       if (strcmp(self->name, module)) {
+                       if (strcmp(curr_map->dso->short_name, module)) {
                                curr_map = map_groups__find_by_name(&session->kmaps, map->type, module);
                                if (curr_map == NULL) {
                                        pr_debug("/proc/{kallsyms,modules} "
-                                                "inconsistency!\n");
+                                                "inconsistency while looking "
+                                                "for \"%s\" module!\n", module);
                                        return -1;
                                }
+
+                               if (curr_map->dso->loaded)
+                                       goto discard_symbol;
                        }
                        /*
                         * So that we look just like we get from .ko files,
@@ -552,10 +558,10 @@ discard_symbol:           rb_erase(&pos->rb_node, root);
 }
 
 
-static int dso__load_kallsyms(struct dso *self, struct map *map,
+static int dso__load_kallsyms(struct dso *self, const char *filename, struct map *map,
                              struct perf_session *session, symbol_filter_t filter)
 {
-       if (dso__load_all_kallsyms(self, map) < 0)
+       if (dso__load_all_kallsyms(self, filename, map) < 0)
                return -1;
 
        symbols__fixup_end(&self->symbols[map->type]);
@@ -1343,13 +1349,33 @@ struct map *map_groups__find_by_name(struct map_groups *self,
        for (nd = rb_first(&self->maps[type]); nd; nd = rb_next(nd)) {
                struct map *map = rb_entry(nd, struct map, rb_node);
 
-               if (map->dso && strcmp(map->dso->name, name) == 0)
+               if (map->dso && strcmp(map->dso->short_name, name) == 0)
                        return map;
        }
 
        return NULL;
 }
 
+static int dso__kernel_module_get_build_id(struct dso *self)
+{
+       char filename[PATH_MAX];
+       /*
+        * kernel module short names are of the form "[module]" and
+        * we need just "module" here.
+        */
+       const char *name = self->short_name + 1;
+
+       snprintf(filename, sizeof(filename),
+                "/sys/module/%.*s/notes/.note.gnu.build-id",
+                (int)strlen(name - 1), name);
+
+       if (sysfs__read_build_id(filename, self->build_id,
+                                sizeof(self->build_id)) == 0)
+               self->has_build_id = true;
+
+       return 0;
+}
+
 static int perf_session__set_modules_path_dir(struct perf_session *self, char *dirname)
 {
        struct dirent *dent;
@@ -1395,6 +1421,7 @@ static int perf_session__set_modules_path_dir(struct perf_session *self, char *d
                        if (long_name == NULL)
                                goto failure;
                        dso__set_long_name(map->dso, long_name);
+                       dso__kernel_module_get_build_id(map->dso);
                }
        }
 
@@ -1437,6 +1464,24 @@ static struct map *map__new2(u64 start, struct dso *dso, enum map_type type)
        return self;
 }
 
+struct map *perf_session__new_module_map(struct perf_session *self, u64 start,
+                                        const char *filename)
+{
+       struct map *map;
+       struct dso *dso = __dsos__findnew(&dsos__kernel, filename);
+
+       if (dso == NULL)
+               return NULL;
+
+       map = map__new2(start, dso, MAP__FUNCTION);
+       if (map == NULL)
+               return NULL;
+
+       dso->origin = DSO__ORIG_KMODULE;
+       map_groups__insert(&self->kmaps, map);
+       return map;
+}
+
 static int perf_session__create_module_maps(struct perf_session *self)
 {
        char *line = NULL;
@@ -1450,7 +1495,6 @@ static int perf_session__create_module_maps(struct perf_session *self)
        while (!feof(file)) {
                char name[PATH_MAX];
                u64 start;
-               struct dso *dso;
                char *sep;
                int line_len;
 
@@ -1476,26 +1520,10 @@ static int perf_session__create_module_maps(struct perf_session *self)
                *sep = '\0';
 
                snprintf(name, sizeof(name), "[%s]", line);
-               dso = dso__new(name);
-
-               if (dso == NULL)
+               map = perf_session__new_module_map(self, start, name);
+               if (map == NULL)
                        goto out_delete_line;
-
-               map = map__new2(start, dso, MAP__FUNCTION);
-               if (map == NULL) {
-                       dso__delete(dso);
-                       goto out_delete_line;
-               }
-
-               snprintf(name, sizeof(name),
-                        "/sys/module/%s/notes/.note.gnu.build-id", line);
-               if (sysfs__read_build_id(name, dso->build_id,
-                                        sizeof(dso->build_id)) == 0)
-                       dso->has_build_id = true;
-
-               dso->origin = DSO__ORIG_KMODULE;
-               map_groups__insert(&self->kmaps, map);
-               dsos__add(&dsos__kernel, dso);
+               dso__kernel_module_get_build_id(map->dso);
        }
 
        free(line);
@@ -1544,7 +1572,7 @@ static int dso__load_vmlinux(struct dso *self, struct map *map,
                return -1;
 
        dso__set_loaded(self, map->type);
-       err = dso__load_sym(self, map, session, self->long_name, fd, filter, 1, 0);
+       err = dso__load_sym(self, map, session, vmlinux, fd, filter, 1, 0);
        close(fd);
 
        return err;
@@ -1554,7 +1582,28 @@ static int dso__load_kernel_sym(struct dso *self, struct map *map,
                                struct perf_session *session, symbol_filter_t filter)
 {
        int err;
-       bool is_kallsyms;
+       const char *kallsyms_filename = NULL;
+       char *kallsyms_allocated_filename = NULL;
+       /*
+        * Step 1: if the user specified a vmlinux filename, use it and only
+        * it, reporting errors to the user if it cannot be used.
+        *
+        * For instance, try to analyse an ARM perf.data file _without_ a
+        * build-id, or if the user specifies the wrong path to the right
+        * vmlinux file, obviously we can't fallback to another vmlinux (a
+        * x86_86 one, on the machine where analysis is being performed, say),
+        * or worse, /proc/kallsyms.
+        *
+        * If the specified file _has_ a build-id and there is a build-id
+        * section in the perf.data file, we will still do the expected
+        * validation in dso__load_vmlinux and will bail out if they don't
+        * match.
+        */
+       if (symbol_conf.vmlinux_name != NULL) {
+               err = dso__load_vmlinux(self, map, session,
+                                       symbol_conf.vmlinux_name, filter);
+               goto out_try_fixup;
+       }
 
        if (vmlinux_path != NULL) {
                int i;
@@ -1573,22 +1622,57 @@ static int dso__load_kernel_sym(struct dso *self, struct map *map,
                }
        }
 
-       is_kallsyms = self->long_name[0] == '[';
-       if (is_kallsyms)
-               goto do_kallsyms;
+       /*
+        * Say the kernel DSO was created when processing the build-id header table,
+        * we have a build-id, so check if it is the same as the running kernel,
+        * using it if it is.
+        */
+       if (self->has_build_id) {
+               u8 kallsyms_build_id[BUILD_ID_SIZE];
+               char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+               if (sysfs__read_build_id("/sys/kernel/notes", kallsyms_build_id,
+                                        sizeof(kallsyms_build_id)) == 0) {
+                       if (dso__build_id_equal(self, kallsyms_build_id)) {
+                               kallsyms_filename = "/proc/kallsyms";
+                               goto do_kallsyms;
+                       }
+               }
+               /*
+                * Now look if we have it on the build-id cache in
+                * $HOME/.debug/[kernel.kallsyms].
+                */
+               build_id__sprintf(self->build_id, sizeof(self->build_id),
+                                 sbuild_id);
+
+               if (asprintf(&kallsyms_allocated_filename,
+                            "%s/.debug/[kernel.kallsyms]/%s",
+                            getenv("HOME"), sbuild_id) == -1)
+                       return -1;
 
-       err = dso__load_vmlinux(self, map, session, self->long_name, filter);
-       if (err <= 0) {
-               pr_info("The file %s cannot be used, "
-                       "trying to use /proc/kallsyms...", self->long_name);
-do_kallsyms:
-               err = dso__load_kallsyms(self, map, session, filter);
-               if (err > 0 && !is_kallsyms)
-                        dso__set_long_name(self, strdup("[kernel.kallsyms]"));
+               if (access(kallsyms_filename, F_OK)) {
+                       free(kallsyms_allocated_filename);
+                       return -1;
+               }
+
+               kallsyms_filename = kallsyms_allocated_filename;
+       } else {
+               /*
+                * Last resort, if we don't have a build-id and couldn't find
+                * any vmlinux file, try the running kernel kallsyms table.
+                */
+               kallsyms_filename = "/proc/kallsyms";
        }
 
+do_kallsyms:
+       err = dso__load_kallsyms(self, kallsyms_filename, map, session, filter);
+       free(kallsyms_allocated_filename);
+
+out_try_fixup:
        if (err > 0) {
 out_fixup:
+               if (kallsyms_filename != NULL)
+                       dso__set_long_name(self, strdup("[kernel.kallsyms]"));
                map__fixup_start(map);
                map__fixup_end(map);
        }
@@ -1610,19 +1694,19 @@ static struct dso *dsos__find(struct list_head *head, const char *name)
        struct dso *pos;
 
        list_for_each_entry(pos, head, node)
-               if (strcmp(pos->name, name) == 0)
+               if (strcmp(pos->long_name, name) == 0)
                        return pos;
        return NULL;
 }
 
-struct dso *dsos__findnew(const char *name)
+struct dso *__dsos__findnew(struct list_head *head, const char *name)
 {
-       struct dso *dso = dsos__find(&dsos__user, name);
+       struct dso *dso = dsos__find(head, name);
 
        if (!dso) {
                dso = dso__new(name);
                if (dso != NULL) {
-                       dsos__add(&dsos__user, dso);
+                       dsos__add(head, dso);
                        dso__set_basename(dso);
                }
        }
@@ -1647,22 +1731,25 @@ void dsos__fprintf(FILE *fp)
        __dsos__fprintf(&dsos__user, fp);
 }
 
-static size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp)
+static size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp,
+                                     bool with_hits)
 {
        struct dso *pos;
        size_t ret = 0;
 
        list_for_each_entry(pos, head, node) {
+               if (with_hits && !pos->hit)
+                       continue;
                ret += dso__fprintf_buildid(pos, fp);
                ret += fprintf(fp, " %s\n", pos->long_name);
        }
        return ret;
 }
 
-size_t dsos__fprintf_buildid(FILE *fp)
+size_t dsos__fprintf_buildid(FILE *fp, bool with_hits)
 {
-       return (__dsos__fprintf_buildid(&dsos__kernel, fp) +
-               __dsos__fprintf_buildid(&dsos__user, fp));
+       return (__dsos__fprintf_buildid(&dsos__kernel, fp, with_hits) +
+               __dsos__fprintf_buildid(&dsos__user, fp, with_hits));
 }
 
 static struct dso *dsos__create_kernel(const char *vmlinux)
@@ -1694,16 +1781,12 @@ out_delete_kernel_dso:
        return NULL;
 }
 
-static int map_groups__create_kernel_maps(struct map_groups *self,
-                                         struct map *vmlinux_maps[MAP__NR_TYPES],
-                                         const char *vmlinux)
+int __map_groups__create_kernel_maps(struct map_groups *self,
+                                    struct map *vmlinux_maps[MAP__NR_TYPES],
+                                    struct dso *kernel)
 {
-       struct dso *kernel = dsos__create_kernel(vmlinux);
        enum map_type type;
 
-       if (kernel == NULL)
-               return -1;
-
        for (type = 0; type < MAP__NR_TYPES; ++type) {
                vmlinux_maps[type] = map__new2(0, kernel, type);
                if (vmlinux_maps[type] == NULL)
@@ -1717,6 +1800,18 @@ static int map_groups__create_kernel_maps(struct map_groups *self,
        return 0;
 }
 
+static int map_groups__create_kernel_maps(struct map_groups *self,
+                                         struct map *vmlinux_maps[MAP__NR_TYPES],
+                                         const char *vmlinux)
+{
+       struct dso *kernel = dsos__create_kernel(vmlinux);
+
+       if (kernel == NULL)
+               return -1;
+
+       return __map_groups__create_kernel_maps(self, vmlinux_maps, kernel);
+}
+
 static void vmlinux_path__exit(void)
 {
        while (--vmlinux_path__nr_entries >= 0) {