ogle: move symbol lookup into dwarf package
Also add a (trivial) test for PCToLine.
Also open up the frame data, as yet unused, in the elf and macho packages.

LGTM=nigeltao
R=nigeltao
https://golang.org/cl/109430043
diff --git a/debug/dwarf/line.go b/debug/dwarf/line.go
index de095bd..affa77b 100644
--- a/debug/dwarf/line.go
+++ b/debug/dwarf/line.go
@@ -4,12 +4,12 @@
 
 package dwarf
 
-// Mapping from PC to lines.
-// http://www.dwarfstd.org/doc/DWARF4.pdf Section 6.2 page 108
-
-// TODO: Convert the I/O to use the buffer interface defined in buf.go.
+// This file implemetns the mapping from PC to lines.
+// TODO: Also map from line to PC.
 // TODO: Find a way to test this properly.
 
+// http://www.dwarfstd.org/doc/DWARF4.pdf Section 6.2 page 108
+
 import (
 	"fmt"
 )
@@ -19,7 +19,7 @@
 // TODO: Return a function descriptor as well.
 func (d *Data) PCToLine(pc uint64) (file string, line int, err error) {
 	if len(d.line) == 0 {
-		return
+		return "", 0, fmt.Errorf("PCToLine: no line table")
 	}
 	var m lineMachine
 	// Assume the first info unit is the same as us. Extremely likely. TODO?
@@ -171,7 +171,7 @@
 // unit in the line table starting at the specified offset.
 func (m *lineMachine) parseLinePrologue(b *buf) error {
 	m.prologue = linePrologue{}
-	m.prologue.unitLength = int(b.uint32())
+	m.prologue.unitLength = int(b.uint32()) // Note: We are assuming 32-bit DWARF format.
 	if m.prologue.unitLength > len(b.data) {
 		return fmt.Errorf("DWARF: bad PC/line header length")
 	}
@@ -192,6 +192,7 @@
 	m.prologue.include = make([]string, 1) // First entry is empty; file index entries are 1-indexed.
 	// Includes
 	name := make([]byte, 0, 64)
+	// TODO: use b.string()
 	zeroTerminatedString := func() string {
 		name = name[:0]
 		for {
diff --git a/debug/dwarf/pclntab_test.go b/debug/dwarf/pclntab_test.go
new file mode 100644
index 0000000..ea9ba04
--- /dev/null
+++ b/debug/dwarf/pclntab_test.go
@@ -0,0 +1,134 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package dwarf_test
+
+// Stripped-down, simplified version of ../../gosym/pclntab_test.go
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"testing"
+
+	. "code.google.com/p/ogle/debug/dwarf"
+	"code.google.com/p/ogle/debug/elf"
+	"code.google.com/p/ogle/debug/macho"
+)
+
+var (
+	pclineTempDir    string
+	pclinetestBinary string
+)
+
+func dotest(self bool) bool {
+	// For now, only works on amd64 platforms.
+	if runtime.GOARCH != "amd64" {
+		return false
+	}
+	// Self test reads test binary; only works on Linux or Mac.
+	if self {
+		if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
+			return false
+		}
+	}
+	// Command below expects "sh", so Unix.
+	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
+		return false
+	}
+	if pclinetestBinary != "" {
+		return true
+	}
+	var err error
+	pclineTempDir, err = ioutil.TempDir("", "pclinetest")
+	if err != nil {
+		panic(err)
+	}
+	if strings.Contains(pclineTempDir, " ") {
+		panic("unexpected space in tempdir")
+	}
+	// This command builds pclinetest from ../../gosym/pclinetest.asm;
+	// the resulting binary looks like it was built from pclinetest.s,
+	// but we have renamed it to keep it away from the go tool.
+	pclinetestBinary = filepath.Join(pclineTempDir, "pclinetest")
+	command := fmt.Sprintf("go tool 6a -o %s.6 ../../gosym/pclinetest.asm && go tool 6l -H %s -E main -o %s %s.6",
+		pclinetestBinary, runtime.GOOS, pclinetestBinary, pclinetestBinary)
+	cmd := exec.Command("sh", "-c", command)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		panic(err)
+	}
+	return true
+}
+
+func endtest() {
+	if pclineTempDir != "" {
+		os.RemoveAll(pclineTempDir)
+		pclineTempDir = ""
+		pclinetestBinary = ""
+	}
+}
+
+func getData(file string) (*Data, error) {
+	switch runtime.GOOS {
+	case "linux":
+		f, err := elf.Open(file)
+		if err != nil {
+			return nil, err
+		}
+		dwarf, err := f.DWARF()
+		if err != nil {
+			return nil, err
+		}
+		f.Close()
+		return dwarf, nil
+	case "darwin":
+		f, err := macho.Open(file)
+		if err != nil {
+			return nil, err
+		}
+		dwarf, err := f.DWARF()
+		if err != nil {
+			return nil, err
+		}
+		f.Close()
+		return dwarf, nil
+	}
+	panic("unimplemented DWARF for GOOS=" + runtime.GOOS)
+}
+
+func TestPCLine(t *testing.T) {
+	if !dotest(false) {
+		return
+	}
+	defer endtest()
+
+	data, err := getData(pclinetestBinary)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Test PCToLine.
+	// TODO: Do much more than this.
+	pc, err := data.LookupSym("linefrompc")
+	if err != nil {
+		t.Fatal(err)
+	}
+	file, line, err := data.PCToLine(pc)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// We expect <longpath>/pclinetest.asm, line 13.
+	if !strings.HasSuffix(file, "/pclinetest.asm") {
+		t.Errorf("got %s; want %s", file, ".../pclinetest.asm")
+	}
+	if line != 13 {
+		t.Errorf("got %d; want %d", line, 13)
+	}
+}
diff --git a/debug/dwarf/symbol.go b/debug/dwarf/symbol.go
new file mode 100644
index 0000000..3b0b8b6
--- /dev/null
+++ b/debug/dwarf/symbol.go
@@ -0,0 +1,99 @@
+// Copyright 2014 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
+
+// This file provides simple methods to access the symbol table by name and address.
+
+import "fmt"
+
+// LookupSym returns the address of the named symbol.
+func (data *Data) LookupSym(name string) (uint64, error) {
+	r := data.Reader()
+	for {
+		entry, err := r.Next()
+		if err != nil {
+			return 0, err
+		}
+		if entry == nil {
+			// TODO: why don't we get an error here?
+			break
+		}
+		if entry.Tag != TagSubprogram {
+			continue
+		}
+		nameAttr := entry.LookupAttr(AttrName)
+		if nameAttr == nil {
+			// TODO: this shouldn't be possible.
+			continue
+		}
+		if nameAttr.(string) != name {
+			continue
+		}
+		addrAttr := entry.LookupAttr(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
+	}
+	return 0, fmt.Errorf("symbol %q not found", name)
+}
+
+// LookupPC returns the name of a symbol at the specified PC.
+func (data *Data) LookupPC(pc uint64) (string, error) {
+	entry, _, err := data.EntryForPC(pc)
+	if err != nil {
+		return "", err
+	}
+	nameAttr := entry.LookupAttr(AttrName)
+	if nameAttr == nil {
+		// TODO: this shouldn't be possible.
+		return "", fmt.Errorf("LookupPC: TODO")
+	}
+	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 (data *Data) EntryForPC(pc uint64) (entry *Entry, lowpc uint64, err error) {
+	// TODO: do something better than a linear scan?
+	r := data.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.LookupAttr(AttrLowpc).(uint64)
+		highpc, hok := entry.LookupAttr(AttrHighpc).(uint64)
+		if !lok || !hok || pc < lowpc || highpc <= pc {
+			continue
+		}
+		return entry, lowpc, nil
+	}
+	return nil, 0, fmt.Errorf("PC %#x not found", pc)
+}
+
+// LookupAttr returns the specified attribute for the entry.
+func (e *Entry) LookupAttr(a Attr) interface{} {
+	for _, f := range e.Field {
+		if f.Attr == a {
+			return f.Val
+		}
+	}
+	return nil
+}
diff --git a/debug/elf/file.go b/debug/elf/file.go
index 65c6b89..8491a77 100644
--- a/debug/elf/file.go
+++ b/debug/elf/file.go
@@ -576,7 +576,7 @@
 	// are the required ones, and the debug/dwarf package
 	// does not use the others, so don't bother loading them.
 	// r: added line.
-	var names = [...]string{"abbrev", "info", "line", "str"}
+	var names = [...]string{"abbrev", "frame", "info", "line", "str"}
 	var dat [len(names)][]byte
 	for i, name := range names {
 		name = ".debug_" + name
@@ -599,14 +599,14 @@
 		if err != nil {
 			return nil, err
 		}
-		err = f.applyRelocations(dat[1], data)
+		err = f.applyRelocations(dat[2], data)
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
-	d, err := dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
+	abbrev, frame, info, line, str := dat[0], dat[1], dat[2], dat[3], dat[4]
+	d, err := dwarf.New(abbrev, nil, frame, info, line, nil, nil, str)
 	if err != nil {
 		return nil, err
 	}
diff --git a/debug/elf/file_test.go b/debug/elf/file_test.go
index 6fb400a..5524173 100644
--- a/debug/elf/file_test.go
+++ b/debug/elf/file_test.go
@@ -167,11 +167,11 @@
 		} else {
 			f, err = Open(tt.file)
 		}
-		defer f.Close()
 		if err != nil {
 			t.Errorf("cannot open file %s: %v", tt.file, err)
 			continue
 		}
+		defer f.Close()
 		if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
 			t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr)
 			continue
diff --git a/debug/macho/file.go b/debug/macho/file.go
index 25ae73a..beab709 100644
--- a/debug/macho/file.go
+++ b/debug/macho/file.go
@@ -475,7 +475,7 @@
 	// There are many other DWARF sections, but these
 	// are the required ones, and the debug/dwarf package
 	// does not use the others, so don't bother loading them.
-	var names = [...]string{"abbrev", "info", "str"}
+	var names = [...]string{"abbrev", "frame", "info", "line", "str"}
 	var dat [len(names)][]byte
 	for i, name := range names {
 		name = "__debug_" + name
@@ -490,8 +490,8 @@
 		dat[i] = b
 	}
 
-	abbrev, info, str := dat[0], dat[1], dat[2]
-	return dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str)
+	abbrev, frame, info, line, str := dat[0], dat[1], dat[2], dat[3], dat[4]
+	return dwarf.New(abbrev, nil, frame, info, line, nil, nil, str)
 }
 
 // ImportedSymbols returns the names of all symbols
diff --git a/program/server/dwarf.go b/program/server/dwarf.go
index 6733485..43fe851 100644
--- a/program/server/dwarf.go
+++ b/program/server/dwarf.go
@@ -5,7 +5,6 @@
 package server
 
 import (
-	"fmt"
 	"regexp"
 
 	"code.google.com/p/ogle/debug/dwarf"
@@ -25,7 +24,7 @@
 		if entry.Tag != dwarf.TagSubprogram {
 			continue
 		}
-		nameAttr := lookupAttr(entry, dwarf.AttrName)
+		nameAttr := entry.LookupAttr(dwarf.AttrName)
 		if nameAttr == nil {
 			// TODO: this shouldn't be possible.
 			continue
@@ -40,89 +39,15 @@
 }
 
 func (s *Server) lookupSym(name string) (uint64, error) {
-	r := s.dwarfData.Reader()
-	for {
-		entry, err := r.Next()
-		if err != nil {
-			return 0, err
-		}
-		if entry == nil {
-			// TODO: why don't we get an error here.
-			break
-		}
-		if entry.Tag != dwarf.TagSubprogram {
-			continue
-		}
-		nameAttr := lookupAttr(entry, dwarf.AttrName)
-		if nameAttr == nil {
-			// TODO: this shouldn't be possible.
-			continue
-		}
-		if nameAttr.(string) != name {
-			continue
-		}
-		addrAttr := lookupAttr(entry, 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
-	}
-	return 0, fmt.Errorf("symbol %q not found", name)
+	return s.dwarfData.LookupSym(name)
 }
 
 func (s *Server) lookupPC(pc uint64) (string, error) {
-	entry, _, err := s.entryForPC(pc)
-	if err != nil {
-		return "", err
-	}
-	nameAttr := lookupAttr(entry, dwarf.AttrName)
-	if nameAttr == nil {
-		// TODO: this shouldn't be possible.
-		return "", fmt.Errorf("TODO")
-	}
-	name, ok := nameAttr.(string)
-	if !ok {
-		return "", fmt.Errorf("name for PC %#x is not a string", pc)
-	}
-	return name, nil
+	return s.dwarfData.LookupPC(pc)
 }
 
 func (s *Server) entryForPC(pc uint64) (entry *dwarf.Entry, lowpc uint64, err error) {
-	// TODO: do something better than a linear scan?
-	r := s.dwarfData.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 != dwarf.TagSubprogram {
-			continue
-		}
-		lowpc, lok := lookupAttr(entry, dwarf.AttrLowpc).(uint64)
-		highpc, hok := lookupAttr(entry, dwarf.AttrHighpc).(uint64)
-		if !lok || !hok || pc < lowpc || highpc <= pc {
-			continue
-		}
-		return entry, lowpc, nil
-	}
-	return nil, 0, fmt.Errorf("PC %#x not found", pc)
-}
-
-func lookupAttr(e *dwarf.Entry, a dwarf.Attr) interface{} {
-	for _, f := range e.Field {
-		if f.Attr == a {
-			return f.Val
-		}
-	}
-	return nil
+	return s.dwarfData.EntryForPC(pc)
 }
 
 // TODO: signedness? Return (x int64, ok bool)??