// Copyright 2014 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. // Generation of runtime function information (pclntab). package main import ( "debug/goobj" "encoding/binary" "os" "sort" ) var zerofunc goobj.Func // pclntab collects the runtime function data for each function that will // be listed in the binary and builds a single table describing all functions. // This table is used at run time for stack traces and to look up PC-specific // information during garbage collection. The symbol created is named // "pclntab" for historical reasons; the scope of the table has grown to // include more than just PC/line number correspondences. // The table format is documented at http://golang.org/s/go12symtab. func (p *Prog) pclntab() { // Count number of functions going into the binary, // so that we can size the initial index correctly. nfunc := 0 for _, sym := range p.SymOrder { if sym.Kind != goobj.STEXT { continue } nfunc++ } // Table header. buf := new(SymBuffer) buf.Init(p) buf.SetSize(8 + p.ptrsize) off := 0 off = buf.Uint32(off, 0xfffffffb) off = buf.Uint8(off, 0) off = buf.Uint8(off, 0) off = buf.Uint8(off, uint8(p.pcquantum)) off = buf.Uint8(off, uint8(p.ptrsize)) off = buf.Uint(off, uint64(nfunc), p.ptrsize) indexOff := off off += (nfunc*2 + 1) * p.ptrsize // function index, to be filled in off += 4 // file table start offset, to be filled in buf.SetSize(off) // One-file cache for reading PCData tables from package files. // TODO(rsc): Better I/O strategy. var ( file *os.File fname string ) // Files gives the file numbering for source file names recorded // in the binary. files := make(map[string]int) // Build the table, build the index, and build the file name numbering. // The loop here must visit functions in the same order that they will // be stored in the binary, or else binary search over the index will fail. // The runtime checks that the index is sorted properly at program start time. var lastSym *Sym for _, sym := range p.SymOrder { if sym.Kind != goobj.STEXT { continue } lastSym = sym // Treat no recorded function information same as all zeros. f := sym.Func if f == nil { f = &zerofunc } // Open package file if needed, for reading PC data. if fname != sym.Package.File { if file != nil { file.Close() } var err error file, err = os.Open(sym.Package.File) if err != nil { p.errorf("%v: %v", sym, err) return } fname = sym.Package.File } // off is the offset of the table entry where we're going to write // the encoded form of Func. // indexOff is the current position in the table index; // we add an entry in the index pointing at off. off = (buf.Size() + p.ptrsize - 1) &^ (p.ptrsize - 1) indexOff = buf.Addr(indexOff, sym.SymID, 0) indexOff = buf.Uint(indexOff, uint64(off), p.ptrsize) // The Func encoding starts with a header giving offsets // to data blobs, and then the data blobs themselves. // end gives the current write position for the data blobs. end := off + p.ptrsize + 3*4 + 5*4 + len(f.PCData)*4 + len(f.FuncData)*p.ptrsize if len(f.FuncData) > 0 { end += -end & (p.ptrsize - 1) } buf.SetSize(end) // entry uintptr // name int32 // args int32 // frame int32 // // The frame recorded in the object file is // the frame size used in an assembly listing, which does // not include the caller PC on the stack. // The frame size we want to list here is the delta from // this function's SP to its caller's SP, which does include // the caller PC. Add p.ptrsize to f.Frame to adjust. // TODO(rsc): Record the same frame size in the object file. off = buf.Addr(off, sym.SymID, 0) off = buf.Uint32(off, uint32(addString(buf, sym.Name))) off = buf.Uint32(off, uint32(f.Args)) off = buf.Uint32(off, uint32(f.Frame+p.ptrsize)) // pcdata off = buf.Uint32(off, uint32(addPCTable(p, buf, file, f.PCSP))) off = buf.Uint32(off, uint32(addPCFileTable(p, buf, file, f.PCFile, sym, files))) off = buf.Uint32(off, uint32(addPCTable(p, buf, file, f.PCLine))) off = buf.Uint32(off, uint32(len(f.PCData))) off = buf.Uint32(off, uint32(len(f.FuncData))) for _, pcdata := range f.PCData { off = buf.Uint32(off, uint32(addPCTable(p, buf, file, pcdata))) } // funcdata if len(f.FuncData) > 0 { off += -off & (p.ptrsize - 1) // must be pointer-aligned for _, funcdata := range f.FuncData { if funcdata.Sym.Name == "" { off = buf.Uint(off, uint64(funcdata.Offset), p.ptrsize) } else { off = buf.Addr(off, funcdata.Sym, funcdata.Offset) } } } if off != end { p.errorf("internal error: invalid math in pclntab: off=%#x end=%#x", off, end) break } } if file != nil { file.Close() } // Final entry of index is end PC of last function. indexOff = buf.Addr(indexOff, lastSym.SymID, int64(lastSym.Size)) // Start file table. // Function index is immediately followed by offset to file table. off = (buf.Size() + p.ptrsize - 1) &^ (p.ptrsize - 1) buf.Uint32(indexOff, uint32(off)) // File table is an array of uint32s. // The first entry gives 1+n, the size of the array. // The following n entries hold offsets to string data. // File number n uses the string pointed at by entry n. // File number 0 is invalid. buf.SetSize(off + (1+len(files))*4) buf.Uint32(off, uint32(1+len(files))) var filestr []string for file := range files { filestr = append(filestr, file) } sort.Strings(filestr) for _, file := range filestr { id := files[file] buf.Uint32(off+4*id, uint32(addString(buf, file))) } pclntab := &Sym{ Sym: &goobj.Sym{ SymID: goobj.SymID{Name: "pclntab"}, Kind: goobj.SPCLNTAB, Size: buf.Size(), Reloc: buf.Reloc(), }, Bytes: buf.Bytes(), } p.addSym(pclntab) } // addString appends the string s to the buffer b. // It returns the offset of the beginning of the string in the buffer. func addString(b *SymBuffer, s string) int { off := b.Size() b.SetSize(off + len(s) + 1) copy(b.data[off:], s) return off } // addPCTable appends the PC-data table stored in the file f at the location loc // to the symbol buffer b. It returns the offset of the beginning of the table // in the buffer. func addPCTable(p *Prog, b *SymBuffer, f *os.File, loc goobj.Data) int { if loc.Size == 0 { return 0 } off := b.Size() b.SetSize(off + int(loc.Size)) _, err := f.ReadAt(b.data[off:off+int(loc.Size)], loc.Offset) if err != nil { p.errorf("%v", err) } return off } // addPCFileTable is like addPCTable, but it renumbers the file names referred to by the table // to use the global numbering maintained in the files map. It adds new files to the // map as necessary. func addPCFileTable(p *Prog, b *SymBuffer, f *os.File, loc goobj.Data, sym *Sym, files map[string]int) int { if loc.Size == 0 { return 0 } off := b.Size() src := make([]byte, loc.Size) _, err := f.ReadAt(src, loc.Offset) if err != nil { p.errorf("%v", err) return 0 } filenum := make([]int, len(sym.Func.File)) for i, name := range sym.Func.File { num := files[name] if num == 0 { num = len(files) + 1 files[name] = num } filenum[i] = num } var dst []byte newval := int32(-1) var it PCIter for it.Init(p, src); !it.Done; it.Next() { // value delta oldval := it.Value val := oldval if oldval != -1 { if oldval < 0 || int(oldval) >= len(filenum) { p.errorf("%s: corrupt pc-file table", sym) break } val = int32(filenum[oldval]) } dv := val - newval newval = val uv := uint32(dv<<1) ^ uint32(dv>>31) dst = appendVarint(dst, uv) // pc delta dst = appendVarint(dst, it.NextPC-it.PC) } if it.Corrupt { p.errorf("%s: corrupt pc-file table", sym) } // terminating value delta dst = appendVarint(dst, 0) b.SetSize(off + len(dst)) copy(b.data[off:], dst) return off } // A SymBuffer is a buffer for preparing the data image of a // linker-generated symbol. type SymBuffer struct { data []byte reloc []goobj.Reloc order binary.ByteOrder ptrsize int } // Init initializes the buffer for writing. func (b *SymBuffer) Init(p *Prog) { b.data = nil b.reloc = nil b.order = p.byteorder b.ptrsize = p.ptrsize } // Bytes returns the buffer data. func (b *SymBuffer) Bytes() []byte { return b.data } // SetSize sets the buffer's data size to n bytes. func (b *SymBuffer) SetSize(n int) { for cap(b.data) < n { b.data = append(b.data[:cap(b.data)], 0) } b.data = b.data[:n] } // Size returns the buffer's data size. func (b *SymBuffer) Size() int { return len(b.data) } // Reloc returns the buffered relocations. func (b *SymBuffer) Reloc() []goobj.Reloc { return b.reloc } // Uint8 sets the uint8 at offset off to v. // It returns the offset just beyond v. func (b *SymBuffer) Uint8(off int, v uint8) int { b.data[off] = v return off + 1 } // Uint16 sets the uint16 at offset off to v. // It returns the offset just beyond v. func (b *SymBuffer) Uint16(off int, v uint16) int { b.order.PutUint16(b.data[off:], v) return off + 2 } // Uint32 sets the uint32 at offset off to v. // It returns the offset just beyond v. func (b *SymBuffer) Uint32(off int, v uint32) int { b.order.PutUint32(b.data[off:], v) return off + 4 } // Uint64 sets the uint64 at offset off to v. // It returns the offset just beyond v. func (b *SymBuffer) Uint64(off int, v uint64) int { b.order.PutUint64(b.data[off:], v) return off + 8 } // Uint sets the size-byte unsigned integer at offset off to v. // It returns the offset just beyond v. func (b *SymBuffer) Uint(off int, v uint64, size int) int { switch size { case 1: return b.Uint8(off, uint8(v)) case 2: return b.Uint16(off, uint16(v)) case 4: return b.Uint32(off, uint32(v)) case 8: return b.Uint64(off, v) } panic("invalid use of SymBuffer.SetUint") } // Addr sets the pointer-sized address at offset off to refer // to symoff bytes past the start of sym. It returns the offset // just beyond the address. func (b *SymBuffer) Addr(off int, sym goobj.SymID, symoff int64) int { b.reloc = append(b.reloc, goobj.Reloc{ Offset: off, Size: b.ptrsize, Sym: sym, Add: int(symoff), Type: R_ADDR, }) return off + b.ptrsize } // A PCIter implements iteration over PC-data tables. // // var it PCIter // for it.Init(p, data); !it.Done; it.Next() { // it.Value holds from it.PC up to (but not including) it.NextPC // } // if it.Corrupt { // data was malformed // } // type PCIter struct { PC uint32 NextPC uint32 Value int32 Done bool Corrupt bool p []byte start bool pcquantum uint32 } // Init initializes the iteration. // On return, if it.Done is true, the iteration is over. // Otherwise it.Value applies in the pc range [it.PC, it.NextPC). func (it *PCIter) Init(p *Prog, buf []byte) { it.p = buf it.PC = 0 it.NextPC = 0 it.Value = -1 it.start = true it.pcquantum = uint32(p.pcquantum) it.Done = false it.Next() } // Next steps forward one entry in the table. // On return, if it.Done is true, the iteration is over. // Otherwise it.Value applies in the pc range [it.PC, it.NextPC). func (it *PCIter) Next() { it.PC = it.NextPC if it.Done { return } if len(it.p) == 0 { it.Done = true return } // value delta uv, p, ok := decodeVarint(it.p) if !ok { it.Done = true it.Corrupt = true return } it.p = p if uv == 0 && !it.start { it.Done = true return } it.start = false sv := int32(uv)>>1 ^ int32(uv)<<31>>31 it.Value += sv // pc delta uv, it.p, ok = decodeVarint(it.p) if !ok { it.Done = true it.Corrupt = true return } it.NextPC = it.PC + uv*it.pcquantum } // decodeVarint decodes an unsigned varint from p, // reporting the value, the remainder of the data, and // whether the decoding was successful. func decodeVarint(p []byte) (v uint32, rest []byte, ok bool) { for shift := uint(0); ; shift += 7 { if len(p) == 0 { return } c := uint32(p[0]) p = p[1:] v |= (c & 0x7F) << shift if c&0x80 == 0 { break } } return v, p, true } // appendVarint appends an unsigned varint encoding of v to p // and returns the resulting slice. func appendVarint(p []byte, v uint32) []byte { for ; v >= 0x80; v >>= 7 { p = append(p, byte(v)|0x80) } p = append(p, byte(v)) return p }