1
0
mirror of https://github.com/golang/go synced 2024-11-18 01:54:45 -07:00

[dev.link] cmd/link: add runtime.pcheader

As of July 2020, a fair amount of the new linker's live memory, and
runtime is spent generating pclntab. In an effort to streamline that
code, this change starts breaking up the generation of runtime.pclntab
into smaller chunks that can run later in a link. These changes are
described in an (as yet not widely distributed) document that lays out
an improved format. Largely the work consists of breaking up
runtime.pclntab into smaller pieces, stopping much of the data
rewriting, and getting runtime.pclntab into a form where we can reason
about its size and look to shrink it. This change is the first part of
that work -- just pulling out the header, and demonstrating where a
majority of that work will be.

Change-Id: I65618d0d0c780f7e5977c9df4abdbd1696fedfcb
Reviewed-on: https://go-review.googlesource.com/c/go/+/241598
Run-TryBot: Jeremy Faller <jeremy@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
Jeremy Faller 2020-06-24 14:30:16 -04:00
parent b3e3c339ff
commit ba9c639470
8 changed files with 227 additions and 66 deletions

View File

@ -1921,6 +1921,8 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
/* 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.
@ -2477,6 +2479,8 @@ func (ctxt *Link) address() []*sym.Segment {
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))

View File

@ -227,13 +227,41 @@ func (state *pclnState) genInlTreeSym(fi loader.FuncInfo, arch *sys.Arch) loader
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
@ -242,21 +270,45 @@ 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)
ldr := ctxt.loader
ftabsym := ldr.LookupOrCreateSym("runtime.pclntab", 0)
ftab := ldr.MakeSymbolUpdater(ftabsym)
ftab.SetType(sym.SPCLNTAB)
ldr.SetAttrReachable(ftabsym, true)
state := makepclnState(ctxt)
// See golang.org/s/go12symtab for the format. Briefly:
// 8-byte header
// 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
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)
ftab.SetReachable(true)
state := makepclnState(ctxt)
// Find container symbols and mark them as such.
for _, s := range ctxt.Textp {
@ -290,12 +342,7 @@ func (ctxt *Link) pclntab() loader.Bitmap {
}
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)
@ -360,8 +407,8 @@ func (ctxt *Link) pclntab() loader.Bitmap {
// 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
@ -410,8 +457,8 @@ func (ctxt *Link) pclntab() loader.Bitmap {
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.
@ -523,14 +570,14 @@ func (ctxt *Link) pclntab() loader.Bitmap {
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))

View File

@ -607,13 +607,15 @@ func (ctxt *Link) symtab() []sym.SymKind {
// 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

View File

@ -287,6 +287,10 @@ func (sb *SymbolBuilder) SetUint(arch *sys.Arch, r int64, v uint64) int64 {
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)

View File

@ -14,6 +14,16 @@ import (
"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,
@ -32,12 +42,17 @@ type LineTable struct {
PC uint64
Line int
// Go 1.2 state
// This mutex is used to keep parsing of pclntab synchronous.
mu sync.Mutex
go12 int // is this in Go 1.2 format? -1 no, 0 unknown, 1 yes
// 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
@ -140,11 +155,12 @@ func NewLineTable(data []byte, text uint64) *LineTable {
// 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.
@ -155,40 +171,68 @@ func (t *LineTable) uintptr(b []byte) uint64 {
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])
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:])
@ -196,8 +240,10 @@ func (t *LineTable) go12Init() {
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
default:
panic("unreachable")
}
t.version = possibleVersion
}
// go12Funcs returns a slice of Funcs derived from the Go 1.2 pcln table.
@ -213,7 +259,7 @@ func (t *LineTable) go12Funcs() []Func {
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{
@ -241,7 +287,7 @@ func (t *LineTable) findFunc(pc uint64) []byte {
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 {
@ -273,8 +319,8 @@ func (t *LineTable) string(off uint32) string {
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
}
@ -301,7 +347,7 @@ func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
// 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
@ -324,8 +370,8 @@ func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum,
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)
@ -412,7 +458,7 @@ func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
// 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:])

View File

@ -5,6 +5,8 @@
package gosym
import (
"bytes"
"compress/gzip"
"debug/elf"
"internal/testenv"
"io/ioutil"
@ -264,3 +266,51 @@ func TestPCLine(t *testing.T) {
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)
}
}

BIN
src/debug/gosym/testdata/pcln115.gz vendored Normal file

Binary file not shown.

View File

@ -334,12 +334,23 @@ const (
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
@ -514,13 +525,10 @@ func moduledataverify() {
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")
}