]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.18] cmd/link: use TOC-relative trampolines on PPC64 when needed
authorPaul E. Murphy <murp@ibm.com>
Mon, 3 May 2021 16:00:54 +0000 (11:00 -0500)
committerHeschi Kreinick <heschi@google.com>
Wed, 6 Jul 2022 20:55:56 +0000 (20:55 +0000)
When linking a PIE binary with the internal linker, TOC relative
relocations need to be generated. Update trampolines to indirect
call using R12 to more closely match the AIX/ELFv2 regardless of
buildmode, and work with position-indepdent code.

Likewise, update the check for offseting R_CALLPOWER relocs to
make a local call. It should be checking ldr.AttrExternal, not
ldr.IsExternal. This offset should not be adjusted for external
(non-go) object files, it is handled when ELF reloc are translated
into go relocs.

And, update trampoline tests to verify these are generated correctly
and produce a working binary using -buildmode=pie on ppc64le.

Fixes #53107

Change-Id: I8a2dea06c3237bdf0e87888b56a17b6c4c99a7de
Reviewed-on: https://go-review.googlesource.com/c/go/+/400234
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Paul Murphy <murp@ibm.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/go/+/409075
Run-TryBot: Cherry Mui <cherryyz@google.com>

src/cmd/link/internal/ppc64/asm.go
src/cmd/link/link_test.go

index d2b140b45dee9a30c68479608038d99605e9f91a..73c2718a3369f755ac3794f0a3cbbea173c48586 100644 (file)
@@ -766,7 +766,7 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
        // For external linking, the linker can insert a call stub to handle a long call, but depends on having the TOC address in
        // r2.  For those build modes with external linking where the TOC address is not maintained in r2, trampolines must be created.
        if ctxt.IsExternal() && r2Valid(ctxt) {
-               // No trampolines needed since r2 contains the TOC
+               // The TOC pointer is valid. The external linker will insert trampolines.
                return
        }
 
@@ -819,14 +819,9 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
                                }
                        }
                        if ldr.SymType(tramp) == 0 {
-                               if r2Valid(ctxt) {
-                                       // Should have returned for above cases
-                                       ctxt.Errorf(s, "unexpected trampoline for shared or dynamic linking")
-                               } else {
-                                       trampb := ldr.MakeSymbolUpdater(tramp)
-                                       ctxt.AddTramp(trampb)
-                                       gentramp(ctxt, ldr, trampb, rs, r.Add())
-                               }
+                               trampb := ldr.MakeSymbolUpdater(tramp)
+                               ctxt.AddTramp(trampb)
+                               gentramp(ctxt, ldr, trampb, rs, r.Add())
                        }
                        sb := ldr.MakeSymbolUpdater(s)
                        relocs := sb.Relocs()
@@ -842,7 +837,6 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
 func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, target loader.Sym, offset int64) {
        tramp.SetSize(16) // 4 instructions
        P := make([]byte, tramp.Size())
-       t := ldr.SymValue(target) + offset
        var o1, o2 uint32
 
        if ctxt.IsAIX() {
@@ -851,8 +845,8 @@ func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, ta
                // However, all text symbols are accessed with a TOC symbol as
                // text relocations aren't supposed to be possible.
                // So, keep using the external linking way to be more AIX friendly.
-               o1 = uint32(0x3fe20000) // lis r2, toctargetaddr hi
-               o2 = uint32(0xebff0000) // ld r31, toctargetaddr lo
+               o1 = uint32(0x3c000000) | 12<<21 | 2<<16  // addis r12,  r2, toctargetaddr hi
+               o2 = uint32(0xe8000000) | 12<<21 | 12<<16 // ld    r12, r12, toctargetaddr lo
 
                toctramp := ldr.CreateSymForUpdate("TOC."+ldr.SymName(tramp.Sym()), 0)
                toctramp.SetType(sym.SXCOFFTOC)
@@ -866,31 +860,32 @@ func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, ta
                // Used for default build mode for an executable
                // Address of the call target is generated using
                // relocation and doesn't depend on r2 (TOC).
-               o1 = uint32(0x3fe00000) // lis r31,targetaddr hi
-               o2 = uint32(0x3bff0000) // addi r31,targetaddr lo
+               o1 = uint32(0x3c000000) | 12<<21          // lis  r12,targetaddr hi
+               o2 = uint32(0x38000000) | 12<<21 | 12<<16 // addi r12,r12,targetaddr lo
 
-               // With external linking, the target address must be
-               // relocated using LO and HA
-               if ctxt.IsExternal() || ldr.SymValue(target) == 0 {
+               t := ldr.SymValue(target)
+               if t == 0 || r2Valid(ctxt) || ctxt.IsExternal() {
+                       // Target address is unknown, generate relocations
                        r, _ := tramp.AddRel(objabi.R_ADDRPOWER)
+                       if r2Valid(ctxt) {
+                               // Use a TOC relative address if R2 holds the TOC pointer
+                               o1 |= uint32(2 << 16) // Transform lis r31,ha into addis r31,r2,ha
+                               r.SetType(objabi.R_ADDRPOWER_TOCREL)
+                       }
                        r.SetOff(0)
                        r.SetSiz(8) // generates 2 relocations: HA + LO
                        r.SetSym(target)
                        r.SetAdd(offset)
                } else {
-                       // adjustment needed if lo has sign bit set
-                       // when using addi to compute address
-                       val := uint32((t & 0xffff0000) >> 16)
-                       if t&0x8000 != 0 {
-                               val += 1
-                       }
-                       o1 |= val                // hi part of addr
-                       o2 |= uint32(t & 0xffff) // lo part of addr
+                       // The target address is known, resolve it
+                       t += offset
+                       o1 |= (uint32(t) + 0x8000) >> 16 // HA
+                       o2 |= uint32(t) & 0xFFFF         // LO
                }
        }
 
-       o3 := uint32(0x7fe903a6) // mtctr r31
-       o4 := uint32(0x4e800420) // bctr
+       o3 := uint32(0x7c0903a6) | 12<<21 // mtctr r12
+       o4 := uint32(0x4e800420)          // bctr
        ctxt.Arch.ByteOrder.PutUint32(P, o1)
        ctxt.Arch.ByteOrder.PutUint32(P[4:], o2)
        ctxt.Arch.ByteOrder.PutUint32(P[8:], o3)
@@ -962,7 +957,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
                // If we are linking PIE or shared code, all golang generated object files have an extra 2 instruction prologue
                // to regenerate the TOC pointer from R12.  The exception are two special case functions tested below.  Note,
                // local call offsets for externally generated objects are accounted for when converting into golang relocs.
-               if !ldr.IsExternal(rs) && ldr.AttrShared(rs) && tgtName != "runtime.duffzero" && tgtName != "runtime.duffcopy" {
+               if !ldr.AttrExternal(rs) && ldr.AttrShared(rs) && tgtName != "runtime.duffzero" && tgtName != "runtime.duffcopy" {
                        // Furthermore, only apply the offset if the target looks like the start of a function call.
                        if r.Add() == 0 && ldr.SymType(rs) == sym.STEXT {
                                t += 8
index ad7658bb2577f0fb163b8854d63c47c26c470a7a..d58ee8669bdc80e929e44827b5bd285737415aad 100644 (file)
@@ -642,8 +642,12 @@ func TestTrampoline(t *testing.T) {
        // For stress test, we set -debugtramp=2 flag, which sets a very low
        // threshold for trampoline generation, and essentially all cross-package
        // calls will use trampolines.
+       buildmodes := []string{"default"}
        switch runtime.GOARCH {
-       case "arm", "arm64", "ppc64", "ppc64le":
+       case "arm", "arm64", "ppc64":
+       case "ppc64le":
+               // Trampolines are generated differently when internal linking PIE, test them too.
+               buildmodes = append(buildmodes, "pie")
        default:
                t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH)
        }
@@ -661,18 +665,20 @@ func TestTrampoline(t *testing.T) {
        }
        exe := filepath.Join(tmpdir, "hello.exe")
 
-       cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2", "-o", exe, src)
-       out, err := cmd.CombinedOutput()
-       if err != nil {
-               t.Fatalf("build failed: %v\n%s", err, out)
-       }
-       cmd = exec.Command(exe)
-       out, err = cmd.CombinedOutput()
-       if err != nil {
-               t.Errorf("executable failed to run: %v\n%s", err, out)
-       }
-       if string(out) != "hello\n" {
-               t.Errorf("unexpected output:\n%s", out)
+       for _, mode := range buildmodes {
+               cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src)
+               out, err := cmd.CombinedOutput()
+               if err != nil {
+                       t.Fatalf("build (%s) failed: %v\n%s", mode, err, out)
+               }
+               cmd = exec.Command(exe)
+               out, err = cmd.CombinedOutput()
+               if err != nil {
+                       t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out)
+               }
+               if string(out) != "hello\n" {
+                       t.Errorf("unexpected output (%s):\n%s", mode, out)
+               }
        }
 }
 
@@ -693,8 +699,12 @@ func TestTrampolineCgo(t *testing.T) {
        // For stress test, we set -debugtramp=2 flag, which sets a very low
        // threshold for trampoline generation, and essentially all cross-package
        // calls will use trampolines.
+       buildmodes := []string{"default"}
        switch runtime.GOARCH {
-       case "arm", "arm64", "ppc64", "ppc64le":
+       case "arm", "arm64", "ppc64":
+       case "ppc64le":
+               // Trampolines are generated differently when internal linking PIE, test them too.
+               buildmodes = append(buildmodes, "pie")
        default:
                t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH)
        }
@@ -713,37 +723,39 @@ func TestTrampolineCgo(t *testing.T) {
        }
        exe := filepath.Join(tmpdir, "hello.exe")
 
-       cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2", "-o", exe, src)
-       out, err := cmd.CombinedOutput()
-       if err != nil {
-               t.Fatalf("build failed: %v\n%s", err, out)
-       }
-       cmd = exec.Command(exe)
-       out, err = cmd.CombinedOutput()
-       if err != nil {
-               t.Errorf("executable failed to run: %v\n%s", err, out)
-       }
-       if string(out) != "hello\n" && string(out) != "hello\r\n" {
-               t.Errorf("unexpected output:\n%s", out)
-       }
+       for _, mode := range buildmodes {
+               cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src)
+               out, err := cmd.CombinedOutput()
+               if err != nil {
+                       t.Fatalf("build (%s) failed: %v\n%s", mode, err, out)
+               }
+               cmd = exec.Command(exe)
+               out, err = cmd.CombinedOutput()
+               if err != nil {
+                       t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out)
+               }
+               if string(out) != "hello\n" && string(out) != "hello\r\n" {
+                       t.Errorf("unexpected output (%s):\n%s", mode, out)
+               }
 
-       // Test internal linking mode.
+               // Test internal linking mode.
 
-       if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || (runtime.GOARCH == "arm64" && runtime.GOOS == "windows") || !testenv.CanInternalLink() {
-               return // internal linking cgo is not supported
-       }
-       cmd = exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src)
-       out, err = cmd.CombinedOutput()
-       if err != nil {
-               t.Fatalf("build failed: %v\n%s", err, out)
-       }
-       cmd = exec.Command(exe)
-       out, err = cmd.CombinedOutput()
-       if err != nil {
-               t.Errorf("executable failed to run: %v\n%s", err, out)
-       }
-       if string(out) != "hello\n" && string(out) != "hello\r\n" {
-               t.Errorf("unexpected output:\n%s", out)
+               if runtime.GOARCH == "ppc64" || (runtime.GOARCH == "arm64" && runtime.GOOS == "windows") || !testenv.CanInternalLink() {
+                       return // internal linking cgo is not supported
+               }
+               cmd = exec.Command(testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src)
+               out, err = cmd.CombinedOutput()
+               if err != nil {
+                       t.Fatalf("build (%s) failed: %v\n%s", mode, err, out)
+               }
+               cmd = exec.Command(exe)
+               out, err = cmd.CombinedOutput()
+               if err != nil {
+                       t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out)
+               }
+               if string(out) != "hello\n" && string(out) != "hello\r\n" {
+                       t.Errorf("unexpected output (%s):\n%s", mode, out)
+               }
        }
 }