x/debug: speed up various DWARF information queries. Builds auxiliary tables from the DWARF data to speed up requests for: - Entries with a given name or matching a regexp (using a map from symbol name to a linked list of *Entry) - Mapping a PC to a function (using a slice of function bounds, sorted by PC). - Mapping a PC to a source line (using a slice of structs containing (PC, file, line), sorted by PC). - Mapping a source line to breakpoint addresses (using a slice containing, for each file, a slice of (line, PC) pairs sorted by line.) Renames some functions to better reflect their return values (lookupFunction->functionStartAddress, lookupRE->LookupMatchingSymbols, lookupPC/entryForPC->PCToFunction, LineToPCs->LineToBreakpointPCs). Uses more specific lookups for entries that must be variables (LookupEntry->LookupVariable). Change-Id: Ia57514887495a7cfca242fa4af5517acf5eecf45 Reviewed-on: https://go-review.googlesource.com/20314 Reviewed-by: Dave Day <djd@golang.org>
diff --git a/dwarf/cache.go b/dwarf/cache.go new file mode 100644 index 0000000..cf795e7 --- /dev/null +++ b/dwarf/cache.go
@@ -0,0 +1,249 @@ +// Copyright 2016 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 dwarf + +import ( + "sort" +) + +// pcToFuncEntries maps PC ranges to function entries. +// +// Each element contains a *Entry for a function and its corresponding start PC. +// If we know the address one past the last instruction of a function, and it is +// not equal to the start address of the next function, we mark that with +// another element containing that address and a nil entry. The elements are +// sorted by PC. Among elements with the same PC, those with non-nil *Entry +// are put earlier. +type pcToFuncEntries []pcToFuncEntry +type pcToFuncEntry struct { + pc uint64 + entry *Entry +} + +func (p pcToFuncEntries) Len() int { return len(p) } +func (p pcToFuncEntries) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p pcToFuncEntries) Less(i, j int) bool { + if p[i].pc != p[j].pc { + return p[i].pc < p[j].pc + } + return p[i].entry != nil && p[j].entry == nil +} + +// nameCache maps each symbol name to a linked list of the entries with that name. +type nameCache map[string]*nameCacheEntry +type nameCacheEntry struct { + entry *Entry + link *nameCacheEntry +} + +// pcToLineEntries maps PCs to line numbers. +// +// It is a slice of (PC, line, file number) triples, sorted by PC. The file +// number is an index into the source files slice. +// If (PC1, line1, file1) and (PC2, line2, file2) are two consecutive elements, +// then the span of addresses [PC1, PC2) belongs to (line1, file1). If an +// element's file number is zero, it only marks the end of a span. +// +// TODO: could save memory by changing pcToLineEntries and lineToPCEntries to use +// interval trees containing references into .debug_line. +type pcToLineEntries []pcToLineEntry +type pcToLineEntry struct { + pc uint64 + line uint64 + file uint64 +} + +func (p pcToLineEntries) Len() int { return len(p) } +func (p pcToLineEntries) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p pcToLineEntries) Less(i, j int) bool { + if p[i].pc != p[j].pc { + return p[i].pc < p[j].pc + } + return p[i].file > p[j].file +} + +// byFileLine is used temporarily while building lineToPCEntries. +type byFileLine []pcToLineEntry + +func (b byFileLine) Len() int { return len(b) } +func (b byFileLine) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byFileLine) Less(i, j int) bool { + if b[i].file != b[j].file { + return b[i].file < b[j].file + } + return b[i].line < b[j].line +} + +// lineToPCEntries maps line numbers to breakpoint addresses. +// +// The slice contains, for each source file in Data, a slice of (line, PC) +// pairs, sorted by line. Note that there may be more than one PC for a line. +type lineToPCEntries [][]lineToPCEntry +type lineToPCEntry struct { + line uint64 + pc uint64 +} + +func (d *Data) buildLineToPCCache(pclfs pcToLineEntries) { + // TODO: only include lines where is_stmt is true + sort.Sort(byFileLine(pclfs)) + // Make a slice of (line, PC) pairs for each (non-zero) file. + var ( + c = make(lineToPCEntries, len(d.sourceFiles)) + curSlice []lineToPCEntry + ) + for i, pclf := range pclfs { + if pclf.file == 0 { + // This entry indicated the end of an instruction sequence, not a breakpoint. + continue + } + curSlice = append(curSlice, lineToPCEntry{line: pclf.line, pc: pclf.pc}) + if i+1 == len(pclfs) || pclf.file != pclfs[i+1].file { + // curSlice now contains all of the entries for pclf.file. + if pclf.file > 0 && pclf.file < uint64(len(c)) { + c[pclf.file] = curSlice + } + curSlice = nil + } + } + d.lineToPCEntries = c +} + +func (d *Data) buildPCToLineCache(cache pcToLineEntries) { + // Sort cache by PC (in increasing order), then by file number (in decreasing order). + sort.Sort(cache) + + // Build a copy without redundant entries. + var out pcToLineEntries + for i, pclf := range cache { + if i > 0 && pclf.pc == cache[i-1].pc { + // This entry is for the same PC as the previous entry. + continue + } + if i > 0 && pclf.file == cache[i-1].file && pclf.line == cache[i-1].line { + // This entry is for the same file and line as the previous entry. + continue + } + out = append(out, pclf) + } + d.pcToLineEntries = out +} + +// buildLineCaches constructs d.sourceFiles, d.lineToPCEntries, d.pcToLineEntries. +func (d *Data) buildLineCaches() { + if len(d.line) == 0 { + return + } + var m lineMachine + // Assume the address_size in the first unit applies to the whole program. + // TODO: we could handle executables containing code for multiple address + // sizes using DW_AT_stmt_list attributes. + if len(d.unit) == 0 { + return + } + buf := makeBuf(d, &d.unit[0], "line", 0, d.line) + if err := m.parseHeader(&buf); err != nil { + return + } + for _, f := range m.header.file { + d.sourceFiles = append(d.sourceFiles, f.name) + } + var cache pcToLineEntries + fn := func(m *lineMachine) bool { + if m.endSequence { + cache = append(cache, pcToLineEntry{ + pc: m.address, + line: 0, + file: 0, + }) + } else { + cache = append(cache, pcToLineEntry{ + pc: m.address, + line: m.line, + file: m.file, + }) + } + return true + } + m.evalCompilationUnit(&buf, fn) + d.buildLineToPCCache(cache) + d.buildPCToLineCache(cache) +} + +// buildInfoCaches initializes nameCache and pcToFuncEntries by walking the +// top-level entries under each compile unit. It swallows any errors in parsing. +func (d *Data) buildInfoCaches() { + // TODO: record errors somewhere? + d.nameCache = make(map[string]*nameCacheEntry) + + var pcToFuncEntries pcToFuncEntries + + r := d.Reader() +loop: + for { + entry, err := r.Next() + if entry == nil || err != nil { + break loop + } + if entry.Tag != TagCompileUnit /* DW_TAG_compile_unit */ { + r.SkipChildren() + continue + } + for { + entry, err := r.Next() + if entry == nil || err != nil { + break loop + } + if entry.Tag == 0 { + // End of children of current compile unit. + break + } + r.SkipChildren() + // Update name-to-entry cache. + if name, ok := entry.Val(AttrName).(string); ok { + d.nameCache[name] = &nameCacheEntry{entry: entry, link: d.nameCache[name]} + } + + // If this entry is a function, update PC-to-containing-function cache. + if entry.Tag != TagSubprogram /* DW_TAG_subprogram */ { + continue + } + + // DW_AT_low_pc, if present, is the address of the first instruction of + // the function. + lowpc, ok := entry.Val(AttrLowpc).(uint64) + if !ok { + continue + } + pcToFuncEntries = append(pcToFuncEntries, pcToFuncEntry{lowpc, entry}) + + // DW_AT_high_pc, if present (TODO: and of class address) is the address + // one past the last instruction of the function. + highpc, ok := entry.Val(AttrHighpc).(uint64) + if !ok { + continue + } + pcToFuncEntries = append(pcToFuncEntries, pcToFuncEntry{highpc, nil}) + } + } + // Sort elements by PC. If there are multiple elements with the same PC, + // those with non-nil *Entry are placed earlier. + sort.Sort(pcToFuncEntries) + + // Copy only the first element for each PC to out. + n := 0 + for i, ce := range pcToFuncEntries { + if i == 0 || ce.pc != pcToFuncEntries[i-1].pc { + n++ + } + } + out := make([]pcToFuncEntry, 0, n) + for i, ce := range pcToFuncEntries { + if i == 0 || ce.pc != pcToFuncEntries[i-1].pc { + out = append(out, ce) + } + } + d.pcToFuncEntries = out +}
diff --git a/dwarf/line.go b/dwarf/line.go index f035ef9..2f47739 100644 --- a/dwarf/line.go +++ b/dwarf/line.go
@@ -11,91 +11,40 @@ import ( "fmt" + "sort" "strings" ) // PCToLine returns the file and line number corresponding to the PC value. // It returns an error if a correspondence cannot be found. func (d *Data) PCToLine(pc uint64) (file string, line uint64, err error) { - if len(d.line) == 0 { + c := d.pcToLineEntries + if len(c) == 0 { return "", 0, fmt.Errorf("PCToLine: no line table") } - var m lineMachine - // Assume the first info unit is the same as us. Extremely likely. TODO? - if len(d.unit) == 0 { - return "", 0, fmt.Errorf("no info section") - } - buf := makeBuf(d, &d.unit[0], "line", 0, d.line) - if err = m.parseHeader(&buf); err != nil { - return "", 0, err - } - state := pcSearchState{pc: pc, newSequence: true} - if err = m.evalCompilationUnit(&buf, state.findPC); err != nil { - return "", 0, err - } - if !state.found { + i := sort.Search(len(c), func(i int) bool { return c[i].pc > pc }) - 1 + // c[i] is now the entry in pcToLineEntries with the largest pc that is not + // larger than the query pc. + // The search has failed if: + // - All pcs in c were larger than the query pc (i == -1). + // - c[i] marked the end of a sequence of instructions (c[i].file == 0). + // - c[i] is the last element of c, and isn't the end of a sequence of + // instructions, and the search pc is much larger than c[i].pc. In this + // case, we don't know the range of the last instruction, but the search + // pc is probably past it. + if i == -1 || c[i].file == 0 || (i+1 == len(c) && pc-c[i].pc > 1024) { return "", 0, fmt.Errorf("no source line defined for PC %#x", pc) } - if state.lastFile >= uint64(len(m.header.file)) { + if c[i].file >= uint64(len(d.sourceFiles)) { return "", 0, fmt.Errorf("invalid file number in DWARF data") } - return m.header.file[state.lastFile].name, state.lastLine, nil + return d.sourceFiles[c[i].file], c[i].line, nil } -// pcSearchState holds the state for the search PCToLine does. -type pcSearchState struct { - pc uint64 // pc we are searching for. - // lastPC, lastFile, and lastLine are the PC, file number and line that were - // output most recently by the line machine. - lastPC uint64 - lastFile uint64 - lastLine uint64 - // found indicates that the above values correspond to the PC we're looking for. - found bool - // newSequence indicates that we are starting a new sequence of instructions, - // and so last{PC,File,Line} are not valid. - newSequence bool -} - -// findPC will execute for every line in the state machine, until we find state.pc. -// It returns a bool indicating whether to continue searching. -func (state *pcSearchState) findPC(m *lineMachine) bool { - if !state.newSequence && state.lastPC < state.pc && state.pc < m.address { - // The PC we are looking for is between the previous PC and the current PC, - // so lastFile and lastLine are its source location. - state.found = true - return false - } - if m.endSequence { - state.newSequence = true - return true - } - state.newSequence = false - state.lastPC, state.lastFile, state.lastLine = m.address, m.file, m.line - if m.address == state.pc { - // lastFile and lastLine are the source location of pc. - state.found = true - return false - } - return true -} - -// LineToPCs returns the PCs corresponding to the file and line number. +// LineToBreakpointPCs returns the PCs that should be used as breakpoints +// corresponding to the given file and line number. // It returns an empty slice if no PCs were found. -func (d *Data) LineToPCs(file string, line uint64) ([]uint64, error) { - if len(d.line) == 0 { - return nil, fmt.Errorf("LineToPCs: no line table") - } - if len(d.unit) == 0 { - return nil, fmt.Errorf("LineToPCs: no info section") - } - - buf := makeBuf(d, &d.unit[0], "line", 0, d.line) - var m lineMachine - if err := m.parseHeader(&buf); err != nil { - return nil, err - } - +func (d *Data) LineToBreakpointPCs(file string, line uint64) ([]uint64, error) { compDir := d.compilationDirectory() // Find the closest match in the executable for the specified file. @@ -103,46 +52,43 @@ // at the end of the name. If there is a tie, we prefer files that are // under the compilation directory. If there is still a tie, we choose // the file with the shortest name. + // TODO: handle duplicate file names in the DWARF? var bestFile struct { fileNum uint64 // Index of the file in the DWARF data. components int // Number of matching path components. length int // Length of the filename. underComp bool // File is under the compilation directory. } - for num, f := range m.header.file { - c := matchingPathComponentSuffixSize(f.name, file) - underComp := strings.HasPrefix(f.name, compDir) + for filenum, filename := range d.sourceFiles { + c := matchingPathComponentSuffixSize(filename, file) + underComp := strings.HasPrefix(filename, compDir) better := false if c != bestFile.components { better = c > bestFile.components } else if underComp != bestFile.underComp { better = underComp } else { - better = len(f.name) < bestFile.length + better = len(filename) < bestFile.length } if better { - bestFile.fileNum = uint64(num) + bestFile.fileNum = uint64(filenum) bestFile.components = c - bestFile.length = len(f.name) + bestFile.length = len(filename) bestFile.underComp = underComp } } if bestFile.components == 0 { - return nil, fmt.Errorf("couldn't find file %s", file) + return nil, fmt.Errorf("couldn't find file %q", file) } - // pcs will contain the PCs for every line machine output with the correct line - // and file number. - var pcs []uint64 - // accumulatePCs will execute for every line machine output. - accumulatePCs := func(m *lineMachine) (cont bool) { - if m.line == line && m.file == bestFile.fileNum { - pcs = append(pcs, m.address) - } - return true - } - if err := m.evalCompilationUnit(&buf, accumulatePCs); err != nil { - return nil, err + c := d.lineToPCEntries[bestFile.fileNum] + // c contains all (pc, line) pairs for the appropriate file. + start := sort.Search(len(c), func(i int) bool { return c[i].line >= line }) + end := sort.Search(len(c), func(i int) bool { return c[i].line > line }) + // c[i].line == line for all i in the range [start, end). + pcs := make([]uint64, 0, end-start) + for i := start; i < end; i++ { + pcs = append(pcs, c[i].pc) } return pcs, nil }
diff --git a/dwarf/open.go b/dwarf/open.go index 488bbf0..a13c6ed 100644 --- a/dwarf/open.go +++ b/dwarf/open.go
@@ -23,11 +23,16 @@ str []byte // parsed data - abbrevCache map[uint32]abbrevTable - order binary.ByteOrder - typeCache map[Offset]Type - typeSigs map[uint64]*typeUnit - unit []unit + abbrevCache map[uint32]abbrevTable + order binary.ByteOrder + typeCache map[Offset]Type + typeSigs map[uint64]*typeUnit + unit []unit + sourceFiles []string // source files listed in .debug_line. + nameCache // map from name to top-level entries in .debug_info. + pcToFuncEntries // cache of .debug_info data for function bounds. + pcToLineEntries // cache of .debug_line data, used for efficient PC-to-line mapping. + lineToPCEntries // cache of .debug_line data, used for efficient line-to-[]PC mapping. } // New returns a new Data object initialized from the given parameters. @@ -75,6 +80,8 @@ return nil, err } d.unit = u + d.buildInfoCaches() + d.buildLineCaches() return d, nil }
diff --git a/dwarf/symbol.go b/dwarf/symbol.go index 6ddc836..52d6829 100644 --- a/dwarf/symbol.go +++ b/dwarf/symbol.go
@@ -6,33 +6,36 @@ // This file provides simple methods to access the symbol table by name and address. -import "fmt" +import ( + "fmt" + "regexp" + "sort" +) -// lookupEntry returns the Entry for the name. If tag is non-zero, only entries -// with that tag are considered. +// lookupEntry returns the first Entry for the name. +// If tag is non-zero, only entries with that tag are considered. func (d *Data) lookupEntry(name string, tag Tag) (*Entry, error) { - r := d.Reader() - for { - entry, err := r.Next() - if err != nil { - return nil, err - } - if entry == nil { - // TODO: why don't we get an error here? - break - } - if tag != 0 && tag != entry.Tag { - continue - } - nameAttr := entry.Val(AttrName) - if nameAttr == nil { - continue - } - if nameAttr.(string) == name { - return entry, nil + x, ok := d.nameCache[name] + if !ok { + return nil, fmt.Errorf("DWARF entry for %q not found", name) + } + for ; x != nil; x = x.link { + if tag == 0 || x.entry.Tag == tag { + return x.entry, nil } } - return nil, fmt.Errorf("DWARF entry for %q not found", name) + return nil, fmt.Errorf("no DWARF entry for %q with tag %s", name, tag) +} + +// LookupMatchingSymbols returns the names of all top-level entries matching +// the given regular expression. +func (d *Data) LookupMatchingSymbols(nameRE *regexp.Regexp) (result []string, err error) { + for name := range d.nameCache { + if nameRE.MatchString(name) { + result = append(result, name) + } + } + return result, nil } // LookupEntry returns the Entry for the named symbol. @@ -40,38 +43,14 @@ return d.lookupEntry(name, 0) } -// LookupFunction returns the address of the named symbol, a function. -func (d *Data) LookupFunction(name string) (uint64, error) { - entry, err := d.lookupEntry(name, TagSubprogram) - if err != nil { - return 0, err - } - addrAttr := entry.Val(AttrLowpc) - if addrAttr == nil { - return 0, fmt.Errorf("symbol %q has no LowPC attribute", name) - } - addr, ok := addrAttr.(uint64) - if !ok { - return 0, fmt.Errorf("symbol %q has non-uint64 LowPC attribute", name) - } - return addr, nil +// LookupFunction returns the entry for a function. +func (d *Data) LookupFunction(name string) (*Entry, error) { + return d.lookupEntry(name, TagSubprogram) } -// TODO: should LookupVariable handle both globals and locals? Locals don't -// necessarily have a fixed address. They may be in a register, or otherwise -// move around. - -// LookupVariable returns the location of a named symbol, a variable. -func (d *Data) LookupVariable(name string) (uint64, error) { - entry, err := d.lookupEntry(name, TagVariable) - if err != nil { - return 0, fmt.Errorf("variable %s: %s", name, err) - } - loc, err := d.EntryLocation(entry) - if err != nil { - return 0, fmt.Errorf("variable %s: %s", name, err) - } - return loc, nil +// LookupVariable returns the entry for a (global) variable. +func (d *Data) LookupVariable(name string) (*Entry, error) { + return d.lookupEntry(name, TagVariable) } // EntryLocation returns the address of the object referred to by the given Entry. @@ -97,6 +76,15 @@ return 0, fmt.Errorf("DWARF entry has an unimplemented Location op") } +// EntryType returns the Type for an Entry. +func (d *Data) EntryType(e *Entry) (Type, error) { + off, err := d.EntryTypeOffset(e) + if err != nil { + return nil, err + } + return d.Type(off) +} + // EntryTypeOffset returns the offset in the given Entry's type attribute. func (d *Data) EntryTypeOffset(e *Entry) (Offset, error) { v := e.Val(AttrType) @@ -110,46 +98,22 @@ return off, nil } -// LookupPC returns the name of a symbol at the specified PC. -func (d *Data) LookupPC(pc uint64) (string, error) { - entry, _, err := d.EntryForPC(pc) - if err != nil { - return "", err +// PCToFunction returns the entry and address for the function containing the +// specified PC. +func (d *Data) PCToFunction(pc uint64) (entry *Entry, lowpc uint64, err error) { + p := d.pcToFuncEntries + if len(p) == 0 { + return nil, 0, fmt.Errorf("no function addresses loaded") } - nameAttr := entry.Val(AttrName) - if nameAttr == nil { - // TODO: this shouldn't be possible. - return "", fmt.Errorf("LookupPC: TODO") + i := sort.Search(len(p), func(i int) bool { return p[i].pc > pc }) - 1 + // The search failed if: + // - pc was before the start of any function. + // - The largest function bound not larger than pc was the end of a function, + // not the start of one. + // - The largest function bound not larger than pc was the start of a function + // that we don't know the end of, and the PC is much larger than the start. + if i == -1 || p[i].entry == nil || (i+1 == len(p) && pc-p[i].pc >= 1<<20) { + return nil, 0, fmt.Errorf("no function at %x", pc) } - name, ok := nameAttr.(string) - if !ok { - return "", fmt.Errorf("name for PC %#x is not a string", pc) - } - return name, nil -} - -// EntryForPC returns the entry and address for a symbol at the specified PC. -func (d *Data) EntryForPC(pc uint64) (entry *Entry, lowpc uint64, err error) { - // TODO: do something better than a linear scan? - r := d.Reader() - for { - entry, err := r.Next() - if err != nil { - return nil, 0, err - } - if entry == nil { - // TODO: why don't we get an error here. - break - } - if entry.Tag != TagSubprogram { - continue - } - lowpc, lok := entry.Val(AttrLowpc).(uint64) - highpc, hok := entry.Val(AttrHighpc).(uint64) - if !lok || !hok || pc < lowpc || highpc <= pc { - continue - } - return entry, lowpc, nil - } - return nil, 0, fmt.Errorf("PC %#x not found", pc) + return p[i].entry, p[i].pc, nil }
diff --git a/server/dwarf.go b/server/dwarf.go index 50f2220..3c4e2a2 100644 --- a/server/dwarf.go +++ b/server/dwarf.go
@@ -6,50 +6,25 @@ import ( "errors" - "regexp" + "fmt" "golang.org/x/debug/dwarf" ) -func (s *Server) lookupRE(re *regexp.Regexp) (result []string, err error) { - r := s.dwarfData.Reader() - for { - entry, err := r.Next() - if err != nil { - return nil, err - } - if entry == nil { - // TODO: why don't we get an error here. - break - } - nameAttr := entry.Val(dwarf.AttrName) - if nameAttr == nil { - // TODO: this shouldn't be possible. - continue - } - name, ok := nameAttr.(string) - if !ok || !re.MatchString(name) { - continue - } - result = append(result, name) +func (s *Server) functionStartAddress(name string) (uint64, error) { + entry, err := s.dwarfData.LookupFunction(name) + if err != nil { + return 0, err } - return result, nil -} - -func (s *Server) lookupFunction(name string) (uint64, error) { - return s.dwarfData.LookupFunction(name) -} - -func (s *Server) lookupVariable(name string) (uint64, error) { - return s.dwarfData.LookupVariable(name) -} - -func (s *Server) lookupPC(pc uint64) (string, error) { - return s.dwarfData.LookupPC(pc) -} - -func (s *Server) entryForPC(pc uint64) (entry *dwarf.Entry, lowpc uint64, err error) { - return s.dwarfData.EntryForPC(pc) + addrAttr := entry.Val(dwarf.AttrLowpc) + if addrAttr == nil { + return 0, fmt.Errorf("symbol %q has no LowPC attribute", name) + } + addr, ok := addrAttr.(uint64) + if !ok { + return 0, fmt.Errorf("symbol %q has non-uint64 LowPC attribute", name) + } + return addr, nil } // evalLocation parses a DWARF location description encoded in v. It works for
diff --git a/server/eval.go b/server/eval.go index b7a1b68..d964a32 100644 --- a/server/eval.go +++ b/server/eval.go
@@ -1565,7 +1565,7 @@ // The PC and SP are used to determine the current function and stack frame. func (s *Server) findLocalVar(name string, pc, sp uint64) (uint64, dwarf.Type) { // Find the DWARF entry for the function at pc. - funcEntry, _, err := s.entryForPC(uint64(pc)) + funcEntry, _, err := s.dwarfData.PCToFunction(uint64(pc)) if err != nil { return 0, nil } @@ -1633,7 +1633,7 @@ // findGlobalVar finds a global variable by name, and returns its address and // DWARF type. It returns a nil type on failure. func (s *Server) findGlobalVar(name string) (uint64, dwarf.Type) { - entry, err := s.dwarfData.LookupEntry(name) + entry, err := s.dwarfData.LookupVariable(name) if err != nil { return 0, nil }
diff --git a/server/eval.m4 b/server/eval.m4 index 0e0def3..7b7594a 100644 --- a/server/eval.m4 +++ b/server/eval.m4
@@ -1173,7 +1173,7 @@ // The PC and SP are used to determine the current function and stack frame. func (s *Server) findLocalVar(name string, pc, sp uint64) (uint64, dwarf.Type) { // Find the DWARF entry for the function at pc. - funcEntry, _, err := s.entryForPC(uint64(pc)) + funcEntry, _, err := s.dwarfData.PCToFunction(uint64(pc)) if err != nil { return 0, nil } @@ -1241,7 +1241,7 @@ // findGlobalVar finds a global variable by name, and returns its address and // DWARF type. It returns a nil type on failure. func (s *Server) findGlobalVar(name string) (uint64, dwarf.Type) { - entry, err := s.dwarfData.LookupEntry(name) + entry, err := s.dwarfData.LookupVariable(name) if err != nil { return 0, nil }
diff --git a/server/server.go b/server/server.go index 640895e..76219db 100644 --- a/server/server.go +++ b/server/server.go
@@ -425,7 +425,7 @@ } func (s *Server) handleBreakpointAtFunction(req *protocol.BreakpointAtFunctionRequest, resp *protocol.BreakpointResponse) error { - pc, err := s.lookupFunction(req.Function) + pc, err := s.functionStartAddress(req.Function) if err != nil { return err } @@ -440,7 +440,7 @@ if s.dwarfData == nil { return fmt.Errorf("no DWARF data") } - if pcs, err := s.dwarfData.LineToPCs(req.File, req.Line); err != nil { + if pcs, err := s.dwarfData.LineToBreakpointPCs(req.File, req.Line); err != nil { return err } else { return s.addBreakpoints(pcs, resp) @@ -520,11 +520,11 @@ if err != nil { return nil, err } - return s.lookupRE(re) + return s.dwarfData.LookupMatchingSymbols(re) case strings.HasPrefix(expr, "addr:"): // Symbol lookup. Return address. - addr, err := s.lookupFunction(expr[5:]) + addr, err := s.functionStartAddress(expr[5:]) if err != nil { return nil, err } @@ -556,11 +556,15 @@ if err != nil { return nil, err } - funcName, err := s.lookupPC(addr) + entry, _, err := s.dwarfData.PCToFunction(addr) if err != nil { return nil, err } - return []string{funcName}, nil + name, ok := entry.Val(dwarf.AttrName).(string) + if !ok { + return nil, fmt.Errorf("function at 0x%x has no name", addr) + } + return []string{name}, nil } return nil, fmt.Errorf("bad expression syntax: %q", expr) @@ -584,24 +588,6 @@ return s.dwarfData.PCToLine(pc) } -// evalAddress takes a simple expression, either a symbol or hex value, -// and evaluates it as an address. -func (s *Server) evalAddress(expr string) (uint64, error) { - // Might be a symbol. - addr, err := s.lookupFunction(expr) // TODO: might not be a function - if err == nil { - return addr, nil - } - - // Must be a number. - addr, err = strconv.ParseUint(expr, 0, 0) - if err != nil { - return 0, fmt.Errorf("eval: %q is neither symbol nor number", expr) - } - - return addr, nil -} - func (s *Server) Frames(req *protocol.FramesRequest, resp *protocol.FramesResponse) error { return s.call(s.otherc, req, resp) } @@ -643,7 +629,7 @@ return frames, err } fp := sp + uint64(fpOffset) - entry, funcEntry, err := s.entryForPC(pc) + entry, funcEntry, err := s.dwarfData.PCToFunction(pc) if err != nil { return frames, err } @@ -720,9 +706,9 @@ indirect bool names []string ) - if _, err := s.lookupVariable("runtime.rt0_goPC"); err != nil { + if _, err := s.dwarfData.LookupVariable("runtime.rt0_goPC"); err != nil { // Look for a Go 1.3 binary (or earlier version). - lookup, indirect, names = s.lookupFunction, false, []string{ + lookup, indirect, names = s.functionStartAddress, false, []string{ "runtime.goexit", "runtime.mstart", "runtime.mcall", @@ -732,7 +718,14 @@ } } else { // Look for a Go 1.4 binary (or later version). - lookup, indirect, names = s.lookupVariable, true, []string{ + lookup = func(name string) (uint64, error) { + entry, err := s.dwarfData.LookupVariable(name) + if err != nil { + return 0, err + } + return s.dwarfData.EntryLocation(entry) + } + indirect, names = true, []string{ "runtime.goexitPC", "runtime.mstartPC", "runtime.mcallPC", @@ -780,7 +773,7 @@ } func (s *Server) handleVarByName(req *protocol.VarByNameRequest, resp *protocol.VarByNameResponse) error { - entry, err := s.dwarfData.LookupEntry(req.Name) + entry, err := s.dwarfData.LookupVariable(req.Name) if err != nil { return fmt.Errorf("variable %s: %s", req.Name, err) } @@ -915,7 +908,7 @@ ) for { // Try to read the slice runtime.allgs. - allgsEntry, err := s.dwarfData.LookupEntry("runtime.allgs") + allgsEntry, err := s.dwarfData.LookupVariable("runtime.allgs") if err != nil { break } @@ -945,7 +938,7 @@ } if !allgPtrOk { // Read runtime.allg. - allgEntry, err := s.dwarfData.LookupEntry("runtime.allg") + allgEntry, err := s.dwarfData.LookupVariable("runtime.allg") if err != nil { return err } @@ -959,7 +952,7 @@ } // Read runtime.allglen. - allglenEntry, err := s.dwarfData.LookupEntry("runtime.allglen") + allglenEntry, err := s.dwarfData.LookupVariable("runtime.allglen") if err != nil { return err } @@ -1056,7 +1049,7 @@ // Best-effort attempt to get the names of the goroutine function and the // function that created the goroutine. They aren't always available. functionName := func(pc uint64) string { - entry, _, err := s.dwarfData.EntryForPC(pc) + entry, _, err := s.dwarfData.PCToFunction(pc) if err != nil { return "" }