#include #include #include #include #include #include #include #include #include #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, ""); }