263 lines
6.6 KiB
Go
263 lines
6.6 KiB
Go
package line
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"debug/dwarf"
|
|
"debug/macho"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
|
|
pdwarf "github.com/go-delve/delve/pkg/dwarf"
|
|
"github.com/go-delve/delve/pkg/dwarf/leb128"
|
|
)
|
|
|
|
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 io.ReadAll(gzin)
|
|
}
|
|
|
|
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.
|
|
|
|
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, nil, t.Logf, 0, false, 8)
|
|
lineInfo.endSeqIsValid = true
|
|
sm := newStateMachine(lineInfo, lineInfo.Instructions, 8)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
ptrSize := ptrSizeByRuntimeArch()
|
|
|
|
write_DW_LNE_set_address := func(addr uint64) {
|
|
instr.WriteByte(0)
|
|
leb128.EncodeUnsigned(instr, 9) // 1 + ptr_size
|
|
instr.WriteByte(DW_LINE_set_address)
|
|
pdwarf.WriteUint(instr, binary.LittleEndian, ptrSize, 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)
|
|
leb128.EncodeUnsigned(instr, off)
|
|
}
|
|
|
|
write_DW_LNS_advance_line := func(off int64) {
|
|
instr.WriteByte(DW_LNS_advance_line)
|
|
leb128.EncodeSigned(instr, off)
|
|
}
|
|
|
|
write_DW_LNE_end_sequence := func() {
|
|
instr.WriteByte(0)
|
|
leb128.EncodeUnsigned(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(),
|
|
ptrSize: ptrSize,
|
|
}
|
|
|
|
// 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, lines.ptrSize)
|
|
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)
|
|
}
|
|
}
|
|
}
|