diff --git a/_fixtures/cgotest.go b/_fixtures/cgotest.go index c19d8c3c..e5f2dd50 100644 --- a/_fixtures/cgotest.go +++ b/_fixtures/cgotest.go @@ -1,12 +1,15 @@ package main /* +#include char* foo(void) { return "hello, world!"; } */ import "C" import "fmt" +import "runtime" func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) fmt.Println(C.GoString(C.foo())) } diff --git a/dwarf/line/line_parser.go b/dwarf/line/line_parser.go index aa9e80f8..def5a119 100644 --- a/dwarf/line/line_parser.go +++ b/dwarf/line/line_parser.go @@ -8,9 +8,9 @@ import ( ) type DebugLinePrologue struct { - Length uint32 + UnitLength uint32 Version uint16 - PrologueLength uint32 + Length uint32 MinInstrLength uint8 InitialIsStmt uint8 LineBase int8 @@ -24,6 +24,7 @@ type DebugLineInfo struct { IncludeDirs []string FileNames []*FileEntry Instructions []byte + Lookup map[string]*FileEntry } type FileEntry struct { @@ -33,26 +34,51 @@ type FileEntry struct { 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 ( - dbl = new(DebugLineInfo) - buf = bytes.NewBuffer(data) + lines = make(DebugLines, 0) + buf = bytes.NewBuffer(data) ) - parseDebugLinePrologue(dbl, buf) - parseIncludeDirs(dbl, buf) - parseFileEntries(dbl, buf) - dbl.Instructions = buf.Bytes() + // We have to parse multiple file name tables here. + for buf.Len() > 0 { + dbl := new(DebugLineInfo) + 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) { 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.PrologueLength = binary.LittleEndian.Uint32(buf.Next(4)) + p.Length = binary.LittleEndian.Uint32(buf.Next(4)) p.MinInstrLength = uint8(buf.Next(1)[0]) p.InitialIsStmt = uint8(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) info.FileNames = append(info.FileNames, entry) + info.Lookup[name] = entry } } diff --git a/dwarf/line/line_parser_test.go b/dwarf/line/line_parser_test.go index 4e558109..d10acca2 100644 --- a/dwarf/line/line_parser_test.go +++ b/dwarf/line/line_parser_test.go @@ -44,7 +44,8 @@ func TestDebugLinePrologueParser(t *testing.T) { } defer os.Remove(p) data := grabDebugLineSection(p, t) - dbl := Parse(data) + debugLines := Parse(data) + dbl := debugLines[0] prologue := dbl.Prologue if prologue.Version != uint16(2) { diff --git a/dwarf/line/state_machine.go b/dwarf/line/state_machine.go index 64c30e71..b9255f85 100644 --- a/dwarf/line/state_machine.go +++ b/dwarf/line/state_machine.go @@ -74,12 +74,13 @@ func newStateMachine(dbl *DebugLineInfo) *StateMachine { // Returns all PCs for a given file/line. Useful for loops where the 'for' line // 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 ( foundFile bool lastAddr uint64 - sm = newStateMachine(dbl) - buf = bytes.NewBuffer(dbl.Instructions) + lineInfo = dbl.GetLineInfo(f) + sm = newStateMachine(lineInfo) + buf = bytes.NewBuffer(lineInfo.Instructions) ) 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 } -func (dbl *DebugLineInfo) AllPCsBetween(begin, end uint64) []uint64 { +func (dbl *DebugLines) AllPCsBetween(begin, end uint64, filename string) []uint64 { + lineInfo := dbl.GetLineInfo(filename) var ( pcs []uint64 - sm = newStateMachine(dbl) - buf = bytes.NewBuffer(dbl.Instructions) + sm = newStateMachine(lineInfo) + buf = bytes.NewBuffer(lineInfo.Instructions) ) for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { diff --git a/proc/proc.go b/proc/proc.go index e9d31d26..97d37b92 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -38,7 +38,7 @@ type Process struct { dwarf *dwarf.Data goSymTable *gosym.Table frameEntries frame.FrameDescriptionEntries - lineInfo *line.DebugLineInfo + lineInfo line.DebugLines firstStart bool os *OSProcessDetails arch Arch diff --git a/proc/proc_test.go b/proc/proc_test.go index b9c21376..c685fbe7 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -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 { line int fn string diff --git a/proc/threads.go b/proc/threads.go index c6757141..8c32801b 100644 --- a/proc/threads.go +++ b/proc/threads.go @@ -168,7 +168,7 @@ func (thread *Thread) setNextBreakpoints() (err error) { if filepath.Ext(loc.File) == ".go" { err = thread.next(curpc, fde, loc.File, loc.Line) } else { - err = thread.cnext(curpc, fde) + err = thread.cnext(curpc, fde, loc.File) } 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 // 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. -func (thread *Thread) cnext(curpc uint64, fde *frame.FrameDescriptionEntry) error { - pcs := thread.dbp.lineInfo.AllPCsBetween(fde.Begin(), fde.End()) +func (thread *Thread) cnext(curpc uint64, fde *frame.FrameDescriptionEntry, file string) error { + pcs := thread.dbp.lineInfo.AllPCsBetween(fde.Begin(), fde.End(), file) ret, err := thread.ReturnAddress() if err != nil { return err