Forked from
Antoine Kaufmann / perf-profiler
2 commits behind the upstream repository.
-
Antoine Kaufmann authoredAntoine Kaufmann authored
symbols.c 9.16 KiB
#include <ctype.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/utsname.h>
#include <bfd.h>
struct symbol {
uint64_t ip;
char *symname;
char *dso;
};
struct symbol_map {
size_t sym_num;
size_t sym_alloc;
struct symbol *symbols;
struct symbol_map *next;
pid_t pid;
};
/** Kernel symbol map */
static struct symbol_map *kernel_map = NULL;
/** Linked list of process symbol maps */
static struct symbol_map *pid_maps = NULL;
/** Allocate new empty symbol map */
static struct symbol_map *symmap_alloc(void)
{
struct symbol_map *m;
if ((m = calloc(1, sizeof(*m))) == NULL) {
return NULL;
}
m->sym_num = 0;
m->sym_alloc = 8192;
if ((m->symbols = calloc(m->sym_alloc, sizeof(*m->symbols))) == NULL) {
free(m);
return NULL;
}
return m;
}
/** Add new symbol to symbol map */
static int symmap_add(struct symbol_map *m, uint64_t ip, const char *sym,
char *dso)
{
if (m->sym_num == m->sym_alloc) {
if ((m->symbols = realloc(m->symbols,
sizeof(*m->symbols) * m->sym_alloc * 2)) == NULL)
{
perror("symmap_add: realloc failed");
return -1;
}
m->sym_alloc *= 2;
}
m->symbols[m->sym_num].ip = ip;
m->symbols[m->sym_num].dso = dso;
if ((m->symbols[m->sym_num].symname = strdup(sym)) == NULL) {
perror("strdup failed");
return -1;
}
m->sym_num++;
return 0;
}
/** Lookup symbol in symbol map */
static inline struct symbol *symmap_lookup(struct symbol_map *m, uint64_t ip)
{
size_t i;
for (i = 0; i < m->sym_num; i++) {
if (m->symbols[i].ip > ip)
break;
}
if (i == 0)
return NULL;
return &m->symbols[i - 1];
}
/** Comparator for sorting symbol map */
static int sym_cmp(const void *a, const void *b)
{
const struct symbol *sa = a, *sb = b;
if (sa->ip < sb->ip) {
return -1;
} else if (sa->ip == sb->ip) {
return 0;
} else {
return 1;
}
}
/** Sort symbol map */
static void symmap_sort(struct symbol_map *m)
{
qsort(m->symbols, m->sym_num, sizeof(m->symbols[0]), sym_cmp);
}
/** Create symbol map from kernel symbols (/proc/kallsyms) */
static struct symbol_map *kernel_symbols(void)
{
struct symbol_map *m;
FILE *f;
char *p, *q, *l = NULL;
size_t l_len = 0;
ssize_t ret;
unsigned long n;
if ((m = symmap_alloc()) == NULL) {
return NULL;
}
if ((f = fopen("/proc/kallsyms", "r")) == NULL) {
return NULL;
}
while ((ret = getline(&l, &l_len, f)) > 0) {
n = strtoul(l, &p, 16);
/* eat space */
if (*p != ' ') {
perror("kallsyms: unexpected format (1)");
abort();
}
p++;
/* skip non text symbols */
if (*p != 't' && *p != 'T')
continue;
p++;
/* eat space */
if (*p != ' ') {
perror("kallsyms: unexpected format (1)");
abort();
}
p++;
/* find end of symbol name */
q = p;
while (*q != 0 && !isspace(*q)) q++;
*q = 0;
if (symmap_add(m, n, p, "vmlinux") != 0)
return NULL;
}
fclose(f);
m->pid = 0;
/* sort symbols */
symmap_sort(m);
return m;
}
/** Add all symbols from bfd symbol table */
static int symtab_process(struct symbol_map *m, uint64_t start_addr,
uint64_t len, uint64_t file_off, asymbol **symbol_table, size_t num,
char *dso)
{
asymbol *sym;
asection *sec;
int c;
uint64_t filepos, addr;
size_t i;
for (i = 0; i < num; i++) {
sym = symbol_table[i];
c = bfd_decode_symclass(sym);
if (c != 't' && c != 'T' && c != '?')
continue;
if (sym->name == NULL)
continue;
sec = sym->section;
filepos = sec->filepos + sym->value;
if (filepos < file_off || filepos > file_off + len)
continue;
addr = start_addr + filepos - file_off;
if (symmap_add(m, addr, sym->name, dso) != 0)
return -1;
}
return 0;
}
/** Load symbols from object file */
static int map_load_exec(struct symbol_map *m, const char *path,
uint64_t start_addr, uint64_t len, uint64_t file_off)
{
bfd *f;
asymbol **symbol_table;
ssize_t storage, num_symbols;
char *dso, *p;
/* get dso name for symbols */
p = strdup(path);
dso = strdup(basename(p));
free(p);
if ((f = bfd_openr(path, NULL)) == NULL) {
fprintf(stderr, "map_load_exec: bfd_openr(%s) failed\n", path);
return -1;
}
if (!bfd_check_format(f, bfd_object)) {
fprintf(stderr, "map_load_exec: bfd_check_format(%s) failed\n", path);
return -1;
}
/* start with regular symbol table */
if ((storage = bfd_get_symtab_upper_bound(f)) < 0) {
fprintf(stderr, "map_load_exec: bfd_get_symtab_upper_bound(%s) failed\n", path);
return -1;
}
if ((symbol_table = malloc(storage)) == NULL) {
perror("map_load_exec: malloc failed");
return -1;
}
if ((num_symbols = bfd_canonicalize_symtab(f, symbol_table)) < 0) {
fprintf(stderr, "map_load_exec: bfd_canonicalize_symtab(%s) failed\n", path);
return -1;
}
if (symtab_process(m, start_addr, len, file_off, symbol_table,
num_symbols, dso) != 0)
{
return -1;
}
free(symbol_table);
/* if no regular symbols, try dynamic symbol table */
if (num_symbols == 0) {
if ((storage = bfd_get_dynamic_symtab_upper_bound(f)) < 0) {
fprintf(stderr, "map_load_exec: bfd_get_dynamic_symtab_upper_bound(%s) failed\n", path);
return -1;
}
if ((symbol_table = malloc(storage)) == NULL) {
perror("map_load_exec: malloc failed");
return -1;
}
if ((num_symbols = bfd_canonicalize_dynamic_symtab(f, symbol_table)) < 0) {
fprintf(stderr, "map_load_exec: bfd_canonicalize_symtab(%s) failed\n", path);
return -1;
}
if (symtab_process(m, start_addr, len, file_off, symbol_table,
num_symbols, dso) != 0)
{
return -1;
}
free(symbol_table);
}
bfd_close(f);
return 0;
}
/** Handle builtin kernel objects, primarily [vdso] */
static int map_load_builtin(struct symbol_map *m, const char *path,
uint64_t start_addr, uint64_t len, uint64_t file_off)
{
char pathbuf[128];
struct utsname utsname;
if (!strcmp(path, "[vdso]")) {
if (uname(&utsname) != 0) {
perror("map_load_builtin: uname failed");
goto out;
}
snprintf(pathbuf, 128, "/lib/modules/%s/vdso/vdso64.so", utsname.release);
if (map_load_exec(m, pathbuf, start_addr, len, file_off) != 0) {
fprintf(stderr, "map_load_builtin: loading vdso from %s failed\n", path);
goto out;
}
return 0;
}
out:
return symmap_add(m, start_addr, path, strdup(path));
}
/** Find or create symbol map for pid */
static struct symbol_map *pid_map(pid_t pid)
{
struct symbol_map *m;
char path[64];
FILE *f;
char *p, *q, *l = NULL;
size_t l_len = 0;
ssize_t ret;
unsigned long start_addr, end_addr, off;
for (m = pid_maps; m != NULL; m = m->next) {
if (m->pid == pid) {
return m;
}
}
if ((m = symmap_alloc()) == NULL) {
fprintf(stderr, "alloc failed {%s}\n", path);
return NULL;
}
/* parse /proc/PID/maps to load symbols from all objects */
sprintf(path, "/proc/%u/maps", pid);
if ((f = fopen(path, "r")) == NULL) {
fprintf(stderr, "fopen failed {%s}\n", path);
return NULL;
}
while ((ret = getline(&l, &l_len, f)) > 0) {
start_addr = strtoul(l, &p, 16);
/* eat dash */
if (*p != '-') {
perror("kallsyms: unexpected format (1)");
abort();
}
p++;
end_addr = strtoul(p, &p, 16);
/* eat space */
if (*p != ' ') {
perror("kallsyms: unexpected format (2)");
abort();
}
p++;
/* skip non-executable mappings */
if (p[2] != 'x')
continue;
p+= 4;
/* eat space */
if (*p != ' ') {
perror("kallsyms: unexpected format (3)");
abort();
}
p++;
/* parse file offset */
off = strtoul(p, &p, 16);
/* eat space */
if (*p != ' ') {
perror("kallsyms: unexpected format (4)");
abort();
}
p++;
/* skip device information */
p+= 5;
/* eat space */
if (*p != ' ') {
perror("kallsyms: unexpected format (5)");
abort();
}
p++;
/* skip number */
while (isdigit(*p))
p++;
/* skip spaces */
while (isspace(*p))
p++;
/* find end of file name */
q = p;
while (*q != 0 && !isspace(*q)) q++;
*q = 0;
/* skip non-files */
if (p[0] == '[')
map_load_builtin(m, p, start_addr, end_addr - start_addr, off);
else
map_load_exec(m, p, start_addr, end_addr - start_addr, off);
}
fclose(f);
/* sort symbols */
symmap_sort(m);
m->pid = pid;
m->next = pid_maps;
pid_maps = m;
return m;
}
/** Lookup symbol */
int symbols_lookup(pid_t pid, uint64_t ip, char **sym, char **dso,
uint64_t *off)
{
struct symbol *s;
struct symbol_map *m;
/* first try kernel map, then process map */
if ((s = symmap_lookup(kernel_map, ip)) == NULL) {
if (pid == 0)
return -1;
if ((m = pid_map(pid)) == NULL) {
perror("symbols_lookup: finding pidmap failed");
return -1;
}
if ((s = symmap_lookup(m, ip)) == NULL) {
return -1;
}
}
*sym = s->symname;
*dso = s->dso;
*off = ip - s->ip;
return 0;
}
/** Initialize symbol maps */
int symbols_init(void)
{
kernel_map = kernel_symbols();
bfd_init();
return 0;
}