diff options
Diffstat (limited to 'src/reloc.c')
-rw-r--r-- | src/reloc.c | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/src/reloc.c b/src/reloc.c new file mode 100644 index 0000000..deb0abe --- /dev/null +++ b/src/reloc.c @@ -0,0 +1,427 @@ +/* Copyright (C) 2001, 2002, 2003, 2005 Red Hat, Inc. + Written by Jakub Jelinek <jakub@redhat.com>, 2001. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include <config.h> +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include "prelink.h" +#include "reloc.h" + +int +find_reloc_sections (DSO *dso, struct reloc_info *rinfo) +{ + int first, last, rela, i, pltfirst; + GElf_Addr start, end, pltstart, pltend; + + memset (rinfo, 0, sizeof (*rinfo)); + + if (dynamic_info_is_set (dso, DT_REL) + && dynamic_info_is_set (dso, DT_RELA)) + { + error (0, 0, "%s: Cannot prelink object with both DT_REL and DT_RELA tags", + dso->filename); + return 1; + } + + rela = dynamic_info_is_set (dso, DT_RELA); + + if (rela) + { + start = dso->info[DT_RELA]; + end = dso->info[DT_RELA] + dso->info[DT_RELASZ]; + } + else + { + start = dso->info[DT_REL]; + end = dso->info[DT_REL] + dso->info[DT_RELSZ]; + } + rinfo->reldyn_rela = rela; + + if (dso->info[DT_JMPREL]) + { + pltstart = dso->info[DT_JMPREL]; + pltend = dso->info[DT_JMPREL] + dso->info[DT_PLTRELSZ]; + pltfirst = first = addr_to_sec (dso, pltstart); + last = addr_to_sec (dso, pltend - 1); + if (first == -1 + || last == -1 + || first != last + || dso->shdr[first].sh_addr != pltstart + || dso->shdr[first].sh_addr + dso->shdr[first].sh_size != pltend + || (dso->info[DT_PLTREL] != DT_REL + && dso->info[DT_PLTREL] != DT_RELA) + || dso->shdr[first].sh_type + != (dso->info[DT_PLTREL] == DT_RELA ? SHT_RELA : SHT_REL) + || strcmp (strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[first].sh_name), + dso->info[DT_PLTREL] == DT_RELA + ? ".rela.plt" : ".rel.plt")) + { + error (0, 0, "%s: DT_JMPREL tags don't surround .rel%s.plt section", + dso->filename, dso->info[DT_PLTREL] == DT_RELA ? "a" : ""); + return 1; + } + rinfo->plt = first; + rinfo->plt_rela = (dso->shdr[first].sh_type == SHT_RELA); + if (dso->shdr[first].sh_type == SHT_REL + && dso->arch->need_rel_to_rela != NULL + && dso->arch->need_rel_to_rela (dso, first, first)) + rinfo->rel_to_rela_plt = 1; + } + else + { + pltstart = end; + pltend = end; + pltfirst = 0; + } + + if (start == 0 && end == 0) + { + /* No non-PLT relocations. */ + return 0; + } + + if (start == end) + { + first = 0; + last = 0; + } + else + { + first = addr_to_sec (dso, start); + last = addr_to_sec (dso, end - 1); + + if (first == -1 + || last == -1 + || dso->shdr[first].sh_addr != start + || dso->shdr[last].sh_addr + dso->shdr[last].sh_size != end) + { + error (0, 0, "%s: DT_REL%s tags don't surround whole relocation sections", + dso->filename, rela ? "A" : ""); + return 1; + } + + for (i = first; i <= last; i++) + if (dso->shdr[i].sh_type != (rela ? SHT_RELA : SHT_REL)) + { + error (0, 0, "%s: DT_REL%s tags don't surround relocation sections of expected type", + dso->filename, rela ? "A" : ""); + return 1; + } + } + + if (pltstart != end && pltend != end + /* There is a gap between .rel(a).dyn and .rel(a).plt sections. + The gap may be due to a linker optimization, in which case + the sections are still adjacent, with a zero-filled gap in-between. */ + && last + 1 != pltfirst) + { + error (0, 0, "%s: DT_JMPREL tag not adjacent to DT_REL%s relocations", + dso->filename, rela ? "A" : ""); + return 1; + } + + if (pltstart == start && pltend == end) + { + /* No non-PLT relocations. */ + rinfo->overlap = 1; + return 0; + } + + if (pltstart != end && pltend == end) + { + rinfo->overlap = 1; + --last; + } + + rinfo->first = first; + rinfo->last = last; + if (! rela + && first + && dso->arch->need_rel_to_rela != NULL + && dso->arch->need_rel_to_rela (dso, first, last)) + rinfo->rel_to_rela = 1; + return 0; +} + +int +convert_rel_to_rela (DSO *dso, int i) +{ + Elf_Data d1, d2, *d; + Elf_Scn *scn; + GElf_Rel rel; + GElf_Rela rela; + int ndx, maxndx; + + scn = dso->scn[i]; + d = elf_getdata (scn, NULL); + assert (elf_getdata (scn, d) == NULL); + assert (d->d_off == 0); + assert (d->d_size == dso->shdr[i].sh_size); + d1 = *d; + d2 = *d; + assert (sizeof (Elf32_Rel) * 3 == sizeof (Elf32_Rela) * 2); + assert (sizeof (Elf64_Rel) * 3 == sizeof (Elf64_Rela) * 2); + d1.d_size = d->d_size / 2 * 3; + d1.d_buf = malloc (d1.d_size); + d1.d_type = ELF_T_RELA; + if (d1.d_buf == NULL) + { + error (0, ENOMEM, "Cannot convert REL section to RELA"); + return 1; + } + + maxndx = d->d_size / dso->shdr[i].sh_entsize; + for (ndx = 0; ndx < maxndx; ndx++) + { + if (gelfx_getrel (dso->elf, d, ndx, &rel) == 0 + || dso->arch->rel_to_rela (dso, &rel, &rela)) + { + free (d1.d_buf); + return 1; + } + /* gelf_update_rel etc. should have Elf * argument, so that + we don't have to do this crap. */ + *d = d1; + if (gelfx_update_rela (dso->elf, d, ndx, &rela) == 0) + { + *d = d2; + free (d1.d_buf); + return 1; + } + *d = d2; + } + + free (d2.d_buf); + *d = d1; + dso->shdr[i].sh_entsize + = gelf_fsize (dso->elf, ELF_T_RELA, 1, EV_CURRENT); + dso->shdr[i].sh_type = SHT_RELA; + return 0; +} + +int +convert_rela_to_rel (DSO *dso, int i) +{ + Elf_Data d1, d2, *d; + Elf_Scn *scn; + GElf_Rel rel; + GElf_Rela rela; + int ndx, maxndx; + + scn = dso->scn[i]; + d = elf_getdata (scn, NULL); + assert (elf_getdata (scn, d) == NULL); + assert (d->d_off == 0); + assert (d->d_size == dso->shdr[i].sh_size); + d1 = *d; + d2 = *d; + assert (sizeof (Elf32_Rel) * 3 == sizeof (Elf32_Rela) * 2); + assert (sizeof (Elf64_Rel) * 3 == sizeof (Elf64_Rela) * 2); + d1.d_size = d->d_size / 3 * 2; + d1.d_buf = malloc (d1.d_size); + d1.d_type = ELF_T_REL; + if (d1.d_buf == NULL) + { + error (0, ENOMEM, "Cannot convert RELA section to REL"); + return 1; + } + + maxndx = d->d_size / dso->shdr[i].sh_entsize; + for (ndx = 0; ndx < maxndx; ndx++) + { + if (gelfx_getrela (dso->elf, d, ndx, &rela) == 0 + || dso->arch->rela_to_rel (dso, &rela, &rel)) + { + free (d1.d_buf); + return 1; + } + /* gelf_update_rela etc. should have Elf * argument, so that + we don't have to do this crap. */ + *d = d1; + if (gelfx_update_rel (dso->elf, d, ndx, &rel) == 0) + { + *d = d2; + free (d1.d_buf); + return 1; + } + *d = d2; + } + + free (d2.d_buf); + *d = d1; + dso->shdr[i].sh_entsize + = gelf_fsize (dso->elf, ELF_T_REL, 1, EV_CURRENT); + dso->shdr[i].sh_type = SHT_REL; + return 0; +} + +int +update_dynamic_rel (DSO *dso, struct reloc_info *rinfo) +{ + GElf_Dyn *info[DT_NUM], *info_DT_RELCOUNT, *info_DT_RELACOUNT; + GElf_Dyn *dynamic = NULL; + int rel = rinfo->first, plt = rinfo->plt, overlap = rinfo->overlap; + int dynsec, count = 0, loc; + Elf_Data *data; + Elf_Scn *scn = NULL; + + memset (&info, 0, sizeof (info)); + info_DT_RELCOUNT = NULL; + info_DT_RELACOUNT = NULL; + for (dynsec = 0; dynsec < dso->ehdr.e_shnum; dynsec++) + if (dso->shdr[dynsec].sh_type == SHT_DYNAMIC) + { + scn = dso->scn[dynsec]; + dynamic = alloca (dso->shdr[dynsec].sh_size + / dso->shdr[dynsec].sh_entsize * sizeof (GElf_Dyn)); + loc = 0; + data = NULL; + while ((data = elf_getdata (scn, data)) != NULL) + { + int ndx, maxndx; + + maxndx = data->d_size / dso->shdr[dynsec].sh_entsize; + for (ndx = 0; ndx < maxndx; ++ndx, ++loc) + { + gelfx_getdyn (dso->elf, data, ndx, dynamic + loc); + if (dynamic[loc].d_tag == DT_NULL) + break; + else if ((GElf_Xword) dynamic[loc].d_tag < DT_NUM) + info[dynamic[loc].d_tag] = dynamic + loc; + else if (dynamic[loc].d_tag == DT_RELCOUNT) + info_DT_RELCOUNT = dynamic + loc; + else if (dynamic[loc].d_tag == DT_RELACOUNT) + info_DT_RELACOUNT = dynamic + loc; + } + if (ndx < maxndx) + break; + } + count = loc; + break; + } + + if (rel && plt && overlap) + { + if (dso->shdr[rel].sh_type != dso->shdr[plt].sh_type) + overlap = 0; + } + + if (rel || (plt && overlap)) + { + int dt_RELENT, dt_REL, dt_RELSZ; + + if (rinfo->reldyn_rela) + { + dt_RELENT = DT_RELAENT; + dt_REL = DT_RELA; + dt_RELSZ = DT_RELASZ; + } + else + { + dt_RELENT = DT_RELENT; + dt_REL = DT_REL; + dt_RELSZ = DT_RELSZ; + } + + assert (dso->info[dt_RELENT] + == gelf_fsize (dso->elf, rinfo->reldyn_rela + ? ELF_T_RELA : ELF_T_REL, 1, EV_CURRENT)); + assert (dso->info[dt_REL] != 0); + assert (dso->info[dt_RELSZ] != 0); + + info[dt_REL]->d_un.d_ptr = dso->shdr[rel ?: plt].sh_addr; + if (plt && overlap) + info[dt_RELSZ]->d_un.d_val = + dso->shdr[plt].sh_addr + dso->shdr[plt].sh_size; + else + info[dt_RELSZ]->d_un.d_val = + dso->shdr[rinfo->last].sh_addr + dso->shdr[rinfo->last].sh_size; + info[dt_RELSZ]->d_un.d_val -= info[dt_REL]->d_un.d_ptr; + + if (!rinfo->reldyn_rela && dso->shdr[rel ?: plt].sh_type == SHT_RELA) + { + info[DT_RELENT]->d_un.d_val = + gelf_fsize (dso->elf, ELF_T_RELA, 1, EV_CURRENT); + info[DT_REL]->d_tag = DT_RELA; + info[DT_RELSZ]->d_tag = DT_RELASZ; + info[DT_RELENT]->d_tag = DT_RELAENT; + if (info_DT_RELCOUNT) + info_DT_RELCOUNT->d_tag = DT_RELACOUNT; + } + else if (rinfo->reldyn_rela && dso->shdr[rel ?: plt].sh_type == SHT_REL) + { + info[DT_RELAENT]->d_un.d_val = + gelf_fsize (dso->elf, ELF_T_REL, 1, EV_CURRENT); + info[DT_RELA]->d_tag = DT_REL; + info[DT_RELASZ]->d_tag = DT_RELSZ; + info[DT_RELAENT]->d_tag = DT_RELENT; + if (info_DT_RELACOUNT) + info_DT_RELACOUNT->d_tag = DT_RELCOUNT; + } + } + + if (plt) + { + assert (dso->info[DT_JMPREL] != 0); + assert (dso->info[DT_PLTREL] == rinfo->plt_rela ? DT_RELA : DT_REL); + + info[DT_JMPREL]->d_un.d_ptr = dso->shdr[plt].sh_addr; + if (!rinfo->plt_rela && dso->shdr[plt].sh_type == SHT_RELA) + { + info[DT_PLTREL]->d_un.d_val = DT_RELA; + info[DT_PLTRELSZ]->d_un.d_val = dso->shdr[plt].sh_size; + } + else if (rinfo->plt_rela && dso->shdr[plt].sh_type == SHT_REL) + { + info[DT_PLTREL]->d_un.d_val = DT_REL; + info[DT_PLTRELSZ]->d_un.d_val = dso->shdr[plt].sh_size; + } + + if (!rel && !overlap) + { + int dt_REL = rinfo->reldyn_rela ? DT_RELA : DT_REL; + + if (info[dt_REL] && info[dt_REL]->d_un.d_ptr) + info[dt_REL]->d_un.d_ptr = info[DT_JMPREL]->d_un.d_ptr; + } + } + + loc = 0; + data = NULL; + while ((data = elf_getdata (scn, data)) != NULL) + { + int ndx, maxndx; + + maxndx = data->d_size / dso->shdr[dynsec].sh_entsize; + for (ndx = 0; ndx < maxndx && loc < count; ++ndx, ++loc) + if ((GElf_Xword) dynamic[loc].d_tag < DT_NUM + || dynamic[loc].d_tag == DT_RELCOUNT + || dynamic[loc].d_tag == DT_RELACOUNT) + gelfx_update_dyn (dso->elf, data, ndx, dynamic + loc); + if (ndx < maxndx) + break; + } + + read_dynamic (dso); + return 0; +} |