From 5387cdcb24a07f5d0d49d5105ced2b69e6aafde9 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Fri, 7 Aug 2020 11:31:20 -0400 Subject: [dev.link] cmd/link, cmd/compile: create content addressable pcdata syms Switch pcdata over to content addressable symbols. This is the last step before removing these from pclntab_old. No meaningful benchmarks changes come from this work. Change-Id: I3f74f3d6026a278babe437c8010e22992c92bd89 Reviewed-on: https://go-review.googlesource.com/c/go/+/247399 Reviewed-by: Austin Clements Reviewed-by: Than McIntosh --- src/cmd/link/internal/loader/loader.go | 46 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 26 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 8fd10b0848..f149e3c831 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -1878,19 +1878,24 @@ func (fi *FuncInfo) FuncID() objabi.FuncID { return objabi.FuncID((*goobj.FuncInfo)(nil).ReadFuncID(fi.data)) } -func (fi *FuncInfo) Pcsp() []byte { - pcsp, end := (*goobj.FuncInfo)(nil).ReadPcsp(fi.data) - return fi.r.BytesAt(fi.r.PcdataBase()+pcsp, int(end-pcsp)) +func (fi *FuncInfo) Pcsp() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcsp(fi.data) + return fi.l.resolve(fi.r, sym) } -func (fi *FuncInfo) Pcfile() []byte { - pcf, end := (*goobj.FuncInfo)(nil).ReadPcfile(fi.data) - return fi.r.BytesAt(fi.r.PcdataBase()+pcf, int(end-pcf)) +func (fi *FuncInfo) Pcfile() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcfile(fi.data) + return fi.l.resolve(fi.r, sym) } -func (fi *FuncInfo) Pcline() []byte { - pcln, end := (*goobj.FuncInfo)(nil).ReadPcline(fi.data) - return fi.r.BytesAt(fi.r.PcdataBase()+pcln, int(end-pcln)) +func (fi *FuncInfo) Pcline() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcline(fi.data) + return fi.l.resolve(fi.r, sym) +} + +func (fi *FuncInfo) Pcinline() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcinline(fi.data) + return fi.l.resolve(fi.r, sym) } // Preload has to be called prior to invoking the various methods @@ -1899,27 +1904,16 @@ func (fi *FuncInfo) Preload() { fi.lengths = (*goobj.FuncInfo)(nil).ReadFuncInfoLengths(fi.data) } -func (fi *FuncInfo) Pcinline() []byte { +func (fi *FuncInfo) Pcdata() []Sym { if !fi.lengths.Initialized { panic("need to call Preload first") } - pcinl, end := (*goobj.FuncInfo)(nil).ReadPcinline(fi.data, fi.lengths.PcdataOff) - return fi.r.BytesAt(fi.r.PcdataBase()+pcinl, int(end-pcinl)) -} - -func (fi *FuncInfo) NumPcdata() uint32 { - if !fi.lengths.Initialized { - panic("need to call Preload first") - } - return fi.lengths.NumPcdata -} - -func (fi *FuncInfo) Pcdata(k int) []byte { - if !fi.lengths.Initialized { - panic("need to call Preload first") + syms := (*goobj.FuncInfo)(nil).ReadPcdata(fi.data) + ret := make([]Sym, len(syms)) + for i := range ret { + ret[i] = fi.l.resolve(fi.r, syms[i]) } - pcdat, end := (*goobj.FuncInfo)(nil).ReadPcdata(fi.data, fi.lengths.PcdataOff, uint32(k)) - return fi.r.BytesAt(fi.r.PcdataBase()+pcdat, int(end-pcdat)) + return ret } func (fi *FuncInfo) NumFuncdataoff() uint32 { -- cgit v1.3 From 26407b22129e2e54db269c1a92826521addd8d56 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Wed, 12 Aug 2020 19:26:53 -0400 Subject: [dev.link] cmd/{compile,link}: remove pcdata tables from pclntab_old MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the pctables out of pclntab_old. Creates a new generator symbol, runtime.pctab, which holds all the deduplicated pctables. Also, tightens up some of the types in runtime. Darwin, cmd/compile statistics: alloc/op Pclntab_GC 26.4MB ± 0% 13.8MB ± 0% allocs/op Pclntab_GC 89.9k ± 0% 86.4k ± 0% liveB Pclntab_GC 25.5M ± 0% 24.2M ± 0% No significant change in binary size. Change-Id: I1560fd4421f8a210f8d4b508fbc54e1780e338f9 Reviewed-on: https://go-review.googlesource.com/c/go/+/248332 Run-TryBot: Jeremy Faller TryBot-Result: Gobot Gobot Reviewed-by: Cherry Zhang --- src/cmd/link/internal/ld/data.go | 2 + src/cmd/link/internal/ld/pcln.go | 143 +++++++++++++++++--------- src/cmd/link/internal/ld/symtab.go | 4 + src/cmd/link/internal/loader/symbolbuilder.go | 9 ++ src/cmd/link/internal/sym/symbol.go | 4 - src/debug/gosym/pclntab.go | 10 +- src/runtime/runtime2.go | 8 +- src/runtime/symtab.go | 28 ++--- 8 files changed, 136 insertions(+), 72 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index a551d46403..2aecbfbeb5 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1925,6 +1925,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.funcnametab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.cutab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) if ctxt.HeadType == objabi.Haix { @@ -2511,6 +2512,7 @@ func (ctxt *Link) address() []*sym.Segment { ctxt.defineInternal("runtime.funcnametab", sym.SRODATA) ctxt.defineInternal("runtime.cutab", sym.SRODATA) ctxt.defineInternal("runtime.filetab", sym.SRODATA) + ctxt.defineInternal("runtime.pctab", sym.SRODATA) ctxt.defineInternal("runtime.pclntab_old", sym.SRODATA) ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length)) ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr)) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index e9fd5937e7..576f1c3780 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -43,6 +43,7 @@ type pclntab struct { findfunctab loader.Sym cutab loader.Sym filetab loader.Sym + pctab loader.Sym // The number of functions + number of TEXT sections - 1. This is such an // unexpected value because platforms that have more than one TEXT section @@ -273,10 +274,11 @@ func (state *pclntab) generatePCHeader(ctxt *Link) { off = writeSymOffset(off, state.funcnametab) off = writeSymOffset(off, state.cutab) off = writeSymOffset(off, state.filetab) + off = writeSymOffset(off, state.pctab) off = writeSymOffset(off, state.pclntab) } - size := int64(8 + 6*ctxt.Arch.PtrSize) + size := int64(8 + 7*ctxt.Arch.PtrSize) state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader) } @@ -463,6 +465,68 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat return cuOffsets } +// generatePctab creates the runtime.pctab variable, holding all the +// deduplicated pcdata. +func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) { + ldr := ctxt.loader + + // Pctab offsets of 0 are considered invalid in the runtime. We respect + // that by just padding a single byte at the beginning of runtime.pctab, + // that way no real offsets can be zero. + size := int64(1) + + // Walk the functions, finding offset to store each pcdata. + seen := make(map[loader.Sym]struct{}) + saveOffset := func(pcSym loader.Sym) { + if _, ok := seen[pcSym]; !ok { + datSize := ldr.SymSize(pcSym) + if datSize != 0 { + ldr.SetSymValue(pcSym, size) + } else { + // Invalid PC data, record as zero. + ldr.SetSymValue(pcSym, 0) + } + size += datSize + seen[pcSym] = struct{}{} + } + } + for _, s := range ctxt.Textp { + if !emitPcln(ctxt, s, container) { + continue + } + fi := ldr.FuncInfo(s) + if !fi.Valid() { + continue + } + fi.Preload() + + pcSyms := []loader.Sym{fi.Pcsp(), fi.Pcfile(), fi.Pcline()} + for _, pcSym := range pcSyms { + saveOffset(pcSym) + } + for _, pcSym := range fi.Pcdata() { + saveOffset(pcSym) + } + if fi.NumInlTree() > 0 { + saveOffset(fi.Pcinline()) + } + } + + // TODO: There is no reason we need a generator for this variable, and it + // could be moved to a carrier symbol. However, carrier symbols containing + // carrier symbols don't work yet (as of Aug 2020). Once this is fixed, + // runtime.pctab could just be a carrier sym. + writePctab := func(ctxt *Link, s loader.Sym) { + ldr := ctxt.loader + sb := ldr.MakeSymbolUpdater(s) + for sym := range seen { + sb.SetBytesAt(ldr.SymValue(sym), ldr.Data(sym)) + } + } + + state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab) +} + // pclntab initializes the pclntab symbol with // runtime function and file name information. @@ -494,6 +558,9 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // runtime.filetab // []null terminated filename strings // + // runtime.pctab + // []byte of deduplicated pc data. + // // runtime.pclntab_old // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] @@ -514,6 +581,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { state.generatePCHeader(ctxt) state.generateFuncnametab(ctxt, container) cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container) + state.generatePctab(ctxt, container) funcdataBytes := int64(0) ldr.SetCarrierSym(state.pclntab, state.carrier) @@ -525,21 +593,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { ftab.Grow(int64(state.nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4) - szHint := len(ctxt.Textp) * 2 - pctaboff := make(map[string]uint32, szHint) - writepctab := func(off int32, p []byte) int32 { - start, ok := pctaboff[string(p)] - if !ok { - if len(p) > 0 { - start = uint32(len(ftab.Data())) - ftab.AddBytes(p) - } - pctaboff[string(p)] = start - } - newoff := int32(ftab.SetUint32(ctxt.Arch, int64(off), start)) - return newoff - } - setAddr := (*loader.SymbolBuilder).SetAddrPlus if ctxt.IsExe() && ctxt.IsInternal() { // Internal linking static executable. At this point the function @@ -555,10 +608,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } } - pcsp := sym.Pcdata{} - pcfile := sym.Pcdata{} - pcline := sym.Pcdata{} - pcdata := []sym.Pcdata{} funcdata := []loader.Sym{} funcdataoff := []int64{} @@ -583,18 +632,13 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } prevFunc = s - pcsp.P = pcsp.P[:0] - pcline.P = pcline.P[:0] - pcfile.P = pcfile.P[:0] - pcdata = pcdata[:0] + var numPCData int32 funcdataoff = funcdataoff[:0] funcdata = funcdata[:0] fi := ldr.FuncInfo(s) if fi.Valid() { fi.Preload() - for _, dataSym := range fi.Pcdata() { - pcdata = append(pcdata, sym.Pcdata{P: ldr.Data(dataSym)}) - } + numPCData = int32(len(fi.Pcdata())) nfd := fi.NumFuncdataoff() for i := uint32(0); i < nfd; i++ { funcdataoff = append(funcdataoff, fi.Funcdataoff(int(i))) @@ -602,15 +646,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { funcdata = fi.Funcdata(funcdata) } + writeInlPCData := false if fi.Valid() && fi.NumInlTree() > 0 { - - if len(pcdata) <= objabi.PCDATA_InlTreeIndex { - // Create inlining pcdata table. - newpcdata := make([]sym.Pcdata, objabi.PCDATA_InlTreeIndex+1) - copy(newpcdata, pcdata) - pcdata = newpcdata + writeInlPCData = true + if numPCData <= objabi.PCDATA_InlTreeIndex { + numPCData = objabi.PCDATA_InlTreeIndex + 1 } - if len(funcdataoff) <= objabi.FUNCDATA_InlTree { // Create inline tree funcdata. newfuncdata := make([]loader.Sym, objabi.FUNCDATA_InlTree+1) @@ -635,7 +676,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // fixed size of struct, checked below off := funcstart - end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) + end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + numPCData*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) { end += 4 } @@ -664,23 +705,21 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn)) cu := ldr.SymUnit(s) - if fi.Valid() { - pcsp = sym.Pcdata{P: ldr.Data(fi.Pcsp())} - pcfile = sym.Pcdata{P: ldr.Data(fi.Pcfile())} - pcline = sym.Pcdata{P: ldr.Data(fi.Pcline())} - } if fi.Valid() && fi.NumInlTree() > 0 { its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state) funcdata[objabi.FUNCDATA_InlTree] = its - pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: ldr.Data(fi.Pcinline())} } // pcdata - off = writepctab(off, pcsp.P) - off = writepctab(off, pcfile.P) - off = writepctab(off, pcline.P) - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcdata)))) + if fi.Valid() { + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcsp())))) + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcfile())))) + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcline())))) + } else { + off += 12 + } + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(numPCData))) // Store the offset to compilation unit's file table. cuIdx := ^uint32(0) @@ -700,9 +739,17 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // nfuncdata must be the final entry. off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata)))) - for i := range pcdata { - off = writepctab(off, pcdata[i].P) + + // Output the pcdata. + if fi.Valid() { + for i, pcSym := range fi.Pcdata() { + ftab.SetUint32(ctxt.Arch, int64(off+int32(i*4)), uint32(ldr.SymValue(pcSym))) + } + if writeInlPCData { + ftab.SetUint32(ctxt.Arch, int64(off+objabi.PCDATA_InlTreeIndex*4), uint32(ldr.SymValue(fi.Pcinline()))) + } } + off += numPCData * 4 // funcdata, must be pointer-aligned and we're only int32-aligned. // Missing funcdata will be 0 (nil pointer). @@ -724,7 +771,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } if off != end { - ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcdata), len(funcdata), ctxt.Arch.PtrSize) + ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, numPCData, len(funcdata), ctxt.Arch.PtrSize) errorexit() } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index d05b98f04a..520aaa44c2 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -627,6 +627,10 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { moduledata.AddAddr(ctxt.Arch, pcln.filetab) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab))) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab))) + // The pctab slice + moduledata.AddAddr(ctxt.Arch, pcln.pctab) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pctab))) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pctab))) // The pclntab slice moduledata.AddAddr(ctxt.Arch, pcln.pclntab) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pclntab))) diff --git a/src/cmd/link/internal/loader/symbolbuilder.go b/src/cmd/link/internal/loader/symbolbuilder.go index e14d89a927..c0c723d7f0 100644 --- a/src/cmd/link/internal/loader/symbolbuilder.go +++ b/src/cmd/link/internal/loader/symbolbuilder.go @@ -336,6 +336,15 @@ func (sb *SymbolBuilder) Addstring(str string) int64 { return r } +func (sb *SymbolBuilder) SetBytesAt(off int64, b []byte) int64 { + datLen := int64(len(b)) + if off+datLen > int64(len(sb.data)) { + panic("attempt to write past end of buffer") + } + copy(sb.data[off:off+datLen], b) + return off + datLen +} + func (sb *SymbolBuilder) addSymRef(tgt Sym, add int64, typ objabi.RelocType, rsize int) int64 { if sb.kind == 0 { sb.kind = sym.SDATA diff --git a/src/cmd/link/internal/sym/symbol.go b/src/cmd/link/internal/sym/symbol.go index 1a4165ebf7..70cf36a87e 100644 --- a/src/cmd/link/internal/sym/symbol.go +++ b/src/cmd/link/internal/sym/symbol.go @@ -33,7 +33,3 @@ func VersionToABI(v int) (obj.ABI, bool) { } return ^obj.ABI(0), false } - -type Pcdata struct { - P []byte -} diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index 21edddda20..a72f9847d7 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -58,6 +58,7 @@ type LineTable struct { functab []byte nfunctab uint32 filetab []byte + pctab []byte // points to the pctables. nfiletab uint32 funcNames map[uint32]string // cache the function names strings map[uint32]string // interned substrings of Data, keyed by offset @@ -235,6 +236,8 @@ func (t *LineTable) parsePclnTab() { offset = t.uintptr(t.Data[8+4*t.ptrsize:]) t.filetab = t.Data[offset:] offset = t.uintptr(t.Data[8+5*t.ptrsize:]) + t.pctab = t.Data[offset:] + offset = t.uintptr(t.Data[8+6*t.ptrsize:]) t.funcdata = t.Data[offset:] t.functab = t.Data[offset:] functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize @@ -244,6 +247,7 @@ func (t *LineTable) parsePclnTab() { t.funcdata = t.Data t.funcnametab = t.Data t.functab = t.Data[8+t.ptrsize:] + t.pctab = t.Data functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize fileoff := t.binary.Uint32(t.functab[functabsize:]) t.functab = t.functab[:functabsize] @@ -373,7 +377,7 @@ func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { // off is the offset to the beginning of the pc-value table, // and entry is the start PC for the corresponding function. func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { - p := t.funcdata[off:] + p := t.pctab[off:] val := int32(-1) pc := entry @@ -396,8 +400,8 @@ func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, return 0 } - fp := t.funcdata[filetab:] - fl := t.funcdata[linetab:] + fp := t.pctab[filetab:] + fl := t.pctab[linetab:] fileVal := int32(-1) filePC := entry lineVal := int32(-1) diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 5a79c7e6ec..755c409078 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -800,10 +800,10 @@ type _func struct { args int32 // in/out args size deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any. - pcsp int32 - pcfile int32 - pcln int32 - npcdata int32 + pcsp uint32 + pcfile uint32 + pcln uint32 + npcdata uint32 cuOffset uint32 // runtime.cutab offset of this function's CU funcID funcID // set for certain special runtime functions _ [2]byte // pad diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index fbd9315522..0610f75179 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -345,6 +345,7 @@ type pcHeader struct { funcnameOffset uintptr // offset to the funcnametab variable from pcHeader cuOffset uintptr // offset to the cutab variable from pcHeader filetabOffset uintptr // offset to the filetab variable from pcHeader + pctabOffset uintptr // offset to the pctab varible from pcHeader pclnOffset uintptr // offset to the pclntab variable from pcHeader } @@ -358,6 +359,7 @@ type moduledata struct { funcnametab []byte cutab []uint32 filetab []byte + pctab []byte pclntable []byte ftab []functab findfunctab uintptr @@ -721,7 +723,7 @@ type pcvalueCache struct { type pcvalueCacheEnt struct { // targetpc and off together are the key of this cache entry. targetpc uintptr - off int32 + off uint32 // val is the value of this cached pcvalue entry. val int32 } @@ -736,7 +738,7 @@ func pcvalueCacheKey(targetpc uintptr) uintptr { // Returns the PCData value, and the PC where this value starts. // TODO: the start PC is returned only when cache is nil. -func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) { +func pcvalue(f funcInfo, off uint32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) { if off == 0 { return -1, 0 } @@ -770,7 +772,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric return -1, 0 } datap := f.datap - p := datap.pclntable[off:] + p := datap.pctab[off:] pc := f.entry prevpc := pc val := int32(-1) @@ -812,7 +814,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n") - p = datap.pclntable[off:] + p = datap.pctab[off:] pc = f.entry val = -1 for { @@ -893,7 +895,7 @@ func funcspdelta(f funcInfo, targetpc uintptr, cache *pcvalueCache) int32 { // funcMaxSPDelta returns the maximum spdelta at any point in f. func funcMaxSPDelta(f funcInfo) int32 { datap := f.datap - p := datap.pclntable[f.pcsp:] + p := datap.pctab[f.pcsp:] pc := f.entry val := int32(-1) max := int32(0) @@ -909,20 +911,20 @@ func funcMaxSPDelta(f funcInfo) int32 { } } -func pcdatastart(f funcInfo, table int32) int32 { - return *(*int32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4)) +func pcdatastart(f funcInfo, table uint32) uint32 { + return *(*uint32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4)) } -func pcdatavalue(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache) int32 { - if table < 0 || table >= f.npcdata { +func pcdatavalue(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache) int32 { + if table >= f.npcdata { return -1 } r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, true) return r } -func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 { - if table < 0 || table >= f.npcdata { +func pcdatavalue1(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 { + if table >= f.npcdata { return -1 } r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, strict) @@ -931,8 +933,8 @@ func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache // Like pcdatavalue, but also return the start PC of this PCData value. // It doesn't take a cache. -func pcdatavalue2(f funcInfo, table int32, targetpc uintptr) (int32, uintptr) { - if table < 0 || table >= f.npcdata { +func pcdatavalue2(f funcInfo, table uint32, targetpc uintptr) (int32, uintptr) { + if table >= f.npcdata { return -1, 0 } return pcvalue(f, pcdatastart(f, table), targetpc, nil, true) -- cgit v1.3 From 5402d40d5b041399392b29e4543f5fc4506197bd Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Tue, 18 Aug 2020 16:35:26 -0400 Subject: [dev.link] cmd/link: fix memory growth on dev.link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CL 247399 caused memory growth in the linker. Fix this by adjusting how we preallocate the number of symbols we'll need. cmd/compile (Darwin), alloc/op: Loadlib_GC 33.5MB ± 0% 27.3MB ± 0% Change-Id: I34997329ea4412716114df97fc9dad6ad0c171ee Reviewed-on: https://go-review.googlesource.com/c/go/+/249024 Run-TryBot: Jeremy Faller Reviewed-by: Cherry Zhang Reviewed-by: Austin Clements TryBot-Result: Gobot Gobot --- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/loader/loader.go | 36 ++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index caa4566190..a01bdefa37 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -543,7 +543,7 @@ func (ctxt *Link) loadlib() { } // Add non-package symbols and references of externally defined symbols. - ctxt.loader.LoadNonpkgSyms(ctxt.Arch) + ctxt.loader.LoadSyms(ctxt.Arch) // Load symbols from shared libraries, after all Go object symbols are loaded. for _, lib := range ctxt.Library { diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index f149e3c831..ea9cd1bd2e 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -328,7 +328,7 @@ func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorRepor ldr := &Loader{ start: make(map[*oReader]Sym), objs: []objIdx{{}, {extReader, 0}}, // reserve index 0 for nil symbol, 1 for external symbols - objSyms: make([]objSym, 1, 100000), // reserve index 0 for nil symbol + objSyms: make([]objSym, 1, 1), // This will get overwritten later. extReader: extReader, symsByName: [2]map[string]Sym{make(map[string]Sym, 80000), make(map[string]Sym, 50000)}, // preallocate ~2MB for ABI0 and ~1MB for ABI1 symbols objByPkg: make(map[string]*oReader), @@ -2016,8 +2016,9 @@ func (l *Loader) FuncInfo(i Sym) FuncInfo { return FuncInfo{} } -// Preload a package: add autolibs, add defined package symbols to the symbol table. -// Does not add non-package symbols yet, which will be done in LoadNonpkgSyms. +// Preload a package: adds autolib. +// Does not add defined package or non-packaged symbols to the symbol table. +// These are done in LoadSyms. // Does not read symbol data. // Returns the fingerprint of the object. func (l *Loader) Preload(localSymVersion int, f *bio.Reader, lib *sym.Library, unit *sym.CompilationUnit, length int64) goobj.FingerprintType { @@ -2060,8 +2061,6 @@ func (l *Loader) Preload(localSymVersion int, f *bio.Reader, lib *sym.Library, u } l.addObj(lib.Pkg, or) - st := loadState{l: l} - st.preloadSyms(or, pkgDef) // The caller expects us consuming all the data f.MustSeek(length, os.SEEK_CUR) @@ -2144,17 +2143,30 @@ func (st *loadState) preloadSyms(r *oReader, kind int) { } } -// Add hashed (content-addressable) symbols, non-package symbols, and +// Add syms, hashed (content-addressable) symbols, non-package symbols, and // references to external symbols (which are always named). -func (l *Loader) LoadNonpkgSyms(arch *sys.Arch) { +func (l *Loader) LoadSyms(arch *sys.Arch) { + // Allocate space for symbols, making a guess as to how much space we need. + // This function was determined empirically by looking at the cmd/compile on + // Darwin, and picking factors for hashed and hashed64 syms. + var symSize, hashedSize, hashed64Size int + for _, o := range l.objs[goObjStart:] { + symSize += o.r.ndef + o.r.nhasheddef/2 + o.r.nhashed64def/2 + o.r.NNonpkgdef() + hashedSize += o.r.nhasheddef / 2 + hashed64Size += o.r.nhashed64def / 2 + } + // Index 0 is invalid for symbols. + l.objSyms = make([]objSym, 1, symSize) + l.npkgsyms = l.NSym() - // Preallocate some space (a few hundreds KB) for some symbols. - // As of Go 1.15, linking cmd/compile has ~8000 hashed64 symbols and - // ~13000 hashed symbols. st := loadState{ l: l, - hashed64Syms: make(map[uint64]symAndSize, 10000), - hashedSyms: make(map[goobj.HashType]symAndSize, 15000), + hashed64Syms: make(map[uint64]symAndSize, hashed64Size), + hashedSyms: make(map[goobj.HashType]symAndSize, hashedSize), + } + + for _, o := range l.objs[goObjStart:] { + st.preloadSyms(o.r, pkgDef) } for _, o := range l.objs[goObjStart:] { st.preloadSyms(o.r, hashed64Def) -- cgit v1.3 From 83ed734df03fd11d71f06bc02906a723afaf3936 Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Tue, 11 Aug 2020 19:57:50 -0400 Subject: cmd/link: pre-resolve package reference Pre-resolve package index references, so it doesn't need to do a map lookup in every cross-package symbol reference resolution. It increases the memory usage very slightly (O(# imported packages)). Change-Id: Ia76c97ac51f1c2c2d5ea7ae34853850ec69ef0a8 Reviewed-on: https://go-review.googlesource.com/c/go/+/253604 Run-TryBot: Cherry Zhang Reviewed-by: Than McIntosh TryBot-Result: Gobot Gobot --- src/cmd/link/internal/loader/loader.go | 36 +++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 8fd10b0848..43a0352e0b 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -93,11 +93,12 @@ type oReader struct { version int // version of static symbol flags uint32 // read from object file pkgprefix string - syms []Sym // Sym's global index, indexed by local index - ndef int // cache goobj.Reader.NSym() - nhashed64def int // cache goobj.Reader.NHashed64Def() - nhasheddef int // cache goobj.Reader.NHashedDef() - objidx uint32 // index of this reader in the objs slice + syms []Sym // Sym's global index, indexed by local index + pkg []uint32 // indices of referenced package by PkgIdx (index into loader.objs array) + ndef int // cache goobj.Reader.NSym() + nhashed64def int // cache goobj.Reader.NHashed64Def() + nhasheddef int // cache goobj.Reader.NHashedDef() + objidx uint32 // index of this reader in the objs slice } // Total number of defined symbols (package symbols, hashed symbols, and @@ -219,7 +220,7 @@ type Loader struct { deferReturnTramp map[Sym]bool // whether the symbol is a trampoline of a deferreturn call - objByPkg map[string]*oReader // map package path to its Go object reader + objByPkg map[string]uint32 // map package path to the index of its Go object reader anonVersion int // most recently assigned ext static sym pseudo-version @@ -331,7 +332,7 @@ func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorRepor objSyms: make([]objSym, 1, 100000), // reserve index 0 for nil symbol extReader: extReader, symsByName: [2]map[string]Sym{make(map[string]Sym, 80000), make(map[string]Sym, 50000)}, // preallocate ~2MB for ABI0 and ~1MB for ABI1 symbols - objByPkg: make(map[string]*oReader), + objByPkg: make(map[string]uint32), outer: make(map[Sym]Sym), sub: make(map[Sym]Sym), dynimplib: make(map[Sym]string), @@ -370,7 +371,7 @@ func (l *Loader) addObj(pkg string, r *oReader) Sym { } pkg = objabi.PathToPrefix(pkg) // the object file contains escaped package path if _, ok := l.objByPkg[pkg]; !ok { - l.objByPkg[pkg] = r + l.objByPkg[pkg] = r.objidx } i := Sym(len(l.objSyms)) l.start[r] = i @@ -635,12 +636,7 @@ func (l *Loader) resolve(r *oReader, s goobj.SymRef) Sym { case goobj.PkgIdxSelf: rr = r default: - pkg := r.Pkg(int(p)) - var ok bool - rr, ok = l.objByPkg[pkg] - if !ok { - log.Fatalf("reference of nonexisted package %s, from %v", pkg, r.unit.Lib) - } + rr = l.objs[r.pkg[p]].r } return l.toGlobal(rr, s.SymIdx) } @@ -2195,6 +2191,18 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) { } } + // referenced packages + npkg := r.NPkg() + r.pkg = make([]uint32, npkg) + for i := 1; i < npkg; i++ { // PkgIdx 0 is a dummy invalid package + pkg := r.Pkg(i) + objidx, ok := l.objByPkg[pkg] + if !ok { + log.Fatalf("reference of nonexisted package %s, from %v", pkg, r.unit.Lib) + } + r.pkg[i] = objidx + } + // load flags of package refs for i, n := 0, r.NRefFlags(); i < n; i++ { rf := r.RefFlags(i) -- cgit v1.3 From af18bce87cc7ee1ffc68f91abefa241ab209539e Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Sun, 20 Sep 2020 23:29:20 -0400 Subject: cmd/link: consider interface conversions only in reachable code The linker prunes methods that are not directly reachable if the receiver type is never converted to interface. Currently, this "never" is too strong: it is invalidated even if the interface conversion is in an unreachable function. This CL improves it by only considering interface conversions in reachable code. To do that, we introduce a marker relocation R_USEIFACE, which marks the target symbol as UsedInIface if the source symbol is reached. binary size before after cmd/compile 18897528 18887400 cmd/go 13607372 13470652 Change-Id: I66c6b69eeff9ae02d84d2e6f2bc7f1b29dd53910 Reviewed-on: https://go-review.googlesource.com/c/go/+/256797 Trust: Cherry Zhang Reviewed-by: Jeremy Faller Reviewed-by: Than McIntosh --- src/cmd/compile/internal/gc/pgen.go | 8 ++- src/cmd/compile/internal/gc/sinit.go | 2 +- src/cmd/compile/internal/gc/walk.go | 15 +++-- src/cmd/internal/obj/s390x/asmz.go | 6 +- src/cmd/internal/obj/wasm/wasmobj.go | 2 + src/cmd/internal/obj/x86/asm6.go | 7 ++- src/cmd/internal/objabi/reloctype.go | 5 ++ src/cmd/internal/objabi/reloctype_string.go | 66 +++++++++++++++++++++- src/cmd/link/internal/ld/data.go | 6 ++ src/cmd/link/internal/ld/deadcode.go | 14 +++++ .../internal/ld/testdata/deadcode/ifacemethod.go | 9 ++- src/cmd/link/internal/loader/loader.go | 1 + src/cmd/link/internal/wasm/asm.go | 3 + 13 files changed, 129 insertions(+), 15 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index 74262595b0..52b1ed351d 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -231,6 +231,11 @@ func compile(fn *Node) { return } + // Set up the function's LSym early to avoid data races with the assemblers. + // Do this before walk, as walk needs the LSym to set attributes/relocations + // (e.g. in markTypeUsedInInterface). + fn.Func.initLSym(true) + walk(fn) if nerrors != 0 { return @@ -250,9 +255,6 @@ func compile(fn *Node) { return } - // Set up the function's LSym early to avoid data races with the assemblers. - fn.Func.initLSym(true) - // Make sure type syms are declared for all types that might // be types of stack objects. We need to do this here // because symbols must be allocated before the parallel diff --git a/src/cmd/compile/internal/gc/sinit.go b/src/cmd/compile/internal/gc/sinit.go index 71ed558461..af19a96bbc 100644 --- a/src/cmd/compile/internal/gc/sinit.go +++ b/src/cmd/compile/internal/gc/sinit.go @@ -278,7 +278,7 @@ func (s *InitSchedule) staticassign(l *Node, r *Node) bool { return Isconst(val, CTNIL) } - markTypeUsedInInterface(val.Type) + markTypeUsedInInterface(val.Type, l.Sym.Linksym()) var itab *Node if l.Type.IsEmptyInterface() { diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 933f16d9a0..d238cc2f45 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -805,8 +805,8 @@ opswitch: fromType := n.Left.Type toType := n.Type - if !fromType.IsInterface() { - markTypeUsedInInterface(fromType) + if !fromType.IsInterface() && !Curfn.Func.Nname.isBlank() { // skip unnamed functions (func _()) + markTypeUsedInInterface(fromType, Curfn.Func.lsym) } // typeword generates the type word of the interface value. @@ -1621,8 +1621,13 @@ opswitch: // markTypeUsedInInterface marks that type t is converted to an interface. // This information is used in the linker in dead method elimination. -func markTypeUsedInInterface(t *types.Type) { - typenamesym(t).Linksym().Set(obj.AttrUsedInIface, true) +func markTypeUsedInInterface(t *types.Type, from *obj.LSym) { + tsym := typenamesym(t).Linksym() + // Emit a marker relocation. The linker will know the type is converted + // to an interface if "from" is reachable. + r := obj.Addrel(from) + r.Sym = tsym + r.Type = objabi.R_USEIFACE } // rtconvfn returns the parameter and result types that will be used by a @@ -3687,6 +3692,8 @@ func usemethod(n *Node) { // Also need to check for reflect package itself (see Issue #38515). if s := res0.Type.Sym; s != nil && s.Name == "Method" && isReflectPkg(s.Pkg) { Curfn.Func.SetReflectMethod(true) + // The LSym is initialized at this point. We need to set the attribute on the LSym. + Curfn.Func.lsym.Set(obj.AttrReflectMethod, true) } } diff --git a/src/cmd/internal/obj/s390x/asmz.go b/src/cmd/internal/obj/s390x/asmz.go index 68f01f1c5d..cb3a2c3196 100644 --- a/src/cmd/internal/obj/s390x/asmz.go +++ b/src/cmd/internal/obj/s390x/asmz.go @@ -461,6 +461,7 @@ func spanz(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { buffer := make([]byte, 0) changed := true loop := 0 + nrelocs0 := len(c.cursym.R) for changed { if loop > 100 { c.ctxt.Diag("stuck in spanz loop") @@ -468,7 +469,10 @@ func spanz(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } changed = false buffer = buffer[:0] - c.cursym.R = make([]obj.Reloc, 0) + for i := range c.cursym.R[nrelocs0:] { + c.cursym.R[nrelocs0+i] = obj.Reloc{} + } + c.cursym.R = c.cursym.R[:nrelocs0] // preserve marker relocations generated by the compiler for p := c.cursym.Func.Text; p != nil; p = p.Link { pc := int64(len(buffer)) if pc != p.Pc { diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go index 70e8e51e65..a9e093a8ad 100644 --- a/src/cmd/internal/obj/wasm/wasmobj.go +++ b/src/cmd/internal/obj/wasm/wasmobj.go @@ -1007,6 +1007,7 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { panic("bad name for Call") } r := obj.Addrel(s) + r.Siz = 1 // actually variable sized r.Off = int32(w.Len()) r.Type = objabi.R_CALL if p.Mark&WasmImport != 0 { @@ -1033,6 +1034,7 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { case AI32Const, AI64Const: if p.From.Name == obj.NAME_EXTERN { r := obj.Addrel(s) + r.Siz = 1 // actually variable sized r.Off = int32(w.Len()) r.Type = objabi.R_ADDR r.Sym = p.From.Sym diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index fb99c620ad..4940c79eaa 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -2100,14 +2100,15 @@ func span6(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { var c int32 errors := ctxt.Errors var nops []nopPad // Padding for a particular assembly (reuse slice storage if multiple assemblies) + nrelocs0 := len(s.R) for { // This loop continues while there are reasons to re-assemble // whole block, like the presence of long forward jumps. reAssemble := false - for i := range s.R { - s.R[i] = obj.Reloc{} + for i := range s.R[nrelocs0:] { + s.R[nrelocs0+i] = obj.Reloc{} } - s.R = s.R[:0] + s.R = s.R[:nrelocs0] // preserve marker relocations generated by the compiler s.P = s.P[:0] c = 0 var pPrev *obj.Prog diff --git a/src/cmd/internal/objabi/reloctype.go b/src/cmd/internal/objabi/reloctype.go index f029a3c396..1e328d659f 100644 --- a/src/cmd/internal/objabi/reloctype.go +++ b/src/cmd/internal/objabi/reloctype.go @@ -89,6 +89,11 @@ const ( // should be linked into the final binary, even if there are no other // direct references. (This is used for types reachable by reflection.) R_USETYPE + // R_USEIFACE marks a type is converted to an interface in the function this + // relocation is applied to. The target is a type descriptor. + // This is a marker relocation (0-sized), for the linker's reachabililty + // analysis. + R_USEIFACE // R_METHODOFF resolves to a 32-bit offset from the beginning of the section // holding the data being relocated to the referenced symbol. // It is a variant of R_ADDROFF used when linking from the uncommonType of a diff --git a/src/cmd/internal/objabi/reloctype_string.go b/src/cmd/internal/objabi/reloctype_string.go index 83dfe71e07..caf24eea58 100644 --- a/src/cmd/internal/objabi/reloctype_string.go +++ b/src/cmd/internal/objabi/reloctype_string.go @@ -4,9 +4,71 @@ package objabi import "strconv" -const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_WEAKADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_METHODOFFR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[R_ADDR-1] + _ = x[R_ADDRPOWER-2] + _ = x[R_ADDRARM64-3] + _ = x[R_ADDRMIPS-4] + _ = x[R_ADDROFF-5] + _ = x[R_WEAKADDROFF-6] + _ = x[R_SIZE-7] + _ = x[R_CALL-8] + _ = x[R_CALLARM-9] + _ = x[R_CALLARM64-10] + _ = x[R_CALLIND-11] + _ = x[R_CALLPOWER-12] + _ = x[R_CALLMIPS-13] + _ = x[R_CALLRISCV-14] + _ = x[R_CONST-15] + _ = x[R_PCREL-16] + _ = x[R_TLS_LE-17] + _ = x[R_TLS_IE-18] + _ = x[R_GOTOFF-19] + _ = x[R_PLT0-20] + _ = x[R_PLT1-21] + _ = x[R_PLT2-22] + _ = x[R_USEFIELD-23] + _ = x[R_USETYPE-24] + _ = x[R_USEIFACE-25] + _ = x[R_METHODOFF-26] + _ = x[R_POWER_TOC-27] + _ = x[R_GOTPCREL-28] + _ = x[R_JMPMIPS-29] + _ = x[R_DWARFSECREF-30] + _ = x[R_DWARFFILEREF-31] + _ = x[R_ARM64_TLS_LE-32] + _ = x[R_ARM64_TLS_IE-33] + _ = x[R_ARM64_GOTPCREL-34] + _ = x[R_ARM64_GOT-35] + _ = x[R_ARM64_PCREL-36] + _ = x[R_ARM64_LDST8-37] + _ = x[R_ARM64_LDST32-38] + _ = x[R_ARM64_LDST64-39] + _ = x[R_ARM64_LDST128-40] + _ = x[R_POWER_TLS_LE-41] + _ = x[R_POWER_TLS_IE-42] + _ = x[R_POWER_TLS-43] + _ = x[R_ADDRPOWER_DS-44] + _ = x[R_ADDRPOWER_GOT-45] + _ = x[R_ADDRPOWER_PCREL-46] + _ = x[R_ADDRPOWER_TOCREL-47] + _ = x[R_ADDRPOWER_TOCREL_DS-48] + _ = x[R_RISCV_PCREL_ITYPE-49] + _ = x[R_RISCV_PCREL_STYPE-50] + _ = x[R_PCRELDBL-51] + _ = x[R_ADDRMIPSU-52] + _ = x[R_ADDRMIPSTLS-53] + _ = x[R_ADDRCUOFF-54] + _ = x[R_WASMIMPORT-55] + _ = x[R_XCOFFREF-56] +} + +const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_WEAKADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_USEIFACER_METHODOFFR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF" -var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 60, 66, 72, 81, 92, 101, 112, 122, 133, 140, 147, 155, 163, 171, 177, 183, 189, 199, 208, 219, 230, 240, 249, 262, 276, 290, 304, 320, 331, 344, 357, 371, 385, 400, 414, 428, 439, 453, 468, 485, 503, 524, 543, 562, 572, 583, 596, 607, 619, 629} +var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 60, 66, 72, 81, 92, 101, 112, 122, 133, 140, 147, 155, 163, 171, 177, 183, 189, 199, 208, 218, 229, 240, 250, 259, 272, 286, 300, 314, 330, 341, 354, 367, 381, 395, 410, 424, 438, 449, 463, 478, 495, 513, 534, 553, 572, 582, 593, 606, 617, 629, 639} func (i RelocType) String() string { i -= 1 diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index a730125cf2..0a3418bfc9 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -698,6 +698,9 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) { relocs := ctxt.loader.Relocs(s) for ri := 0; ri < relocs.Count(); ri++ { r := relocs.At(ri) + if r.IsMarker() { + continue // skip marker relocations + } targ := r.Sym() if targ == 0 { continue @@ -775,6 +778,9 @@ func dynrelocsym(ctxt *Link, s loader.Sym) { relocs := ldr.Relocs(s) for ri := 0; ri < relocs.Count(); ri++ { r := relocs.At(ri) + if r.IsMarker() { + continue // skip marker relocations + } if ctxt.BuildMode == BuildModePIE && ctxt.LinkMode == LinkInternal { // It's expected that some relocations will be done // later by relocsym (R_TLS_LE, R_ADDROFF), so diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go index 7f14aa3d27..816a23b9a7 100644 --- a/src/cmd/link/internal/ld/deadcode.go +++ b/src/cmd/link/internal/ld/deadcode.go @@ -153,6 +153,20 @@ func (d *deadcodePass) flood() { // do nothing for now as we still load all type symbols. continue } + if t == objabi.R_USEIFACE { + // R_USEIFACE is a marker relocation that tells the linker the type is + // converted to an interface, i.e. should have UsedInIface set. See the + // comment below for why we need to unset the Reachable bit and re-mark it. + rs := r.Sym() + if !d.ldr.AttrUsedInIface(rs) { + d.ldr.SetAttrUsedInIface(rs, true) + if d.ldr.AttrReachable(rs) { + d.ldr.SetAttrReachable(rs, false) + d.mark(rs, symIdx) + } + } + continue + } rs := r.Sym() if isgotype && usedInIface && d.ldr.IsGoType(rs) && !d.ldr.AttrUsedInIface(rs) { // If a type is converted to an interface, it is possible to obtain an diff --git a/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod.go b/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod.go index b62f18c342..32a24cf6f0 100644 --- a/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod.go +++ b/src/cmd/link/internal/ld/testdata/deadcode/ifacemethod.go @@ -18,6 +18,13 @@ var p *T var e interface{} func main() { - p = new(T) // used T, but never converted to interface + p = new(T) // used T, but never converted to interface in any reachable code e.(I).M() // used I and I.M } + +func Unused() { // convert T to interface, but this function is not reachable + var i I = T(0) + i.M() +} + +var Unused2 interface{} = T(1) // convert T to interface, in an unreachable global initializer diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 43a0352e0b..ea99233f67 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -63,6 +63,7 @@ type Reloc struct { func (rel Reloc) Type() objabi.RelocType { return objabi.RelocType(rel.Reloc.Type()) + rel.typ } func (rel Reloc) Sym() Sym { return rel.l.resolve(rel.r, rel.Reloc.Sym()) } func (rel Reloc) SetSym(s Sym) { rel.Reloc.SetSym(goobj.SymRef{PkgIdx: 0, SymIdx: uint32(s)}) } +func (rel Reloc) IsMarker() bool { return rel.Siz() == 0 } func (rel Reloc) SetType(t objabi.RelocType) { if t != objabi.RelocType(uint8(t)) { diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go index 3bd56a6e3a..31851fbb56 100644 --- a/src/cmd/link/internal/wasm/asm.go +++ b/src/cmd/link/internal/wasm/asm.go @@ -167,6 +167,9 @@ func asmb2(ctxt *ld.Link, ldr *loader.Loader) { off := int32(0) for ri := 0; ri < relocs.Count(); ri++ { r := relocs.At(ri) + if r.Siz() == 0 { + continue // skip marker relocations + } wfn.Write(P[off:r.Off()]) off = r.Off() rs := ldr.ResolveABIAlias(r.Sym()) -- cgit v1.3 From 66770f4b1de37d9c5c962edb2980a70102e09ec3 Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Mon, 28 Sep 2020 13:10:30 -0400 Subject: cmd/compile: mark type namedata symbols content-addressable Type namedata symbols are for type/field/method names and package paths. We can use content-addressable symbol mechanism for them. Change-Id: I923fda17b7094c7a0e46aad7c450622eb3826294 Reviewed-on: https://go-review.googlesource.com/c/go/+/257960 Trust: Cherry Zhang Run-TryBot: Cherry Zhang TryBot-Result: Go Bot Reviewed-by: Than McIntosh Reviewed-by: Jeremy Faller --- src/cmd/compile/internal/gc/reflect.go | 2 ++ src/cmd/internal/obj/objfile.go | 7 +++++++ src/cmd/internal/obj/sym.go | 5 ++++- src/cmd/link/internal/loader/loader.go | 4 ++-- 4 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index ae3e2f8e65..21429af782 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -511,6 +511,7 @@ func dimportpath(p *types.Pkg) { s := Ctxt.Lookup("type..importpath." + p.Prefix + ".") ot := dnameData(s, 0, str, "", nil, false) ggloblsym(s, int32(ot), obj.DUPOK|obj.RODATA) + s.Set(obj.AttrContentAddressable, true) p.Pathsym = s } @@ -638,6 +639,7 @@ func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym { } ot := dnameData(s, 0, name, tag, pkg, exported) ggloblsym(s, int32(ot), obj.DUPOK|obj.RODATA) + s.Set(obj.AttrContentAddressable, true) return s } diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index e4b9620568..186016c970 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -372,6 +372,13 @@ func contentHash64(s *LSym) goobj.Hash64Type { // hashed symbols. func (w *writer) contentHash(s *LSym) goobj.HashType { h := sha1.New() + // Don't dedup type symbols with others, as they are in a different + // section. + if strings.HasPrefix(s.Name, "type.") { + h.Write([]byte{'T'}) + } else { + h.Write([]byte{0}) + } // The compiler trims trailing zeros _sometimes_. We just do // it always. h.Write(bytes.TrimRight(s.P, "\x00")) diff --git a/src/cmd/internal/obj/sym.go b/src/cmd/internal/obj/sym.go index d58877ee15..e5d7b2cbfd 100644 --- a/src/cmd/internal/obj/sym.go +++ b/src/cmd/internal/obj/sym.go @@ -38,6 +38,7 @@ import ( "log" "math" "sort" + "strings" ) func Linknew(arch *LinkArch) *Link { @@ -204,7 +205,9 @@ func (ctxt *Link) NumberSyms() { // if Pkgpath is unknown, cannot hash symbols with relocations, as it // may reference named symbols whose names are not fully expanded. if s.ContentAddressable() && (ctxt.Pkgpath != "" || len(s.R) == 0) { - if len(s.P) <= 8 && len(s.R) == 0 { // we can use short hash only for symbols without relocations + if len(s.P) <= 8 && len(s.R) == 0 && !strings.HasPrefix(s.Name, "type.") { + // We can use short hash only for symbols without relocations. + // Don't use short hash for type symbols, as they need special handling. s.PkgIdx = goobj.PkgIdxHashed64 s.SymIdx = hashed64idx if hashed64idx != int32(len(ctxt.hashed64defs)) { diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index ea99233f67..4025edc23f 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -2153,11 +2153,11 @@ func (l *Loader) LoadNonpkgSyms(arch *sys.Arch) { l.npkgsyms = l.NSym() // Preallocate some space (a few hundreds KB) for some symbols. // As of Go 1.15, linking cmd/compile has ~8000 hashed64 symbols and - // ~13000 hashed symbols. + // ~27000 hashed symbols. st := loadState{ l: l, hashed64Syms: make(map[uint64]symAndSize, 10000), - hashedSyms: make(map[goobj.HashType]symAndSize, 15000), + hashedSyms: make(map[goobj.HashType]symAndSize, 30000), } for _, o := range l.objs[goObjStart:] { st.preloadSyms(o.r, hashed64Def) -- cgit v1.3 From f46a5b1e4559191363dbd4f510105dd31ae97aaa Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Sun, 11 Oct 2020 15:48:22 -0400 Subject: cmd/link: support PIE internal linking on darwin/amd64 This CL adds support of PIE internal linking on darwin/amd64. This is also preparation for supporting internal linking on darwin/arm64 (macOS), which requires PIE for everything. Updates #38485. Change-Id: I2ed58583dcc102f5e0521982491fc7ba6f2754ed Reviewed-on: https://go-review.googlesource.com/c/go/+/261642 Trust: Cherry Zhang Reviewed-by: Than McIntosh --- misc/cgo/test/issue4029.c | 1 + misc/cgo/test/issue4029.go | 4 + misc/cgo/test/issue4029w.go | 2 +- src/cmd/dist/test.go | 17 +- src/cmd/internal/sys/supported.go | 3 +- src/cmd/link/internal/amd64/asm.go | 65 +++----- src/cmd/link/internal/ld/config.go | 1 + src/cmd/link/internal/ld/lib.go | 6 + src/cmd/link/internal/ld/macho.go | 226 +++++++++++++++++++++++++- src/cmd/link/internal/loader/symbolbuilder.go | 18 ++ 10 files changed, 289 insertions(+), 54 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/misc/cgo/test/issue4029.c b/misc/cgo/test/issue4029.c index 30646ade02..e6a777fe64 100644 --- a/misc/cgo/test/issue4029.c +++ b/misc/cgo/test/issue4029.c @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // +build !windows,!static +// +build !darwin !internal_pie #include #include diff --git a/misc/cgo/test/issue4029.go b/misc/cgo/test/issue4029.go index 1bf029d760..8602ce19e2 100644 --- a/misc/cgo/test/issue4029.go +++ b/misc/cgo/test/issue4029.go @@ -3,6 +3,10 @@ // license that can be found in the LICENSE file. // +build !windows,!static +// +build !darwin !internal_pie + +// Excluded in darwin internal linking PIE mode, as dynamic export is not +// supported. package cgotest diff --git a/misc/cgo/test/issue4029w.go b/misc/cgo/test/issue4029w.go index eee33f7010..de0cf2138a 100644 --- a/misc/cgo/test/issue4029w.go +++ b/misc/cgo/test/issue4029w.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build windows static +// +build windows static darwin,internal_pie package cgotest diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 03e6866d62..94b81613a9 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -964,10 +964,10 @@ func (t *tester) internalLink() bool { func (t *tester) internalLinkPIE() bool { switch goos + "-" + goarch { - case "linux-amd64", "linux-arm64", - "android-arm64": - return true - case "windows-amd64", "windows-386", "windows-arm": + case "darwin-amd64", + "linux-amd64", "linux-arm64", + "android-arm64", + "windows-amd64", "windows-386", "windows-arm": return true } return false @@ -1100,6 +1100,13 @@ func (t *tester) cgoTest(dt *distTest) error { cmd = t.addCmd(dt, "misc/cgo/test", t.goTest(), "-ldflags", "-linkmode=external -s") + if t.supportedBuildmode("pie") { + t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie") + if t.internalLink() && t.internalLinkPIE() { + t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal", "-tags=internal,internal_pie") + } + } + case "aix-ppc64", "android-arm", "android-arm64", "dragonfly-amd64", @@ -1151,7 +1158,7 @@ func (t *tester) cgoTest(dt *distTest) error { if t.supportedBuildmode("pie") { t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie") if t.internalLink() && t.internalLinkPIE() { - t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal") + t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal", "-tags=internal,internal_pie") } t.addCmd(dt, "misc/cgo/testtls", t.goTest(), "-buildmode=pie") t.addCmd(dt, "misc/cgo/nocgo", t.goTest(), "-buildmode=pie") diff --git a/src/cmd/internal/sys/supported.go b/src/cmd/internal/sys/supported.go index 55709f3915..07be998035 100644 --- a/src/cmd/internal/sys/supported.go +++ b/src/cmd/internal/sys/supported.go @@ -118,7 +118,8 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool { func InternalLinkPIESupported(goos, goarch string) bool { switch goos + "/" + goarch { - case "linux/amd64", "linux/arm64", + case "darwin/amd64", + "linux/amd64", "linux/arm64", "android/arm64", "windows-amd64", "windows-386", "windows-arm": return true diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go index e5a6ef51b0..3658ac0be0 100644 --- a/src/cmd/link/internal/amd64/asm.go +++ b/src/cmd/link/internal/amd64/asm.go @@ -76,9 +76,9 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade targType = ldr.SymType(targ) } - switch r.Type() { + switch rt := r.Type(); rt { default: - if r.Type() >= objabi.ElfRelocOffset { + if rt >= objabi.ElfRelocOffset { ldr.Errorf(s, "unexpected relocation type %d (%s)", r.Type(), sym.RelocName(target.Arch, r.Type())) return false } @@ -167,13 +167,24 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_UNSIGNED*2 + 0, objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED*2 + 0, objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 0: - // TODO: What is the difference between all these? su := ldr.MakeSymbolUpdater(s) su.SetRelocType(rIdx, objabi.R_ADDR) if targType == sym.SDYNIMPORT { ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ)) } + 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. + if rt == objabi.MachoRelocOffset+ld.MACHO_X86_64_RELOC_UNSIGNED*2 { + break + } else { + // MACHO_X86_64_RELOC_SIGNED or MACHO_X86_64_RELOC_BRANCH + // Can this happen? The object is expected to be PIC. + ldr.Errorf(s, "unsupported relocation for PIE: %v", rt) + } + } return true case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 1: @@ -223,7 +234,7 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade if targType != sym.SDYNIMPORT { ldr.Errorf(s, "unexpected GOT reloc for non-dynamic symbol %s", ldr.SymName(targ)) } - ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_X86_64_GLOB_DAT)) + ld.AddGotSym(target, ldr, syms, targ, 0) su := ldr.MakeSymbolUpdater(s) su.SetRelocType(rIdx, objabi.R_PCREL) su.SetRelocSym(rIdx, syms.GOT) @@ -355,28 +366,15 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade return true } - if target.IsDarwin() && ldr.SymSize(s) == int64(target.Arch.PtrSize) && r.Off() == 0 { + if target.IsDarwin() { // Mach-O relocations are a royal pain to lay out. - // They use a compact stateful bytecode representation - // that is too much bother to deal with. - // Instead, interpret the C declaration - // void *_Cvar_stderr = &stderr; - // as making _Cvar_stderr the name of a GOT entry - // for stderr. This is separate from the usual GOT entry, - // just in case the C code assigns to the variable, - // and of course it only works for single pointers, - // but we only need to support cgo and that's all it needs. - ld.Adddynsym(ldr, target, syms, targ) - - got := ldr.MakeSymbolUpdater(syms.GOT) - su := ldr.MakeSymbolUpdater(s) - su.SetType(got.Type()) - got.AddInteriorSym(s) - su.SetValue(got.Size()) - got.AddUint64(target.Arch, 0) - leg := ldr.MakeSymbolUpdater(syms.LinkEditGOT) - leg.AddUint32(target.Arch, uint32(ldr.SymDynid(targ))) - su.SetRelocType(rIdx, objabi.ElfRelocOffset) // ignore during relocsym + // They use a compact stateful bytecode representation. + // Here we record what are needed and encode them later. + ld.MachoAddRebase(s, int64(r.Off())) + // 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 } } @@ -627,26 +625,16 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade ldr.SetPlt(s, int32(plt.Size()-16)) } else if target.IsDarwin() { - // To do lazy symbol lookup right, we're supposed - // to tell the dynamic loader which library each - // symbol comes from and format the link info - // section just so. I'm too lazy (ha!) to do that - // so for now we'll just use non-lazy pointers, - // which don't need to be told which library to use. - // - // https://networkpx.blogspot.com/2009/09/about-lcdyldinfoonly-command.html - // has details about what we're avoiding. - - ld.AddGotSym(target, ldr, syms, s, uint32(elf.R_X86_64_GLOB_DAT)) - plt := ldr.MakeSymbolUpdater(syms.PLT) + ld.AddGotSym(target, ldr, syms, s, 0) sDynid := ldr.SymDynid(s) lep := ldr.MakeSymbolUpdater(syms.LinkEditPLT) lep.AddUint32(target.Arch, uint32(sDynid)) - // jmpq *got+size(IP) + plt := ldr.MakeSymbolUpdater(syms.PLT) ldr.SetPlt(s, int32(plt.Size())) + // jmpq *got+size(IP) plt.AddUint8(0xff) plt.AddUint8(0x25) plt.AddPCRelPlus(target.Arch, syms.GOT, int64(ldr.SymGot(s))) @@ -654,6 +642,7 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade ldr.Errorf(s, "addpltsym: unsupported binary format") } } + func tlsIEtoLE(P []byte, off, size int) { // Transform the PC-relative instruction into a constant load. // That is, diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go index aaf74b58de..c680d11c1d 100644 --- a/src/cmd/link/internal/ld/config.go +++ b/src/cmd/link/internal/ld/config.go @@ -222,6 +222,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { switch objabi.GOOS + "/" + objabi.GOARCH { case "linux/amd64", "linux/arm64", "android/arm64": case "windows/386", "windows/amd64", "windows/arm": + case "darwin/amd64": default: // Internal linking does not support TLS_IE. return true, "buildmode=pie" diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 5fe028d321..8d04973fcf 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -2526,6 +2526,12 @@ func AddGotSym(target *Target, ldr *loader.Loader, syms *ArchSyms, s loader.Sym, } else if target.IsDarwin() { leg := ldr.MakeSymbolUpdater(syms.LinkEditGOT) leg.AddUint32(target.Arch, uint32(ldr.SymDynid(s))) + if target.IsPIE() && target.IsInternal() { + // Mach-O relocations are a royal pain to lay out. + // They use a compact stateful bytecode representation. + // Here we record what are needed and encode them later. + MachoAddBind(int64(ldr.SymGot(s)), s) + } } else { ldr.Errorf(s, "addgotsym: unsupported binary format") } diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index eab6537c75..a19a4afd9a 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -118,6 +118,8 @@ const ( MH_EXECUTE = 0x2 MH_NOUNDEFS = 0x1 + MH_DYLDLINK = 0x4 + MH_PIE = 0x200000 ) const ( @@ -193,6 +195,56 @@ const ( PLATFORM_BRIDGEOS MachoPlatform = 5 ) +// rebase table opcode +const ( + REBASE_TYPE_POINTER = 1 + REBASE_TYPE_TEXT_ABSOLUTE32 = 2 + REBASE_TYPE_TEXT_PCREL32 = 3 + + REBASE_OPCODE_MASK = 0xF0 + REBASE_IMMEDIATE_MASK = 0x0F + REBASE_OPCODE_DONE = 0x00 + REBASE_OPCODE_SET_TYPE_IMM = 0x10 + REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x20 + REBASE_OPCODE_ADD_ADDR_ULEB = 0x30 + REBASE_OPCODE_ADD_ADDR_IMM_SCALED = 0x40 + REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50 + REBASE_OPCODE_DO_REBASE_ULEB_TIMES = 0x60 + REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = 0x70 + REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80 +) + +// bind table opcode +const ( + BIND_TYPE_POINTER = 1 + BIND_TYPE_TEXT_ABSOLUTE32 = 2 + BIND_TYPE_TEXT_PCREL32 = 3 + + BIND_SPECIAL_DYLIB_SELF = 0 + BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1 + BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2 + BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3 + + BIND_OPCODE_MASK = 0xF0 + BIND_IMMEDIATE_MASK = 0x0F + BIND_OPCODE_DONE = 0x00 + BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 0x10 + BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 0x20 + BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 0x30 + BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 0x40 + BIND_OPCODE_SET_TYPE_IMM = 0x50 + BIND_OPCODE_SET_ADDEND_SLEB = 0x60 + BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x70 + BIND_OPCODE_ADD_ADDR_ULEB = 0x80 + BIND_OPCODE_DO_BIND = 0x90 + BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 0xA0 + BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 0xB0 + BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 0xC0 + BIND_OPCODE_THREADED = 0xD0 + BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00 + BIND_SUBOPCODE_THREADED_APPLY = 0x01 +) + // Mach-O file writing // https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html @@ -279,7 +331,7 @@ var dylib []string var linkoff int64 -func machowrite(arch *sys.Arch, out *OutBuf, linkmode LinkMode) int { +func machowrite(ctxt *Link, arch *sys.Arch, out *OutBuf, linkmode LinkMode) int { o1 := out.Offset() loadsize := 4 * 4 * ndebug @@ -308,11 +360,14 @@ func machowrite(arch *sys.Arch, out *OutBuf, linkmode LinkMode) int { } out.Write32(uint32(len(load)) + uint32(nseg) + uint32(ndebug)) out.Write32(uint32(loadsize)) + flags := uint32(0) if nkind[SymKindUndef] == 0 { - out.Write32(MH_NOUNDEFS) /* flags - no undefines */ - } else { - out.Write32(0) /* flags */ + flags |= MH_NOUNDEFS + } + if ctxt.IsPIE() && linkmode == LinkInternal { + flags |= MH_PIE | MH_DYLDLINK } + out.Write32(flags) /* flags */ if arch.PtrSize == 8 { out.Write32(0) /* reserved */ } @@ -712,15 +767,17 @@ func asmbMacho(ctxt *Link) { s2 := ldr.SymSize(ctxt.ArchSyms.LinkEditPLT) s3 := ldr.SymSize(ctxt.ArchSyms.LinkEditGOT) s4 := ldr.SymSize(ldr.Lookup(".machosymstr", 0)) + s5 := ldr.SymSize(ldr.Lookup(".machorebase", 0)) + s6 := ldr.SymSize(ldr.Lookup(".machobind", 0)) if ctxt.LinkMode != LinkExternal { ms := newMachoSeg("__LINKEDIT", 0) ms.vaddr = uint64(Rnd(int64(Segdata.Vaddr+Segdata.Length), int64(*FlagRound))) - ms.vsize = uint64(s1) + uint64(s2) + uint64(s3) + uint64(s4) + ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6) ms.fileoffset = uint64(linkoff) ms.filesize = ms.vsize - ms.prot1 = 7 - ms.prot2 = 3 + ms.prot1 = 1 + ms.prot2 = 1 } ml := newMachoLoad(ctxt.Arch, LC_SYMTAB, 4) @@ -745,9 +802,23 @@ func asmbMacho(ctxt *Link) { stringtouint32(ml.data[4:], lib) } } + + if ctxt.LinkMode != LinkExternal && ctxt.IsPIE() { + ml := newMachoLoad(ctxt.Arch, LC_DYLD_INFO_ONLY, 10) + ml.data[0] = uint32(linkoff + s1 + s2 + s3 + s4) // rebase off + ml.data[1] = uint32(s5) // rebase size + ml.data[2] = uint32(linkoff + s1 + s2 + s3 + s4 + s5) // bind off + ml.data[3] = uint32(s6) // bind size + ml.data[4] = 0 // weak bind off + ml.data[5] = 0 // weak bind size + ml.data[6] = 0 // lazy bind off + ml.data[7] = 0 // lazy bind size + ml.data[8] = 0 // export + ml.data[9] = 0 // export size + } } - a := machowrite(ctxt.Arch, ctxt.Out, ctxt.LinkMode) + a := machowrite(ctxt, ctxt.Arch, ctxt.Out, ctxt.LinkMode) if int32(a) > HEADR { Exitf("HEADR too small: %d > %d", a, HEADR) } @@ -989,6 +1060,8 @@ func machodysymtab(ctxt *Link) { func doMachoLink(ctxt *Link) int64 { machosymtab(ctxt) + machoDyldInfo(ctxt) + ldr := ctxt.loader // write data that will be linkedit section @@ -996,6 +1069,8 @@ func doMachoLink(ctxt *Link) int64 { s2 := ctxt.ArchSyms.LinkEditPLT s3 := ctxt.ArchSyms.LinkEditGOT s4 := ldr.Lookup(".machosymstr", 0) + s5 := ldr.Lookup(".machorebase", 0) + s6 := ldr.Lookup(".machobind", 0) // Force the linkedit section to end on a 16-byte // boundary. This allows pure (non-cgo) Go binaries @@ -1019,7 +1094,7 @@ func doMachoLink(ctxt *Link) int64 { s4b.AddUint8(0) } - size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4)) + size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6)) if size > 0 { linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segrelrodata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound)) @@ -1029,6 +1104,8 @@ func doMachoLink(ctxt *Link) int64 { ctxt.Out.Write(ldr.Data(s2)) ctxt.Out.Write(ldr.Data(s3)) ctxt.Out.Write(ldr.Data(s4)) + ctxt.Out.Write(ldr.Data(s5)) + ctxt.Out.Write(ldr.Data(s6)) } return Rnd(int64(size), int64(*FlagRound)) @@ -1172,3 +1249,134 @@ func peekMachoPlatform(m *macho.File) (*MachoPlatformLoad, error) { } return nil, nil } + +// A rebase entry tells the dynamic linker the data at sym+off needs to be +// relocated when the in-memory image moves. (This is somewhat like, say, +// ELF R_X86_64_RELATIVE). +// For now, the only kind of entry we support is that the data is an absolute +// address. That seems all we need. +// In the binary it uses a compact stateful bytecode encoding. So we record +// entries as we go and build the table at the end. +type machoRebaseRecord struct { + sym loader.Sym + off int64 +} + +var machorebase []machoRebaseRecord + +func MachoAddRebase(s loader.Sym, off int64) { + machorebase = append(machorebase, machoRebaseRecord{s, off}) +} + +// A bind entry tells the dynamic linker the data at GOT+off should be bound +// to the address of the target symbol, which is a dynamic import. +// For now, the only kind of entry we support is that the data is an absolute +// address, and the source symbol is always the GOT. That seems all we need. +// In the binary it uses a compact stateful bytecode encoding. So we record +// entries as we go and build the table at the end. +type machoBindRecord struct { + off int64 + targ loader.Sym +} + +var machobind []machoBindRecord + +func MachoAddBind(off int64, targ loader.Sym) { + machobind = append(machobind, machoBindRecord{off, targ}) +} + +// Generate data for the dynamic linker, used in LC_DYLD_INFO_ONLY load command. +// See mach-o/loader.h, struct dyld_info_command, for the encoding. +// e.g. https://opensource.apple.com/source/xnu/xnu-6153.81.5/EXTERNAL_HEADERS/mach-o/loader.h +func machoDyldInfo(ctxt *Link) { + ldr := ctxt.loader + rebase := ldr.CreateSymForUpdate(".machorebase", 0) + bind := ldr.CreateSymForUpdate(".machobind", 0) + + if !(ctxt.IsPIE() && ctxt.IsInternal()) { + return + } + + segId := func(seg *sym.Segment) uint8 { + switch seg { + case &Segtext: + return 1 + case &Segrelrodata: + return 2 + case &Segdata: + if Segrelrodata.Length > 0 { + return 3 + } + return 2 + } + panic("unknown segment") + } + + dylibId := func(s loader.Sym) int { + slib := ldr.SymDynimplib(s) + for i, lib := range dylib { + if lib == slib { + return i + 1 + } + } + return BIND_SPECIAL_DYLIB_FLAT_LOOKUP // don't know where it is from + } + + // Rebase table. + // TODO: use more compact encoding. The encoding is stateful, and + // we can use delta encoding. + rebase.AddUint8(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER) + for _, r := range machorebase { + seg := ldr.SymSect(r.sym).Seg + off := uint64(ldr.SymValue(r.sym)+r.off) - seg.Vaddr + rebase.AddUint8(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg)) + rebase.AddUleb(off) + + rebase.AddUint8(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1) + } + rebase.AddUint8(REBASE_OPCODE_DONE) + sz := Rnd(rebase.Size(), 8) + rebase.Grow(sz) + rebase.SetSize(sz) + + // Bind table. + // TODO: compact encoding, as above. + // TODO: lazy binding? + got := ctxt.GOT + seg := ldr.SymSect(got).Seg + gotAddr := ldr.SymValue(got) + bind.AddUint8(BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER) + for _, r := range machobind { + off := uint64(gotAddr+r.off) - seg.Vaddr + bind.AddUint8(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg)) + bind.AddUleb(off) + + d := dylibId(r.targ) + if d > 0 && d < 128 { + bind.AddUint8(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | uint8(d)&0xf) + } else if d >= 128 { + bind.AddUint8(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB) + bind.AddUleb(uint64(d)) + } else { // d <= 0 + bind.AddUint8(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | uint8(d)&0xf) + } + + bind.AddUint8(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM) + // target symbol name as a C string, with _ prefix + bind.AddUint8('_') + bind.Addstring(ldr.SymExtname(r.targ)) + + bind.AddUint8(BIND_OPCODE_DO_BIND) + } + bind.AddUint8(BIND_OPCODE_DONE) + sz = Rnd(bind.Size(), 16) // make it 16-byte aligned, see the comment in doMachoLink + bind.Grow(sz) + bind.SetSize(sz) + + // TODO: export table. + // The symbols names are encoded as a trie. I'm really too lazy to do that + // for now. + // Without it, the symbols are not dynamically exported, so they cannot be + // e.g. dlsym'd. But internal linking is not the default in that case, so + // it is fine. +} diff --git a/src/cmd/link/internal/loader/symbolbuilder.go b/src/cmd/link/internal/loader/symbolbuilder.go index c0c723d7f0..5d37da8ac6 100644 --- a/src/cmd/link/internal/loader/symbolbuilder.go +++ b/src/cmd/link/internal/loader/symbolbuilder.go @@ -420,3 +420,21 @@ func (sb *SymbolBuilder) MakeWritable() { sb.l.SetAttrReadOnly(sb.symIdx, false) } } + +func (sb *SymbolBuilder) AddUleb(v uint64) { + if v < 128 { // common case: 1 byte + sb.AddUint8(uint8(v)) + return + } + for { + c := uint8(v & 0x7f) + v >>= 7 + if v != 0 { + c |= 0x80 + } + sb.AddUint8(c) + if c&0x80 == 0 { + break + } + } +} -- cgit v1.3 From 15eaa870e14195c17dbb4be0d11bc40dba94ff22 Mon Sep 17 00:00:00 2001 From: Joel Sing Date: Fri, 14 Aug 2020 00:13:28 +1000 Subject: cmd/link: add support for external linking on linux/riscv64 Fixes #36739 Change-Id: Id7573b343786360c72524f9f27d2a8f08d379cf3 Reviewed-on: https://go-review.googlesource.com/c/go/+/243517 Trust: Joel Sing Reviewed-by: Cherry Zhang Reviewed-by: Than McIntosh Run-TryBot: Cherry Zhang TryBot-Result: Go Bot --- src/cmd/dist/test.go | 2 +- src/cmd/link/internal/ld/config.go | 4 +- src/cmd/link/internal/ld/elf.go | 3 + src/cmd/link/internal/ld/lib.go | 6 +- src/cmd/link/internal/ld/main.go | 8 ++ src/cmd/link/internal/loader/loader.go | 5 ++ src/cmd/link/internal/riscv64/asm.go | 148 +++++++++++++++++++++++++++++++-- src/cmd/link/internal/riscv64/obj.go | 3 + 8 files changed, 169 insertions(+), 10 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 94b81613a9..bcb12f29fb 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -921,7 +921,7 @@ func (t *tester) extLink() bool { "darwin-amd64", "darwin-arm64", "dragonfly-amd64", "freebsd-386", "freebsd-amd64", "freebsd-arm", - "linux-386", "linux-amd64", "linux-arm", "linux-arm64", "linux-ppc64le", "linux-mips64", "linux-mips64le", "linux-mips", "linux-mipsle", "linux-s390x", + "linux-386", "linux-amd64", "linux-arm", "linux-arm64", "linux-ppc64le", "linux-mips64", "linux-mips64le", "linux-mips", "linux-mipsle", "linux-riscv64", "linux-s390x", "netbsd-386", "netbsd-amd64", "openbsd-386", "openbsd-amd64", "windows-386", "windows-amd64": diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go index f55e4fc027..834c87d06b 100644 --- a/src/cmd/link/internal/ld/config.go +++ b/src/cmd/link/internal/ld/config.go @@ -202,7 +202,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { // Internally linking cgo is incomplete on some architectures. // https://golang.org/issue/14449 // https://golang.org/issue/21961 - if iscgo && ctxt.Arch.InFamily(sys.MIPS64, sys.MIPS, sys.PPC64) { + if iscgo && ctxt.Arch.InFamily(sys.MIPS64, sys.MIPS, sys.PPC64, sys.RISCV64) { return true, objabi.GOARCH + " does not support internal cgo" } if iscgo && objabi.GOOS == "android" { @@ -285,8 +285,6 @@ func determineLinkMode(ctxt *Link) { } case LinkExternal: switch { - case objabi.GOARCH == "riscv64": - Exitf("external linking not supported for %s/riscv64", objabi.GOOS) case objabi.GOARCH == "ppc64" && objabi.GOOS != "aix": Exitf("external linking not supported for %s/ppc64", objabi.GOOS) } diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index f5a2f899fc..f44e16583d 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -506,6 +506,9 @@ func Elfinit(ctxt *Link) { if ctxt.Arch.Family == sys.MIPS64 { ehdr.flags = 0x20000004 /* MIPS 3 CPIC */ } + if ctxt.Arch.Family == sys.RISCV64 { + ehdr.flags = 0x4 /* RISCV Float ABI Double */ + } elf64 = true ehdr.phoff = ELF64HDRSIZE /* Must be ELF64HDRSIZE: first PHdr must follow ELF header */ diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 8d04973fcf..a68725bef9 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -247,12 +247,16 @@ type Arch struct { Elfreloc1 func(*Link, *OutBuf, *loader.Loader, loader.Sym, loader.ExtReloc, int, int64) bool ElfrelocSize uint32 // size of an ELF relocation record, must match Elfreloc1. Elfsetupplt func(ctxt *Link, plt, gotplt *loader.SymbolBuilder, dynamic loader.Sym) - Gentext func(*Link, *loader.Loader) + Gentext func(*Link, *loader.Loader) // Generate text before addressing has been performed. Machoreloc1 func(*sys.Arch, *OutBuf, *loader.Loader, loader.Sym, loader.ExtReloc, int64) bool MachorelocSize uint32 // size of an Mach-O relocation record, must match Machoreloc1. PEreloc1 func(*sys.Arch, *OutBuf, *loader.Loader, loader.Sym, loader.ExtReloc, int64) bool Xcoffreloc1 func(*sys.Arch, *OutBuf, *loader.Loader, loader.Sym, loader.ExtReloc, int64) bool + // Generate additional symbols for the native symbol table just prior to + // code generation. + GenSymsLate func(*Link, *loader.Loader) + // TLSIEtoLE converts a TLS Initial Executable relocation to // a TLS Local Executable relocation. // diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 3f7370b636..5ae57d1992 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -341,6 +341,14 @@ func Main(arch *sys.Arch, theArch Arch) { }(f, s) } wg.Wait() + + // Generate additional symbols for the native symbol table just prior + // to code generation. + bench.Start("GenSymsLate") + if thearch.GenSymsLate != nil { + thearch.GenSymsLate(ctxt, ctxt.loader) + } + bench.Start("Asmb2") asmb2(ctxt) diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 19d8d98b1e..47cac0441b 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -1791,6 +1791,11 @@ func (l *Loader) SortSub(s Sym) Sym { return sl[0].s } +// SortSyms sorts a list of symbols by their value. +func (l *Loader) SortSyms(ss []Sym) { + sort.SliceStable(ss, func(i, j int) bool { return l.SymValue(ss[i]) < l.SymValue(ss[j]) }) +} + // Insure that reachable bitmap and its siblings have enough size. func (l *Loader) growAttrBitmaps(reqLen int) { if reqLen > l.attrReachable.Len() { diff --git a/src/cmd/link/internal/riscv64/asm.go b/src/cmd/link/internal/riscv64/asm.go index 1236145fb1..66c47c69f8 100644 --- a/src/cmd/link/internal/riscv64/asm.go +++ b/src/cmd/link/internal/riscv64/asm.go @@ -11,20 +11,138 @@ import ( "cmd/link/internal/ld" "cmd/link/internal/loader" "cmd/link/internal/sym" + "debug/elf" "fmt" "log" + "sort" ) +// fakeLabelName matches the RISCV_FAKE_LABEL_NAME from binutils. +const fakeLabelName = ".L0 " + func gentext(ctxt *ld.Link, ldr *loader.Loader) { } +func genSymsLate(ctxt *ld.Link, ldr *loader.Loader) { + if ctxt.LinkMode != ld.LinkExternal { + return + } + + // Generate a local text symbol for each relocation target, as the + // R_RISCV_PCREL_LO12_* relocations generated by elfreloc1 need it. + if ctxt.Textp == nil { + log.Fatal("genSymsLate called before Textp has been assigned") + } + var hi20Syms []loader.Sym + for _, s := range ctxt.Textp { + relocs := ldr.Relocs(s) + for ri := 0; ri < relocs.Count(); ri++ { + r := relocs.At(ri) + if r.Type() != objabi.R_RISCV_PCREL_ITYPE && r.Type() != objabi.R_RISCV_PCREL_STYPE { + continue + } + if r.Off() == 0 && ldr.SymType(s) == sym.STEXT { + // Use the symbol for the function instead of creating + // an overlapping symbol. + continue + } + + // TODO(jsing): Consider generating ELF symbols without needing + // loader symbols, in order to reduce memory consumption. This + // would require changes to genelfsym so that it called + // putelfsym and putelfsyment as appropriate. + sb := ldr.MakeSymbolBuilder(fakeLabelName) + sb.SetType(sym.STEXT) + sb.SetValue(ldr.SymValue(s) + int64(r.Off())) + sb.SetLocal(true) + sb.SetReachable(true) + sb.SetVisibilityHidden(true) + sb.SetSect(ldr.SymSect(s)) + if outer := ldr.OuterSym(s); outer != 0 { + ldr.AddInteriorSym(outer, sb.Sym()) + } + hi20Syms = append(hi20Syms, sb.Sym()) + } + } + ctxt.Textp = append(ctxt.Textp, hi20Syms...) + ldr.SortSyms(ctxt.Textp) +} + +func findHI20Symbol(ctxt *ld.Link, ldr *loader.Loader, val int64) loader.Sym { + idx := sort.Search(len(ctxt.Textp), func(i int) bool { return ldr.SymValue(ctxt.Textp[i]) >= val }) + if idx >= len(ctxt.Textp) { + return 0 + } + if s := ctxt.Textp[idx]; ldr.SymValue(s) == val && ldr.SymType(s) == sym.STEXT { + return s + } + return 0 +} + func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, ri int, sectoff int64) bool { - log.Fatalf("elfreloc1") - return false + elfsym := ld.ElfSymForReloc(ctxt, r.Xsym) + switch r.Type { + case objabi.R_ADDR, objabi.R_DWARFSECREF: + out.Write64(uint64(sectoff)) + switch r.Size { + case 4: + out.Write64(uint64(elf.R_RISCV_32) | uint64(elfsym)<<32) + case 8: + out.Write64(uint64(elf.R_RISCV_64) | uint64(elfsym)<<32) + default: + ld.Errorf(nil, "unknown size %d for %v relocation", r.Size, r.Type) + return false + } + out.Write64(uint64(r.Xadd)) + + case objabi.R_CALLRISCV: + // Call relocations are currently handled via R_RISCV_PCREL_ITYPE. + // TODO(jsing): Consider generating elf.R_RISCV_CALL instead of a + // HI20/LO12_I pair. + + case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE: + // Find the text symbol for the AUIPC instruction targeted + // by this relocation. + relocs := ldr.Relocs(s) + offset := int64(relocs.At(ri).Off()) + hi20Sym := findHI20Symbol(ctxt, ldr, ldr.SymValue(s)+offset) + if hi20Sym == 0 { + ld.Errorf(nil, "failed to find text symbol for HI20 relocation at %d (%x)", sectoff, ldr.SymValue(s)+offset) + return false + } + hi20ElfSym := ld.ElfSymForReloc(ctxt, hi20Sym) + + // Emit two relocations - a R_RISCV_PCREL_HI20 relocation and a + // corresponding R_RISCV_PCREL_LO12_I or R_RISCV_PCREL_LO12_S relocation. + // Note that the LO12 relocation must point to a target that has a valid + // HI20 PC-relative relocation text symbol, which in turn points to the + // given symbol. For further details see the ELF specification for RISC-V: + // + // https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#pc-relative-symbol-addresses + // + var hiRel, loRel elf.R_RISCV + switch r.Type { + case objabi.R_RISCV_PCREL_ITYPE: + hiRel, loRel = elf.R_RISCV_PCREL_HI20, elf.R_RISCV_PCREL_LO12_I + case objabi.R_RISCV_PCREL_STYPE: + hiRel, loRel = elf.R_RISCV_PCREL_HI20, elf.R_RISCV_PCREL_LO12_S + } + out.Write64(uint64(sectoff)) + out.Write64(uint64(hiRel) | uint64(elfsym)<<32) + out.Write64(uint64(r.Xadd)) + out.Write64(uint64(sectoff + 4)) + out.Write64(uint64(loRel) | uint64(hi20ElfSym)<<32) + out.Write64(uint64(0)) + + default: + return false + } + + return true } func elfsetupplt(ctxt *ld.Link, plt, gotplt *loader.SymbolBuilder, dynamic loader.Sym) { - log.Fatalf("elfsetuplt") + log.Fatalf("elfsetupplt") } func machoreloc1(*sys.Arch, *ld.OutBuf, *loader.Loader, loader.Sym, loader.ExtReloc, int64) bool { @@ -33,8 +151,20 @@ func machoreloc1(*sys.Arch, *ld.OutBuf, *loader.Loader, loader.Sym, loader.ExtRe } func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loader.Reloc, s loader.Sym, val int64) (o int64, nExtReloc int, ok bool) { - rs := r.Sym() - rs = ldr.ResolveABIAlias(rs) + if target.IsExternal() { + switch r.Type() { + case objabi.R_CALLRISCV: + return val, 0, true + + case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE: + return val, 2, true + } + + return val, 0, false + } + + rs := ldr.ResolveABIAlias(r.Sym()) + switch r.Type() { case objabi.R_CALLRISCV: // Nothing to do. @@ -89,3 +219,11 @@ func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant log.Fatalf("archrelocvariant") return -1 } + +func extreloc(target *ld.Target, ldr *loader.Loader, r loader.Reloc, s loader.Sym) (loader.ExtReloc, bool) { + switch r.Type() { + case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE: + return ld.ExtrelocViaOuterSym(ldr, r, s), true + } + return loader.ExtReloc{}, false +} diff --git a/src/cmd/link/internal/riscv64/obj.go b/src/cmd/link/internal/riscv64/obj.go index e66d3cd856..917324d922 100644 --- a/src/cmd/link/internal/riscv64/obj.go +++ b/src/cmd/link/internal/riscv64/obj.go @@ -23,9 +23,12 @@ func Init() (*sys.Arch, ld.Arch) { Archinit: archinit, Archreloc: archreloc, Archrelocvariant: archrelocvariant, + Extreloc: extreloc, Elfreloc1: elfreloc1, + ElfrelocSize: 24, Elfsetupplt: elfsetupplt, Gentext: gentext, + GenSymsLate: genSymsLate, Machoreloc1: machoreloc1, Linuxdynld: "/lib/ld.so.1", -- cgit v1.3 From 627959eb04ee0edc4a985a7526ed7fe838ad2573 Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Wed, 14 Oct 2020 21:15:37 -0400 Subject: cmd/link: support cgo internal/linking on darwin/arm64 Cgo programs work as well. Still not enabled by default for now. Enable internal linking tests. Updates #38485. Change-Id: I8324a5c263fba221eb4e67d71207ca84fa241e6c Reviewed-on: https://go-review.googlesource.com/c/go/+/263637 Trust: Cherry Zhang Run-TryBot: Cherry Zhang TryBot-Result: Go Bot Reviewed-by: Than McIntosh --- misc/cgo/test/issue4029.c | 2 +- misc/cgo/test/issue4029.go | 3 +- misc/cgo/test/issue4029w.go | 2 +- src/cmd/dist/test.go | 7 +-- src/cmd/link/internal/arm64/asm.go | 91 ++++++++++++++++++++++++++++-- src/cmd/link/internal/ld/config.go | 3 - src/cmd/link/internal/loader/loader.go | 8 ++- src/cmd/link/internal/loadmacho/ldmacho.go | 57 +++++++++++-------- 8 files changed, 131 insertions(+), 42 deletions(-) (limited to 'src/cmd/link/internal/loader') diff --git a/misc/cgo/test/issue4029.c b/misc/cgo/test/issue4029.c index e6a777fe64..e79c5a709c 100644 --- a/misc/cgo/test/issue4029.c +++ b/misc/cgo/test/issue4029.c @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build !windows,!static -// +build !darwin !internal_pie +// +build !darwin !internal_pie,!arm64 #include #include diff --git a/misc/cgo/test/issue4029.go b/misc/cgo/test/issue4029.go index 8602ce19e2..b2d131833a 100644 --- a/misc/cgo/test/issue4029.go +++ b/misc/cgo/test/issue4029.go @@ -3,10 +3,11 @@ // license that can be found in the LICENSE file. // +build !windows,!static -// +build !darwin !internal_pie +// +build !darwin !internal_pie,!arm64 // Excluded in darwin internal linking PIE mode, as dynamic export is not // supported. +// Excluded in internal linking mode on darwin/arm64, as it is always PIE. package cgotest diff --git a/misc/cgo/test/issue4029w.go b/misc/cgo/test/issue4029w.go index de0cf2138a..b969bdd0fe 100644 --- a/misc/cgo/test/issue4029w.go +++ b/misc/cgo/test/issue4029w.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build windows static darwin,internal_pie +// +build windows static darwin,internal_pie darwin,arm64 package cgotest diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index bcb12f29fb..09d69f72ed 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -946,9 +946,6 @@ func (t *tester) internalLink() bool { if goos == "ios" { return false } - if goos == "darwin" && goarch == "arm64" { - return false - } // Internally linking cgo is incomplete on some architectures. // https://golang.org/issue/10373 // https://golang.org/issue/14449 @@ -964,7 +961,7 @@ func (t *tester) internalLink() bool { func (t *tester) internalLinkPIE() bool { switch goos + "-" + goarch { - case "darwin-amd64", + case "darwin-amd64", "darwin-arm64", "linux-amd64", "linux-arm64", "android-arm64", "windows-amd64", "windows-386", "windows-arm": @@ -1088,7 +1085,7 @@ func (t *tester) cgoTest(dt *distTest) error { pair := gohostos + "-" + goarch switch pair { - case "darwin-amd64", + case "darwin-amd64", "darwin-arm64", "openbsd-386", "openbsd-amd64", "windows-386", "windows-amd64": // test linkmode=external, but __thread not supported, so skip testtls. diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 585c96852f..e456411155 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -71,13 +71,13 @@ func gentext(ctxt *ld.Link, ldr *loader.Loader) { } func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loader.Sym, r loader.Reloc, rIdx int) bool { - targ := r.Sym() var targType sym.SymKind if targ != 0 { targType = ldr.SymType(targ) } + const pcrel = 1 switch r.Type() { default: if r.Type() >= objabi.ElfRelocOffset { @@ -201,6 +201,75 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade su := ldr.MakeSymbolUpdater(s) su.SetRelocType(rIdx, objabi.R_ARM64_LDST128) return true + + // Handle relocations found in Mach-O object files. + case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_UNSIGNED*2: + if targType == sym.SDYNIMPORT { + ldr.Errorf(s, "unexpected reloc 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.MachoRelocOffset + ld.MACHO_ARM64_RELOC_BRANCH26*2 + pcrel: + su := ldr.MakeSymbolUpdater(s) + su.SetRelocType(rIdx, objabi.R_CALLARM64) + if targType == sym.SDYNIMPORT { + addpltsym(target, ldr, syms, targ) + su.SetRelocSym(rIdx, syms.PLT) + su.SetRelocAdd(rIdx, int64(ldr.SymPlt(targ))) + } + return true + + case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_PAGE21*2 + pcrel, + objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_PAGEOFF12*2: + if targType == sym.SDYNIMPORT { + ldr.Errorf(s, "unexpected relocation for dynamic symbol %s", ldr.SymName(targ)) + } + su := ldr.MakeSymbolUpdater(s) + su.SetRelocType(rIdx, objabi.R_ARM64_PCREL) + return true + + case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_GOT_LOAD_PAGE21*2 + pcrel, + objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12*2: + if targType != sym.SDYNIMPORT { + // have symbol + // turn MOVD sym@GOT (adrp+ldr) into MOVD $sym (adrp+add) + data := ldr.Data(s) + off := r.Off() + if int(off+3) >= len(data) { + ldr.Errorf(s, "unexpected GOT_LOAD reloc for non-dynamic symbol %s", ldr.SymName(targ)) + return false + } + o := target.Arch.ByteOrder.Uint32(data[off:]) + su := ldr.MakeSymbolUpdater(s) + switch { + case (o>>24)&0x9f == 0x90: // adrp + // keep instruction unchanged, change relocation type below + case o>>24 == 0xf9: // ldr + // rewrite to add + o = (0x91 << 24) | (o & (1<<22 - 1)) + su.MakeWritable() + su.SetUint32(target.Arch, int64(off), o) + default: + ldr.Errorf(s, "unexpected GOT_LOAD reloc for non-dynamic symbol %s", ldr.SymName(targ)) + return false + } + su.SetRelocType(rIdx, objabi.R_ARM64_PCREL) + return true + } + ld.AddGotSym(target, ldr, syms, targ, 0) + su := ldr.MakeSymbolUpdater(s) + su.SetRelocType(rIdx, objabi.R_ARM64_GOT) + su.SetRelocSym(rIdx, syms.GOT) + su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ))) + return true } // Reread the reloc to incorporate any changes in type above. @@ -671,14 +740,28 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade } o0 := (uint32((t>>12)&3) << 29) | (uint32((t>>12>>2)&0x7ffff) << 5) return val | int64(o0), noExtReloc, isOk - } else if (val>>24)&0x91 == 0x91 { - // R_AARCH64_ADD_ABS_LO12_NC + } else if (val>>24)&0x9f == 0x91 { + // ELF R_AARCH64_ADD_ABS_LO12_NC or Mach-O ARM64_RELOC_PAGEOFF12 // patch instruction: add t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff) o1 := uint32(t&0xfff) << 10 return val | int64(o1), noExtReloc, isOk + } else if (val>>24)&0x3b == 0x39 { + // Mach-O ARM64_RELOC_PAGEOFF12 + // patch ldr/str(b/h/w/d/q) (integer or vector) instructions, which have different scaling factors. + // Mach-O uses same relocation type for them. + shift := uint32(val) >> 30 + if shift == 0 && (val>>20)&0x048 == 0x048 { // 128-bit vector load + shift = 4 + } + t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff) + if t&(1<> shift) << 10 + return val | int64(o1), noExtReloc, isOk } else { - ldr.Errorf(s, "unsupported instruction for %x R_PCRELARM64", val) + ldr.Errorf(s, "unsupported instruction for %x R_ARM64_PCREL", val) } case objabi.R_ARM64_LDST8: diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go index 834c87d06b..a54b96dd5d 100644 --- a/src/cmd/link/internal/ld/config.go +++ b/src/cmd/link/internal/ld/config.go @@ -208,9 +208,6 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { if iscgo && objabi.GOOS == "android" { return true, objabi.GOOS + " does not support internal cgo" } - if iscgo && objabi.GOOS == "darwin" && objabi.GOARCH == "arm64" { - return true, objabi.GOOS + "/" + objabi.GOARCH + " does not support internal cgo" - } // When the race flag is set, the LLVM tsan relocatable file is linked // into the final binary, which means external linking is required because diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 47cac0441b..d861efcb13 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -2622,11 +2622,15 @@ func (l *Loader) Dump() { fmt.Println("Nsyms:", len(l.objSyms)) fmt.Println("syms") for i := Sym(1); i < Sym(len(l.objSyms)); i++ { - pi := interface{}("") + pi := "" if l.IsExternal(i) { pi = fmt.Sprintf("", l.extIndex(i)) } - fmt.Println(i, l.SymName(i), l.SymType(i), pi) + sect := "" + if l.SymSect(i) != nil { + sect = l.SymSect(i).Name + } + fmt.Printf("%v %v %v %v %x %v\n", i, l.SymName(i), l.SymType(i), pi, l.SymValue(i), sect) } fmt.Println("symsByName") for name, i := range l.symsByName[0] { diff --git a/src/cmd/link/internal/loadmacho/ldmacho.go b/src/cmd/link/internal/loadmacho/ldmacho.go index 864d80835b..d12f2bc2ac 100644 --- a/src/cmd/link/internal/loadmacho/ldmacho.go +++ b/src/cmd/link/internal/loadmacho/ldmacho.go @@ -43,7 +43,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// TODO(crawshaw): de-duplicate these symbols with cmd/internal/ld +// TODO(crawshaw): de-duplicate these symbols with cmd/link/internal/ld const ( MACHO_X86_64_RELOC_UNSIGNED = 0 MACHO_X86_64_RELOC_SIGNED = 1 @@ -172,11 +172,12 @@ const ( LdMachoCpuVax = 1 LdMachoCpu68000 = 6 LdMachoCpu386 = 7 - LdMachoCpuAmd64 = 0x1000007 + LdMachoCpuAmd64 = 1<<24 | 7 LdMachoCpuMips = 8 LdMachoCpu98000 = 10 LdMachoCpuHppa = 11 LdMachoCpuArm = 12 + LdMachoCpuArm64 = 1<<24 | 12 LdMachoCpu88000 = 13 LdMachoCpuSparc = 14 LdMachoCpu860 = 15 @@ -471,11 +472,14 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, switch arch.Family { default: return errorf("mach-o %s unimplemented", arch.Name) - case sys.AMD64: if e != binary.LittleEndian || m.cputype != LdMachoCpuAmd64 { return errorf("mach-o object but not amd64") } + case sys.ARM64: + if e != binary.LittleEndian || m.cputype != LdMachoCpuArm64 { + return errorf("mach-o object but not arm64") + } } m.cmd = make([]ldMachoCmd, ncmd) @@ -633,7 +637,9 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, } bld.SetType(l.SymType(outer)) - l.AddInteriorSym(outer, s) + if l.SymSize(outer) != 0 { // skip empty section (0-sized symbol) + l.AddInteriorSym(outer, s) + } bld.SetValue(int64(machsym.value - sect.addr)) if !l.AttrCgoExportDynamic(s) { @@ -722,27 +728,28 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, // Handle X86_64_RELOC_SIGNED referencing a section (rel.extrn == 0). p := l.Data(s) - if arch.Family == sys.AMD64 && rel.extrn == 0 && rel.type_ == MACHO_X86_64_RELOC_SIGNED { - // Calculate the addend as the offset into the section. - // - // The rip-relative offset stored in the object file is encoded - // as follows: - // - // movsd 0x00000360(%rip),%xmm0 - // - // To get the absolute address of the value this rip-relative address is pointing - // to, we must add the address of the next instruction to it. This is done by - // taking the address of the relocation and adding 4 to it (since the rip-relative - // offset can at most be 32 bits long). To calculate the offset into the section the - // relocation is referencing, we subtract the vaddr of the start of the referenced - // section found in the original object file. - // - // [For future reference, see Darwin's /usr/include/mach-o/x86_64/reloc.h] - secaddr := c.seg.sect[rel.symnum-1].addr - - rAdd = int64(uint64(int64(int32(e.Uint32(p[rOff:])))+int64(rOff)+4) - secaddr) - } else { - rAdd = int64(int32(e.Uint32(p[rOff:]))) + if arch.Family == sys.AMD64 { + if rel.extrn == 0 && rel.type_ == MACHO_X86_64_RELOC_SIGNED { + // Calculate the addend as the offset into the section. + // + // The rip-relative offset stored in the object file is encoded + // as follows: + // + // movsd 0x00000360(%rip),%xmm0 + // + // To get the absolute address of the value this rip-relative address is pointing + // to, we must add the address of the next instruction to it. This is done by + // taking the address of the relocation and adding 4 to it (since the rip-relative + // offset can at most be 32 bits long). To calculate the offset into the section the + // relocation is referencing, we subtract the vaddr of the start of the referenced + // section found in the original object file. + // + // [For future reference, see Darwin's /usr/include/mach-o/x86_64/reloc.h] + secaddr := c.seg.sect[rel.symnum-1].addr + rAdd = int64(uint64(int64(int32(e.Uint32(p[rOff:])))+int64(rOff)+4) - secaddr) + } else { + rAdd = int64(int32(e.Uint32(p[rOff:]))) + } } // An unsigned internal relocation has a value offset -- cgit v1.3 From 8e5778ed70ec3d371615a663520a586745fb7bee Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Thu, 5 Nov 2020 14:19:47 -0500 Subject: cmd/link: report error if builtin referenced but not defined When the compiler refers to a runtime builtin, it emits an indexed symbol reference in the object file via predetermined/preassigned ID within the PkgIdxBuiltin pseudo-package. At link time when the loader encounters these references, it redirects them to the corresponding defined symbol in the runtime package. This redirection process currently assumes that if a runtime builtin is referenced, we'll always have a definition for it. This assumption holds in most cases, however for the builtins "runtime.racefuncenter" and "runtime.racefuncexit", we'll only see definitions if the runtime package we're linking against was built with "-race". In the bug in question, build passes "-gcflags=-race" during compilation of the main package, but doesn't pass "-race" directly to 'go build', and as a result the final link combines a race-instrumented main with a non-race runtime; this results in R_CALL relocations with zero-valued target symbols, resulting in a panic during stack checking. This patch changes the loader's resolve method to detect situations where we're asking for builtin "runtime.X", but the runtime package read in doesn't contain a definition for X. Fixes #42396. Change-Id: Iafd38bd3b0f7f462868d120ccd4d7d1b88b27436 Reviewed-on: https://go-review.googlesource.com/c/go/+/267881 Trust: Than McIntosh Run-TryBot: Than McIntosh Reviewed-by: Jeremy Faller Reviewed-by: Cherry Zhang TryBot-Result: Go Bot --- src/cmd/link/internal/loader/loader.go | 29 ++++++++++++++++++- src/cmd/link/link_test.go | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) (limited to 'src/cmd/link/internal/loader') diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index d861efcb13..971cc432ff 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -633,7 +633,11 @@ func (l *Loader) resolve(r *oReader, s goobj.SymRef) Sym { i := int(s.SymIdx) + r.ndef + r.nhashed64def + r.nhasheddef return r.syms[i] case goobj.PkgIdxBuiltin: - return l.builtinSyms[s.SymIdx] + if bi := l.builtinSyms[s.SymIdx]; bi != 0 { + return bi + } + l.reportMissingBuiltin(int(s.SymIdx), r.unit.Lib.Pkg) + return 0 case goobj.PkgIdxSelf: rr = r default: @@ -642,6 +646,29 @@ func (l *Loader) resolve(r *oReader, s goobj.SymRef) Sym { return l.toGlobal(rr, s.SymIdx) } +// reportMissingBuiltin issues an error in the case where we have a +// relocation against a runtime builtin whose definition is not found +// when the runtime package is built. The canonical example is +// "runtime.racefuncenter" -- currently if you do something like +// +// go build -gcflags=-race myprogram.go +// +// the compiler will insert calls to the builtin runtime.racefuncenter, +// but the version of the runtime used for linkage won't actually contain +// definitions of that symbol. See issue #42396 for details. +// +// As currently implemented, this is a fatal error. This has drawbacks +// in that if there are multiple missing builtins, the error will only +// cite the first one. On the plus side, terminating the link here has +// advantages in that we won't run the risk of panics or crashes later +// on in the linker due to R_CALL relocations with 0-valued target +// symbols. +func (l *Loader) reportMissingBuiltin(bsym int, reflib string) { + bname, _ := goobj.BuiltinName(bsym) + log.Fatalf("reference to undefined builtin %q from package %q", + bname, reflib) +} + // Look up a symbol by name, return global index, or 0 if not found. // This is more like Syms.ROLookup than Lookup -- it doesn't create // new symbol. diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 204410e976..158c670739 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -7,6 +7,7 @@ package main import ( "bufio" "bytes" + "cmd/internal/sys" "debug/macho" "internal/testenv" "io/ioutil" @@ -873,3 +874,54 @@ func TestIssue38554(t *testing.T) { t.Errorf("binary too big: got %d, want < %d", got, want) } } + +const testIssue42396src = ` +package main + +//go:noinline +//go:nosplit +func callee(x int) { +} + +func main() { + callee(9) +} +` + +func TestIssue42396(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if !sys.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) { + t.Skip("no race detector support") + } + + t.Parallel() + + tmpdir, err := ioutil.TempDir("", "TestIssue42396") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + src := filepath.Join(tmpdir, "main.go") + err = ioutil.WriteFile(src, []byte(testIssue42396src), 0666) + if err != nil { + t.Fatalf("failed to write source file: %v", err) + } + exe := filepath.Join(tmpdir, "main.exe") + cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-race", "-o", exe, src) + out, err := cmd.CombinedOutput() + if err == nil { + t.Fatalf("build unexpectedly succeeded") + } + + // Check to make sure that we see a reasonable error message + // and not a panic. + if strings.Contains(string(out), "panic:") { + t.Fatalf("build should not fail with panic:\n%s", out) + } + const want = "reference to undefined builtin" + if !strings.Contains(string(out), want) { + t.Fatalf("error message incorrect: expected it to contain %q but instead got:\n%s\n", want, out) + } +} -- cgit v1.3