linux/tools/perf/util/symbol.c
Arnaldo Carvalho de Melo 8216416167 perf symbols: Call the symbol filter in dso__synthesize_plt_symbols()
We need to pass the symbol to the filter so that, for instance,
'perf top' can do filtering and also set the private area it
manages, setting the ->map pointer, etc.

I found this while running 'perf top' on a machine where hits
happened on PLT symbols, where ->map wasn't being set up and
segfaults thus happened.

Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <peterz@infradead.org>
LKML-Reference: <1258386491-20278-1-git-send-email-acme@infradead.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-11-16 17:29:54 +01:00

1450 lines
30 KiB
C

#include "util.h"
#include "../perf.h"
#include "string.h"
#include "symbol.h"
#include "thread.h"
#include "debug.h"
#include <libelf.h>
#include <gelf.h>
#include <elf.h>
#include <sys/utsname.h>
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 dso *dso);
static struct dso *dsos__find(const char *name);
static struct map *map__new2(u64 start, struct dso *dso);
static void kernel_maps__insert(struct map *map);
unsigned int symbol__priv_size;
static struct rb_root kernel_maps;
static void dso__fixup_sym_end(struct dso *self)
{
struct rb_node *nd, *prevnd = rb_first(&self->syms);
struct symbol *curr, *prev;
if (prevnd == NULL)
return;
curr = rb_entry(prevnd, struct symbol, rb_node);
for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
prev = curr;
curr = rb_entry(nd, struct symbol, rb_node);
if (prev->end == prev->start)
prev->end = curr->start - 1;
}
/* Last entry */
if (curr->end == curr->start)
curr->end = roundup(curr->start, 4096);
}
static void kernel_maps__fixup_end(void)
{
struct map *prev, *curr;
struct rb_node *nd, *prevnd = rb_first(&kernel_maps);
if (prevnd == NULL)
return;
curr = rb_entry(prevnd, struct map, rb_node);
for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
prev = curr;
curr = rb_entry(nd, struct map, rb_node);
prev->end = curr->start - 1;
}
nd = rb_last(&curr->dso->syms);
if (nd) {
struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
curr->end = sym->end;
}
}
static struct symbol *symbol__new(u64 start, u64 len, const char *name)
{
size_t namelen = strlen(name) + 1;
struct symbol *self = calloc(1, (symbol__priv_size +
sizeof(*self) + namelen));
if (!self)
return NULL;
if (symbol__priv_size) {
memset(self, 0, symbol__priv_size);
self = ((void *)self) + symbol__priv_size;
}
self->start = start;
self->end = len ? start + len - 1 : start;
pr_debug3("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end);
memcpy(self->name, name, namelen);
return self;
}
static void symbol__delete(struct symbol *self)
{
free(((void *)self) - symbol__priv_size);
}
static size_t symbol__fprintf(struct symbol *self, FILE *fp)
{
return fprintf(fp, " %llx-%llx %s\n",
self->start, self->end, self->name);
}
struct dso *dso__new(const char *name)
{
struct dso *self = malloc(sizeof(*self) + strlen(name) + 1);
if (self != NULL) {
strcpy(self->name, name);
self->long_name = self->name;
self->short_name = self->name;
self->syms = RB_ROOT;
self->find_symbol = dso__find_symbol;
self->slen_calculated = 0;
self->origin = DSO__ORIG_NOT_FOUND;
self->loaded = 0;
self->has_build_id = 0;
}
return self;
}
static void dso__delete_symbols(struct dso *self)
{
struct symbol *pos;
struct rb_node *next = rb_first(&self->syms);
while (next) {
pos = rb_entry(next, struct symbol, rb_node);
next = rb_next(&pos->rb_node);
rb_erase(&pos->rb_node, &self->syms);
symbol__delete(pos);
}
}
void dso__delete(struct dso *self)
{
dso__delete_symbols(self);
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 dso__insert_symbol(struct dso *self, struct symbol *sym)
{
struct rb_node **p = &self->syms.rb_node;
struct rb_node *parent = NULL;
const u64 ip = sym->start;
struct symbol *s;
while (*p != NULL) {
parent = *p;
s = rb_entry(parent, struct symbol, rb_node);
if (ip < s->start)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&sym->rb_node, parent, p);
rb_insert_color(&sym->rb_node, &self->syms);
}
struct symbol *dso__find_symbol(struct dso *self, u64 ip)
{
struct rb_node *n;
if (self == NULL)
return NULL;
n = self->syms.rb_node;
while (n) {
struct symbol *s = rb_entry(n, struct symbol, rb_node);
if (ip < s->start)
n = n->rb_left;
else if (ip > s->end)
n = n->rb_right;
else
return s;
}
return NULL;
}
int build_id__sprintf(u8 *self, int len, char *bf)
{
char *bid = bf;
u8 *raw = self;
int i;
for (i = 0; i < len; ++i) {
sprintf(bid, "%02x", *raw);
++raw;
bid += 2;
}
return raw - self;
}
size_t dso__fprintf(struct dso *self, FILE *fp)
{
char sbuild_id[BUILD_ID_SIZE * 2 + 1];
struct rb_node *nd;
size_t ret;
build_id__sprintf(self->build_id, sizeof(self->build_id), sbuild_id);
ret = fprintf(fp, "dso: %s (%s)\n", self->short_name, sbuild_id);
for (nd = rb_first(&self->syms); nd; nd = rb_next(nd)) {
struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
ret += symbol__fprintf(pos, fp);
}
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 kernel_maps__load_all_kallsyms(void)
{
char *line = NULL;
size_t n;
FILE *file = fopen("/proc/kallsyms", "r");
if (file == NULL)
goto out_failure;
while (!feof(file)) {
u64 start;
struct symbol *sym;
int line_len, len;
char symbol_type;
char *symbol_name;
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;
symbol_type = toupper(line[len]);
/*
* We're interested only in code ('T'ext)
*/
if (symbol_type != 'T' && symbol_type != 'W')
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);
if (sym == NULL)
goto out_delete_line;
/*
* We will pass the symbols to the filter later, in
* kernel_maps__split_kallsyms, when we have split the
* maps per module
*/
dso__insert_symbol(kernel_map->dso, sym);
}
free(line);
fclose(file);
return 0;
out_delete_line:
free(line);
out_failure:
return -1;
}
/*
* Split the symbols into maps, making sure there are no overlaps, i.e. the
* kernel range is broken in several maps, named [kernel].N, as we don't have
* the original ELF section names vmlinux have.
*/
static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules)
{
struct map *map = kernel_map;
struct symbol *pos;
int count = 0;
struct rb_node *next = rb_first(&kernel_map->dso->syms);
int kernel_range = 0;
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) {
if (!use_modules)
goto delete_symbol;
*module++ = '\0';
if (strcmp(map->dso->name, module)) {
map = kernel_maps__find_by_dso_name(module);
if (!map) {
pr_err("/proc/{kallsyms,modules} "
"inconsistency!\n");
return -1;
}
}
/*
* So that we look just like we get from .ko files,
* i.e. not prelinked, relative to map->start.
*/
pos->start = map->map_ip(map, pos->start);
pos->end = map->map_ip(map, pos->end);
} else if (map != kernel_map) {
char dso_name[PATH_MAX];
struct dso *dso;
snprintf(dso_name, sizeof(dso_name), "[kernel].%d",
kernel_range++);
dso = dso__new(dso_name);
if (dso == NULL)
return -1;
map = map__new2(pos->start, dso);
if (map == NULL) {
dso__delete(dso);
return -1;
}
map->map_ip = map->unmap_ip = identity__map_ip;
kernel_maps__insert(map);
++kernel_range;
}
if (filter && filter(map, pos)) {
delete_symbol:
rb_erase(&pos->rb_node, &kernel_map->dso->syms);
symbol__delete(pos);
} else {
if (map != kernel_map) {
rb_erase(&pos->rb_node, &kernel_map->dso->syms);
dso__insert_symbol(map->dso, pos);
}
count++;
}
}
return count;
}
static int kernel_maps__load_kallsyms(symbol_filter_t filter, int use_modules)
{
if (kernel_maps__load_all_kallsyms())
return -1;
dso__fixup_sym_end(kernel_map->dso);
return kernel_maps__split_kallsyms(filter, use_modules);
}
static size_t kernel_maps__fprintf(FILE *fp)
{
size_t printed = fprintf(fp, "Kernel maps:\n");
struct rb_node *nd;
for (nd = rb_first(&kernel_maps); nd; nd = rb_next(nd)) {
struct map *pos = rb_entry(nd, struct map, rb_node);
printed += fprintf(fp, "Map:");
printed += map__fprintf(pos, fp);
if (verbose > 1) {
printed += dso__fprintf(pos->dso, fp);
printed += fprintf(fp, "--\n");
}
}
return printed + fprintf(fp, "END kernel maps\n");
}
static int dso__load_perf_map(struct dso *self, struct map *map,
symbol_filter_t filter)
{
char *line = NULL;
size_t n;
FILE *file;
int nr_syms = 0;
file = fopen(self->long_name, "r");
if (file == NULL)
goto out_failure;
while (!feof(file)) {
u64 start, size;
struct symbol *sym;
int line_len, len;
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 {
dso__insert_symbol(self, sym);
nr_syms++;
}
}
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);
}
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 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;
}
static inline const char *elf_sec__name(const GElf_Shdr *shdr,
const Elf_Data *secstrs)
{
return secstrs->d_buf + shdr->sh_name;
}
static inline int elf_sec__is_text(const GElf_Shdr *shdr,
const Elf_Data *secstrs)
{
return strstr(elf_sec__name(shdr, secstrs), "text") != NULL;
}
static inline const char *elf_sym__name(const GElf_Sym *sym,
const Elf_Data *symstrs)
{
return symstrs->d_buf + sym->st_name;
}
static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
GElf_Shdr *shp, const char *name,
size_t *idx)
{
Elf_Scn *sec = NULL;
size_t cnt = 1;
while ((sec = elf_nextscn(elf, sec)) != NULL) {
char *str;
gelf_getshdr(sec, shp);
str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name);
if (!strcmp(name, str)) {
if (idx)
*idx = cnt;
break;
}
++cnt;
}
return sec;
}
#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)
{
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;
if (shdr_rel_plt.sh_link != dynsym_idx)
goto out_elf_end;
if (elf_section_by_name(elf, &ehdr, &shdr_plt, ".plt", NULL) == NULL)
goto out_elf_end;
/*
* 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 {
dso__insert_symbol(self, 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 {
dso__insert_symbol(self, f);
++nr;
}
}
}
err = 0;
out_elf_end:
elf_end(elf);
out_close:
close(fd);
if (err == 0)
return nr;
out:
pr_warning("%s: problems reading %s PLT info.\n",
__func__, self->long_name);
return 0;
}
static int dso__load_sym(struct dso *self, struct map *map, const char *name,
int fd, symbol_filter_t filter, int kernel,
int kmodule)
{
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;
}
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;
}
syms = elf_getdata(sec, NULL);
if (syms == NULL)
goto out_elf_end;
sec = elf_getscn(elf, shdr.sh_link);
if (sec == NULL)
goto out_elf_end;
symstrs = elf_getdata(sec, NULL);
if (symstrs == NULL)
goto out_elf_end;
sec_strndx = elf_getscn(elf, ehdr.e_shstrndx);
if (sec_strndx == NULL)
goto out_elf_end;
secstrs = elf_getdata(sec_strndx, NULL);
if (secstrs == NULL)
goto out_elf_end;
nr_syms = shdr.sh_size / shdr.sh_entsize;
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;
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;
if (!is_label && !elf_sym__is_function(&sym))
continue;
sec = elf_getscn(elf, sym.st_shndx);
if (!sec)
goto out_elf_end;
gelf_getshdr(sec, &shdr);
if (is_label && !elf_sec__is_text(&shdr, secstrs))
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 = kernel_maps__find_by_dso_name(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);
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;
kernel_maps__insert(curr_map);
dsos__add(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 {
dso__insert_symbol(curr_dso, f);
nr++;
}
}
/*
* For misannotated, zeroed, ASM function sizes.
*/
if (nr > 0)
dso__fixup_sym_end(self);
err = nr;
out_elf_end:
elf_end(elf);
out_close:
return err;
}
bool fetch_build_id_table(struct list_head *head)
{
bool have_buildid = false;
struct dso *pos;
list_for_each_entry(pos, &dsos, node) {
struct build_id_list *new;
struct build_id_event b;
size_t len;
if (filename__read_build_id(pos->long_name,
&b.build_id,
sizeof(b.build_id)) < 0)
continue;
have_buildid = true;
memset(&b.header, 0, sizeof(b.header));
len = strlen(pos->long_name) + 1;
len = ALIGN(len, 64);
b.header.size = sizeof(b) + len;
new = malloc(sizeof(*new));
if (!new)
die("No memory\n");
memcpy(&new->event, &b, sizeof(b));
new->dso_name = pos->long_name;
new->len = len;
list_add_tail(&new->list, head);
}
return have_buildid;
}
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 *build_id_data;
Elf_Scn *sec;
Elf *elf;
if (size < BUILD_ID_SIZE)
goto out;
fd = open(filename, O_RDONLY);
if (fd < 0)
goto out;
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;
}
if (gelf_getehdr(elf, &ehdr) == NULL) {
pr_err("%s: cannot get elf header.\n", __func__);
goto out_elf_end;
}
sec = elf_section_by_name(elf, &ehdr, &shdr,
".note.gnu.build-id", NULL);
if (sec == NULL)
goto out_elf_end;
build_id_data = elf_getdata(sec, NULL);
if (build_id_data == NULL)
goto out_elf_end;
memcpy(bf, build_id_data->d_buf + 16, BUILD_ID_SIZE);
err = BUILD_ID_SIZE;
out_elf_end:
elf_end(elf);
out_close:
close(fd);
out:
return err;
}
static char *dso__read_build_id(struct dso *self)
{
int len;
char *build_id = NULL;
unsigned char rawbf[BUILD_ID_SIZE];
len = filename__read_build_id(self->long_name, rawbf, sizeof(rawbf));
if (len < 0)
goto out;
build_id = malloc(len * 2 + 1);
if (build_id == NULL)
goto out;
build_id__sprintf(rawbf, len, build_id);
out:
return build_id;
}
char dso__symtab_origin(const struct dso *self)
{
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',
};
if (self == NULL || self->origin == DSO__ORIG_NOT_FOUND)
return '!';
return origin[self->origin];
}
int dso__load(struct dso *self, struct map *map, symbol_filter_t filter)
{
int size = PATH_MAX;
char *name = malloc(size), *build_id = NULL;
int ret = -1;
int fd;
self->loaded = 1;
if (!name)
return -1;
self->adjust_symbols = 0;
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;
}
self->origin = DSO__ORIG_FEDORA - 1;
more:
do {
int berr = 0;
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:
build_id = dso__read_build_id(self);
if (build_id != NULL) {
snprintf(name, size,
"/usr/lib/debug/.build-id/%.2s/%s.debug",
build_id, build_id + 2);
goto compare_build_id;
}
self->origin++;
/* Fall thru */
case DSO__ORIG_DSO:
snprintf(name, size, "%s", self->long_name);
break;
default:
goto out;
}
if (self->has_build_id) {
bool match;
build_id = malloc(BUILD_ID_SIZE);
if (build_id == NULL)
goto more;
berr = filename__read_build_id(name, build_id,
BUILD_ID_SIZE);
compare_build_id:
match = berr > 0 && memcmp(build_id, self->build_id,
sizeof(self->build_id)) == 0;
free(build_id);
build_id = NULL;
if (!match)
goto more;
}
fd = open(name, O_RDONLY);
} while (fd < 0);
ret = dso__load_sym(self, map, name, fd, filter, 0, 0);
close(fd);
/*
* Some people seem to have debuginfo files _WITHOUT_ debug info!?!?
*/
if (!ret)
goto more;
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;
}
struct map *kernel_map;
static void kernel_maps__insert(struct map *map)
{
maps__insert(&kernel_maps, map);
}
struct symbol *kernel_maps__find_symbol(u64 ip, struct map **mapp)
{
struct map *map = maps__find(&kernel_maps, ip);
if (mapp)
*mapp = map;
if (map) {
ip = map->map_ip(map, ip);
return map->dso->find_symbol(map->dso, ip);
}
return NULL;
}
struct map *kernel_maps__find_by_dso_name(const char *name)
{
struct rb_node *nd;
for (nd = rb_first(&kernel_maps); nd; nd = rb_next(nd)) {
struct map *map = rb_entry(nd, struct map, rb_node);
if (map->dso && strcmp(map->dso->name, name) == 0)
return map;
}
return NULL;
}
static int dso__load_module_sym(struct dso *self, struct map *map,
symbol_filter_t filter)
{
int err = 0, fd = open(self->long_name, O_RDONLY);
self->loaded = 1;
if (fd < 0) {
pr_err("%s: cannot open %s\n", __func__, self->long_name);
return err;
}
err = dso__load_sym(self, map, self->long_name, fd, filter, 0, 1);
close(fd);
return err;
}
static int dsos__load_modules_sym_dir(char *dirname, symbol_filter_t filter)
{
struct dirent *dent;
int nr_symbols = 0, err;
DIR *dir = opendir(dirname);
if (!dir) {
pr_err("%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;
snprintf(path, sizeof(path), "%s/%s",
dirname, dent->d_name);
err = dsos__load_modules_sym_dir(path, filter);
if (err < 0)
goto failure;
} else {
char *dot = strrchr(dent->d_name, '.'),
dso_name[PATH_MAX];
struct map *map;
struct rb_node *last;
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 = kernel_maps__find_by_dso_name(dso_name);
if (map == NULL)
continue;
snprintf(path, sizeof(path), "%s/%s",
dirname, dent->d_name);
map->dso->long_name = strdup(path);
if (map->dso->long_name == NULL)
goto failure;
err = dso__load_module_sym(map->dso, map, filter);
if (err < 0)
goto failure;
last = rb_last(&map->dso->syms);
if (last) {
struct symbol *sym;
/*
* We do this here as well, even having the
* symbol size found in the symtab because
* misannotated ASM symbols may have the size
* set to zero.
*/
dso__fixup_sym_end(map->dso);
sym = rb_entry(last, struct symbol, rb_node);
map->end = map->start + sym->end;
}
}
nr_symbols += err;
}
return nr_symbols;
failure:
closedir(dir);
return -1;
}
static int dsos__load_modules_sym(symbol_filter_t filter)
{
struct utsname uts;
char modules_path[PATH_MAX];
if (uname(&uts) < 0)
return -1;
snprintf(modules_path, sizeof(modules_path), "/lib/modules/%s/kernel",
uts.release);
return dsos__load_modules_sym_dir(modules_path, filter);
}
/*
* 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)
{
struct map *self = malloc(sizeof(*self));
if (self != NULL) {
/*
* ->end will be filled after we load all the symbols
*/
map__init(self, start, 0, 0, dso);
}
return self;
}
static int dsos__load_modules(void)
{
char *line = NULL;
size_t n;
FILE *file = fopen("/proc/modules", "r");
struct map *map;
if (file == NULL)
return -1;
while (!feof(file)) {
char name[PATH_MAX];
u64 start;
struct dso *dso;
char *sep;
int line_len;
line_len = getline(&line, &n, file);
if (line_len < 0)
break;
if (!line)
goto out_failure;
line[--line_len] = '\0'; /* \n */
sep = strrchr(line, 'x');
if (sep == NULL)
continue;
hex2u64(sep + 1, &start);
sep = strchr(line, ' ');
if (sep == NULL)
continue;
*sep = '\0';
snprintf(name, sizeof(name), "[%s]", line);
dso = dso__new(name);
if (dso == NULL)
goto out_delete_line;
map = map__new2(start, dso);
if (map == NULL) {
dso__delete(dso);
goto out_delete_line;
}
dso->origin = DSO__ORIG_KMODULE;
kernel_maps__insert(map);
dsos__add(dso);
}
free(line);
fclose(file);
return 0;
out_delete_line:
free(line);
out_failure:
return -1;
}
static int dso__load_vmlinux(struct dso *self, struct map *map,
const char *vmlinux, symbol_filter_t filter)
{
int err, fd = open(vmlinux, O_RDONLY);
self->loaded = 1;
if (fd < 0)
return -1;
err = dso__load_sym(self, map, self->long_name, fd, filter, 1, 0);
close(fd);
return err;
}
int dsos__load_kernel(const char *vmlinux, symbol_filter_t filter,
int use_modules)
{
int err = -1;
struct dso *dso = dso__new(vmlinux);
if (dso == NULL)
return -1;
dso->short_name = "[kernel]";
kernel_map = map__new2(0, dso);
if (kernel_map == NULL)
goto out_delete_dso;
kernel_map->map_ip = kernel_map->unmap_ip = identity__map_ip;
if (use_modules && dsos__load_modules() < 0) {
pr_warning("Failed to load list of modules in use! "
"Continuing...\n");
use_modules = 0;
}
if (vmlinux) {
err = dso__load_vmlinux(dso, kernel_map, vmlinux, filter);
if (err > 0 && use_modules) {
int syms = dsos__load_modules_sym(filter);
if (syms < 0)
pr_warning("Failed to read module symbols!"
" Continuing...\n");
else
err += syms;
}
}
if (err <= 0)
err = kernel_maps__load_kallsyms(filter, use_modules);
if (err > 0) {
struct rb_node *node = rb_first(&dso->syms);
struct symbol *sym = rb_entry(node, struct symbol, rb_node);
kernel_map->start = sym->start;
node = rb_last(&dso->syms);
sym = rb_entry(node, struct symbol, rb_node);
kernel_map->end = sym->end;
dso->origin = DSO__ORIG_KERNEL;
kernel_maps__insert(kernel_map);
/*
* Now that we have all sorted out, just set the ->end of all
* maps:
*/
kernel_maps__fixup_end();
dsos__add(dso);
if (verbose)
kernel_maps__fprintf(stderr);
}
return err;
out_delete_dso:
dso__delete(dso);
return -1;
}
LIST_HEAD(dsos);
struct dso *vdso;
const char *vmlinux_name = "vmlinux";
int modules;
static void dsos__add(struct dso *dso)
{
list_add_tail(&dso->node, &dsos);
}
static struct dso *dsos__find(const char *name)
{
struct dso *pos;
list_for_each_entry(pos, &dsos, node)
if (strcmp(pos->name, name) == 0)
return pos;
return NULL;
}
struct dso *dsos__findnew(const char *name)
{
struct dso *dso = dsos__find(name);
if (!dso) {
dso = dso__new(name);
if (dso != NULL)
dsos__add(dso);
}
return dso;
}
void dsos__fprintf(FILE *fp)
{
struct dso *pos;
list_for_each_entry(pos, &dsos, node)
dso__fprintf(pos, fp);
}
int load_kernel(symbol_filter_t filter)
{
if (dsos__load_kernel(vmlinux_name, filter, modules) <= 0)
return -1;
vdso = dso__new("[vdso]");
if (!vdso)
return -1;
dsos__add(vdso);
return 0;
}
void symbol__init(unsigned int priv_size)
{
elf_version(EV_CURRENT);
symbol__priv_size = priv_size;
}