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 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 30819db4c67e5880f055e96ccbb179eb883be176..d6c25fac41162f72edb8ae8c1ceaffd4009eafa3 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -37,6 +37,7 @@ "cmd/link/internal/ld" "cmd/link/internal/loader" "cmd/link/internal/sym" "debug/elf" + "fmt" "log" ) @@ -472,6 +473,20 @@ 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 { @@ -487,10 +502,6 @@ if v == 0 { ldr.Errorf(s, "reloc %d (%s) to symbol %s in non-macho section %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymSect(rs).Name, ldr.SymType(rs), ldr.SymType(rs)) return false } - } - - if r.Xadd != signext24(r.Xadd) { - ldr.Errorf(s, "relocation addend overflow: %s+0x%x", ldr.SymName(rs), r.Xadd) } switch rt { @@ -499,8 +510,8 @@ 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 @@ // Two relocation entries: MACHO_ARM64_RELOC_PAGEOFF12 MACHO_ARM64_RELOC_PAGE21 // 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 @@ // Two relocation entries: MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12 MACHO_ARM64_RELOC_GOT_LOAD_PAGE21 // 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 @@ } else { 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 ab3dfd99f73211cf5b39da8a1c7e3fdfe5b66f5a..bd13295e6130bee64c6d7c44e3dfe0400c95d283 100644 --- a/src/cmd/link/internal/arm64/obj.go +++ b/src/cmd/link/internal/arm64/obj.go @@ -55,6 +55,7 @@ Elfreloc1: elfreloc1, 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 00130044abbb9d3a1d9e1f65ca3fa85095f88e6e..3c5091e6a0cbf75816e6d4220116d0017bea264c 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1815,6 +1815,7 @@ } 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 @@ ctxt.Errorf(s, "s.Outer (%s) in different section from s, %s != %s", ldr.SymName(outer), ldr.SymSect(outer).Name, sect.Name) } } 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 @@ ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) 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 460564476782d129733d53fb78a04968b831ec82..3630e67c25d60c7b1fddb843a57b8edb80c8884b 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -969,6 +969,15 @@ ldr.SetSymDynid(s, int32(i)) } } +// 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 facb30fe15c7b4aeb60f308fad65d9054877e579..72bf33e611e59ea5b249e9639b0baacb73258a89 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -859,6 +859,7 @@ ldr := ctxt.loader 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 49713896138a7faa0d7cd463eb29b3e9bb1615a8..c98e4de03f8bfe12fdad7165956ef6f56e5ee724 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -483,6 +483,8 @@ s.SetSize(0) 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 @@ s := ldr.CreateSymForUpdate(name, 0) s.SetType(t) s.SetSize(0) s.SetLocal(true) + setCarrierSym(t, s.Sym()) return s.Sym() } var ( @@ -800,3 +803,23 @@ lastmoduledatap.AddAddr(ctxt.Arch, moduledata.Sym()) } 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 7bf06eaa4614e0c8d433c794ce1f417b8b072243..ba818eaa961e525293d4f573bdf7f62af56bfae1 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 158c6707391bb9a3654b8a087c8bcd2b991d5fd3..4eb02c9e8a16fe17f06574be1b98decf640b1ad5 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -925,3 +925,57 @@ if !strings.Contains(string(out), want) { 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) + } + } +}