diff options
Diffstat (limited to 'libdwfl/relocate.c')
-rw-r--r-- | libdwfl/relocate.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/libdwfl/relocate.c b/libdwfl/relocate.c new file mode 100644 index 00000000..41de6d18 --- /dev/null +++ b/libdwfl/relocate.c @@ -0,0 +1,293 @@ +/* Relocate debug information. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +typedef uint8_t GElf_Byte; + +/* Adjust *VALUE to add the load address of the SHNDX section. + We update the section header in place to cache the result. */ + +Dwfl_Error +internal_function_def +__libdwfl_relocate_value (Dwfl_Module *mod, size_t symshstrndx, + Elf32_Word shndx, GElf_Addr *value) +{ + Elf_Scn *refscn = elf_getscn (mod->symfile->elf, shndx); + GElf_Shdr refshdr_mem, *refshdr = gelf_getshdr (refscn, &refshdr_mem); + if (refshdr == NULL) + return DWFL_E_LIBELF; + + if ((refshdr->sh_flags & SHF_ALLOC) && refshdr->sh_addr == 0) + { + /* This is a loaded section. Find its actual + address and update the section header. */ + const char *name = elf_strptr (mod->symfile->elf, symshstrndx, + refshdr->sh_name); + if (name == NULL) + return DWFL_E_LIBELF; + + if ((*mod->dwfl->callbacks->section_address) (MODCB_ARGS (mod), name, + &refshdr->sh_addr)) + return CBFAIL; + + if (refshdr->sh_addr == 0) + /* The callback resolved this to zero, indicating it wasn't + really loaded but we don't really care. Mark it so we + don't check it again for the next relocation. */ + refshdr->sh_flags &= ~SHF_ALLOC; + + /* Update the in-core file's section header to show the final + load address (or unloadedness). This serves as a cache, + so we won't get here again for the same section. */ + if (! gelf_update_shdr (refscn, refshdr)) + return DWFL_E_LIBELF; + } + + /* Apply the adjustment. */ + *value += refshdr->sh_addr; + return DWFL_E_NOERROR; +} + +Dwfl_Error +internal_function_def +__libdwfl_relocate (Dwfl_Module *mod) +{ + assert (mod->isrel); + + GElf_Ehdr ehdr_mem; + const GElf_Ehdr *ehdr = gelf_getehdr (mod->debug.elf, &ehdr_mem); + if (ehdr == NULL) + return DWFL_E_LIBELF; + + size_t symshstrndx, d_shstrndx; + if (elf_getshstrndx (mod->symfile->elf, &symshstrndx) < 0) + return DWFL_E_LIBELF; + if (mod->symfile == &mod->debug) + d_shstrndx = symshstrndx; + else if (elf_getshstrndx (mod->debug.elf, &d_shstrndx) < 0) + return DWFL_E_LIBELF; + + /* Look at each section in the debuginfo file, and process the + relocation sections for debugging sections. */ + Dwfl_Error result = DWFL_E_NO_DWARF; + Elf_Scn *scn = NULL; + while ((scn = elf_nextscn (mod->debug.elf, scn)) != NULL) + { + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + + if (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) + { + /* It's a relocation section. First, fetch the name of the + section these relocations apply to. */ + + Elf_Scn *tscn = elf_getscn (mod->debug.elf, shdr->sh_info); + if (tscn == NULL) + return DWFL_E_LIBELF; + + GElf_Shdr tshdr_mem; + GElf_Shdr *tshdr = gelf_getshdr (tscn, &tshdr_mem); + const char *tname = elf_strptr (mod->debug.elf, d_shstrndx, + tshdr->sh_name); + if (tname == NULL) + return DWFL_E_LIBELF; + + if (! ebl_debugscn_p (mod->ebl, tname)) + /* This relocation section is not for a debugging section. + Nothing to do here. */ + continue; + + /* Fetch the section data that needs the relocations applied. */ + Elf_Data *tdata = elf_rawdata (tscn, NULL); + if (tdata == NULL) + return DWFL_E_LIBELF; + + /* Apply one relocation. Returns true for any invalid data. */ + Dwfl_Error relocate (GElf_Addr offset, const GElf_Sxword *addend, + int rtype, int symndx) + { + /* First, resolve the symbol to an absolute value. */ + GElf_Addr value; + inline Dwfl_Error adjust (GElf_Word shndx) + { + return __libdwfl_relocate_value (mod, symshstrndx, + shndx, &value); + } + + if (symndx == STN_UNDEF) + /* When strip removes a section symbol referring to a + section moved into the debuginfo file, it replaces + that symbol index in relocs with STN_UNDEF. We + don't actually need the symbol, because those relocs + are always references relative to the nonallocated + debugging sections, which start at zero. */ + value = 0; + else + { + GElf_Sym sym_mem; + GElf_Word shndx; + GElf_Sym *sym = gelf_getsymshndx (mod->symdata, + mod->symxndxdata, + symndx, &sym_mem, + &shndx); + if (sym == NULL) + return DWFL_E_LIBELF; + + value = sym->st_value; + if (sym->st_shndx != SHN_XINDEX) + shndx = sym->st_shndx; + switch (shndx) + { + case SHN_ABS: + break; + + case SHN_UNDEF: + case SHN_COMMON: + return DWFL_E_RELUNDEF; + + default: + { + Dwfl_Error error = adjust (shndx); + if (error != DWFL_E_NOERROR) + return error; + break; + } + } + } + + /* These are the types we can relocate. */ +#define TYPES DO_TYPE (BYTE, Byte) DO_TYPE (HALF, Half) \ + DO_TYPE (WORD, Word) DO_TYPE (SWORD, Sword) \ + DO_TYPE (XWORD, Xword) DO_TYPE (SXWORD, Sxword) + size_t size; + Elf_Type type = ebl_reloc_simple_type (mod->ebl, rtype); + switch (type) + { +#define DO_TYPE(NAME, Name) \ + case ELF_T_##NAME: \ + size = sizeof (GElf_##Name); \ + break; + TYPES +#undef DO_TYPE + default: + return DWFL_E_BADRELTYPE; + } + + if (offset + size >= tdata->d_size) + return DWFL_E_BADRELOFF; + +#define DO_TYPE(NAME, Name) GElf_##Name Name; + union { TYPES } tmpbuf; +#undef DO_TYPE + Elf_Data tmpdata = + { + .d_type = type, + .d_buf = &tmpbuf, + .d_size = size, + .d_version = EV_CURRENT, + }; + Elf_Data rdata = + { + .d_type = type, + .d_buf = tdata->d_buf + offset, + .d_size = size, + .d_version = EV_CURRENT, + }; + + /* XXX check for overflow? */ + if (addend) + { + /* For the addend form, we have the value already. */ + value += *addend; + switch (type) + { +#define DO_TYPE(NAME, Name) \ + case ELF_T_##NAME: \ + tmpbuf.Name = value; \ + break; + TYPES +#undef DO_TYPE + default: + abort (); + } + } + else + { + /* Extract the original value and apply the reloc. */ + Elf_Data *d = gelf_xlatetom (mod->main.elf, &tmpdata, &rdata, + ehdr->e_ident[EI_DATA]); + if (d == NULL) + return DWFL_E_LIBELF; + assert (d == &tmpdata); + switch (type) + { +#define DO_TYPE(NAME, Name) \ + case ELF_T_##NAME: \ + tmpbuf.Name += (GElf_##Name) value; \ + break; + TYPES +#undef DO_TYPE + default: + abort (); + } + } + + /* Now convert the relocated datum back to the target + format. This will write into rdata.d_buf, which + points into the raw section data being relocated. */ + Elf_Data *s = gelf_xlatetof (mod->main.elf, &rdata, &tmpdata, + ehdr->e_ident[EI_DATA]); + if (s == NULL) + return DWFL_E_LIBELF; + assert (s == &rdata); + + /* We have applied this relocation! */ + return DWFL_E_NOERROR; + } + + /* Fetch the relocation section and apply each reloc in it. */ + Elf_Data *reldata = elf_getdata (scn, NULL); + if (reldata == NULL) + return DWFL_E_LIBELF; + + result = DWFL_E_NOERROR; + size_t nrels = shdr->sh_size / shdr->sh_entsize; + if (shdr->sh_type == SHT_REL) + for (size_t relidx = 0; !result && relidx < nrels; ++relidx) + { + GElf_Rel rel_mem, *r = gelf_getrel (reldata, relidx, &rel_mem); + if (r == NULL) + return DWFL_E_LIBELF; + result = relocate (r->r_offset, NULL, + GELF_R_TYPE (r->r_info), + GELF_R_SYM (r->r_info)); + } + else + for (size_t relidx = 0; !result && relidx < nrels; ++relidx) + { + GElf_Rela rela_mem, *r = gelf_getrela (reldata, relidx, + &rela_mem); + if (r == NULL) + return DWFL_E_LIBELF; + result = relocate (r->r_offset, &r->r_addend, + GELF_R_TYPE (r->r_info), + GELF_R_SYM (r->r_info)); + } + if (result != DWFL_E_NOERROR) + break; + } + } + + return result; +} |