dwarf/line: Support for parsing multiple file tables

Support multiple file / directory tables for multiple compilation units.

- added a type DebugLines that can hold number of DebugLineInfo
- added a supporting attribute to DebugLineInfo called 'Lookup' which is to be
used to quickly lookup if file exists in FileNames slice
- added supporting methods to lookup and return corresponding DebugLineInfo
- changed the debug_line parsing behavior to read all the available tables and
push them to DebugLines

- since Process.lineInfo is now a slice, it was breaking AllPCsBetween as well
- updated that function's definition to accept a new filename parameter to be
able to extract related DebugLineInfo
- updated calls to AllPCsBetween

- fixed tests that were broken due to attribute type change in Process
- updated _fixtures/cgotest program to include stdio.h, so that it updates
.debug_line header
- added a test to check 'next' in a cgo binary
- OSX - 1.4 does not support cgo, handle that in new testcase
This commit is contained in:
omie 2015-08-28 13:36:22 +05:30 committed by Derek Parker
parent a0cffab881
commit d5e00a583d
7 changed files with 83 additions and 23 deletions

@ -1,12 +1,15 @@
package main package main
/* /*
#include <stdio.h>
char* foo(void) { return "hello, world!"; } char* foo(void) { return "hello, world!"; }
*/ */
import "C" import "C"
import "fmt" import "fmt"
import "runtime"
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println(C.GoString(C.foo())) fmt.Println(C.GoString(C.foo()))
} }

@ -8,9 +8,9 @@ import (
) )
type DebugLinePrologue struct { type DebugLinePrologue struct {
Length uint32 UnitLength uint32
Version uint16 Version uint16
PrologueLength uint32 Length uint32
MinInstrLength uint8 MinInstrLength uint8
InitialIsStmt uint8 InitialIsStmt uint8
LineBase int8 LineBase int8
@ -24,6 +24,7 @@ type DebugLineInfo struct {
IncludeDirs []string IncludeDirs []string
FileNames []*FileEntry FileNames []*FileEntry
Instructions []byte Instructions []byte
Lookup map[string]*FileEntry
} }
type FileEntry struct { type FileEntry struct {
@ -33,26 +34,51 @@ type FileEntry struct {
Length uint64 Length uint64
} }
func Parse(data []byte) *DebugLineInfo { type DebugLines []*DebugLineInfo
func (d *DebugLines) GetLineInfo(name string) *DebugLineInfo {
// Find in which table file exists and return it.
for _, l := range *d {
if _, ok := l.Lookup[name]; ok {
return l
}
}
return nil
}
func Parse(data []byte) DebugLines {
var ( var (
dbl = new(DebugLineInfo) lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data) buf = bytes.NewBuffer(data)
) )
parseDebugLinePrologue(dbl, buf) // We have to parse multiple file name tables here.
parseIncludeDirs(dbl, buf) for buf.Len() > 0 {
parseFileEntries(dbl, buf) dbl := new(DebugLineInfo)
dbl.Instructions = buf.Bytes() dbl.Lookup = make(map[string]*FileEntry)
return dbl parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf)
parseFileEntries(dbl, buf)
// Instructions size calculation breakdown:
// - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length.
// - dbl.Prologue.Length is the length of the prologue not including unit length, version or prologue length itself.
// - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)).
dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6))
lines = append(lines, dbl)
}
return lines
} }
func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) { func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
p := new(DebugLinePrologue) p := new(DebugLinePrologue)
p.Length = binary.LittleEndian.Uint32(buf.Next(4)) p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4))
p.Version = binary.LittleEndian.Uint16(buf.Next(2)) p.Version = binary.LittleEndian.Uint16(buf.Next(2))
p.PrologueLength = binary.LittleEndian.Uint32(buf.Next(4)) p.Length = binary.LittleEndian.Uint32(buf.Next(4))
p.MinInstrLength = uint8(buf.Next(1)[0]) p.MinInstrLength = uint8(buf.Next(1)[0])
p.InitialIsStmt = uint8(buf.Next(1)[0]) p.InitialIsStmt = uint8(buf.Next(1)[0])
p.LineBase = int8(buf.Next(1)[0]) p.LineBase = int8(buf.Next(1)[0])
@ -91,5 +117,6 @@ func parseFileEntries(info *DebugLineInfo, buf *bytes.Buffer) {
entry.Length, _ = util.DecodeULEB128(buf) entry.Length, _ = util.DecodeULEB128(buf)
info.FileNames = append(info.FileNames, entry) info.FileNames = append(info.FileNames, entry)
info.Lookup[name] = entry
} }
} }

@ -44,7 +44,8 @@ func TestDebugLinePrologueParser(t *testing.T) {
} }
defer os.Remove(p) defer os.Remove(p)
data := grabDebugLineSection(p, t) data := grabDebugLineSection(p, t)
dbl := Parse(data) debugLines := Parse(data)
dbl := debugLines[0]
prologue := dbl.Prologue prologue := dbl.Prologue
if prologue.Version != uint16(2) { if prologue.Version != uint16(2) {

@ -74,12 +74,13 @@ func newStateMachine(dbl *DebugLineInfo) *StateMachine {
// Returns all PCs for a given file/line. Useful for loops where the 'for' line // Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs. // could be split amongst 2 PCs.
func (dbl *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) { func (dbl *DebugLines) AllPCsForFileLine(f string, l int) (pcs []uint64) {
var ( var (
foundFile bool foundFile bool
lastAddr uint64 lastAddr uint64
sm = newStateMachine(dbl) lineInfo = dbl.GetLineInfo(f)
buf = bytes.NewBuffer(dbl.Instructions) sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
) )
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
@ -105,11 +106,12 @@ func (dbl *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) {
return return
} }
func (dbl *DebugLineInfo) AllPCsBetween(begin, end uint64) []uint64 { func (dbl *DebugLines) AllPCsBetween(begin, end uint64, filename string) []uint64 {
lineInfo := dbl.GetLineInfo(filename)
var ( var (
pcs []uint64 pcs []uint64
sm = newStateMachine(dbl) sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(dbl.Instructions) buf = bytes.NewBuffer(lineInfo.Instructions)
) )
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {

@ -38,7 +38,7 @@ type Process struct {
dwarf *dwarf.Data dwarf *dwarf.Data
goSymTable *gosym.Table goSymTable *gosym.Table
frameEntries frame.FrameDescriptionEntries frameEntries frame.FrameDescriptionEntries
lineInfo *line.DebugLineInfo lineInfo line.DebugLines
firstStart bool firstStart bool
os *OSProcessDetails os *OSProcessDetails
arch Arch arch Arch

@ -495,6 +495,33 @@ func TestSwitchThread(t *testing.T) {
}) })
} }
func TestCGONext(t *testing.T) {
// Test if one can do 'next' in a cgo binary
// On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973
if runtime.GOOS == "darwin" && strings.Contains(runtime.Version(), "1.4") {
return
}
withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) {
pc, err := p.FindFunctionLocation("main.main", true, 0)
if err != nil {
t.Fatal(err)
}
_, err = p.SetBreakpoint(pc)
if err != nil {
t.Fatal(err)
}
err = p.Continue()
if err != nil {
t.Fatal(err)
}
err = p.Next()
if err != nil {
t.Fatal(err)
}
})
}
type loc struct { type loc struct {
line int line int
fn string fn string

@ -168,7 +168,7 @@ func (thread *Thread) setNextBreakpoints() (err error) {
if filepath.Ext(loc.File) == ".go" { if filepath.Ext(loc.File) == ".go" {
err = thread.next(curpc, fde, loc.File, loc.Line) err = thread.next(curpc, fde, loc.File, loc.Line)
} else { } else {
err = thread.cnext(curpc, fde) err = thread.cnext(curpc, fde, loc.File)
} }
return err return err
} }
@ -226,8 +226,8 @@ func (thread *Thread) next(curpc uint64, fde *frame.FrameDescriptionEntry, file
// Set a breakpoint at every reachable location, as well as the return address. Without // Set a breakpoint at every reachable location, as well as the return address. Without
// the benefit of an AST we can't be sure we're not at a branching statement and thus // the benefit of an AST we can't be sure we're not at a branching statement and thus
// cannot accurately predict where we may end up. // cannot accurately predict where we may end up.
func (thread *Thread) cnext(curpc uint64, fde *frame.FrameDescriptionEntry) error { func (thread *Thread) cnext(curpc uint64, fde *frame.FrameDescriptionEntry, file string) error {
pcs := thread.dbp.lineInfo.AllPCsBetween(fde.Begin(), fde.End()) pcs := thread.dbp.lineInfo.AllPCsBetween(fde.Begin(), fde.End(), file)
ret, err := thread.ReturnAddress() ret, err := thread.ReturnAddress()
if err != nil { if err != nil {
return err return err