dwarf/line: fix state machine behavior with multi-sequence units (#1681)
A compile unit can produce a debug_line program consisting of multiple sequences according to the DWARF standard. The standard guarantees that addresses monotonically increment within a single sequence but different sequences may not follow this rule. This commit changes dwarf/line (in particular PCToLine and AllPCsBetween) to support debug_line sections containing units with multiple sequences. TestPCToLine needs to be changed so that it picks valid addresses (i.e. addresses covered by a sequence) as values for basePC, instead of just rounding. Fixes #1694
This commit is contained in:
parent
ca6c6301cd
commit
fb3941324b
@ -210,8 +210,9 @@ func (entry *pctolineEntry) match(file string, line int) bool {
|
||||
return entry.file == file && entry.line == line
|
||||
}
|
||||
|
||||
func setupTestPCToLine(t testing.TB, lineInfos DebugLines) []pctolineEntry {
|
||||
func setupTestPCToLine(t testing.TB, lineInfos DebugLines) ([]pctolineEntry, []uint64) {
|
||||
entries := []pctolineEntry{}
|
||||
basePCs := []uint64{}
|
||||
|
||||
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
|
||||
for {
|
||||
@ -225,6 +226,9 @@ func setupTestPCToLine(t testing.TB, lineInfos DebugLines) []pctolineEntry {
|
||||
// having two entries at the same PC address messes up the test
|
||||
entries[len(entries)-1].file = ""
|
||||
}
|
||||
if len(basePCs) == 0 || sm.address-basePCs[len(basePCs)-1] >= 0x1000 {
|
||||
basePCs = append(basePCs, sm.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,16 +238,21 @@ func setupTestPCToLine(t testing.TB, lineInfos DebugLines) []pctolineEntry {
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
return entries, basePCs
|
||||
}
|
||||
|
||||
func runTestPCToLine(t testing.TB, lineInfos DebugLines, entries []pctolineEntry, log bool, testSize uint64) {
|
||||
func runTestPCToLine(t testing.TB, lineInfos DebugLines, entries []pctolineEntry, basePCs []uint64, log bool, testSize uint64) {
|
||||
const samples = 1000
|
||||
t0 := time.Now()
|
||||
|
||||
i := 0
|
||||
basePCIdx := 0
|
||||
for pc := entries[0].pc; pc <= entries[0].pc+testSize; pc++ {
|
||||
file, line := lineInfos[0].PCToLine(pc/0x1000*0x1000, pc)
|
||||
if basePCIdx+1 < len(basePCs) && pc >= basePCs[basePCIdx+1] {
|
||||
basePCIdx++
|
||||
}
|
||||
basePC := basePCs[basePCIdx]
|
||||
file, line := lineInfos[0].PCToLine(basePC, pc)
|
||||
if pc == entries[i].pc {
|
||||
if i%samples == 0 && log {
|
||||
fmt.Printf("match %x / %x (%v)\n", pc, entries[len(entries)-1].pc, time.Since(t0)/samples)
|
||||
@ -265,18 +274,18 @@ func runTestPCToLine(t testing.TB, lineInfos DebugLines, entries []pctolineEntry
|
||||
func TestPCToLine(t *testing.T) {
|
||||
lineInfos := loadBenchmarkData(t)
|
||||
|
||||
entries := setupTestPCToLine(t, lineInfos)
|
||||
runTestPCToLine(t, lineInfos, entries, true, 0x50000)
|
||||
entries, basePCs := setupTestPCToLine(t, lineInfos)
|
||||
runTestPCToLine(t, lineInfos, entries, basePCs, true, 0x50000)
|
||||
t.Logf("restart form beginning")
|
||||
runTestPCToLine(t, lineInfos, entries, true, 0x10000)
|
||||
runTestPCToLine(t, lineInfos, entries, basePCs, true, 0x10000)
|
||||
}
|
||||
|
||||
func BenchmarkPCToLine(b *testing.B) {
|
||||
lineInfos := loadBenchmarkData(b)
|
||||
|
||||
entries := setupTestPCToLine(b, lineInfos)
|
||||
entries, basePCs := setupTestPCToLine(b, lineInfos)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
runTestPCToLine(b, lineInfos, entries, false, 0x10000)
|
||||
runTestPCToLine(b, lineInfos, entries, basePCs, false, 0x10000)
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
|
||||
for op := range standardopcodes {
|
||||
opcodes[op] = standardopcodes[op]
|
||||
}
|
||||
sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase}
|
||||
sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase, lastAddress: ^uint64(0)}
|
||||
return sm
|
||||
}
|
||||
|
||||
@ -193,10 +193,10 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile stri
|
||||
if !sm.valid {
|
||||
continue
|
||||
}
|
||||
if sm.address > end {
|
||||
if (sm.address > end) && (end >= sm.lastAddress) {
|
||||
break
|
||||
}
|
||||
if (sm.address >= begin && sm.address > lastaddr) && sm.isStmt && ((sm.file != excludeFile) || (sm.line != excludeLine)) {
|
||||
if sm.address >= begin && sm.address <= end && sm.address > lastaddr && sm.isStmt && ((sm.file != excludeFile) || (sm.line != excludeLine)) {
|
||||
lastaddr = sm.address
|
||||
pcs = append(pcs, sm.address)
|
||||
}
|
||||
@ -265,12 +265,12 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
|
||||
return "", 0, false
|
||||
}
|
||||
}
|
||||
if sm.lastAddress > pc {
|
||||
if sm.lastAddress > pc && sm.lastAddress != ^uint64(0) {
|
||||
return "", 0, false
|
||||
}
|
||||
for {
|
||||
if sm.valid {
|
||||
if sm.address > pc {
|
||||
if (sm.address > pc) && (pc >= sm.lastAddress) {
|
||||
return sm.lastFile, sm.lastLine, true
|
||||
}
|
||||
if sm.address == pc {
|
||||
@ -360,6 +360,7 @@ func (sm *StateMachine) next() error {
|
||||
sm.isa = 0
|
||||
sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1)
|
||||
sm.basicBlock = false
|
||||
sm.lastAddress = ^uint64(0)
|
||||
}
|
||||
b, err := sm.buf.ReadByte()
|
||||
if err != nil {
|
||||
@ -463,6 +464,7 @@ func endsequence(sm *StateMachine, buf *bytes.Buffer) {
|
||||
}
|
||||
|
||||
func setaddress(sm *StateMachine, buf *bytes.Buffer) {
|
||||
//TODO: this needs to be changed to support 32bit architectures (addr must be target arch pointer sized) -- also target endianness
|
||||
var addr uint64
|
||||
|
||||
binary.Read(buf, binary.LittleEndian, &addr)
|
||||
|
@ -5,12 +5,15 @@ import (
|
||||
"compress/gzip"
|
||||
"debug/dwarf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
func slurpGzip(path string) ([]byte, error) {
|
||||
@ -126,3 +129,142 @@ func checkCompileUnit(t *testing.T, cuname string, lnrdr *dwarf.LineReader, sm *
|
||||
t.Fatalf("state machine ended before the line reader for compile unit %s", cuname)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleSequences(t *testing.T) {
|
||||
// Check that our state machine (specifically PCToLine and AllPCsBetween)
|
||||
// are correct when dealing with units containing more than one sequence.
|
||||
|
||||
const thefile = "thefile.go"
|
||||
|
||||
instr := bytes.NewBuffer(nil)
|
||||
|
||||
write_DW_LNE_set_address := func(addr uint64) {
|
||||
instr.WriteByte(0)
|
||||
util.EncodeULEB128(instr, 9) // 1 + ptr_size
|
||||
instr.WriteByte(DW_LINE_set_address)
|
||||
binary.Write(instr, binary.LittleEndian, addr)
|
||||
|
||||
}
|
||||
|
||||
write_DW_LNS_copy := func() {
|
||||
instr.WriteByte(DW_LNS_copy)
|
||||
}
|
||||
|
||||
write_DW_LNS_advance_pc := func(off uint64) {
|
||||
instr.WriteByte(DW_LNS_advance_pc)
|
||||
util.EncodeULEB128(instr, off)
|
||||
}
|
||||
|
||||
write_DW_LNS_advance_line := func(off int64) {
|
||||
instr.WriteByte(DW_LNS_advance_line)
|
||||
util.EncodeSLEB128(instr, off)
|
||||
}
|
||||
|
||||
write_DW_LNE_end_sequence := func() {
|
||||
instr.WriteByte(0)
|
||||
util.EncodeULEB128(instr, 1)
|
||||
instr.WriteByte(DW_LINE_end_sequence)
|
||||
}
|
||||
|
||||
write_DW_LNE_set_address(0x400000)
|
||||
write_DW_LNS_copy() // thefile.go:1 0x400000
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNS_advance_line(1)
|
||||
write_DW_LNS_copy() // thefile.go:2 0x400002
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNS_advance_line(1)
|
||||
write_DW_LNS_copy() // thefile.go:3 0x400004
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNE_end_sequence() // thefile.go:3 ends the byte before 0x400006
|
||||
|
||||
write_DW_LNE_set_address(0x600000)
|
||||
write_DW_LNS_advance_line(10)
|
||||
write_DW_LNS_copy() // thefile.go:11 0x600000
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNS_advance_line(1)
|
||||
write_DW_LNS_copy() // thefile.go:12 0x600002
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNS_advance_line(1)
|
||||
write_DW_LNS_copy() // thefile.go:13 0x600004
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNE_end_sequence() // thefile.go:13 ends the byte before 0x600006
|
||||
|
||||
write_DW_LNE_set_address(0x500000)
|
||||
write_DW_LNS_advance_line(20)
|
||||
write_DW_LNS_copy() // thefile.go:21 0x500000
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNS_advance_line(1)
|
||||
write_DW_LNS_copy() // thefile.go:22 0x500002
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNS_advance_line(1)
|
||||
write_DW_LNS_copy() // thefile.go:23 0x500004
|
||||
write_DW_LNS_advance_pc(0x2)
|
||||
write_DW_LNE_end_sequence() // thefile.go:23 ends the byte before 0x500006
|
||||
|
||||
lines := &DebugLineInfo{
|
||||
Prologue: &DebugLinePrologue{
|
||||
UnitLength: 1,
|
||||
Version: 2,
|
||||
MinInstrLength: 1,
|
||||
InitialIsStmt: 1,
|
||||
LineBase: -3,
|
||||
LineRange: 12,
|
||||
OpcodeBase: 13,
|
||||
StdOpLengths: []uint8{0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1},
|
||||
},
|
||||
IncludeDirs: []string{},
|
||||
FileNames: []*FileEntry{&FileEntry{Path: thefile}},
|
||||
Instructions: instr.Bytes(),
|
||||
}
|
||||
|
||||
// Test that PCToLine is correct for all three sequences
|
||||
|
||||
for _, testCase := range []struct {
|
||||
pc uint64
|
||||
line int
|
||||
}{
|
||||
{0x400000, 1},
|
||||
{0x400002, 2},
|
||||
{0x400004, 3},
|
||||
|
||||
{0x500000, 21},
|
||||
{0x500002, 22},
|
||||
{0x500004, 23},
|
||||
|
||||
{0x600000, 11},
|
||||
{0x600002, 12},
|
||||
{0x600004, 13},
|
||||
} {
|
||||
sm := newStateMachine(lines, lines.Instructions)
|
||||
file, curline, ok := sm.PCToLine(testCase.pc)
|
||||
if !ok {
|
||||
t.Fatalf("Could not find %#x", testCase.pc)
|
||||
}
|
||||
if file != thefile {
|
||||
t.Fatalf("Wrong file returned for %#x %q", testCase.pc, file)
|
||||
}
|
||||
if curline != testCase.line {
|
||||
t.Errorf("Wrong line returned for %#x: got %d expected %d", testCase.pc, curline, testCase.line)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Test that AllPCsBetween is correct for all three sequences
|
||||
for _, testCase := range []struct {
|
||||
start, end uint64
|
||||
tgt []uint64
|
||||
}{
|
||||
{0x400000, 0x400005, []uint64{0x400000, 0x400002, 0x400004}},
|
||||
{0x500000, 0x500005, []uint64{0x500000, 0x500002, 0x500004}},
|
||||
{0x600000, 0x600005, []uint64{0x600000, 0x600002, 0x600004}},
|
||||
} {
|
||||
out, err := lines.AllPCsBetween(testCase.start, testCase.end, "", -1)
|
||||
if err != nil {
|
||||
t.Fatalf("AllPCsBetween(%#x, %#x): %v", testCase.start, testCase.end, err)
|
||||
}
|
||||
|
||||
if len(out) != len(testCase.tgt) {
|
||||
t.Errorf("AllPCsBetween(%#x, %#x): expected: %#x got: %#x", testCase.start, testCase.end, testCase.tgt, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user