diff --git a/pkg/dwarf/line/_testdata/debug.grafana.debug.gz b/pkg/dwarf/line/_testdata/debug.grafana.debug.gz new file mode 100755 index 00000000..8e905b2b Binary files /dev/null and b/pkg/dwarf/line/_testdata/debug.grafana.debug.gz differ diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index d97f682b..af52eb8b 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "errors" "fmt" + "io" "github.com/derekparker/delve/pkg/dwarf/util" ) @@ -34,8 +35,6 @@ type StateMachine struct { // the compilation unit) valid bool - lastOpcodeKind opcodeKind - started bool buf *bytes.Buffer // remaining instructions @@ -272,7 +271,7 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 { for { if err := sm.next(); err != nil { - if lineInfo.Logf != nil { + if lineInfo.Logf != nil && err != io.EOF { lineInfo.Logf("LineToPC error: %v", err) } break @@ -313,38 +312,37 @@ func (sm *StateMachine) next() error { sm.started = true if sm.valid { sm.lastAddress, sm.lastFile, sm.lastLine = sm.address, sm.file, sm.line + + // valid is set by either a special opcode or a DW_LNS_copy, in both cases + // we need to reset basic_block, prologue_end and epilogue_begin + sm.basicBlock = false + sm.prologueEnd = false + sm.epilogueBegin = false } if sm.endSeq { sm.endSeq = false sm.file = sm.dbl.FileNames[0].Path sm.line = 1 sm.column = 0 - sm.isStmt = false + sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1) sm.basicBlock = false } - if sm.lastOpcodeKind == specialOpcode { - sm.basicBlock = false - sm.prologueEnd = false - sm.epilogueBegin = false - } b, err := sm.buf.ReadByte() if err != nil { return err } - if int(b) < len(sm.opcodes) { - if b == 0 { - sm.lastOpcodeKind = extendedOpcode + if b < sm.dbl.Prologue.OpcodeBase { + if int(b) < len(sm.opcodes) { + sm.valid = false + sm.opcodes[b](sm, sm.buf) } else { - sm.lastOpcodeKind = standardOpcode - } - sm.valid = false - sm.opcodes[b](sm, sm.buf) - } else if b < sm.dbl.Prologue.OpcodeBase { - // unimplemented standard opcode, read the number of arguments specified - // in the prologue and do nothing with them - opnum := sm.dbl.Prologue.StdOpLengths[b-1] - for i := 0; i < int(opnum); i++ { - util.DecodeSLEB128(sm.buf) + // unimplemented standard opcode, read the number of arguments specified + // in the prologue and do nothing with them + opnum := sm.dbl.Prologue.StdOpLengths[b-1] + for i := 0; i < int(opnum); i++ { + util.DecodeSLEB128(sm.buf) + } + fmt.Printf("unknown opcode\n") } } else { execSpecialOpcode(sm, b) @@ -361,8 +359,6 @@ func execSpecialOpcode(sm *StateMachine, instr byte) { sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange)) sm.line += sm.lastDelta sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength) - sm.basicBlock = false - sm.lastOpcodeKind = specialOpcode sm.valid = true } @@ -375,7 +371,6 @@ func execExtendedOpcode(sm *StateMachine, buf *bytes.Buffer) { } func copyfn(sm *StateMachine, buf *bytes.Buffer) { - sm.basicBlock = false sm.valid = true } diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go new file mode 100644 index 00000000..89cb418a --- /dev/null +++ b/pkg/dwarf/line/state_machine_test.go @@ -0,0 +1,128 @@ +package line + +import ( + "bytes" + "compress/gzip" + "debug/dwarf" + "debug/macho" + "fmt" + "io" + "io/ioutil" + "os" + "runtime" + "testing" +) + +func slurpGzip(path string) ([]byte, error) { + fh, err := os.Open(path) + if err != nil { + return nil, err + } + defer fh.Close() + gzin, err := gzip.NewReader(fh) + if err != nil { + return nil, err + } + defer gzin.Close() + return ioutil.ReadAll(gzin) +} + +const ( + newCompileUnit = "NEW COMPILE UNIT" + debugLineEnd = "END" +) + +func TestGrafana(t *testing.T) { + // Compares a full execution of our state machine on the debug_line section + // of grafana to the output generated using debug/dwarf.LineReader on the + // same section. + + if runtime.GOOS == "windows" { + t.Skip("filepath.Join ruins this test on windows") + } + + debugBytes, err := slurpGzip("_testdata/debug.grafana.debug.gz") + if err != nil { + t.Fatal(err) + } + exe, err := macho.NewFile(bytes.NewReader(debugBytes)) + if err != nil { + t.Fatal(err) + } + + sec := exe.Section("__debug_line") + debugLineBytes, err := sec.Data() + if err != nil { + t.Fatal(err) + } + + data, err := exe.DWARF() + if err != nil { + t.Fatal(err) + } + + debugLineBuffer := bytes.NewBuffer(debugLineBytes) + rdr := data.Reader() + for { + e, err := rdr.Next() + if err != nil { + t.Fatal(err) + } + if e == nil { + break + } + rdr.SkipChildren() + if e.Tag != dwarf.TagCompileUnit { + continue + } + cuname, _ := e.Val(dwarf.AttrName).(string) + + lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf) + sm := newStateMachine(lineInfo, lineInfo.Instructions) + + lnrdr, err := data.LineReader(e) + if err != nil { + t.Fatal(err) + } + + checkCompileUnit(t, cuname, lnrdr, sm) + } +} + +func checkCompileUnit(t *testing.T, cuname string, lnrdr *dwarf.LineReader, sm *StateMachine) { + var lne dwarf.LineEntry + for { + if err := sm.next(); err != nil { + if err != io.EOF { + t.Fatalf("state machine next error: %v", err) + } + break + } + if !sm.valid { + continue + } + + err := lnrdr.Next(&lne) + if err == io.EOF { + t.Fatalf("line reader ended before our state machine for compile unit %s", cuname) + } + if err != nil { + t.Fatal(err) + } + + tgt := fmt.Sprintf("%#x %s:%d isstmt:%v prologue_end:%v epilogue_begin:%v", lne.Address, lne.File.Name, lne.Line, lne.IsStmt, lne.PrologueEnd, lne.EpilogueBegin) + + out := fmt.Sprintf("%#x %s:%d isstmt:%v prologue_end:%v epilogue_begin:%v", sm.address, sm.file, sm.line, sm.isStmt, sm.prologueEnd, sm.epilogueBegin) + if out != tgt { + t.Errorf("mismatch:\n") + t.Errorf("got:\t%s\n", out) + t.Errorf("expected:\t%s\n", tgt) + t.Fatal("previous error") + } + } + + err := lnrdr.Next(&lne) + if err != io.EOF { + t.Fatalf("state machine ended before the line reader for compile unit %s", cuname) + } +}