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