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:
parent
a0cffab881
commit
d5e00a583d
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user