github.com/undoio/delve@v1.9.0/pkg/proc/dwarf_expr_test.go (about)

     1  // Tests for loading variables that have complex location expressions. They
     2  // are only produced for optimized code (for both Go and C) therefore we can
     3  // not get the compiler to produce them reliably enough for tests.
     4  
     5  package proc_test
     6  
     7  import (
     8  	"bytes"
     9  	"debug/dwarf"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"go/constant"
    13  	"testing"
    14  	"unsafe"
    15  
    16  	"github.com/undoio/delve/pkg/dwarf/dwarfbuilder"
    17  	"github.com/undoio/delve/pkg/dwarf/godwarf"
    18  	"github.com/undoio/delve/pkg/dwarf/op"
    19  	"github.com/undoio/delve/pkg/proc"
    20  	"github.com/undoio/delve/pkg/proc/linutil"
    21  )
    22  
    23  func ptrSizeByRuntimeArch() int {
    24  	return int(unsafe.Sizeof(uintptr(0)))
    25  }
    26  
    27  func fakeCFA() uint64 {
    28  	ptrSize := ptrSizeByRuntimeArch()
    29  	if ptrSize == 8 {
    30  		return 0xc420051d00
    31  	}
    32  	if ptrSize == 4 {
    33  		return 0xc4251d00
    34  	}
    35  	panic(fmt.Errorf("not support ptr size %d", ptrSize))
    36  }
    37  
    38  func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) (*proc.BinaryInfo, *dwarf.Data) {
    39  	abbrev, aranges, frame, info, line, pubnames, ranges, str, loc, err := dwb.Build()
    40  	assertNoError(err, t, "dwarfbuilder.Build")
    41  	dwdata, err := dwarf.New(abbrev, aranges, frame, info, line, pubnames, ranges, str)
    42  	assertNoError(err, t, "creating dwarf")
    43  
    44  	bi := proc.NewBinaryInfo("linux", "amd64")
    45  	bi.LoadImageFromData(dwdata, frame, line, loc)
    46  
    47  	return bi, dwdata
    48  }
    49  
    50  // fakeMemory implements proc.MemoryReadWriter by reading from a byte slice.
    51  // Byte 0 of "data"  is at address "base".
    52  type fakeMemory struct {
    53  	base uint64
    54  	data []byte
    55  }
    56  
    57  func newFakeMemory(base uint64, contents ...interface{}) *fakeMemory {
    58  	mem := &fakeMemory{base: base}
    59  	var buf bytes.Buffer
    60  	for _, x := range contents {
    61  		binary.Write(&buf, binary.LittleEndian, x)
    62  	}
    63  	mem.data = buf.Bytes()
    64  	return mem
    65  }
    66  
    67  func (mem *fakeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
    68  	if uint64(addr) < mem.base {
    69  		return 0, fmt.Errorf("read out of bounds %d %#x", len(data), addr)
    70  	}
    71  	start := uint64(addr) - mem.base
    72  	end := uint64(len(data)) + start
    73  	if end > uint64(len(mem.data)) {
    74  		panic(fmt.Errorf("read out of bounds %d %#x", len(data), addr))
    75  	}
    76  	copy(data, mem.data[start:end])
    77  	return len(data), nil
    78  }
    79  
    80  func (mem *fakeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
    81  	if uint64(addr) < mem.base {
    82  		return 0, fmt.Errorf("write out of bounds %d %#x", len(data), addr)
    83  	}
    84  	start := uint64(addr) - mem.base
    85  	end := uint64(len(data)) + start
    86  	if end > uint64(len(mem.data)) {
    87  		panic(fmt.Errorf("write out of bounds %d %#x", len(data), addr))
    88  	}
    89  	copy(mem.data[start:end], data)
    90  	return len(data), nil
    91  }
    92  
    93  func uintExprCheck(t *testing.T, scope *proc.EvalScope, expr string, tgt uint64) {
    94  	thevar, err := scope.EvalExpression(expr, normalLoadConfig)
    95  	assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", expr))
    96  	if thevar.Unreadable != nil {
    97  		t.Errorf("variable %q unreadable: %v", expr, thevar.Unreadable)
    98  	} else {
    99  		if v, _ := constant.Uint64Val(thevar.Value); v != tgt {
   100  			t.Errorf("expected value %x got %x for %q", tgt, v, expr)
   101  		}
   102  	}
   103  }
   104  
   105  func fakeScope(mem proc.MemoryReadWriter, regs *op.DwarfRegisters, bi *proc.BinaryInfo, fn *proc.Function) *proc.EvalScope {
   106  	return &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: fn}, Regs: *regs, Mem: mem, BinInfo: bi}
   107  }
   108  
   109  func dwarfExprCheck(t *testing.T, scope *proc.EvalScope, testCases map[string]uint16) {
   110  	for name, value := range testCases {
   111  		uintExprCheck(t, scope, name, uint64(value))
   112  	}
   113  }
   114  
   115  func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) *op.DwarfRegisters {
   116  	a := proc.AMD64Arch("linux")
   117  	so := bi.PCToImage(regs.PC())
   118  	dwarfRegs := a.RegistersToDwarfRegisters(so.StaticBase, regs)
   119  	dwarfRegs.CFA = int64(fakeCFA())
   120  	dwarfRegs.FrameBase = int64(fakeCFA())
   121  	return dwarfRegs
   122  }
   123  
   124  func TestDwarfExprRegisters(t *testing.T) {
   125  	testCases := map[string]uint16{
   126  		"a": 0x1234,
   127  		"b": 0x4321,
   128  		"c": 0x2143,
   129  	}
   130  
   131  	dwb := dwarfbuilder.New()
   132  
   133  	uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
   134  
   135  	dwb.AddSubprogram("main.main", 0x40100, 0x41000)
   136  	dwb.Attr(dwarf.AttrFrameBase, dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa))
   137  	dwb.AddVariable("a", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_reg0))
   138  	dwb.AddVariable("b", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_fbreg, int(8)))
   139  	dwb.AddVariable("c", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_regx, int(1)))
   140  	dwb.TagClose()
   141  
   142  	bi, _ := fakeBinaryInfo(t, dwb)
   143  
   144  	mainfn := bi.LookupFunc["main.main"]
   145  	mem := newFakeMemory(fakeCFA(), uint64(0), uint64(testCases["b"]))
   146  	regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}}
   147  	regs.Regs.Rax = uint64(testCases["a"])
   148  	regs.Regs.Rdx = uint64(testCases["c"])
   149  
   150  	dwarfExprCheck(t, fakeScope(mem, dwarfRegisters(bi, &regs), bi, mainfn), testCases)
   151  }
   152  
   153  func TestDwarfExprComposite(t *testing.T) {
   154  	testCases := map[string]uint16{
   155  		"pair.k":  0x8765,
   156  		"pair.v":  0x5678,
   157  		"n":       42,
   158  		"pair2.k": 0x8765,
   159  		"pair2.v": 0,
   160  	}
   161  
   162  	const stringVal = "this is a string"
   163  
   164  	dwb := dwarfbuilder.New()
   165  
   166  	uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
   167  	intoff := dwb.AddBaseType("int", dwarfbuilder.DW_ATE_signed, 8)
   168  
   169  	byteoff := dwb.AddBaseType("uint8", dwarfbuilder.DW_ATE_unsigned, 1)
   170  
   171  	byteptroff := dwb.AddPointerType("*uint8", byteoff)
   172  
   173  	pairoff := dwb.AddStructType("main.pair", 4)
   174  	dwb.Attr(godwarf.AttrGoKind, uint8(25))
   175  	dwb.AddMember("k", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(0)))
   176  	dwb.AddMember("v", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(2)))
   177  	dwb.TagClose()
   178  
   179  	stringoff := dwb.AddStructType("string", 16)
   180  	dwb.Attr(godwarf.AttrGoKind, uint8(24))
   181  	dwb.AddMember("str", byteptroff, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(0)))
   182  	dwb.AddMember("len", intoff, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(8)))
   183  	dwb.TagClose()
   184  
   185  	dwb.AddSubprogram("main.main", 0x40100, 0x41000)
   186  	dwb.AddVariable("pair", pairoff, dwarfbuilder.LocationBlock(
   187  		op.DW_OP_reg2, op.DW_OP_piece, uint(2),
   188  		op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(16), op.DW_OP_plus, op.DW_OP_piece, uint(2)))
   189  	dwb.AddVariable("s", stringoff, dwarfbuilder.LocationBlock(
   190  		op.DW_OP_reg1, op.DW_OP_piece, uint(8),
   191  		op.DW_OP_reg0, op.DW_OP_piece, uint(8)))
   192  	dwb.AddVariable("n", intoff, dwarfbuilder.LocationBlock(op.DW_OP_reg3))
   193  	dwb.AddVariable("pair2", pairoff, dwarfbuilder.LocationBlock(
   194  		op.DW_OP_reg2, op.DW_OP_piece, uint(2),
   195  		op.DW_OP_piece, uint(2)))
   196  	dwb.TagClose()
   197  
   198  	bi, _ := fakeBinaryInfo(t, dwb)
   199  
   200  	mainfn := bi.LookupFunc["main.main"]
   201  
   202  	mem := newFakeMemory(fakeCFA(), uint64(0), uint64(0), uint16(testCases["pair.v"]), []byte(stringVal))
   203  	var regs linutil.AMD64Registers
   204  	regs.Regs = &linutil.AMD64PtraceRegs{}
   205  	regs.Regs.Rax = uint64(len(stringVal))
   206  	regs.Regs.Rdx = fakeCFA() + 18
   207  	regs.Regs.Rcx = uint64(testCases["pair.k"])
   208  	regs.Regs.Rbx = uint64(testCases["n"])
   209  
   210  	dwarfRegs := dwarfRegisters(bi, &regs)
   211  	var changeCalls []string
   212  	dwarfRegs.ChangeFunc = func(regNum uint64, reg *op.DwarfRegister) error {
   213  		t.Logf("SetReg(%d, %x)", regNum, reg.Bytes)
   214  		changeCalls = append(changeCalls, fmt.Sprintf("%d - %x", regNum, reg.Bytes))
   215  		return nil
   216  	}
   217  
   218  	scope := fakeScope(mem, dwarfRegs, bi, mainfn)
   219  
   220  	dwarfExprCheck(t, scope, testCases)
   221  
   222  	thevar, err := scope.EvalExpression("s", normalLoadConfig)
   223  	assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", "s"))
   224  	if thevar.Unreadable != nil {
   225  		t.Errorf("variable \"s\" unreadable: %v", thevar.Unreadable)
   226  	} else {
   227  		if v := constant.StringVal(thevar.Value); v != stringVal {
   228  			t.Errorf("expected value %q got %q", stringVal, v)
   229  		}
   230  	}
   231  
   232  	// Test writes to composite memory
   233  
   234  	assertNoError(scope.SetVariable("n", "47"), t, "SetVariable(n, 47)")
   235  	assertNoError(scope.SetVariable("pair.k", "12"), t, "SetVariable(pair.k, 12)")
   236  	assertNoError(scope.SetVariable("pair.v", "13"), t, "SetVariable(pair.v, 13)")
   237  
   238  	for i := range changeCalls {
   239  		t.Logf("%q\n", changeCalls[i])
   240  	}
   241  
   242  	if len(changeCalls) != 2 {
   243  		t.Errorf("wrong number of calls to SetReg")
   244  	}
   245  	if changeCalls[0] != "3 - 2f00000000000000" {
   246  		t.Errorf("wrong call to SetReg (Rbx)")
   247  	}
   248  	if changeCalls[1] != "2 - 0c00000000000000" {
   249  		t.Errorf("wrong call to SetReg (Rcx)")
   250  	}
   251  	if mem.data[0x10] != 13 || mem.data[0x11] != 0x00 {
   252  		t.Errorf("memory was not written %v", mem.data[:2])
   253  	}
   254  }
   255  
   256  func TestDwarfExprLoclist(t *testing.T) {
   257  	const before = 0x1234
   258  	const after = 0x4321
   259  
   260  	dwb := dwarfbuilder.New()
   261  
   262  	uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
   263  
   264  	dwb.AddSubprogram("main.main", 0x40100, 0x41000)
   265  	dwb.AddVariable("a", uint16off, []dwarfbuilder.LocEntry{
   266  		{Lowpc: 0x40100, Highpc: 0x40700, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa)},
   267  		{Lowpc: 0x40700, Highpc: 0x41000, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(2), op.DW_OP_plus)},
   268  	})
   269  	dwb.TagClose()
   270  
   271  	bi, _ := fakeBinaryInfo(t, dwb)
   272  
   273  	mainfn := bi.LookupFunc["main.main"]
   274  
   275  	mem := newFakeMemory(fakeCFA(), uint16(before), uint16(after))
   276  	const PC = 0x40100
   277  	regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{Rip: PC}}
   278  
   279  	scope := &proc.EvalScope{Location: proc.Location{PC: PC, Fn: mainfn}, Regs: *dwarfRegisters(bi, &regs), Mem: mem, BinInfo: bi}
   280  
   281  	uintExprCheck(t, scope, "a", before)
   282  	scope.PC = 0x40800
   283  	scope.Regs.Reg(scope.Regs.PCRegNum).Uint64Val = scope.PC
   284  	uintExprCheck(t, scope, "a", after)
   285  }
   286  
   287  func TestIssue1419(t *testing.T) {
   288  	// trying to read a slice variable with a location list that tries to read
   289  	// from registers we don't have should not cause a panic.
   290  
   291  	dwb := dwarfbuilder.New()
   292  
   293  	uint64off := dwb.AddBaseType("uint64", dwarfbuilder.DW_ATE_unsigned, 8)
   294  	intoff := dwb.AddBaseType("int", dwarfbuilder.DW_ATE_signed, 8)
   295  	intptroff := dwb.AddPointerType("*int", intoff)
   296  
   297  	sliceoff := dwb.AddStructType("[]int", 24)
   298  	dwb.Attr(godwarf.AttrGoKind, uint8(23))
   299  	dwb.AddMember("array", intptroff, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(0)))
   300  	dwb.AddMember("len", uint64off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(8)))
   301  	dwb.AddMember("cap", uint64off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(16)))
   302  	dwb.TagClose()
   303  
   304  	dwb.AddSubprogram("main.main", 0x40100, 0x41000)
   305  	dwb.AddVariable("a", sliceoff, dwarfbuilder.LocationBlock(op.DW_OP_reg2, op.DW_OP_piece, uint(8), op.DW_OP_reg2, op.DW_OP_piece, uint(8), op.DW_OP_reg2, op.DW_OP_piece, uint(8)))
   306  	dwb.TagClose()
   307  
   308  	bi, _ := fakeBinaryInfo(t, dwb)
   309  
   310  	mainfn := bi.LookupFunc["main.main"]
   311  
   312  	mem := newFakeMemory(fakeCFA())
   313  
   314  	scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: op.DwarfRegisters{}, Mem: mem, BinInfo: bi}
   315  
   316  	va, err := scope.EvalExpression("a", normalLoadConfig)
   317  	assertNoError(err, t, "EvalExpression(a)")
   318  	t.Logf("%#x\n", va.Addr)
   319  	t.Logf("%v", va)
   320  	if va.Unreadable == nil {
   321  		t.Fatalf("expected 'a' to be unreadable but it wasn't")
   322  	}
   323  	if va.Unreadable.Error() != "could not read 8 bytes from register 2 (size: 0)" {
   324  		t.Fatalf("wrong unreadable reason for variable 'a': %v", va.Unreadable)
   325  	}
   326  }
   327  
   328  func TestLocationCovers(t *testing.T) {
   329  	dwb := dwarfbuilder.New()
   330  
   331  	uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
   332  
   333  	dwb.AddCompileUnit("main", 0x0)
   334  	dwb.AddSubprogram("main.main", 0x40100, 0x41000)
   335  	aOff := dwb.AddVariable("a", uint16off, []dwarfbuilder.LocEntry{
   336  		{Lowpc: 0x40100, Highpc: 0x40700, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa)},
   337  		{Lowpc: 0x40700, Highpc: 0x41000, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(2), op.DW_OP_plus)},
   338  	})
   339  	dwb.TagClose()
   340  	dwb.TagClose()
   341  
   342  	bi, dwdata := fakeBinaryInfo(t, dwb)
   343  
   344  	dwrdr := dwdata.Reader()
   345  	dwrdr.Seek(aOff)
   346  	aEntry, err := dwrdr.Next()
   347  	assertNoError(err, t, "reading 'a' entry")
   348  	ranges, err := bi.LocationCovers(aEntry, dwarf.AttrLocation)
   349  	assertNoError(err, t, "LocationCovers")
   350  	t.Logf("%x", ranges)
   351  	if fmt.Sprintf("%x", ranges) != "[[40100 40700] [40700 41000]]" {
   352  		t.Error("wrong value returned by LocationCover")
   353  	}
   354  
   355  }
   356  
   357  func TestIssue1636_InlineWithoutOrigin(t *testing.T) {
   358  	// Gcc (specifically GNU C++11 6.3.0) will emit DW_TAG_inlined_subroutine
   359  	// without a DW_AT_abstract_origin or a name. What is an inlined subroutine
   360  	// without a reference to an abstract origin or even a name? Regardless,
   361  	// Delve shouldn't crash.
   362  	dwb := dwarfbuilder.New()
   363  	dwb.AddCompileUnit("main", 0x0)
   364  	dwb.AddSubprogram("main.main", 0x40100, 0x41000)
   365  	dwb.TagOpen(dwarf.TagInlinedSubroutine, "")
   366  	dwb.TagClose()
   367  	dwb.TagClose()
   368  	dwb.TagClose()
   369  	fakeBinaryInfo(t, dwb)
   370  }
   371  
   372  func TestUnsupportedType(t *testing.T) {
   373  	// Tests that reading an unsupported type does not cause an error
   374  	dwb := dwarfbuilder.New()
   375  	dwb.AddCompileUnit("main", 0x0)
   376  	off := dwb.TagOpen(dwarf.TagReferenceType, "blah")
   377  	dwb.TagClose()
   378  	dwb.TagClose()
   379  	_, dw := fakeBinaryInfo(t, dwb)
   380  	_, err := godwarf.ReadType(dw, 0, off, make(map[dwarf.Offset]godwarf.Type))
   381  	if err != nil {
   382  		t.Errorf("unexpected error reading unsupported type: %#v", err)
   383  	}
   384  }
   385  
   386  func TestNestedCompileUnts(t *testing.T) {
   387  	// Tests that a compile unit with a nested entry that we don't care about
   388  	// (such as a DW_TAG_namespace) is read fully.
   389  	dwb := dwarfbuilder.New()
   390  	dwb.AddCompileUnit("main", 0x0)
   391  	dwb.TagOpen(dwarf.TagNamespace, "namespace")
   392  	dwb.AddVariable("var1", 0x0, uint64(0x0))
   393  	dwb.TagClose()
   394  	dwb.AddVariable("var2", 0x0, uint64(0x0))
   395  	dwb.TagClose()
   396  	bi, _ := fakeBinaryInfo(t, dwb)
   397  	if n := len(bi.PackageVars()); n != 2 {
   398  		t.Errorf("expected 2 variables, got %d", n)
   399  	}
   400  }
   401  
   402  func TestAbstractOriginDefinedAfterUse(t *testing.T) {
   403  	// Tests that an abstract origin entry can appear after its uses.
   404  	dwb := dwarfbuilder.New()
   405  	dwb.AddCompileUnit("main", 0x0)
   406  
   407  	// Concrete implementation
   408  	dwb.TagOpen(dwarf.TagSubprogram, "")
   409  	originRef1 := dwb.Attr(dwarf.AttrAbstractOrigin, dwarf.Offset(0))
   410  	dwb.Attr(dwarf.AttrLowpc, dwarfbuilder.Address(0x40100))
   411  	dwb.Attr(dwarf.AttrHighpc, dwarfbuilder.Address(0x41000))
   412  	dwb.TagClose()
   413  
   414  	// Inlined call
   415  	dwb.AddSubprogram("callingFn", 0x41100, 0x42000)
   416  	dwb.TagOpen(dwarf.TagInlinedSubroutine, "")
   417  	originRef2 := dwb.Attr(dwarf.AttrAbstractOrigin, dwarf.Offset(0))
   418  	dwb.Attr(dwarf.AttrLowpc, dwarfbuilder.Address(0x41150))
   419  	dwb.Attr(dwarf.AttrHighpc, dwarfbuilder.Address(0x41155))
   420  	dwb.Attr(dwarf.AttrCallFile, uint8(1))
   421  	dwb.Attr(dwarf.AttrCallLine, uint8(1))
   422  	dwb.TagClose()
   423  	dwb.TagClose()
   424  
   425  	// Abstract origin
   426  	abstractOriginOff := dwb.TagOpen(dwarf.TagSubprogram, "inlinedFn")
   427  	dwb.Attr(dwarf.AttrInline, uint8(1))
   428  	dwb.TagClose()
   429  
   430  	dwb.TagClose()
   431  
   432  	dwb.PatchOffset(originRef1, abstractOriginOff)
   433  	dwb.PatchOffset(originRef2, abstractOriginOff)
   434  
   435  	bi, _ := fakeBinaryInfo(t, dwb)
   436  	fn := bi.PCToFunc(0x40100)
   437  	if fn == nil {
   438  		t.Fatalf("could not find concrete instance of inlined function")
   439  	}
   440  }