1
0
mirror of https://github.com/golang/go synced 2024-09-29 01:14:29 -06:00

cmd/link/internal/ppc64: support non-PIC PLT call stubs

Simplify the PLT stub generation code to minimize stub generation
knowing there is only ever a single TOC pointer when linking
internally.

The OpenBSD port requires Go make dynamic calls into its C library,
so the linker must create stubs which work without R2 being set up.
This new case is exactly case 3 described in the PPC64 ELFv2 1.5
section 4.2.5.3.

Updates #56001

Change-Id: I07ebd08442302e55b94b57db474dfd7e7a0c2ac9
Reviewed-on: https://go-review.googlesource.com/c/go/+/488316
Auto-Submit: Carlos Amedee <carlos@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Paul Murphy <murp@ibm.com>
Reviewed-by: Lynn Boger <laboger@linux.vnet.ibm.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Paul E. Murphy 2023-04-24 15:59:17 -05:00 committed by Gopher Robot
parent 767fbe01ae
commit f742ddc349

View File

@ -48,17 +48,18 @@ import (
// The build configuration supports PC-relative instructions and relocations (limited to tested targets).
var hasPCrel = buildcfg.GOPPC64 >= 10 && buildcfg.GOOS == "linux"
func genpltstub(ctxt *ld.Link, ldr *loader.Loader, r loader.Reloc, s loader.Sym) (sym loader.Sym, firstUse bool) {
func genpltstub(ctxt *ld.Link, ldr *loader.Loader, r loader.Reloc, ri int, s loader.Sym) (sym loader.Sym, firstUse bool) {
// The ppc64 ABI PLT has similar concepts to other
// architectures, but is laid out quite differently. When we
// see an R_PPC64_REL24 relocation to a dynamic symbol
// (indicating that the call needs to go through the PLT), we
// generate up to three stubs and reserve a PLT slot.
// see a relocation to a dynamic symbol (indicating that the
// call needs to go through the PLT), we generate up to three
// stubs and reserve a PLT slot.
//
// 1) The call site will be bl x; nop (where the relocation
// applies to the bl). We rewrite this to bl x_stub; ld
// r2,24(r1). The ld is necessary because x_stub will save
// r2 (the TOC pointer) at 24(r1) (the "TOC save slot").
// 1) The call site is a "bl x" where genpltstub rewrites it to
// "bl x_stub". Depending on the properties of the caller
// (see ELFv2 1.5 4.2.5.3), a nop may be expected immediately
// after the bl. This nop is rewritten to ld r2,24(r1) to
// restore the toc pointer saved by x_stub.
//
// 2) We reserve space for a pointer in the .plt section (once
// per referenced dynamic function). .plt is a data
@ -67,11 +68,8 @@ func genpltstub(ctxt *ld.Link, ldr *loader.Loader, r loader.Reloc, s loader.Sym)
// dynamic linker will fill each slot with a pointer to the
// corresponding x@plt entry point.
//
// 3) We generate the "call stub" x_stub (once per dynamic
// function/object file pair). This saves the TOC in the
// TOC save slot, reads the function pointer from x's .plt
// slot and calls it like any other global entry point
// (including setting r12 to the function address).
// 3) We generate a "call stub" x_stub based on the properties
// of the caller.
//
// 4) We generate the "symbol resolver stub" x@plt (once per
// dynamic function). This is solely a branch to the glink
@ -90,49 +88,65 @@ func genpltstub(ctxt *ld.Link, ldr *loader.Loader, r loader.Reloc, s loader.Sym)
// platforms and ppc64's .glink is like .plt on other
// platforms.
// Find all R_PPC64_REL24 relocations that reference dynamic
// imports. Reserve PLT entries for these symbols and
// generate call stubs. The call stubs need to live in .text,
// which is why we need to do this pass this early.
//
// This assumes "case 1" from the ABI, where the caller needs
// us to save and restore the TOC pointer.
// Find all relocations that reference dynamic imports.
// Reserve PLT entries for these symbols and generate call
// stubs. The call stubs need to live in .text, which is why we
// need to do this pass this early.
// Reserve PLT entry and generate symbol
// resolver
// Reserve PLT entry and generate symbol resolver
addpltsym(ctxt, ldr, r.Sym())
// Generate call stub. Important to note that we're looking
// up the stub using the same version as the parent symbol (s),
// needed so that symtoc() will select the right .TOC. symbol
// when processing the stub. In older versions of the linker
// this was done by setting stub.Outer to the parent, but
// if the stub has the right version initially this is not needed.
n := fmt.Sprintf("%s.%s", ldr.SymName(s), ldr.SymName(r.Sym()))
stub := ldr.CreateSymForUpdate(n, ldr.SymVersion(s))
// The stub types are described in gencallstub.
stubType := 0
stubTypeStr := ""
// For now, the choice of call stub type is determined by whether
// the caller maintains a TOC pointer in R2. A TOC pointer implies
// we can always generate a position independent stub.
//
// For dynamic calls made from an external object, it is safe to
// assume a TOC pointer is maintained. These were imported from
// a R_PPC64_REL24 relocation.
//
// For dynamic calls made from a Go object, the shared attribute
// indicates a PIC symbol, which requires a TOC pointer be
// maintained. Otherwise, a simpler non-PIC stub suffices.
if ldr.AttrExternal(s) || ldr.AttrShared(s) {
stubTypeStr = "_tocrel"
stubType = 1
} else {
stubTypeStr = "_nopic"
stubType = 3
}
n := fmt.Sprintf("_pltstub%s.%s", stubTypeStr, ldr.SymName(r.Sym()))
// When internal linking, all text symbols share the same toc pointer.
stub := ldr.CreateSymForUpdate(n, 0)
firstUse = stub.Size() == 0
if firstUse {
gencallstub(ctxt, ldr, 1, stub, r.Sym())
gencallstub(ctxt, ldr, stubType, stub, r.Sym())
}
// Update the relocation to use the call stub
r.SetSym(stub.Sym())
// Make the symbol writeable so we can fixup toc.
su := ldr.MakeSymbolUpdater(s)
su.MakeWritable()
p := su.Data()
su.SetRelocSym(ri, stub.Sym())
// Check for toc restore slot (a nop), and replace with toc restore.
var nop uint32
if len(p) >= int(r.Off()+8) {
nop = ctxt.Arch.ByteOrder.Uint32(p[r.Off()+4:])
// A type 1 call must restore the toc pointer after the call.
if stubType == 1 {
su.MakeWritable()
p := su.Data()
// Check for a toc pointer restore slot (a nop), and rewrite to restore the toc pointer.
var nop uint32
if len(p) >= int(r.Off()+8) {
nop = ctxt.Arch.ByteOrder.Uint32(p[r.Off()+4:])
}
if nop != 0x60000000 {
ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), r.Off()+4)
}
const o1 = 0xe8410018 // ld r2,24(r1)
ctxt.Arch.ByteOrder.PutUint32(p[r.Off()+4:], o1)
}
if nop != 0x60000000 {
ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), r.Off()+4)
}
const o1 = 0xe8410018 // ld r2,24(r1)
ctxt.Arch.ByteOrder.PutUint32(p[r.Off()+4:], o1)
return stub.Sym(), firstUse
}
@ -150,7 +164,7 @@ func genstubs(ctxt *ld.Link, ldr *loader.Loader) {
switch ldr.SymType(r.Sym()) {
case sym.SDYNIMPORT:
// This call goes through the PLT, generate and call through a PLT stub.
if sym, firstUse := genpltstub(ctxt, ldr, r, s); firstUse {
if sym, firstUse := genpltstub(ctxt, ldr, r, i, s); firstUse {
stubs = append(stubs, sym)
}
@ -344,44 +358,38 @@ func gentext(ctxt *ld.Link, ldr *loader.Loader) {
}
}
// Construct a call stub in stub that calls symbol targ via its PLT
// entry.
func gencallstub(ctxt *ld.Link, ldr *loader.Loader, abicase int, stub *loader.SymbolBuilder, targ loader.Sym) {
if abicase != 1 {
// If we see R_PPC64_TOCSAVE or R_PPC64_REL24_NOTOC
// relocations, we'll need to implement cases 2 and 3.
log.Fatalf("gencallstub only implements case 1 calls")
}
// Create a calling stub. The stubType maps directly to the properties listed in the ELFv2 1.5
// section 4.2.5.3.
//
// There are 3 cases today (as paraphrased from the ELFv2 document):
//
// 1. R2 holds the TOC pointer on entry. The call stub must save R2 into the ELFv2 TOC stack save slot.
//
// 2. R2 holds the TOC pointer on entry. The caller has already saved R2 to the TOC stack save slot.
//
// 3. R2 does not hold the TOC pointer on entry. The caller has no expectations of R2.
//
// Go only needs case 1 and 3 today. Go symbols which have AttrShare set could use case 2, but case 1 always
// works in those cases too.
func gencallstub(ctxt *ld.Link, ldr *loader.Loader, stubType int, stub *loader.SymbolBuilder, targ loader.Sym) {
plt := ctxt.PLT
stub.SetType(sym.STEXT)
// Save TOC pointer in TOC save slot
stub.AddUint32(ctxt.Arch, 0xf8410018) // std r2,24(r1)
// Load the function pointer from the PLT.
rel, ri1 := stub.AddRel(objabi.R_POWER_TOC)
rel.SetOff(int32(stub.Size()))
rel.SetSiz(2)
rel.SetAdd(int64(ldr.SymPlt(targ)))
rel.SetSym(plt)
if ctxt.Arch.ByteOrder == binary.BigEndian {
rel.SetOff(rel.Off() + int32(rel.Siz()))
switch stubType {
case 1:
// Save TOC, then load targ address from PLT using TOC.
stub.AddUint32(ctxt.Arch, 0xf8410018) // std r2,24(r1)
stub.AddSymRef(ctxt.Arch, plt, int64(ldr.SymPlt(targ)), objabi.R_ADDRPOWER_TOCREL_DS, 8)
stub.SetUint32(ctxt.Arch, stub.Size()-8, 0x3d820000) // addis r12,r2,targ@plt@toc@ha
stub.SetUint32(ctxt.Arch, stub.Size()-4, 0xe98c0000) // ld r12,targ@plt@toc@l(r12)
case 3:
// Load targ address from PLT. This is position dependent.
stub.AddSymRef(ctxt.Arch, plt, int64(ldr.SymPlt(targ)), objabi.R_ADDRPOWER_DS, 8)
stub.SetUint32(ctxt.Arch, stub.Size()-8, 0x3d800000) // lis r12,targ@plt@ha
stub.SetUint32(ctxt.Arch, stub.Size()-4, 0xe98c0000) // ld r12,targ@plt@l(r12)
default:
log.Fatalf("gencallstub does not support ELFv2 ABI property %d", stubType)
}
ldr.SetRelocVariant(stub.Sym(), int(ri1), sym.RV_POWER_HA)
stub.AddUint32(ctxt.Arch, 0x3d820000) // addis r12,r2,targ@plt@toc@ha
rel2, ri2 := stub.AddRel(objabi.R_POWER_TOC)
rel2.SetOff(int32(stub.Size()))
rel2.SetSiz(2)
rel2.SetAdd(int64(ldr.SymPlt(targ)))
rel2.SetSym(plt)
if ctxt.Arch.ByteOrder == binary.BigEndian {
rel2.SetOff(rel2.Off() + int32(rel2.Siz()))
}
ldr.SetRelocVariant(stub.Sym(), int(ri2), sym.RV_POWER_LO)
stub.AddUint32(ctxt.Arch, 0xe98c0000) // ld r12,targ@plt@toc@l(r12)
// Jump to the loaded pointer
stub.AddUint32(ctxt.Arch, 0x7d8903a6) // mtctr r12