| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | package gosym |
| |
|
| | import ( |
| | "bytes" |
| | "encoding/binary" |
| | "fmt" |
| | "strconv" |
| | "strings" |
| | ) |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | type Sym struct { |
| | Value uint64 |
| | Type byte |
| | Name string |
| | GoType uint64 |
| | |
| | Func *Func |
| |
|
| | goVersion version |
| | } |
| |
|
| | |
| | func (s *Sym) Static() bool { return s.Type >= 'a' } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func (s *Sym) nameWithoutInst() string { |
| | start := strings.Index(s.Name, "[") |
| | if start < 0 { |
| | return s.Name |
| | } |
| | end := strings.LastIndex(s.Name, "]") |
| | if end < 0 { |
| | |
| | return s.Name |
| | } |
| | return s.Name[0:start] + s.Name[end+1:] |
| | } |
| |
|
| | |
| | |
| | func (s *Sym) PackageName() string { |
| | name := s.nameWithoutInst() |
| |
|
| | |
| | |
| | |
| | |
| | if s.goVersion >= ver120 && (strings.HasPrefix(name, "go:") || strings.HasPrefix(name, "type:")) { |
| | return "" |
| | } |
| |
|
| | |
| | if s.goVersion <= ver118 && (strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.")) { |
| | return "" |
| | } |
| |
|
| | pathend := strings.LastIndex(name, "/") |
| | if pathend < 0 { |
| | pathend = 0 |
| | } |
| |
|
| | if i := strings.Index(name[pathend:], "."); i != -1 { |
| | return name[:pathend+i] |
| | } |
| | return "" |
| | } |
| |
|
| | |
| | |
| | |
| | func (s *Sym) ReceiverName() string { |
| | name := s.nameWithoutInst() |
| | |
| | |
| | pathend := strings.LastIndex(name, "/") |
| | if pathend < 0 { |
| | pathend = 0 |
| | } |
| | |
| | |
| | l := strings.Index(name[pathend:], ".") |
| | |
| | r := strings.LastIndex(name[pathend:], ".") |
| | if l == -1 || r == -1 || l == r { |
| | |
| | return "" |
| | } |
| | |
| | |
| | |
| | r = strings.LastIndex(s.Name[pathend:], ".") |
| | return s.Name[pathend+l+1 : pathend+r] |
| | } |
| |
|
| | |
| | func (s *Sym) BaseName() string { |
| | name := s.nameWithoutInst() |
| | if i := strings.LastIndex(name, "."); i != -1 { |
| | if s.Name != name { |
| | brack := strings.Index(s.Name, "[") |
| | if i > brack { |
| | |
| | |
| | |
| | |
| | i = strings.LastIndex(s.Name, ".") |
| | } |
| | } |
| | return s.Name[i+1:] |
| | } |
| | return s.Name |
| | } |
| |
|
| | |
| | type Func struct { |
| | Entry uint64 |
| | *Sym |
| | End uint64 |
| | Params []*Sym |
| | Locals []*Sym |
| | FrameSize int |
| | LineTable *LineTable |
| | Obj *Obj |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | type Obj struct { |
| | |
| | Funcs []Func |
| |
|
| | |
| | |
| | |
| | |
| | Paths []Sym |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | type Table struct { |
| | Syms []Sym |
| | Funcs []Func |
| | Files map[string]*Obj |
| | Objs []Obj |
| |
|
| | go12line *LineTable |
| | } |
| |
|
| | type sym struct { |
| | value uint64 |
| | gotype uint64 |
| | typ byte |
| | name []byte |
| | } |
| |
|
| | var ( |
| | littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00} |
| | bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00} |
| | oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00} |
| | ) |
| |
|
| | func walksymtab(data []byte, fn func(sym) error) error { |
| | if len(data) == 0 { |
| | return nil |
| | } |
| | var order binary.ByteOrder = binary.BigEndian |
| | newTable := false |
| | switch { |
| | case bytes.HasPrefix(data, oldLittleEndianSymtab): |
| | |
| | |
| | |
| | data = data[6:] |
| | order = binary.LittleEndian |
| | case bytes.HasPrefix(data, bigEndianSymtab): |
| | newTable = true |
| | case bytes.HasPrefix(data, littleEndianSymtab): |
| | newTable = true |
| | order = binary.LittleEndian |
| | } |
| | var ptrsz int |
| | if newTable { |
| | if len(data) < 8 { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | ptrsz = int(data[7]) |
| | if ptrsz != 4 && ptrsz != 8 { |
| | return &DecodingError{7, "invalid pointer size", ptrsz} |
| | } |
| | data = data[8:] |
| | } |
| | var s sym |
| | p := data |
| | for len(p) >= 4 { |
| | var typ byte |
| | if newTable { |
| | |
| | typ = p[0] & 0x3F |
| | wideValue := p[0]&0x40 != 0 |
| | goType := p[0]&0x80 != 0 |
| | if typ < 26 { |
| | typ += 'A' |
| | } else { |
| | typ += 'a' - 26 |
| | } |
| | s.typ = typ |
| | p = p[1:] |
| | if wideValue { |
| | if len(p) < ptrsz { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | |
| | if ptrsz == 8 { |
| | s.value = order.Uint64(p[0:8]) |
| | p = p[8:] |
| | } else { |
| | s.value = uint64(order.Uint32(p[0:4])) |
| | p = p[4:] |
| | } |
| | } else { |
| | |
| | s.value = 0 |
| | shift := uint(0) |
| | for len(p) > 0 && p[0]&0x80 != 0 { |
| | s.value |= uint64(p[0]&0x7F) << shift |
| | shift += 7 |
| | p = p[1:] |
| | } |
| | if len(p) == 0 { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | s.value |= uint64(p[0]) << shift |
| | p = p[1:] |
| | } |
| | if goType { |
| | if len(p) < ptrsz { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | |
| | if ptrsz == 8 { |
| | s.gotype = order.Uint64(p[0:8]) |
| | p = p[8:] |
| | } else { |
| | s.gotype = uint64(order.Uint32(p[0:4])) |
| | p = p[4:] |
| | } |
| | } |
| | } else { |
| | |
| | s.value = uint64(order.Uint32(p[0:4])) |
| | if len(p) < 5 { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | 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 len(p) < i+nnul { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | s.name = p[0:i] |
| | i += nnul |
| | p = p[i:] |
| |
|
| | if !newTable { |
| | if len(p) < 4 { |
| | return &DecodingError{len(data), "unexpected EOF", nil} |
| | } |
| | |
| | s.gotype = uint64(order.Uint32(p[:4])) |
| | p = p[4:] |
| | } |
| | fn(s) |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func NewTable(symtab []byte, pcln *LineTable) (*Table, error) { |
| | var n int |
| | err := walksymtab(symtab, func(s sym) error { |
| | n++ |
| | return nil |
| | }) |
| | if err != nil { |
| | return nil, err |
| | } |
| |
|
| | var t Table |
| | if pcln.isGo12() { |
| | t.go12line = pcln |
| | } |
| | fname := make(map[uint16]string) |
| | t.Syms = make([]Sym, 0, n) |
| | nf := 0 |
| | nz := 0 |
| | lasttyp := uint8(0) |
| | err = walksymtab(symtab, func(s sym) error { |
| | n := len(t.Syms) |
| | t.Syms = t.Syms[0 : n+1] |
| | ts := &t.Syms[n] |
| | ts.Type = s.typ |
| | ts.Value = s.value |
| | ts.GoType = s.gotype |
| | ts.goVersion = pcln.version |
| | switch s.typ { |
| | default: |
| | |
| | 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.Files = make(map[string]*Obj) |
| |
|
| | var obj *Obj |
| | if t.go12line != nil { |
| | |
| | t.Objs = make([]Obj, 1) |
| | obj = &t.Objs[0] |
| | t.go12line.go12MapFiles(t.Files, obj) |
| | } else { |
| | t.Objs = make([]Obj, 0, nz) |
| | } |
| |
|
| | |
| | |
| | lastf := 0 |
| | for i := 0; i < len(t.Syms); i++ { |
| | sym := &t.Syms[i] |
| | switch sym.Type { |
| | case 'Z', 'z': |
| | if t.go12line != nil { |
| | |
| | break |
| | } |
| | |
| | if obj != nil { |
| | obj.Funcs = t.Funcs[lastf:] |
| | } |
| | lastf = len(t.Funcs) |
| |
|
| | |
| | n := len(t.Objs) |
| | t.Objs = t.Objs[0 : n+1] |
| | obj = &t.Objs[n] |
| |
|
| | |
| | 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 |
| |
|
| | |
| | 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': |
| | if n := len(t.Funcs); n > 0 { |
| | t.Funcs[n-1].End = sym.Value |
| | } |
| | if sym.Name == "runtime.etext" || sym.Name == "etext" { |
| | continue |
| | } |
| |
|
| | |
| | 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++ |
| | } |
| | } |
| |
|
| | |
| | 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 t.go12line != nil { |
| | |
| | |
| | |
| | fn.LineTable = t.go12line |
| | } else 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 |
| | } |
| | } |
| |
|
| | if t.go12line != nil && nf == 0 { |
| | t.Funcs = t.go12line.go12Funcs() |
| | } |
| | if obj != nil { |
| | obj.Funcs = t.Funcs[lastf:] |
| | } |
| | return &t, nil |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) { |
| | if fn = t.PCToFunc(pc); fn == nil { |
| | return |
| | } |
| | if t.go12line != nil { |
| | file = t.go12line.go12PCToFile(pc) |
| | line = t.go12line.go12PCToLine(pc) |
| | } else { |
| | file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc)) |
| | } |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) { |
| | obj, ok := t.Files[file] |
| | if !ok { |
| | return 0, nil, UnknownFileError(file) |
| | } |
| |
|
| | if t.go12line != nil { |
| | pc := t.go12line.go12LineToPC(file, line) |
| | if pc == 0 { |
| | return 0, nil, &UnknownLineError{file, line} |
| | } |
| | return pc, t.PCToFunc(pc), nil |
| | } |
| |
|
| | 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} |
| | } |
| |
|
| | |
| | |
| | func (t *Table) LookupSym(name string) *Sym { |
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | func (t *Table) SymByAddr(addr uint64) *Sym { |
| | 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 |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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 |
| |
|
| | pathloop: |
| | for _, s := range o.Paths { |
| | val := int(s.Value) |
| | switch { |
| | case val > aline: |
| | break pathloop |
| |
|
| | case val == 1: |
| | |
| | tos = &stackEnt{s.Name, val, 0, noPath} |
| |
|
| | case s.Name == "": |
| | |
| | if tos == noPath { |
| | return "<malformed symbol table>", 0 |
| | } |
| | tos.prev.offset += val - tos.start |
| | tos = tos.prev |
| |
|
| | default: |
| | |
| | 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, error) { |
| | if line < 1 { |
| | return 0, &UnknownLineError{path, line} |
| | } |
| |
|
| | for i, s := range o.Paths { |
| | |
| | if s.Name != path { |
| | continue |
| | } |
| |
|
| | |
| | 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) |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | type UnknownFileError string |
| |
|
| | func (e UnknownFileError) Error() string { return "unknown file: " + string(e) } |
| |
|
| | |
| | |
| | |
| | type UnknownLineError struct { |
| | File string |
| | Line int |
| | } |
| |
|
| | func (e *UnknownLineError) Error() string { |
| | return "no code at " + e.File + ":" + strconv.Itoa(e.Line) |
| | } |
| |
|
| | |
| | |
| | type DecodingError struct { |
| | off int |
| | msg string |
| | val any |
| | } |
| |
|
| | func (e *DecodingError) Error() string { |
| | msg := e.msg |
| | if e.val != nil { |
| | msg += fmt.Sprintf(" '%v'", e.val) |
| | } |
| | msg += fmt.Sprintf(" at byte %#x", e.off) |
| | return msg |
| | } |
| |
|