
Generate names of the concrete types stored inside interface variables by fully parsing their runtime._type instead of simply using the str field. This allows delve to read the contents of an interface variable when the program imports multiple packages that have the same name. It also allows delve to correctly interpret some complex anonymous types. Fixes #455
190 lines
4.4 KiB
Go
190 lines
4.4 KiB
Go
package proc
|
|
|
|
import (
|
|
"go/constant"
|
|
"unsafe"
|
|
)
|
|
|
|
// delve counterpart to runtime.moduledata
|
|
type moduleData struct {
|
|
types, etypes uintptr
|
|
typemapVar *Variable
|
|
}
|
|
|
|
func (dbp *Process) loadModuleData() (err error) {
|
|
dbp.loadModuleDataOnce.Do(func() {
|
|
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
|
|
var md *Variable
|
|
md, err = scope.packageVarAddr("runtime.firstmoduledata")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for md.Addr != 0 {
|
|
var typesVar, etypesVar, nextVar, typemapVar *Variable
|
|
var types, etypes uint64
|
|
|
|
if typesVar, err = md.structMember("types"); err != nil {
|
|
return
|
|
}
|
|
if etypesVar, err = md.structMember("etypes"); err != nil {
|
|
return
|
|
}
|
|
if nextVar, err = md.structMember("next"); err != nil {
|
|
return
|
|
}
|
|
if typemapVar, err = md.structMember("typemap"); err != nil {
|
|
return
|
|
}
|
|
if types, err = typesVar.asUint(); err != nil {
|
|
return
|
|
}
|
|
if etypes, err = etypesVar.asUint(); err != nil {
|
|
return
|
|
}
|
|
|
|
dbp.moduleData = append(dbp.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar})
|
|
|
|
md = nextVar.maybeDereference()
|
|
if md.Unreadable != nil {
|
|
err = md.Unreadable
|
|
return
|
|
}
|
|
}
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, error) {
|
|
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
|
|
if err := dbp.loadModuleData(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var md *moduleData
|
|
for i := range dbp.moduleData {
|
|
if typeAddr >= dbp.moduleData[i].types && typeAddr < dbp.moduleData[i].etypes {
|
|
md = &dbp.moduleData[i]
|
|
}
|
|
}
|
|
|
|
rtyp, err := dbp.findType("runtime._type")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if md == nil {
|
|
v, err := dbp.reflectOffsMapAccess(off)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v.loadValue(LoadConfig{false, 1, 0, 0, -1})
|
|
addr, _ := constant.Int64Val(v.Value)
|
|
return v.newVariable(v.Name, uintptr(addr), rtyp), nil
|
|
}
|
|
|
|
if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread)); t != nil {
|
|
return t, nil
|
|
}
|
|
|
|
res := md.types + uintptr(off)
|
|
|
|
return dbp.CurrentThread.newVariable("", res, rtyp), nil
|
|
}
|
|
|
|
func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag string, pkgpathoff int32, err error) {
|
|
// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go
|
|
if err = dbp.loadModuleData(); err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
|
|
for _, md := range dbp.moduleData {
|
|
if typeAddr >= md.types && typeAddr < md.etypes {
|
|
return dbp.loadName(md.types + off)
|
|
}
|
|
}
|
|
|
|
v, err := dbp.reflectOffsMapAccess(off)
|
|
if err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
|
|
resv := v.maybeDereference()
|
|
if resv.Unreadable != nil {
|
|
return "", "", 0, resv.Unreadable
|
|
}
|
|
|
|
return dbp.loadName(resv.Addr)
|
|
}
|
|
|
|
func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) {
|
|
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
|
|
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reflectOffsm, err := reflectOffs.structMember("m")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread))
|
|
}
|
|
|
|
const (
|
|
// flags for the name struct (see 'type name struct' in $GOROOT/src/reflect/type.go)
|
|
nameflagExported = 1 << 0
|
|
nameflagHasTag = 1 << 1
|
|
nameflagHasPkg = 1 << 2
|
|
)
|
|
|
|
func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, err error) {
|
|
off := addr
|
|
namedata, err := dbp.CurrentThread.readMemory(off, 3)
|
|
off += 3
|
|
if err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
|
|
namelen := uint16(namedata[1]<<8) | uint16(namedata[2])
|
|
|
|
rawstr, err := dbp.CurrentThread.readMemory(off, int(namelen))
|
|
off += uintptr(namelen)
|
|
if err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
|
|
name = string(rawstr)
|
|
|
|
if namedata[0]&nameflagHasTag != 0 {
|
|
taglendata, err := dbp.CurrentThread.readMemory(off, 2)
|
|
off += 2
|
|
if err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
taglen := uint16(taglendata[0]<<8) | uint16(taglendata[1])
|
|
|
|
rawstr, err := dbp.CurrentThread.readMemory(off, int(taglen))
|
|
off += uintptr(taglen)
|
|
if err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
|
|
tag = string(rawstr)
|
|
}
|
|
|
|
if namedata[0]&nameflagHasPkg != 0 {
|
|
pkgdata, err := dbp.CurrentThread.readMemory(off, 4)
|
|
if err != nil {
|
|
return "", "", 0, err
|
|
}
|
|
|
|
// see func pkgPath in $GOROOT/src/reflect/type.go
|
|
copy((*[4]byte)(unsafe.Pointer(&pkgpathoff))[:], pkgdata)
|
|
}
|
|
|
|
return name, tag, pkgpathoff, nil
|
|
}
|