/* gopclntab */
sect = state.allocateNamedSectionAndAssignSyms(seg, genrelrosecname(".gopclntab"), sym.SPCLNTAB, sym.SRODATA, relroSecPerm)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab", 0), sect)
+ ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pcheader", 0), sect)
+ ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect)
// 6g uses 4-byte relocation offsets, so the entire segment must fit in 32 bits.
ctxt.xdefine("runtime.symtab", sym.SRODATA, int64(symtab.Vaddr))
ctxt.xdefine("runtime.esymtab", sym.SRODATA, int64(symtab.Vaddr+symtab.Length))
ctxt.xdefine("runtime.pclntab", sym.SRODATA, int64(pclntab.Vaddr))
+ pcvar := ctxt.xdefine("runtime.pcheader", sym.SRODATA, int64(pclntab.Vaddr))
+ ctxt.xdefine("runtime.pclntab_old", sym.SRODATA, int64(pclntab.Vaddr)+ldr.SymSize(pcvar))
ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length))
ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr))
ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr+noptr.Length))
return its
}
+// generatePCHeader creates the runtime.pcheader symbol, setting it up as a
+// generator to fill in its data later.
+func generatePCHeader(ctxt *Link, carrier *loader.SymbolBuilder, pclntabSym loader.Sym) {
+ ldr := ctxt.loader
+ writeHeader := func(ctxt *Link, s loader.Sym) {
+ ldr := ctxt.loader
+ header := ctxt.loader.MakeSymbolUpdater(s)
+
+ // Check symbol order.
+ diff := ldr.SymValue(pclntabSym) - ldr.SymValue(s)
+ if diff <= 0 {
+ panic(fmt.Sprintf("expected runtime.pcheader(%x) to be placed before runtime.pclntab(%x)", ldr.SymValue(s), ldr.SymValue(pclntabSym)))
+ }
+
+ // 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(pclntabNfunc))
+ header.SetUintptr(ctxt.Arch, off, uintptr(diff))
+ }
+
+ size := int64(8 + 2*ctxt.Arch.PtrSize)
+ s := ctxt.createGeneratorSymbol("runtime.pcheader", 0, sym.SPCLNTAB, size, writeHeader)
+ ldr.SetAttrReachable(s, true)
+ ldr.SetCarrierSym(s, carrier.Sym())
+}
+
// pclntab initializes the pclntab symbol with
// runtime function and file name information.
// These variables are used to initialize runtime.firstmoduledata, see symtab.go:symtab.
var pclntabNfunc int32
var pclntabFiletabOffset int32
-var pclntabPclntabOffset int32
var pclntabFirstFunc loader.Sym
var pclntabLastFunc loader.Sym
// symbols, e.g. the set of all symbols X such that Outer(S) = X for
// some other text symbol S.
func (ctxt *Link) pclntab() loader.Bitmap {
- funcdataBytes := int64(0)
+ // 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.pclntab_old from beginning of runtime.pcheader
+ //
+ // 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, function names, pcdata tables.
+ // filetable
+
ldr := ctxt.loader
- ftabsym := ldr.LookupOrCreateSym("runtime.pclntab", 0)
- ftab := ldr.MakeSymbolUpdater(ftabsym)
+ carrier := ldr.CreateSymForUpdate("runtime.pclntab", 0)
+ carrier.SetType(sym.SPCLNTAB)
+ carrier.SetReachable(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
+ // ration form.
+ pclntabSym := ldr.LookupOrCreateSym("runtime.pclntab_old", 0)
+ generatePCHeader(ctxt, carrier, pclntabSym)
+
+ funcdataBytes := int64(0)
+ ldr.SetCarrierSym(pclntabSym, carrier.Sym())
+ ftab := ldr.MakeSymbolUpdater(pclntabSym)
ftab.SetType(sym.SPCLNTAB)
- ldr.SetAttrReachable(ftabsym, true)
+ ftab.SetReachable(true)
state := makepclnState(ctxt)
- // See golang.org/s/go12symtab for the format. Briefly:
- // 8-byte header
- // nfunc [thearch.ptrsize bytes]
- // 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]
-
// Find container symbols and mark them as such.
for _, s := range ctxt.Textp {
outer := ldr.OuterSym(s)
}
pclntabNfunc = nfunc
- ftab.Grow(8 + int64(ctxt.Arch.PtrSize) + int64(nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4)
- ftab.SetUint32(ctxt.Arch, 0, 0xfffffffb)
- ftab.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC))
- ftab.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize))
- ftab.SetUint(ctxt.Arch, 8, uint64(nfunc))
- pclntabPclntabOffset = int32(8 + ctxt.Arch.PtrSize)
+ ftab.Grow(int64(nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4)
szHint := len(ctxt.Textp) * 2
funcnameoff := make(map[string]int32, szHint)
// invalid funcoff value to mark the hole. See also
// runtime/symtab.go:findfunc
prevFuncSize := int64(ldr.SymSize(prevFunc))
- setAddr(ftab, ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), prevFunc, prevFuncSize)
- ftab.SetUint(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), ^uint64(0))
+ 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
funcstart := int32(dSize)
funcstart += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1) // align to ptrsize
- setAddr(ftab, ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), s, 0)
- ftab.SetUint(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint64(funcstart))
+ 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.
last := ctxt.Textp[len(ctxt.Textp)-1]
pclntabLastFunc = last
// Final entry of table is just end pc.
- setAddr(ftab, ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize), last, ldr.SymSize(last))
+ setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), last, ldr.SymSize(last))
// Start file table.
dSize := len(ftab.Data())
start := int32(dSize)
start += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1)
pclntabFiletabOffset = start
- ftab.SetUint32(ctxt.Arch, 8+int64(ctxt.Arch.PtrSize)+int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint32(start))
+ ftab.SetUint32(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint32(start))
nf := len(state.numberedFiles)
ftab.Grow(int64(start) + int64((nf+1)*4))
// the definition of moduledata in runtime/symtab.go.
// This code uses several global variables that are set by pcln.go:pclntab.
moduledata := ldr.MakeSymbolUpdater(ctxt.Moduledata)
- pclntab := ldr.Lookup("runtime.pclntab", 0)
+ // The pcHeader
+ moduledata.AddAddr(ctxt.Arch, ldr.Lookup("runtime.pcheader", 0))
// The pclntab slice
+ pclntab := ldr.Lookup("runtime.pclntab_old", 0)
moduledata.AddAddr(ctxt.Arch, pclntab)
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pclntab)))
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pclntab)))
// The ftab slice
- moduledata.AddAddrPlus(ctxt.Arch, pclntab, int64(pclntabPclntabOffset))
+ moduledata.AddAddr(ctxt.Arch, pclntab)
moduledata.AddUint(ctxt.Arch, uint64(pclntabNfunc+1))
moduledata.AddUint(ctxt.Arch, uint64(pclntabNfunc+1))
// The filetab slice
return sb.setUintXX(arch, r, v, int64(arch.PtrSize))
}
+func (sb *SymbolBuilder) SetUintptr(arch *sys.Arch, r int64, v uintptr) int64 {
+ return sb.setUintXX(arch, r, uint64(v), int64(arch.PtrSize))
+}
+
func (sb *SymbolBuilder) SetAddrPlus(arch *sys.Arch, off int64, tgt Sym, add int64) int64 {
if sb.Type() == 0 {
sb.SetType(sym.SDATA)
"sync"
)
+// version of the pclntab
+type version int
+
+const (
+ verUnknown version = iota
+ ver11
+ ver12
+ ver116
+)
+
// A LineTable is a data structure mapping program counters to line numbers.
//
// In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable,
PC uint64
Line int
- // Go 1.2 state
- mu sync.Mutex
- go12 int // is this in Go 1.2 format? -1 no, 0 unknown, 1 yes
+ // This mutex is used to keep parsing of pclntab synchronous.
+ mu sync.Mutex
+
+ // Contains the version of the pclntab section.
+ version version
+
+ // Go 1.2/1.16 state
binary binary.ByteOrder
quantum uint32
ptrsize uint32
+ funcdata []byte
functab []byte
nfunctab uint32
filetab []byte
// isGo12 reports whether this is a Go 1.2 (or later) symbol table.
func (t *LineTable) isGo12() bool {
- t.go12Init()
- return t.go12 == 1
+ t.parsePclnTab()
+ return t.version >= ver12
}
const go12magic = 0xfffffffb
+const go116magic = 0xfffffffa
// uintptr returns the pointer-sized value encoded at b.
// The pointer size is dictated by the table being read.
return t.binary.Uint64(b)
}
-// go12init initializes the Go 1.2 metadata if t is a Go 1.2 symbol table.
-func (t *LineTable) go12Init() {
+// parsePclnTab parses the pclntab, setting the version.
+func (t *LineTable) parsePclnTab() {
t.mu.Lock()
defer t.mu.Unlock()
- if t.go12 != 0 {
+ if t.version != verUnknown {
return
}
+ // Note that during this function, setting the version is the last thing we do.
+ // If we set the version too early, and parsing failed (likely as a panic on
+ // slice lookups), we'd have a mistaken version.
+ //
+ // Error paths through this code will default the version to 1.1.
+ t.version = ver11
+
defer func() {
- // If we panic parsing, assume it's not a Go 1.2 symbol table.
+ // If we panic parsing, assume it's a Go 1.1 pclntab.
recover()
}()
// Check header: 4-byte magic, two zeros, pc quantum, pointer size.
- t.go12 = -1 // not Go 1.2 until proven otherwise
if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 ||
(t.Data[6] != 1 && t.Data[6] != 2 && t.Data[6] != 4) || // pc quantum
(t.Data[7] != 4 && t.Data[7] != 8) { // pointer size
return
}
- switch uint32(go12magic) {
- case binary.LittleEndian.Uint32(t.Data):
- t.binary = binary.LittleEndian
- case binary.BigEndian.Uint32(t.Data):
- t.binary = binary.BigEndian
+ var possibleVersion version
+ leMagic := binary.LittleEndian.Uint32(t.Data)
+ beMagic := binary.BigEndian.Uint32(t.Data)
+ switch {
+ case leMagic == go12magic:
+ t.binary, possibleVersion = binary.LittleEndian, ver12
+ case beMagic == go12magic:
+ t.binary, possibleVersion = binary.BigEndian, ver12
+ case leMagic == go116magic:
+ t.binary, possibleVersion = binary.LittleEndian, ver116
+ case beMagic == go116magic:
+ t.binary, possibleVersion = binary.BigEndian, ver116
default:
return
}
+ // quantum and ptrSize are the same between 1.2 and 1.16
t.quantum = uint32(t.Data[6])
t.ptrsize = uint32(t.Data[7])
- t.nfunctab = uint32(t.uintptr(t.Data[8:]))
- t.functab = t.Data[8+t.ptrsize:]
- functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
- fileoff := t.binary.Uint32(t.functab[functabsize:])
- t.functab = t.functab[:functabsize]
- t.filetab = t.Data[fileoff:]
- t.nfiletab = t.binary.Uint32(t.filetab)
- t.filetab = t.filetab[:t.nfiletab*4]
-
- t.go12 = 1 // so far so good
+ switch possibleVersion {
+ case ver116:
+ t.nfunctab = uint32(t.uintptr(t.Data[8:]))
+ offset := t.uintptr(t.Data[8+t.ptrsize:])
+ t.funcdata = t.Data[offset:]
+ t.functab = t.Data[offset:]
+ functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
+ fileoff := t.binary.Uint32(t.functab[functabsize:])
+ t.filetab = t.functab[fileoff:]
+ t.functab = t.functab[:functabsize]
+ t.nfiletab = t.binary.Uint32(t.filetab)
+ t.filetab = t.filetab[:t.nfiletab*4]
+ case ver12:
+ t.nfunctab = uint32(t.uintptr(t.Data[8:]))
+ t.funcdata = t.Data
+ t.functab = t.Data[8+t.ptrsize:]
+ functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
+ fileoff := t.binary.Uint32(t.functab[functabsize:])
+ t.functab = t.functab[:functabsize]
+ t.filetab = t.Data[fileoff:]
+ t.nfiletab = t.binary.Uint32(t.filetab)
+ t.filetab = t.filetab[:t.nfiletab*4]
+ default:
+ panic("unreachable")
+ }
+ t.version = possibleVersion
}
// go12Funcs returns a slice of Funcs derived from the Go 1.2 pcln table.
f := &funcs[i]
f.Entry = t.uintptr(t.functab[2*i*int(t.ptrsize):])
f.End = t.uintptr(t.functab[(2*i+2)*int(t.ptrsize):])
- info := t.Data[t.uintptr(t.functab[(2*i+1)*int(t.ptrsize):]):]
+ info := t.funcdata[t.uintptr(t.functab[(2*i+1)*int(t.ptrsize):]):]
f.LineTable = t
f.FrameSize = int(t.binary.Uint32(info[t.ptrsize+2*4:]))
f.Sym = &Sym{
m := nf / 2
fm := f[2*t.ptrsize*m:]
if t.uintptr(fm) <= pc && pc < t.uintptr(fm[2*t.ptrsize:]) {
- return t.Data[t.uintptr(fm[t.ptrsize:]):]
+ return t.funcdata[t.uintptr(fm[t.ptrsize:]):]
} else if pc < t.uintptr(fm) {
nf = m
} else {
if s, ok := t.strings[off]; ok {
return s
}
- i := bytes.IndexByte(t.Data[off:], 0)
- s := string(t.Data[off : off+uint32(i)])
+ i := bytes.IndexByte(t.funcdata[off:], 0)
+ s := string(t.funcdata[off : off+uint32(i)])
t.strings[off] = s
return s
}
// 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.Data[off:]
+ p := t.funcdata[off:]
val := int32(-1)
pc := entry
return 0
}
- fp := t.Data[filetab:]
- fl := t.Data[linetab:]
+ fp := t.funcdata[filetab:]
+ fl := t.funcdata[linetab:]
fileVal := int32(-1)
filePC := entry
lineVal := int32(-1)
// If this turns out to be a bottleneck, we could build a map[int32][]int32
// mapping file number to a list of functions with code from that file.
for i := uint32(0); i < t.nfunctab; i++ {
- f := t.Data[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):]
+ f := t.funcdata[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):]
entry := t.uintptr(f)
filetab := t.binary.Uint32(f[t.ptrsize+4*4:])
linetab := t.binary.Uint32(f[t.ptrsize+5*4:])
package gosym
import (
+ "bytes"
+ "compress/gzip"
"debug/elf"
"internal/testenv"
"io/ioutil"
off = pc + 1 - text.Addr
}
}
+
+// Test that we can parse a pclntab from 1.15.
+// The file was compiled in /tmp/hello.go:
+// [BEGIN]
+// package main
+//
+// func main() {
+// println("hello")
+// }
+// [END]
+func Test115PclnParsing(t *testing.T) {
+ zippedDat, err := ioutil.ReadFile("testdata/pcln115.gz")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var gzReader *gzip.Reader
+ gzReader, err = gzip.NewReader(bytes.NewBuffer(zippedDat))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var dat []byte
+ dat, err = ioutil.ReadAll(gzReader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ const textStart = 0x1001000
+ pcln := NewLineTable(dat, textStart)
+ var tab *Table
+ tab, err = NewTable(nil, pcln)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var f *Func
+ var pc uint64
+ pc, f, err = tab.LineToPC("/tmp/hello.go", 3)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if pcln.version != ver12 {
+ t.Fatal("Expected pcln to parse as an older version")
+ }
+ if pc != 0x105c280 {
+ t.Fatalf("expect pc = 0x105c280, got 0x%x", pc)
+ }
+ if f.Name != "main.main" {
+ t.Fatalf("expected to parse name as main.main, got %v", f.Name)
+ }
+}
funcID_wrapper // any autogenerated code (hash/eq algorithms, method wrappers, etc.)
)
+// PCHeader holds data used by the pclntab lookups.
+type pcHeader struct {
+ magic uint32 // 0xFFFFFFFA
+ pad1, pad2 uint8 // 0,0
+ minLC uint8 // min instruction size
+ ptrSize uint8 // size of a ptr in bytes
+ nfunc int // number of functions in the module
+ pclnOffset uintptr // offset to the pclntab variable from pcHeader
+}
+
// moduledata records information about the layout of the executable
// image. It is written by the linker. Any changes here must be
// matched changes to the code in cmd/internal/ld/symtab.go:symtab.
// moduledata is stored in statically allocated non-pointer memory;
// none of the pointers here are visible to the garbage collector.
type moduledata struct {
+ pcHeader *pcHeader
pclntable []byte
ftab []functab
filetab []uint32
const debugPcln = false
func moduledataverify1(datap *moduledata) {
- // See golang.org/s/go12symtab for header: 0xfffffffb,
- // two zero bytes, a byte giving the PC quantum,
- // and a byte giving the pointer width in bytes.
- pcln := *(**[8]byte)(unsafe.Pointer(&datap.pclntable))
- pcln32 := *(**[2]uint32)(unsafe.Pointer(&datap.pclntable))
- if pcln32[0] != 0xfffffffb || pcln[4] != 0 || pcln[5] != 0 || pcln[6] != sys.PCQuantum || pcln[7] != sys.PtrSize {
- println("runtime: function symbol table header:", hex(pcln32[0]), hex(pcln[4]), hex(pcln[5]), hex(pcln[6]), hex(pcln[7]))
+ // Check that the pclntab's format is valid.
+ hdr := datap.pcHeader
+ if hdr.magic != 0xfffffffa || hdr.pad1 != 0 || hdr.pad2 != 0 || hdr.minLC != sys.PCQuantum || hdr.ptrSize != sys.PtrSize {
+ println("runtime: function symbol table header:", hex(hdr.magic), hex(hdr.pad1), hex(hdr.pad2), hex(hdr.minLC), hex(hdr.ptrSize))
throw("invalid function symbol table\n")
}