提交 3f9875e2 编写于 作者: A aarzilli 提交者: Derek Parker

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
上级 8f1fc63d
......@@ -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
}
......
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)
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册