mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	 f3b149bda7
			
		
	
	f3b149bda7
	
	
	
		
			
			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
		
			
				
	
	
		
			206 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package proc
 | |
| 
 | |
| import (
 | |
| 	"go/constant"
 | |
| 	"unsafe"
 | |
| )
 | |
| 
 | |
| // delve counterpart to runtime.moduledata
 | |
| type moduleData struct {
 | |
| 	text, etext   uintptr
 | |
| 	types, etypes uintptr
 | |
| 	typemapVar    *Variable
 | |
| }
 | |
| 
 | |
| func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) ([]moduleData, error) {
 | |
| 	scope := globalScope(bi, bi.Images[0], mem)
 | |
| 	var md *Variable
 | |
| 	md, err := scope.findGlobal("runtime.firstmoduledata")
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	r := []moduleData{}
 | |
| 
 | |
| 	for md.Addr != 0 {
 | |
| 		const (
 | |
| 			typesField   = "types"
 | |
| 			etypesField  = "etypes"
 | |
| 			textField    = "text"
 | |
| 			etextField   = "etext"
 | |
| 			nextField    = "next"
 | |
| 			typemapField = "typemap"
 | |
| 		)
 | |
| 		vars := map[string]*Variable{}
 | |
| 
 | |
| 		for _, fieldName := range []string{typesField, etypesField, textField, etextField, nextField, typemapField} {
 | |
| 			var err error
 | |
| 			vars[fieldName], err = md.structMember(fieldName)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		var err error
 | |
| 
 | |
| 		touint := func(name string) (ret uintptr) {
 | |
| 			if err == nil {
 | |
| 				var n uint64
 | |
| 				n, err = vars[name].asUint()
 | |
| 				ret = uintptr(n)
 | |
| 			}
 | |
| 			return ret
 | |
| 		}
 | |
| 
 | |
| 		r = append(r, moduleData{
 | |
| 			types: touint(typesField), etypes: touint(etypesField),
 | |
| 			text: touint(textField), etext: touint(etextField),
 | |
| 			typemapVar: vars[typemapField],
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		md = vars[nextField].maybeDereference()
 | |
| 		if md.Unreadable != nil {
 | |
| 			return nil, md.Unreadable
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| func findModuleDataForType(bi *BinaryInfo, mds []moduleData, typeAddr uintptr, mem MemoryReadWriter) *moduleData {
 | |
| 	for i := range mds {
 | |
| 		if typeAddr >= mds[i].types && typeAddr < mds[i].etypes {
 | |
| 			return &mds[i]
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func resolveTypeOff(bi *BinaryInfo, mds []moduleData, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (*Variable, error) {
 | |
| 	// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
 | |
| 	md := findModuleDataForType(bi, mds, typeAddr, mem)
 | |
| 
 | |
| 	rtyp, err := bi.findType("runtime._type")
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if md == nil {
 | |
| 		v, err := reflectOffsMapAccess(bi, off, mem)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
 | |
| 		addr, _ := constant.Int64Val(v.Value)
 | |
| 		return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil
 | |
| 	}
 | |
| 
 | |
| 	if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem)); t != nil {
 | |
| 		return t, nil
 | |
| 	}
 | |
| 
 | |
| 	res := md.types + uintptr(off)
 | |
| 
 | |
| 	return newVariable("", res, rtyp, bi, mem), nil
 | |
| }
 | |
| 
 | |
| func resolveNameOff(bi *BinaryInfo, mds []moduleData, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
 | |
| 	// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go
 | |
| 	for _, md := range mds {
 | |
| 		if typeAddr >= md.types && typeAddr < md.etypes {
 | |
| 			return loadName(bi, md.types+off, mem)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	v, err := reflectOffsMapAccess(bi, off, mem)
 | |
| 	if err != nil {
 | |
| 		return "", "", 0, err
 | |
| 	}
 | |
| 
 | |
| 	resv := v.maybeDereference()
 | |
| 	if resv.Unreadable != nil {
 | |
| 		return "", "", 0, resv.Unreadable
 | |
| 	}
 | |
| 
 | |
| 	return loadName(bi, resv.Addr, mem)
 | |
| }
 | |
| 
 | |
| func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
 | |
| 	scope := globalScope(bi, bi.Images[0], mem)
 | |
| 	reflectOffs, err := scope.findGlobal("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)), mem))
 | |
| }
 | |
| 
 | |
| 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 loadName(bi *BinaryInfo, addr uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
 | |
| 	off := addr
 | |
| 	namedata := make([]byte, 3)
 | |
| 	_, err = mem.ReadMemory(namedata, off)
 | |
| 	off += 3
 | |
| 	if err != nil {
 | |
| 		return "", "", 0, err
 | |
| 	}
 | |
| 
 | |
| 	namelen := uint16(namedata[1])<<8 | uint16(namedata[2])
 | |
| 
 | |
| 	rawstr := make([]byte, int(namelen))
 | |
| 	_, err = mem.ReadMemory(rawstr, off)
 | |
| 	off += uintptr(namelen)
 | |
| 	if err != nil {
 | |
| 		return "", "", 0, err
 | |
| 	}
 | |
| 
 | |
| 	name = string(rawstr)
 | |
| 
 | |
| 	if namedata[0]&nameflagHasTag != 0 {
 | |
| 		taglendata := make([]byte, 2)
 | |
| 		_, err = mem.ReadMemory(taglendata, off)
 | |
| 		off += 2
 | |
| 		if err != nil {
 | |
| 			return "", "", 0, err
 | |
| 		}
 | |
| 		taglen := uint16(taglendata[0])<<8 | uint16(taglendata[1])
 | |
| 
 | |
| 		rawstr := make([]byte, int(taglen))
 | |
| 		_, err = mem.ReadMemory(rawstr, off)
 | |
| 		off += uintptr(taglen)
 | |
| 		if err != nil {
 | |
| 			return "", "", 0, err
 | |
| 		}
 | |
| 
 | |
| 		tag = string(rawstr)
 | |
| 	}
 | |
| 
 | |
| 	if namedata[0]&nameflagHasPkg != 0 {
 | |
| 		pkgdata := make([]byte, 4)
 | |
| 		_, err = mem.ReadMemory(pkgdata, off)
 | |
| 		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
 | |
| }
 |