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