diff options
Diffstat (limited to 'src/backtrace.c')
-rw-r--r-- | src/backtrace.c | 390 |
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, ""); +} |