proc,dwarf/godwarf: support parametric types with dictionaries
Change debug_info type reader and proc to convert parametric types into their real types by reading the corresponding dictionary entry and using the same method used for interfaces to retrieve the DIE from a runtime._type address. '2586e9b1'.
This commit is contained in:
parent
4e7b689e1a
commit
18f2a4c46b
22
_fixtures/testvariables_generic.go
Normal file
22
_fixtures/testvariables_generic.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type astruct struct {
|
||||
x, y int
|
||||
}
|
||||
|
||||
func testfn[T any, K comparable](arg1 T, arg2 K) {
|
||||
m := make(map[K]T)
|
||||
m[arg2] = arg1
|
||||
runtime.Breakpoint()
|
||||
fmt.Println(arg1, arg2, m)
|
||||
}
|
||||
|
||||
func main() {
|
||||
testfn[int, float32](3, 2.1)
|
||||
testfn(&astruct{0, 1}, astruct{2, 3})
|
||||
}
|
@ -27,6 +27,7 @@ const (
|
||||
AttrGoEmbeddedField dwarf.Attr = 0x2903
|
||||
AttrGoRuntimeType dwarf.Attr = 0x2904
|
||||
AttrGoPackageName dwarf.Attr = 0x2905
|
||||
AttrGoDictIndex dwarf.Attr = 0x2906
|
||||
)
|
||||
|
||||
// Basic type encodings -- the value for AttrEncoding in a TagBaseType Entry.
|
||||
@ -513,6 +514,11 @@ func (t *ChanType) stringIntl(recCheck recCheck) string {
|
||||
return "chan " + t.ElemType.String()
|
||||
}
|
||||
|
||||
type ParametricType struct {
|
||||
TypedefType
|
||||
DictIndex int64
|
||||
}
|
||||
|
||||
// An UnsupportedType is a placeholder returned in situations where we
|
||||
// encounter a type that isn't supported.
|
||||
type UnsupportedType struct {
|
||||
@ -1011,7 +1017,15 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
|
||||
typeCache[off] = it
|
||||
t = &it.TypedefType
|
||||
default:
|
||||
typ = t
|
||||
if dictIndex, ok := e.Val(AttrGoDictIndex).(int64); ok {
|
||||
pt := new(ParametricType)
|
||||
pt.DictIndex = dictIndex
|
||||
typ = pt
|
||||
typeCache[off] = pt
|
||||
t = &pt.TypedefType
|
||||
} else {
|
||||
typ = t
|
||||
}
|
||||
}
|
||||
typeCache[off] = typ
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
|
@ -20,10 +20,13 @@ import (
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
)
|
||||
|
||||
var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
|
||||
|
||||
const goDictionaryName = ".dict"
|
||||
|
||||
// EvalScope is the scope for variable evaluation. Contains the thread,
|
||||
// current location (PC), and canonical frame address.
|
||||
type EvalScope struct {
|
||||
@ -49,6 +52,8 @@ type EvalScope struct {
|
||||
// The goroutine executing the expression evaluation shall signal that the
|
||||
// evaluation is complete by closing the continueRequest channel.
|
||||
callCtx *callContext
|
||||
|
||||
dictAddr uint64 // dictionary address for instantiated generic functions
|
||||
}
|
||||
|
||||
type localsFlags uint8
|
||||
@ -230,10 +235,35 @@ func (scope *EvalScope) Locals(flags localsFlags) ([]*Variable, error) {
|
||||
}
|
||||
|
||||
varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, variablesFlags)
|
||||
|
||||
// look for dictionary entry
|
||||
if scope.dictAddr == 0 {
|
||||
for _, entry := range varEntries {
|
||||
name, _ := entry.Val(dwarf.AttrName).(string)
|
||||
if name == goDictionaryName {
|
||||
dictVar, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree, 0)
|
||||
if err != nil {
|
||||
logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err)
|
||||
} else if dictVar.Unreadable != nil {
|
||||
logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, dictVar.Unreadable)
|
||||
} else {
|
||||
scope.dictAddr, err = readUintRaw(dictVar.mem, dictVar.Addr, int64(scope.BinInfo.Arch.PtrSize()))
|
||||
if err != nil {
|
||||
logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vars := make([]*Variable, 0, len(varEntries))
|
||||
depths := make([]int, 0, len(varEntries))
|
||||
for _, entry := range varEntries {
|
||||
val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree)
|
||||
if name, _ := entry.Val(dwarf.AttrName).(string); name == goDictionaryName {
|
||||
continue
|
||||
}
|
||||
val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree, scope.dictAddr)
|
||||
if err != nil {
|
||||
// skip variables that we can't parse yet
|
||||
continue
|
||||
@ -489,7 +519,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
|
||||
}
|
||||
|
||||
// Ignore errors trying to extract values
|
||||
val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry))
|
||||
val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry), 0)
|
||||
if val != nil && val.Kind == reflect.Invalid {
|
||||
continue
|
||||
}
|
||||
@ -526,7 +556,7 @@ func (scope *EvalScope) findGlobalInternal(name string) (*Variable, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry))
|
||||
return extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry), 0)
|
||||
}
|
||||
}
|
||||
for _, fn := range scope.BinInfo.Functions {
|
||||
|
@ -566,7 +566,7 @@ func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg *
|
||||
var formalArgVar *Variable
|
||||
if formalArg.dwarfEntry != nil {
|
||||
var err error
|
||||
formalArgVar, err = extractVarInfoFromEntry(scope.target, formalScope.BinInfo, formalScope.image(), formalScope.Regs, formalScope.Mem, formalArg.dwarfEntry)
|
||||
formalArgVar, err = extractVarInfoFromEntry(scope.target, formalScope.BinInfo, formalScope.image(), formalScope.Regs, formalScope.Mem, formalArg.dwarfEntry, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -174,6 +174,34 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind
|
||||
return typ, kind, nil
|
||||
}
|
||||
|
||||
// resolveParametricType returns the real type of t if t is a parametric
|
||||
// type, by reading the correct dictionary entry.
|
||||
func resolveParametricType(tgt *Target, bi *BinaryInfo, mem MemoryReadWriter, t godwarf.Type, dictAddr uint64) (godwarf.Type, error) {
|
||||
ptyp, _ := t.(*godwarf.ParametricType)
|
||||
if ptyp == nil {
|
||||
return t, nil
|
||||
}
|
||||
if dictAddr == 0 {
|
||||
return ptyp.TypedefType.Type, errors.New("parametric type without a dictionary")
|
||||
}
|
||||
rtypeAddr, err := readUintRaw(mem, dictAddr+uint64(ptyp.DictIndex*int64(bi.Arch.PtrSize())), int64(bi.Arch.PtrSize()))
|
||||
if err != nil {
|
||||
return ptyp.TypedefType.Type, err
|
||||
}
|
||||
runtimeType, err := bi.findType("runtime._type")
|
||||
if err != nil {
|
||||
return ptyp.TypedefType.Type, err
|
||||
}
|
||||
_type := newVariable("", rtypeAddr, runtimeType, bi, mem)
|
||||
|
||||
typ, _, err := runtimeTypeToDIE(_type, 0)
|
||||
if err != nil {
|
||||
return ptyp.TypedefType.Type, err
|
||||
}
|
||||
|
||||
return typ, nil
|
||||
}
|
||||
|
||||
type nameOfRuntimeTypeEntry struct {
|
||||
typename string
|
||||
kind int64
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -1135,7 +1136,7 @@ func readVarEntry(entry *godwarf.Tree, image *Image) (name string, typ godwarf.T
|
||||
|
||||
// Extracts the name and type of a variable from a dwarf entry
|
||||
// then executes the instructions given in the DW_AT_location attribute to grab the variable's address
|
||||
func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree) (*Variable, error) {
|
||||
func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree, dictAddr uint64) (*Variable, error) {
|
||||
if entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagVariable {
|
||||
return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", entry.Tag.String())
|
||||
}
|
||||
@ -1145,6 +1146,12 @@ func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err = resolveParametricType(tgt, bi, mem, t, dictAddr)
|
||||
if err != nil {
|
||||
// Log the error, keep going with t, which will be the shape type
|
||||
logflags.DebuggerLogger().Errorf("could not resolve parametric type of %s", n)
|
||||
}
|
||||
|
||||
addr, pieces, descr, err := bi.Location(entry, dwarf.AttrLocation, regs.PC(), regs, mem)
|
||||
if pieces != nil {
|
||||
var cmem *compositeMemory
|
||||
|
@ -1602,7 +1602,48 @@ func TestCgoEval(t *testing.T) {
|
||||
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEvalExpressionGenerics(t *testing.T) {
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
|
||||
t.Skip("generics not supported")
|
||||
}
|
||||
|
||||
testcases := [][]varTest{
|
||||
// testfn[int, float32]
|
||||
[]varTest{
|
||||
{"arg1", true, "3", "", "int", nil},
|
||||
{"arg2", true, "2.1", "", "float32", nil},
|
||||
{"m", true, "map[float32]int [2.1: 3, ]", "", "map[float32]int", nil},
|
||||
},
|
||||
|
||||
// testfn[*astruct, astruct]
|
||||
[]varTest{
|
||||
{"arg1", true, "*main.astruct {x: 0, y: 1}", "", "*main.astruct", nil},
|
||||
{"arg2", true, "main.astruct {x: 2, y: 3}", "", "main.astruct", nil},
|
||||
{"m", true, "map[main.astruct]*main.astruct [{x: 2, y: 3}: *{x: 0, y: 1}, ]", "", "map[main.astruct]*main.astruct", nil},
|
||||
},
|
||||
}
|
||||
|
||||
withTestProcess("testvariables_generic", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
for i, tcs := range testcases {
|
||||
assertNoError(p.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i))
|
||||
for _, tc := range tcs {
|
||||
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
|
||||
if tc.err == nil {
|
||||
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name))
|
||||
assertVariable(t, variable, tc)
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
|
||||
}
|
||||
if tc.err.Error() != err.Error() {
|
||||
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user