diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go index 43cf657f..dd3b5f58 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -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) } } diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index d9f06b97..8ffd049e 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -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) diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index af189826..6e9e79eb 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -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) + } + } +}