2017-08-22 14:40:01 +00:00
|
|
|
// Tests for loading variables that have complex location expressions. They
|
|
|
|
// are only produced for optimized code (for both Go and C) therefore we can
|
|
|
|
// not get the compiler to produce them reliably enough for tests.
|
|
|
|
|
|
|
|
package proc_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"debug/dwarf"
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"go/constant"
|
|
|
|
"testing"
|
2020-03-10 16:34:40 +00:00
|
|
|
"unsafe"
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2019-01-04 18:39:25 +00:00
|
|
|
"github.com/go-delve/delve/pkg/dwarf/dwarfbuilder"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
|
|
|
"github.com/go-delve/delve/pkg/proc"
|
|
|
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
2017-08-22 14:40:01 +00:00
|
|
|
)
|
|
|
|
|
2020-03-10 16:34:40 +00:00
|
|
|
func ptrSizeByRuntimeArch() int {
|
|
|
|
return int(unsafe.Sizeof(uintptr(0)))
|
|
|
|
}
|
|
|
|
|
|
|
|
func fakeCFA() uint64 {
|
|
|
|
ptrSize := ptrSizeByRuntimeArch()
|
|
|
|
if ptrSize == 8 {
|
|
|
|
return 0xc420051d00
|
|
|
|
}
|
|
|
|
if ptrSize == 4 {
|
|
|
|
return 0xc4251d00
|
|
|
|
}
|
|
|
|
panic(fmt.Errorf("not support ptr size %d", ptrSize))
|
|
|
|
}
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2019-06-24 15:02:14 +00:00
|
|
|
func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) (*proc.BinaryInfo, *dwarf.Data) {
|
2017-08-24 07:46:47 +00:00
|
|
|
abbrev, aranges, frame, info, line, pubnames, ranges, str, loc, err := dwb.Build()
|
2018-03-20 10:05:35 +00:00
|
|
|
assertNoError(err, t, "dwarfbuilder.Build")
|
2017-08-22 14:40:01 +00:00
|
|
|
dwdata, err := dwarf.New(abbrev, aranges, frame, info, line, pubnames, ranges, str)
|
|
|
|
assertNoError(err, t, "creating dwarf")
|
|
|
|
|
|
|
|
bi := proc.NewBinaryInfo("linux", "amd64")
|
2019-05-08 21:06:38 +00:00
|
|
|
bi.LoadImageFromData(dwdata, frame, line, loc)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2019-06-24 15:02:14 +00:00
|
|
|
return bi, dwdata
|
2017-08-22 14:40:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// fakeMemory implements proc.MemoryReadWriter by reading from a byte slice.
|
|
|
|
// Byte 0 of "data" is at address "base".
|
|
|
|
type fakeMemory struct {
|
|
|
|
base uint64
|
|
|
|
data []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func newFakeMemory(base uint64, contents ...interface{}) *fakeMemory {
|
|
|
|
mem := &fakeMemory{base: base}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
for _, x := range contents {
|
|
|
|
binary.Write(&buf, binary.LittleEndian, x)
|
|
|
|
}
|
|
|
|
mem.data = buf.Bytes()
|
|
|
|
return mem
|
|
|
|
}
|
|
|
|
|
2020-09-09 17:36:15 +00:00
|
|
|
func (mem *fakeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
|
2023-11-14 15:36:55 +00:00
|
|
|
if addr < mem.base {
|
2017-08-22 14:40:01 +00:00
|
|
|
return 0, fmt.Errorf("read out of bounds %d %#x", len(data), addr)
|
|
|
|
}
|
2023-11-14 15:36:55 +00:00
|
|
|
start := addr - mem.base
|
2017-08-22 14:40:01 +00:00
|
|
|
end := uint64(len(data)) + start
|
|
|
|
if end > uint64(len(mem.data)) {
|
|
|
|
panic(fmt.Errorf("read out of bounds %d %#x", len(data), addr))
|
|
|
|
}
|
|
|
|
copy(data, mem.data[start:end])
|
|
|
|
return len(data), nil
|
|
|
|
}
|
|
|
|
|
2021-03-04 18:28:28 +00:00
|
|
|
func (mem *fakeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
|
2023-11-14 15:36:55 +00:00
|
|
|
if addr < mem.base {
|
2021-03-04 18:28:28 +00:00
|
|
|
return 0, fmt.Errorf("write out of bounds %d %#x", len(data), addr)
|
|
|
|
}
|
2023-11-14 15:36:55 +00:00
|
|
|
start := addr - mem.base
|
2021-03-04 18:28:28 +00:00
|
|
|
end := uint64(len(data)) + start
|
|
|
|
if end > uint64(len(mem.data)) {
|
|
|
|
panic(fmt.Errorf("write out of bounds %d %#x", len(data), addr))
|
|
|
|
}
|
|
|
|
copy(mem.data[start:end], data)
|
|
|
|
return len(data), nil
|
2017-08-22 14:40:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func uintExprCheck(t *testing.T, scope *proc.EvalScope, expr string, tgt uint64) {
|
|
|
|
thevar, err := scope.EvalExpression(expr, normalLoadConfig)
|
|
|
|
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", expr))
|
|
|
|
if thevar.Unreadable != nil {
|
|
|
|
t.Errorf("variable %q unreadable: %v", expr, thevar.Unreadable)
|
|
|
|
} else {
|
|
|
|
if v, _ := constant.Uint64Val(thevar.Value); v != tgt {
|
|
|
|
t.Errorf("expected value %x got %x for %q", tgt, v, expr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-28 17:00:26 +00:00
|
|
|
func fakeScope(mem proc.MemoryReadWriter, regs *op.DwarfRegisters, bi *proc.BinaryInfo, fn *proc.Function) *proc.EvalScope {
|
|
|
|
return &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: fn}, Regs: *regs, Mem: mem, BinInfo: bi}
|
2021-03-04 18:28:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func dwarfExprCheck(t *testing.T, scope *proc.EvalScope, testCases map[string]uint16) {
|
2017-08-22 14:40:01 +00:00
|
|
|
for name, value := range testCases {
|
|
|
|
uintExprCheck(t, scope, name, uint64(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-15 12:56:00 +00:00
|
|
|
func exprToStringCheck(t *testing.T, scope *proc.EvalScope, exprToStringCheck map[string]string) {
|
|
|
|
for name, expr := range exprToStringCheck {
|
|
|
|
thevar, err := scope.EvalExpression(name, normalLoadConfig)
|
|
|
|
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", name))
|
|
|
|
out := thevar.LocationExpr.String()
|
|
|
|
t.Logf("%q -> %q\n", name, out)
|
|
|
|
if out != expr {
|
|
|
|
t.Errorf("%q expected expression: %q got %q", name, expr, out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-28 17:00:26 +00:00
|
|
|
func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) *op.DwarfRegisters {
|
2017-08-24 07:46:47 +00:00
|
|
|
a := proc.AMD64Arch("linux")
|
2019-08-08 03:15:20 +00:00
|
|
|
so := bi.PCToImage(regs.PC())
|
|
|
|
dwarfRegs := a.RegistersToDwarfRegisters(so.StaticBase, regs)
|
2020-03-10 16:34:40 +00:00
|
|
|
dwarfRegs.CFA = int64(fakeCFA())
|
|
|
|
dwarfRegs.FrameBase = int64(fakeCFA())
|
2017-08-24 07:46:47 +00:00
|
|
|
return dwarfRegs
|
|
|
|
}
|
|
|
|
|
2017-08-22 14:40:01 +00:00
|
|
|
func TestDwarfExprRegisters(t *testing.T) {
|
|
|
|
testCases := map[string]uint16{
|
|
|
|
"a": 0x1234,
|
|
|
|
"b": 0x4321,
|
|
|
|
"c": 0x2143,
|
|
|
|
}
|
|
|
|
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddSubprogram("main.main", 0x40100, 0x41000)
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.Attr(dwarf.AttrFrameBase, dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa))
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddVariable("a", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_reg0))
|
|
|
|
dwb.AddVariable("b", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_fbreg, int(8)))
|
|
|
|
dwb.AddVariable("c", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_regx, int(1)))
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.TagClose()
|
|
|
|
|
2019-06-24 15:02:14 +00:00
|
|
|
bi, _ := fakeBinaryInfo(t, dwb)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2023-03-22 18:38:09 +00:00
|
|
|
mainfn := bi.LookupFunc()["main.main"][0]
|
2020-03-10 16:34:40 +00:00
|
|
|
mem := newFakeMemory(fakeCFA(), uint64(0), uint64(testCases["b"]))
|
2018-10-17 14:55:53 +00:00
|
|
|
regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}}
|
|
|
|
regs.Regs.Rax = uint64(testCases["a"])
|
|
|
|
regs.Regs.Rdx = uint64(testCases["c"])
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2021-03-04 18:28:28 +00:00
|
|
|
dwarfExprCheck(t, fakeScope(mem, dwarfRegisters(bi, ®s), bi, mainfn), testCases)
|
2017-08-22 14:40:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDwarfExprComposite(t *testing.T) {
|
|
|
|
testCases := map[string]uint16{
|
2021-05-17 17:26:49 +00:00
|
|
|
"pair.k": 0x8765,
|
|
|
|
"pair.v": 0x5678,
|
|
|
|
"n": 42,
|
|
|
|
"pair2.k": 0x8765,
|
|
|
|
"pair2.v": 0,
|
2017-08-22 14:40:01 +00:00
|
|
|
}
|
|
|
|
|
2022-07-15 12:56:00 +00:00
|
|
|
testCasesExprToString := map[string]string{
|
|
|
|
"pair": "[block] DW_OP_reg2(Rcx) DW_OP_piece 0x2 DW_OP_call_frame_cfa DW_OP_consts 0x10 DW_OP_plus DW_OP_piece 0x2 ",
|
|
|
|
"n": "[block] DW_OP_reg3(Rbx) ",
|
|
|
|
"pair2": "[block] DW_OP_reg2(Rcx) DW_OP_piece 0x2 DW_OP_piece 0x2 ",
|
|
|
|
}
|
|
|
|
|
2017-08-22 14:40:01 +00:00
|
|
|
const stringVal = "this is a string"
|
|
|
|
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
|
|
|
|
intoff := dwb.AddBaseType("int", dwarfbuilder.DW_ATE_signed, 8)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
byteoff := dwb.AddBaseType("uint8", dwarfbuilder.DW_ATE_unsigned, 1)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2018-11-22 13:42:11 +00:00
|
|
|
byteptroff := dwb.AddPointerType("*uint8", byteoff)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
pairoff := dwb.AddStructType("main.pair", 4)
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.Attr(godwarf.AttrGoKind, uint8(25))
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddMember("k", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(0)))
|
|
|
|
dwb.AddMember("v", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(2)))
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.TagClose()
|
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
stringoff := dwb.AddStructType("string", 16)
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.Attr(godwarf.AttrGoKind, uint8(24))
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddMember("str", byteptroff, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(0)))
|
|
|
|
dwb.AddMember("len", intoff, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(8)))
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.TagClose()
|
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddSubprogram("main.main", 0x40100, 0x41000)
|
|
|
|
dwb.AddVariable("pair", pairoff, dwarfbuilder.LocationBlock(
|
2017-08-22 14:40:01 +00:00
|
|
|
op.DW_OP_reg2, op.DW_OP_piece, uint(2),
|
|
|
|
op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(16), op.DW_OP_plus, op.DW_OP_piece, uint(2)))
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddVariable("s", stringoff, dwarfbuilder.LocationBlock(
|
2017-08-22 14:40:01 +00:00
|
|
|
op.DW_OP_reg1, op.DW_OP_piece, uint(8),
|
|
|
|
op.DW_OP_reg0, op.DW_OP_piece, uint(8)))
|
2018-01-27 15:50:18 +00:00
|
|
|
dwb.AddVariable("n", intoff, dwarfbuilder.LocationBlock(op.DW_OP_reg3))
|
2021-05-17 17:26:49 +00:00
|
|
|
dwb.AddVariable("pair2", pairoff, dwarfbuilder.LocationBlock(
|
|
|
|
op.DW_OP_reg2, op.DW_OP_piece, uint(2),
|
|
|
|
op.DW_OP_piece, uint(2)))
|
2017-08-22 14:40:01 +00:00
|
|
|
dwb.TagClose()
|
|
|
|
|
2019-06-24 15:02:14 +00:00
|
|
|
bi, _ := fakeBinaryInfo(t, dwb)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2023-03-22 18:38:09 +00:00
|
|
|
mainfn := bi.LookupFunc()["main.main"][0]
|
proc: support inlining
Go 1.10 added inlined calls to debug_info, this commit adds support
for DW_TAG_inlined_call to delve, both for stack traces (where
inlined calls will appear as normal stack frames) and to correct
the behavior of next, step and stepout.
The calls to Next and Frame of stackIterator continue to work
unchanged and only return real stack frames, after reading each line
appendInlinedCalls is called to unpacked all the inlined calls that
involve the current PC.
The fake stack frames produced by appendInlinedCalls are
distinguished from real stack frames by having the Inlined attribute
set to true. Also their Current and Call locations are treated
differently. The Call location will be changed to represent the
position inside the inlined call, while the Current location will
always reference the real stack frame. This is done because:
* next, step and stepout need to access the debug_info entry of
the real function they are stepping through
* we are already manipulating Call in different ways while Current
is just what we read from the call stack
The strategy remains mostly the same, we disassemble the function
and we set a breakpoint on each instruction corresponding to a
different file:line. The function in question will be the one
corresponding to the first real (i.e. non-inlined) stack frame.
* If the current function contains inlined calls, 'next' will not
set any breakpoints on instructions that belong to inlined calls. We
do not do this for 'step'.
* If we are inside an inlined call that makes other inlined
functions, 'next' will not set any breakpoints that belong to
inlined calls that are children of the current inlined call.
* If the current function is inlined the breakpoint on the return
address won't be set, because inlined frames don't have a return
address.
* The code we use for stepout doesn't work at all if we are inside
an inlined call, instead we call 'next' but instruct it to remove
all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
|
|
|
|
2023-11-14 15:36:55 +00:00
|
|
|
mem := newFakeMemory(fakeCFA(), uint64(0), uint64(0), testCases["pair.v"], []byte(stringVal))
|
2018-10-17 14:55:53 +00:00
|
|
|
var regs linutil.AMD64Registers
|
|
|
|
regs.Regs = &linutil.AMD64PtraceRegs{}
|
|
|
|
regs.Regs.Rax = uint64(len(stringVal))
|
2020-03-10 16:34:40 +00:00
|
|
|
regs.Regs.Rdx = fakeCFA() + 18
|
2018-10-17 14:55:53 +00:00
|
|
|
regs.Regs.Rcx = uint64(testCases["pair.k"])
|
|
|
|
regs.Regs.Rbx = uint64(testCases["n"])
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2021-03-04 18:28:28 +00:00
|
|
|
dwarfRegs := dwarfRegisters(bi, ®s)
|
|
|
|
var changeCalls []string
|
|
|
|
dwarfRegs.ChangeFunc = func(regNum uint64, reg *op.DwarfRegister) error {
|
|
|
|
t.Logf("SetReg(%d, %x)", regNum, reg.Bytes)
|
|
|
|
changeCalls = append(changeCalls, fmt.Sprintf("%d - %x", regNum, reg.Bytes))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := fakeScope(mem, dwarfRegs, bi, mainfn)
|
|
|
|
|
|
|
|
dwarfExprCheck(t, scope, testCases)
|
2022-07-15 12:56:00 +00:00
|
|
|
exprToStringCheck(t, scope, testCasesExprToString)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
|
|
|
thevar, err := scope.EvalExpression("s", normalLoadConfig)
|
2017-10-13 20:13:43 +00:00
|
|
|
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", "s"))
|
2017-08-22 14:40:01 +00:00
|
|
|
if thevar.Unreadable != nil {
|
|
|
|
t.Errorf("variable \"s\" unreadable: %v", thevar.Unreadable)
|
|
|
|
} else {
|
|
|
|
if v := constant.StringVal(thevar.Value); v != stringVal {
|
|
|
|
t.Errorf("expected value %q got %q", stringVal, v)
|
|
|
|
}
|
|
|
|
}
|
2021-03-04 18:28:28 +00:00
|
|
|
|
|
|
|
// Test writes to composite memory
|
|
|
|
|
|
|
|
assertNoError(scope.SetVariable("n", "47"), t, "SetVariable(n, 47)")
|
|
|
|
assertNoError(scope.SetVariable("pair.k", "12"), t, "SetVariable(pair.k, 12)")
|
|
|
|
assertNoError(scope.SetVariable("pair.v", "13"), t, "SetVariable(pair.v, 13)")
|
|
|
|
|
|
|
|
for i := range changeCalls {
|
|
|
|
t.Logf("%q\n", changeCalls[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(changeCalls) != 2 {
|
|
|
|
t.Errorf("wrong number of calls to SetReg")
|
|
|
|
}
|
|
|
|
if changeCalls[0] != "3 - 2f00000000000000" {
|
|
|
|
t.Errorf("wrong call to SetReg (Rbx)")
|
|
|
|
}
|
2021-09-24 22:27:44 +00:00
|
|
|
if changeCalls[1] != "2 - 0c00000000000000" {
|
2021-03-04 18:28:28 +00:00
|
|
|
t.Errorf("wrong call to SetReg (Rcx)")
|
|
|
|
}
|
|
|
|
if mem.data[0x10] != 13 || mem.data[0x11] != 0x00 {
|
|
|
|
t.Errorf("memory was not written %v", mem.data[:2])
|
|
|
|
}
|
2017-08-22 14:40:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDwarfExprLoclist(t *testing.T) {
|
|
|
|
const before = 0x1234
|
|
|
|
const after = 0x4321
|
|
|
|
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2017-08-24 07:46:47 +00:00
|
|
|
dwb.AddSubprogram("main.main", 0x40100, 0x41000)
|
|
|
|
dwb.AddVariable("a", uint16off, []dwarfbuilder.LocEntry{
|
2020-03-18 16:25:32 +00:00
|
|
|
{Lowpc: 0x40100, Highpc: 0x40700, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa)},
|
|
|
|
{Lowpc: 0x40700, Highpc: 0x41000, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(2), op.DW_OP_plus)},
|
2017-08-22 14:40:01 +00:00
|
|
|
})
|
|
|
|
dwb.TagClose()
|
|
|
|
|
2019-06-24 15:02:14 +00:00
|
|
|
bi, _ := fakeBinaryInfo(t, dwb)
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2023-03-22 18:38:09 +00:00
|
|
|
mainfn := bi.LookupFunc()["main.main"][0]
|
proc: support inlining
Go 1.10 added inlined calls to debug_info, this commit adds support
for DW_TAG_inlined_call to delve, both for stack traces (where
inlined calls will appear as normal stack frames) and to correct
the behavior of next, step and stepout.
The calls to Next and Frame of stackIterator continue to work
unchanged and only return real stack frames, after reading each line
appendInlinedCalls is called to unpacked all the inlined calls that
involve the current PC.
The fake stack frames produced by appendInlinedCalls are
distinguished from real stack frames by having the Inlined attribute
set to true. Also their Current and Call locations are treated
differently. The Call location will be changed to represent the
position inside the inlined call, while the Current location will
always reference the real stack frame. This is done because:
* next, step and stepout need to access the debug_info entry of
the real function they are stepping through
* we are already manipulating Call in different ways while Current
is just what we read from the call stack
The strategy remains mostly the same, we disassemble the function
and we set a breakpoint on each instruction corresponding to a
different file:line. The function in question will be the one
corresponding to the first real (i.e. non-inlined) stack frame.
* If the current function contains inlined calls, 'next' will not
set any breakpoints on instructions that belong to inlined calls. We
do not do this for 'step'.
* If we are inside an inlined call that makes other inlined
functions, 'next' will not set any breakpoints that belong to
inlined calls that are children of the current inlined call.
* If the current function is inlined the breakpoint on the return
address won't be set, because inlined frames don't have a return
address.
* The code we use for stepout doesn't work at all if we are inside
an inlined call, instead we call 'next' but instruct it to remove
all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
|
|
|
|
2020-03-10 16:34:40 +00:00
|
|
|
mem := newFakeMemory(fakeCFA(), uint16(before), uint16(after))
|
2019-08-11 20:56:16 +00:00
|
|
|
const PC = 0x40100
|
|
|
|
regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{Rip: PC}}
|
2017-08-22 14:40:01 +00:00
|
|
|
|
2021-04-28 17:00:26 +00:00
|
|
|
scope := &proc.EvalScope{Location: proc.Location{PC: PC, Fn: mainfn}, Regs: *dwarfRegisters(bi, ®s), Mem: mem, BinInfo: bi}
|
2017-08-22 14:40:01 +00:00
|
|
|
|
|
|
|
uintExprCheck(t, scope, "a", before)
|
|
|
|
scope.PC = 0x40800
|
2020-05-13 18:56:50 +00:00
|
|
|
scope.Regs.Reg(scope.Regs.PCRegNum).Uint64Val = scope.PC
|
2017-08-22 14:40:01 +00:00
|
|
|
uintExprCheck(t, scope, "a", after)
|
|
|
|
}
|
2018-11-22 13:42:11 +00:00
|
|
|
|
|
|
|
func TestIssue1419(t *testing.T) {
|
|
|
|
// trying to read a slice variable with a location list that tries to read
|
|
|
|
// from registers we don't have should not cause a panic.
|
|
|
|
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
|
|
|
|
uint64off := dwb.AddBaseType("uint64", dwarfbuilder.DW_ATE_unsigned, 8)
|
|
|
|
intoff := dwb.AddBaseType("int", dwarfbuilder.DW_ATE_signed, 8)
|
|
|
|
intptroff := dwb.AddPointerType("*int", intoff)
|
|
|
|
|
|
|
|
sliceoff := dwb.AddStructType("[]int", 24)
|
|
|
|
dwb.Attr(godwarf.AttrGoKind, uint8(23))
|
|
|
|
dwb.AddMember("array", intptroff, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(0)))
|
|
|
|
dwb.AddMember("len", uint64off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(8)))
|
|
|
|
dwb.AddMember("cap", uint64off, dwarfbuilder.LocationBlock(op.DW_OP_plus_uconst, uint(16)))
|
|
|
|
dwb.TagClose()
|
|
|
|
|
|
|
|
dwb.AddSubprogram("main.main", 0x40100, 0x41000)
|
|
|
|
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)))
|
|
|
|
dwb.TagClose()
|
|
|
|
|
2019-06-24 15:02:14 +00:00
|
|
|
bi, _ := fakeBinaryInfo(t, dwb)
|
2018-11-22 13:42:11 +00:00
|
|
|
|
2023-03-22 18:38:09 +00:00
|
|
|
mainfn := bi.LookupFunc()["main.main"][0]
|
2018-11-22 13:42:11 +00:00
|
|
|
|
2020-03-10 16:34:40 +00:00
|
|
|
mem := newFakeMemory(fakeCFA())
|
2018-11-22 13:42:11 +00:00
|
|
|
|
2019-06-30 17:44:30 +00:00
|
|
|
scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: op.DwarfRegisters{}, Mem: mem, BinInfo: bi}
|
2018-11-22 13:42:11 +00:00
|
|
|
|
|
|
|
va, err := scope.EvalExpression("a", normalLoadConfig)
|
|
|
|
assertNoError(err, t, "EvalExpression(a)")
|
|
|
|
t.Logf("%#x\n", va.Addr)
|
|
|
|
t.Logf("%v", va)
|
|
|
|
if va.Unreadable == nil {
|
|
|
|
t.Fatalf("expected 'a' to be unreadable but it wasn't")
|
|
|
|
}
|
|
|
|
if va.Unreadable.Error() != "could not read 8 bytes from register 2 (size: 0)" {
|
|
|
|
t.Fatalf("wrong unreadable reason for variable 'a': %v", va.Unreadable)
|
|
|
|
}
|
|
|
|
}
|
2019-06-24 15:02:14 +00:00
|
|
|
|
|
|
|
func TestLocationCovers(t *testing.T) {
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
|
|
|
|
uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
|
|
|
|
|
|
|
|
dwb.AddCompileUnit("main", 0x0)
|
|
|
|
dwb.AddSubprogram("main.main", 0x40100, 0x41000)
|
|
|
|
aOff := dwb.AddVariable("a", uint16off, []dwarfbuilder.LocEntry{
|
2020-03-18 16:25:32 +00:00
|
|
|
{Lowpc: 0x40100, Highpc: 0x40700, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa)},
|
|
|
|
{Lowpc: 0x40700, Highpc: 0x41000, Loc: dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(2), op.DW_OP_plus)},
|
2019-06-24 15:02:14 +00:00
|
|
|
})
|
|
|
|
dwb.TagClose()
|
|
|
|
dwb.TagClose()
|
|
|
|
|
|
|
|
bi, dwdata := fakeBinaryInfo(t, dwb)
|
|
|
|
|
|
|
|
dwrdr := dwdata.Reader()
|
|
|
|
dwrdr.Seek(aOff)
|
|
|
|
aEntry, err := dwrdr.Next()
|
|
|
|
assertNoError(err, t, "reading 'a' entry")
|
|
|
|
ranges, err := bi.LocationCovers(aEntry, dwarf.AttrLocation)
|
|
|
|
assertNoError(err, t, "LocationCovers")
|
|
|
|
t.Logf("%x", ranges)
|
|
|
|
if fmt.Sprintf("%x", ranges) != "[[40100 40700] [40700 41000]]" {
|
|
|
|
t.Error("wrong value returned by LocationCover")
|
|
|
|
}
|
|
|
|
}
|
2019-07-26 18:24:35 +00:00
|
|
|
|
|
|
|
func TestIssue1636_InlineWithoutOrigin(t *testing.T) {
|
|
|
|
// Gcc (specifically GNU C++11 6.3.0) will emit DW_TAG_inlined_subroutine
|
|
|
|
// without a DW_AT_abstract_origin or a name. What is an inlined subroutine
|
|
|
|
// without a reference to an abstract origin or even a name? Regardless,
|
|
|
|
// Delve shouldn't crash.
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
dwb.AddCompileUnit("main", 0x0)
|
|
|
|
dwb.AddSubprogram("main.main", 0x40100, 0x41000)
|
|
|
|
dwb.TagOpen(dwarf.TagInlinedSubroutine, "")
|
|
|
|
dwb.TagClose()
|
|
|
|
dwb.TagClose()
|
|
|
|
dwb.TagClose()
|
|
|
|
fakeBinaryInfo(t, dwb)
|
|
|
|
}
|
2020-07-15 17:09:28 +00:00
|
|
|
|
|
|
|
func TestUnsupportedType(t *testing.T) {
|
|
|
|
// Tests that reading an unsupported type does not cause an error
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
dwb.AddCompileUnit("main", 0x0)
|
|
|
|
off := dwb.TagOpen(dwarf.TagReferenceType, "blah")
|
|
|
|
dwb.TagClose()
|
|
|
|
dwb.TagClose()
|
|
|
|
_, dw := fakeBinaryInfo(t, dwb)
|
|
|
|
_, err := godwarf.ReadType(dw, 0, off, make(map[dwarf.Offset]godwarf.Type))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("unexpected error reading unsupported type: %#v", err)
|
|
|
|
}
|
|
|
|
}
|
2020-07-16 12:53:59 +00:00
|
|
|
|
|
|
|
func TestNestedCompileUnts(t *testing.T) {
|
|
|
|
// Tests that a compile unit with a nested entry that we don't care about
|
|
|
|
// (such as a DW_TAG_namespace) is read fully.
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
dwb.AddCompileUnit("main", 0x0)
|
|
|
|
dwb.TagOpen(dwarf.TagNamespace, "namespace")
|
|
|
|
dwb.AddVariable("var1", 0x0, uint64(0x0))
|
|
|
|
dwb.TagClose()
|
|
|
|
dwb.AddVariable("var2", 0x0, uint64(0x0))
|
|
|
|
dwb.TagClose()
|
|
|
|
bi, _ := fakeBinaryInfo(t, dwb)
|
|
|
|
if n := len(bi.PackageVars()); n != 2 {
|
|
|
|
t.Errorf("expected 2 variables, got %d", n)
|
|
|
|
}
|
|
|
|
}
|
2021-01-27 14:58:48 +00:00
|
|
|
|
|
|
|
func TestAbstractOriginDefinedAfterUse(t *testing.T) {
|
|
|
|
// Tests that an abstract origin entry can appear after its uses.
|
|
|
|
dwb := dwarfbuilder.New()
|
|
|
|
dwb.AddCompileUnit("main", 0x0)
|
|
|
|
|
|
|
|
// Concrete implementation
|
|
|
|
dwb.TagOpen(dwarf.TagSubprogram, "")
|
|
|
|
originRef1 := dwb.Attr(dwarf.AttrAbstractOrigin, dwarf.Offset(0))
|
|
|
|
dwb.Attr(dwarf.AttrLowpc, dwarfbuilder.Address(0x40100))
|
|
|
|
dwb.Attr(dwarf.AttrHighpc, dwarfbuilder.Address(0x41000))
|
|
|
|
dwb.TagClose()
|
|
|
|
|
|
|
|
// Inlined call
|
|
|
|
dwb.AddSubprogram("callingFn", 0x41100, 0x42000)
|
|
|
|
dwb.TagOpen(dwarf.TagInlinedSubroutine, "")
|
|
|
|
originRef2 := dwb.Attr(dwarf.AttrAbstractOrigin, dwarf.Offset(0))
|
|
|
|
dwb.Attr(dwarf.AttrLowpc, dwarfbuilder.Address(0x41150))
|
|
|
|
dwb.Attr(dwarf.AttrHighpc, dwarfbuilder.Address(0x41155))
|
|
|
|
dwb.Attr(dwarf.AttrCallFile, uint8(1))
|
|
|
|
dwb.Attr(dwarf.AttrCallLine, uint8(1))
|
|
|
|
dwb.TagClose()
|
|
|
|
dwb.TagClose()
|
|
|
|
|
|
|
|
// Abstract origin
|
|
|
|
abstractOriginOff := dwb.TagOpen(dwarf.TagSubprogram, "inlinedFn")
|
|
|
|
dwb.Attr(dwarf.AttrInline, uint8(1))
|
|
|
|
dwb.TagClose()
|
|
|
|
|
|
|
|
dwb.TagClose()
|
|
|
|
|
|
|
|
dwb.PatchOffset(originRef1, abstractOriginOff)
|
|
|
|
dwb.PatchOffset(originRef2, abstractOriginOff)
|
|
|
|
|
|
|
|
bi, _ := fakeBinaryInfo(t, dwb)
|
|
|
|
fn := bi.PCToFunc(0x40100)
|
|
|
|
if fn == nil {
|
|
|
|
t.Fatalf("could not find concrete instance of inlined function")
|
|
|
|
}
|
|
|
|
}
|