int internal_function __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) { size_t shstrndx = SHN_UNDEF; int result = 0; Elf_Scn *scn = elf_nextscn (elf, NULL); if (scn == NULL) { /* No sections, have to look for phdrs. */ GElf_Ehdr ehdr_mem; GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); size_t phnum; if (unlikely (ehdr == NULL) || unlikely (elf_getphdrnum (elf, &phnum) != 0)) { __libdwfl_seterrno (DWFL_E_LIBELF); return -1; } for (size_t i = 0; result == 0 && i < phnum; ++i) { GElf_Phdr phdr_mem; GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); if (likely (phdr != NULL) && phdr->p_type == PT_NOTE) result = check_notes (mod, set, elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz, ELF_T_NHDR), phdr->p_vaddr + mod->main.bias); } } else do { GElf_Shdr shdr_mem; GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); if (likely (shdr != NULL) && shdr->sh_type == SHT_NOTE) { /* Determine the right sh_addr in this module. */ GElf_Addr vaddr = 0; if (!(shdr->sh_flags & SHF_ALLOC)) vaddr = NO_VADDR; else if (mod->e_type != ET_REL) vaddr = shdr->sh_addr + mod->main.bias; else if (__libdwfl_relocate_value (mod, elf, &shstrndx, elf_ndxscn (scn), &vaddr)) vaddr = NO_VADDR; result = check_notes (mod, set, elf_getdata (scn, NULL), vaddr); } } while (result == 0 && (scn = elf_nextscn (elf, scn)) != NULL); return result; }
static int cache_sections (Dwfl_Module *mod) { struct secref *refs = NULL; size_t nrefs = 0; size_t shstrndx; if (unlikely (elf_getshdrstrndx (mod->main.elf, &shstrndx) < 0)) { elf_error: __libdwfl_seterrno (DWFL_E_LIBELF); return -1; } bool check_reloc_sections = false; Elf_Scn *scn = NULL; while ((scn = elf_nextscn (mod->main.elf, scn)) != NULL) { GElf_Shdr shdr_mem; GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); if (shdr == NULL) goto elf_error; if ((shdr->sh_flags & SHF_ALLOC) && shdr->sh_addr == 0 && mod->e_type == ET_REL) { /* This section might not yet have been looked at. */ if (__libdwfl_relocate_value (mod, mod->main.elf, &shstrndx, elf_ndxscn (scn), &shdr->sh_addr) != DWFL_E_NOERROR) continue; shdr = gelf_getshdr (scn, &shdr_mem); if (unlikely (shdr == NULL)) goto elf_error; } if (shdr->sh_flags & SHF_ALLOC) { const char *name = elf_strptr (mod->main.elf, shstrndx, shdr->sh_name); if (unlikely (name == NULL)) goto elf_error; struct secref *newref = alloca (sizeof *newref); newref->scn = scn; newref->relocs = NULL; newref->name = name; newref->start = dwfl_adjusted_address (mod, shdr->sh_addr); newref->end = newref->start + shdr->sh_size; newref->next = refs; refs = newref; ++nrefs; } if (mod->e_type == ET_REL && shdr->sh_size != 0 && (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) && mod->dwfl->callbacks->section_address != NULL) { if (shdr->sh_info < elf_ndxscn (scn)) { /* We've already looked at the section these relocs apply to. */ Elf_Scn *tscn = elf_getscn (mod->main.elf, shdr->sh_info); if (likely (tscn != NULL)) for (struct secref *sec = refs; sec != NULL; sec = sec->next) if (sec->scn == tscn) { sec->relocs = scn; break; } } else /* We'll have to do a second pass. */ check_reloc_sections = true; } } mod->reloc_info = malloc (offsetof (struct dwfl_relocation, refs[nrefs])); if (mod->reloc_info == NULL) { __libdwfl_seterrno (DWFL_E_NOMEM); return -1; } struct secref **sortrefs = alloca (nrefs * sizeof sortrefs[0]); for (size_t i = nrefs; i-- > 0; refs = refs->next) sortrefs[i] = refs; assert (refs == NULL); qsort (sortrefs, nrefs, sizeof sortrefs[0], &compare_secrefs); mod->reloc_info->count = nrefs; for (size_t i = 0; i < nrefs; ++i) { mod->reloc_info->refs[i].name = sortrefs[i]->name; mod->reloc_info->refs[i].scn = sortrefs[i]->scn; mod->reloc_info->refs[i].relocs = sortrefs[i]->relocs; mod->reloc_info->refs[i].start = sortrefs[i]->start; mod->reloc_info->refs[i].end = sortrefs[i]->end; } if (unlikely (check_reloc_sections)) { /* There was a reloc section that preceded its target section. So we have to scan again now that we have cached all the possible target sections we care about. */ scn = NULL; while ((scn = elf_nextscn (mod->main.elf, scn)) != NULL) { GElf_Shdr shdr_mem; GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); if (shdr == NULL) goto elf_error; if (shdr->sh_size != 0 && (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA)) { Elf_Scn *tscn = elf_getscn (mod->main.elf, shdr->sh_info); if (likely (tscn != NULL)) for (size_t i = 0; i < nrefs; ++i) if (mod->reloc_info->refs[i].scn == tscn) { mod->reloc_info->refs[i].relocs = scn; break; } } } } return nrefs; }
internal_function __libdwfl_getsym (Dwfl_Module *mod, int ndx, GElf_Sym *sym, GElf_Addr *addr, GElf_Word *shndxp, Elf **elfp, Dwarf_Addr *biasp, bool *resolved, bool adjust_st_value) { if (unlikely (mod == NULL)) return NULL; if (unlikely (mod->symdata == NULL)) { int result = INTUSE(dwfl_module_getsymtab) (mod); if (result < 0) return NULL; } /* All local symbols should come before all global symbols. If we have an auxiliary table make sure all the main locals come first, then all aux locals, then all main globals and finally all aux globals. And skip the auxiliary table zero undefined entry. */ GElf_Word shndx; int tndx = ndx; int skip_aux_zero = (mod->syments > 0 && mod->aux_syments > 0) ? 1 : 0; Elf *elf; Elf_Data *symdata; Elf_Data *symxndxdata; Elf_Data *symstrdata; if (mod->aux_symdata == NULL || ndx < mod->first_global) { /* main symbol table (locals). */ tndx = ndx; elf = mod->symfile->elf; symdata = mod->symdata; symxndxdata = mod->symxndxdata; symstrdata = mod->symstrdata; } else if (ndx < mod->first_global + mod->aux_first_global - skip_aux_zero) { /* aux symbol table (locals). */ tndx = ndx - mod->first_global + skip_aux_zero; elf = mod->aux_sym.elf; symdata = mod->aux_symdata; symxndxdata = mod->aux_symxndxdata; symstrdata = mod->aux_symstrdata; } else if ((size_t) ndx < mod->syments + mod->aux_first_global - skip_aux_zero) { /* main symbol table (globals). */ tndx = ndx - mod->aux_first_global + skip_aux_zero; elf = mod->symfile->elf; symdata = mod->symdata; symxndxdata = mod->symxndxdata; symstrdata = mod->symstrdata; } else { /* aux symbol table (globals). */ tndx = ndx - mod->syments + skip_aux_zero; elf = mod->aux_sym.elf; symdata = mod->aux_symdata; symxndxdata = mod->aux_symxndxdata; symstrdata = mod->aux_symstrdata; } sym = gelf_getsymshndx (symdata, symxndxdata, tndx, sym, &shndx); if (unlikely (sym == NULL)) { __libdwfl_seterrno (DWFL_E_LIBELF); return NULL; } if (sym->st_shndx != SHN_XINDEX) shndx = sym->st_shndx; /* Figure out whether this symbol points into an SHF_ALLOC section. */ bool alloc = true; if ((shndxp != NULL || mod->e_type != ET_REL) && (sym->st_shndx == SHN_XINDEX || (sym->st_shndx < SHN_LORESERVE && sym->st_shndx != SHN_UNDEF))) { GElf_Shdr shdr_mem; GElf_Shdr *shdr = gelf_getshdr (elf_getscn (elf, shndx), &shdr_mem); alloc = unlikely (shdr == NULL) || (shdr->sh_flags & SHF_ALLOC); } /* In case of an value in an allocated section the main Elf Ebl might know where the real value is (e.g. for function descriptors). */ char *ident; GElf_Addr st_value = sym->st_value & ebl_func_addr_mask (mod->ebl); *resolved = false; if (! adjust_st_value && mod->e_type != ET_REL && alloc && (GELF_ST_TYPE (sym->st_info) == STT_FUNC || (GELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC && (ident = elf_getident (elf, NULL)) != NULL && ident[EI_OSABI] == ELFOSABI_LINUX))) { if (likely (__libdwfl_module_getebl (mod) == DWFL_E_NOERROR)) { if (elf != mod->main.elf) { st_value = dwfl_adjusted_st_value (mod, elf, st_value); st_value = dwfl_deadjust_st_value (mod, mod->main.elf, st_value); } *resolved = ebl_resolve_sym_value (mod->ebl, &st_value); if (! *resolved) st_value = sym->st_value; } } if (shndxp != NULL) /* Yield -1 in case of a non-SHF_ALLOC section. */ *shndxp = alloc ? shndx : (GElf_Word) -1; switch (sym->st_shndx) { case SHN_ABS: /* XXX sometimes should use bias?? */ case SHN_UNDEF: case SHN_COMMON: break; default: if (mod->e_type == ET_REL) { /* In an ET_REL file, the symbol table values are relative to the section, not to the module's load base. */ size_t symshstrndx = SHN_UNDEF; Dwfl_Error result = __libdwfl_relocate_value (mod, elf, &symshstrndx, shndx, &st_value); if (unlikely (result != DWFL_E_NOERROR)) { __libdwfl_seterrno (result); return NULL; } } else if (alloc) /* Apply the bias to the symbol value. */ st_value = dwfl_adjusted_st_value (mod, *resolved ? mod->main.elf : elf, st_value); break; } if (adjust_st_value) sym->st_value = st_value; if (addr != NULL) *addr = st_value; if (unlikely (sym->st_name >= symstrdata->d_size)) { __libdwfl_seterrno (DWFL_E_BADSTROFF); return NULL; } if (elfp) *elfp = elf; if (biasp) *biasp = dwfl_adjusted_st_value (mod, elf, 0); return (const char *) symstrdata->d_buf + sym->st_name; }
/* Handle an undefined symbol. We really only support ET_REL for Linux kernel modules, and offline archives. The behavior of the Linux module loader is very simple and easy to mimic. It only matches magically exported symbols, and we match any defined symbols. But we get the same answer except when the module's symbols are undefined and would prevent it from being loaded. */ static Dwfl_Error resolve_symbol (Dwfl_Module *referer, struct reloc_symtab_cache *symtab, GElf_Sym *sym, GElf_Word shndx) { /* First we need its name. */ if (sym->st_name != 0) { if (symtab->symstrdata == NULL) { /* Cache the strtab for this symtab. */ assert (referer->symfile == NULL || referer->symfile->elf != symtab->symelf); symtab->symstrdata = elf_getdata (elf_getscn (symtab->symelf, symtab->strtabndx), NULL); if (unlikely (symtab->symstrdata == NULL)) return DWFL_E_LIBELF; } if (unlikely (sym->st_name >= symtab->symstrdata->d_size)) return DWFL_E_BADSTROFF; const char *name = symtab->symstrdata->d_buf; name += sym->st_name; for (Dwfl_Module *m = referer->dwfl->modulelist; m != NULL; m = m->next) if (m != referer) { /* Get this module's symtab. If we got a fresh error reading the table, report it. If we just have no symbols in this module, no harm done. */ if (m->symdata == NULL && m->symerr == DWFL_E_NOERROR && INTUSE(dwfl_module_getsymtab) (m) < 0 && m->symerr != DWFL_E_NO_SYMTAB) return m->symerr; for (size_t ndx = 1; ndx < m->syments; ++ndx) { sym = gelf_getsymshndx (m->symdata, m->symxndxdata, ndx, sym, &shndx); if (unlikely (sym == NULL)) return DWFL_E_LIBELF; if (sym->st_shndx != SHN_XINDEX) shndx = sym->st_shndx; /* We are looking for a defined global symbol with a name. */ if (shndx == SHN_UNDEF || shndx == SHN_COMMON || GELF_ST_BIND (sym->st_info) == STB_LOCAL || sym->st_name == 0) continue; /* Get this candidate symbol's name. */ if (unlikely (sym->st_name >= m->symstrdata->d_size)) return DWFL_E_BADSTROFF; const char *n = m->symstrdata->d_buf; n += sym->st_name; /* Does the name match? */ if (strcmp (name, n)) continue; /* We found it! */ if (shndx == SHN_ABS) /* XXX maybe should apply bias? */ return DWFL_E_NOERROR; if (m->e_type != ET_REL) { sym->st_value = dwfl_adjusted_st_value (m, sym->st_value); return DWFL_E_NOERROR; } /* In an ET_REL file, the symbol table values are relative to the section, not to the module's load base. */ size_t symshstrndx = SHN_UNDEF; return __libdwfl_relocate_value (m, m->symfile->elf, &symshstrndx, shndx, &sym->st_value); } } } return DWFL_E_RELUNDEF; }
/* This is just doing dwfl_module_getsym, except that we must always use the symbol table in RELOCATED itself when it has one, not MOD->symfile. */ static Dwfl_Error relocate_getsym (Dwfl_Module *mod, Elf *relocated, struct reloc_symtab_cache *cache, int symndx, GElf_Sym *sym, GElf_Word *shndx) { if (cache->symdata == NULL) { if (mod->symfile == NULL || mod->symfile->elf != relocated) { /* We have to look up the symbol table in the file we are relocating, if it has its own. These reloc sections refer to the symbol table in this file, and a symbol table in the main file might not match. However, some tools did produce ET_REL .debug files with relocs but no symtab of their own. */ Elf_Scn *scn = NULL; while ((scn = elf_nextscn (relocated, scn)) != NULL) { GElf_Shdr shdr_mem, *shdr = gelf_getshdr (scn, &shdr_mem); if (shdr != NULL) switch (shdr->sh_type) { default: continue; case SHT_SYMTAB: cache->symelf = relocated; cache->symdata = elf_getdata (scn, NULL); cache->strtabndx = shdr->sh_link; if (unlikely (cache->symdata == NULL)) return DWFL_E_LIBELF; break; case SHT_SYMTAB_SHNDX: cache->symxndxdata = elf_getdata (scn, NULL); if (unlikely (cache->symxndxdata == NULL)) return DWFL_E_LIBELF; break; } if (cache->symdata != NULL && cache->symxndxdata != NULL) break; } } if (cache->symdata == NULL) { /* We might not have looked for a symbol table file yet, when coming from __libdwfl_relocate_section. */ if (unlikely (mod->symfile == NULL) && unlikely (INTUSE(dwfl_module_getsymtab) (mod) < 0)) return dwfl_errno (); /* The symbol table we have already cached is the one from the file being relocated, so it's what we need. Or else this is an ET_REL .debug file with no .symtab of its own; the symbols refer to the section indices in the main file. */ cache->symelf = mod->symfile->elf; cache->symdata = mod->symdata; cache->symxndxdata = mod->symxndxdata; cache->symstrdata = mod->symstrdata; } } if (unlikely (gelf_getsymshndx (cache->symdata, cache->symxndxdata, symndx, sym, shndx) == NULL)) return DWFL_E_LIBELF; if (sym->st_shndx != SHN_XINDEX) *shndx = sym->st_shndx; switch (sym->st_shndx) { case SHN_ABS: case SHN_UNDEF: return DWFL_E_NOERROR; case SHN_COMMON: sym->st_value = 0; /* Value is size, not helpful. */ return DWFL_E_NOERROR; } return __libdwfl_relocate_value (mod, cache->symelf, &cache->symshstrndx, *shndx, &sym->st_value); }
const char * dwfl_module_getsym (Dwfl_Module *mod, int ndx, GElf_Sym *sym, GElf_Word *shndxp) { if (unlikely (mod == NULL)) return NULL; if (unlikely (mod->symdata == NULL)) { int result = INTUSE(dwfl_module_getsymtab) (mod); if (result < 0) return NULL; } GElf_Word shndx; sym = gelf_getsymshndx (mod->symdata, mod->symxndxdata, ndx, sym, &shndx); if (unlikely (sym == NULL)) { __libdwfl_seterrno (DWFL_E_LIBELF); return NULL; } if (sym->st_shndx != SHN_XINDEX) shndx = sym->st_shndx; /* Figure out whether this symbol points into an SHF_ALLOC section. */ bool alloc = true; if ((shndxp != NULL || mod->e_type != ET_REL) && (sym->st_shndx == SHN_XINDEX || (sym->st_shndx < SHN_LORESERVE && sym->st_shndx != SHN_UNDEF))) { GElf_Shdr shdr_mem; GElf_Shdr *shdr = gelf_getshdr (elf_getscn (mod->symfile->elf, shndx), &shdr_mem); alloc = unlikely (shdr == NULL) || (shdr->sh_flags & SHF_ALLOC); } if (shndxp != NULL) /* Yield -1 in case of a non-SHF_ALLOC section. */ *shndxp = alloc ? shndx : (GElf_Word) -1; switch (sym->st_shndx) { case SHN_ABS: /* XXX sometimes should use bias?? */ case SHN_UNDEF: case SHN_COMMON: break; default: if (mod->e_type == ET_REL) { /* In an ET_REL file, the symbol table values are relative to the section, not to the module's load base. */ size_t symshstrndx = SHN_UNDEF; Dwfl_Error result = __libdwfl_relocate_value (mod, mod->symfile->elf, &symshstrndx, shndx, &sym->st_value); if (unlikely (result != DWFL_E_NOERROR)) { __libdwfl_seterrno (result); return NULL; } } else if (alloc) /* Apply the bias to the symbol value. */ sym->st_value += mod->symfile->bias; break; } if (unlikely (sym->st_name >= mod->symstrdata->d_size)) { __libdwfl_seterrno (DWFL_E_BADSTROFF); return NULL; } return (const char *) mod->symstrdata->d_buf + sym->st_name; }