/* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2009, 2011 Red Hat, Inc. Written by Jakub Jelinek , 2001. * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #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 || GELF_R_TYPE (rela->r_info) == R_PPC_IRELATIVE) { 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 || GELF_R_TYPE (rela->r_info) == R_PPC_IRELATIVE) 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, GElf_Addr dest_addr) { GElf_Rela *ret; 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; case R_PPC_IRELATIVE: if (dest_addr == 0) return 5; ret = prelink_conflict_add_rela (info); if (ret == NULL) return 1; ret->r_offset = dest_addr; ret->r_info = GELF_R_INFO (0, R_PPC_IRELATIVE); ret->r_addend = 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) { 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: if (info->curtls == NULL || info->dso == dso) return 0; break; /* Similarly IRELATIVE relocations always need conflicts. */ case R_PPC_IRELATIVE: break; default: return 0; } value = 0; } else if (info->dso == dso && !conflict->ifunc) return 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; case R_PPC_ADDR32: case R_PPC_UADDR32: case R_PPC_IRELATIVE: if (conflict != NULL && conflict->ifunc) r_type = R_PPC_IRELATIVE; break; case R_PPC_JMP_SLOT: if (dynamic_info_is_set (dso, DT_PPC_GOT_BIT)) { r_type = R_PPC_ADDR32; if (conflict != NULL && conflict->ifunc) r_type = R_PPC_IRELATIVE; } 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; } if (conflict != NULL && conflict->ifunc && r_type != R_PPC_IRELATIVE) { error (0, 0, "%s: relocation %d against IFUNC symbol", dso->filename, (int) GELF_R_TYPE (rela->r_info)); 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_IRELATIVE: /* .iplt section will become SHT_NOBITS. */ 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; if (l->max_page_size > 0x10000) error (EXIT_FAILURE, 0, "--layout-page-size too large"); 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(ppc) = { .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, .rtype_class_valid = RTYPE_CLASS_VALID, .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 };