| // Copyright 2009 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 gosym implements access to the Go symbol |
| // and line number tables embedded in Go binaries generated |
| // by the gc compilers. |
| package gosym |
| |
| // The table format is a variant of the format used in Plan 9's a.out |
| // format, documented at http://plan9.bell-labs.com/magic/man2html/6/a.out. |
| // The best reference for the differences between the Plan 9 format |
| // and the Go format is the runtime source, specifically ../../runtime/symtab.c. |
| |
| import ( |
| "encoding/binary" |
| "fmt" |
| "os" |
| "strconv" |
| "strings" |
| ) |
| |
| /* |
| * Symbols |
| */ |
| |
| // A Sym represents a single symbol table entry. |
| type Sym struct { |
| Value uint64 |
| Type byte |
| Name string |
| GoType uint64 |
| // If this symbol if a function symbol, the corresponding Func |
| Func *Func |
| } |
| |
| // Static returns whether this symbol is static (not visible outside its file). |
| func (s *Sym) Static() bool { return s.Type >= 'a' } |
| |
| // PackageName returns the package part of the symbol name, |
| // or the empty string if there is none. |
| func (s *Sym) PackageName() string { |
| if i := strings.Index(s.Name, "."); i != -1 { |
| return s.Name[0:i] |
| } |
| return "" |
| } |
| |
| // ReceiverName returns the receiver type name of this symbol, |
| // or the empty string if there is none. |
| func (s *Sym) ReceiverName() string { |
| l := strings.Index(s.Name, ".") |
| r := strings.LastIndex(s.Name, ".") |
| if l == -1 || r == -1 || l == r { |
| return "" |
| } |
| return s.Name[l+1 : r] |
| } |
| |
| // BaseName returns the symbol name without the package or receiver name. |
| func (s *Sym) BaseName() string { |
| if i := strings.LastIndex(s.Name, "."); i != -1 { |
| return s.Name[i+1:] |
| } |
| return s.Name |
| } |
| |
| // A Func collects information about a single function. |
| type Func struct { |
| Entry uint64 |
| *Sym |
| End uint64 |
| Params []*Sym |
| Locals []*Sym |
| FrameSize int |
| LineTable *LineTable |
| Obj *Obj |
| } |
| |
| // An Obj represents a single object file. |
| type Obj struct { |
| Funcs []Func |
| Paths []Sym |
| } |
| |
| /* |
| * Symbol tables |
| */ |
| |
| // Table represents a Go symbol table. It stores all of the |
| // symbols decoded from the program and provides methods to translate |
| // between symbols, names, and addresses. |
| type Table struct { |
| Syms []Sym |
| Funcs []Func |
| Files map[string]*Obj |
| Objs []Obj |
| // textEnd uint64; |
| } |
| |
| type sym struct { |
| value uint32 |
| gotype uint32 |
| typ byte |
| name []byte |
| } |
| |
| func walksymtab(data []byte, fn func(sym) os.Error) os.Error { |
| var s sym |
| p := data |
| for len(p) >= 6 { |
| s.value = binary.BigEndian.Uint32(p[0:4]) |
| typ := p[4] |
| if typ&0x80 == 0 { |
| return &DecodingError{len(data) - len(p) + 4, "bad symbol type", typ} |
| } |
| typ &^= 0x80 |
| s.typ = typ |
| p = p[5:] |
| var i int |
| var nnul int |
| for i = 0; i < len(p); i++ { |
| if p[i] == 0 { |
| nnul = 1 |
| break |
| } |
| } |
| switch typ { |
| case 'z', 'Z': |
| p = p[i+nnul:] |
| for i = 0; i+2 <= len(p); i += 2 { |
| if p[i] == 0 && p[i+1] == 0 { |
| nnul = 2 |
| break |
| } |
| } |
| } |
| if i+nnul+4 > len(p) { |
| return &DecodingError{len(data), "unexpected EOF", nil} |
| } |
| s.name = p[0:i] |
| i += nnul |
| s.gotype = binary.BigEndian.Uint32(p[i : i+4]) |
| p = p[i+4:] |
| fn(s) |
| } |
| return nil |
| } |
| |
| // NewTable decodes the Go symbol table in data, |
| // returning an in-memory representation. |
| func NewTable(symtab []byte, pcln *LineTable) (*Table, os.Error) { |
| var n int |
| err := walksymtab(symtab, func(s sym) os.Error { |
| n++ |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| var t Table |
| fname := make(map[uint16]string) |
| t.Syms = make([]Sym, 0, n) |
| nf := 0 |
| nz := 0 |
| lasttyp := uint8(0) |
| err = walksymtab(symtab, func(s sym) os.Error { |
| n := len(t.Syms) |
| t.Syms = t.Syms[0 : n+1] |
| ts := &t.Syms[n] |
| ts.Type = s.typ |
| ts.Value = uint64(s.value) |
| ts.GoType = uint64(s.gotype) |
| switch s.typ { |
| default: |
| // rewrite name to use . instead of ยท (c2 b7) |
| w := 0 |
| b := s.name |
| for i := 0; i < len(b); i++ { |
| if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 { |
| i++ |
| b[i] = '.' |
| } |
| b[w] = b[i] |
| w++ |
| } |
| ts.Name = string(s.name[0:w]) |
| case 'z', 'Z': |
| if lasttyp != 'z' && lasttyp != 'Z' { |
| nz++ |
| } |
| for i := 0; i < len(s.name); i += 2 { |
| eltIdx := binary.BigEndian.Uint16(s.name[i : i+2]) |
| elt, ok := fname[eltIdx] |
| if !ok { |
| return &DecodingError{-1, "bad filename code", eltIdx} |
| } |
| if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' { |
| ts.Name += "/" |
| } |
| ts.Name += elt |
| } |
| } |
| switch s.typ { |
| case 'T', 't', 'L', 'l': |
| nf++ |
| case 'f': |
| fname[uint16(s.value)] = ts.Name |
| } |
| lasttyp = s.typ |
| return nil |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| t.Funcs = make([]Func, 0, nf) |
| t.Objs = make([]Obj, 0, nz) |
| t.Files = make(map[string]*Obj) |
| |
| // Count text symbols and attach frame sizes, parameters, and |
| // locals to them. Also, find object file boundaries. |
| var obj *Obj |
| lastf := 0 |
| for i := 0; i < len(t.Syms); i++ { |
| sym := &t.Syms[i] |
| switch sym.Type { |
| case 'Z', 'z': // path symbol |
| // Finish the current object |
| if obj != nil { |
| obj.Funcs = t.Funcs[lastf:] |
| } |
| lastf = len(t.Funcs) |
| |
| // Start new object |
| n := len(t.Objs) |
| t.Objs = t.Objs[0 : n+1] |
| obj = &t.Objs[n] |
| |
| // Count & copy path symbols |
| var end int |
| for end = i + 1; end < len(t.Syms); end++ { |
| if c := t.Syms[end].Type; c != 'Z' && c != 'z' { |
| break |
| } |
| } |
| obj.Paths = t.Syms[i:end] |
| i = end - 1 // loop will i++ |
| |
| // Record file names |
| depth := 0 |
| for j := range obj.Paths { |
| s := &obj.Paths[j] |
| if s.Name == "" { |
| depth-- |
| } else { |
| if depth == 0 { |
| t.Files[s.Name] = obj |
| } |
| depth++ |
| } |
| } |
| |
| case 'T', 't', 'L', 'l': // text symbol |
| if n := len(t.Funcs); n > 0 { |
| t.Funcs[n-1].End = sym.Value |
| } |
| if sym.Name == "etext" { |
| continue |
| } |
| |
| // Count parameter and local (auto) syms |
| var np, na int |
| var end int |
| countloop: |
| for end = i + 1; end < len(t.Syms); end++ { |
| switch t.Syms[end].Type { |
| case 'T', 't', 'L', 'l', 'Z', 'z': |
| break countloop |
| case 'p': |
| np++ |
| case 'a': |
| na++ |
| } |
| } |
| |
| // Fill in the function symbol |
| n := len(t.Funcs) |
| t.Funcs = t.Funcs[0 : n+1] |
| fn := &t.Funcs[n] |
| sym.Func = fn |
| fn.Params = make([]*Sym, 0, np) |
| fn.Locals = make([]*Sym, 0, na) |
| fn.Sym = sym |
| fn.Entry = sym.Value |
| fn.Obj = obj |
| if pcln != nil { |
| fn.LineTable = pcln.slice(fn.Entry) |
| pcln = fn.LineTable |
| } |
| for j := i; j < end; j++ { |
| s := &t.Syms[j] |
| switch s.Type { |
| case 'm': |
| fn.FrameSize = int(s.Value) |
| case 'p': |
| n := len(fn.Params) |
| fn.Params = fn.Params[0 : n+1] |
| fn.Params[n] = s |
| case 'a': |
| n := len(fn.Locals) |
| fn.Locals = fn.Locals[0 : n+1] |
| fn.Locals[n] = s |
| } |
| } |
| i = end - 1 // loop will i++ |
| } |
| } |
| if obj != nil { |
| obj.Funcs = t.Funcs[lastf:] |
| } |
| return &t, nil |
| } |
| |
| // PCToFunc returns the function containing the program counter pc, |
| // or nil if there is no such function. |
| func (t *Table) PCToFunc(pc uint64) *Func { |
| funcs := t.Funcs |
| for len(funcs) > 0 { |
| m := len(funcs) / 2 |
| fn := &funcs[m] |
| switch { |
| case pc < fn.Entry: |
| funcs = funcs[0:m] |
| case fn.Entry <= pc && pc < fn.End: |
| return fn |
| default: |
| funcs = funcs[m+1:] |
| } |
| } |
| return nil |
| } |
| |
| // PCToLine looks up line number information for a program counter. |
| // If there is no information, it returns fn == nil. |
| func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) { |
| if fn = t.PCToFunc(pc); fn == nil { |
| return |
| } |
| file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc)) |
| return |
| } |
| |
| // LineToPC looks up the first program counter on the given line in |
| // the named file. Returns UnknownPathError or UnknownLineError if |
| // there is an error looking up this line. |
| func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err os.Error) { |
| obj, ok := t.Files[file] |
| if !ok { |
| return 0, nil, UnknownFileError(file) |
| } |
| abs, err := obj.alineFromLine(file, line) |
| if err != nil { |
| return |
| } |
| for i := range obj.Funcs { |
| f := &obj.Funcs[i] |
| pc := f.LineTable.LineToPC(abs, f.End) |
| if pc != 0 { |
| return pc, f, nil |
| } |
| } |
| return 0, nil, &UnknownLineError{file, line} |
| } |
| |
| // LookupSym returns the text, data, or bss symbol with the given name, |
| // or nil if no such symbol is found. |
| func (t *Table) LookupSym(name string) *Sym { |
| // TODO(austin) Maybe make a map |
| for i := range t.Syms { |
| s := &t.Syms[i] |
| switch s.Type { |
| case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': |
| if s.Name == name { |
| return s |
| } |
| } |
| } |
| return nil |
| } |
| |
| // LookupFunc returns the text, data, or bss symbol with the given name, |
| // or nil if no such symbol is found. |
| func (t *Table) LookupFunc(name string) *Func { |
| for i := range t.Funcs { |
| f := &t.Funcs[i] |
| if f.Sym.Name == name { |
| return f |
| } |
| } |
| return nil |
| } |
| |
| // SymByAddr returns the text, data, or bss symbol starting at the given address. |
| // TODO(rsc): Allow lookup by any address within the symbol. |
| func (t *Table) SymByAddr(addr uint64) *Sym { |
| // TODO(austin) Maybe make a map |
| for i := range t.Syms { |
| s := &t.Syms[i] |
| switch s.Type { |
| case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': |
| if s.Value == addr { |
| return s |
| } |
| } |
| } |
| return nil |
| } |
| |
| /* |
| * Object files |
| */ |
| |
| func (o *Obj) lineFromAline(aline int) (string, int) { |
| type stackEnt struct { |
| path string |
| start int |
| offset int |
| prev *stackEnt |
| } |
| |
| noPath := &stackEnt{"", 0, 0, nil} |
| tos := noPath |
| |
| // TODO(austin) I have no idea how 'Z' symbols work, except |
| // that they pop the stack. |
| pathloop: |
| for _, s := range o.Paths { |
| val := int(s.Value) |
| switch { |
| case val > aline: |
| break pathloop |
| |
| case val == 1: |
| // Start a new stack |
| tos = &stackEnt{s.Name, val, 0, noPath} |
| |
| case s.Name == "": |
| // Pop |
| if tos == noPath { |
| return "<malformed symbol table>", 0 |
| } |
| tos.prev.offset += val - tos.start |
| tos = tos.prev |
| |
| default: |
| // Push |
| tos = &stackEnt{s.Name, val, 0, tos} |
| } |
| } |
| |
| if tos == noPath { |
| return "", 0 |
| } |
| return tos.path, aline - tos.start - tos.offset + 1 |
| } |
| |
| func (o *Obj) alineFromLine(path string, line int) (int, os.Error) { |
| if line < 1 { |
| return 0, &UnknownLineError{path, line} |
| } |
| |
| for i, s := range o.Paths { |
| // Find this path |
| if s.Name != path { |
| continue |
| } |
| |
| // Find this line at this stack level |
| depth := 0 |
| var incstart int |
| line += int(s.Value) |
| pathloop: |
| for _, s := range o.Paths[i:] { |
| val := int(s.Value) |
| switch { |
| case depth == 1 && val >= line: |
| return line - 1, nil |
| |
| case s.Name == "": |
| depth-- |
| if depth == 0 { |
| break pathloop |
| } else if depth == 1 { |
| line += val - incstart |
| } |
| |
| default: |
| if depth == 1 { |
| incstart = val |
| } |
| depth++ |
| } |
| } |
| return 0, &UnknownLineError{path, line} |
| } |
| return 0, UnknownFileError(path) |
| } |
| |
| /* |
| * Errors |
| */ |
| |
| // UnknownFileError represents a failure to find the specific file in |
| // the symbol table. |
| type UnknownFileError string |
| |
| func (e UnknownFileError) String() string { return "unknown file: " + string(e) } |
| |
| // UnknownLineError represents a failure to map a line to a program |
| // counter, either because the line is beyond the bounds of the file |
| // or because there is no code on the given line. |
| type UnknownLineError struct { |
| File string |
| Line int |
| } |
| |
| func (e *UnknownLineError) String() string { |
| return "no code at " + e.File + ":" + strconv.Itoa(e.Line) |
| } |
| |
| // DecodingError represents an error during the decoding of |
| // the symbol table. |
| type DecodingError struct { |
| off int |
| msg string |
| val interface{} |
| } |
| |
| func (e *DecodingError) String() string { |
| msg := e.msg |
| if e.val != nil { |
| msg += fmt.Sprintf(" '%v'", e.val) |
| } |
| msg += fmt.Sprintf(" at byte %#x", e.off) |
| return msg |
| } |