| // 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 | |
| } |