dwarf/line: fix some bugs with the state machine
Adds a test that compares the output of our state machine with the output of the debug_line reader in the standard library and checks that they produce the same output for the debug_line section of grafana as compiled on macOS (which is the most interesting case since it uses cgo and therefore goes through dsymutil). A few bugs were uncovered and fixed: 1. is_stmt was reset improperly after a DW_LNS_end_sequence instruction 2. basic_block, prologue_end and epilogue_begin were not reset after a DW_LNS_copy instruction 3. some opcodes were not decoded properly if the debug_line section declares fewer standard opcodes than we know about. Fixes #1282
This commit is contained in:
parent
8f1fc63da8
commit
3f9875e272
BIN
pkg/dwarf/line/_testdata/debug.grafana.debug.gz
Executable file
BIN
pkg/dwarf/line/_testdata/debug.grafana.debug.gz
Executable file
Binary file not shown.
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
@ -34,8 +35,6 @@ type StateMachine struct {
|
|||||||
// the compilation unit)
|
// the compilation unit)
|
||||||
valid bool
|
valid bool
|
||||||
|
|
||||||
lastOpcodeKind opcodeKind
|
|
||||||
|
|
||||||
started bool
|
started bool
|
||||||
|
|
||||||
buf *bytes.Buffer // remaining instructions
|
buf *bytes.Buffer // remaining instructions
|
||||||
@ -272,7 +271,7 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
if err := sm.next(); err != nil {
|
if err := sm.next(); err != nil {
|
||||||
if lineInfo.Logf != nil {
|
if lineInfo.Logf != nil && err != io.EOF {
|
||||||
lineInfo.Logf("LineToPC error: %v", err)
|
lineInfo.Logf("LineToPC error: %v", err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -313,38 +312,37 @@ func (sm *StateMachine) next() error {
|
|||||||
sm.started = true
|
sm.started = true
|
||||||
if sm.valid {
|
if sm.valid {
|
||||||
sm.lastAddress, sm.lastFile, sm.lastLine = sm.address, sm.file, sm.line
|
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 {
|
if sm.endSeq {
|
||||||
sm.endSeq = false
|
sm.endSeq = false
|
||||||
sm.file = sm.dbl.FileNames[0].Path
|
sm.file = sm.dbl.FileNames[0].Path
|
||||||
sm.line = 1
|
sm.line = 1
|
||||||
sm.column = 0
|
sm.column = 0
|
||||||
sm.isStmt = false
|
sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1)
|
||||||
sm.basicBlock = false
|
sm.basicBlock = false
|
||||||
}
|
}
|
||||||
if sm.lastOpcodeKind == specialOpcode {
|
|
||||||
sm.basicBlock = false
|
|
||||||
sm.prologueEnd = false
|
|
||||||
sm.epilogueBegin = false
|
|
||||||
}
|
|
||||||
b, err := sm.buf.ReadByte()
|
b, err := sm.buf.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if int(b) < len(sm.opcodes) {
|
if b < sm.dbl.Prologue.OpcodeBase {
|
||||||
if b == 0 {
|
if int(b) < len(sm.opcodes) {
|
||||||
sm.lastOpcodeKind = extendedOpcode
|
sm.valid = false
|
||||||
|
sm.opcodes[b](sm, sm.buf)
|
||||||
} else {
|
} else {
|
||||||
sm.lastOpcodeKind = standardOpcode
|
// unimplemented standard opcode, read the number of arguments specified
|
||||||
}
|
// in the prologue and do nothing with them
|
||||||
sm.valid = false
|
opnum := sm.dbl.Prologue.StdOpLengths[b-1]
|
||||||
sm.opcodes[b](sm, sm.buf)
|
for i := 0; i < int(opnum); i++ {
|
||||||
} else if b < sm.dbl.Prologue.OpcodeBase {
|
util.DecodeSLEB128(sm.buf)
|
||||||
// unimplemented standard opcode, read the number of arguments specified
|
}
|
||||||
// in the prologue and do nothing with them
|
fmt.Printf("unknown opcode\n")
|
||||||
opnum := sm.dbl.Prologue.StdOpLengths[b-1]
|
|
||||||
for i := 0; i < int(opnum); i++ {
|
|
||||||
util.DecodeSLEB128(sm.buf)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
execSpecialOpcode(sm, b)
|
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.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange))
|
||||||
sm.line += sm.lastDelta
|
sm.line += sm.lastDelta
|
||||||
sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength)
|
sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength)
|
||||||
sm.basicBlock = false
|
|
||||||
sm.lastOpcodeKind = specialOpcode
|
|
||||||
sm.valid = true
|
sm.valid = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +371,6 @@ func execExtendedOpcode(sm *StateMachine, buf *bytes.Buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func copyfn(sm *StateMachine, buf *bytes.Buffer) {
|
func copyfn(sm *StateMachine, buf *bytes.Buffer) {
|
||||||
sm.basicBlock = false
|
|
||||||
sm.valid = true
|
sm.valid = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
128
pkg/dwarf/line/state_machine_test.go
Normal file
128
pkg/dwarf/line/state_machine_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user