]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link/internal/ppc64: support non-PIC PLT call stubs
authorPaul E. Murphy <murp@ibm.com>
Mon, 24 Apr 2023 20:59:17 +0000 (15:59 -0500)
committerGopher Robot <gobot@golang.org>
Wed, 3 May 2023 22:13:51 +0000 (22:13 +0000)
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>
src/cmd/link/internal/ppc64/asm.go

index 121fbf8fa50f5e1b73b945df94cb1934264ff8b0..b77ee500fc091fbb65c152223a0e5b7913765460 100644 (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:])
-       }
-       if nop != 0x60000000 {
-               ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), 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)
        }
-       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()))
-       }
-       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()))
+       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(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