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, ®s), 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, ®s) 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, ®s), 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 }