diff options
Diffstat (limited to 'trunk/src/arch-ppc.c')
-rw-r--r-- | trunk/src/arch-ppc.c | 1154 |
1 files changed, 1154 insertions, 0 deletions
diff --git a/trunk/src/arch-ppc.c b/trunk/src/arch-ppc.c new file mode 100644 index 0000000..5608750 --- /dev/null +++ b/trunk/src/arch-ppc.c @@ -0,0 +1,1154 @@ +/* Copyright (C) 2001, 2002, 2003, 2004, 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 <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <locale.h> +#include <error.h> +#include <argp.h> +#include <stdlib.h> + +#include "prelink.h" +#include "layout.h" + +#ifndef DT_PPC_GOT +# define DT_PPC_GOT (DT_LOPROC + 0) +#endif + +#define DT_PPC_GOT_BIT DT_LOPROC_BIT + +static int +ppc_adjust_dyn (DSO *dso, int n, GElf_Dyn *dyn, GElf_Addr start, + GElf_Addr adjust) +{ + if (dyn->d_tag == DT_PPC_GOT) + { + Elf32_Addr data; + + data = read_ube32 (dso, dyn->d_un.d_ptr); + /* DT_PPC_GOT[0] points to _DYNAMIC, it needs to be adjusted. */ + if (data == dso->shdr[n].sh_addr && data >= start) + write_be32 (dso, dyn->d_un.d_ptr, data + adjust); + + data = read_ube32 (dso, dyn->d_un.d_ptr + 4); + /* DT_PPC_GOT[1] points to .glink in prelinked libs. */ + if (data && data >= start) + write_be32 (dso, dyn->d_un.d_ptr + 4, data + adjust); + + if (dyn->d_un.d_ptr >= start) + { + dyn->d_un.d_ptr += adjust; + return 1; + } + } + else if (dyn->d_tag == DT_PLTGOT + && !dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + { + int i; + + for (i = 1; i < dso->ehdr.e_shnum; ++i) + if (! strcmp (strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[i].sh_name), ".got") + && dso->shdr[i].sh_size >= 16) + { + Elf32_Addr data, addr; + int step; + + /* If .got[1] points to _DYNAMIC, it needs to be adjusted. + Other possible locations of the .got header are at the + end of .got or around offset 32768 in it. */ + for (addr = dso->shdr[i].sh_addr, step = 0; step < 18; step++) + { + if (read_ube32 (dso, addr) == 0x4e800021 + && (data = read_ube32 (dso, addr + 4)) + == dso->shdr[n].sh_addr + && data >= start + && read_ube32 (dso, addr + 8) == 0 + && read_ube32 (dso, addr + 12) == 0) + { + /* Probably should use here a check that neither of + the 4 addresses contains a dynamic relocation against + it. */ + write_be32 (dso, addr + 4, data + adjust); + break; + } + if (step == 0) + addr = dso->shdr[i].sh_addr + dso->shdr[i].sh_size - 16; + else if (step == 1) + { + if (dso->shdr[i].sh_size >= 32768 - 32) + addr = dso->shdr[i].sh_addr + 32768 - 32 - 16; + else + break; + } + else + { + addr += 4; + if (addr + 16 + > dso->shdr[i].sh_addr + dso->shdr[i].sh_size) + break; + } + } + break; + } + } + + return 0; +} + +static int +ppc_adjust_rel (DSO *dso, GElf_Rel *rel, GElf_Addr start, + GElf_Addr adjust) +{ + error (0, 0, "%s: PowerPC doesn't support REL relocs", dso->filename); + return 1; +} + +static int +ppc_adjust_rela (DSO *dso, GElf_Rela *rela, GElf_Addr start, + GElf_Addr adjust) +{ + if (GELF_R_TYPE (rela->r_info) == R_PPC_RELATIVE) + { + if ((Elf32_Word) rela->r_addend >= start) + rela->r_addend += (Elf32_Sword) adjust; + } + if (GELF_R_TYPE (rela->r_info) == R_PPC_JMP_SLOT + && dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + { + Elf32_Addr data = read_ube32 (dso, rela->r_offset); + if (data >= start) + write_be32 (dso, rela->r_offset, data + adjust); + } + return 0; +} + +static int +ppc_prelink_rel (struct prelink_info *info, GElf_Rel *rel, + GElf_Addr reladdr) +{ + error (0, 0, "%s: PowerPC doesn't support REL relocs", info->dso->filename); + return 1; +} + +static void +ppc_fixup_plt (DSO *dso, GElf_Rela *rela, GElf_Addr value) +{ + Elf32_Sword disp = value - rela->r_offset; + + if (disp >= -0x2000000 && disp < 0x2000000) + { + /* b value */ + write_be32 (dso, rela->r_offset, 0x48000000 | (disp & 0x3fffffc)); + } + else if ((Elf32_Addr) value >= -0x2000000 || value < 0x2000000) + { + /* ba value */ + write_be32 (dso, rela->r_offset, 0x48000002 | (value & 0x3fffffc)); + } + else + { + Elf32_Addr plt = dso->info[DT_PLTGOT]; + + if (rela->r_offset - plt < (8192 * 2 + 18) * 4) + { + Elf32_Word index = (rela->r_offset - plt - 18 * 4) / (4 * 2); + Elf32_Word count = dso->info[DT_PLTRELSZ] / sizeof (Elf32_Rela); + Elf32_Addr data; + + data = plt + (18 + 2 * count + + (count > 8192 ? (count - 8192) * 2 : 0)) * 4; + write_be32 (dso, data + 4 * index, value); + /* li %r11, 4*index + b .plt+0 */ + write_be32 (dso, rela->r_offset, + 0x39600000 | ((index * 4) & 0xffff)); + write_be32 (dso, rela->r_offset + 4, + 0x48000000 | ((plt - rela->r_offset - 4) & 0x3fffffc)); + } + else + { + /* lis %r12, %hi(finaladdr) + addi %r12, %r12, %lo(finaladdr) + mtctr %r12 + bctr */ + write_be32 (dso, rela->r_offset, + 0x39800000 | (((value + 0x8000) >> 16) & 0xffff)); + write_be32 (dso, rela->r_offset + 4, 0x398c0000 | (value & 0xffff)); + write_be32 (dso, rela->r_offset + 8, 0x7d8903a6); + write_be32 (dso, rela->r_offset + 12, 0x4e800420); + } + } +} + +static int +ppc_prelink_rela (struct prelink_info *info, GElf_Rela *rela, + GElf_Addr relaaddr) +{ + DSO *dso = info->dso; + GElf_Addr value; + + if (GELF_R_TYPE (rela->r_info) == R_PPC_NONE) + return 0; + else if (GELF_R_TYPE (rela->r_info) == R_PPC_RELATIVE) + { + write_be32 (dso, rela->r_offset, rela->r_addend); + return 0; + } + value = info->resolve (info, GELF_R_SYM (rela->r_info), + GELF_R_TYPE (rela->r_info)); + value += rela->r_addend; + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_GLOB_DAT: + case R_PPC_ADDR32: + case R_PPC_UADDR32: + write_be32 (dso, rela->r_offset, value); + break; + case R_PPC_DTPREL32: + write_be32 (dso, rela->r_offset, value - 0x8000); + break; + case R_PPC_JMP_SLOT: + if (dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + write_be32 (dso, rela->r_offset, value); + else + ppc_fixup_plt (dso, rela, value); + break; + case R_PPC_ADDR16: + case R_PPC_UADDR16: + case R_PPC_ADDR16_LO: + write_be16 (dso, rela->r_offset, value); + break; + case R_PPC_DTPREL16: + case R_PPC_DTPREL16_LO: + write_be16 (dso, rela->r_offset, value - 0x8000); + break; + case R_PPC_ADDR16_HI: + case R_PPC_DTPREL16_HA: + write_be16 (dso, rela->r_offset, value >> 16); + break; + case R_PPC_DTPREL16_HI: + write_be16 (dso, rela->r_offset, (value - 0x8000) >> 16); + break; + case R_PPC_ADDR16_HA: + write_be16 (dso, rela->r_offset, (value + 0x8000) >> 16); + break; + case R_PPC_ADDR24: + write_be32 (dso, rela->r_offset, + (value & 0x03fffffc) + | (read_ube32 (dso, rela->r_offset) & 0xfc000003)); + break; + case R_PPC_ADDR14: + write_be32 (dso, rela->r_offset, + (value & 0xfffc) + | (read_ube32 (dso, rela->r_offset) & 0xffff0003)); + break; + case R_PPC_ADDR14_BRTAKEN: + case R_PPC_ADDR14_BRNTAKEN: + write_be32 (dso, rela->r_offset, + (value & 0xfffc) + | (read_ube32 (dso, rela->r_offset) & 0xffdf0003) + | ((((GELF_R_TYPE (rela->r_info) == R_PPC_ADDR14_BRTAKEN) + << 21) + ^ (value >> 10)) & 0x00200000)); + break; + case R_PPC_REL24: + write_be32 (dso, rela->r_offset, + ((value - rela->r_offset) & 0x03fffffc) + | (read_ube32 (dso, rela->r_offset) & 0xfc000003)); + break; + case R_PPC_REL32: + write_be32 (dso, rela->r_offset, value - rela->r_offset); + break; + /* DTPMOD32 and TPREL* is impossible to predict in shared libraries + unless prelink sets the rules. */ + case R_PPC_DTPMOD32: + if (dso->ehdr.e_type == ET_EXEC) + { + error (0, 0, "%s: R_PPC_DTPMOD32 reloc in executable?", + dso->filename); + return 1; + } + break; + case R_PPC_TPREL32: + case R_PPC_TPREL16: + case R_PPC_TPREL16_LO: + case R_PPC_TPREL16_HI: + case R_PPC_TPREL16_HA: + if (dso->ehdr.e_type == ET_EXEC && info->resolvetls) + { + value += info->resolvetls->offset - 0x7000; + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_TPREL32: + write_be32 (dso, rela->r_offset, value); + break; + case R_PPC_TPREL16: + case R_PPC_TPREL16_LO: + write_be16 (dso, rela->r_offset, value); + break; + case R_PPC_TPREL16_HI: + write_be16 (dso, rela->r_offset, value >> 16); + break; + case R_PPC_TPREL16_HA: + write_be16 (dso, rela->r_offset, (value + 0x8000) >> 16); + break; + } + } + break; + case R_PPC_COPY: + if (dso->ehdr.e_type == ET_EXEC) + /* COPY relocs are handled specially in generic code. */ + return 0; + error (0, 0, "%s: R_PPC_COPY reloc in shared library?", dso->filename); + return 1; + default: + error (0, 0, "%s: Unknown ppc relocation type %d", dso->filename, + (int) GELF_R_TYPE (rela->r_info)); + return 1; + } + return 0; +} + +static int +ppc_apply_conflict_rela (struct prelink_info *info, GElf_Rela *rela, + char *buf) +{ + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_ADDR32: + case R_PPC_UADDR32: + buf_write_be32 (buf, rela->r_addend); + break; + case R_PPC_ADDR16: + case R_PPC_UADDR16: + buf_write_be16 (buf, rela->r_addend); + break; + default: + abort (); + } + return 0; +} + +static int +ppc_apply_rel (struct prelink_info *info, GElf_Rel *rel, char *buf) +{ + error (0, 0, "%s: PowerPC doesn't support REL relocs", info->dso->filename); + return 1; +} + +static int +ppc_apply_rela (struct prelink_info *info, GElf_Rela *rela, char *buf) +{ + GElf_Addr value; + + value = info->resolve (info, GELF_R_SYM (rela->r_info), + GELF_R_TYPE (rela->r_info)); + value += rela->r_addend; + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_NONE: + break; + case R_PPC_GLOB_DAT: + case R_PPC_ADDR32: + case R_PPC_UADDR32: + buf_write_be32 (buf, value); + break; + case R_PPC_ADDR16_HA: + value += 0x8000; + /* FALLTHROUGH */ + case R_PPC_ADDR16_HI: + value = value >> 16; + /* FALLTHROUGH */ + case R_PPC_ADDR16: + case R_PPC_UADDR16: + case R_PPC_ADDR16_LO: + buf_write_be16 (buf, value); + break; + case R_PPC_ADDR24: + buf_write_be32 (buf, (value & 0x03fffffc) + | (buf_read_ube32 (buf) & 0xfc000003)); + break; + case R_PPC_ADDR14: + buf_write_be32 (buf, (value & 0xfffc) + | (buf_read_ube32 (buf) & 0xffff0003)); + break; + case R_PPC_ADDR14_BRTAKEN: + case R_PPC_ADDR14_BRNTAKEN: + buf_write_be32 (buf, (value & 0xfffc) + | (buf_read_ube32 (buf) & 0xffdf0003) + | ((((GELF_R_TYPE (rela->r_info) + == R_PPC_ADDR14_BRTAKEN) << 21) + ^ (value >> 10)) & 0x00200000)); + break; + case R_PPC_REL24: + buf_write_be32 (buf, ((value - rela->r_offset) & 0x03fffffc) + | (buf_read_ube32 (buf) & 0xfc000003)); + break; + case R_PPC_REL32: + buf_write_be32 (buf, value - rela->r_offset); + break; + case R_PPC_RELATIVE: + error (0, 0, "%s: R_PPC_RELATIVE in ET_EXEC object?", + info->dso->filename); + return 1; + default: + return 1; + } + return 0; +} + +static int +ppc_prelink_conflict_rel (DSO *dso, struct prelink_info *info, + GElf_Rel *rel, GElf_Addr reladdr) +{ + error (0, 0, "%s: PowerPC doesn't support REL relocs", dso->filename); + return 1; +} + +static int +ppc_prelink_conflict_rela (DSO *dso, struct prelink_info *info, + GElf_Rela *rela, GElf_Addr relaaddr) +{ + GElf_Addr value; + struct prelink_conflict *conflict; + struct prelink_tls *tls; + GElf_Rela *ret; + int r_type; + + if (GELF_R_TYPE (rela->r_info) == R_PPC_RELATIVE + || GELF_R_TYPE (rela->r_info) == R_PPC_NONE) + /* Fast path: nothing to do. */ + return 0; + conflict = prelink_conflict (info, GELF_R_SYM (rela->r_info), + GELF_R_TYPE (rela->r_info)); + if (conflict == NULL) + { + if (info->curtls == NULL) + return 0; + switch (GELF_R_TYPE (rela->r_info)) + { + /* Even local DTPMOD and TPREL relocs need conflicts. */ + case R_PPC_DTPMOD32: + case R_PPC_TPREL32: + case R_PPC_TPREL16: + case R_PPC_TPREL16_LO: + case R_PPC_TPREL16_HI: + case R_PPC_TPREL16_HA: + break; + default: + return 0; + } + value = 0; + } + else + { + /* DTPREL wants to see only real conflicts, not lookups + with reloc_class RTYPE_CLASS_TLS. */ + if (conflict->lookup.tls == conflict->conflict.tls + && conflict->lookupval == conflict->conflictval) + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_DTPREL32: + case R_PPC_DTPREL16: + case R_PPC_DTPREL16_LO: + case R_PPC_DTPREL16_HI: + case R_PPC_DTPREL16_HA: + return 0; + } + + value = conflict_lookup_value (conflict); + } + ret = prelink_conflict_add_rela (info); + if (ret == NULL) + return 1; + ret->r_offset = rela->r_offset; + value += rela->r_addend; + r_type = GELF_R_TYPE (rela->r_info); + switch (r_type) + { + case R_PPC_GLOB_DAT: + r_type = R_PPC_ADDR32; + break; + case R_PPC_ADDR32: + case R_PPC_UADDR32: + break; + case R_PPC_JMP_SLOT: + if (dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + r_type = R_PPC_ADDR32; + break; + case R_PPC_ADDR16_HA: + value += 0x8000; + /* FALLTHROUGH */ + case R_PPC_ADDR16_HI: + value = value >> 16; + /* FALLTHROUGH */ + case R_PPC_ADDR16: + case R_PPC_UADDR16: + case R_PPC_ADDR16_LO: + if (r_type != R_PPC_UADDR16) + r_type = R_PPC_ADDR16; + value = ((value & 0xffff) ^ 0x8000) - 0x8000; + break; + case R_PPC_ADDR24: + r_type = R_PPC_ADDR32; + value = (value & 0x03fffffc) + | (read_ube32 (dso, rela->r_offset) & 0xfc000003); + break; + case R_PPC_ADDR14: + r_type = R_PPC_ADDR32; + value = (value & 0xfffc) + | (read_ube32 (dso, rela->r_offset) & 0xffff0003); + break; + case R_PPC_ADDR14_BRTAKEN: + case R_PPC_ADDR14_BRNTAKEN: + r_type = R_PPC_ADDR32; + value = (value & 0xfffc) + | (read_ube32 (dso, rela->r_offset) & 0xffdf0003) + | ((((r_type == R_PPC_ADDR14_BRTAKEN) << 21) + ^ (value >> 10)) & 0x00200000); + break; + case R_PPC_REL24: + r_type = R_PPC_ADDR32; + value = ((value - rela->r_offset) & 0x03fffffc) + | (read_ube32 (dso, rela->r_offset) & 0xfc000003); + break; + case R_PPC_REL32: + r_type = R_PPC_ADDR32; + value -= rela->r_offset; + break; + case R_PPC_DTPMOD32: + case R_PPC_DTPREL32: + case R_PPC_DTPREL16: + case R_PPC_DTPREL16_LO: + case R_PPC_DTPREL16_HI: + case R_PPC_DTPREL16_HA: + case R_PPC_TPREL32: + case R_PPC_TPREL16: + case R_PPC_TPREL16_LO: + case R_PPC_TPREL16_HI: + case R_PPC_TPREL16_HA: + if (conflict != NULL + && (conflict->reloc_class != RTYPE_CLASS_TLS + || conflict->lookup.tls == NULL)) + { + error (0, 0, "%s: TLS reloc not resolving to STT_TLS symbol", + dso->filename); + return 1; + } + tls = conflict ? conflict->lookup.tls : info->curtls; + r_type = R_PPC_ADDR16; + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_DTPMOD32: + r_type = R_PPC_ADDR32; + value = tls->modid; + break; + case R_PPC_DTPREL32: + r_type = R_PPC_ADDR32; + value -= 0x8000; + break; + case R_PPC_DTPREL16: + case R_PPC_DTPREL16_LO: + value -= 0x8000; + break; + case R_PPC_DTPREL16_HI: + value = (value - 0x8000) >> 16; + break; + case R_PPC_DTPREL16_HA: + value >>= 16; + break; + case R_PPC_TPREL32: + r_type = R_PPC_ADDR32; + value += tls->offset - 0x7000; + break; + case R_PPC_TPREL16: + case R_PPC_TPREL16_LO: + value += tls->offset - 0x7000; + break; + case R_PPC_TPREL16_HI: + value = (value + tls->offset - 0x7000) >> 16; + break; + case R_PPC_TPREL16_HA: + value = (value + tls->offset - 0x7000 + 0x8000) >> 16; + break; + } + if (r_type == R_PPC_ADDR16) + value = ((value & 0xffff) ^ 0x8000) - 0x8000; + break; + default: + error (0, 0, "%s: Unknown PowerPC relocation type %d", dso->filename, + r_type); + return 1; + } + ret->r_info = GELF_R_INFO (0, r_type); + ret->r_addend = (Elf32_Sword) value; + return 0; +} + +static int +ppc_rel_to_rela (DSO *dso, GElf_Rel *rel, GElf_Rela *rela) +{ + error (0, 0, "%s: PowerPC doesn't support REL relocs", dso->filename); + return 1; +} + +static int +ppc_need_rel_to_rela (DSO *dso, int first, int last) +{ + return 0; +} + +static int +ppc_arch_pre_prelink (DSO *dso) +{ + Elf_Data *data = NULL; + Elf_Scn *scn; + GElf_Dyn dyn; + Elf32_Addr val; + int i; + + if (!dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + return 0; + + assert (dso->shdr[dso->dynamic].sh_type == SHT_DYNAMIC); + + scn = dso->scn[dso->dynamic]; + while ((data = elf_getdata (scn, data)) != NULL) + { + int ndx, maxndx; + + maxndx = data->d_size / dso->shdr[dso->dynamic].sh_entsize; + for (ndx = 0; ndx < maxndx; ++ndx) + { + gelfx_getdyn (dso->elf, data, ndx, &dyn); + assert (dyn.d_tag != DT_NULL); + if (dyn.d_tag == DT_PPC_GOT) + break; + } + if (ndx < maxndx) + break; + } + + /* DT_PPC_GOT[1] should point to .glink in prelinked libs. */ + val = read_ube32 (dso, dyn.d_un.d_ptr + 4); + if (val) + return 0; + + for (i = 1; i < dso->ehdr.e_shnum; ++i) + if (! strcmp (strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[i].sh_name), ".plt")) + break; + + if (i == dso->ehdr.e_shnum) + return 0; + + val = read_ube32 (dso, dso->shdr[i].sh_addr); + write_be32 (dso, dyn.d_un.d_ptr + 4, val); + + return 0; +} + +static int +ppc_arch_prelink (struct prelink_info *info) +{ + DSO *dso = info->dso; + Elf32_Addr plt = dso->info[DT_PLTGOT]; + + if (plt && !dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + { + Elf32_Word count = dso->info[DT_PLTRELSZ] / sizeof (Elf32_Rela); + Elf32_Addr data; + + data = plt + (18 + 2 * count + + (count > 8192 ? (count - 8192) * 2 : 0)) * 4; + + /* addis %r11, %r11, %hi(data) + lwz %r11, %r11, %lo(data) + mtctr %r11 + bctr */ + write_be32 (dso, plt, 0x3d6b0000 | (((data + 0x8000) >> 16) & 0xffff)); + write_be32 (dso, plt + 4, 0x816b0000 | (data & 0xffff)); + write_be32 (dso, plt + 8, 0x7d6903a6); + write_be32 (dso, plt + 12, 0x4e800420); + } + return 0; +} + +static int +ppc_arch_undo_prelink (DSO *dso) +{ + Elf_Data *data = NULL; + Elf_Scn *scn; + GElf_Dyn dyn; + Elf32_Addr val, addr, endaddr; + int i; + + if (!dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) + return 0; + + assert (dso->shdr[dso->dynamic].sh_type == SHT_DYNAMIC); + + scn = dso->scn[dso->dynamic]; + while ((data = elf_getdata (scn, data)) != NULL) + { + int ndx, maxndx; + + maxndx = data->d_size / dso->shdr[dso->dynamic].sh_entsize; + for (ndx = 0; ndx < maxndx; ++ndx) + { + gelfx_getdyn (dso->elf, data, ndx, &dyn); + assert (dyn.d_tag != DT_NULL); + if (dyn.d_tag == DT_PPC_GOT) + break; + } + if (ndx < maxndx) + break; + } + + /* DT_PPC_GOT[1] should point to .glink in prelinked libs. */ + val = read_ube32 (dso, dyn.d_un.d_ptr + 4); + if (!val) + return 0; + + for (i = 1; i < dso->ehdr.e_shnum; ++i) + if (! strcmp (strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[i].sh_name), ".plt")) + break; + + if (i == dso->ehdr.e_shnum || (dso->shdr[i].sh_size & 3)) + return 0; + + addr = dso->shdr[i].sh_addr; + endaddr = addr + dso->shdr[i].sh_size; + for (; addr < endaddr; addr += 4, val += 4) + write_be32 (dso, addr, val); + + write_be32 (dso, dyn.d_un.d_ptr + 4, 0); + + return 0; +} + + +static int +ppc_undo_prelink_rela (DSO *dso, GElf_Rela *rela, GElf_Addr relaaddr) +{ + switch (GELF_R_TYPE (rela->r_info)) + { + case R_PPC_NONE: + return 0; + case R_PPC_RELATIVE: + case R_PPC_GLOB_DAT: + case R_PPC_ADDR32: + case R_PPC_UADDR32: + case R_PPC_REL32: + case R_PPC_DTPMOD32: + case R_PPC_DTPREL32: + case R_PPC_TPREL32: + write_be32 (dso, rela->r_offset, 0); + break; + case R_PPC_JMP_SLOT: + /* .plt section will become SHT_NOBITS if DT_PPC_GOT is not present, + otherwise .plt section will be unprelinked in + ppc_arch_undo_prelink. */ + return 0; + case R_PPC_ADDR16: + case R_PPC_UADDR16: + case R_PPC_ADDR16_LO: + case R_PPC_ADDR16_HI: + case R_PPC_ADDR16_HA: + case R_PPC_DTPREL16: + case R_PPC_TPREL16: + case R_PPC_DTPREL16_LO: + case R_PPC_TPREL16_LO: + case R_PPC_DTPREL16_HI: + case R_PPC_TPREL16_HI: + case R_PPC_DTPREL16_HA: + case R_PPC_TPREL16_HA: + write_be16 (dso, rela->r_offset, 0); + break; + case R_PPC_ADDR24: + case R_PPC_REL24: + write_be32 (dso, rela->r_offset, + read_ube32 (dso, rela->r_offset) & 0xfc000003); + break; + case R_PPC_ADDR14: + write_be32 (dso, rela->r_offset, + read_ube32 (dso, rela->r_offset) & 0xffff0003); + break; + case R_PPC_ADDR14_BRTAKEN: + case R_PPC_ADDR14_BRNTAKEN: + write_be32 (dso, rela->r_offset, + read_ube32 (dso, rela->r_offset) & 0xffdf0003); + break; + case R_PPC_COPY: + if (dso->ehdr.e_type == ET_EXEC) + /* COPY relocs are handled specially in generic code. */ + return 0; + error (0, 0, "%s: R_PPC_COPY reloc in shared library?", dso->filename); + return 1; + default: + error (0, 0, "%s: Unknown ppc relocation type %d", dso->filename, + (int) GELF_R_TYPE (rela->r_info)); + return 1; + } + return 0; +} + +static int +ppc_reloc_size (int reloc_type) +{ + switch (reloc_type) + { + case R_PPC_ADDR16: + case R_PPC_UADDR16: + case R_PPC_ADDR16_LO: + case R_PPC_ADDR16_HI: + case R_PPC_ADDR16_HA: + case R_PPC_DTPREL16: + case R_PPC_DTPREL16_LO: + case R_PPC_DTPREL16_HI: + case R_PPC_DTPREL16_HA: + case R_PPC_TPREL16: + case R_PPC_TPREL16_LO: + case R_PPC_TPREL16_HI: + case R_PPC_TPREL16_HA: + return 2; + default: + break; + } + return 4; +} + +static int +ppc_reloc_class (int reloc_type) +{ + switch (reloc_type) + { + case R_PPC_COPY: return RTYPE_CLASS_COPY; + case R_PPC_JMP_SLOT: return RTYPE_CLASS_PLT; + default: + if (reloc_type >= R_PPC_DTPMOD32 && reloc_type <= R_PPC_DTPREL32) + return RTYPE_CLASS_TLS; + return RTYPE_CLASS_VALID; + } +} + +/* Library memory regions in order of precedence: + 0xe800000 .. 0x10000000 top to bottom + 0x40000 .. 0xe800000 bottom to top + 0x18000000 .. 0x30000000 bottom to top */ + +#define REG0S 0x0e800000 +#define REG0E 0x10000000 +#define REG1S 0x00040000 +#define REG1E REG0S +#define REG2S 0x18000000 +#define REG2E 0x30000000 + +struct ppc_layout_data +{ + int cnt; + struct prelink_entry e[3]; + Elf32_Addr mmap_start, first_start, last_start; + struct + { + struct prelink_entry *e; + Elf32_Addr base, end, layend; + } ents[0]; +}; + +static inline void +list_append (struct prelink_entry *x, struct prelink_entry *e) +{ + x->prev->next = e; + e->prev = x->prev; + e->next = NULL; + x->prev = e; +} + +static int +addr_cmp (const void *A, const void *B) +{ + struct prelink_entry *a = * (struct prelink_entry **) A; + struct prelink_entry *b = * (struct prelink_entry **) B; + + if (a->base < b->base) + return -1; + else if (a->base > b->base) + return 1; + if (a->layend < b->layend) + return -1; + else if (a->layend > b->layend) + return 1; + return 0; +} + +static void +list_sort (struct prelink_entry *x) +{ + int cnt, i; + struct prelink_entry *e; + struct prelink_entry **a; + + if (x->next == NULL) + return; + for (cnt = 0, e = x->next; e != NULL; e = e->next) + ++cnt; + a = alloca (cnt * sizeof (*a)); + for (i = 0, e = x->next; e != NULL; e = e->next) + a[i++] = e; + qsort (a, cnt, sizeof (*a), addr_cmp); + x->next = NULL; + x->prev = x; + for (i = 0; i < cnt; ++i) + list_append (x, a[i]); +} + +static int +ppc_layout_libs_pre (struct layout_libs *l) +{ + Elf32_Addr mmap_start = l->mmap_start - REG1S; + Elf32_Addr first_start = REG0S, last_start = REG2S; + struct prelink_entry *e, e0, *next = NULL; + struct ppc_layout_data *pld; + int cnt; + + mmap_start = REG0E - (mmap_start & 0xff0000); + for (cnt = 0, e = l->list; e != NULL; e = e->next, ++cnt) + { + if (e->base < mmap_start && e->layend > mmap_start) + mmap_start = (e->layend + 0xffff) & ~0xffff; + if (e->base < REG0S && e->layend > REG0S && first_start > e->base) + first_start = e->base; + if (e->base < REG0E && e->layend > REG2S && last_start < e->layend) + last_start = e->layend; + } + if (mmap_start > REG0E) + mmap_start = REG0E; + + pld = calloc (sizeof (*pld) + cnt * sizeof (pld->ents[0]), 1); + if (pld == NULL) + error (EXIT_FAILURE, ENOMEM, "Cannot lay libraries out"); + + l->arch_data = pld; + memset (&e0, 0, sizeof (e0)); + e0.prev = &e0; + pld->cnt = cnt; + pld->e[0].u.tmp = -1; + pld->e[0].base = REG1S + REG0E - mmap_start; + pld->e[0].end = pld->e[0].base; + pld->e[0].layend = pld->e[0].end; + pld->e[0].prev = &pld->e[0]; + pld->e[1].u.tmp = -1; + pld->e[1].base = pld->e[0].end + mmap_start - REG0S; + pld->e[1].end = pld->e[1].base; + pld->e[1].layend = pld->e[1].end; + pld->e[1].prev = &pld->e[1]; + pld->e[2].u.tmp = -1; + pld->e[2].base = pld->e[1].end + first_start - REG1S; + pld->e[2].end = pld->e[1].base; + pld->e[2].layend = pld->e[2].end; + pld->e[2].prev = &pld->e[2]; + for (cnt = 0, e = l->list; e != NULL; e = next, ++cnt) + { + next = e->next; + pld->ents[cnt].e = e; + pld->ents[cnt].base = e->base; + pld->ents[cnt].end = e->end; + pld->ents[cnt].layend = e->layend; + if (e->layend <= REG0S) + { + if (e->base < REG1S) + e->base = REG1S; + else if (e->base > first_start) + e->base = first_start; + if (e->layend < REG1S) + e->layend = REG1S; + else if (e->layend > first_start) + e->layend = first_start; + e->base += pld->e[1].end - REG1S; + e->layend += pld->e[1].end - REG1S; + list_append (&pld->e[1], e); + } + else if (e->base < mmap_start) + { + if (e->base < REG0S) + e->base = REG0S; + if (e->layend > mmap_start) + e->layend = mmap_start; + e->base = pld->e[0].end + mmap_start - e->layend; + e->layend = pld->e[0].layend + mmap_start - pld->ents[cnt].base; + list_append (&pld->e[0], e); + } + else if (e->base < REG0E) + { + if (e->layend > REG0E) + e->layend = REG0E; + e->base = REG1S + REG0E - e->layend; + e->layend = REG1S + REG0E - pld->ents[cnt].base; + list_append (&e0, e); + } + else if (e->layend >= last_start) + { + if (e->base < last_start) + e->base = last_start; + e->base += pld->e[2].end - last_start; + e->layend += pld->e[2].end - last_start; + list_append (&pld->e[2], e); + } + e->end = e->layend; + } + + list_sort (&pld->e[0]); + if (e0.next == NULL) + l->list = &pld->e[0]; + else + { + list_sort (&e0); + l->list = e0.next; + l->list->prev = pld->e[0].prev; + e0.prev->next = &pld->e[0]; + pld->e[0].prev = e0.prev; + } + + e0.prev = l->list->prev; + l->list->prev = pld->e[1].prev; + e0.prev->next = &pld->e[1]; + pld->e[1].prev = e0.prev; + + e0.prev = l->list->prev; + l->list->prev = pld->e[2].prev; + e0.prev->next = &pld->e[2]; + pld->e[2].prev = e0.prev; + + pld->mmap_start = mmap_start; + pld->first_start = first_start; + pld->last_start = last_start; + + l->mmap_start = REG1S; + l->mmap_fin = pld->e[2].end + REG2E - last_start; + l->mmap_end = l->mmap_fin; + l->fakecnt = 3; + l->fake = pld->e; + + return 0; +} + +static int +ppc_layout_libs_post (struct layout_libs *l) +{ + struct prelink_entry *e; + struct ppc_layout_data *pld = (struct ppc_layout_data *) l->arch_data; + Elf32_Addr base, end; + int i; + + /* First fix up base and end fields we saved. */ + for (i = 0; i < pld->cnt; ++i) + { + pld->ents[i].e->base = pld->ents[i].base; + pld->ents[i].e->layend = pld->ents[i].layend; + pld->ents[i].e->end = pld->ents[i].end; + pld->ents[i].e->done |= 0x40; + } + pld->e[0].done |= 0x40; + pld->e[1].done |= 0x40; + pld->e[2].done |= 0x40; + + /* Now fix up the newly created items. */ + for (e = l->list; e != NULL; e = e->next) + if (e->done & 0x40) + e->done &= ~0x40; + else + { + base = e->base; + end = e->layend; + if (e->base < pld->e[0].base) + { + e->base = REG1S + REG0E - end; + e->end += e->base - base; + e->layend = REG1S + REG0E - base; + } + else if (e->base < pld->e[1].base) + { + e->base = pld->e[0].end + pld->mmap_start - end; + e->end += e->base - base; + e->layend = pld->e[0].end + pld->mmap_start - base; + } + else if (e->base < pld->e[2].base) + { + e->base -= pld->e[1].end - REG1S; + e->end -= pld->e[1].end - REG1S; + e->layend -= pld->e[1].end - REG1S; + } + else + { + e->base -= pld->e[2].end - pld->last_start; + e->end -= pld->e[2].end - pld->last_start; + e->layend -= pld->e[2].end - pld->last_start; + } + } + + for (i = 0; i < pld->cnt; ++i) + pld->ents[i].e->done &= ~0x40; + + free (l->arch_data); + return 0; +} + +PL_ARCH = { + .name = "PowerPC", + .class = ELFCLASS32, + .machine = EM_PPC, + .alternate_machine = { EM_NONE }, + .R_JMP_SLOT = R_PPC_JMP_SLOT, + .R_COPY = R_PPC_COPY, + .R_RELATIVE = R_PPC_RELATIVE, + .dynamic_linker = "/lib/ld.so.1", + .adjust_dyn = ppc_adjust_dyn, + .adjust_rel = ppc_adjust_rel, + .adjust_rela = ppc_adjust_rela, + .prelink_rel = ppc_prelink_rel, + .prelink_rela = ppc_prelink_rela, + .prelink_conflict_rel = ppc_prelink_conflict_rel, + .prelink_conflict_rela = ppc_prelink_conflict_rela, + .apply_conflict_rela = ppc_apply_conflict_rela, + .apply_rel = ppc_apply_rel, + .apply_rela = ppc_apply_rela, + .rel_to_rela = ppc_rel_to_rela, + .need_rel_to_rela = ppc_need_rel_to_rela, + .reloc_size = ppc_reloc_size, + .reloc_class = ppc_reloc_class, + .max_reloc_size = 4, + .arch_pre_prelink = ppc_arch_pre_prelink, + .arch_prelink = ppc_arch_prelink, + .arch_undo_prelink = ppc_arch_undo_prelink, + .undo_prelink_rela = ppc_undo_prelink_rela, + .layout_libs_pre = ppc_layout_libs_pre, + .layout_libs_post = ppc_layout_libs_post, + /* This will need some changes in layout.c. + PowerPC prefers addresses right below REG0E + and can use the region above REG2S if libs don't fit. */ + .mmap_base = REG1S, + .mmap_end = REG2E, + .max_page_size = 0x10000, + .page_size = 0x1000 +}; |