summaryrefslogtreecommitdiffstats
path: root/src/backtrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backtrace.c')
-rw-r--r--src/backtrace.c390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/backtrace.c b/src/backtrace.c
new file mode 100644
index 0000000..fb9c060
--- /dev/null
+++ b/src/backtrace.c
@@ -0,0 +1,390 @@
+#include <link.h>
+#include <elf.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "config.h"
+#include "backtrace.h"
+
+extern __thread int pipe_fd;
+
+static struct bt_map* maps;
+
+#ifdef CONFIG_LIBELF
+static void symbols_destroy(struct bt_map *map)
+{
+ struct bt_symbol* next = map->symbols;
+
+ do {
+ struct bt_symbol *symbol = next;
+ next = symbol->next;
+ free(symbol->name);
+ free(symbol);
+ } while(next);
+}
+
+static Elf_Scn *get_section(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;
+}
+
+static int sym_type(GElf_Sym *sym)
+{
+ return GELF_ST_TYPE(sym->st_info);
+}
+
+static int sym_is_function(GElf_Sym *sym)
+{
+ return sym_type(sym) == STT_FUNC &&
+ sym->st_name != 0 &&
+ sym->st_shndx != SHN_UNDEF;
+}
+
+static int sym_is_label(GElf_Sym *sym)
+{
+ return sym_type(sym) == STT_NOTYPE &&
+ sym->st_name != 0 &&
+ sym->st_shndx != SHN_UNDEF &&
+ sym->st_shndx != SHN_ABS;
+}
+
+static char *sec_name(GElf_Shdr *shdr, Elf_Data *secstrs)
+{
+ return secstrs->d_buf + shdr->sh_name;
+}
+
+static char *sym_name(GElf_Sym *sym, Elf_Data *symstrs)
+{
+ return symstrs->d_buf + sym->st_name;
+}
+
+static struct bt_symbol* __get_symbols(struct lt_config_shared *cfg,
+ Elf *elf, GElf_Ehdr *ehdr,
+ Elf_Scn *sec, GElf_Shdr *shdr)
+{
+ Elf_Data *syms, *symstrs, *secstrs;
+ Elf_Scn *sec_str, *sec_strndx;
+ GElf_Sym sym;
+ struct bt_symbol *sym_last = NULL;
+ uint32_t idx, nr_syms;
+
+ syms = elf_getdata(sec, NULL);
+ if (!syms) {
+ PRINT_VERBOSE(cfg, 1, "elf_getdata failed\n");
+ return NULL;
+ }
+
+ sec_str = elf_getscn(elf, shdr->sh_link);
+ if (!sec_str) {
+ PRINT_VERBOSE(cfg, 1, "no strtab\n");
+ return NULL;
+ }
+
+ symstrs = elf_getdata(sec_str, NULL);
+ if (!symstrs) {
+ PRINT_VERBOSE(cfg, 1, "no strtab\n");
+ return NULL;
+ }
+
+ sec_strndx = elf_getscn(elf, ehdr->e_shstrndx);
+ if (!sec_strndx) {
+ PRINT_VERBOSE(cfg, 1, "no strndx\n");
+ return NULL;
+ }
+
+ secstrs = elf_getdata(sec_strndx, NULL);
+ if (!secstrs) {
+ PRINT_VERBOSE(cfg, 1, "no strndx data\n");
+ return NULL;
+ }
+
+ nr_syms = shdr->sh_size / shdr->sh_entsize;
+
+ for (idx = 0, gelf_getsym(syms, idx, &sym);
+ idx < nr_syms;
+ idx++, gelf_getsym(syms, idx, &sym)) {
+ Elf_Scn *sec_code;
+ GElf_Shdr shdr_code;
+ struct bt_symbol *symbol;
+
+ if (!sym_is_label(&sym) && !sym_is_function(&sym))
+ continue;
+
+ sec_code = elf_getscn(elf, sym.st_shndx);
+ if (!sec_code) {
+ PRINT_VERBOSE(cfg, 1, "failed to get symbol section\n");
+ return NULL;
+ }
+
+ gelf_getshdr(sec_code, &shdr_code);
+
+ if (!strstr(sec_name(&shdr_code, secstrs), "text")) {
+ PRINT_VERBOSE(cfg, 1, "text section expected, got '%s'\n",
+ sec_name(&shdr_code, secstrs));
+ continue;
+ }
+
+ symbol = malloc(sizeof(*symbol));
+ if (!symbol) {
+ PRINT_VERBOSE(cfg, 1, "failed to allocate symbol\n");
+ return NULL;
+ }
+
+ symbol->start = sym.st_value;
+ symbol->end = symbol->start + sym.st_size;
+ symbol->name = strdup(sym_name(&sym, symstrs));
+ symbol->next = sym_last;
+
+ sym_last = symbol;
+
+ PRINT_VERBOSE(cfg, 1, "adding symbol %s, %lx, %lx\n",
+ symbol->name, symbol->start, symbol->end);
+ }
+
+ return sym_last;
+}
+
+static struct bt_symbol* get_symbols(struct lt_config_shared *cfg,
+ int fd, struct bt_map *map)
+{
+ Elf *elf;
+ GElf_Ehdr ehdr;
+ GElf_Shdr shdr_sym, shdr_dyn;
+ Elf_Scn *sec_sym, *sec_dyn;
+ struct bt_symbol *s = NULL;
+
+ PRINT_VERBOSE(cfg, 1, "map1 %s\n", map->name);
+
+ elf_version(EV_CURRENT);
+
+ elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
+ if (!elf) {
+ PRINT_VERBOSE(cfg, 1, "elf_begin failed: %s\n",
+ elf_errmsg(elf_errno()));
+ return NULL;
+ }
+
+ PRINT_VERBOSE(cfg, 1, "map2 %s\n", map->name);
+
+ do {
+ if (gelf_getehdr(elf, &ehdr) == NULL) {
+ PRINT_VERBOSE(cfg, 1, "gelf_getehdr failed\n");
+ break;
+ }
+
+ sec_sym = get_section(elf, &ehdr, &shdr_sym, ".symtab", NULL);
+ sec_dyn = get_section(elf, &ehdr, &shdr_dyn, ".dynsym", NULL);
+
+ if (!sec_sym && !sec_dyn) {
+ PRINT_VERBOSE(cfg, 1, "no symbols\n");
+ break;
+ }
+
+ PRINT_VERBOSE(cfg, 1, "map %s\n", map->name);
+
+ if (sec_sym)
+ s = __get_symbols(cfg, elf, &ehdr,
+ sec_sym, &shdr_sym);
+
+ map->symbols = s;
+
+ if (sec_dyn) {
+ s = __get_symbols(cfg, elf, &ehdr,
+ sec_dyn, &shdr_dyn);
+ if (s) {
+ s->next = map->symbols;
+ map->symbols = s;
+ }
+ }
+ PRINT_VERBOSE(cfg, 1, "map %s, symbols %p\n", map->name, map->symbols);
+ } while(0);
+
+ elf_end(elf);
+ return s;
+}
+
+static int symbols_init(struct lt_config_shared *cfg, struct bt_map *map)
+{
+ int fd;
+
+ fd = open(map->name, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ map->symbols = get_symbols(cfg, fd, map);
+
+ close(fd);
+
+ return map->symbols ? 0 : -1;
+}
+
+int bt_find_symbol(struct lt_config_shared *cfg, struct bt_symbol **sym,
+ struct bt_map *map, bt_word_t ip)
+{
+ struct bt_symbol* symbol = map->symbols;
+
+ PRINT_VERBOSE(cfg, 1, "looking for ip %lx, %p, %p\n", ip, map, symbol);
+
+ while(symbol) {
+ PRINT_VERBOSE(cfg, 1, "looking for ip %lx, start %lx, size %lx, name %s\n",
+ ip, symbol->start, symbol->end, symbol->name);
+
+ if (ip >= symbol->start && ip <= symbol->end) {
+ *sym = symbol;
+ return 1;
+ }
+ symbol = symbol->next;
+ }
+
+ PRINT_VERBOSE(cfg, 1, "looking for ip %lx, %s\n", ip, map->name);
+ return 0;
+}
+#else
+int bt_find_symbol(struct lt_config_shared *cfg, struct bt_symbol **sym,
+ struct bt_map *map, bt_word_t ip)
+{
+ return 0;
+}
+
+static int symbols_init(struct lt_config_shared *cfg, struct bt_map *map)
+{
+ return 0;
+}
+
+static void symbols_destroy(struct bt_map *map) { }
+#endif /* CONFIG_LIBELF */
+
+int bt_find_map(struct lt_config_shared *cfg, struct bt_map **map,
+ bt_word_t ip)
+{
+ struct bt_map* m = maps;
+
+ PRINT_VERBOSE(cfg, 1, "ip %lx\n", ip);
+
+ while(m) {
+ PRINT_VERBOSE(cfg, 1, "ip %lx, start %lx. end %lx\n", ip, m->start, m->end);
+ if (ip >= m->start && ip <= m->end) {
+ PRINT_VERBOSE(cfg, 1, "found map %p\n", m);
+ *map = m;
+ return 1;
+ }
+ m = m->next;
+ }
+
+ PRINT_VERBOSE(cfg, 1, "not found\n");
+ return 0;
+}
+
+/* TODO erro handling */
+void bt_display(struct lt_config_shared *cfg, char *text)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+
+ if (lt_sh(cfg, pipe)) {
+ char buf[LT_FIFO_MSG_MAXLEN];
+ int len;
+
+ len = lt_fifo_mtext_get(cfg, buf, &tv, text);
+ lt_fifo_send(cfg, pipe_fd, buf, len);
+ } else
+ lt_out_text(cfg, &tv, syscall(SYS_gettid), text);
+}
+
+static int maps_callback(struct dl_phdr_info *info,
+ size_t size, void *data)
+{
+ struct lt_config_shared *cfg = data;
+ const ElfW(Phdr) *phdr = info->dlpi_phdr;
+ bt_word_t load_base = info->dlpi_addr;
+ long n;
+
+ for (n = info->dlpi_phnum; --n >= 0; phdr++) {
+ struct bt_map *map;
+
+ if ((phdr->p_type != PT_LOAD) ||
+ (!(phdr->p_flags & PF_X)))
+ continue;
+
+ map = malloc(sizeof(*map));
+ if (!map)
+ return -1;
+
+ map->base = load_base;
+ map->start = phdr->p_vaddr + load_base;
+ map->end = map->start + phdr->p_memsz;
+ map->name = (char*) info->dlpi_name;
+ map->symbols = NULL;
+
+ map->next = maps;
+ maps = map;
+
+ if (symbols_init(cfg, map))
+ PRINT_VERBOSE(cfg, 1,
+ "failed to build symbols for %s\n",
+ map->name);
+
+ PRINT_VERBOSE(cfg, 1, "adding map %p, %p - %p @ %s\n",
+ map, map->start, map->end, map->name);
+ }
+
+ return 0;
+}
+
+static int maps_init(struct lt_config_shared *cfg)
+{
+ return dl_iterate_phdr(maps_callback, cfg);
+}
+
+static void maps_destroy(struct lt_config_shared *cfg)
+{
+ struct bt_map* next = maps;
+
+ do {
+ struct bt_map *map = next;
+ next = map->next;
+
+ symbols_destroy(map);
+ free(map);
+ } while(next);
+}
+
+void dump_backtrace(struct lt_config_shared *cfg, unsigned long flags)
+{
+ /* moving on even if we fail here */
+ if (maps_init(cfg))
+ bt_display(cfg, "failed to build maps");
+
+ if (bt_dump(cfg, flags))
+ bt_display(cfg, "dump failed");
+
+ if (BT_FLAG_DESTROY && flags)
+ maps_destroy(cfg);
+
+ /* empty line after the backtrace */
+ bt_display(cfg, "");
+}