aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJosh Bleecher Snyder <josharian@gmail.com>2021-09-24 10:57:37 -0700
committerJosh Bleecher Snyder <josharian@gmail.com>2021-10-05 23:25:06 +0000
commite31c9ab557e9f5ee20a61914f1a2bf94191997dc (patch)
treea1e031064b7a58ad8954e5b21be208971d04db33 /src
parent96fface83ad69b6d6ad8059d61d232737572e85d (diff)
downloadgo-e31c9ab557e9f5ee20a61914f1a2bf94191997dc.tar.xz
cmd/link,runtime: remove functab relocations
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 <josharian@gmail.com> Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> Reviewed-by: Cherry Mui <cherryyz@google.com> TryBot-Result: Go Bot <gobot@golang.org>
Diffstat (limited to 'src')
-rw-r--r--src/cmd/link/internal/ld/pcln.go92
-rw-r--r--src/debug/gosym/pclntab.go16
-rw-r--r--src/runtime/plugin.go2
-rw-r--r--src/runtime/symtab.go34
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.