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