1
0
mirror of https://github.com/golang/go synced 2024-11-11 21:10:21 -07:00

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 <cherryyz@google.com>
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Cherry Zhang 2020-12-14 18:52:13 -05:00
parent a318d56c1e
commit 5abda2618b
8 changed files with 176 additions and 10 deletions

View File

@ -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)
}

View File

@ -55,6 +55,7 @@ func Init() (*sys.Arch, ld.Arch) {
ElfrelocSize: 24,
Elfsetupplt: elfsetupplt,
Gentext: gentext,
GenSymsLate: gensymlate,
Machoreloc1: machoreloc1,
MachorelocSize: 8,

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}
}
}