Skip to content
Snippets Groups Projects
Forked from Antoine Kaufmann / perf-profiler
2 commits behind the upstream repository.
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;
}