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
|
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{}
|
entries := []pctolineEntry{}
|
||||||
|
basePCs := []uint64{}
|
||||||
|
|
||||||
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
|
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
|
||||||
for {
|
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
|
// having two entries at the same PC address messes up the test
|
||||||
entries[len(entries)-1].file = ""
|
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
|
const samples = 1000
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
|
basePCIdx := 0
|
||||||
for pc := entries[0].pc; pc <= entries[0].pc+testSize; pc++ {
|
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 pc == entries[i].pc {
|
||||||
if i%samples == 0 && log {
|
if i%samples == 0 && log {
|
||||||
fmt.Printf("match %x / %x (%v)\n", pc, entries[len(entries)-1].pc, time.Since(t0)/samples)
|
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) {
|
func TestPCToLine(t *testing.T) {
|
||||||
lineInfos := loadBenchmarkData(t)
|
lineInfos := loadBenchmarkData(t)
|
||||||
|
|
||||||
entries := setupTestPCToLine(t, lineInfos)
|
entries, basePCs := setupTestPCToLine(t, lineInfos)
|
||||||
runTestPCToLine(t, lineInfos, entries, true, 0x50000)
|
runTestPCToLine(t, lineInfos, entries, basePCs, true, 0x50000)
|
||||||
t.Logf("restart form beginning")
|
t.Logf("restart form beginning")
|
||||||
runTestPCToLine(t, lineInfos, entries, true, 0x10000)
|
runTestPCToLine(t, lineInfos, entries, basePCs, true, 0x10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkPCToLine(b *testing.B) {
|
func BenchmarkPCToLine(b *testing.B) {
|
||||||
lineInfos := loadBenchmarkData(b)
|
lineInfos := loadBenchmarkData(b)
|
||||||
|
|
||||||
entries := setupTestPCToLine(b, lineInfos)
|
entries, basePCs := setupTestPCToLine(b, lineInfos)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
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 {
|
for op := range standardopcodes {
|
||||||
opcodes[op] = standardopcodes[op]
|
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
|
return sm
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,10 +193,10 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile stri
|
|||||||
if !sm.valid {
|
if !sm.valid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sm.address > end {
|
if (sm.address > end) && (end >= sm.lastAddress) {
|
||||||
break
|
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
|
lastaddr = sm.address
|
||||||
pcs = append(pcs, sm.address)
|
pcs = append(pcs, sm.address)
|
||||||
}
|
}
|
||||||
@ -265,12 +265,12 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
|
|||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sm.lastAddress > pc {
|
if sm.lastAddress > pc && sm.lastAddress != ^uint64(0) {
|
||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if sm.valid {
|
if sm.valid {
|
||||||
if sm.address > pc {
|
if (sm.address > pc) && (pc >= sm.lastAddress) {
|
||||||
return sm.lastFile, sm.lastLine, true
|
return sm.lastFile, sm.lastLine, true
|
||||||
}
|
}
|
||||||
if sm.address == pc {
|
if sm.address == pc {
|
||||||
@ -360,6 +360,7 @@ func (sm *StateMachine) next() error {
|
|||||||
sm.isa = 0
|
sm.isa = 0
|
||||||
sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1)
|
sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1)
|
||||||
sm.basicBlock = false
|
sm.basicBlock = false
|
||||||
|
sm.lastAddress = ^uint64(0)
|
||||||
}
|
}
|
||||||
b, err := sm.buf.ReadByte()
|
b, err := sm.buf.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -463,6 +464,7 @@ func endsequence(sm *StateMachine, buf *bytes.Buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setaddress(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
|
var addr uint64
|
||||||
|
|
||||||
binary.Read(buf, binary.LittleEndian, &addr)
|
binary.Read(buf, binary.LittleEndian, &addr)
|
||||||
|
@ -5,12 +5,15 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"debug/dwarf"
|
"debug/dwarf"
|
||||||
"debug/macho"
|
"debug/macho"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func slurpGzip(path string) ([]byte, error) {
|
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)
|
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