// 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/objabi" "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" "fmt" "internal/buildcfg" "os" "path/filepath" "strings" ) const funcSize = 11 * 4 // funcSize is the size of the _func object in runtime/runtime2.go // pclntab holds the state needed for pclntab generation. type pclntab struct { // The first and last functions found. firstFunc, lastFunc loader.Sym // 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 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 // 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 // The number of filenames in runtime.filetab. nfiles uint32 } // addGeneratedSym adds a generator symbol to pclntab, returning the new Sym. // It is the caller's responsibility to save the 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 } // makePclntab makes a pclntab object, and assembles all the compilation units // we'll need to write pclntab. Returns the pclntab structure, a slice of the // CompilationUnits we need, and a slice of the function symbols we need to // generate pclntab. func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit, []loader.Sym) { ldr := ctxt.loader state := new(pclntab) // Gather some basic stats and info. seenCUs := make(map[*sym.CompilationUnit]struct{}) compUnits := []*sym.CompilationUnit{} funcs := []loader.Sym{} for _, s := range ctxt.Textp { if !emitPcln(ctxt, s, container) { continue } funcs = append(funcs, s) state.nfunc++ if state.firstFunc == 0 { state.firstFunc = s } state.lastFunc = s // 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, funcs } func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool { // We want to generate func table entries only for the "lowest // level" symbols, not containers of subsymbols. return !container.Has(s) } func computeDeferReturn(ctxt *Link, deferReturnSym, s loader.Sym) uint32 { ldr := ctxt.loader target := ctxt.Target deferreturn := uint32(0) lastWasmAddr := uint32(0) relocs := ldr.Relocs(s) for ri := 0; ri < relocs.Count(); ri++ { r := relocs.At(ri) if target.IsWasm() && r.Type() == objabi.R_ADDR { // wasm/ssa.go generates an ARESUMEPOINT just // before the deferreturn call. The "PC" of // the deferreturn call is stored in the // R_ADDR relocation on the ARESUMEPOINT. lastWasmAddr = uint32(r.Add()) } if r.Type().IsDirectCall() && (r.Sym() == deferReturnSym || 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.ARM, sys.ARM64, sys.Loong64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64: // no change 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 genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, nameOffsets map[loader.Sym]uint32) loader.Sym { ldr := ctxt.loader 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) ldr.SetSymAlign(its, 4) // it has 32-bit fields ninl := fi.NumInlTree() for i := 0; i < int(ninl); i++ { call := fi.InlTree(i) nameOff, ok := nameOffsets[call.Func] if !ok { panic("couldn't find function name offset") } inlFunc := ldr.FuncInfo(call.Func) var funcID objabi.FuncID startLine := int32(0) if inlFunc.Valid() { funcID = inlFunc.FuncID() startLine = inlFunc.StartLine() } else if !ctxt.linkShared { // Inlined functions are always Go functions, and thus // must have FuncInfo. // // Unfortunately, with -linkshared, the inlined // function may be external symbols (from another // shared library), and we don't load FuncInfo from the // shared library. We will report potentially incorrect // FuncID in this case. See https://go.dev/issue/55954. panic(fmt.Sprintf("inlined function %s missing func info", ldr.SymName(call.Func))) } // Construct runtime.inlinedCall value. const size = 16 inlTreeSym.SetUint8(arch, int64(i*size+0), uint8(funcID)) // Bytes 1-3 are unused. inlTreeSym.SetUint32(arch, int64(i*size+4), uint32(nameOff)) inlTreeSym.SetUint32(arch, int64(i*size+8), uint32(call.ParentPC)) inlTreeSym.SetUint32(arch, int64(i*size+12), uint32(startLine)) } return its } // makeInlSyms returns a map of loader.Sym that are created inlSyms. func makeInlSyms(ctxt *Link, funcs []loader.Sym, nameOffsets map[loader.Sym]uint32) map[loader.Sym]loader.Sym { ldr := ctxt.loader // Create the inline symbols we need. inlSyms := make(map[loader.Sym]loader.Sym) for _, s := range funcs { if fi := ldr.FuncInfo(s); fi.Valid() { fi.Preload() if fi.NumInlTree() > 0 { inlSyms[s] = genInlTreeSym(ctxt, ldr.SymUnit(s), fi, ctxt.Arch, nameOffsets) } } } return inlSyms } // generatePCHeader creates the runtime.pcheader symbol, setting it up as a // generator to fill in its data later. func (state *pclntab) generatePCHeader(ctxt *Link) { ldr := ctxt.loader textStartOff := int64(8 + 2*ctxt.Arch.PtrSize) size := int64(8 + 8*ctxt.Arch.PtrSize) writeHeader := func(ctxt *Link, s loader.Sym) { 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 and package debug/gosym. header.SetUint32(ctxt.Arch, 0, 0xfffffff1) 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 = header.SetUint(ctxt.Arch, off, uint64(state.nfiles)) if off != textStartOff { panic(fmt.Sprintf("pcHeader textStartOff: %d != %d", off, textStartOff)) } off += int64(ctxt.Arch.PtrSize) // skip runtimeText relocation 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) if off != size { panic(fmt.Sprintf("pcHeader size: %d != %d", off, size)) } } state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader) // Create the runtimeText relocation. sb := ldr.MakeSymbolUpdater(state.pcheader) sb.SetAddr(ctxt.Arch, textStartOff, ldr.Lookup("runtime.text", 0)) } // walkFuncs iterates over the funcs, calling a function for each unique // function and inlined function. func walkFuncs(ctxt *Link, funcs []loader.Sym, f func(loader.Sym)) { ldr := ctxt.loader seen := make(map[loader.Sym]struct{}) for _, s := range funcs { 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. Returns a map of // func symbol to the name offset in runtime.funcnamtab. func (state *pclntab) generateFuncnametab(ctxt *Link, funcs []loader.Sym) map[loader.Sym]uint32 { nameOffsets := make(map[loader.Sym]uint32, state.nfunc) // The name used by the runtime is the concatenation of the 3 returned strings. // For regular functions, only one returned string is nonempty. // For generic functions, we use three parts so that we can print everything // within the outermost "[]" as "...". nameParts := func(name string) (string, string, string) { i := strings.IndexByte(name, '[') if i < 0 { return name, "", "" } // TODO: use LastIndexByte once the bootstrap compiler is >= Go 1.5. j := len(name) - 1 for j > i && name[j] != ']' { j-- } if j <= i { return name, "", "" } return name[:i], "[...]", name[j+1:] } // Write the null terminated strings. writeFuncNameTab := func(ctxt *Link, s loader.Sym) { symtab := ctxt.loader.MakeSymbolUpdater(s) for s, off := range nameOffsets { a, b, c := nameParts(ctxt.loader.SymName(s)) o := int64(off) o = symtab.AddStringAt(o, a) o = symtab.AddStringAt(o, b) _ = symtab.AddCStringAt(o, c) } } // Loop through the CUs, and calculate the size needed. var size int64 walkFuncs(ctxt, funcs, func(s loader.Sym) { nameOffsets[s] = uint32(size) a, b, c := nameParts(ctxt.loader.SymName(s)) size += int64(len(a) + len(b) + len(c) + 1) // NULL terminate }) state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab) return nameOffsets } // walkFilenames walks funcs, calling a function for each filename used in each // function's line table. func walkFilenames(ctxt *Link, funcs []loader.Sym, f func(*sym.CompilationUnit, goobj.CUFileIndex)) { ldr := ctxt.loader // Loop through all functions, finding the filenames we need. for _, s := range funcs { fi := ldr.FuncInfo(s) if !fi.Valid() { continue } fi.Preload() cu := ldr.SymUnit(s) for i, nf := 0, int(fi.NumFile()); i < nf; i++ { f(cu, fi.File(i)) } for i, ninl := 0, int(fi.NumInlTree()); i < ninl; i++ { call := fi.InlTree(i) f(cu, call.File) } } } // generateFilenameTabs creates LUTs needed for filename lookup. Returns a slice // of the index at which each CU begins in runtime.cutab. // // Function objects keep track of the files they reference to print the stack. // This function creates a per-CU list of filenames if CU[M] references // files[1-N], the following is generated: // // runtime.cutab: // CU[M] // offsetToFilename[0] // offsetToFilename[1] // .. // // runtime.filetab // filename[0] // filename[1] // // Looking up a filename then becomes: // 0. Given a func, and filename index [K] // 1. Get Func.CUIndex: M := func.cuOffset // 2. Find filename offset: fileOffset := runtime.cutab[M+K] // 3. Get the filename: getcstring(runtime.filetab[fileOffset]) func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, funcs []loader.Sym) []uint32 { // On a per-CU basis, keep track of all the filenames we need. // // Note, that we store the filenames in a separate section in the object // files, and deduplicate based on the actual value. It would be better to // store the filenames as symbols, using content addressable symbols (and // then not loading extra filenames), and just use the hash value of the // symbol name to do this cataloging. // // TODO: Store filenames as symbols. (Note this would be easiest if you // also move strings to ALWAYS using the larger content addressable hash // function, and use that hash value for uniqueness testing.) cuEntries := make([]goobj.CUFileIndex, len(compUnits)) fileOffsets := make(map[string]uint32) // Walk the filenames. // We store the total filename string length we need to load, and the max // file index we've seen per CU so we can calculate how large the // CU->global table needs to be. var fileSize int64 walkFilenames(ctxt, funcs, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) { // Note we use the raw filename for lookup, but use the expanded filename // when we save the size. filename := cu.FileTable[i] if _, ok := fileOffsets[filename]; !ok { fileOffsets[filename] = uint32(fileSize) fileSize += int64(len(expandFile(filename)) + 1) // NULL terminate } // Find the maximum file index we've seen. if cuEntries[cu.PclnIndex] < i+1 { cuEntries[cu.PclnIndex] = i + 1 // Store max + 1 } }) // Calculate the size of the runtime.cutab variable. var totalEntries uint32 cuOffsets := make([]uint32, len(cuEntries)) for i, entries := range cuEntries { // Note, cutab is a slice of uint32, so an offset to a cu's entry is just the // running total of all cu indices we've needed to store so far, not the // number of bytes we've stored so far. cuOffsets[i] = totalEntries totalEntries += uint32(entries) } // Write cutab. writeCutab := func(ctxt *Link, s loader.Sym) { sb := ctxt.loader.MakeSymbolUpdater(s) var off int64 for i, max := range cuEntries { // Write the per CU LUT. cu := compUnits[i] for j := goobj.CUFileIndex(0); j < max; j++ { fileOffset, ok := fileOffsets[cu.FileTable[j]] if !ok { // We're looping through all possible file indices. It's possible a file's // been deadcode eliminated, and although it's a valid file in the CU, it's // not needed in this binary. When that happens, use an invalid offset. fileOffset = ^uint32(0) } off = sb.SetUint32(ctxt.Arch, off, fileOffset) } } } state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), writeCutab) // Write filetab. writeFiletab := func(ctxt *Link, s loader.Sym) { sb := ctxt.loader.MakeSymbolUpdater(s) // Write the strings. for filename, loc := range fileOffsets { sb.AddStringAt(int64(loc), expandFile(filename)) } } state.nfiles = uint32(len(fileOffsets)) state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, writeFiletab) return cuOffsets } // generatePctab creates the runtime.pctab variable, holding all the // deduplicated pcdata. func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) { 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{}{} } } var pcsp, pcline, pcfile, pcinline loader.Sym var pcdata []loader.Sym for _, s := range funcs { fi := ldr.FuncInfo(s) if !fi.Valid() { continue } fi.Preload() pcsp, pcfile, pcline, pcinline, pcdata = ldr.PcdataAuxs(s, pcdata) pcSyms := []loader.Sym{pcsp, pcfile, pcline} for _, pcSym := range pcSyms { saveOffset(pcSym) } for _, pcSym := range pcdata { saveOffset(pcSym) } if fi.NumInlTree() > 0 { saveOffset(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) } // numPCData returns the number of PCData syms for the FuncInfo. // NB: Preload must be called on valid FuncInfos before calling this function. func numPCData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo) uint32 { if !fi.Valid() { return 0 } numPCData := uint32(ldr.NumPcdata(s)) if fi.NumInlTree() > 0 { if numPCData < objabi.PCDATA_InlTreeIndex+1 { numPCData = objabi.PCDATA_InlTreeIndex + 1 } } return numPCData } // generateFunctab creates the runtime.functab // // runtime.functab contains two things: // // - pc->func look up table. // - array of func objects, interleaved with pcdata and funcdata 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) writePcln := func(ctxt *Link, s loader.Sym) { ldr := ctxt.loader sb := ldr.MakeSymbolUpdater(s) // Write the data. writePCToFunc(ctxt, sb, funcs, startLocations) writeFuncs(ctxt, sb, funcs, inlSyms, startLocations, cuOffsets, nameOffsets) } state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, writePcln) } // funcData returns the funcdata and offsets for the FuncInfo. // The funcdata are written into runtime.functab after each func // object. This is a helper function to make querying the FuncInfo object // cleaner. // // NB: Preload must be called on the FuncInfo before calling. // NB: fdSyms is used as scratch space. func funcData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo, inlSym loader.Sym, fdSyms []loader.Sym) []loader.Sym { fdSyms = fdSyms[:0] if fi.Valid() { fdSyms = ldr.Funcdata(s, fdSyms) if fi.NumInlTree() > 0 { if len(fdSyms) < objabi.FUNCDATA_InlTree+1 { fdSyms = append(fdSyms, make([]loader.Sym, objabi.FUNCDATA_InlTree+1-len(fdSyms))...) } fdSyms[objabi.FUNCDATA_InlTree] = inlSym } } return fdSyms } // calculateFunctabSize calculates the size of the pclntab, and the offsets in // the output buffer for individual func entries. func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64, []uint32) { ldr := ctxt.loader startLocations := make([]uint32, len(funcs)) // 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*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. for i, s := range funcs { size = Rnd(size, int64(ctxt.Arch.PtrSize)) startLocations[i] = uint32(size) fi := ldr.FuncInfo(s) size += funcSize if fi.Valid() { fi.Preload() numFuncData := ldr.NumFuncdata(s) if fi.NumInlTree() > 0 { if numFuncData < objabi.FUNCDATA_InlTree+1 { numFuncData = objabi.FUNCDATA_InlTree + 1 } } size += int64(numPCData(ldr, s, fi) * 4) size += int64(numFuncData * 4) } } return size, startLocations } // writePCToFunc writes the PC->func lookup table. 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) } for i, s := range funcs { sb.SetUint32(ctxt.Arch, int64(i*2*4), pcOff(s)) sb.SetUint32(ctxt.Arch, int64((i*2+1)*4), startLocations[i]) } // Final entry of table is just end pc offset. lastFunc := funcs[len(funcs)-1] sb.SetUint32(ctxt.Arch, int64(len(funcs))*2*4, pcOff(lastFunc)+uint32(ldr.SymSize(lastFunc))) } // writeFuncs writes the func structures and pcdata to runtime.functab. func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) { ldr := ctxt.loader deferReturnSym := ldr.Lookup("runtime.deferreturn", abiInternalVer) gofunc := ldr.Lookup("go:func.*", 0) gofuncBase := ldr.SymValue(gofunc) textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0)) funcdata := []loader.Sym{} var pcsp, pcfile, pcline, pcinline loader.Sym var pcdata []loader.Sym // Write the individual func objects. for i, s := range funcs { startLine := int32(0) fi := ldr.FuncInfo(s) if fi.Valid() { fi.Preload() pcsp, pcfile, pcline, pcinline, pcdata = ldr.PcdataAuxs(s, pcdata) startLine = fi.StartLine() } off := int64(startLocations[i]) // entryOff uint32 (offset of func entry PC from textStart) entryOff := ldr.SymValue(s) - textStart if entryOff < 0 { panic(fmt.Sprintf("expected func %s(%x) to be placed before or at textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart)) } off = sb.SetUint32(ctxt.Arch, off, uint32(entryOff)) // nameOff int32 nameOff, ok := nameOffsets[s] if !ok { panic("couldn't find function name offset") } off = sb.SetUint32(ctxt.Arch, off, uint32(nameOff)) // args int32 // TODO: Move into funcinfo. args := uint32(0) if fi.Valid() { args = uint32(fi.Args()) } off = sb.SetUint32(ctxt.Arch, off, args) // deferreturn deferreturn := computeDeferReturn(ctxt, deferReturnSym, s) off = sb.SetUint32(ctxt.Arch, off, deferreturn) // pcdata if fi.Valid() { off = sb.SetUint32(ctxt.Arch, off, uint32(ldr.SymValue(pcsp))) off = sb.SetUint32(ctxt.Arch, off, uint32(ldr.SymValue(pcfile))) off = sb.SetUint32(ctxt.Arch, off, uint32(ldr.SymValue(pcline))) } else { off += 12 } off = sb.SetUint32(ctxt.Arch, off, uint32(numPCData(ldr, s, fi))) // Store the offset to compilation unit's file table. cuIdx := ^uint32(0) if cu := ldr.SymUnit(s); cu != nil { cuIdx = cuOffsets[cu.PclnIndex] } off = sb.SetUint32(ctxt.Arch, off, cuIdx) // startLine int32 off = sb.SetUint32(ctxt.Arch, off, uint32(startLine)) // funcID uint8 var funcID objabi.FuncID if fi.Valid() { funcID = fi.FuncID() } off = sb.SetUint8(ctxt.Arch, off, uint8(funcID)) // flag uint8 var flag objabi.FuncFlag if fi.Valid() { flag = fi.FuncFlag() } off = sb.SetUint8(ctxt.Arch, off, uint8(flag)) off += 1 // pad // nfuncdata must be the final entry. funcdata = funcData(ldr, s, fi, 0, funcdata) off = sb.SetUint8(ctxt.Arch, off, uint8(len(funcdata))) // Output the pcdata. if fi.Valid() { for j, pcSym := range pcdata { sb.SetUint32(ctxt.Arch, off+int64(j*4), uint32(ldr.SymValue(pcSym))) } if fi.NumInlTree() > 0 { sb.SetUint32(ctxt.Arch, off+objabi.PCDATA_InlTreeIndex*4, uint32(ldr.SymValue(pcinline))) } } // Write funcdata refs as offsets from go:func.* and go:funcrel.*. funcdata = funcData(ldr, s, fi, inlSyms[s], funcdata) // Missing funcdata will be ^0. See runtime/symtab.go:funcdata. off = int64(startLocations[i] + funcSize + numPCData(ldr, s, fi)*4) for j := range funcdata { dataoff := off + int64(4*j) fdsym := funcdata[j] if fdsym == 0 { sb.SetUint32(ctxt.Arch, dataoff, ^uint32(0)) // ^0 is a sentinel for "no value" continue } if outer := ldr.OuterSym(fdsym); outer != gofunc { panic(fmt.Sprintf("bad carrier sym for symbol %s (funcdata %s#%d), want go:func.* got %s", ldr.SymName(fdsym), ldr.SymName(s), j, ldr.SymName(outer))) } sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym)-gofuncBase)) } } } // 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 August 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.cutab // for i=0..#CUs // for j=0..#max used file index in CU[i] // uint32 offset into runtime.filetab for the filename[j] // // runtime.filetab // []null terminated filename strings // // runtime.pctab // []byte of deduplicated pc data. // // runtime.functab // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] // func structures, pcdata offsets, func data. state, compUnits, funcs := 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) setCarrierSym(sym.SPCLNTAB, state.carrier) state.generatePCHeader(ctxt) nameOffsets := state.generateFuncnametab(ctxt, funcs) cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs) state.generatePctab(ctxt, funcs) inlSyms := makeInlSyms(ctxt, funcs, nameOffsets) state.generateFunctab(ctxt, funcs, inlSyms, cuOffsets, nameOffsets) return state } func gorootFinal() string { root := buildcfg.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] == '\\') { if final := gorootFinal(); final != "" { return filepath.ToSlash(filepath.Join(final, 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 }