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:
aarzilli 2021-09-16 21:26:36 +02:00 committed by Alessandro Arzilli
parent 4e7b689e1a
commit 18f2a4c46b
7 changed files with 149 additions and 7 deletions

@ -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 AttrGoEmbeddedField dwarf.Attr = 0x2903
AttrGoRuntimeType dwarf.Attr = 0x2904 AttrGoRuntimeType dwarf.Attr = 0x2904
AttrGoPackageName dwarf.Attr = 0x2905 AttrGoPackageName dwarf.Attr = 0x2905
AttrGoDictIndex dwarf.Attr = 0x2906
) )
// Basic type encodings -- the value for AttrEncoding in a TagBaseType Entry. // 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() return "chan " + t.ElemType.String()
} }
type ParametricType struct {
TypedefType
DictIndex int64
}
// An UnsupportedType is a placeholder returned in situations where we // An UnsupportedType is a placeholder returned in situations where we
// encounter a type that isn't supported. // encounter a type that isn't supported.
type UnsupportedType struct { type UnsupportedType struct {
@ -1011,7 +1017,15 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
typeCache[off] = it typeCache[off] = it
t = &it.TypedefType t = &it.TypedefType
default: 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 typeCache[off] = typ
t.Name, _ = e.Val(dwarf.AttrName).(string) 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/op"
"github.com/go-delve/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/goversion" "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") var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
const goDictionaryName = ".dict"
// EvalScope is the scope for variable evaluation. Contains the thread, // EvalScope is the scope for variable evaluation. Contains the thread,
// current location (PC), and canonical frame address. // current location (PC), and canonical frame address.
type EvalScope struct { type EvalScope struct {
@ -49,6 +52,8 @@ type EvalScope struct {
// The goroutine executing the expression evaluation shall signal that the // The goroutine executing the expression evaluation shall signal that the
// evaluation is complete by closing the continueRequest channel. // evaluation is complete by closing the continueRequest channel.
callCtx *callContext callCtx *callContext
dictAddr uint64 // dictionary address for instantiated generic functions
} }
type localsFlags uint8 type localsFlags uint8
@ -230,10 +235,35 @@ func (scope *EvalScope) Locals(flags localsFlags) ([]*Variable, error) {
} }
varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, variablesFlags) 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)) vars := make([]*Variable, 0, len(varEntries))
depths := make([]int, 0, len(varEntries)) depths := make([]int, 0, len(varEntries))
for _, entry := range 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 { if err != nil {
// skip variables that we can't parse yet // skip variables that we can't parse yet
continue continue
@ -489,7 +519,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
} }
// Ignore errors trying to extract values // 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 { if val != nil && val.Kind == reflect.Invalid {
continue continue
} }
@ -526,7 +556,7 @@ func (scope *EvalScope) findGlobalInternal(name string) (*Variable, error) {
if err != nil { if err != nil {
return nil, err 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 { for _, fn := range scope.BinInfo.Functions {

@ -566,7 +566,7 @@ func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg *
var formalArgVar *Variable var formalArgVar *Variable
if formalArg.dwarfEntry != nil { if formalArg.dwarfEntry != nil {
var err error 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 { if err != nil {
return err return err
} }

@ -174,6 +174,34 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind
return typ, kind, nil 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 { type nameOfRuntimeTypeEntry struct {
typename string typename string
kind int64 kind int64

@ -18,6 +18,7 @@ import (
"github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
) )
const ( 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 // 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 // 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 { 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()) 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 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) addr, pieces, descr, err := bi.Location(entry, dwarf.AttrLocation, regs.PC(), regs, mem)
if pieces != nil { if pieces != nil {
var cmem *compositeMemory 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()) 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())
}
}
}
} }
}) })
} }