diff options
Diffstat (limited to 'tools/perf/util/symbol.c')
| -rw-r--r-- | tools/perf/util/symbol.c | 2455 |
1 files changed, 1263 insertions, 1192 deletions
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index ab92763edb0..7b9096f29cd 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -1,83 +1,176 @@ +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <fcntl.h> +#include <unistd.h> +#include <inttypes.h> +#include "build-id.h" #include "util.h" -#include "../perf.h" -#include "session.h" -#include "sort.h" -#include "string.h" -#include "symbol.h" -#include "thread.h" - #include "debug.h" +#include "machine.h" +#include "symbol.h" +#include "strlist.h" -#include <asm/bug.h> -#include <libelf.h> -#include <gelf.h> #include <elf.h> #include <limits.h> +#include <symbol/kallsyms.h> #include <sys/utsname.h> -#ifndef NT_GNU_BUILD_ID -#define NT_GNU_BUILD_ID 3 -#endif - -enum dso_origin { - DSO__ORIG_KERNEL = 0, - DSO__ORIG_JAVA_JIT, - DSO__ORIG_FEDORA, - DSO__ORIG_UBUNTU, - DSO__ORIG_BUILDID, - DSO__ORIG_DSO, - DSO__ORIG_KMODULE, - DSO__ORIG_NOT_FOUND, -}; - -static void dsos__add(struct list_head *head, struct dso *dso); -static struct map *map__new2(u64 start, struct dso *dso, enum map_type type); -static int dso__load_kernel_sym(struct dso *self, struct map *map, - struct perf_session *session, symbol_filter_t filter); -static int vmlinux_path__nr_entries; -static char **vmlinux_path; +static int dso__load_kernel_sym(struct dso *dso, struct map *map, + symbol_filter_t filter); +static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map, + symbol_filter_t filter); +int vmlinux_path__nr_entries; +char **vmlinux_path; struct symbol_conf symbol_conf = { - .exclude_other = true, - .use_modules = true, - .try_vmlinux_path = true, + .use_modules = true, + .try_vmlinux_path = true, + .annotate_src = true, + .demangle = true, + .cumulate_callchain = true, + .symfs = "", }; -bool dso__loaded(const struct dso *self, enum map_type type) -{ - return self->loaded & (1 << type); -} +static enum dso_binary_type binary_type_symtab[] = { + DSO_BINARY_TYPE__KALLSYMS, + DSO_BINARY_TYPE__GUEST_KALLSYMS, + DSO_BINARY_TYPE__JAVA_JIT, + DSO_BINARY_TYPE__DEBUGLINK, + DSO_BINARY_TYPE__BUILD_ID_CACHE, + DSO_BINARY_TYPE__FEDORA_DEBUGINFO, + DSO_BINARY_TYPE__UBUNTU_DEBUGINFO, + DSO_BINARY_TYPE__BUILDID_DEBUGINFO, + DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + DSO_BINARY_TYPE__GUEST_KMODULE, + DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE, + DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, + DSO_BINARY_TYPE__NOT_FOUND, +}; + +#define DSO_BINARY_TYPE__SYMTAB_CNT ARRAY_SIZE(binary_type_symtab) -bool dso__sorted_by_name(const struct dso *self, enum map_type type) +bool symbol_type__is_a(char symbol_type, enum map_type map_type) { - return self->sorted_by_name & (1 << type); + symbol_type = toupper(symbol_type); + + switch (map_type) { + case MAP__FUNCTION: + return symbol_type == 'T' || symbol_type == 'W'; + case MAP__VARIABLE: + return symbol_type == 'D'; + default: + return false; + } } -static void dso__set_loaded(struct dso *self, enum map_type type) +static int prefix_underscores_count(const char *str) { - self->loaded |= (1 << type); + const char *tail = str; + + while (*tail == '_') + tail++; + + return tail - str; } -static void dso__set_sorted_by_name(struct dso *self, enum map_type type) +#define SYMBOL_A 0 +#define SYMBOL_B 1 + +static int choose_best_symbol(struct symbol *syma, struct symbol *symb) { - self->sorted_by_name |= (1 << type); + s64 a; + s64 b; + size_t na, nb; + + /* Prefer a symbol with non zero length */ + a = syma->end - syma->start; + b = symb->end - symb->start; + if ((b == 0) && (a > 0)) + return SYMBOL_A; + else if ((a == 0) && (b > 0)) + return SYMBOL_B; + + /* Prefer a non weak symbol over a weak one */ + a = syma->binding == STB_WEAK; + b = symb->binding == STB_WEAK; + if (b && !a) + return SYMBOL_A; + if (a && !b) + return SYMBOL_B; + + /* Prefer a global symbol over a non global one */ + a = syma->binding == STB_GLOBAL; + b = symb->binding == STB_GLOBAL; + if (a && !b) + return SYMBOL_A; + if (b && !a) + return SYMBOL_B; + + /* Prefer a symbol with less underscores */ + a = prefix_underscores_count(syma->name); + b = prefix_underscores_count(symb->name); + if (b > a) + return SYMBOL_A; + else if (a > b) + return SYMBOL_B; + + /* Choose the symbol with the longest name */ + na = strlen(syma->name); + nb = strlen(symb->name); + if (na > nb) + return SYMBOL_A; + else if (na < nb) + return SYMBOL_B; + + /* Avoid "SyS" kernel syscall aliases */ + if (na >= 3 && !strncmp(syma->name, "SyS", 3)) + return SYMBOL_B; + if (na >= 10 && !strncmp(syma->name, "compat_SyS", 10)) + return SYMBOL_B; + + return SYMBOL_A; } -static bool symbol_type__is_a(char symbol_type, enum map_type map_type) +void symbols__fixup_duplicate(struct rb_root *symbols) { - switch (map_type) { - case MAP__FUNCTION: - return symbol_type == 'T' || symbol_type == 'W'; - case MAP__VARIABLE: - return symbol_type == 'D' || symbol_type == 'd'; - default: - return false; + struct rb_node *nd; + struct symbol *curr, *next; + + nd = rb_first(symbols); + + while (nd) { + curr = rb_entry(nd, struct symbol, rb_node); +again: + nd = rb_next(&curr->rb_node); + next = rb_entry(nd, struct symbol, rb_node); + + if (!nd) + break; + + if (curr->start != next->start) + continue; + + if (choose_best_symbol(curr, next) == SYMBOL_A) { + rb_erase(&next->rb_node, symbols); + symbol__delete(next); + goto again; + } else { + nd = rb_next(&curr->rb_node); + rb_erase(&curr->rb_node, symbols); + symbol__delete(curr); + } } } -static void symbols__fixup_end(struct rb_root *self) +void symbols__fixup_end(struct rb_root *symbols) { - struct rb_node *nd, *prevnd = rb_first(self); + struct rb_node *nd, *prevnd = rb_first(symbols); struct symbol *curr, *prev; if (prevnd == NULL) @@ -89,7 +182,7 @@ static void symbols__fixup_end(struct rb_root *self) prev = curr; curr = rb_entry(nd, struct symbol, rb_node); - if (prev->end == prev->start) + if (prev->end == prev->start && prev->end != curr->start) prev->end = curr->start - 1; } @@ -98,10 +191,10 @@ static void symbols__fixup_end(struct rb_root *self) curr->end = roundup(curr->start, 4096); } -static void __map_groups__fixup_end(struct map_groups *self, enum map_type type) +void __map_groups__fixup_end(struct map_groups *mg, enum map_type type) { struct map *prev, *curr; - struct rb_node *nd, *prevnd = rb_first(&self->maps[type]); + struct rb_node *nd, *prevnd = rb_first(&mg->maps[type]); if (prevnd == NULL) return; @@ -118,114 +211,87 @@ static void __map_groups__fixup_end(struct map_groups *self, enum map_type type) * We still haven't the actual symbols, so guess the * last map final address. */ - curr->end = ~0UL; + curr->end = ~0ULL; } -static void map_groups__fixup_end(struct map_groups *self) -{ - int i; - for (i = 0; i < MAP__NR_TYPES; ++i) - __map_groups__fixup_end(self, i); -} - -static struct symbol *symbol__new(u64 start, u64 len, const char *name) +struct symbol *symbol__new(u64 start, u64 len, u8 binding, const char *name) { size_t namelen = strlen(name) + 1; - struct symbol *self = zalloc(symbol_conf.priv_size + - sizeof(*self) + namelen); - if (self == NULL) + struct symbol *sym = calloc(1, (symbol_conf.priv_size + + sizeof(*sym) + namelen)); + if (sym == NULL) return NULL; if (symbol_conf.priv_size) - self = ((void *)self) + symbol_conf.priv_size; + sym = ((void *)sym) + symbol_conf.priv_size; - self->start = start; - self->end = len ? start + len - 1 : start; + sym->start = start; + sym->end = len ? start + len - 1 : start; + sym->binding = binding; + sym->namelen = namelen - 1; - pr_debug3("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end); + pr_debug4("%s: %s %#" PRIx64 "-%#" PRIx64 "\n", + __func__, name, start, sym->end); + memcpy(sym->name, name, namelen); - memcpy(self->name, name, namelen); - - return self; + return sym; } -static void symbol__delete(struct symbol *self) +void symbol__delete(struct symbol *sym) { - free(((void *)self) - symbol_conf.priv_size); + free(((void *)sym) - symbol_conf.priv_size); } -static size_t symbol__fprintf(struct symbol *self, FILE *fp) +size_t symbol__fprintf(struct symbol *sym, FILE *fp) { - return fprintf(fp, " %llx-%llx %s\n", - self->start, self->end, self->name); + return fprintf(fp, " %" PRIx64 "-%" PRIx64 " %c %s\n", + sym->start, sym->end, + sym->binding == STB_GLOBAL ? 'g' : + sym->binding == STB_LOCAL ? 'l' : 'w', + sym->name); } -static void dso__set_long_name(struct dso *self, char *name) +size_t symbol__fprintf_symname_offs(const struct symbol *sym, + const struct addr_location *al, FILE *fp) { - if (name == NULL) - return; - self->long_name = name; - self->long_name_len = strlen(name); -} + unsigned long offset; + size_t length; -static void dso__set_basename(struct dso *self) -{ - self->short_name = basename(self->long_name); + if (sym && sym->name) { + length = fprintf(fp, "%s", sym->name); + if (al) { + if (al->addr < sym->end) + offset = al->addr - sym->start; + else + offset = al->addr - al->map->start - sym->start; + length += fprintf(fp, "+0x%lx", offset); + } + return length; + } else + return fprintf(fp, "[unknown]"); } -struct dso *dso__new(const char *name) +size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp) { - struct dso *self = malloc(sizeof(*self) + strlen(name) + 1); - - if (self != NULL) { - int i; - strcpy(self->name, name); - dso__set_long_name(self, self->name); - self->short_name = self->name; - for (i = 0; i < MAP__NR_TYPES; ++i) - self->symbols[i] = self->symbol_names[i] = RB_ROOT; - self->slen_calculated = 0; - self->origin = DSO__ORIG_NOT_FOUND; - self->loaded = 0; - self->sorted_by_name = 0; - self->has_build_id = 0; - } - - return self; + return symbol__fprintf_symname_offs(sym, NULL, fp); } -static void symbols__delete(struct rb_root *self) +void symbols__delete(struct rb_root *symbols) { struct symbol *pos; - struct rb_node *next = rb_first(self); + struct rb_node *next = rb_first(symbols); while (next) { pos = rb_entry(next, struct symbol, rb_node); next = rb_next(&pos->rb_node); - rb_erase(&pos->rb_node, self); + rb_erase(&pos->rb_node, symbols); symbol__delete(pos); } } -void dso__delete(struct dso *self) -{ - int i; - for (i = 0; i < MAP__NR_TYPES; ++i) - symbols__delete(&self->symbols[i]); - if (self->long_name != self->name) - free(self->long_name); - free(self); -} - -void dso__set_build_id(struct dso *self, void *build_id) -{ - memcpy(self->build_id, build_id, sizeof(self->build_id)); - self->has_build_id = 1; -} - -static void symbols__insert(struct rb_root *self, struct symbol *sym) +void symbols__insert(struct rb_root *symbols, struct symbol *sym) { - struct rb_node **p = &self->rb_node; + struct rb_node **p = &symbols->rb_node; struct rb_node *parent = NULL; const u64 ip = sym->start; struct symbol *s; @@ -239,17 +305,17 @@ static void symbols__insert(struct rb_root *self, struct symbol *sym) p = &(*p)->rb_right; } rb_link_node(&sym->rb_node, parent, p); - rb_insert_color(&sym->rb_node, self); + rb_insert_color(&sym->rb_node, symbols); } -static struct symbol *symbols__find(struct rb_root *self, u64 ip) +static struct symbol *symbols__find(struct rb_root *symbols, u64 ip) { struct rb_node *n; - if (self == NULL) + if (symbols == NULL) return NULL; - n = self->rb_node; + n = symbols->rb_node; while (n) { struct symbol *s = rb_entry(n, struct symbol, rb_node); @@ -265,16 +331,28 @@ static struct symbol *symbols__find(struct rb_root *self, u64 ip) return NULL; } +static struct symbol *symbols__first(struct rb_root *symbols) +{ + struct rb_node *n = rb_first(symbols); + + if (n) + return rb_entry(n, struct symbol, rb_node); + + return NULL; +} + struct symbol_name_rb_node { struct rb_node rb_node; struct symbol sym; }; -static void symbols__insert_by_name(struct rb_root *self, struct symbol *sym) +static void symbols__insert_by_name(struct rb_root *symbols, struct symbol *sym) { - struct rb_node **p = &self->rb_node; + struct rb_node **p = &symbols->rb_node; struct rb_node *parent = NULL; - struct symbol_name_rb_node *symn = ((void *)sym) - sizeof(*parent), *s; + struct symbol_name_rb_node *symn, *s; + + symn = container_of(sym, struct symbol_name_rb_node, sym); while (*p != NULL) { parent = *p; @@ -285,27 +363,29 @@ static void symbols__insert_by_name(struct rb_root *self, struct symbol *sym) p = &(*p)->rb_right; } rb_link_node(&symn->rb_node, parent, p); - rb_insert_color(&symn->rb_node, self); + rb_insert_color(&symn->rb_node, symbols); } -static void symbols__sort_by_name(struct rb_root *self, struct rb_root *source) +static void symbols__sort_by_name(struct rb_root *symbols, + struct rb_root *source) { struct rb_node *nd; for (nd = rb_first(source); nd; nd = rb_next(nd)) { struct symbol *pos = rb_entry(nd, struct symbol, rb_node); - symbols__insert_by_name(self, pos); + symbols__insert_by_name(symbols, pos); } } -static struct symbol *symbols__find_by_name(struct rb_root *self, const char *name) +static struct symbol *symbols__find_by_name(struct rb_root *symbols, + const char *name) { struct rb_node *n; - if (self == NULL) + if (symbols == NULL) return NULL; - n = self->rb_node; + n = symbols->rb_node; while (n) { struct symbol_name_rb_node *s; @@ -325,128 +405,222 @@ static struct symbol *symbols__find_by_name(struct rb_root *self, const char *na return NULL; } -struct symbol *dso__find_symbol(struct dso *self, +struct symbol *dso__find_symbol(struct dso *dso, enum map_type type, u64 addr) { - return symbols__find(&self->symbols[type], addr); -} - -struct symbol *dso__find_symbol_by_name(struct dso *self, enum map_type type, - const char *name) -{ - return symbols__find_by_name(&self->symbol_names[type], name); + return symbols__find(&dso->symbols[type], addr); } -void dso__sort_by_name(struct dso *self, enum map_type type) +static struct symbol *dso__first_symbol(struct dso *dso, enum map_type type) { - dso__set_sorted_by_name(self, type); - return symbols__sort_by_name(&self->symbol_names[type], - &self->symbols[type]); + return symbols__first(&dso->symbols[type]); } -int build_id__sprintf(u8 *self, int len, char *bf) +struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type, + const char *name) { - char *bid = bf; - u8 *raw = self; - int i; - - for (i = 0; i < len; ++i) { - sprintf(bid, "%02x", *raw); - ++raw; - bid += 2; - } - - return raw - self; + return symbols__find_by_name(&dso->symbol_names[type], name); } -size_t dso__fprintf_buildid(struct dso *self, FILE *fp) +void dso__sort_by_name(struct dso *dso, enum map_type type) { - char sbuild_id[BUILD_ID_SIZE * 2 + 1]; - - build_id__sprintf(self->build_id, sizeof(self->build_id), sbuild_id); - return fprintf(fp, "%s", sbuild_id); + dso__set_sorted_by_name(dso, type); + return symbols__sort_by_name(&dso->symbol_names[type], + &dso->symbols[type]); } -size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp) +size_t dso__fprintf_symbols_by_name(struct dso *dso, + enum map_type type, FILE *fp) { + size_t ret = 0; struct rb_node *nd; - size_t ret = fprintf(fp, "dso: %s (", self->short_name); + struct symbol_name_rb_node *pos; - ret += dso__fprintf_buildid(self, fp); - ret += fprintf(fp, ")\n"); - for (nd = rb_first(&self->symbols[type]); nd; nd = rb_next(nd)) { - struct symbol *pos = rb_entry(nd, struct symbol, rb_node); - ret += symbol__fprintf(pos, fp); + for (nd = rb_first(&dso->symbol_names[type]); nd; nd = rb_next(nd)) { + pos = rb_entry(nd, struct symbol_name_rb_node, rb_node); + fprintf(fp, "%s\n", pos->sym.name); } return ret; } -/* - * Loads the function entries in /proc/kallsyms into kernel_map->dso, - * 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) +int modules__parse(const char *filename, void *arg, + int (*process_module)(void *arg, const char *name, + u64 start)) { char *line = NULL; size_t n; - struct rb_root *root = &self->symbols[map->type]; - FILE *file = fopen("/proc/kallsyms", "r"); + FILE *file; + int err = 0; + file = fopen(filename, "r"); if (file == NULL) - goto out_failure; + return -1; - while (!feof(file)) { + while (1) { + char name[PATH_MAX]; u64 start; - struct symbol *sym; - int line_len, len; - char symbol_type; - char *symbol_name; + char *sep; + ssize_t line_len; line_len = getline(&line, &n, file); - if (line_len < 0) - break; + if (line_len < 0) { + if (feof(file)) + break; + err = -1; + goto out; + } - if (!line) - goto out_failure; + if (!line) { + err = -1; + goto out; + } line[--line_len] = '\0'; /* \n */ - len = hex2u64(line, &start); - - len++; - if (len + 2 >= line_len) + sep = strrchr(line, 'x'); + if (sep == NULL) continue; - symbol_type = toupper(line[len]); - if (!symbol_type__is_a(symbol_type, map->type)) + hex2u64(sep + 1, &start); + + sep = strchr(line, ' '); + if (sep == NULL) continue; - symbol_name = line + len + 2; - /* - * Will fix up the end later, when we have all symbols sorted. - */ - sym = symbol__new(start, 0, symbol_name); + *sep = '\0'; - if (sym == NULL) - goto out_delete_line; - /* - * We will pass the symbols to the filter later, in - * map__split_kallsyms, when we have split the maps per module - */ - symbols__insert(root, sym); - } + scnprintf(name, sizeof(name), "[%s]", line); + err = process_module(arg, name, start); + if (err) + break; + } +out: free(line); fclose(file); + return err; +} + +struct process_kallsyms_args { + struct map *map; + struct dso *dso; +}; + +bool symbol__is_idle(struct symbol *sym) +{ + const char * const idle_symbols[] = { + "cpu_idle", + "intel_idle", + "default_idle", + "native_safe_halt", + "enter_idle", + "exit_idle", + "mwait_idle", + "mwait_idle_with_hints", + "poll_idle", + "ppc64_runlatch_off", + "pseries_dedicated_idle_sleep", + NULL + }; + + int i; + + if (!sym) + return false; + + for (i = 0; idle_symbols[i]; i++) { + if (!strcmp(idle_symbols[i], sym->name)) + return true; + } + + return false; +} + +static int map__process_kallsym_symbol(void *arg, const char *name, + char type, u64 start) +{ + struct symbol *sym; + struct process_kallsyms_args *a = arg; + struct rb_root *root = &a->dso->symbols[a->map->type]; + + if (!symbol_type__is_a(type, a->map->type)) + return 0; + + /* + * module symbols are not sorted so we add all + * symbols, setting length to 0, and rely on + * symbols__fixup_end() to fix it up. + */ + sym = symbol__new(start, 0, kallsyms2elf_type(type), name); + if (sym == NULL) + return -ENOMEM; + /* + * We will pass the symbols to the filter later, in + * map__split_kallsyms, when we have split the maps per module + */ + symbols__insert(root, sym); return 0; +} -out_delete_line: - free(line); -out_failure: - return -1; +/* + * Loads the function entries in /proc/kallsyms into kernel_map->dso, + * 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 *dso, const char *filename, + struct map *map) +{ + struct process_kallsyms_args args = { .map = map, .dso = dso, }; + return kallsyms__parse(filename, &args, map__process_kallsym_symbol); +} + +static int dso__split_kallsyms_for_kcore(struct dso *dso, struct map *map, + symbol_filter_t filter) +{ + struct map_groups *kmaps = map__kmap(map)->kmaps; + struct map *curr_map; + struct symbol *pos; + int count = 0, moved = 0; + struct rb_root *root = &dso->symbols[map->type]; + struct rb_node *next = rb_first(root); + + while (next) { + char *module; + + pos = rb_entry(next, struct symbol, rb_node); + next = rb_next(&pos->rb_node); + + module = strchr(pos->name, '\t'); + if (module) + *module = '\0'; + + curr_map = map_groups__find(kmaps, map->type, pos->start); + + if (!curr_map || (filter && filter(curr_map, pos))) { + rb_erase(&pos->rb_node, root); + symbol__delete(pos); + } else { + pos->start -= curr_map->start - curr_map->pgoff; + if (pos->end) + pos->end -= curr_map->start - curr_map->pgoff; + if (curr_map != map) { + rb_erase(&pos->rb_node, root); + symbols__insert( + &curr_map->dso->symbols[curr_map->type], + pos); + ++moved; + } else { + ++count; + } + } + } + + /* Symbols have been adjusted */ + dso->adjust_symbols = 1; + + return count + moved; } /* @@ -454,13 +628,15 @@ out_failure: * kernel range is broken in several maps, named [kernel].N, as we don't have * the original ELF section names vmlinux have. */ -static int dso__split_kallsyms(struct dso *self, struct map *map, - struct perf_session *session, symbol_filter_t filter) +static int dso__split_kallsyms(struct dso *dso, struct map *map, u64 delta, + symbol_filter_t filter) { + struct map_groups *kmaps = map__kmap(map)->kmaps; + struct machine *machine = kmaps->machine; struct map *curr_map = map; struct symbol *pos; - int count = 0; - struct rb_root *root = &self->symbols[map->type]; + int count = 0, moved = 0; + struct rb_root *root = &dso->symbols[map->type]; struct rb_node *next = rb_first(root); int kernel_range = 0; @@ -477,13 +653,35 @@ static int dso__split_kallsyms(struct dso *self, struct map *map, *module++ = '\0'; - if (strcmp(self->name, module)) { - curr_map = map_groups__find_by_name(&session->kmaps, map->type, module); + if (strcmp(curr_map->dso->short_name, module)) { + if (curr_map != map && + dso->kernel == DSO_TYPE_GUEST_KERNEL && + machine__is_default_guest(machine)) { + /* + * We assume all symbols of a module are + * continuous in * kallsyms, so curr_map + * points to a module and all its + * symbols are in its kmap. Mark it as + * loaded. + */ + dso__set_loaded(curr_map->dso, + curr_map->type); + } + + curr_map = map_groups__find_by_name(kmaps, + map->type, module); if (curr_map == NULL) { - pr_debug("/proc/{kallsyms,modules} " - "inconsistency!\n"); - return -1; + pr_debug("%s/proc/{kallsyms,modules} " + "inconsistency while looking " + "for \"%s\" module!\n", + machine->root_dir, module); + curr_map = map; + goto discard_symbol; } + + if (curr_map->dso->loaded && + !machine__is_default_guest(machine)) + goto discard_symbol; } /* * So that we look just like we get from .ko files, @@ -493,26 +691,49 @@ static int dso__split_kallsyms(struct dso *self, struct map *map, pos->end = curr_map->map_ip(curr_map, pos->end); } else if (curr_map != map) { char dso_name[PATH_MAX]; - struct dso *dso; + struct dso *ndso; - snprintf(dso_name, sizeof(dso_name), "[kernel].%d", - kernel_range++); + if (delta) { + /* Kernel was relocated at boot time */ + pos->start -= delta; + pos->end -= delta; + } - dso = dso__new(dso_name); - if (dso == NULL) + if (count == 0) { + curr_map = map; + goto filter_symbol; + } + + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + snprintf(dso_name, sizeof(dso_name), + "[guest.kernel].%d", + kernel_range++); + else + snprintf(dso_name, sizeof(dso_name), + "[kernel].%d", + kernel_range++); + + ndso = dso__new(dso_name); + if (ndso == NULL) return -1; - curr_map = map__new2(pos->start, dso, map->type); - if (map == NULL) { - dso__delete(dso); + ndso->kernel = dso->kernel; + + curr_map = map__new2(pos->start, ndso, map->type); + if (curr_map == NULL) { + dso__delete(ndso); return -1; } curr_map->map_ip = curr_map->unmap_ip = identity__map_ip; - map_groups__insert(&session->kmaps, curr_map); + map_groups__insert(kmaps, curr_map); ++kernel_range; + } else if (delta) { + /* Kernel was relocated at boot time */ + pos->start -= delta; + pos->end -= delta; } - +filter_symbol: if (filter && filter(curr_map, pos)) { discard_symbol: rb_erase(&pos->rb_node, root); symbol__delete(pos); @@ -520,905 +741,471 @@ discard_symbol: rb_erase(&pos->rb_node, root); if (curr_map != map) { rb_erase(&pos->rb_node, root); symbols__insert(&curr_map->dso->symbols[curr_map->type], pos); - } - count++; + ++moved; + } else + ++count; } } - return count; -} - - -static int dso__load_kallsyms(struct dso *self, struct map *map, - struct perf_session *session, symbol_filter_t filter) -{ - if (dso__load_all_kallsyms(self, map) < 0) - return -1; - - symbols__fixup_end(&self->symbols[map->type]); - self->origin = DSO__ORIG_KERNEL; + if (curr_map != map && + dso->kernel == DSO_TYPE_GUEST_KERNEL && + machine__is_default_guest(kmaps->machine)) { + dso__set_loaded(curr_map->dso, curr_map->type); + } - return dso__split_kallsyms(self, map, session, filter); + return count + moved; } -static int dso__load_perf_map(struct dso *self, struct map *map, - symbol_filter_t filter) +bool symbol__restricted_filename(const char *filename, + const char *restricted_filename) { - char *line = NULL; - size_t n; - FILE *file; - int nr_syms = 0; - - file = fopen(self->long_name, "r"); - if (file == NULL) - goto out_failure; + bool restricted = false; - while (!feof(file)) { - u64 start, size; - struct symbol *sym; - int line_len, len; + if (symbol_conf.kptr_restrict) { + char *r = realpath(filename, NULL); - line_len = getline(&line, &n, file); - if (line_len < 0) - break; - - if (!line) - goto out_failure; - - line[--line_len] = '\0'; /* \n */ - - len = hex2u64(line, &start); - - len++; - if (len + 2 >= line_len) - continue; - - len += hex2u64(line + len, &size); - - len++; - if (len + 2 >= line_len) - continue; - - sym = symbol__new(start, size, line + len); - - if (sym == NULL) - goto out_delete_line; - - if (filter && filter(map, sym)) - symbol__delete(sym); - else { - symbols__insert(&self->symbols[map->type], sym); - nr_syms++; + if (r != NULL) { + restricted = strcmp(r, restricted_filename) == 0; + free(r); + return restricted; } } - free(line); - fclose(file); - - return nr_syms; - -out_delete_line: - free(line); -out_failure: - return -1; -} - -/** - * elf_symtab__for_each_symbol - iterate thru all the symbols - * - * @self: struct elf_symtab instance to iterate - * @idx: uint32_t idx - * @sym: GElf_Sym iterator - */ -#define elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) \ - for (idx = 0, gelf_getsym(syms, idx, &sym);\ - idx < nr_syms; \ - idx++, gelf_getsym(syms, idx, &sym)) - -static inline uint8_t elf_sym__type(const GElf_Sym *sym) -{ - return GELF_ST_TYPE(sym->st_info); + return restricted; } -static inline int elf_sym__is_function(const GElf_Sym *sym) -{ - return elf_sym__type(sym) == STT_FUNC && - sym->st_name != 0 && - sym->st_shndx != SHN_UNDEF; -} - -static inline bool elf_sym__is_object(const GElf_Sym *sym) -{ - return elf_sym__type(sym) == STT_OBJECT && - sym->st_name != 0 && - sym->st_shndx != SHN_UNDEF; -} - -static inline int elf_sym__is_label(const GElf_Sym *sym) -{ - return elf_sym__type(sym) == STT_NOTYPE && - sym->st_name != 0 && - sym->st_shndx != SHN_UNDEF && - sym->st_shndx != SHN_ABS; -} +struct module_info { + struct rb_node rb_node; + char *name; + u64 start; +}; -static inline const char *elf_sec__name(const GElf_Shdr *shdr, - const Elf_Data *secstrs) +static void add_module(struct module_info *mi, struct rb_root *modules) { - return secstrs->d_buf + shdr->sh_name; -} + struct rb_node **p = &modules->rb_node; + struct rb_node *parent = NULL; + struct module_info *m; -static inline int elf_sec__is_text(const GElf_Shdr *shdr, - const Elf_Data *secstrs) -{ - return strstr(elf_sec__name(shdr, secstrs), "text") != NULL; + while (*p != NULL) { + parent = *p; + m = rb_entry(parent, struct module_info, rb_node); + if (strcmp(mi->name, m->name) < 0) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + rb_link_node(&mi->rb_node, parent, p); + rb_insert_color(&mi->rb_node, modules); } -static inline bool elf_sec__is_data(const GElf_Shdr *shdr, - const Elf_Data *secstrs) +static void delete_modules(struct rb_root *modules) { - return strstr(elf_sec__name(shdr, secstrs), "data") != NULL; -} + struct module_info *mi; + struct rb_node *next = rb_first(modules); -static inline const char *elf_sym__name(const GElf_Sym *sym, - const Elf_Data *symstrs) -{ - return symstrs->d_buf + sym->st_name; + while (next) { + mi = rb_entry(next, struct module_info, rb_node); + next = rb_next(&mi->rb_node); + rb_erase(&mi->rb_node, modules); + zfree(&mi->name); + free(mi); + } } -static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, - GElf_Shdr *shp, const char *name, - size_t *idx) +static struct module_info *find_module(const char *name, + struct rb_root *modules) { - Elf_Scn *sec = NULL; - size_t cnt = 1; + struct rb_node *n = modules->rb_node; - while ((sec = elf_nextscn(elf, sec)) != NULL) { - char *str; + while (n) { + struct module_info *m; + int cmp; - gelf_getshdr(sec, shp); - str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); - if (!strcmp(name, str)) { - if (idx) - *idx = cnt; - break; - } - ++cnt; + m = rb_entry(n, struct module_info, rb_node); + cmp = strcmp(name, m->name); + if (cmp < 0) + n = n->rb_left; + else if (cmp > 0) + n = n->rb_right; + else + return m; } - return sec; + return NULL; } -#define elf_section__for_each_rel(reldata, pos, pos_mem, idx, nr_entries) \ - for (idx = 0, pos = gelf_getrel(reldata, 0, &pos_mem); \ - idx < nr_entries; \ - ++idx, pos = gelf_getrel(reldata, idx, &pos_mem)) - -#define elf_section__for_each_rela(reldata, pos, pos_mem, idx, nr_entries) \ - for (idx = 0, pos = gelf_getrela(reldata, 0, &pos_mem); \ - idx < nr_entries; \ - ++idx, pos = gelf_getrela(reldata, idx, &pos_mem)) - -/* - * We need to check if we have a .dynsym, so that we can handle the - * .plt, synthesizing its symbols, that aren't on the symtabs (be it - * .dynsym or .symtab). - * And always look at the original dso, not at debuginfo packages, that - * have the PLT data stripped out (shdr_rel_plt.sh_type == SHT_NOBITS). - */ -static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, - symbol_filter_t filter) +static int __read_proc_modules(void *arg, const char *name, u64 start) { - uint32_t nr_rel_entries, idx; - GElf_Sym sym; - u64 plt_offset; - GElf_Shdr shdr_plt; - struct symbol *f; - GElf_Shdr shdr_rel_plt, shdr_dynsym; - Elf_Data *reldata, *syms, *symstrs; - Elf_Scn *scn_plt_rel, *scn_symstrs, *scn_dynsym; - size_t dynsym_idx; - GElf_Ehdr ehdr; - char sympltname[1024]; - Elf *elf; - int nr = 0, symidx, fd, err = 0; - - fd = open(self->long_name, O_RDONLY); - if (fd < 0) - goto out; - - elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); - if (elf == NULL) - goto out_close; - - if (gelf_getehdr(elf, &ehdr) == NULL) - goto out_elf_end; - - scn_dynsym = elf_section_by_name(elf, &ehdr, &shdr_dynsym, - ".dynsym", &dynsym_idx); - if (scn_dynsym == NULL) - goto out_elf_end; - - scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt, - ".rela.plt", NULL); - if (scn_plt_rel == NULL) { - scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt, - ".rel.plt", NULL); - if (scn_plt_rel == NULL) - goto out_elf_end; - } - - err = -1; + struct rb_root *modules = arg; + struct module_info *mi; - if (shdr_rel_plt.sh_link != dynsym_idx) - goto out_elf_end; + mi = zalloc(sizeof(struct module_info)); + if (!mi) + return -ENOMEM; - if (elf_section_by_name(elf, &ehdr, &shdr_plt, ".plt", NULL) == NULL) - goto out_elf_end; + mi->name = strdup(name); + mi->start = start; - /* - * Fetch the relocation section to find the idxes to the GOT - * and the symbols in the .dynsym they refer to. - */ - reldata = elf_getdata(scn_plt_rel, NULL); - if (reldata == NULL) - goto out_elf_end; - - syms = elf_getdata(scn_dynsym, NULL); - if (syms == NULL) - goto out_elf_end; - - scn_symstrs = elf_getscn(elf, shdr_dynsym.sh_link); - if (scn_symstrs == NULL) - goto out_elf_end; - - symstrs = elf_getdata(scn_symstrs, NULL); - if (symstrs == NULL) - goto out_elf_end; - - nr_rel_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize; - plt_offset = shdr_plt.sh_offset; - - if (shdr_rel_plt.sh_type == SHT_RELA) { - GElf_Rela pos_mem, *pos; - - elf_section__for_each_rela(reldata, pos, pos_mem, idx, - nr_rel_entries) { - symidx = GELF_R_SYM(pos->r_info); - plt_offset += shdr_plt.sh_entsize; - gelf_getsym(syms, symidx, &sym); - snprintf(sympltname, sizeof(sympltname), - "%s@plt", elf_sym__name(&sym, symstrs)); - - f = symbol__new(plt_offset, shdr_plt.sh_entsize, - sympltname); - if (!f) - goto out_elf_end; - - if (filter && filter(map, f)) - symbol__delete(f); - else { - symbols__insert(&self->symbols[map->type], f); - ++nr; - } - } - } else if (shdr_rel_plt.sh_type == SHT_REL) { - GElf_Rel pos_mem, *pos; - elf_section__for_each_rel(reldata, pos, pos_mem, idx, - nr_rel_entries) { - symidx = GELF_R_SYM(pos->r_info); - plt_offset += shdr_plt.sh_entsize; - gelf_getsym(syms, symidx, &sym); - snprintf(sympltname, sizeof(sympltname), - "%s@plt", elf_sym__name(&sym, symstrs)); - - f = symbol__new(plt_offset, shdr_plt.sh_entsize, - sympltname); - if (!f) - goto out_elf_end; - - if (filter && filter(map, f)) - symbol__delete(f); - else { - symbols__insert(&self->symbols[map->type], f); - ++nr; - } - } + if (!mi->name) { + free(mi); + return -ENOMEM; } - err = 0; -out_elf_end: - elf_end(elf); -out_close: - close(fd); + add_module(mi, modules); - if (err == 0) - return nr; -out: - pr_warning("%s: problems reading %s PLT info.\n", - __func__, self->long_name); return 0; } -static bool elf_sym__is_a(GElf_Sym *self, enum map_type type) +static int read_proc_modules(const char *filename, struct rb_root *modules) { - switch (type) { - case MAP__FUNCTION: - return elf_sym__is_function(self); - case MAP__VARIABLE: - return elf_sym__is_object(self); - default: - return false; - } -} + if (symbol__restricted_filename(filename, "/proc/modules")) + return -1; -static bool elf_sec__is_a(GElf_Shdr *self, Elf_Data *secstrs, enum map_type type) -{ - switch (type) { - case MAP__FUNCTION: - return elf_sec__is_text(self, secstrs); - case MAP__VARIABLE: - return elf_sec__is_data(self, secstrs); - default: - return false; + if (modules__parse(filename, modules, __read_proc_modules)) { + delete_modules(modules); + return -1; } + + return 0; } -static int dso__load_sym(struct dso *self, struct map *map, - struct perf_session *session, const char *name, int fd, - symbol_filter_t filter, int kernel, int kmodule) +int compare_proc_modules(const char *from, const char *to) { - struct map *curr_map = map; - struct dso *curr_dso = self; - size_t dso_name_len = strlen(self->short_name); - Elf_Data *symstrs, *secstrs; - uint32_t nr_syms; - int err = -1; - uint32_t idx; - GElf_Ehdr ehdr; - GElf_Shdr shdr; - Elf_Data *syms; - GElf_Sym sym; - Elf_Scn *sec, *sec_strndx; - Elf *elf; - int nr = 0; - - elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); - if (elf == NULL) { - pr_err("%s: cannot read %s ELF file.\n", __func__, name); - goto out_close; - } - - if (gelf_getehdr(elf, &ehdr) == NULL) { - pr_err("%s: cannot get elf header.\n", __func__); - goto out_elf_end; - } + struct rb_root from_modules = RB_ROOT; + struct rb_root to_modules = RB_ROOT; + struct rb_node *from_node, *to_node; + struct module_info *from_m, *to_m; + int ret = -1; - sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL); - if (sec == NULL) { - sec = elf_section_by_name(elf, &ehdr, &shdr, ".dynsym", NULL); - if (sec == NULL) - goto out_elf_end; - } + if (read_proc_modules(from, &from_modules)) + return -1; - syms = elf_getdata(sec, NULL); - if (syms == NULL) - goto out_elf_end; + if (read_proc_modules(to, &to_modules)) + goto out_delete_from; - sec = elf_getscn(elf, shdr.sh_link); - if (sec == NULL) - goto out_elf_end; + from_node = rb_first(&from_modules); + to_node = rb_first(&to_modules); + while (from_node) { + if (!to_node) + break; - symstrs = elf_getdata(sec, NULL); - if (symstrs == NULL) - goto out_elf_end; + from_m = rb_entry(from_node, struct module_info, rb_node); + to_m = rb_entry(to_node, struct module_info, rb_node); - sec_strndx = elf_getscn(elf, ehdr.e_shstrndx); - if (sec_strndx == NULL) - goto out_elf_end; + if (from_m->start != to_m->start || + strcmp(from_m->name, to_m->name)) + break; - secstrs = elf_getdata(sec_strndx, NULL); - if (secstrs == NULL) - goto out_elf_end; + from_node = rb_next(from_node); + to_node = rb_next(to_node); + } - nr_syms = shdr.sh_size / shdr.sh_entsize; + if (!from_node && !to_node) + ret = 0; - memset(&sym, 0, sizeof(sym)); - if (!kernel) { - self->adjust_symbols = (ehdr.e_type == ET_EXEC || - elf_section_by_name(elf, &ehdr, &shdr, - ".gnu.prelink_undo", - NULL) != NULL); - } else self->adjust_symbols = 0; + delete_modules(&to_modules); +out_delete_from: + delete_modules(&from_modules); - elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) { - struct symbol *f; - const char *elf_name; - char *demangled = NULL; - int is_label = elf_sym__is_label(&sym); - const char *section_name; + return ret; +} - if (!is_label && !elf_sym__is_a(&sym, map->type)) - continue; +static int do_validate_kcore_modules(const char *filename, struct map *map, + struct map_groups *kmaps) +{ + struct rb_root modules = RB_ROOT; + struct map *old_map; + int err; - sec = elf_getscn(elf, sym.st_shndx); - if (!sec) - goto out_elf_end; + err = read_proc_modules(filename, &modules); + if (err) + return err; - gelf_getshdr(sec, &shdr); + old_map = map_groups__first(kmaps, map->type); + while (old_map) { + struct map *next = map_groups__next(old_map); + struct module_info *mi; - if (is_label && !elf_sec__is_a(&shdr, secstrs, map->type)) + if (old_map == map || old_map->start == map->start) { + /* The kernel map */ + old_map = next; continue; - - elf_name = elf_sym__name(&sym, symstrs); - section_name = elf_sec__name(&shdr, secstrs); - - if (kernel || kmodule) { - char dso_name[PATH_MAX]; - - if (strcmp(section_name, - curr_dso->short_name + dso_name_len) == 0) - goto new_symbol; - - if (strcmp(section_name, ".text") == 0) { - curr_map = map; - curr_dso = self; - goto new_symbol; - } - - snprintf(dso_name, sizeof(dso_name), - "%s%s", self->short_name, section_name); - - curr_map = map_groups__find_by_name(&session->kmaps, map->type, dso_name); - if (curr_map == NULL) { - u64 start = sym.st_value; - - if (kmodule) - start += map->start + shdr.sh_offset; - - curr_dso = dso__new(dso_name); - if (curr_dso == NULL) - goto out_elf_end; - curr_map = map__new2(start, curr_dso, - MAP__FUNCTION); - if (curr_map == NULL) { - dso__delete(curr_dso); - goto out_elf_end; - } - curr_map->map_ip = identity__map_ip; - curr_map->unmap_ip = identity__map_ip; - curr_dso->origin = DSO__ORIG_KERNEL; - map_groups__insert(&session->kmaps, curr_map); - dsos__add(&dsos__kernel, curr_dso); - } else - curr_dso = curr_map->dso; - - goto new_symbol; } - if (curr_dso->adjust_symbols) { - pr_debug2("adjusting symbol: st_value: %Lx sh_addr: " - "%Lx sh_offset: %Lx\n", (u64)sym.st_value, - (u64)shdr.sh_addr, (u64)shdr.sh_offset); - sym.st_value -= shdr.sh_addr - shdr.sh_offset; - } - /* - * We need to figure out if the object was created from C++ sources - * DWARF DW_compile_unit has this, but we don't always have access - * to it... - */ - demangled = bfd_demangle(NULL, elf_name, DMGL_PARAMS | DMGL_ANSI); - if (demangled != NULL) - elf_name = demangled; -new_symbol: - f = symbol__new(sym.st_value, sym.st_size, elf_name); - free(demangled); - if (!f) - goto out_elf_end; - - if (filter && filter(curr_map, f)) - symbol__delete(f); - else { - symbols__insert(&curr_dso->symbols[curr_map->type], f); - nr++; + /* Module must be in memory at the same address */ + mi = find_module(old_map->dso->short_name, &modules); + if (!mi || mi->start != old_map->start) { + err = -EINVAL; + goto out; } - } - /* - * For misannotated, zeroed, ASM function sizes. - */ - if (nr > 0) - symbols__fixup_end(&self->symbols[map->type]); - err = nr; -out_elf_end: - elf_end(elf); -out_close: + old_map = next; + } +out: + delete_modules(&modules); return err; } -static bool dso__build_id_equal(const struct dso *self, u8 *build_id) +/* + * If kallsyms is referenced by name then we look for filename in the same + * directory. + */ +static bool filename_from_kallsyms_filename(char *filename, + const char *base_name, + const char *kallsyms_filename) { - return memcmp(self->build_id, build_id, sizeof(self->build_id)) == 0; -} + char *name; -static bool __dsos__read_build_ids(struct list_head *head) -{ - bool have_build_id = false; - struct dso *pos; - - list_for_each_entry(pos, head, node) - if (filename__read_build_id(pos->long_name, pos->build_id, - sizeof(pos->build_id)) > 0) { - have_build_id = true; - pos->has_build_id = true; - } + strcpy(filename, kallsyms_filename); + name = strrchr(filename, '/'); + if (!name) + return false; + + name += 1; + + if (!strcmp(name, "kallsyms")) { + strcpy(name, base_name); + return true; + } - return have_build_id; + return false; } -bool dsos__read_build_ids(void) +static int validate_kcore_modules(const char *kallsyms_filename, + struct map *map) { - bool kbuildids = __dsos__read_build_ids(&dsos__kernel), - ubuildids = __dsos__read_build_ids(&dsos__user); - return kbuildids || ubuildids; -} + struct map_groups *kmaps = map__kmap(map)->kmaps; + char modules_filename[PATH_MAX]; -/* - * Align offset to 4 bytes as needed for note name and descriptor data. - */ -#define NOTE_ALIGN(n) (((n) + 3) & -4U) + if (!filename_from_kallsyms_filename(modules_filename, "modules", + kallsyms_filename)) + return -EINVAL; -int filename__read_build_id(const char *filename, void *bf, size_t size) -{ - int fd, err = -1; - GElf_Ehdr ehdr; - GElf_Shdr shdr; - Elf_Data *data; - Elf_Scn *sec; - Elf_Kind ek; - void *ptr; - Elf *elf; - - if (size < BUILD_ID_SIZE) - goto out; - - fd = open(filename, O_RDONLY); - if (fd < 0) - goto out; + if (do_validate_kcore_modules(modules_filename, map, kmaps)) + return -EINVAL; - elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); - if (elf == NULL) { - pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); - goto out_close; - } + return 0; +} - ek = elf_kind(elf); - if (ek != ELF_K_ELF) - goto out_elf_end; +static int validate_kcore_addresses(const char *kallsyms_filename, + struct map *map) +{ + struct kmap *kmap = map__kmap(map); - if (gelf_getehdr(elf, &ehdr) == NULL) { - pr_err("%s: cannot get elf header.\n", __func__); - goto out_elf_end; - } + if (kmap->ref_reloc_sym && kmap->ref_reloc_sym->name) { + u64 start; - sec = elf_section_by_name(elf, &ehdr, &shdr, - ".note.gnu.build-id", NULL); - if (sec == NULL) { - sec = elf_section_by_name(elf, &ehdr, &shdr, - ".notes", NULL); - if (sec == NULL) - goto out_elf_end; + start = kallsyms__get_function_start(kallsyms_filename, + kmap->ref_reloc_sym->name); + if (start != kmap->ref_reloc_sym->addr) + return -EINVAL; } - data = elf_getdata(sec, NULL); - if (data == NULL) - goto out_elf_end; - - ptr = data->d_buf; - while (ptr < (data->d_buf + data->d_size)) { - GElf_Nhdr *nhdr = ptr; - int namesz = NOTE_ALIGN(nhdr->n_namesz), - descsz = NOTE_ALIGN(nhdr->n_descsz); - const char *name; - - ptr += sizeof(*nhdr); - name = ptr; - ptr += namesz; - if (nhdr->n_type == NT_GNU_BUILD_ID && - nhdr->n_namesz == sizeof("GNU")) { - if (memcmp(name, "GNU", sizeof("GNU")) == 0) { - memcpy(bf, ptr, BUILD_ID_SIZE); - err = BUILD_ID_SIZE; - break; - } - } - ptr += descsz; - } -out_elf_end: - elf_end(elf); -out_close: - close(fd); -out: - return err; + return validate_kcore_modules(kallsyms_filename, map); } -int sysfs__read_build_id(const char *filename, void *build_id, size_t size) +struct kcore_mapfn_data { + struct dso *dso; + enum map_type type; + struct list_head maps; +}; + +static int kcore_mapfn(u64 start, u64 len, u64 pgoff, void *data) { - int fd, err = -1; + struct kcore_mapfn_data *md = data; + struct map *map; - if (size < BUILD_ID_SIZE) - goto out; + map = map__new2(start, md->dso, md->type); + if (map == NULL) + return -ENOMEM; - fd = open(filename, O_RDONLY); - if (fd < 0) - goto out; + map->end = map->start + len; + map->pgoff = pgoff; - while (1) { - char bf[BUFSIZ]; - GElf_Nhdr nhdr; - int namesz, descsz; - - if (read(fd, &nhdr, sizeof(nhdr)) != sizeof(nhdr)) - break; + list_add(&map->node, &md->maps); - namesz = NOTE_ALIGN(nhdr.n_namesz); - descsz = NOTE_ALIGN(nhdr.n_descsz); - if (nhdr.n_type == NT_GNU_BUILD_ID && - nhdr.n_namesz == sizeof("GNU")) { - if (read(fd, bf, namesz) != namesz) - break; - if (memcmp(bf, "GNU", sizeof("GNU")) == 0) { - if (read(fd, build_id, - BUILD_ID_SIZE) == BUILD_ID_SIZE) { - err = 0; - break; - } - } else if (read(fd, bf, descsz) != descsz) - break; - } else { - int n = namesz + descsz; - if (read(fd, bf, n) != n) - break; - } - } - close(fd); -out: - return err; + return 0; } -char dso__symtab_origin(const struct dso *self) +static int dso__load_kcore(struct dso *dso, struct map *map, + const char *kallsyms_filename) { - static const char origin[] = { - [DSO__ORIG_KERNEL] = 'k', - [DSO__ORIG_JAVA_JIT] = 'j', - [DSO__ORIG_FEDORA] = 'f', - [DSO__ORIG_UBUNTU] = 'u', - [DSO__ORIG_BUILDID] = 'b', - [DSO__ORIG_DSO] = 'd', - [DSO__ORIG_KMODULE] = 'K', - }; + struct map_groups *kmaps = map__kmap(map)->kmaps; + struct machine *machine = kmaps->machine; + struct kcore_mapfn_data md; + struct map *old_map, *new_map, *replacement_map = NULL; + bool is_64_bit; + int err, fd; + char kcore_filename[PATH_MAX]; + struct symbol *sym; - if (self == NULL || self->origin == DSO__ORIG_NOT_FOUND) - return '!'; - return origin[self->origin]; -} + /* This function requires that the map is the kernel map */ + if (map != machine->vmlinux_maps[map->type]) + return -EINVAL; -int dso__load(struct dso *self, struct map *map, struct perf_session *session, - symbol_filter_t filter) -{ - int size = PATH_MAX; - char *name; - u8 build_id[BUILD_ID_SIZE]; - int ret = -1; - int fd; + if (!filename_from_kallsyms_filename(kcore_filename, "kcore", + kallsyms_filename)) + return -EINVAL; - dso__set_loaded(self, map->type); + /* Modules and kernel must be present at their original addresses */ + if (validate_kcore_addresses(kallsyms_filename, map)) + return -EINVAL; - if (self->kernel) - return dso__load_kernel_sym(self, map, session, filter); + md.dso = dso; + md.type = map->type; + INIT_LIST_HEAD(&md.maps); - name = malloc(size); - if (!name) - return -1; + fd = open(kcore_filename, O_RDONLY); + if (fd < 0) + return -EINVAL; - self->adjust_symbols = 0; + /* Read new maps into temporary lists */ + err = file__read_maps(fd, md.type == MAP__FUNCTION, kcore_mapfn, &md, + &is_64_bit); + if (err) + goto out_err; - if (strncmp(self->name, "/tmp/perf-", 10) == 0) { - ret = dso__load_perf_map(self, map, filter); - self->origin = ret > 0 ? DSO__ORIG_JAVA_JIT : - DSO__ORIG_NOT_FOUND; - return ret; + if (list_empty(&md.maps)) { + err = -EINVAL; + goto out_err; } - self->origin = DSO__ORIG_FEDORA - 1; + /* Remove old maps */ + old_map = map_groups__first(kmaps, map->type); + while (old_map) { + struct map *next = map_groups__next(old_map); -more: - do { - self->origin++; - switch (self->origin) { - case DSO__ORIG_FEDORA: - snprintf(name, size, "/usr/lib/debug%s.debug", - self->long_name); - break; - case DSO__ORIG_UBUNTU: - snprintf(name, size, "/usr/lib/debug%s", - self->long_name); - break; - case DSO__ORIG_BUILDID: - if (filename__read_build_id(self->long_name, build_id, - sizeof(build_id))) { - char build_id_hex[BUILD_ID_SIZE * 2 + 1]; - - build_id__sprintf(build_id, sizeof(build_id), - build_id_hex); - snprintf(name, size, - "/usr/lib/debug/.build-id/%.2s/%s.debug", - build_id_hex, build_id_hex + 2); - if (self->has_build_id) - goto compare_build_id; - break; - } - self->origin++; - /* Fall thru */ - case DSO__ORIG_DSO: - snprintf(name, size, "%s", self->long_name); - break; + if (old_map != map) + map_groups__remove(kmaps, old_map); + old_map = next; + } - default: - goto out; + /* Find the kernel map using the first symbol */ + sym = dso__first_symbol(dso, map->type); + list_for_each_entry(new_map, &md.maps, node) { + if (sym && sym->start >= new_map->start && + sym->start < new_map->end) { + replacement_map = new_map; + break; } + } - if (self->has_build_id) { - if (filename__read_build_id(name, build_id, - sizeof(build_id)) < 0) - goto more; -compare_build_id: - if (!dso__build_id_equal(self, build_id)) - goto more; + if (!replacement_map) + replacement_map = list_entry(md.maps.next, struct map, node); + + /* Add new maps */ + while (!list_empty(&md.maps)) { + new_map = list_entry(md.maps.next, struct map, node); + list_del(&new_map->node); + if (new_map == replacement_map) { + map->start = new_map->start; + map->end = new_map->end; + map->pgoff = new_map->pgoff; + map->map_ip = new_map->map_ip; + map->unmap_ip = new_map->unmap_ip; + map__delete(new_map); + /* Ensure maps are correctly ordered */ + map_groups__remove(kmaps, map); + map_groups__insert(kmaps, map); + } else { + map_groups__insert(kmaps, new_map); } - - fd = open(name, O_RDONLY); - } while (fd < 0); - - ret = dso__load_sym(self, map, NULL, name, fd, filter, 0, 0); - close(fd); + } /* - * Some people seem to have debuginfo files _WITHOUT_ debug info!?!? + * Set the data type and long name so that kcore can be read via + * dso__data_read_addr(). */ - if (!ret) - goto more; + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + dso->binary_type = DSO_BINARY_TYPE__GUEST_KCORE; + else + dso->binary_type = DSO_BINARY_TYPE__KCORE; + dso__set_long_name(dso, strdup(kcore_filename), true); - if (ret > 0) { - int nr_plt = dso__synthesize_plt_symbols(self, map, filter); - if (nr_plt > 0) - ret += nr_plt; - } -out: - free(name); - if (ret < 0 && strstr(self->name, " (deleted)") != NULL) - return 0; - return ret; -} + close(fd); -struct map *map_groups__find_by_name(struct map_groups *self, - enum map_type type, const char *name) -{ - struct rb_node *nd; + if (map->type == MAP__FUNCTION) + pr_debug("Using %s for kernel object code\n", kcore_filename); + else + pr_debug("Using %s for kernel data\n", kcore_filename); - for (nd = rb_first(&self->maps[type]); nd; nd = rb_next(nd)) { - struct map *map = rb_entry(nd, struct map, rb_node); + return 0; - if (map->dso && strcmp(map->dso->name, name) == 0) - return map; +out_err: + while (!list_empty(&md.maps)) { + map = list_entry(md.maps.next, struct map, node); + list_del(&map->node); + map__delete(map); } - - return NULL; + close(fd); + return -EINVAL; } -static int perf_session__set_modules_path_dir(struct perf_session *self, char *dirname) +/* + * If the kernel is relocated at boot time, kallsyms won't match. Compute the + * delta based on the relocation reference symbol. + */ +static int kallsyms__delta(struct map *map, const char *filename, u64 *delta) { - struct dirent *dent; - DIR *dir = opendir(dirname); + struct kmap *kmap = map__kmap(map); + u64 addr; - if (!dir) { - pr_debug("%s: cannot open %s dir\n", __func__, dirname); - return -1; - } - - while ((dent = readdir(dir)) != NULL) { - char path[PATH_MAX]; - - if (dent->d_type == DT_DIR) { - if (!strcmp(dent->d_name, ".") || - !strcmp(dent->d_name, "..")) - continue; + if (!kmap->ref_reloc_sym || !kmap->ref_reloc_sym->name) + return 0; - snprintf(path, sizeof(path), "%s/%s", - dirname, dent->d_name); - if (perf_session__set_modules_path_dir(self, path) < 0) - goto failure; - } else { - char *dot = strrchr(dent->d_name, '.'), - dso_name[PATH_MAX]; - struct map *map; - char *long_name; - - if (dot == NULL || strcmp(dot, ".ko")) - continue; - snprintf(dso_name, sizeof(dso_name), "[%.*s]", - (int)(dot - dent->d_name), dent->d_name); - - strxfrchar(dso_name, '-', '_'); - map = map_groups__find_by_name(&self->kmaps, MAP__FUNCTION, dso_name); - if (map == NULL) - continue; - - snprintf(path, sizeof(path), "%s/%s", - dirname, dent->d_name); - - long_name = strdup(path); - if (long_name == NULL) - goto failure; - dso__set_long_name(map->dso, long_name); - } - } + addr = kallsyms__get_function_start(filename, + kmap->ref_reloc_sym->name); + if (!addr) + return -1; + *delta = addr - kmap->ref_reloc_sym->addr; return 0; -failure: - closedir(dir); - return -1; } -static int perf_session__set_modules_path(struct perf_session *self) +int dso__load_kallsyms(struct dso *dso, const char *filename, + struct map *map, symbol_filter_t filter) { - struct utsname uts; - char modules_path[PATH_MAX]; + u64 delta = 0; - if (uname(&uts) < 0) + if (symbol__restricted_filename(filename, "/proc/kallsyms")) return -1; - snprintf(modules_path, sizeof(modules_path), "/lib/modules/%s/kernel", - uts.release); + if (dso__load_all_kallsyms(dso, filename, map) < 0) + return -1; - return perf_session__set_modules_path_dir(self, modules_path); -} + if (kallsyms__delta(map, filename, &delta)) + return -1; -/* - * Constructor variant for modules (where we know from /proc/modules where - * they are loaded) and for vmlinux, where only after we load all the - * symbols we'll know where it starts and ends. - */ -static struct map *map__new2(u64 start, struct dso *dso, enum map_type type) -{ - struct map *self = malloc(sizeof(*self)); + symbols__fixup_duplicate(&dso->symbols[map->type]); + symbols__fixup_end(&dso->symbols[map->type]); - if (self != NULL) { - /* - * ->end will be filled after we load all the symbols - */ - map__init(self, type, start, 0, 0, dso); - } + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + dso->symtab_type = DSO_BINARY_TYPE__GUEST_KALLSYMS; + else + dso->symtab_type = DSO_BINARY_TYPE__KALLSYMS; - return self; + if (!dso__load_kcore(dso, map, filename)) + return dso__split_kallsyms_for_kcore(dso, map, filter); + else + return dso__split_kallsyms(dso, map, delta, filter); } -static int perf_session__create_module_maps(struct perf_session *self) +static int dso__load_perf_map(struct dso *dso, struct map *map, + symbol_filter_t filter) { char *line = NULL; size_t n; - FILE *file = fopen("/proc/modules", "r"); - struct map *map; + FILE *file; + int nr_syms = 0; + file = fopen(dso->long_name, "r"); if (file == NULL) - return -1; + goto out_failure; while (!feof(file)) { - char name[PATH_MAX]; - u64 start; - struct dso *dso; - char *sep; - int line_len; + u64 start, size; + struct symbol *sym; + int line_len, len; line_len = getline(&line, &n, file); if (line_len < 0) @@ -1429,45 +1216,35 @@ static int perf_session__create_module_maps(struct perf_session *self) line[--line_len] = '\0'; /* \n */ - sep = strrchr(line, 'x'); - if (sep == NULL) + len = hex2u64(line, &start); + + len++; + if (len + 2 >= line_len) continue; - hex2u64(sep + 1, &start); + len += hex2u64(line + len, &size); - sep = strchr(line, ' '); - if (sep == NULL) + len++; + if (len + 2 >= line_len) continue; - *sep = '\0'; - - snprintf(name, sizeof(name), "[%s]", line); - dso = dso__new(name); + sym = symbol__new(start, size, STB_GLOBAL, line + len); - if (dso == NULL) + if (sym == NULL) goto out_delete_line; - map = map__new2(start, dso, MAP__FUNCTION); - if (map == NULL) { - dso__delete(dso); - goto out_delete_line; + if (filter && filter(map, sym)) + symbol__delete(sym); + else { + symbols__insert(&dso->symbols[map->type], sym); + nr_syms++; } - - 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); } free(line); fclose(file); - return perf_session__set_modules_path(self); + return nr_syms; out_delete_line: free(line); @@ -1475,226 +1252,478 @@ out_failure: return -1; } -static int dso__load_vmlinux(struct dso *self, struct map *map, - struct perf_session *session, - const char *vmlinux, symbol_filter_t filter) +static bool dso__is_compatible_symtab_type(struct dso *dso, bool kmod, + enum dso_binary_type type) +{ + switch (type) { + case DSO_BINARY_TYPE__JAVA_JIT: + case DSO_BINARY_TYPE__DEBUGLINK: + case DSO_BINARY_TYPE__SYSTEM_PATH_DSO: + case DSO_BINARY_TYPE__FEDORA_DEBUGINFO: + case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__BUILDID_DEBUGINFO: + case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO: + return !kmod && dso->kernel == DSO_TYPE_USER; + + case DSO_BINARY_TYPE__KALLSYMS: + case DSO_BINARY_TYPE__VMLINUX: + case DSO_BINARY_TYPE__KCORE: + return dso->kernel == DSO_TYPE_KERNEL; + + case DSO_BINARY_TYPE__GUEST_KALLSYMS: + case DSO_BINARY_TYPE__GUEST_VMLINUX: + case DSO_BINARY_TYPE__GUEST_KCORE: + return dso->kernel == DSO_TYPE_GUEST_KERNEL; + + case DSO_BINARY_TYPE__GUEST_KMODULE: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE: + /* + * kernel modules know their symtab type - it's set when + * creating a module dso in machine__new_module(). + */ + return kmod && dso->symtab_type == type; + + case DSO_BINARY_TYPE__BUILD_ID_CACHE: + return true; + + case DSO_BINARY_TYPE__NOT_FOUND: + default: + return false; + } +} + +int dso__load(struct dso *dso, struct map *map, symbol_filter_t filter) { - int err = -1, fd; + char *name; + int ret = -1; + u_int i; + struct machine *machine; + char *root_dir = (char *) ""; + int ss_pos = 0; + struct symsrc ss_[2]; + struct symsrc *syms_ss = NULL, *runtime_ss = NULL; + bool kmod; + + dso__set_loaded(dso, map->type); + + if (dso->kernel == DSO_TYPE_KERNEL) + return dso__load_kernel_sym(dso, map, filter); + else if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + return dso__load_guest_kernel_sym(dso, map, filter); + + if (map->groups && map->groups->machine) + machine = map->groups->machine; + else + machine = NULL; - if (self->has_build_id) { - u8 build_id[BUILD_ID_SIZE]; + dso->adjust_symbols = 0; - if (filename__read_build_id(vmlinux, build_id, - sizeof(build_id)) < 0) { - pr_debug("No build_id in %s, ignoring it\n", vmlinux); + if (strncmp(dso->name, "/tmp/perf-", 10) == 0) { + struct stat st; + + if (lstat(dso->name, &st) < 0) return -1; - } - if (!dso__build_id_equal(self, build_id)) { - char expected_build_id[BUILD_ID_SIZE * 2 + 1], - vmlinux_build_id[BUILD_ID_SIZE * 2 + 1]; - - build_id__sprintf(self->build_id, - sizeof(self->build_id), - expected_build_id); - build_id__sprintf(build_id, sizeof(build_id), - vmlinux_build_id); - pr_debug("build_id in %s is %s while expected is %s, " - "ignoring it\n", vmlinux, vmlinux_build_id, - expected_build_id); + + if (st.st_uid && (st.st_uid != geteuid())) { + pr_warning("File %s not owned by current user or root, " + "ignoring it.\n", dso->name); return -1; } + + ret = dso__load_perf_map(dso, map, filter); + dso->symtab_type = ret > 0 ? DSO_BINARY_TYPE__JAVA_JIT : + DSO_BINARY_TYPE__NOT_FOUND; + return ret; } - fd = open(vmlinux, O_RDONLY); - if (fd < 0) + if (machine) + root_dir = machine->root_dir; + + name = malloc(PATH_MAX); + if (!name) return -1; - dso__set_loaded(self, map->type); - err = dso__load_sym(self, map, session, self->long_name, fd, filter, 1, 0); - close(fd); + kmod = dso->symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE || + dso->symtab_type == DSO_BINARY_TYPE__GUEST_KMODULE; - return err; -} + /* + * Iterate over candidate debug images. + * Keep track of "interesting" ones (those which have a symtab, dynsym, + * and/or opd section) for processing. + */ + for (i = 0; i < DSO_BINARY_TYPE__SYMTAB_CNT; i++) { + struct symsrc *ss = &ss_[ss_pos]; + bool next_slot = false; -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; - - if (vmlinux_path != NULL) { - int i; - pr_debug("Looking at the vmlinux_path (%d entries long)\n", - vmlinux_path__nr_entries); - for (i = 0; i < vmlinux_path__nr_entries; ++i) { - err = dso__load_vmlinux(self, map, session, - vmlinux_path[i], filter); - if (err > 0) { - pr_debug("Using %s for symbols\n", - vmlinux_path[i]); - dso__set_long_name(self, - strdup(vmlinux_path[i])); - goto out_fixup; - } + enum dso_binary_type symtab_type = binary_type_symtab[i]; + + if (!dso__is_compatible_symtab_type(dso, kmod, symtab_type)) + continue; + + if (dso__read_binary_type_filename(dso, symtab_type, + root_dir, name, PATH_MAX)) + continue; + + /* Name is now the name of the next image to try */ + if (symsrc__init(ss, dso, name, symtab_type) < 0) + continue; + + if (!syms_ss && symsrc__has_symtab(ss)) { + syms_ss = ss; + next_slot = true; + if (!dso->symsrc_filename) + dso->symsrc_filename = strdup(name); } - } - is_kallsyms = self->long_name[0] == '['; - if (is_kallsyms) - goto do_kallsyms; + if (!runtime_ss && symsrc__possibly_runtime(ss)) { + runtime_ss = ss; + next_slot = true; + } + + if (next_slot) { + ss_pos++; + + if (syms_ss && runtime_ss) + break; + } else { + symsrc__destroy(ss); + } - 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 (err > 0) { -out_fixup: - map__fixup_start(map); - map__fixup_end(map); + if (!runtime_ss && !syms_ss) + goto out_free; + + if (runtime_ss && !syms_ss) { + syms_ss = runtime_ss; } - return err; -} + /* We'll have to hope for the best */ + if (!runtime_ss && syms_ss) + runtime_ss = syms_ss; -LIST_HEAD(dsos__user); -LIST_HEAD(dsos__kernel); -struct dso *vdso; + if (syms_ss) + ret = dso__load_sym(dso, map, syms_ss, runtime_ss, filter, kmod); + else + ret = -1; -static void dsos__add(struct list_head *head, struct dso *dso) -{ - list_add_tail(&dso->node, head); + if (ret > 0) { + int nr_plt; + + nr_plt = dso__synthesize_plt_symbols(dso, runtime_ss, map, filter); + if (nr_plt > 0) + ret += nr_plt; + } + + for (; ss_pos > 0; ss_pos--) + symsrc__destroy(&ss_[ss_pos - 1]); +out_free: + free(name); + if (ret < 0 && strstr(dso->name, " (deleted)") != NULL) + return 0; + return ret; } -static struct dso *dsos__find(struct list_head *head, const char *name) +struct map *map_groups__find_by_name(struct map_groups *mg, + enum map_type type, const char *name) { - struct dso *pos; + struct rb_node *nd; + + for (nd = rb_first(&mg->maps[type]); nd; nd = rb_next(nd)) { + struct map *map = rb_entry(nd, struct map, rb_node); + + if (map->dso && strcmp(map->dso->short_name, name) == 0) + return map; + } - list_for_each_entry(pos, head, node) - if (strcmp(pos->name, name) == 0) - return pos; return NULL; } -struct dso *dsos__findnew(const char *name) +int dso__load_vmlinux(struct dso *dso, struct map *map, + const char *vmlinux, bool vmlinux_allocated, + symbol_filter_t filter) { - struct dso *dso = dsos__find(&dsos__user, name); + int err = -1; + struct symsrc ss; + char symfs_vmlinux[PATH_MAX]; + enum dso_binary_type symtab_type; + + if (vmlinux[0] == '/') + snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s", vmlinux); + else + snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s%s", + symbol_conf.symfs, vmlinux); + + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + symtab_type = DSO_BINARY_TYPE__GUEST_VMLINUX; + else + symtab_type = DSO_BINARY_TYPE__VMLINUX; + + if (symsrc__init(&ss, dso, symfs_vmlinux, symtab_type)) + return -1; - if (!dso) { - dso = dso__new(name); - if (dso != NULL) { - dsos__add(&dsos__user, dso); - dso__set_basename(dso); - } + err = dso__load_sym(dso, map, &ss, &ss, filter, 0); + symsrc__destroy(&ss); + + if (err > 0) { + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + dso->binary_type = DSO_BINARY_TYPE__GUEST_VMLINUX; + else + dso->binary_type = DSO_BINARY_TYPE__VMLINUX; + dso__set_long_name(dso, vmlinux, vmlinux_allocated); + dso__set_loaded(dso, map->type); + pr_debug("Using %s for symbols\n", symfs_vmlinux); } - return dso; + return err; } -static void __dsos__fprintf(struct list_head *head, FILE *fp) +int dso__load_vmlinux_path(struct dso *dso, struct map *map, + symbol_filter_t filter) { - struct dso *pos; + int i, err = 0; + char *filename; - list_for_each_entry(pos, head, node) { - int i; - for (i = 0; i < MAP__NR_TYPES; ++i) - dso__fprintf(pos, i, fp); + pr_debug("Looking at the vmlinux_path (%d entries long)\n", + vmlinux_path__nr_entries + 1); + + filename = dso__build_id_filename(dso, NULL, 0); + if (filename != NULL) { + err = dso__load_vmlinux(dso, map, filename, true, filter); + if (err > 0) + goto out; + free(filename); } -} -void dsos__fprintf(FILE *fp) -{ - __dsos__fprintf(&dsos__kernel, fp); - __dsos__fprintf(&dsos__user, fp); + for (i = 0; i < vmlinux_path__nr_entries; ++i) { + err = dso__load_vmlinux(dso, map, vmlinux_path[i], false, filter); + if (err > 0) + break; + } +out: + return err; } -static size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp) +static int find_matching_kcore(struct map *map, char *dir, size_t dir_sz) { - struct dso *pos; - size_t ret = 0; + char kallsyms_filename[PATH_MAX]; + struct dirent *dent; + int ret = -1; + DIR *d; - list_for_each_entry(pos, head, node) { - ret += dso__fprintf_buildid(pos, fp); - ret += fprintf(fp, " %s\n", pos->long_name); + d = opendir(dir); + if (!d) + return -1; + + while (1) { + dent = readdir(d); + if (!dent) + break; + if (dent->d_type != DT_DIR) + continue; + scnprintf(kallsyms_filename, sizeof(kallsyms_filename), + "%s/%s/kallsyms", dir, dent->d_name); + if (!validate_kcore_addresses(kallsyms_filename, map)) { + strlcpy(dir, kallsyms_filename, dir_sz); + ret = 0; + break; + } } + + closedir(d); + return ret; } -size_t dsos__fprintf_buildid(FILE *fp) +static char *dso__find_kallsyms(struct dso *dso, struct map *map) { - return (__dsos__fprintf_buildid(&dsos__kernel, fp) + - __dsos__fprintf_buildid(&dsos__user, fp)); -} + u8 host_build_id[BUILD_ID_SIZE]; + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + bool is_host = false; + char path[PATH_MAX]; -static struct dso *dsos__create_kernel( const char *vmlinux) -{ - struct dso *kernel = dso__new(vmlinux ?: "[kernel.kallsyms]"); + if (!dso->has_build_id) { + /* + * Last resort, if we don't have a build-id and couldn't find + * any vmlinux file, try the running kernel kallsyms table. + */ + goto proc_kallsyms; + } - if (kernel == NULL) - return NULL; + if (sysfs__read_build_id("/sys/kernel/notes", host_build_id, + sizeof(host_build_id)) == 0) + is_host = dso__build_id_equal(dso, host_build_id); - kernel->short_name = "[kernel]"; - kernel->kernel = 1; + build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id); - vdso = dso__new("[vdso]"); - if (vdso == NULL) - goto out_delete_kernel_dso; - dso__set_loaded(vdso, MAP__FUNCTION); + scnprintf(path, sizeof(path), "%s/[kernel.kcore]/%s", buildid_dir, + sbuild_id); - if (sysfs__read_build_id("/sys/kernel/notes", kernel->build_id, - sizeof(kernel->build_id)) == 0) - kernel->has_build_id = true; + /* Use /proc/kallsyms if possible */ + if (is_host) { + DIR *d; + int fd; - dsos__add(&dsos__kernel, kernel); - dsos__add(&dsos__user, vdso); + /* If no cached kcore go with /proc/kallsyms */ + d = opendir(path); + if (!d) + goto proc_kallsyms; + closedir(d); - return kernel; + /* + * Do not check the build-id cache, until we know we cannot use + * /proc/kcore. + */ + fd = open("/proc/kcore", O_RDONLY); + if (fd != -1) { + close(fd); + /* If module maps match go with /proc/kallsyms */ + if (!validate_kcore_addresses("/proc/kallsyms", map)) + goto proc_kallsyms; + } -out_delete_kernel_dso: - dso__delete(kernel); - return NULL; + /* Find kallsyms in build-id cache with kcore */ + if (!find_matching_kcore(map, path, sizeof(path))) + return strdup(path); + + goto proc_kallsyms; + } + + /* Find kallsyms in build-id cache with kcore */ + if (!find_matching_kcore(map, path, sizeof(path))) + return strdup(path); + + scnprintf(path, sizeof(path), "%s/[kernel.kallsyms]/%s", + buildid_dir, sbuild_id); + + if (access(path, F_OK)) { + pr_err("No kallsyms or vmlinux with build-id %s was found\n", + sbuild_id); + return NULL; + } + + return strdup(path); + +proc_kallsyms: + return strdup("/proc/kallsyms"); } -static int map_groups__create_kernel_maps(struct map_groups *self, const char *vmlinux) +static int dso__load_kernel_sym(struct dso *dso, struct map *map, + symbol_filter_t filter) { - struct map *functions, *variables; - struct dso *kernel = dsos__create_kernel(vmlinux); + int err; + const char *kallsyms_filename = NULL; + char *kallsyms_allocated_filename = NULL; + /* + * Step 1: if the user specified a kallsyms or 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.kallsyms_name != NULL) { + kallsyms_filename = symbol_conf.kallsyms_name; + goto do_kallsyms; + } + + if (!symbol_conf.ignore_vmlinux && symbol_conf.vmlinux_name != NULL) { + return dso__load_vmlinux(dso, map, symbol_conf.vmlinux_name, + false, filter); + } + + if (!symbol_conf.ignore_vmlinux && vmlinux_path != NULL) { + err = dso__load_vmlinux_path(dso, map, filter); + if (err > 0) + return err; + } - if (kernel == NULL) + /* do not try local files if a symfs was given */ + if (symbol_conf.symfs[0] != 0) return -1; - functions = map__new2(0, kernel, MAP__FUNCTION); - if (functions == NULL) + kallsyms_allocated_filename = dso__find_kallsyms(dso, map); + if (!kallsyms_allocated_filename) return -1; - variables = map__new2(0, kernel, MAP__VARIABLE); - if (variables == NULL) { - map__delete(functions); + kallsyms_filename = kallsyms_allocated_filename; + +do_kallsyms: + err = dso__load_kallsyms(dso, kallsyms_filename, map, filter); + if (err > 0) + pr_debug("Using %s for symbols\n", kallsyms_filename); + free(kallsyms_allocated_filename); + + if (err > 0 && !dso__is_kcore(dso)) { + dso__set_long_name(dso, "[kernel.kallsyms]", false); + map__fixup_start(map); + map__fixup_end(map); + } + + return err; +} + +static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map, + symbol_filter_t filter) +{ + int err; + const char *kallsyms_filename = NULL; + struct machine *machine; + char path[PATH_MAX]; + + if (!map->groups) { + pr_debug("Guest kernel map hasn't the point to groups\n"); return -1; } + machine = map->groups->machine; - functions->map_ip = functions->unmap_ip = - variables->map_ip = variables->unmap_ip = identity__map_ip; - map_groups__insert(self, functions); - map_groups__insert(self, variables); + if (machine__is_default_guest(machine)) { + /* + * if the user specified a vmlinux filename, use it and only + * it, reporting errors to the user if it cannot be used. + * Or use file guest_kallsyms inputted by user on commandline + */ + if (symbol_conf.default_guest_vmlinux_name != NULL) { + err = dso__load_vmlinux(dso, map, + symbol_conf.default_guest_vmlinux_name, + false, filter); + return err; + } - return 0; + kallsyms_filename = symbol_conf.default_guest_kallsyms; + if (!kallsyms_filename) + return -1; + } else { + sprintf(path, "%s/proc/kallsyms", machine->root_dir); + kallsyms_filename = path; + } + + err = dso__load_kallsyms(dso, kallsyms_filename, map, filter); + if (err > 0) + pr_debug("Using %s for symbols\n", kallsyms_filename); + if (err > 0 && !dso__is_kcore(dso)) { + machine__mmap_name(machine, path, sizeof(path)); + dso__set_long_name(dso, strdup(path), true); + map__fixup_start(map); + map__fixup_end(map); + } + + return err; } static void vmlinux_path__exit(void) { - while (--vmlinux_path__nr_entries >= 0) { - free(vmlinux_path[vmlinux_path__nr_entries]); - vmlinux_path[vmlinux_path__nr_entries] = NULL; - } + while (--vmlinux_path__nr_entries >= 0) + zfree(&vmlinux_path[vmlinux_path__nr_entries]); - free(vmlinux_path); - vmlinux_path = NULL; + zfree(&vmlinux_path); } static int vmlinux_path__init(void) @@ -1702,9 +1731,6 @@ static int vmlinux_path__init(void) struct utsname uts; char bf[PATH_MAX]; - if (uname(&uts) < 0) - return -1; - vmlinux_path = malloc(sizeof(char *) * 5); if (vmlinux_path == NULL) return -1; @@ -1717,6 +1743,14 @@ static int vmlinux_path__init(void) if (vmlinux_path[vmlinux_path__nr_entries] == NULL) goto out_fail; ++vmlinux_path__nr_entries; + + /* only try running kernel version if no symfs was given */ + if (symbol_conf.symfs[0] != 0) + return 0; + + if (uname(&uts) < 0) + return -1; + snprintf(bf, sizeof(bf), "/boot/vmlinux-%s", uts.release); vmlinux_path[vmlinux_path__nr_entries] = strdup(bf); if (vmlinux_path[vmlinux_path__nr_entries] == NULL) @@ -1741,7 +1775,7 @@ out_fail: return -1; } -static int setup_list(struct strlist **list, const char *list_str, +int setup_list(struct strlist **list, const char *list_str, const char *list_name) { if (list_str == NULL) @@ -1755,9 +1789,36 @@ static int setup_list(struct strlist **list, const char *list_str, return 0; } +static bool symbol__read_kptr_restrict(void) +{ + bool value = false; + + if (geteuid() != 0) { + FILE *fp = fopen("/proc/sys/kernel/kptr_restrict", "r"); + if (fp != NULL) { + char line[8]; + + if (fgets(line, sizeof(line), fp) != NULL) + value = atoi(line) != 0; + + fclose(fp); + } + } + + return value; +} + int symbol__init(void) { - elf_version(EV_CURRENT); + const char *symfs; + + if (symbol_conf.initialized) + return 0; + + symbol_conf.priv_size = PERF_ALIGN(symbol_conf.priv_size, sizeof(u64)); + + symbol__elf_init(); + if (symbol_conf.sort_by_name) symbol_conf.priv_size += (sizeof(struct symbol_name_rb_node) - sizeof(struct symbol)); @@ -1782,28 +1843,38 @@ int symbol__init(void) symbol_conf.sym_list_str, "symbol") < 0) goto out_free_comm_list; + /* + * A path to symbols of "/" is identical to "" + * reset here for simplicity. + */ + symfs = realpath(symbol_conf.symfs, NULL); + if (symfs == NULL) + symfs = symbol_conf.symfs; + if (strcmp(symfs, "/") == 0) + symbol_conf.symfs = ""; + if (symfs != symbol_conf.symfs) + free((void *)symfs); + + symbol_conf.kptr_restrict = symbol__read_kptr_restrict(); + + symbol_conf.initialized = true; return 0; -out_free_dso_list: - strlist__delete(symbol_conf.dso_list); out_free_comm_list: strlist__delete(symbol_conf.comm_list); +out_free_dso_list: + strlist__delete(symbol_conf.dso_list); return -1; } -int perf_session__create_kernel_maps(struct perf_session *self) +void symbol__exit(void) { - if (map_groups__create_kernel_maps(&self->kmaps, - symbol_conf.vmlinux_name) < 0) - return -1; - - if (symbol_conf.use_modules && - perf_session__create_module_maps(self) < 0) - pr_debug("Failed to load list of modules for session %s, " - "continuing...\n", self->filename); - /* - * Now that we have all the maps created, just set the ->end of them: - */ - map_groups__fixup_end(&self->kmaps); - return 0; + if (!symbol_conf.initialized) + return; + strlist__delete(symbol_conf.sym_list); + strlist__delete(symbol_conf.dso_list); + strlist__delete(symbol_conf.comm_list); + vmlinux_path__exit(); + symbol_conf.sym_list = symbol_conf.dso_list = symbol_conf.comm_list = NULL; + symbol_conf.initialized = false; } |
