From 5abda2618b6cda692ae9b04a9a9fc706888a0e71 Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Mon, 14 Dec 2020 18:52:13 -0500 Subject: [PATCH] cmd/link: handle large relocation addend on darwin/arm64 Mach-O relocation addend is signed 24-bit. When external linking, if the addend is larger, we cannot put it directly into a Mach-O relocation. This CL handles large addend by creating "label" symbols at sym+0x800000, sym+(0x800000*2), etc., and emitting Mach-O relocations that target the label symbols with a smaller addend. The label symbols are generated late (similar to what we do for RISC-V64). One complexity comes from handling of carrier symbols, which does not track its size or its inner symbols. But relocations can target them. We track them in a side table (similar to what we do for XCOFF, xcoffUpdateOuterSize). Fixes #42738. Change-Id: I8c53ab2397f8b88870d26f00e9026285e5ff5584 Reviewed-on: https://go-review.googlesource.com/c/go/+/278332 Trust: Cherry Zhang Run-TryBot: Cherry Zhang TryBot-Result: Go Bot Reviewed-by: Than McIntosh --- src/cmd/link/internal/arm64/asm.go | 94 ++++++++++++++++++++++++++---- src/cmd/link/internal/arm64/obj.go | 1 + src/cmd/link/internal/ld/data.go | 3 + src/cmd/link/internal/ld/macho.go | 9 +++ src/cmd/link/internal/ld/pcln.go | 1 + src/cmd/link/internal/ld/symtab.go | 23 ++++++++ src/cmd/link/internal/ld/xcoff.go | 1 + src/cmd/link/link_test.go | 54 +++++++++++++++++ 8 files changed, 176 insertions(+), 10 deletions(-) diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 30819db4c6..d6c25fac41 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -37,6 +37,7 @@ import ( "cmd/link/internal/loader" "cmd/link/internal/sym" "debug/elf" + "fmt" "log" ) @@ -472,6 +473,20 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy rs := r.Xsym rt := r.Type siz := r.Size + xadd := r.Xadd + + if xadd != signext24(xadd) { + // If the relocation target would overflow the addend, then target + // a linker-manufactured label symbol with a smaller addend instead. + label := ldr.Lookup(machoLabelName(ldr, rs, xadd), ldr.SymVersion(rs)) + if label != 0 { + xadd = ldr.SymValue(rs) + xadd - ldr.SymValue(label) + rs = label + } + if xadd != signext24(xadd) { + ldr.Errorf(s, "internal error: relocation addend overflow: %s+0x%x", ldr.SymName(rs), xadd) + } + } if ldr.SymType(rs) == sym.SHOSTOBJ || rt == objabi.R_CALLARM64 || rt == objabi.R_ADDRARM64 || rt == objabi.R_ARM64_GOTPCREL { if ldr.SymDynid(rs) < 0 { @@ -489,18 +504,14 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy } } - if r.Xadd != signext24(r.Xadd) { - ldr.Errorf(s, "relocation addend overflow: %s+0x%x", ldr.SymName(rs), r.Xadd) - } - switch rt { default: return false case objabi.R_ADDR: v |= ld.MACHO_ARM64_RELOC_UNSIGNED << 28 case objabi.R_CALLARM64: - if r.Xadd != 0 { - ldr.Errorf(s, "ld64 doesn't allow BR26 reloc with non-zero addend: %s+%d", ldr.SymName(rs), r.Xadd) + if xadd != 0 { + ldr.Errorf(s, "ld64 doesn't allow BR26 reloc with non-zero addend: %s+%d", ldr.SymName(rs), xadd) } v |= 1 << 24 // pc-relative bit @@ -511,13 +522,13 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy // if r.Xadd is non-zero, add two MACHO_ARM64_RELOC_ADDEND. if r.Xadd != 0 { out.Write32(uint32(sectoff + 4)) - out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(r.Xadd&0xffffff)) + out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(xadd&0xffffff)) } out.Write32(uint32(sectoff + 4)) out.Write32(v | (ld.MACHO_ARM64_RELOC_PAGEOFF12 << 28) | (2 << 25)) if r.Xadd != 0 { out.Write32(uint32(sectoff)) - out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(r.Xadd&0xffffff)) + out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(xadd&0xffffff)) } v |= 1 << 24 // pc-relative bit v |= ld.MACHO_ARM64_RELOC_PAGE21 << 28 @@ -527,13 +538,13 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy // if r.Xadd is non-zero, add two MACHO_ARM64_RELOC_ADDEND. if r.Xadd != 0 { out.Write32(uint32(sectoff + 4)) - out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(r.Xadd&0xffffff)) + out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(xadd&0xffffff)) } out.Write32(uint32(sectoff + 4)) out.Write32(v | (ld.MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12 << 28) | (2 << 25)) if r.Xadd != 0 { out.Write32(uint32(sectoff)) - out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(r.Xadd&0xffffff)) + out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(xadd&0xffffff)) } v |= 1 << 24 // pc-relative bit v |= ld.MACHO_ARM64_RELOC_GOT_LOAD_PAGE21 << 28 @@ -972,3 +983,66 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade ldr.Errorf(s, "addpltsym: unsupported binary format") } } + +const machoRelocLimit = 1 << 23 + +func gensymlate(ctxt *ld.Link, ldr *loader.Loader) { + // When external linking on darwin, Mach-O relocation has only signed 24-bit + // addend. For large symbols, we generate "label" symbols in the middle, so + // that relocations can target them with smaller addends. + if !ctxt.IsDarwin() || !ctxt.IsExternal() { + return + } + + big := false + for _, seg := range ld.Segments { + if seg.Length >= machoRelocLimit { + big = true + break + } + } + if !big { + return // skip work if nothing big + } + + // addLabelSyms adds "label" symbols at s+machoRelocLimit, s+2*machoRelocLimit, etc. + addLabelSyms := func(s loader.Sym, sz int64) { + v := ldr.SymValue(s) + for off := int64(machoRelocLimit); off < sz; off += machoRelocLimit { + p := ldr.LookupOrCreateSym(machoLabelName(ldr, s, off), ldr.SymVersion(s)) + ldr.SetAttrReachable(p, true) + ldr.SetSymValue(p, v+off) + ldr.SetSymSect(p, ldr.SymSect(s)) + ld.AddMachoSym(ldr, p) + //fmt.Printf("gensymlate %s %x\n", ldr.SymName(p), ldr.SymValue(p)) + } + } + + for s, n := loader.Sym(1), loader.Sym(ldr.NSym()); s < n; s++ { + if !ldr.AttrReachable(s) { + continue + } + if ldr.SymType(s) == sym.STEXT { + continue // we don't target the middle of a function + } + sz := ldr.SymSize(s) + if sz <= machoRelocLimit { + continue + } + addLabelSyms(s, sz) + } + + // Also for carrier symbols (for which SymSize is 0) + for _, ss := range ld.CarrierSymByType { + if ss.Sym != 0 && ss.Size > machoRelocLimit { + addLabelSyms(ss.Sym, ss.Size) + } + } +} + +// machoLabelName returns the name of the "label" symbol used for a +// relocation targetting s+off. The label symbols is used on darwin +// when external linking, so that the addend fits in a Mach-O relocation. +func machoLabelName(ldr *loader.Loader, s loader.Sym, off int64) string { + return fmt.Sprintf("%s.%d", ldr.SymExtname(s), off/machoRelocLimit) +} diff --git a/src/cmd/link/internal/arm64/obj.go b/src/cmd/link/internal/arm64/obj.go index ab3dfd99f7..bd13295e61 100644 --- a/src/cmd/link/internal/arm64/obj.go +++ b/src/cmd/link/internal/arm64/obj.go @@ -55,6 +55,7 @@ func Init() (*sys.Arch, ld.Arch) { ElfrelocSize: 24, Elfsetupplt: elfsetupplt, Gentext: gentext, + GenSymsLate: gensymlate, Machoreloc1: machoreloc1, MachorelocSize: 8, diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 00130044ab..3c5091e6a0 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1815,6 +1815,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { for _, symn := range sym.ReadOnly { symnStartValue := state.datsize state.assignToSection(sect, symn, sym.SRODATA) + setCarrierSize(symn, state.datsize-symnStartValue) if ctxt.HeadType == objabi.Haix { // Read-only symbols might be wrapped inside their outer // symbol. @@ -1902,6 +1903,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { } } state.assignToSection(sect, symn, sym.SRODATA) + setCarrierSize(symn, state.datsize-symnStartValue) if ctxt.HeadType == objabi.Haix { // Read-only symbols might be wrapped inside their outer // symbol. @@ -1949,6 +1951,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.functab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) + setCarrierSize(sym.SPCLNTAB, int64(sect.Length)) if ctxt.HeadType == objabi.Haix { xcoffUpdateOuterSize(ctxt, int64(sect.Length), sym.SPCLNTAB) } diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index 4605644767..3630e67c25 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -969,6 +969,15 @@ func machosymorder(ctxt *Link) { } } +// AddMachoSym adds s to Mach-O symbol table, used in GenSymLate. +// Currently only used on ARM64 when external linking. +func AddMachoSym(ldr *loader.Loader, s loader.Sym) { + ldr.SetSymDynid(s, int32(nsortsym)) + sortsym = append(sortsym, s) + nsortsym++ + nkind[symkind(ldr, s)]++ +} + // machoShouldExport reports whether a symbol needs to be exported. // // When dynamically linking, all non-local variables and plugin-exported diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index facb30fe15..72bf33e611 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -859,6 +859,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0) ldr.MakeSymbolUpdater(state.carrier).SetType(sym.SPCLNTAB) ldr.SetAttrReachable(state.carrier, true) + setCarrierSym(sym.SPCLNTAB, state.carrier) state.generatePCHeader(ctxt) nameOffsets := state.generateFuncnametab(ctxt, funcs) diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index 4971389613..c98e4de03f 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -483,6 +483,8 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { symtype = s.Sym() symtyperel = s.Sym() } + setCarrierSym(sym.STYPE, symtype) + setCarrierSym(sym.STYPERELRO, symtyperel) } groupSym := func(name string, t sym.SymKind) loader.Sym { @@ -490,6 +492,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { s.SetType(t) s.SetSize(0) s.SetLocal(true) + setCarrierSym(t, s.Sym()) return s.Sym() } var ( @@ -800,3 +803,23 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { } return symGroupType } + +// CarrierSymByType tracks carrier symbols and their sizes. +var CarrierSymByType [sym.SXREF]struct { + Sym loader.Sym + Size int64 +} + +func setCarrierSym(typ sym.SymKind, s loader.Sym) { + if CarrierSymByType[typ].Sym != 0 { + panic(fmt.Sprintf("carrier symbol for type %v already set", typ)) + } + CarrierSymByType[typ].Sym = s +} + +func setCarrierSize(typ sym.SymKind, sz int64) { + if CarrierSymByType[typ].Size != 0 { + panic(fmt.Sprintf("carrier symbol size for type %v already set", typ)) + } + CarrierSymByType[typ].Size = sz +} diff --git a/src/cmd/link/internal/ld/xcoff.go b/src/cmd/link/internal/ld/xcoff.go index 7bf06eaa46..ba818eaa96 100644 --- a/src/cmd/link/internal/ld/xcoff.go +++ b/src/cmd/link/internal/ld/xcoff.go @@ -574,6 +574,7 @@ func xcoffUpdateOuterSize(ctxt *Link, size int64, stype sym.SymKind) { if size == 0 { return } + // TODO: use CarrierSymByType ldr := ctxt.loader switch stype { diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 158c670739..4eb02c9e8a 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -925,3 +925,57 @@ func TestIssue42396(t *testing.T) { t.Fatalf("error message incorrect: expected it to contain %q but instead got:\n%s\n", want, out) } } + +const testLargeRelocSrc = ` +package main + +var x = [1<<25]byte{1<<23: 23, 1<<24: 24} + +func main() { + check(x[1<<23-1], 0) + check(x[1<<23], 23) + check(x[1<<23+1], 0) + check(x[1<<24-1], 0) + check(x[1<<24], 24) + check(x[1<<24+1], 0) +} + +func check(x, y byte) { + if x != y { + panic("FAIL") + } +} +` + +func TestLargeReloc(t *testing.T) { + // Test that large relocation addend is handled correctly. + // In particular, on darwin/arm64 when external linking, + // Mach-O relocation has only 24-bit addend. See issue #42738. + testenv.MustHaveGoBuild(t) + t.Parallel() + + tmpdir, err := ioutil.TempDir("", "TestIssue42396") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + src := filepath.Join(tmpdir, "x.go") + err = ioutil.WriteFile(src, []byte(testLargeRelocSrc), 0666) + if err != nil { + t.Fatalf("failed to write source file: %v", err) + } + cmd := exec.Command(testenv.GoToolPath(t), "run", src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("build failed: %v. output:\n%s", err, out) + } + + if testenv.HasCGO() { // currently all targets that support cgo can external link + cmd = exec.Command(testenv.GoToolPath(t), "run", "-ldflags=-linkmode=external", src) + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("build failed: %v. output:\n%s", err, out) + } + } +} -- 2.48.1