aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/link
diff options
context:
space:
mode:
authorGeorge Adams <georgeadams1995@gmail.com>2026-02-05 15:05:23 +0000
committerQuim Muntal <quimmuntal@gmail.com>2026-02-13 12:01:23 -0800
commit58bc91a9671f3aec46a7e3c1e55d1d0e303dfe4e (patch)
tree2af3f8c73798554441d53dc0b94a18f3045c5c1f /src/cmd/link
parent4dca8b3e619a321de36b99b6b514d9c01ef76e5e (diff)
downloadgo-58bc91a9671f3aec46a7e3c1e55d1d0e303dfe4e.tar.xz
cmd/link: support PIE on linux/s390x without cgo
Enable PIE builds on linux/s390x when CGO is disabled by teaching the linker to handle dynamic relocations against SDYNIMPORT symbols. This adds support for TLS_IE and handles R_CALL, R_PCRELDBL, and R_ADDR relocations by generating the appropriate PLT/GOT entries instead of rejecting them as unsupported. Fixes #77449 Cq-Include-Trybots: luci.golang.try:gotip-linux-s390x Change-Id: Ib6586780073fedbcbd42b9a2c554a99dd7386aa6 Reviewed-on: https://go-review.googlesource.com/c/go/+/742342 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Quim Muntal <quimmuntal@gmail.com> Reviewed-by: Junyang Shao <shaojunyang@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/cmd/link')
-rw-r--r--src/cmd/link/internal/ld/elf.go4
-rw-r--r--src/cmd/link/internal/s390x/asm.go177
-rw-r--r--src/cmd/link/internal/s390x/obj.go1
3 files changed, 165 insertions, 17 deletions
diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go
index 8cd9e9f1b4..1cb21926a6 100644
--- a/src/cmd/link/internal/ld/elf.go
+++ b/src/cmd/link/internal/ld/elf.go
@@ -1540,10 +1540,6 @@ func (ctxt *Link) doelf() {
dynamic.SetType(sym.SELFSECT)
}
- if ctxt.IsS390X() {
- // S390X uses .got instead of .got.plt
- gotplt = got
- }
thearch.ELF.SetupPLT(ctxt, ctxt.loader, plt, gotplt, dynamic.Sym())
// .dynamic table
diff --git a/src/cmd/link/internal/s390x/asm.go b/src/cmd/link/internal/s390x/asm.go
index dee0348410..9a802f458b 100644
--- a/src/cmd/link/internal/s390x/asm.go
+++ b/src/cmd/link/internal/s390x/asm.go
@@ -37,6 +37,7 @@ import (
"cmd/link/internal/loader"
"cmd/link/internal/sym"
"debug/elf"
+ "log"
)
// gentext generates assembly to append the local moduledata to the global
@@ -87,7 +88,7 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
return false
}
- // Handle relocations found in ELF object files.
+ // Handle relocations found in ELF object files.
case objabi.ElfRelocOffset + objabi.RelocType(elf.R_390_12),
objabi.ElfRelocOffset + objabi.RelocType(elf.R_390_GOT12):
ldr.Errorf(s, "s390x 12-bit relocations have not been implemented (relocation type %d)", r.Type()-objabi.ElfRelocOffset)
@@ -100,9 +101,14 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
if targType == sym.SDYNIMPORT {
ldr.Errorf(s, "unexpected R_390_nn relocation for dynamic symbol %s", ldr.SymName(targ))
}
-
su := ldr.MakeSymbolUpdater(s)
su.SetRelocType(rIdx, objabi.R_ADDR)
+ if target.IsPIE() && target.IsInternal() {
+ // For internal linking PIE, this R_ADDR relocation cannot
+ // be resolved statically. We need to generate a dynamic
+ // relocation. Let the code below handle it.
+ break
+ }
return true
case objabi.ElfRelocOffset + objabi.RelocType(elf.R_390_PC16),
@@ -209,6 +215,101 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymGot(targ))+int64(r.Siz()))
return true
}
+
+ // Reread the reloc to incorporate any changes in type above.
+ relocs := ldr.Relocs(s)
+ r = relocs.At(rIdx)
+
+ switch r.Type() {
+ case objabi.R_CALL, objabi.R_PCRELDBL:
+ if targType != sym.SDYNIMPORT {
+ // nothing to do, the relocation will be laid out in reloc
+ return true
+ }
+ if target.IsExternal() {
+ // External linker will do this relocation.
+ return true
+ }
+ // Internal linking: build a PLT entry and redirect to it.
+ addpltsym(target, ldr, syms, targ)
+ su := ldr.MakeSymbolUpdater(s)
+ su.SetRelocSym(rIdx, syms.PLT)
+ su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymPlt(targ)))
+ return true
+
+ case objabi.R_ADDR:
+ if ldr.SymType(s).IsText() && target.IsElf() {
+ // The code is asking for the address of an external
+ // function. We provide it with the address of the
+ // correspondent GOT symbol.
+ ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_390_GLOB_DAT))
+ su := ldr.MakeSymbolUpdater(s)
+ su.SetRelocSym(rIdx, syms.GOT)
+ su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymGot(targ)))
+ return true
+ }
+
+ // Process dynamic relocations for the data sections.
+ if target.IsPIE() && target.IsInternal() {
+ // When internally linking, generate dynamic relocations
+ // for all typical R_ADDR relocations. The exception
+ // are those R_ADDR that are created as part of generating
+ // the dynamic relocations and must be resolved statically.
+ //
+ // These synthetic static R_ADDR relocs must be skipped
+ // now, or else we will be caught in an infinite loop
+ // of generating synthetic relocs for our synthetic
+ // relocs.
+ switch ldr.SymName(s) {
+ case ".dynsym", ".rela", ".rela.plt", ".got.plt", ".dynamic":
+ return false
+ }
+ } else {
+ // Either internally linking a static executable,
+ // in which case we can resolve these relocations
+ // statically in the 'reloc' phase, or externally
+ // linking, in which case the relocation will be
+ // prepared in the 'reloc' phase and passed to the
+ // external linker in the 'asmb' phase.
+ if t := ldr.SymType(s); !t.IsDATA() && !t.IsRODATA() {
+ break
+ }
+ }
+
+ if target.IsElf() {
+ // Generate R_390_RELATIVE relocations for best
+ // efficiency in the dynamic linker.
+ //
+ // As noted above, symbol addresses have not been
+ // assigned yet, so we can't generate the final reloc
+ // entry yet. We ultimately want:
+ //
+ // r_offset = s + r.Off
+ // r_info = R_390_RELATIVE
+ // r_addend = targ + r.Add
+ //
+ // The dynamic linker will set *offset = base address +
+ // addend.
+ //
+ // AddAddrPlus is used for r_offset and r_addend to
+ // generate new R_ADDR relocations that will update
+ // these fields in the 'reloc' phase.
+ rela := ldr.MakeSymbolUpdater(syms.Rela)
+ rela.AddAddrPlus(target.Arch, s, int64(r.Off()))
+ if r.Siz() == 8 {
+ rela.AddUint64(target.Arch, elf.R_INFO(0, uint32(elf.R_390_RELATIVE)))
+ } else {
+ ldr.Errorf(s, "unexpected relocation for dynamic symbol %s", ldr.SymName(targ))
+ }
+ rela.AddAddrPlus(target.Arch, targ, r.Add())
+ // Not mark r done here. So we still apply it statically,
+ // so in the file content we'll also have the right offset
+ // to the relocation target. So it can be examined statically
+ // (e.g. go version).
+ return true
+ }
+ }
+
// Handle references to ELF symbols from our own object files.
return targType != sym.SDYNIMPORT
}
@@ -309,7 +410,7 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym,
return true
}
-func elfsetupplt(ctxt *ld.Link, ldr *loader.Loader, plt, got *loader.SymbolBuilder, dynamic loader.Sym) {
+func elfsetupplt(ctxt *ld.Link, ldr *loader.Loader, plt, gotplt *loader.SymbolBuilder, dynamic loader.Sym) {
if plt.Size() == 0 {
// stg %r1,56(%r15)
plt.AddUint8(0xe3)
@@ -321,7 +422,7 @@ func elfsetupplt(ctxt *ld.Link, ldr *loader.Loader, plt, got *loader.SymbolBuild
// larl %r1,_GLOBAL_OFFSET_TABLE_
plt.AddUint8(0xc0)
plt.AddUint8(0x10)
- plt.AddSymRef(ctxt.Arch, got.Sym(), 6, objabi.R_PCRELDBL, 4)
+ plt.AddSymRef(ctxt.Arch, gotplt.Sym(), 6, objabi.R_PCRELDBL, 4)
// mvc 48(8,%r15),8(%r1)
plt.AddUint8(0xd2)
plt.AddUint8(0x07)
@@ -349,11 +450,11 @@ func elfsetupplt(ctxt *ld.Link, ldr *loader.Loader, plt, got *loader.SymbolBuild
plt.AddUint8(0x07)
plt.AddUint8(0x00)
- // assume got->size == 0 too
- got.AddAddrPlus(ctxt.Arch, dynamic, 0)
+ // assume gotplt.size == 0 too
+ gotplt.AddAddrPlus(ctxt.Arch, dynamic, 0)
- got.AddUint64(ctxt.Arch, 0)
- got.AddUint64(ctxt.Arch, 0)
+ gotplt.AddUint64(ctxt.Arch, 0)
+ gotplt.AddUint64(ctxt.Arch, 0)
}
}
@@ -391,7 +492,7 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
if target.IsElf() {
plt := ldr.MakeSymbolUpdater(syms.PLT)
- got := ldr.MakeSymbolUpdater(syms.GOT)
+ gotplt := ldr.MakeSymbolUpdater(syms.GOTPLT)
rela := ldr.MakeSymbolUpdater(syms.RelaPLT)
if plt.Size() == 0 {
panic("plt is not set up")
@@ -400,12 +501,12 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
plt.AddUint8(0xc0)
plt.AddUint8(0x10)
- plt.AddPCRelPlus(target.Arch, got.Sym(), got.Size()+6)
+ plt.AddPCRelPlus(target.Arch, gotplt.Sym(), gotplt.Size()+6)
pltrelocs := plt.Relocs()
ldr.SetRelocVariant(plt.Sym(), pltrelocs.Count()-1, sym.RV_390_DBL)
- // add to got: pointer to current pos in plt
- got.AddAddrPlus(target.Arch, plt.Sym(), plt.Size()+8) // weird but correct
+ // add to gotplt: pointer to current pos in plt
+ gotplt.AddAddrPlus(target.Arch, plt.Sym(), plt.Size()+8) // weird but correct
// lg %r1,0(%r1)
plt.AddUint8(0xe3)
plt.AddUint8(0x10)
@@ -435,7 +536,7 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
plt.AddUint32(target.Arch, uint32(rela.Size())) // rela size before current entry
// rela
- rela.AddAddrPlus(target.Arch, got.Sym(), got.Size()-8)
+ rela.AddAddrPlus(target.Arch, gotplt.Sym(), gotplt.Size()-8)
sDynid := ldr.SymDynid(s)
rela.AddUint64(target.Arch, elf.R_INFO(uint32(sDynid), uint32(elf.R_390_JMP_SLOT)))
@@ -447,3 +548,53 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
ldr.Errorf(s, "addpltsym: unsupported binary format")
}
}
+
+// tlsIEtoLE converts TLS Initial Exec (IE) relocation to TLS Local Exec (LE).
+//
+// On s390x, the TLS IE sequence is:
+//
+// LARL %rN, <var>@INDNTPOFF ; 6 bytes - load address of GOT entry
+// LG %rN, 0(%rN) ; 6 bytes - load offset from GOT
+//
+// We convert this to TLS LE by replacing it with:
+//
+// LGFI %rN, <offset> ; 6 bytes - load 32-bit sign-extended immediate
+// BCR 0,0 ; 2 bytes - NOP
+// BCR 0,0 ; 2 bytes - NOP
+// BCR 0,0 ; 2 bytes - NOP
+//
+// The relocation offset points to byte 2 of the LARL instruction (the immediate field).
+func tlsIEtoLE(P []byte, off, size int) {
+ // off is the offset of the relocation within the instruction sequence,
+ // which is at byte 2 of the LARL instruction (the 4-byte immediate).
+ // We need to work with the beginning of LARL (off-2) through LG (off+10).
+
+ if off < 2 {
+ log.Fatalf("R_390_TLS_IEENT relocation at offset %d is too small", off)
+ }
+
+ // Verify we have a LARL instruction (opcode 0xC0, second nibble 0x0)
+ // LARL format: C0 R0 I2 I2 I2 I2 (where R is register, I2 is 32-bit immediate)
+ if P[off-2] != 0xc0 || P[off-1]&0x0f != 0x00 {
+ log.Fatalf("R_390_TLS_IEENT relocation not preceded by LARL instruction: %02x %02x", P[off-2], P[off-1])
+ }
+
+ // Extract the register from LARL (upper nibble of second byte)
+ reg := P[off-1] >> 4
+
+ // Convert LARL to LGFI: change opcode from C0x0 to C0x1
+ // LGFI format: C0 R1 I2 I2 I2 I2
+ P[off-1] = (reg << 4) | 0x01
+
+ // The immediate field (bytes off to off+3) will be filled in by the linker
+ // with the TLS offset value.
+
+ // Replace the LG instruction (6 bytes starting at off+4) with NOPs
+ // BCR 0,0 = 0x07 0x00 (2 bytes each, need 3 of them)
+ P[off+4] = 0x07
+ P[off+5] = 0x00
+ P[off+6] = 0x07
+ P[off+7] = 0x00
+ P[off+8] = 0x07
+ P[off+9] = 0x00
+}
diff --git a/src/cmd/link/internal/s390x/obj.go b/src/cmd/link/internal/s390x/obj.go
index 76aa962a82..dbce128bf0 100644
--- a/src/cmd/link/internal/s390x/obj.go
+++ b/src/cmd/link/internal/s390x/obj.go
@@ -52,6 +52,7 @@ func Init() (*sys.Arch, ld.Arch) {
Archrelocvariant: archrelocvariant,
Gentext: gentext,
Machoreloc1: machoreloc1,
+ TLSIEtoLE: tlsIEtoLE,
ELF: ld.ELFArch{
Linuxdynld: "/lib64/ld64.so.1",