blob: 30e0bdc839c9b0a5ff4d540876b39642906024d0 [file] [log] [blame]
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ld
import (
"cmd/internal/goobj"
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/src"
"cmd/internal/sys"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
"encoding/binary"
"fmt"
"log"
"math"
"os"
"path/filepath"
"strings"
)
// oldPclnState holds state information used during pclntab generation. Here
// 'ldr' is just a pointer to the context's loader, 'deferReturnSym' is the
// index for the symbol "runtime.deferreturn", 'nameToOffset' is a helper
// function for capturing function names, 'numberedFiles' records the file
// number assigned to a given file symbol, 'filepaths' is a slice of expanded
// paths (indexed by file number).
//
// NB: This is deprecated, and will be eliminated when pclntab_old is
// eliminated.
type oldPclnState struct {
ldr *loader.Loader
deferReturnSym loader.Sym
numberedFiles map[string]int64
filepaths []string
}
// pclntab holds the state needed for pclntab generation.
type pclntab struct {
// The first and last functions found.
firstFunc, lastFunc loader.Sym
// The offset to the filetab.
filetabOffset int32
// Running total size of pclntab.
size int64
// runtime.pclntab's symbols
carrier loader.Sym
pclntab loader.Sym
pcheader loader.Sym
funcnametab loader.Sym
findfunctab 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
// get a dummy function inserted between because the external linker can place
// functions in those areas. We mark those areas as not covered by the Go
// runtime.
//
// On most platforms this is the number of reachable functions.
nfunc int32
// maps the function symbol to offset in runtime.funcnametab
// This doesn't need to reside in the state once pclntab_old's been
// deleted -- it can live in generateFuncnametab.
// TODO(jfaller): Delete me!
funcNameOffset map[loader.Sym]int32
}
// addGeneratedSym adds a generator symbol to pclntab, returning the new Sym.
// It is the caller's responsibilty to save they symbol in state.
func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f generatorFunc) loader.Sym {
size = Rnd(size, int64(ctxt.Arch.PtrSize))
state.size += size
s := ctxt.createGeneratorSymbol(name, 0, sym.SPCLNTAB, size, f)
ctxt.loader.SetAttrReachable(s, true)
ctxt.loader.SetCarrierSym(s, state.carrier)
ctxt.loader.SetAttrNotInSymbolTable(s, true)
return s
}
func makeOldPclnState(ctxt *Link) *oldPclnState {
ldr := ctxt.loader
drs := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal)
state := &oldPclnState{
ldr: ldr,
deferReturnSym: drs,
numberedFiles: make(map[string]int64),
// NB: initial entry in filepaths below is to reserve the zero value,
// so that when we do a map lookup in numberedFiles fails, it will not
// return a value slot in filepaths.
filepaths: []string{""},
}
return state
}
// makePclntab makes a pclntab object, and assembles all the compilation units
// we'll need to write pclntab.
func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit) {
ldr := ctxt.loader
state := &pclntab{
funcNameOffset: make(map[loader.Sym]int32),
}
// Gather some basic stats and info.
seenCUs := make(map[*sym.CompilationUnit]struct{})
prevSect := ldr.SymSect(ctxt.Textp[0])
compUnits := []*sym.CompilationUnit{}
for _, s := range ctxt.Textp {
if !emitPcln(ctxt, s, container) {
continue
}
state.nfunc++
if state.firstFunc == 0 {
state.firstFunc = s
}
state.lastFunc = s
ss := ldr.SymSect(s)
if ss != prevSect {
// With multiple text sections, the external linker may
// insert functions between the sections, which are not
// known by Go. This leaves holes in the PC range covered
// by the func table. We need to generate an entry to mark
// the hole.
state.nfunc++
prevSect = ss
}
// We need to keep track of all compilation units we see. Some symbols
// (eg, go.buildid, _cgoexp_, etc) won't have a compilation unit.
cu := ldr.SymUnit(s)
if _, ok := seenCUs[cu]; cu != nil && !ok {
seenCUs[cu] = struct{}{}
cu.PclnIndex = len(compUnits)
compUnits = append(compUnits, cu)
}
}
return state, compUnits
}
func ftabaddstring(ftab *loader.SymbolBuilder, s string) int32 {
start := len(ftab.Data())
ftab.Grow(int64(start + len(s) + 1)) // make room for s plus trailing NUL
ftd := ftab.Data()
copy(ftd[start:], s)
return int32(start)
}
// numberfile assigns a file number to the file if it hasn't been assigned already.
// This funciton looks at a CU's file at index [i], and if it's a new filename,
// stores that filename in the global file table, and adds it to the map lookup
// for renumbering pcfile.
func (state *oldPclnState) numberfile(cu *sym.CompilationUnit, i goobj.CUFileIndex) int64 {
file := cu.FileTable[i]
if val, ok := state.numberedFiles[file]; ok {
return val
}
path := file
if strings.HasPrefix(path, src.FileSymPrefix) {
path = file[len(src.FileSymPrefix):]
}
val := int64(len(state.filepaths))
state.numberedFiles[file] = val
state.filepaths = append(state.filepaths, expandGoroot(path))
return val
}
func (state *oldPclnState) fileVal(cu *sym.CompilationUnit, i int32) int64 {
file := cu.FileTable[i]
if val, ok := state.numberedFiles[file]; ok {
return val
}
panic("should have been numbered first")
}
func (state *oldPclnState) renumberfiles(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, d *sym.Pcdata) {
// Give files numbers.
nf := fi.NumFile()
for i := uint32(0); i < nf; i++ {
state.numberfile(cu, fi.File(int(i)))
}
buf := make([]byte, binary.MaxVarintLen32)
newval := int32(-1)
var out sym.Pcdata
it := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
for it.Init(d.P); !it.Done; it.Next() {
// value delta
oldval := it.Value
var val int32
if oldval == -1 {
val = -1
} else {
if oldval < 0 || oldval >= int32(len(cu.FileTable)) {
log.Fatalf("bad pcdata %d", oldval)
}
val = int32(state.fileVal(cu, oldval))
}
dv := val - newval
newval = val
// value
n := binary.PutVarint(buf, int64(dv))
out.P = append(out.P, buf[:n]...)
// pc delta
pc := (it.NextPC - it.PC) / it.PCScale
n = binary.PutUvarint(buf, uint64(pc))
out.P = append(out.P, buf[:n]...)
}
// terminating value delta
// we want to write varint-encoded 0, which is just 0
out.P = append(out.P, 0)
*d = out
}
// onlycsymbol looks at a symbol's name to report whether this is a
// symbol that is referenced by C code
func onlycsymbol(sname string) bool {
switch sname {
case "_cgo_topofstack", "__cgo_topofstack", "_cgo_panic", "crosscall2":
return true
}
if strings.HasPrefix(sname, "_cgoexp_") {
return true
}
return false
}
func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool {
if ctxt.BuildMode == BuildModePlugin && ctxt.HeadType == objabi.Hdarwin && onlycsymbol(ctxt.loader.SymName(s)) {
return false
}
// We want to generate func table entries only for the "lowest
// level" symbols, not containers of subsymbols.
return !container.Has(s)
}
func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint32 {
deferreturn := uint32(0)
lastWasmAddr := uint32(0)
relocs := state.ldr.Relocs(s)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if target.IsWasm() && r.Type() == objabi.R_ADDR {
// Wasm does not have a live variable set at the deferreturn
// call itself. Instead it has one identified by the
// resumption point immediately preceding the deferreturn.
// The wasm code has a R_ADDR relocation which is used to
// set the resumption point to PC_B.
lastWasmAddr = uint32(r.Add())
}
if r.Type().IsDirectCall() && (r.Sym() == state.deferReturnSym || state.ldr.IsDeferReturnTramp(r.Sym())) {
if target.IsWasm() {
deferreturn = lastWasmAddr - 1
} else {
// Note: the relocation target is in the call instruction, but
// is not necessarily the whole instruction (for instance, on
// x86 the relocation applies to bytes [1:5] of the 5 byte call
// instruction).
deferreturn = uint32(r.Off())
switch target.Arch.Family {
case sys.AMD64, sys.I386:
deferreturn--
case sys.PPC64, sys.ARM, sys.ARM64, sys.MIPS, sys.MIPS64:
// no change
case sys.RISCV64:
// TODO(jsing): The JALR instruction is marked with
// R_CALLRISCV, whereas the actual reloc is currently
// one instruction earlier starting with the AUIPC.
deferreturn -= 4
case sys.S390X:
deferreturn -= 2
default:
panic(fmt.Sprint("Unhandled architecture:", target.Arch.Family))
}
}
break // only need one
}
}
return deferreturn
}
// genInlTreeSym generates the InlTree sym for a function with the
// specified FuncInfo.
func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, newState *pclntab) loader.Sym {
ldr := state.ldr
its := ldr.CreateExtSym("", 0)
inlTreeSym := ldr.MakeSymbolUpdater(its)
// Note: the generated symbol is given a type of sym.SGOFUNC, as a
// signal to the symtab() phase that it needs to be grouped in with
// other similar symbols (gcdata, etc); the dodata() phase will
// eventually switch the type back to SRODATA.
inlTreeSym.SetType(sym.SGOFUNC)
ldr.SetAttrReachable(its, true)
ninl := fi.NumInlTree()
for i := 0; i < int(ninl); i++ {
call := fi.InlTree(i)
// Usually, call.File is already numbered since the file
// shows up in the Pcfile table. However, two inlined calls
// might overlap exactly so that only the innermost file
// appears in the Pcfile table. In that case, this assigns
// the outer file a number.
val := state.numberfile(cu, call.File)
nameoff, ok := newState.funcNameOffset[call.Func]
if !ok {
panic("couldn't find function name offset")
}
inlTreeSym.SetUint16(arch, int64(i*20+0), uint16(call.Parent))
inlFunc := ldr.FuncInfo(call.Func)
var funcID objabi.FuncID
if inlFunc.Valid() {
funcID = inlFunc.FuncID()
}
inlTreeSym.SetUint8(arch, int64(i*20+2), uint8(funcID))
// byte 3 is unused
inlTreeSym.SetUint32(arch, int64(i*20+4), uint32(val))
inlTreeSym.SetUint32(arch, int64(i*20+8), uint32(call.Line))
inlTreeSym.SetUint32(arch, int64(i*20+12), uint32(nameoff))
inlTreeSym.SetUint32(arch, int64(i*20+16), uint32(call.ParentPC))
}
return its
}
// generatePCHeader creates the runtime.pcheader symbol, setting it up as a
// generator to fill in its data later.
func (state *pclntab) generatePCHeader(ctxt *Link) {
writeHeader := func(ctxt *Link, s loader.Sym) {
ldr := ctxt.loader
header := ctxt.loader.MakeSymbolUpdater(s)
writeSymOffset := func(off int64, ws loader.Sym) int64 {
diff := ldr.SymValue(ws) - ldr.SymValue(s)
if diff <= 0 {
name := ldr.SymName(ws)
panic(fmt.Sprintf("expected runtime.pcheader(%x) to be placed before %s(%x)", ldr.SymValue(s), name, ldr.SymValue(ws)))
}
return header.SetUintptr(ctxt.Arch, off, uintptr(diff))
}
// Write header.
// Keep in sync with runtime/symtab.go:pcHeader.
header.SetUint32(ctxt.Arch, 0, 0xfffffffa)
header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC))
header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize))
off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc))
off = writeSymOffset(off, state.funcnametab)
off = writeSymOffset(off, state.pclntab)
}
size := int64(8 + 3*ctxt.Arch.PtrSize)
state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
}
// walkFuncs iterates over the Textp, calling a function for each unique
// function and inlined function.
func (state *pclntab) walkFuncs(ctxt *Link, container loader.Bitmap, f func(loader.Sym)) {
ldr := ctxt.loader
seen := make(map[loader.Sym]struct{})
for _, ls := range ctxt.Textp {
s := loader.Sym(ls)
if !emitPcln(ctxt, s, container) {
continue
}
if _, ok := seen[s]; !ok {
f(s)
seen[s] = struct{}{}
}
fi := ldr.FuncInfo(s)
if !fi.Valid() {
continue
}
fi.Preload()
for i, ni := 0, fi.NumInlTree(); i < int(ni); i++ {
call := fi.InlTree(i).Func
if _, ok := seen[call]; !ok {
f(call)
seen[call] = struct{}{}
}
}
}
}
// generateFuncnametab creates the function name table.
func (state *pclntab) generateFuncnametab(ctxt *Link, container loader.Bitmap) {
// Write the null terminated strings.
writeFuncNameTab := func(ctxt *Link, s loader.Sym) {
symtab := ctxt.loader.MakeSymbolUpdater(s)
for s, off := range state.funcNameOffset {
symtab.AddStringAt(int64(off), ctxt.loader.SymName(s))
}
}
// Loop through the CUs, and calculate the size needed.
var size int64
state.walkFuncs(ctxt, container, func(s loader.Sym) {
state.funcNameOffset[s] = int32(size)
size += int64(ctxt.loader.SymNameLen(s)) + 1 // NULL terminate
})
state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab)
}
// pclntab initializes the pclntab symbol with
// runtime function and file name information.
// pclntab generates the pcln table for the link output.
func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// Go 1.2's symtab layout is documented in golang.org/s/go12symtab, but the
// layout and data has changed since that time.
//
// As of July 2020, here's the layout of pclntab:
//
// .gopclntab/__gopclntab [elf/macho section]
// runtime.pclntab
// Carrier symbol for the entire pclntab section.
//
// runtime.pcheader (see: runtime/symtab.go:pcHeader)
// 8-byte magic
// nfunc [thearch.ptrsize bytes]
// offset to runtime.funcnametab from the beginning of runtime.pcheader
// offset to runtime.pclntab_old from beginning of runtime.pcheader
//
// runtime.funcnametab
// []list of null terminated function names
//
// runtime.pclntab_old
// function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes]
// end PC [thearch.ptrsize bytes]
// offset to file table [4 bytes]
// func structures, pcdata tables.
// filetable
oldState := makeOldPclnState(ctxt)
state, _ := makePclntab(ctxt, container)
ldr := ctxt.loader
state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0)
ldr.MakeSymbolUpdater(state.carrier).SetType(sym.SPCLNTAB)
ldr.SetAttrReachable(state.carrier, true)
// runtime.pclntab_old is just a placeholder,and will eventually be deleted.
// It contains the pieces of runtime.pclntab that haven't moved to a more
// rational form.
state.pclntab = ldr.LookupOrCreateSym("runtime.pclntab_old", 0)
state.generatePCHeader(ctxt)
state.generateFuncnametab(ctxt, container)
funcdataBytes := int64(0)
ldr.SetCarrierSym(state.pclntab, state.carrier)
ldr.SetAttrNotInSymbolTable(state.pclntab, true)
ftab := ldr.MakeSymbolUpdater(state.pclntab)
ftab.SetValue(state.size)
ftab.SetType(sym.SPCLNTAB)
ftab.SetReachable(true)
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
// addresses are known, so we can just use them instead of emitting
// relocations.
// For other cases we are generating a relocatable binary so we
// still need to emit relocations.
setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 {
if v := ldr.SymValue(tgt); v != 0 {
return s.SetUint(arch, off, uint64(v+add))
}
return s.SetAddrPlus(arch, off, tgt, add)
}
}
pcsp := sym.Pcdata{}
pcfile := sym.Pcdata{}
pcline := sym.Pcdata{}
pcdata := []sym.Pcdata{}
funcdata := []loader.Sym{}
funcdataoff := []int64{}
var nfunc int32
prevFunc := ctxt.Textp[0]
for _, s := range ctxt.Textp {
if !emitPcln(ctxt, s, container) {
continue
}
thisSect := ldr.SymSect(s)
prevSect := ldr.SymSect(prevFunc)
if thisSect != prevSect {
// With multiple text sections, there may be a hole here
// in the address space (see the comment above). We use an
// invalid funcoff value to mark the hole. See also
// runtime/symtab.go:findfunc
prevFuncSize := int64(ldr.SymSize(prevFunc))
setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), prevFunc, prevFuncSize)
ftab.SetUint(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), ^uint64(0))
nfunc++
}
prevFunc = s
pcsp.P = pcsp.P[:0]
pcline.P = pcline.P[:0]
pcfile.P = pcfile.P[:0]
pcdata = pcdata[:0]
funcdataoff = funcdataoff[:0]
funcdata = funcdata[:0]
fi := ldr.FuncInfo(s)
if fi.Valid() {
fi.Preload()
npc := fi.NumPcdata()
for i := uint32(0); i < npc; i++ {
pcdata = append(pcdata, sym.Pcdata{P: fi.Pcdata(int(i))})
}
nfd := fi.NumFuncdataoff()
for i := uint32(0); i < nfd; i++ {
funcdataoff = append(funcdataoff, fi.Funcdataoff(int(i)))
}
funcdata = fi.Funcdata(funcdata)
}
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
}
if len(funcdataoff) <= objabi.FUNCDATA_InlTree {
// Create inline tree funcdata.
newfuncdata := make([]loader.Sym, objabi.FUNCDATA_InlTree+1)
newfuncdataoff := make([]int64, objabi.FUNCDATA_InlTree+1)
copy(newfuncdata, funcdata)
copy(newfuncdataoff, funcdataoff)
funcdata = newfuncdata
funcdataoff = newfuncdataoff
}
}
dSize := len(ftab.Data())
funcstart := int32(dSize)
funcstart += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1) // align to ptrsize
setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), s, 0)
ftab.SetUint(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint64(funcstart))
// Write runtime._func. Keep in sync with ../../../../runtime/runtime2.go:/_func
// and package debug/gosym.
// fixed size of struct, checked below
off := funcstart
end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 5*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize)
if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) {
end += 4
}
ftab.Grow(int64(end))
// entry uintptr
off = int32(setAddr(ftab, ctxt.Arch, int64(off), s, 0))
// name int32
nameoff, ok := state.funcNameOffset[s]
if !ok {
panic("couldn't find function name offset")
}
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(nameoff)))
// args int32
// TODO: Move into funcinfo.
args := uint32(0)
if fi.Valid() {
args = uint32(fi.Args())
}
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), args))
// deferreturn
deferreturn := oldState.computeDeferReturn(&ctxt.Target, s)
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn))
cu := ldr.SymUnit(s)
if fi.Valid() {
pcsp = sym.Pcdata{P: fi.Pcsp()}
pcfile = sym.Pcdata{P: fi.Pcfile()}
pcline = sym.Pcdata{P: fi.Pcline()}
oldState.renumberfiles(ctxt, cu, fi, &pcfile)
if false {
// Sanity check the new numbering
it := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
for it.Init(pcfile.P); !it.Done; it.Next() {
if it.Value < 1 || it.Value > int32(len(oldState.numberedFiles)) {
ctxt.Errorf(s, "bad file number in pcfile: %d not in range [1, %d]\n", it.Value, len(oldState.numberedFiles))
errorexit()
}
}
}
}
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: 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))))
// Store the compilation unit index.
cuIdx := ^uint16(0)
if cu := ldr.SymUnit(s); cu != nil {
if cu.PclnIndex > math.MaxUint16 {
panic("cu limit reached.")
}
cuIdx = uint16(cu.PclnIndex)
}
off = int32(ftab.SetUint16(ctxt.Arch, int64(off), cuIdx))
// funcID uint8
var funcID objabi.FuncID
if fi.Valid() {
funcID = fi.FuncID()
}
off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(funcID)))
// 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)
}
// funcdata, must be pointer-aligned and we're only int32-aligned.
// Missing funcdata will be 0 (nil pointer).
if len(funcdata) > 0 {
if off&int32(ctxt.Arch.PtrSize-1) != 0 {
off += 4
}
for i := range funcdata {
dataoff := int64(off) + int64(ctxt.Arch.PtrSize)*int64(i)
if funcdata[i] == 0 {
ftab.SetUint(ctxt.Arch, dataoff, uint64(funcdataoff[i]))
continue
}
// TODO: Dedup.
funcdataBytes += int64(len(ldr.Data(funcdata[i])))
setAddr(ftab, ctxt.Arch, dataoff, funcdata[i], funcdataoff[i])
}
off += int32(len(funcdata)) * int32(ctxt.Arch.PtrSize)
}
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)
errorexit()
}
nfunc++
}
// Final entry of table is just end pc.
setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), state.lastFunc, ldr.SymSize(state.lastFunc))
// Start file table.
dSize := len(ftab.Data())
start := int32(dSize)
start += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1)
state.filetabOffset = start
ftab.SetUint32(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint32(start))
nf := len(oldState.numberedFiles)
ftab.Grow(int64(start) + int64((nf+1)*4))
ftab.SetUint32(ctxt.Arch, int64(start), uint32(nf+1))
for i := nf; i > 0; i-- {
path := oldState.filepaths[i]
val := int64(i)
ftab.SetUint32(ctxt.Arch, int64(start)+val*4, uint32(ftabaddstring(ftab, path)))
}
ftab.SetSize(int64(len(ftab.Data())))
ctxt.NumFilesyms = len(oldState.numberedFiles)
if ctxt.Debugvlog != 0 {
ctxt.Logf("pclntab=%d bytes, funcdata total %d bytes\n", ftab.Size(), funcdataBytes)
}
return state
}
func gorootFinal() string {
root := objabi.GOROOT
if final := os.Getenv("GOROOT_FINAL"); final != "" {
root = final
}
return root
}
func expandGoroot(s string) string {
const n = len("$GOROOT")
if len(s) >= n+1 && s[:n] == "$GOROOT" && (s[n] == '/' || s[n] == '\\') {
return filepath.ToSlash(filepath.Join(gorootFinal(), s[n:]))
}
return s
}
const (
BUCKETSIZE = 256 * MINFUNC
SUBBUCKETS = 16
SUBBUCKETSIZE = BUCKETSIZE / SUBBUCKETS
NOIDX = 0x7fffffff
)
// findfunctab generates a lookup table to quickly find the containing
// function for a pc. See src/runtime/symtab.go:findfunc for details.
func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
ldr := ctxt.loader
// find min and max address
min := ldr.SymValue(ctxt.Textp[0])
lastp := ctxt.Textp[len(ctxt.Textp)-1]
max := ldr.SymValue(lastp) + ldr.SymSize(lastp)
// for each subbucket, compute the minimum of all symbol indexes
// that map to that subbucket.
n := int32((max - min + SUBBUCKETSIZE - 1) / SUBBUCKETSIZE)
nbuckets := int32((max - min + BUCKETSIZE - 1) / BUCKETSIZE)
size := 4*int64(nbuckets) + int64(n)
writeFindFuncTab := func(_ *Link, s loader.Sym) {
t := ldr.MakeSymbolUpdater(s)
indexes := make([]int32, n)
for i := int32(0); i < n; i++ {
indexes[i] = NOIDX
}
idx := int32(0)
for i, s := range ctxt.Textp {
if !emitPcln(ctxt, s, container) {
continue
}
p := ldr.SymValue(s)
var e loader.Sym
i++
if i < len(ctxt.Textp) {
e = ctxt.Textp[i]
}
for e != 0 && !emitPcln(ctxt, e, container) && i < len(ctxt.Textp) {
e = ctxt.Textp[i]
i++
}
q := max
if e != 0 {
q = ldr.SymValue(e)
}
//print("%d: [%lld %lld] %s\n", idx, p, q, s->name);
for ; p < q; p += SUBBUCKETSIZE {
i = int((p - min) / SUBBUCKETSIZE)
if indexes[i] > idx {
indexes[i] = idx
}
}
i = int((q - 1 - min) / SUBBUCKETSIZE)
if indexes[i] > idx {
indexes[i] = idx
}
idx++
}
// fill in table
for i := int32(0); i < nbuckets; i++ {
base := indexes[i*SUBBUCKETS]
if base == NOIDX {
Errorf(nil, "hole in findfunctab")
}
t.SetUint32(ctxt.Arch, int64(i)*(4+SUBBUCKETS), uint32(base))
for j := int32(0); j < SUBBUCKETS && i*SUBBUCKETS+j < n; j++ {
idx = indexes[i*SUBBUCKETS+j]
if idx == NOIDX {
Errorf(nil, "hole in findfunctab")
}
if idx-base >= 256 {
Errorf(nil, "too many functions in a findfunc bucket! %d/%d %d %d", i, nbuckets, j, idx-base)
}
t.SetUint8(ctxt.Arch, int64(i)*(4+SUBBUCKETS)+4+int64(j), uint8(idx-base))
}
}
}
state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SRODATA, size, writeFindFuncTab)
ldr.SetAttrReachable(state.findfunctab, true)
ldr.SetAttrLocal(state.findfunctab, true)
}
// findContainerSyms returns a bitmap, indexed by symbol number, where there's
// a 1 for every container symbol.
func (ctxt *Link) findContainerSyms() loader.Bitmap {
ldr := ctxt.loader
container := loader.MakeBitmap(ldr.NSym())
// Find container symbols and mark them as such.
for _, s := range ctxt.Textp {
outer := ldr.OuterSym(s)
if outer != 0 {
container.Set(outer)
}
}
return container
}