delve/pkg/proc/types.go

218 lines
6.8 KiB
Go
Raw Permalink Normal View History

package proc
import (
"debug/dwarf"
"errors"
"fmt"
"go/constant"
"reflect"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/reader"
)
// The kind field in runtime._type is a reflect.Kind value plus
// some extra flags defined here.
// See equivalent declaration in $GOROOT/src/reflect/type.go
const (
kindDirectIface = 1 << 5 // +rtype kindDirectIface|internal/abi.KindDirectIface
kindGCProg = 1 << 6 // +rtype kindGCProg|internal/abi.KindGCProg
kindNoPointers = 1 << 7
kindMask = (1 << 5) - 1 // +rtype kindMask|internal/abi.KindMask
)
type runtimeTypeDIE struct {
offset dwarf.Offset
kind int64
}
func pointerTo(typ godwarf.Type, arch *Arch) godwarf.Type {
return &godwarf.PtrType{
CommonType: godwarf.CommonType{
ByteSize: int64(arch.PtrSize()),
Name: "*" + typ.Common().Name,
ReflectKind: reflect.Ptr,
Offset: 0,
},
Type: typ,
}
}
type functionsDebugInfoByEntry []Function
func (v functionsDebugInfoByEntry) Len() int { return len(v) }
func (v functionsDebugInfoByEntry) Less(i, j int) bool { return v[i].Entry < v[j].Entry }
func (v functionsDebugInfoByEntry) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
type compileUnitsByOffset []*compileUnit
func (v compileUnitsByOffset) Len() int { return len(v) }
func (v compileUnitsByOffset) Less(i int, j int) bool { return v[i].offset < v[j].offset }
func (v compileUnitsByOffset) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
type packageVarsByAddr []packageVar
func (v packageVarsByAddr) Len() int { return len(v) }
func (v packageVarsByAddr) Less(i int, j int) bool { return v[i].addr < v[j].addr }
func (v packageVarsByAddr) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
type loadDebugInfoMapsContext struct {
ardr *reader.Reader
abstractOriginTable map[dwarf.Offset]int
knownPackageVars map[string]struct{}
offsetToVersion map[dwarf.Offset]uint8
}
func newLoadDebugInfoMapsContext(bi *BinaryInfo, image *Image, offsetToVersion map[dwarf.Offset]uint8) *loadDebugInfoMapsContext {
ctxt := &loadDebugInfoMapsContext{}
ctxt.ardr = image.DwarfReader()
ctxt.abstractOriginTable = make(map[dwarf.Offset]int)
ctxt.offsetToVersion = offsetToVersion
ctxt.knownPackageVars = map[string]struct{}{}
for _, v := range bi.packageVars {
ctxt.knownPackageVars[v.name] = struct{}{}
}
return ctxt
}
func (ctxt *loadDebugInfoMapsContext) lookupAbstractOrigin(bi *BinaryInfo, off dwarf.Offset) int {
r, ok := ctxt.abstractOriginTable[off]
if !ok {
bi.Functions = append(bi.Functions, Function{})
r = len(bi.Functions) - 1
bi.Functions[r].offset = off
ctxt.abstractOriginTable[off] = r
}
return r
}
// RuntimeTypeToDIE returns the DIE corresponding to the runtime._type.
// This is done in three different ways depending on the version of go.
// - Before go1.7 the type name is retrieved directly from the runtime._type
// and looked up in debug_info
// - After go1.7 the runtime._type struct is read recursively to reconstruct
// the name of the type, and then the type's name is used to look up
// debug_info
// - After go1.11 the runtimeTypeToDIE map is used to look up the address of
// the type and map it directly to a DIE.
func RuntimeTypeToDIE(_type *Variable, dataAddr uint64, mds []ModuleData) (typ godwarf.Type, kind int64, err error) {
bi := _type.bi
_type = _type.maybeDereference()
// go 1.11 implementation: use extended attribute in debug_info
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
md := findModuleDataForType(bi, mds, _type.Addr, _type.mem)
if md != nil {
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
so := bi.moduleDataToImage(md)
if so != nil {
if rtdie, ok := so.runtimeTypeToDIE[_type.Addr-md.types]; ok {
typ, err := godwarf.ReadType(so.dwarf, so.index, rtdie.offset, so.typeCache)
if err != nil {
return nil, 0, fmt.Errorf("invalid interface type: %v", err)
}
if rtdie.kind == -1 {
if kindField := _type.loadFieldNamed("kind"); kindField != nil && kindField.Value != nil {
rtdie.kind, _ = constant.Int64Val(kindField.Value)
} else if kindField := _type.loadFieldNamed("Kind_"); kindField != nil && kindField.Value != nil {
rtdie.kind, _ = constant.Int64Val(kindField.Value)
}
}
return typ, rtdie.kind, nil
}
}
}
return nil, 0, errors.New("could not resolve interface type")
}
// resolveParametricType returns the real type of t if t is a parametric
// type, by reading the correct dictionary entry.
func resolveParametricType(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(bi.runtimeTypeTypename())
if err != nil {
return ptyp.TypedefType.Type, err
}
_type := newVariable("", rtypeAddr, runtimeType, bi, mem)
var mds []ModuleData
if bi.moduleDataCache != nil {
mds = bi.moduleDataCache
} else {
mds, err = LoadModuleData(bi, _type.mem)
if err != nil {
return ptyp.TypedefType.Type, fmt.Errorf("error loading module data: %v", err)
}
bi.moduleDataCache = mds
}
typ, _, err := RuntimeTypeToDIE(_type, 0, mds)
if err != nil {
return ptyp.TypedefType.Type, err
}
return typ, nil
}
func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type) (typeAddr uint64, typeKind uint64, found bool, err error) {
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
so := bi.typeToImage(typ)
rdr := so.DwarfReader()
rdr.Seek(typ.Common().Offset)
e, err := rdr.Next()
if err != nil {
return 0, 0, false, err
}
off, ok := e.Val(godwarf.AttrGoRuntimeType).(uint64)
if !ok {
return 0, 0, false, nil
}
mds, err := LoadModuleData(bi, mem)
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
if err != nil {
return 0, 0, false, err
}
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
md := bi.imageToModuleData(so, mds)
if md == nil {
if so.index > 0 {
return 0, 0, false, fmt.Errorf("could not find module data for type %s (shared object: %q)", typ, so.Path)
} else {
return 0, 0, false, fmt.Errorf("could not find module data for type %s", typ)
}
}
typeAddr = md.types + off
rtyp, err := bi.findType(bi.runtimeTypeTypename())
if err != nil {
return 0, 0, false, err
}
_type := newVariable("", typeAddr, rtyp, bi, mem)
kindv := _type.loadFieldNamed("kind")
if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
kindv = _type.loadFieldNamed("Kind_")
}
if kindv == nil {
return 0, 0, false, fmt.Errorf("unreadable interface type (no kind field)")
}
if kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable)
}
typeKind, _ = constant.Uint64Val(kindv.Value)
return typeAddr, typeKind, true, nil
}