| // 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 ( |
| "cmd/internal/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: "runtime.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 |
| } |