From e31c9ab557e9f5ee20a61914f1a2bf94191997dc Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 24 Sep 2021 10:57:37 -0700 Subject: [PATCH] cmd/link,runtime: remove functab relocations MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Use an offset from runtime.text instead. This removes the last relocation from functab generation, which lets us simplify that code. size before after Δ % addr2line 3680818 3652498 -28320 -0.769% api 4944850 4892418 -52432 -1.060% asm 4757586 4711266 -46320 -0.974% buildid 2418546 2392578 -25968 -1.074% cgo 4197346 4164818 -32528 -0.775% compile 22076882 21875890 -200992 -0.910% cover 4411362 4358418 -52944 -1.200% dist 3091346 3062738 -28608 -0.925% doc 3563234 3532610 -30624 -0.859% fix 3020658 2991666 -28992 -0.960% link 6164642 6110834 -53808 -0.873% nm 3646818 3618482 -28336 -0.777% objdump 4012594 3983042 -29552 -0.736% pack 2153554 2128338 -25216 -1.171% pprof 13011666 12870114 -141552 -1.088% test2json 2383906 2357554 -26352 -1.105% trace 9736514 9631186 -105328 -1.082% vet 6655058 6580370 -74688 -1.122% total 103927380 102914820 -1012560 -0.974% relocs before after Δ % addr2line 25069 22709 -2360 -9.414% api 17176 13321 -3855 -22.444% asm 18271 15630 -2641 -14.455% buildid 9233 7352 -1881 -20.373% cgo 16222 13044 -3178 -19.591% compile 60421 46299 -14122 -23.373% cover 18479 14526 -3953 -21.392% dist 10135 7733 -2402 -23.700% doc 12735 9940 -2795 -21.947% fix 10820 8341 -2479 -22.911% link 21849 17785 -4064 -18.600% nm 24988 22642 -2346 -9.389% objdump 26060 23462 -2598 -9.969% pack 7665 5936 -1729 -22.557% pprof 60764 50998 -9766 -16.072% test2json 8389 6431 -1958 -23.340% trace 37180 29382 -7798 -20.974% vet 24044 19055 -4989 -20.749% total 409499 334585 -74914 -18.294% Caching the field size in debug/gosym.funcTab avoids a 20% PCToLine performance regression. name old time/op new time/op delta 115/LineToPC-8 56.4µs ± 3% 57.3µs ± 2% +1.66% (p=0.006 n=15+13) 115/PCToLine-8 188ns ± 2% 190ns ± 3% +1.46% (p=0.030 n=15+15) Change-Id: I2816a1b28e62b01852e3b306f08546f1e56cd5ac Reviewed-on: https://go-review.googlesource.com/c/go/+/352191 Trust: Josh Bleecher Snyder Run-TryBot: Josh Bleecher Snyder Reviewed-by: Cherry Mui TryBot-Result: Go Bot --- src/cmd/link/internal/ld/pcln.go | 92 +++++++------------------------- src/debug/gosym/pclntab.go | 16 ++++-- src/runtime/plugin.go | 2 +- src/runtime/symtab.go | 34 +++++++----- 4 files changed, 52 insertions(+), 92 deletions(-) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index b5a66b8517..8f025f91e2 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -539,79 +539,23 @@ func numPCData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo) uint32 { return numPCData } -// Helper types for iterating pclntab. -type pclnSetAddr func(*loader.SymbolBuilder, *sys.Arch, int64, loader.Sym, int64) int64 -type pclnSetUint func(*loader.SymbolBuilder, *sys.Arch, int64, uint64) int64 - // generateFunctab creates the runtime.functab // // runtime.functab contains two things: // // - pc->func look up table. // - array of func objects, interleaved with pcdata and funcdata -// -// Because of timing in the linker, generating this table takes two passes. -// The first pass is executed early in the link, and it creates any needed -// relocations to lay out the data. The piece that needs relocations is -// the PC->func table, handled in writePCToFunc. -// After relocations, once we know where to write things in the output buffer, -// we execute the second pass, which is actually writing the data. func (state *pclntab) generateFunctab(ctxt *Link, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) { // Calculate the size of the table. size, startLocations := state.calculateFunctabSize(ctxt, funcs) - - // If we are internally linking a static executable, the function addresses - // are known, so we can just use them instead of emitting relocations. For - // other cases we still need to emit relocations. - // - // This boolean just helps us figure out which callback to use. - useSymValue := ctxt.IsExe() && ctxt.IsInternal() - writePcln := func(ctxt *Link, s loader.Sym) { ldr := ctxt.loader sb := ldr.MakeSymbolUpdater(s) - - // Create our callbacks. - var setAddr pclnSetAddr - if useSymValue { - // We need to write the offset. - setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { - if v := ldr.SymValue(tgt); v != 0 { - s.SetUint(arch, off, uint64(v+add)) - } - return 0 - } - } else { - // We already wrote relocations. - setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { return 0 } - } - // Write the data. - writePCToFunc(ctxt, sb, funcs, startLocations, setAddr, (*loader.SymbolBuilder).SetUint) + writePCToFunc(ctxt, sb, funcs, startLocations) writeFuncs(ctxt, sb, funcs, inlSyms, startLocations, cuOffsets, nameOffsets) } - state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, writePcln) - - // Create the relocations we need. - ldr := ctxt.loader - sb := ldr.MakeSymbolUpdater(state.pclntab) - - var setAddr pclnSetAddr - if useSymValue { - // If we should use the symbol value, and we don't have one, write a relocation. - setAddr = func(sb *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { - if v := ldr.SymValue(tgt); v == 0 { - sb.SetAddrPlus(arch, off, tgt, add) - } - return 0 - } - } else { - // If we're externally linking, write a relocation. - setAddr = (*loader.SymbolBuilder).SetAddrPlus - } - setUintNOP := func(*loader.SymbolBuilder, *sys.Arch, int64, uint64) int64 { return 0 } - writePCToFunc(ctxt, sb, funcs, startLocations, setAddr, setUintNOP) } // funcData returns the funcdata and offsets for the FuncInfo. @@ -641,10 +585,10 @@ func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64 ldr := ctxt.loader startLocations := make([]uint32, len(funcs)) - // Allocate space for the pc->func table. This structure consists of a pc + // Allocate space for the pc->func table. This structure consists of a pc offset // and an offset to the func structure. After that, we have a single pc // value that marks the end of the last function in the binary. - size := int64(int(state.nfunc)*2*ctxt.Arch.PtrSize + ctxt.Arch.PtrSize) + size := int64(int(state.nfunc)*2*4 + 4) // Now find the space for the func objects. We do this in a running manner, // so that we can find individual starting locations, and because funcdata @@ -674,10 +618,16 @@ func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64 } // writePCToFunc writes the PC->func lookup table. -// This function walks the pc->func lookup table, executing callbacks -// to generate relocations and writing the values for the table. -func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32, setAddr pclnSetAddr, setUint pclnSetUint) { +func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32) { ldr := ctxt.loader + textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0)) + pcOff := func(s loader.Sym) uint32 { + off := ldr.SymValue(s) - textStart + if off < 0 { + panic(fmt.Sprintf("expected func %s(%x) to be placed at or after textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart)) + } + return uint32(off) + } var prevFunc loader.Sym prevSect := ldr.SymSect(funcs[0]) funcIndex := 0 @@ -686,24 +636,20 @@ func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, sta // With multiple text sections, there may be a hole here in the // address space. We use an invalid funcoff value to mark the hole. // See also runtime/symtab.go:findfunc - prevFuncSize := int64(ldr.SymSize(prevFunc)) - setAddr(sb, ctxt.Arch, int64(funcIndex*2*ctxt.Arch.PtrSize), prevFunc, prevFuncSize) - setUint(sb, ctxt.Arch, int64((funcIndex*2+1)*ctxt.Arch.PtrSize), ^uint64(0)) + prevFuncSize := uint32(ldr.SymSize(prevFunc)) + sb.SetUint32(ctxt.Arch, int64(funcIndex*2*4), pcOff(prevFunc)+prevFuncSize) + sb.SetUint32(ctxt.Arch, int64((funcIndex*2+1)*4), ^uint32(0)) funcIndex++ prevSect = thisSect } prevFunc = s - // TODO: We don't actually need these relocations, provided we go to a - // module->func look-up-table like we do for filenames. We could have a - // single relocation for the module, and have them all laid out as - // offsets from the beginning of that module. - setAddr(sb, ctxt.Arch, int64(funcIndex*2*ctxt.Arch.PtrSize), s, 0) - setUint(sb, ctxt.Arch, int64((funcIndex*2+1)*ctxt.Arch.PtrSize), uint64(startLocations[i])) + sb.SetUint32(ctxt.Arch, int64(funcIndex*2*4), pcOff(s)) + sb.SetUint32(ctxt.Arch, int64((funcIndex*2+1)*4), startLocations[i]) funcIndex++ } - // Final entry of table is just end pc. - setAddr(sb, ctxt.Arch, int64(funcIndex)*2*int64(ctxt.Arch.PtrSize), prevFunc, ldr.SymSize(prevFunc)) + // Final entry of table is just end pc offset. + sb.SetUint32(ctxt.Arch, int64(funcIndex)*2*4, pcOff(prevFunc)+uint32(ldr.SymSize(prevFunc))) } // writeFuncs writes the func structures and pcdata to runtime.functab. diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index 8fe45decd6..134cb3d194 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -373,18 +373,22 @@ func (t *LineTable) string(off uint32) string { // functabFieldSize returns the size in bytes of a single functab field. func (t *LineTable) functabFieldSize() int { + if t.version >= ver118 { + return 4 + } return int(t.ptrsize) } // funcTab returns t's funcTab. func (t *LineTable) funcTab() funcTab { - return funcTab{t} + return funcTab{LineTable: t, sz: t.functabFieldSize()} } // funcTab is memory corresponding to a slice of functab structs, followed by an invalid PC. // A functab struct is a PC and a func offset. type funcTab struct { *LineTable + sz int // cached result of t.functabFieldSize } // Count returns the number of func entries in f. @@ -394,17 +398,21 @@ func (f funcTab) Count() int { // pc returns the PC of the i'th func in f. func (f funcTab) pc(i int) uint64 { - return f.uint(f.functab[2*i*f.functabFieldSize():]) + u := f.uint(f.functab[2*i*f.sz:]) + if f.version >= ver118 { + u += uint64(f.textStart) + } + return u } // funcOff returns the funcdata offset of the i'th func in f. func (f funcTab) funcOff(i int) uint64 { - return f.uint(f.functab[(2*i+1)*f.functabFieldSize():]) + return f.uint(f.functab[(2*i+1)*f.sz:]) } // uint returns the uint stored at b. func (f funcTab) uint(b []byte) uint64 { - if f.functabFieldSize() == 4 { + if f.sz == 4 { return uint64(f.binary.Uint32(b)) } return f.binary.Uint64(b) diff --git a/src/runtime/plugin.go b/src/runtime/plugin.go index 500663bfe2..ab3d802389 100644 --- a/src/runtime/plugin.go +++ b/src/runtime/plugin.go @@ -96,7 +96,7 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}, errstr s func pluginftabverify(md *moduledata) { badtable := false for i := 0; i < len(md.ftab); i++ { - entry := md.ftab[i].entry + entry := md.textAddr(uintptr(md.ftab[i].entryoff)) if md.minpc <= entry && entry <= md.maxpc { continue } diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index f423957f88..647300b0c4 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -553,8 +553,8 @@ func modulesinit() { } type functab struct { - entry uintptr - funcoff uintptr + entryoff uint32 // relative to runtime.text + funcoff uint32 } // Mapping information for secondary text sections @@ -604,16 +604,16 @@ func moduledataverify1(datap *moduledata) { nftab := len(datap.ftab) - 1 for i := 0; i < nftab; i++ { // NOTE: ftab[nftab].entry is legal; it is the address beyond the final function. - if datap.ftab[i].entry > datap.ftab[i+1].entry { + if datap.ftab[i].entryoff > datap.ftab[i+1].entryoff { f1 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i].funcoff])), datap} f2 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i+1].funcoff])), datap} f2name := "end" if i+1 < nftab { f2name = funcname(f2) } - println("function symbol table not sorted by PC:", hex(datap.ftab[i].entry), funcname(f1), ">", hex(datap.ftab[i+1].entry), f2name, ", plugin:", datap.pluginpath) + println("function symbol table not sorted by PC offset:", hex(datap.ftab[i].entryoff), funcname(f1), ">", hex(datap.ftab[i+1].entryoff), f2name, ", plugin:", datap.pluginpath) for j := 0; j <= i; j++ { - println("\t", hex(datap.ftab[j].entry), funcname(funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[j].funcoff])), datap})) + println("\t", hex(datap.ftab[j].entryoff), funcname(funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[j].funcoff])), datap})) } if GOOS == "aix" && isarchive { println("-Wl,-bnoobjreorder is mandatory on aix/ppc64 with c-archive") @@ -622,8 +622,12 @@ func moduledataverify1(datap *moduledata) { } } - if datap.minpc != datap.ftab[0].entry || - datap.maxpc != datap.ftab[nftab].entry { + min := datap.textAddr(uintptr(datap.ftab[0].entryoff)) + // The max PC is outside of the text section. + // Subtract 1 to get a PC inside the text section, look it up, then add 1 back in. + max := datap.textAddr(uintptr(datap.ftab[nftab].entryoff-1)) + 1 + if datap.minpc != min || datap.maxpc != max { + println("minpc=", hex(datap.minpc), "min=", hex(min), "maxpc=", hex(datap.maxpc), "max=", hex(max)) throw("minpc or maxpc invalid") } @@ -649,6 +653,9 @@ func moduledataverify1(datap *moduledata) { // Each function's offset is compared against the section vaddrs and sizes to determine the containing section. // Then the section relative offset is added to the section's // relocated baseaddr to compute the function addess. +// +// It is nosplit because it is part of the findfunc implementation. +//go:nosplit func (md *moduledata) textAddr(off uintptr) uintptr { var res uintptr if len(md.textsectmap) > 1 { @@ -808,24 +815,23 @@ func findfunc(pc uintptr) funcInfo { if idx >= uint32(len(datap.ftab)) { idx = uint32(len(datap.ftab) - 1) } - if pc < datap.ftab[idx].entry { + if pc < datap.textAddr(uintptr(datap.ftab[idx].entryoff)) { // With multiple text sections, the idx might reference a function address that - // is higher than the pc being searched, so search backward until the matching address is found. - - for datap.ftab[idx].entry > pc && idx > 0 { + // is higher than the pcOff being searched, so search backward until the matching address is found. + for datap.textAddr(uintptr(datap.ftab[idx].entryoff)) > pc && idx > 0 { idx-- } if idx == 0 { throw("findfunc: bad findfunctab entry idx") } } else { - // linear search to find func with pc >= entry. - for datap.ftab[idx+1].entry <= pc { + // linear search to find func with pcOff >= entry. + for datap.textAddr(uintptr(datap.ftab[idx+1].entryoff)) <= pc { idx++ } } funcoff := datap.ftab[idx].funcoff - if funcoff == ^uintptr(0) { + if funcoff == ^uint32(0) { // With multiple text sections, there may be functions inserted by the external // linker that are not known by Go. This means there may be holes in the PC // range covered by the func table. The invalid funcoff value indicates a hole. -- 2.50.0