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)??