mirror of
https://github.com/rui314/mold.git
synced 2024-09-11 13:06:59 +03:00
677 lines
23 KiB
C++
677 lines
23 KiB
C++
// This file implements the PowerPC ELFv2 ABI which was standardized in
|
|
// 2014. Modern little-endian PowerPC systems are based on this ABI.
|
|
// The ABI is often referred to as "ppc64le". This shouldn't be confused
|
|
// with "ppc64" which refers to the original, big-endian PowerPC systems.
|
|
//
|
|
// PPC64 is a bit tricky to support because PC-relative load/store
|
|
// instructions hadn't been available until Power10 which debuted in 2021.
|
|
// Prior to Power10, it wasn't trivial for position-independent code (PIC)
|
|
// to load a value from, for example, .got, as we can't do that with [PC +
|
|
// the offset to the .got entry].
|
|
//
|
|
// In the following, I'll explain how PIC is supported on pre-Power10
|
|
// systems first and then explain what has changed with Power10.
|
|
//
|
|
//
|
|
// Position-independent code on Power9 or earlier:
|
|
//
|
|
// We can get the program counter on older PPC64 systems with the
|
|
// following four instructions
|
|
//
|
|
// mflr r1 // save the current link register to r1
|
|
// bl .+4 // branch to the next instruction as if it were a function
|
|
// mflr r0 // copy the return address to r0
|
|
// mtlr r1 // restore the original link register value
|
|
//
|
|
// , but it's too expensive to do if we do this for each load/store.
|
|
//
|
|
// As a workaround, most functions are compiled in such a way that r2 is
|
|
// assumed to always contain the address of .got + 0x8000. With this, we
|
|
// can for example load the first entry of .got with a single instruction
|
|
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
|
|
//
|
|
// There's only one .got for each ELF module. Therefore, if a callee is in
|
|
// the same ELF module, r2 doesn't have to be recomputed. Most function
|
|
// calls are usually within the same ELF module, so this mechanism is
|
|
// efficient.
|
|
//
|
|
// A function compiled for pre-Power10 usually has two entry points,
|
|
// global and local. The global entry point usually 8 bytes precedes
|
|
// the local entry point. In between is the following instructions:
|
|
//
|
|
// addis r2, r12, .TOC.@ha
|
|
// addi r2, r2, .TOC.@lo + 4;
|
|
//
|
|
// The global entry point assumes that the address of itself is in r12,
|
|
// and it computes its own TOC pointer from r12. It's easy to do so for
|
|
// the callee because the offset between its .got + 0x8000 and the
|
|
// function is known at link-time. The above code sequence then falls
|
|
// through to the local entry point that assumes r2 is .got + 0x8000.
|
|
//
|
|
// So, if a callee's TOC pointer is different from the current one
|
|
// (e.g. calling a function in another .so), we first load the callee's
|
|
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
|
|
// to that address. Then the callee computes its own TOC pointer using
|
|
// r12.
|
|
//
|
|
//
|
|
// Position-independent code on Power10:
|
|
//
|
|
// Power10 added 8-bytes-long instructions to the ISA. Some of them are
|
|
// PC-relative load/store instructions that take 34 bits offsets.
|
|
// Functions compiled with `-mcpu=power10` use these instructions for PIC.
|
|
// r2 does not have a special meaning in such fucntions.
|
|
//
|
|
// When a fucntion compiled for Power10 calls a function that uses the TOC
|
|
// pointer, we need to compute a correct value for TOC and set it to r2
|
|
// before transferring the control to the callee. Thunks are responsible
|
|
// for doing it.
|
|
//
|
|
// `_NOTOC` relocations such as `R_PPC64_REL24_NOTOC` indicate that the
|
|
// callee does not use TOC (i.e. compiled with `-mcpu=power10`). If a
|
|
// function using TOC is referenced via a `_NOTOC` relocation, that call
|
|
// is made through a range extension thunk.
|
|
//
|
|
//
|
|
// Note on section names: the PPC64 psABI uses a weird naming convention
|
|
// which calls .got.plt .plt. We ignored that part because it's just
|
|
// confusing. Since the runtime only cares about segments, we should be
|
|
// able to name sections whatever we want.
|
|
//
|
|
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
|
|
|
|
#include "mold.h"
|
|
|
|
namespace mold::elf {
|
|
|
|
using E = PPC64V2;
|
|
|
|
static u64 lo(u64 x) { return x & 0xffff; }
|
|
static u64 hi(u64 x) { return x >> 16; }
|
|
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
|
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
|
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
|
|
|
static void write34(u8 *loc, u64 x) {
|
|
ul32 *buf = (ul32 *)loc;
|
|
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
|
|
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
|
|
}
|
|
|
|
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
|
// calls are made via range extension thunks even if they are within
|
|
// reach. Thunks read addresses from .got.plt and jump there.
|
|
// Therefore, once PLT symbols are resolved and final addresses are
|
|
// written to .got.plt, thunks just skip .plt and directly jump to the
|
|
// resolved addresses.
|
|
template <>
|
|
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
|
static const ul32 insn[] = {
|
|
// Get PC
|
|
0x7c08'02a6, // mflr r0
|
|
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
|
0x7d68'02a6, // mflr r11
|
|
0x7c08'03a6, // mtlr r0
|
|
|
|
// Compute the PLT entry index
|
|
0x398c'ffd4, // addi r12, r12, -44
|
|
0x7c0b'6050, // subf r0, r11, r12
|
|
0x7800'f082, // rldicl r0, r0, 62, 2
|
|
|
|
// Compute the address of .got.plt
|
|
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
|
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
|
|
|
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
|
|
0xe98b'0000, // ld r12, 0(r11)
|
|
0x7d89'03a6, // mtctr r12
|
|
0xe96b'0008, // ld r11, 8(r11)
|
|
0x4e80'0420, // bctr
|
|
};
|
|
|
|
memcpy(buf, insn, sizeof(insn));
|
|
|
|
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
|
*(ul32 *)(buf + 28) |= higha(val);
|
|
*(ul32 *)(buf + 32) |= lo(val);
|
|
}
|
|
|
|
template <>
|
|
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
|
// When the control is transferred to a PLT entry, the PLT entry's
|
|
// address is already set to %r12 by the caller.
|
|
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx);
|
|
*(ul32 *)buf = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
|
}
|
|
|
|
// .plt.got is not necessary on PPC64 because range extension thunks
|
|
// directly read GOT entries and jump there.
|
|
template <>
|
|
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
|
|
|
template <>
|
|
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
|
u64 offset, u64 val) {
|
|
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
|
|
|
switch (rel.r_type) {
|
|
case R_NONE:
|
|
break;
|
|
case R_PPC64_ADDR64:
|
|
*(ul64 *)loc = val;
|
|
break;
|
|
case R_PPC64_REL32:
|
|
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
|
break;
|
|
case R_PPC64_REL64:
|
|
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
|
break;
|
|
default:
|
|
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
|
}
|
|
}
|
|
|
|
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
|
i64 val = sym.esym().ppc_local_entry;
|
|
assert(val <= 7);
|
|
if (val == 7)
|
|
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
|
|
|
if (val == 0 || val == 1)
|
|
return 0;
|
|
return 1 << val;
|
|
}
|
|
|
|
template <>
|
|
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
|
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
|
|
|
ElfRel<E> *dynrel = nullptr;
|
|
if (ctx.reldyn)
|
|
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
|
file.reldyn_offset + this->reldyn_offset);
|
|
|
|
for (i64 i = 0; i < rels.size(); i++) {
|
|
const ElfRel<E> &rel = rels[i];
|
|
if (rel.r_type == R_NONE)
|
|
continue;
|
|
|
|
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
|
u8 *loc = base + rel.r_offset;
|
|
|
|
u64 S = sym.get_addr(ctx);
|
|
u64 A = rel.r_addend;
|
|
u64 P = get_addr() + rel.r_offset;
|
|
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
|
u64 GOT = ctx.got->shdr.sh_addr;
|
|
u64 TOC = ctx.extra.TOC->value;
|
|
|
|
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
|
|
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
|
|
|
|
switch (rel.r_type) {
|
|
case R_PPC64_ADDR64:
|
|
if (name() == ".toc")
|
|
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
|
else
|
|
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
|
break;
|
|
case R_PPC64_TOC16_HA:
|
|
*(ul16 *)loc = ha(S + A - TOC);
|
|
break;
|
|
case R_PPC64_TOC16_LO:
|
|
*(ul16 *)loc = lo(S + A - TOC);
|
|
break;
|
|
case R_PPC64_TOC16_DS:
|
|
case R_PPC64_TOC16_LO_DS:
|
|
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
|
|
break;
|
|
case R_PPC64_REL24:
|
|
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
|
|
i64 val = r2save_thunk_addr() + A - P;
|
|
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
|
|
|
// The thunk saves %r2 to the caller's r2 save slot. We need to
|
|
// restore it after function return. To do so, there's usually a
|
|
// NOP as a placeholder after a BL. 0x6000'0000 is a NOP.
|
|
if (*(ul32 *)(loc + 4) == 0x6000'0000)
|
|
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
|
} else {
|
|
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
|
|
if (sign_extend(val, 25) != val)
|
|
val = no_r2save_thunk_addr() + A - P;
|
|
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
|
}
|
|
break;
|
|
case R_PPC64_REL24_NOTOC:
|
|
if (sym.has_plt(ctx) || sym.esym().uses_toc()) {
|
|
i64 val = no_r2save_thunk_addr() + A - P;
|
|
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
|
} else {
|
|
i64 val = S + A - P;
|
|
if (sign_extend(val, 25) != val)
|
|
val = no_r2save_thunk_addr() + A - P;
|
|
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
|
}
|
|
break;
|
|
case R_PPC64_REL32:
|
|
*(ul32 *)loc = S + A - P;
|
|
break;
|
|
case R_PPC64_REL64:
|
|
*(ul64 *)loc = S + A - P;
|
|
break;
|
|
case R_PPC64_REL16_HA:
|
|
*(ul16 *)loc = ha(S + A - P);
|
|
break;
|
|
case R_PPC64_REL16_LO:
|
|
*(ul16 *)loc = lo(S + A - P);
|
|
break;
|
|
case R_PPC64_PLT16_HA:
|
|
*(ul16 *)loc = ha(G + GOT - TOC);
|
|
break;
|
|
case R_PPC64_PLT16_HI:
|
|
*(ul16 *)loc = hi(G + GOT - TOC);
|
|
break;
|
|
case R_PPC64_PLT16_LO:
|
|
*(ul16 *)loc = lo(G + GOT - TOC);
|
|
break;
|
|
case R_PPC64_PLT16_LO_DS:
|
|
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
|
break;
|
|
case R_PPC64_PLT_PCREL34:
|
|
case R_PPC64_PLT_PCREL34_NOTOC:
|
|
case R_PPC64_GOT_PCREL34:
|
|
write34(loc, G + GOT - P);
|
|
break;
|
|
case R_PPC64_PCREL34:
|
|
write34(loc, S + A - P);
|
|
break;
|
|
case R_PPC64_GOT_TPREL16_HA:
|
|
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
|
break;
|
|
case R_PPC64_GOT_TPREL16_LO_DS:
|
|
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
|
break;
|
|
case R_PPC64_GOT_TPREL_PCREL34:
|
|
write34(loc, sym.get_gottp_addr(ctx) - P);
|
|
break;
|
|
case R_PPC64_GOT_TLSGD16_HA:
|
|
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
|
break;
|
|
case R_PPC64_GOT_TLSGD16_LO:
|
|
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
|
break;
|
|
case R_PPC64_GOT_TLSGD_PCREL34:
|
|
write34(loc, sym.get_tlsgd_addr(ctx) - P);
|
|
break;
|
|
case R_PPC64_GOT_TLSLD16_HA:
|
|
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
|
break;
|
|
case R_PPC64_GOT_TLSLD16_LO:
|
|
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
|
break;
|
|
case R_PPC64_GOT_TLSLD_PCREL34:
|
|
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
|
|
break;
|
|
case R_PPC64_DTPREL16_HA:
|
|
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
|
break;
|
|
case R_PPC64_DTPREL16_LO:
|
|
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
|
break;
|
|
case R_PPC64_DTPREL16_LO_DS:
|
|
*(ul16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
|
|
break;
|
|
case R_PPC64_DTPREL34:
|
|
write34(loc, S + A - ctx.dtp_addr);
|
|
break;
|
|
case R_PPC64_TPREL16_HA:
|
|
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
|
break;
|
|
case R_PPC64_TPREL16_LO:
|
|
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
|
break;
|
|
case R_PPC64_TPREL34:
|
|
write34(loc, S + A - ctx.tp_addr);
|
|
break;
|
|
case R_PPC64_PLTSEQ:
|
|
case R_PPC64_PLTSEQ_NOTOC:
|
|
case R_PPC64_PLTCALL:
|
|
case R_PPC64_PLTCALL_NOTOC:
|
|
case R_PPC64_TLS:
|
|
case R_PPC64_TLSGD:
|
|
case R_PPC64_TLSLD:
|
|
break;
|
|
default:
|
|
unreachable();
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
|
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
|
|
|
for (i64 i = 0; i < rels.size(); i++) {
|
|
const ElfRel<E> &rel = rels[i];
|
|
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
|
continue;
|
|
|
|
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
|
u8 *loc = base + rel.r_offset;
|
|
|
|
auto check = [&](i64 val, i64 lo, i64 hi) {
|
|
if (val < lo || hi <= val)
|
|
Error(ctx) << *this << ": relocation " << rel << " against "
|
|
<< sym << " out of range: " << val << " is not in ["
|
|
<< lo << ", " << hi << ")";
|
|
};
|
|
|
|
SectionFragment<E> *frag;
|
|
i64 frag_addend;
|
|
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
|
|
|
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
|
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
|
|
|
switch (rel.r_type) {
|
|
case R_PPC64_ADDR64:
|
|
if (std::optional<u64> val = get_tombstone(sym, frag))
|
|
*(ul64 *)loc = *val;
|
|
else
|
|
*(ul64 *)loc = S + A;
|
|
break;
|
|
case R_PPC64_ADDR32: {
|
|
i64 val = S + A;
|
|
check(val, 0, 1LL << 32);
|
|
*(ul32 *)loc = val;
|
|
break;
|
|
}
|
|
case R_PPC64_DTPREL64:
|
|
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
|
break;
|
|
default:
|
|
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
|
<< rel;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
|
assert(shdr().sh_flags & SHF_ALLOC);
|
|
|
|
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
|
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
|
|
|
// Scan relocations
|
|
for (i64 i = 0; i < rels.size(); i++) {
|
|
const ElfRel<E> &rel = rels[i];
|
|
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
|
continue;
|
|
|
|
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
|
|
|
if (sym.is_ifunc())
|
|
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
|
|
|
switch (rel.r_type) {
|
|
case R_PPC64_ADDR64:
|
|
if (name() == ".toc")
|
|
scan_toc_rel(ctx, sym, rel);
|
|
else
|
|
scan_dyn_absrel(ctx, sym, rel);
|
|
break;
|
|
case R_PPC64_GOT_TPREL16_HA:
|
|
case R_PPC64_GOT_TPREL_PCREL34:
|
|
sym.flags |= NEEDS_GOTTP;
|
|
break;
|
|
case R_PPC64_REL24:
|
|
if (sym.is_imported)
|
|
sym.flags |= NEEDS_PLT;
|
|
break;
|
|
case R_PPC64_REL24_NOTOC:
|
|
if (sym.is_imported)
|
|
sym.flags |= NEEDS_PLT;
|
|
ctx.extra.is_power10 = true;
|
|
break;
|
|
case R_PPC64_PLT16_HA:
|
|
case R_PPC64_PLT_PCREL34:
|
|
case R_PPC64_PLT_PCREL34_NOTOC:
|
|
case R_PPC64_GOT_PCREL34:
|
|
sym.flags |= NEEDS_GOT;
|
|
break;
|
|
case R_PPC64_GOT_TLSGD16_HA:
|
|
case R_PPC64_GOT_TLSGD_PCREL34:
|
|
sym.flags |= NEEDS_TLSGD;
|
|
break;
|
|
case R_PPC64_GOT_TLSLD16_HA:
|
|
case R_PPC64_GOT_TLSLD_PCREL34:
|
|
ctx.needs_tlsld = true;
|
|
break;
|
|
case R_PPC64_TPREL16_HA:
|
|
case R_PPC64_TPREL16_LO:
|
|
case R_PPC64_TPREL34:
|
|
check_tlsle(ctx, sym, rel);
|
|
break;
|
|
case R_PPC64_REL32:
|
|
case R_PPC64_REL64:
|
|
case R_PPC64_TOC16_HA:
|
|
case R_PPC64_TOC16_LO:
|
|
case R_PPC64_TOC16_LO_DS:
|
|
case R_PPC64_TOC16_DS:
|
|
case R_PPC64_REL16_HA:
|
|
case R_PPC64_REL16_LO:
|
|
case R_PPC64_PLT16_HI:
|
|
case R_PPC64_PLT16_LO:
|
|
case R_PPC64_PLT16_LO_DS:
|
|
case R_PPC64_PCREL34:
|
|
case R_PPC64_PLTSEQ:
|
|
case R_PPC64_PLTSEQ_NOTOC:
|
|
case R_PPC64_PLTCALL:
|
|
case R_PPC64_PLTCALL_NOTOC:
|
|
case R_PPC64_GOT_TPREL16_LO_DS:
|
|
case R_PPC64_GOT_TLSGD16_LO:
|
|
case R_PPC64_GOT_TLSLD16_LO:
|
|
case R_PPC64_TLS:
|
|
case R_PPC64_TLSGD:
|
|
case R_PPC64_TLSLD:
|
|
case R_PPC64_DTPREL16_HA:
|
|
case R_PPC64_DTPREL16_LO:
|
|
case R_PPC64_DTPREL16_LO_DS:
|
|
case R_PPC64_DTPREL34:
|
|
break;
|
|
default:
|
|
Error(ctx) << *this << ": unknown relocation: " << rel;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
|
// If the destination is PLT, we read an address from .got.plt or .got
|
|
// and jump there.
|
|
static const ul32 plt_thunk[] = {
|
|
0xf841'0018, // std r2, 24(r1)
|
|
0x6000'0000, // nop
|
|
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
|
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
|
0x7d89'03a6, // mtctr r12
|
|
0x4e80'0420, // bctr
|
|
};
|
|
|
|
static const ul32 plt_thunk_power10[] = {
|
|
0xf841'0018, // std r2, 24(r1)
|
|
0x6000'0000, // nop
|
|
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
|
0xe580'0000,
|
|
0x7d89'03a6, // mtctr r12
|
|
0x4e80'0420, // bctr
|
|
};
|
|
|
|
// If the destination is a non-imported function, we directly jump
|
|
// to its local entry point.
|
|
static const ul32 local_thunk[] = {
|
|
0xf841'0018, // std r2, 24(r1)
|
|
0x6000'0000, // nop
|
|
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
|
0x398c'0000, // addi r12, r12, foo@toc@lo
|
|
0x7d89'03a6, // mtctr r12
|
|
0x4e80'0420, // bctr
|
|
};
|
|
|
|
static const ul32 local_thunk_power10[] = {
|
|
0xf841'0018, // std r2, 24(r1)
|
|
0x6000'0000, // nop
|
|
0x0610'0000, // pla r12, foo@pcrel
|
|
0x3980'0000,
|
|
0x7d89'03a6, // mtctr r12
|
|
0x4e80'0420, // bctr
|
|
};
|
|
|
|
static_assert(E::thunk_size == sizeof(plt_thunk));
|
|
static_assert(E::thunk_size == sizeof(plt_thunk_power10));
|
|
static_assert(E::thunk_size == sizeof(local_thunk));
|
|
static_assert(E::thunk_size == sizeof(local_thunk_power10));
|
|
|
|
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
|
u64 P = output_section.shdr.sh_addr + offset;
|
|
u64 TOC = ctx.extra.TOC->value;
|
|
|
|
for (Symbol<E> *sym : symbols) {
|
|
if (sym->has_plt(ctx)) {
|
|
u64 got =
|
|
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
|
|
|
|
if (ctx.extra.is_power10) {
|
|
memcpy(buf, plt_thunk_power10, E::thunk_size);
|
|
write34(buf + 8, got - P - 8);
|
|
} else {
|
|
memcpy(buf, plt_thunk, E::thunk_size);
|
|
*(ul32 *)(buf + 8) |= higha(got - TOC);
|
|
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
|
}
|
|
} else {
|
|
u64 S = sym->get_addr(ctx);
|
|
if (ctx.extra.is_power10) {
|
|
memcpy(buf, local_thunk_power10, E::thunk_size);
|
|
write34(buf + 8, S - P - 8);
|
|
} else {
|
|
memcpy(buf, local_thunk, E::thunk_size);
|
|
*(ul32 *)(buf + 8) |= higha(S - TOC);
|
|
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
|
}
|
|
}
|
|
|
|
buf += E::thunk_size;
|
|
P += E::thunk_size;
|
|
}
|
|
}
|
|
|
|
// GCC may emit references to the following functions in function prologue
|
|
// and epiilogue if -Os is specified. For some reason, these functions are
|
|
// not in libgcc.a and expected to be synthesized by the linker.
|
|
const std::vector<std::pair<std::string_view, u32>>
|
|
ppc64_save_restore_insns = {
|
|
{ "_savegpr0_14", 0xf9c1ff70 }, // std r14,-144(r1)
|
|
{ "_savegpr0_15", 0xf9e1ff78 }, // std r15,-136(r1)
|
|
{ "_savegpr0_16", 0xfa01ff80 }, // std r16,-128(r1)
|
|
{ "_savegpr0_17", 0xfa21ff88 }, // std r17,-120(r1)
|
|
{ "_savegpr0_18", 0xfa41ff90 }, // std r18,-112(r1)
|
|
{ "_savegpr0_19", 0xfa61ff98 }, // std r19,-104(r1)
|
|
{ "_savegpr0_20", 0xfa81ffa0 }, // std r20,-96(r1)
|
|
{ "_savegpr0_21", 0xfaa1ffa8 }, // std r21,-88(r1)
|
|
{ "_savegpr0_22", 0xfac1ffb0 }, // std r22,-80(r1)
|
|
{ "_savegpr0_23", 0xfae1ffb8 }, // std r23,-72(r1)
|
|
{ "_savegpr0_24", 0xfb01ffc0 }, // std r24,-64(r1)
|
|
{ "_savegpr0_25", 0xfb21ffc8 }, // std r25,-56(r1)
|
|
{ "_savegpr0_26", 0xfb41ffd0 }, // std r26,-48(r1)
|
|
{ "_savegpr0_27", 0xfb61ffd8 }, // std r27,-40(r1)
|
|
{ "_savegpr0_28", 0xfb81ffe0 }, // std r28,-32(r1)
|
|
{ "_savegpr0_29", 0xfba1ffe8 }, // std r29,-24(r1)
|
|
{ "_savegpr0_30", 0xfbc1fff0 }, // std r30,-16(r1)
|
|
{ "_savegpr0_31", 0xfbe1fff8 }, // std r31,-8(r1)
|
|
{ "", 0xf8010010 }, // std r0,16(r1)
|
|
{ "", 0x4e800020 }, // blr
|
|
|
|
{ "_restgpr0_14", 0xe9c1ff70 }, // ld r14,-144(r1)
|
|
{ "_restgpr0_15", 0xe9e1ff78 }, // ld r15,-136(r1)
|
|
{ "_restgpr0_16", 0xea01ff80 }, // ld r16,-128(r1)
|
|
{ "_restgpr0_17", 0xea21ff88 }, // ld r17,-120(r1)
|
|
{ "_restgpr0_18", 0xea41ff90 }, // ld r18,-112(r1)
|
|
{ "_restgpr0_19", 0xea61ff98 }, // ld r19,-104(r1)
|
|
{ "_restgpr0_20", 0xea81ffa0 }, // ld r20,-96(r1)
|
|
{ "_restgpr0_21", 0xeaa1ffa8 }, // ld r21,-88(r1)
|
|
{ "_restgpr0_22", 0xeac1ffb0 }, // ld r22,-80(r1)
|
|
{ "_restgpr0_23", 0xeae1ffb8 }, // ld r23,-72(r1)
|
|
{ "_restgpr0_24", 0xeb01ffc0 }, // ld r24,-64(r1)
|
|
{ "_restgpr0_25", 0xeb21ffc8 }, // ld r25,-56(r1)
|
|
{ "_restgpr0_26", 0xeb41ffd0 }, // ld r26,-48(r1)
|
|
{ "_restgpr0_27", 0xeb61ffd8 }, // ld r27,-40(r1)
|
|
{ "_restgpr0_28", 0xeb81ffe0 }, // ld r28,-32(r1)
|
|
{ "_restgpr0_29", 0xe8010010 }, // ld r0,16(r1)
|
|
{ "", 0xeba1ffe8 }, // ld r29,-24(r1)
|
|
{ "", 0x7c0803a6 }, // mtlr r0
|
|
{ "", 0xebc1fff0 }, // ld r30,-16(r1)
|
|
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
|
|
{ "", 0x4e800020 }, // blr
|
|
{ "_restgpr0_30", 0xebc1fff0 }, // ld r30,-16(r1)
|
|
{ "_restgpr0_31", 0xe8010010 }, // ld r0,16(r1)
|
|
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
|
|
{ "", 0x7c0803a6 }, // mtlr r0
|
|
{ "", 0x4e800020 }, // blr
|
|
|
|
{ "_savegpr1_14", 0xf9ccff70 }, // std r14,-144(r12)
|
|
{ "_savegpr1_15", 0xf9ecff78 }, // std r15,-136(r12)
|
|
{ "_savegpr1_16", 0xfa0cff80 }, // std r16,-128(r12)
|
|
{ "_savegpr1_17", 0xfa2cff88 }, // std r17,-120(r12)
|
|
{ "_savegpr1_18", 0xfa4cff90 }, // std r18,-112(r12)
|
|
{ "_savegpr1_19", 0xfa6cff98 }, // std r19,-104(r12)
|
|
{ "_savegpr1_20", 0xfa8cffa0 }, // std r20,-96(r12)
|
|
{ "_savegpr1_21", 0xfaacffa8 }, // std r21,-88(r12)
|
|
{ "_savegpr1_22", 0xfaccffb0 }, // std r22,-80(r12)
|
|
{ "_savegpr1_23", 0xfaecffb8 }, // std r23,-72(r12)
|
|
{ "_savegpr1_24", 0xfb0cffc0 }, // std r24,-64(r12)
|
|
{ "_savegpr1_25", 0xfb2cffc8 }, // std r25,-56(r12)
|
|
{ "_savegpr1_26", 0xfb4cffd0 }, // std r26,-48(r12)
|
|
{ "_savegpr1_27", 0xfb6cffd8 }, // std r27,-40(r12)
|
|
{ "_savegpr1_28", 0xfb8cffe0 }, // std r28,-32(r12)
|
|
{ "_savegpr1_29", 0xfbacffe8 }, // std r29,-24(r12)
|
|
{ "_savegpr1_30", 0xfbccfff0 }, // std r30,-16(r12)
|
|
{ "_savegpr1_31", 0xfbecfff8 }, // std r31,-8(r12)
|
|
{ "", 0x4e800020 }, // blr
|
|
|
|
{ "_restgpr1_14", 0xe9ccff70 }, // ld r14,-144(r12)
|
|
{ "_restgpr1_15", 0xe9ecff78 }, // ld r15,-136(r12)
|
|
{ "_restgpr1_16", 0xea0cff80 }, // ld r16,-128(r12)
|
|
{ "_restgpr1_17", 0xea2cff88 }, // ld r17,-120(r12)
|
|
{ "_restgpr1_18", 0xea4cff90 }, // ld r18,-112(r12)
|
|
{ "_restgpr1_19", 0xea6cff98 }, // ld r19,-104(r12)
|
|
{ "_restgpr1_20", 0xea8cffa0 }, // ld r20,-96(r12)
|
|
{ "_restgpr1_21", 0xeaacffa8 }, // ld r21,-88(r12)
|
|
{ "_restgpr1_22", 0xeaccffb0 }, // ld r22,-80(r12)
|
|
{ "_restgpr1_23", 0xeaecffb8 }, // ld r23,-72(r12)
|
|
{ "_restgpr1_24", 0xeb0cffc0 }, // ld r24,-64(r12)
|
|
{ "_restgpr1_25", 0xeb2cffc8 }, // ld r25,-56(r12)
|
|
{ "_restgpr1_26", 0xeb4cffd0 }, // ld r26,-48(r12)
|
|
{ "_restgpr1_27", 0xeb6cffd8 }, // ld r27,-40(r12)
|
|
{ "_restgpr1_28", 0xeb8cffe0 }, // ld r28,-32(r12)
|
|
{ "_restgpr1_29", 0xebacffe8 }, // ld r29,-24(r12)
|
|
{ "_restgpr1_30", 0xebccfff0 }, // ld r30,-16(r12)
|
|
{ "_restgpr1_31", 0xebecfff8 }, // ld r31,-8(r12)
|
|
{ "", 0x4e800020 }, // blr
|
|
};
|
|
|
|
void PPC64SaveRestoreSection::copy_buf(Context<E> &ctx) {
|
|
ul32 *buf = (ul32 *)(ctx.buf + this->shdr.sh_offset);
|
|
for (auto [label, insn] : ppc64_save_restore_insns)
|
|
*buf++ = insn;
|
|
}
|
|
|
|
template <>
|
|
u64 get_eflags(Context<E> &ctx) {
|
|
return 2;
|
|
}
|
|
|
|
} // namespace mold::elf
|