| // 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 ( |
| "debug/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; |
| } |
| |
| // 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:len(s.Name)]; |
| } |
| 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:len(p)]; |
| 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:len(p)]; |
| 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:len(p)]; |
| 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:len(t.Funcs)]; |
| } |
| 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]; |
| 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:len(t.Funcs)]; |
| } |
| 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:len(funcs)]; |
| } |
| } |
| 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:len(o.Paths)] { |
| 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; |
| } |
| |