diff --git a/pkg/dwarf/frame/entries.go b/pkg/dwarf/frame/entries.go index cb3917e67c..1607b1fc18 100644 --- a/pkg/dwarf/frame/entries.go +++ b/pkg/dwarf/frame/entries.go @@ -75,3 +75,12 @@ func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, } return fdes[idx], nil } + +// Append appends otherFDEs to fdes and returns the result. +func (fdes FrameDescriptionEntries) Append(otherFDEs FrameDescriptionEntries) FrameDescriptionEntries { + r := append(fdes, otherFDEs...) + sort.Slice(r, func(i, j int) bool { + return r[i].Begin() < r[j].Begin() + }) + return r +} diff --git a/pkg/dwarf/godwarf/type.go b/pkg/dwarf/godwarf/type.go index e5a0f91d60..474a26e650 100644 --- a/pkg/dwarf/godwarf/type.go +++ b/pkg/dwarf/godwarf/type.go @@ -54,6 +54,7 @@ type Type interface { // If a field is not known or not applicable for a given type, // the zero value is used. type CommonType struct { + Index int // index supplied by caller of ReadType ByteSize int64 // size of value of this type, in bytes Name string // name that can be used to refer to type ReflectKind reflect.Kind // the reflect kind of the type. @@ -359,8 +360,12 @@ func (t *ChanType) String() string { } // Type reads the type at off in the DWARF ``info'' section. -func ReadType(d *dwarf.Data, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) { - return readType(d, "info", d.Reader(), off, typeCache) +func ReadType(d *dwarf.Data, index int, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) { + typ, err := readType(d, "info", d.Reader(), off, typeCache) + if typ != nil { + typ.Common().Index = index + } + return typ, err } func getKind(e *dwarf.Entry) reflect.Kind { diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index 5d92463143..8ff0c0d10f 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -17,7 +17,7 @@ type Arch interface { DerefTLS() bool FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext RegSize(uint64) int - RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters + RegistersToDwarfRegisters(bi *BinaryInfo, regs Registers) op.DwarfRegisters GoroutineToDwarfRegisters(*G) op.DwarfRegisters } @@ -270,7 +270,7 @@ func maxAmd64DwarfRegister() int { // RegistersToDwarfRegisters converts hardware registers to the format used // by the DWARF expression interpreter. -func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters { +func (a *AMD64) RegistersToDwarfRegisters(bi *BinaryInfo, regs Registers) op.DwarfRegisters { dregs := make([]*op.DwarfRegister, maxAmd64DwarfRegister()+1) dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC()) @@ -292,7 +292,9 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op. } } - return op.DwarfRegisters{StaticBase: staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} + so := bi.funcToImage(bi.PCToFunc(regs.PC())) + + return op.DwarfRegisters{StaticBase: so.StaticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} } // GoroutineToDwarfRegisters extract the saved DWARF registers from a parked @@ -302,5 +304,10 @@ func (a *AMD64) GoroutineToDwarfRegisters(g *G) op.DwarfRegisters { dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(g.PC) dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(g.SP) dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(g.BP) - return op.DwarfRegisters{StaticBase: g.variable.bi.staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} + + bi := g.variable.bi + fn := bi.PCToFunc(g.PC) + so := bi.funcToImage(fn) + + return op.DwarfRegisters{StaticBase: so.StaticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} } diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index ff65bc1c0a..c799a59984 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -29,14 +29,14 @@ import ( // BinaryInfo holds information on the binaries being executed (this // includes both the executable and also any loaded libraries). type BinaryInfo struct { - // Path on disk of the binary being executed. - Path string // Architecture of this binary. Arch Arch // GOOS operating system this binary is executing on. GOOS string + debugInfoDirectories []string + // Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point Functions []Function // Sources is a list of all source files found in debug_line. @@ -46,46 +46,32 @@ type BinaryInfo struct { lastModified time.Time // Time the executable of this process was last modified - closer io.Closer - sepDebugCloser io.Closer - - staticBase uint64 - ElfDynamicSection ElfDynamicSection // Maps package names to package paths, needed to lookup types inside DWARF info packageMap map[string]string - dwarf *dwarf.Data - dwarfReader *dwarf.Reader frameEntries frame.FrameDescriptionEntries - loclist loclistReader compileUnits []*compileUnit - types map[string]dwarf.Offset + types map[string]dwarfRef packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address - typeCache map[dwarf.Offset]godwarf.Type gStructOffset uint64 - loadModuleDataOnce sync.Once - moduleData []moduleData - nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry - - // runtimeTypeToDIE maps between the offset of a runtime._type in - // runtime.moduledata.types and the offset of the DIE in debug_info. This - // map is filled by using the extended attribute godwarf.AttrGoRuntimeType - // which was added in go 1.11. - runtimeTypeToDIE map[uint64]runtimeTypeDIE + // nameOfRuntimeType maps an address of a runtime._type struct to its + // decoded name. Used with versions of Go <= 1.10 to figure out the DIE of + // the concrete type of interfaces. + nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry // consts[off] lists all the constants with the type defined at offset off. consts constantsMap - loadErrMu sync.Mutex - loadErr error - // Images is a list of loaded shared libraries (also known as // shared objects on linux or DLLs on windws). + // Element 0 is the executable file. Images []*Image + + initialized bool } // ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture. @@ -119,6 +105,14 @@ type compileUnit struct { producer string // producer attribute startOffset, endOffset dwarf.Offset // interval of offsets contained in this compile unit + + image *Image // parent image of this compilation unit. +} + +// dwarfRef is a reference to a Debug Info Entry inside a shared object. +type dwarfRef struct { + imageIndex int + offset dwarf.Offset } type partialUnitConstant struct { @@ -202,7 +196,7 @@ func (fn *Function) Optimized() bool { return fn.cu.optimized } -type constantsMap map[dwarf.Offset]*constantType +type constantsMap map[dwarfRef]*constantType type constantType struct { initialized bool @@ -221,6 +215,7 @@ type constantValue struct { // a register, or non-contiguously) addr will be 0. type packageVar struct { name string + cu *compileUnit offset dwarf.Offset addr uint64 } @@ -303,7 +298,7 @@ type ElfDynamicSection struct { // NewBinaryInfo returns an initialized but unloaded BinaryInfo struct. func NewBinaryInfo(goos, goarch string) *BinaryInfo { - r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)} + r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry)} // TODO: find better way to determine proc arch (perhaps use executable file info). switch goarch { @@ -323,16 +318,22 @@ func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDi bi.lastModified = fi.ModTime() } + bi.debugInfoDirectories = debugInfoDirs + + return bi.AddImage(path, entryPoint) +} + +func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64) error { var wg sync.WaitGroup defer wg.Wait() - bi.Path = path + switch bi.GOOS { case "linux": - return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, &wg) + return loadBinaryInfoElf(bi, image, path, entryPoint, &wg) case "windows": - return bi.LoadBinaryInfoPE(path, entryPoint, &wg) + return loadBinaryInfoPE(bi, image, path, entryPoint, &wg) case "darwin": - return bi.LoadBinaryInfoMacho(path, entryPoint, &wg) + return loadBinaryInfoMacho(bi, image, path, entryPoint, &wg) } return errors.New("unsupported operating system") } @@ -349,8 +350,8 @@ func (bi *BinaryInfo) LastModified() time.Time { } // DwarfReader returns a reader for the dwarf data -func (bi *BinaryInfo) DwarfReader() *reader.Reader { - return reader.New(bi.dwarf) +func (so *Image) DwarfReader() *reader.Reader { + return reader.New(so.dwarf) } // Types returns list of types present in the debugged program. @@ -426,41 +427,126 @@ func (bi *BinaryInfo) PCToFunc(pc uint64) *Function { // Image represents a loaded library file (shared object on linux, DLL on windows). type Image struct { - Path string - addr uint64 + Path string + StaticBase uint64 + addr uint64 + + index int // index of this object in BinaryInfo.SharedObjects + + closer io.Closer + sepDebugCloser io.Closer + + dwarf *dwarf.Data + dwarfReader *dwarf.Reader + loclist loclistReader + + typeCache map[dwarf.Offset]godwarf.Type + + // runtimeTypeToDIE maps between the offset of a runtime._type in + // runtime.moduledata.types and the offset of the DIE in debug_info. This + // map is filled by using the extended attribute godwarf.AttrGoRuntimeType + // which was added in go 1.11. + runtimeTypeToDIE map[uint64]runtimeTypeDIE + + loadErrMu sync.Mutex + loadErr error } -// AddSharedObject adds the specified shared object to bi. -func (bi *BinaryInfo) AddImage(path string, addr uint64) { - if !strings.HasPrefix(path, "/") { - return +// hasImage returns true if the shared object has already been added. +func (bi *BinaryInfo) hasImage(path string, addr uint64) bool { + if len(bi.Images) > 0 && !strings.HasPrefix(path, "/") { + return true } for _, image := range bi.Images { if image.Path == path && image.addr == addr { - return + return true + } + } + return false +} + +// AddImage adds the specified image to bi, loading data asynchronously. +// Addr is the relocated entry point for the executable and staticBase (i.e. +// the relocation offset) for all other images. +// The first image added must be the executable file. +func (bi *BinaryInfo) AddImage(path string, addr uint64) error { + if bi.hasImage(path, addr) { + return nil + } + image := &Image{Path: path, addr: addr, typeCache: make(map[dwarf.Offset]godwarf.Type)} + // add Image regardless of error so that we don't attempt to re-add it every time we stop + image.index = len(bi.Images) + bi.Images = append(bi.Images, image) + err := loadBinaryInfo(bi, image, path, addr) + if err != nil { + bi.Images[len(bi.Images)-1].loadErr = err + } + return err +} + +// moduleDataToImage finds the image corresponding to the given module data object. +func (bi *BinaryInfo) moduleDataToImage(md *moduleData) *Image { + return bi.funcToImage(bi.PCToFunc(uint64(md.text))) +} + +// imageToModuleData finds the module data in mds corresponding to the given image. +func (bi *BinaryInfo) imageToModuleData(image *Image, mds []moduleData) *moduleData { + for _, md := range mds { + im2 := bi.moduleDataToImage(&md) + if im2.index == image.index { + return &md } } - //TODO(aarzilli): actually load informations about the image here - bi.Images = append(bi.Images, &Image{Path: path, addr: addr}) + return nil +} + +// typeToImage returns the image containing the give type. +func (bi *BinaryInfo) typeToImage(typ godwarf.Type) *Image { + return bi.Images[typ.Common().Index] } // Close closes all internal readers. func (bi *BinaryInfo) Close() error { - if bi.sepDebugCloser != nil { - bi.sepDebugCloser.Close() + var errs []error + for _, image := range bi.Images { + if image.sepDebugCloser != nil { + err := image.sepDebugCloser.Close() + if err != nil { + errs = append(errs, fmt.Errorf("closing shared object %q (split dwarf): %v", image.Path, err)) + } + } + err := image.closer.Close() + if err != nil { + errs = append(errs, fmt.Errorf("closing shared object %q: %v", image.Path, err)) + } + } + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + return fmt.Errorf("multiple errors closing executable files") } - return bi.closer.Close() } -func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) { - bi.loadErrMu.Lock() - bi.loadErr = fmt.Errorf(fmtstr, args...) - bi.loadErrMu.Unlock() +func (image *Image) setLoadError(fmtstr string, args ...interface{}) { + image.loadErrMu.Lock() + image.loadErr = fmt.Errorf(fmtstr, args...) + image.loadErrMu.Unlock() } -// LoadError returns any internal load error. +// LoadError returns any internal error incurred while loading the last shared object. func (bi *BinaryInfo) LoadError() error { - return bi.loadErr + if len(bi.Images) == 0 { + return nil + } + return bi.Images[len(bi.Images)-1].loadErr +} + +// LoadError returns any error incurred while loading this image. +func (image *Image) LoadError() error { + return image.loadErr } type nilCloser struct{} @@ -470,22 +556,26 @@ func (c *nilCloser) Close() error { return nil } // LoadFromData creates a new BinaryInfo object using the specified data. // This is used for debugging BinaryInfo, you should use LoadBinary instead. func (bi *BinaryInfo) LoadFromData(dwdata *dwarf.Data, debugFrameBytes, debugLineBytes, debugLocBytes []byte) { - bi.closer = (*nilCloser)(nil) - bi.sepDebugCloser = (*nilCloser)(nil) - bi.dwarf = dwdata + image := &Image{} + image.closer = (*nilCloser)(nil) + image.sepDebugCloser = (*nilCloser)(nil) + image.dwarf = dwdata + image.typeCache = make(map[dwarf.Offset]godwarf.Type) if debugFrameBytes != nil { - bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), bi.staticBase) + bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0) } - bi.loclistInit(debugLocBytes) + image.loclistInit(debugLocBytes, bi.Arch.PtrSize()) + + bi.loadDebugInfoMaps(image, debugLineBytes, nil, nil) - bi.loadDebugInfoMaps(debugLineBytes, nil, nil) + bi.Images = append(bi.Images, image) } -func (bi *BinaryInfo) loclistInit(data []byte) { - bi.loclist.data = data - bi.loclist.ptrSz = bi.Arch.PtrSize() +func (image *Image) loclistInit(data []byte, ptrSz int) { + image.loclist.data = data + image.loclist.ptrSz = ptrSz } func (bi *BinaryInfo) locationExpr(entry reader.Entry, attr dwarf.Attr, pc uint64) ([]byte, string, error) { @@ -503,9 +593,6 @@ func (bi *BinaryInfo) locationExpr(entry reader.Entry, attr dwarf.Attr, pc uint6 if !ok { return nil, "", fmt.Errorf("could not interpret location attribute %s", attr) } - if bi.loclist.data == nil { - return nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x (no debug_loc section found)", off, pc) - } instr := bi.loclistEntry(off, pc) if instr == nil { return nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x", off, pc) @@ -533,13 +620,18 @@ func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, r // for address pc. func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte { var base uint64 + image := bi.Images[0] if cu := bi.findCompileUnit(pc); cu != nil { base = cu.lowPC + image = cu.image + } + if image == nil || image.loclist.data == nil { + return nil } - bi.loclist.Seek(int(off)) + image.loclist.Seek(int(off)) var e loclistEntry - for bi.loclist.Next(&e) { + for image.loclist.Next(&e) { if e.BaseAddressSelection() { base = e.highpc continue @@ -584,8 +676,17 @@ func (bi *BinaryInfo) Producer() string { } // Type returns the Dwarf type entry at `offset`. -func (bi *BinaryInfo) Type(offset dwarf.Offset) (godwarf.Type, error) { - return godwarf.ReadType(bi.dwarf, offset, bi.typeCache) +func (image *Image) Type(offset dwarf.Offset) (godwarf.Type, error) { + return godwarf.ReadType(image.dwarf, image.index, offset, image.typeCache) +} + +// funcToImage returns the Image containing function fn, or the +// executable file as a fallback. +func (bi *BinaryInfo) funcToImage(fn *Function) *Image { + if fn == nil { + return bi.Images[0] + } + return fn.cu.image } // ELF /////////////////////////////////////////////////////////////// @@ -607,7 +708,7 @@ func (e *ErrNoBuildIDNote) Error() string { // // Alternatively, if the debug file cannot be found be the build-id, Delve // will look in directories specified by the debug-info-directories config value. -func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) { +func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) { var debugFilePath string for _, dir := range debugInfoDirectories { var potentialDebugFilePath string @@ -618,7 +719,7 @@ func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File, debugInfoDirectories } potentialDebugFilePath = fmt.Sprintf("%s/%s/%s.debug", dir, desc1, desc2) } else { - potentialDebugFilePath = fmt.Sprintf("%s/%s.debug", dir, filepath.Base(bi.Path)) + potentialDebugFilePath = fmt.Sprintf("%s/%s.debug", dir, filepath.Base(image.Path)) } _, err := os.Stat(potentialDebugFilePath) if err == nil { @@ -677,13 +778,13 @@ func parseBuildID(exe *elf.File) (string, string, error) { return desc[:2], desc[2:], nil } -// LoadBinaryInfoElf specifically loads information from an ELF binary. -func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, debugInfoDirectories []string, wg *sync.WaitGroup) error { +// loadBinaryInfoElf specifically loads information from an ELF binary. +func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, wg *sync.WaitGroup) error { exe, err := os.OpenFile(path, 0, os.ModePerm) if err != nil { return err } - bi.closer = exe + image.closer = exe elfFile, err := elf.NewFile(exe) if err != nil { return err @@ -692,73 +793,85 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, debugInf return ErrUnsupportedLinuxArch } - if entryPoint != 0 { - bi.staticBase = entryPoint - elfFile.Entry - } else { - if elfFile.Type == elf.ET_DYN { - return ErrCouldNotDetermineRelocation + if image.index == 0 { + // adding executable file: + // - addr is entryPoint therefore staticBase needs to be calculated by + // subtracting it from the entry point specified in the executable file + // - memory address of the .dynamic section needs to be recorded in + // BinaryInfo so that we can find loadded libraries. + if addr != 0 { + image.StaticBase = addr - elfFile.Entry + } else { + if elfFile.Type == elf.ET_DYN { + return ErrCouldNotDetermineRelocation + } } - } - - if dynsec := elfFile.Section(".dynamic"); dynsec != nil { - bi.ElfDynamicSection.Addr = dynsec.Addr + bi.staticBase - bi.ElfDynamicSection.Size = dynsec.Size + if dynsec := elfFile.Section(".dynamic"); dynsec != nil { + bi.ElfDynamicSection.Addr = dynsec.Addr + image.StaticBase + bi.ElfDynamicSection.Size = dynsec.Size + } + } else { + image.StaticBase = addr } dwarfFile := elfFile - bi.dwarf, err = elfFile.DWARF() + image.dwarf, err = elfFile.DWARF() if err != nil { var sepFile *os.File var serr error - sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile, debugInfoDirectories) + sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(image, elfFile, bi.debugInfoDirectories) if serr != nil { if _, ok := serr.(*ErrNoBuildIDNote); ok { return err } return serr } - bi.sepDebugCloser = sepFile - bi.dwarf, err = dwarfFile.DWARF() + image.sepDebugCloser = sepFile + image.dwarf, err = dwarfFile.DWARF() if err != nil { return err } } - bi.dwarfReader = bi.dwarf.Reader() + image.dwarfReader = image.dwarf.Reader() debugLineBytes, err := godwarf.GetDebugSectionElf(dwarfFile, "line") if err != nil { return err } debugLocBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "loc") - bi.loclistInit(debugLocBytes) + image.loclistInit(debugLocBytes, bi.Arch.PtrSize()) - wg.Add(3) - go bi.parseDebugFrameElf(dwarfFile, wg) - go bi.loadDebugInfoMaps(debugLineBytes, wg, nil) - go bi.setGStructOffsetElf(dwarfFile, wg) + wg.Add(2) + go bi.parseDebugFrameElf(image, dwarfFile, wg) + go bi.loadDebugInfoMaps(image, debugLineBytes, wg, nil) + if image.index == 0 { + // determine g struct offset only when loading the executable file + wg.Add(1) + go bi.setGStructOffsetElf(image, dwarfFile, wg) + } return nil } -func (bi *BinaryInfo) parseDebugFrameElf(exe *elf.File, wg *sync.WaitGroup) { +func (bi *BinaryInfo) parseDebugFrameElf(image *Image, exe *elf.File, wg *sync.WaitGroup) { defer wg.Done() debugFrameData, err := godwarf.GetDebugSectionElf(exe, "frame") if err != nil { - bi.setLoadError("could not get .debug_frame section: %v", err) + image.setLoadError("could not get .debug_frame section: %v", err) return } debugInfoData, err := godwarf.GetDebugSectionElf(exe, "info") if err != nil { - bi.setLoadError("could not get .debug_info section: %v", err) + image.setLoadError("could not get .debug_info section: %v", err) return } - bi.frameEntries = frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), bi.staticBase) + bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), image.StaticBase)) } -func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { +func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.WaitGroup) { defer wg.Done() // This is a bit arcane. Essentially: @@ -769,7 +882,7 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { // offset in libc's TLS block. symbols, err := exe.Symbols() if err != nil { - bi.setLoadError("could not parse ELF symbols: %v", err) + image.setLoadError("could not parse ELF symbols: %v", err) return } var tlsg *elf.Symbol @@ -800,17 +913,17 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { const _IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 -// LoadBinaryInfoPE specifically loads information from a PE binary. -func (bi *BinaryInfo) LoadBinaryInfoPE(path string, entryPoint uint64, wg *sync.WaitGroup) error { +// loadBinaryInfoPE specifically loads information from a PE binary. +func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint64, wg *sync.WaitGroup) error { peFile, closer, err := openExecutablePathPE(path) if err != nil { return err } - bi.closer = closer + image.closer = closer if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { return ErrUnsupportedWindowsArch } - bi.dwarf, err = peFile.DWARF() + image.dwarf, err = peFile.DWARF() if err != nil { return err } @@ -818,25 +931,25 @@ func (bi *BinaryInfo) LoadBinaryInfoPE(path string, entryPoint uint64, wg *sync. //TODO(aarzilli): actually test this when Go supports PIE buildmode on Windows. opth := peFile.OptionalHeader.(*pe.OptionalHeader64) if entryPoint != 0 { - bi.staticBase = entryPoint - opth.ImageBase + image.StaticBase = entryPoint - opth.ImageBase } else { if opth.DllCharacteristics&_IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE != 0 { return ErrCouldNotDetermineRelocation } } - bi.dwarfReader = bi.dwarf.Reader() + image.dwarfReader = image.dwarf.Reader() debugLineBytes, err := godwarf.GetDebugSectionPE(peFile, "line") if err != nil { return err } debugLocBytes, _ := godwarf.GetDebugSectionPE(peFile, "loc") - bi.loclistInit(debugLocBytes) + image.loclistInit(debugLocBytes, bi.Arch.PtrSize()) wg.Add(2) - go bi.parseDebugFramePE(peFile, wg) - go bi.loadDebugInfoMaps(debugLineBytes, wg, nil) + go bi.parseDebugFramePE(image, peFile, wg) + go bi.loadDebugInfoMaps(image, debugLineBytes, wg, nil) // Use ArbitraryUserPointer (0x28) as pointer to pointer // to G struct per: @@ -859,21 +972,21 @@ func openExecutablePathPE(path string) (*pe.File, io.Closer, error) { return peFile, f, nil } -func (bi *BinaryInfo) parseDebugFramePE(exe *pe.File, wg *sync.WaitGroup) { +func (bi *BinaryInfo) parseDebugFramePE(image *Image, exe *pe.File, wg *sync.WaitGroup) { defer wg.Done() debugFrameBytes, err := godwarf.GetDebugSectionPE(exe, "frame") if err != nil { - bi.setLoadError("could not get .debug_frame section: %v", err) + image.setLoadError("could not get .debug_frame section: %v", err) return } debugInfoBytes, err := godwarf.GetDebugSectionPE(exe, "info") if err != nil { - bi.setLoadError("could not get .debug_info section: %v", err) + image.setLoadError("could not get .debug_info section: %v", err) return } - bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), bi.staticBase) + bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase)) } // Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go @@ -895,33 +1008,33 @@ func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { // MACH-O //////////////////////////////////////////////////////////// -// LoadBinaryInfoMacho specifically loads information from a Mach-O binary. -func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, entryPoint uint64, wg *sync.WaitGroup) error { +// loadBinaryInfoMacho specifically loads information from a Mach-O binary. +func loadBinaryInfoMacho(bi *BinaryInfo, image *Image, path string, entryPoint uint64, wg *sync.WaitGroup) error { exe, err := macho.Open(path) if err != nil { return err } - bi.closer = exe + image.closer = exe if exe.Cpu != macho.CpuAmd64 { return ErrUnsupportedDarwinArch } - bi.dwarf, err = exe.DWARF() + image.dwarf, err = exe.DWARF() if err != nil { return err } - bi.dwarfReader = bi.dwarf.Reader() + image.dwarfReader = image.dwarf.Reader() debugLineBytes, err := godwarf.GetDebugSectionMacho(exe, "line") if err != nil { return err } debugLocBytes, _ := godwarf.GetDebugSectionMacho(exe, "loc") - bi.loclistInit(debugLocBytes) + image.loclistInit(debugLocBytes, bi.Arch.PtrSize()) wg.Add(2) - go bi.parseDebugFrameMacho(exe, wg) - go bi.loadDebugInfoMaps(debugLineBytes, wg, bi.setGStructOffsetMacho) + go bi.parseDebugFrameMacho(image, exe, wg) + go bi.loadDebugInfoMaps(image, debugLineBytes, wg, bi.setGStructOffsetMacho) return nil } @@ -937,19 +1050,19 @@ func (bi *BinaryInfo) setGStructOffsetMacho() { bi.gStructOffset = 0x8a0 } -func (bi *BinaryInfo) parseDebugFrameMacho(exe *macho.File, wg *sync.WaitGroup) { +func (bi *BinaryInfo) parseDebugFrameMacho(image *Image, exe *macho.File, wg *sync.WaitGroup) { defer wg.Done() debugFrameBytes, err := godwarf.GetDebugSectionMacho(exe, "frame") if err != nil { - bi.setLoadError("could not get __debug_frame section: %v", err) + image.setLoadError("could not get __debug_frame section: %v", err) return } debugInfoBytes, err := godwarf.GetDebugSectionMacho(exe, "info") if err != nil { - bi.setLoadError("could not get .debug_info section: %v", err) + image.setLoadError("could not get .debug_info section: %v", err) return } - bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), bi.staticBase) + bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase)) } diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go index 6f02369dfb..68513a8db0 100644 --- a/pkg/proc/dwarf_expr_test.go +++ b/pkg/proc/dwarf_expr_test.go @@ -88,9 +88,9 @@ func dwarfExprCheck(t *testing.T, mem proc.MemoryReadWriter, regs op.DwarfRegist return scope } -func dwarfRegisters(regs *linutil.AMD64Registers) op.DwarfRegisters { +func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) op.DwarfRegisters { a := proc.AMD64Arch("linux") - dwarfRegs := a.RegistersToDwarfRegisters(regs, 0) + dwarfRegs := a.RegistersToDwarfRegisters(bi, regs) dwarfRegs.CFA = defaultCFA dwarfRegs.FrameBase = defaultCFA return dwarfRegs @@ -123,7 +123,7 @@ func TestDwarfExprRegisters(t *testing.T) { regs.Regs.Rax = uint64(testCases["a"]) regs.Regs.Rdx = uint64(testCases["c"]) - dwarfExprCheck(t, mem, dwarfRegisters(®s), bi, testCases, mainfn) + dwarfExprCheck(t, mem, dwarfRegisters(bi, ®s), bi, testCases, mainfn) } func TestDwarfExprComposite(t *testing.T) { @@ -181,7 +181,7 @@ func TestDwarfExprComposite(t *testing.T) { regs.Regs.Rcx = uint64(testCases["pair.k"]) regs.Regs.Rbx = uint64(testCases["n"]) - scope := dwarfExprCheck(t, mem, dwarfRegisters(®s), bi, testCases, mainfn) + scope := dwarfExprCheck(t, mem, dwarfRegisters(bi, ®s), bi, testCases, mainfn) thevar, err := scope.EvalExpression("s", normalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", "s")) @@ -216,7 +216,7 @@ func TestDwarfExprLoclist(t *testing.T) { mem := newFakeMemory(defaultCFA, uint16(before), uint16(after)) regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}} - scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: dwarfRegisters(®s), Mem: mem, Gvar: nil, BinInfo: bi} + scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: dwarfRegisters(bi, ®s), Mem: mem, Gvar: nil, BinInfo: bi} uintExprCheck(t, scope, "a", before) scope.PC = 0x40800 diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 76775004a7..ac2c58a32d 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -299,7 +299,7 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) { const CFA = 0x1000 - vrdr := reader.Variables(bi.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, bi.staticBase), int(^uint(0)>>1), false) + vrdr := reader.Variables(fn.cu.image.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, fn.cu.image.StaticBase), int(^uint(0)>>1), false) // typechecks arguments, calculates argument frame size for vrdr.Next() { @@ -307,7 +307,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i if e.Tag != dwarf.TagFormalParameter { continue } - entry, argname, typ, err := readVarEntry(e, bi) + entry, argname, typ, err := readVarEntry(e, fn.cu.image) if err != nil { return 0, nil, err } @@ -552,8 +552,8 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64 scope.Regs.CFA = cfa scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = sp - scope.BinInfo.dwarfReader.Seek(fn.offset) - e, err := scope.BinInfo.dwarfReader.Next() + fn.cu.image.dwarfReader.Seek(fn.offset) + e, err := fn.cu.image.dwarfReader.Next() if err != nil { return err } diff --git a/pkg/proc/moduledata.go b/pkg/proc/moduledata.go index a24714e008..8f44471f33 100644 --- a/pkg/proc/moduledata.go +++ b/pkg/proc/moduledata.go @@ -7,76 +7,82 @@ import ( // delve counterpart to runtime.moduledata type moduleData struct { + text, etext uintptr types, etypes uintptr typemapVar *Variable } -func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) { - bi.loadModuleDataOnce.Do(func() { - scope := globalScope(bi, mem) - var md *Variable - md, err = scope.findGlobal("runtime.firstmoduledata") - if err != nil { - return - } - - for md.Addr != 0 { - var typesVar, etypesVar, nextVar, typemapVar *Variable - var types, etypes uint64 +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 + } - 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 + 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 } - bi.moduleData = append(bi.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar}) + } + + var err error - md = nextVar.maybeDereference() - if md.Unreadable != nil { - err = md.Unreadable - return + touint := func(name string) (ret uintptr) { + if err == nil { + var n uint64 + n, err = vars[name].asUint() + ret = uintptr(n) } + return ret } - }) - return -} + 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 + } -func findModuleDataForType(bi *BinaryInfo, typeAddr uintptr, mem MemoryReadWriter) (*moduleData, error) { - if err := loadModuleData(bi, mem); err != nil { - return nil, err + md = vars[nextField].maybeDereference() + if md.Unreadable != nil { + return nil, md.Unreadable + } } - var md *moduleData - for i := range bi.moduleData { - if typeAddr >= bi.moduleData[i].types && typeAddr < bi.moduleData[i].etypes { - md = &bi.moduleData[i] + 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 md, nil + return nil } -func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (*Variable, error) { +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, err := findModuleDataForType(bi, typeAddr, mem) - if err != nil { - return nil, err - } + md := findModuleDataForType(bi, mds, typeAddr, mem) rtyp, err := bi.findType("runtime._type") if err != nil { @@ -102,13 +108,9 @@ func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea return newVariable("", res, rtyp, bi, mem), nil } -func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) { +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 - if err = loadModuleData(bi, mem); err != nil { - return "", "", 0, err - } - - for _, md := range bi.moduleData { + for _, md := range mds { if typeAddr >= md.types && typeAddr < md.etypes { return loadName(bi, md.types+off, mem) } @@ -128,7 +130,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea } func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) { - scope := globalScope(bi, mem) + scope := globalScope(bi, bi.Images[0], mem) reflectOffs, err := scope.findGlobal("runtime.reflectOffs") if err != nil { return nil, err diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index a845187d8d..16d658586a 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -506,10 +506,12 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) { } } + exeimage := dbp.BinInfo().Images[0] // Image corresponding to the executable file + var ( threadg = map[int]*G{} allg []*G - rdr = dbp.BinInfo().DwarfReader() + rdr = exeimage.DwarfReader() ) threads := dbp.ThreadList() @@ -523,7 +525,7 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) { } } - addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase) + addr, err := rdr.AddrFor("runtime.allglen", exeimage.StaticBase) if err != nil { return nil, -1, err } @@ -535,10 +537,10 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) { allglen := binary.LittleEndian.Uint64(allglenBytes) rdr.Seek(0) - allgentryaddr, err := rdr.AddrFor("runtime.allgs", dbp.BinInfo().staticBase) + allgentryaddr, err := rdr.AddrFor("runtime.allgs", exeimage.StaticBase) if err != nil { // try old name (pre Go 1.6) - allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase) + allgentryaddr, err = rdr.AddrFor("runtime.allg", exeimage.StaticBase) if err != nil { return nil, -1, err } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index f5d67b6bda..7b0edf1152 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -15,6 +15,7 @@ import ( "path/filepath" "reflect" "runtime" + "strconv" "strings" "testing" "time" @@ -184,7 +185,11 @@ func setFunctionBreakpoint(p proc.Process, fname string) (*proc.Breakpoint, erro } func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, lineno int) *proc.Breakpoint { - addr, err := proc.FindFileLocation(p, fixture.Source, lineno) + return setFileLineBreakpoint(p, t, fixture.Source, lineno) +} + +func setFileLineBreakpoint(p proc.Process, t *testing.T, path string, lineno int) *proc.Breakpoint { + addr, err := proc.FindFileLocation(p, path, lineno) if err != nil { t.Fatalf("FindFileLocation: %v", err) } @@ -366,7 +371,7 @@ const ( type seqTest struct { cf contFunc - pos int + pos interface{} } func testseq(program string, contFunc contFunc, testcases []nextTest, initialLocation string, t *testing.T) { @@ -398,7 +403,7 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te bp, err = setFunctionBreakpoint(p, initialLocation) } else if testcases[0].cf == contContinue { var pc uint64 - pc, err = proc.FindFileLocation(p, fixture.Source, testcases[0].pos) + pc, err = proc.FindFileLocation(p, fixture.Source, testcases[0].pos.(int)) assertNoError(err, t, "FindFileLocation()") bp, err = p.SetBreakpoint(pc, proc.UserBreakpoint, nil) } else { @@ -449,9 +454,19 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te if traceTestseq2 { t.Logf("at %#x %s:%d", pc, f, ln) + fmt.Printf("at %#x %s:%d", pc, f, ln) } - if ln != tc.pos { - t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", tc.pos, filepath.Base(f), ln, pc, i) + switch pos := tc.pos.(type) { + case int: + if ln != pos { + t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i) + } + case string: + v := strings.Split(pos, ":") + tgtln, _ := strconv.Atoi(v[1]) + if !strings.HasSuffix(f, v[0]) || (ln != tgtln) { + t.Fatalf("Program did not continue to correct next location, expected %s was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i) + } } } @@ -4123,10 +4138,11 @@ func TestListImages(t *testing.T) { withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, 0, func(p proc.Process, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "first continue") + f, l := currentLineNumber(p, t) plugin1Found := false - t.Logf("Libraries before:") + t.Logf("Libraries before %s:%d:", f, l) for _, image := range p.BinInfo().Images { - t.Logf("\t%#v", image) + t.Logf("\t%#x %q err:%v", image.StaticBase, image.Path, image.LoadError()) if image.Path == pluginFixtures[0].Path { plugin1Found = true } @@ -4135,10 +4151,11 @@ func TestListImages(t *testing.T) { t.Fatalf("Could not find plugin1") } assertNoError(proc.Continue(p), t, "second continue") + f, l = currentLineNumber(p, t) plugin1Found, plugin2Found := false, false - t.Logf("Libraries after:") + t.Logf("Libraries after %s:%d:", f, l) for _, image := range p.BinInfo().Images { - t.Logf("\t%#v", image) + t.Logf("\t%#x %q err:%v", image.StaticBase, image.Path, image.LoadError()) switch image.Path { case pluginFixtures[0].Path: plugin1Found = true @@ -4154,3 +4171,20 @@ func TestListImages(t *testing.T) { } }) } + +func TestPluginStepping(t *testing.T) { + pluginFixtures := protest.WithPlugins(t, "plugin1/", "plugin2/") + + testseq2Args(".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, 0, t, "plugintest2", "", []seqTest{ + {contContinue, 41}, + {contStep, "plugin1.go:9"}, + {contStep, "plugin1.go:10"}, + {contStep, "plugin1.go:11"}, + {contNext, "plugin1.go:12"}, + {contNext, "plugintest2.go:41"}, + {contNext, "plugintest2.go:42"}, + {contStep, "plugin2.go:22"}, + {contNext, "plugin2.go:23"}, + {contNext, "plugin2.go:26"}, + {contNext, "plugintest2.go:42"}}) +} diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 30965c896b..5949928612 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -100,7 +100,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) { if err != nil { return nil, err } - it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs, thread.BinInfo().staticBase), 0, nil, -1, nil) + it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(thread.BinInfo(), regs), 0, nil, -1, nil) return it.stacktrace(depth) } return g.Stacktrace(depth, false) @@ -117,7 +117,7 @@ func (g *G) stackIterator() (*stackIterator, error) { if err != nil { return nil, err } - return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs, g.variable.bi.staticBase), g.stackhi, stkbar, g.stkbarPos, g), nil + return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(g.variable.bi, regs), g.stackhi, stkbar, g.stkbarPos, g), nil } return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos, g), nil } @@ -168,8 +168,6 @@ type stackIterator struct { g *G // the goroutine being stacktraced, nil if we are stacktracing a goroutine-less thread g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use) - - dwarfReader *dwarf.Reader } type savedLR struct { @@ -209,7 +207,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegiste } } } - return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp, dwarfReader: bi.dwarf.Reader()} + return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp} } // Next points the iterator to the next stack frame. @@ -353,8 +351,9 @@ func (it *stackIterator) Err() error { // frameBase calculates the frame base pseudo-register for DWARF for fn and // the current frame. func (it *stackIterator) frameBase(fn *Function) int64 { - it.dwarfReader.Seek(fn.offset) - e, err := it.dwarfReader.Next() + rdr := fn.cu.image.dwarfReader + rdr.Seek(fn.offset) + e, err := rdr.Next() if err != nil { return 0 } @@ -442,9 +441,11 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe callpc-- } - irdr := reader.InlineStack(it.bi.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, it.bi.staticBase)) + image := frame.Call.Fn.cu.image + + irdr := reader.InlineStack(image.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, image.StaticBase)) for irdr.Next() { - entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), it.dwarfReader) + entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), image.dwarfReader) fnname, okname := entry.Val(dwarf.AttrName).(string) fileidx, okfileidx := entry.Val(dwarf.AttrCallFile).(int64) @@ -498,11 +499,13 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0) if cfareg == nil { it.err = fmt.Errorf("CFA becomes undefined at PC %#x", it.pc) - return op.DwarfRegisters{StaticBase: it.bi.staticBase}, 0, 0 + return op.DwarfRegisters{}, 0, 0 } it.regs.CFA = int64(cfareg.Uint64Val) - callFrameRegs = op.DwarfRegisters{StaticBase: it.bi.staticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum} + callimage := it.bi.funcToImage(it.bi.PCToFunc(it.pc)) + + callFrameRegs = op.DwarfRegisters{StaticBase: callimage.StaticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum} // According to the standard the compiler should be responsible for emitting // rules for the RSP register so that it can then be used to calculate CFA, @@ -714,8 +717,9 @@ func (d *Defer) EvalScope(thread Thread) (*EvalScope, error) { scope.Regs.CFA = (int64(d.variable.Addr) + d.variable.RealType.Common().ByteSize) scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = uint64(scope.Regs.CFA - int64(bi.Arch.PtrSize())) - bi.dwarfReader.Seek(scope.Fn.offset) - e, err := bi.dwarfReader.Next() + rdr := scope.Fn.cu.image.dwarfReader + rdr.Seek(scope.Fn.offset) + e, err := rdr.Next() if err != nil { return nil, fmt.Errorf("could not read DWARF function entry: %v", err) } diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 78450583ff..efb4d781f2 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -343,19 +343,20 @@ func findDeferReturnCalls(text []AsmInstruction) []uint64 { // If includeCurrentFn is true it will also remove all instructions // belonging to the current function. func removeInlinedCalls(dbp Process, pcs []uint64, topframe Stackframe) ([]uint64, error) { - bi := dbp.BinInfo() - irdr := reader.InlineStack(bi.dwarf, topframe.Call.Fn.offset, 0) + image := topframe.Call.Fn.cu.image + dwarf := image.dwarf + irdr := reader.InlineStack(dwarf, topframe.Call.Fn.offset, 0) for irdr.Next() { e := irdr.Entry() if e.Offset == topframe.Call.Fn.offset { continue } - ranges, err := bi.dwarf.Ranges(e) + ranges, err := dwarf.Ranges(e) if err != nil { return pcs, err } for _, rng := range ranges { - pcs = removePCsBetween(pcs, rng[0], rng[1], bi.staticBase) + pcs = removePCsBetween(pcs, rng[0], rng[1], image.StaticBase) } irdr.SkipChildren() } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index d30316ffcf..78f2db362b 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -57,11 +57,12 @@ const ( // Do not call this function directly it isn't able to deal correctly with package paths func (bi *BinaryInfo) findType(name string) (godwarf.Type, error) { - off, found := bi.types[name] + ref, found := bi.types[name] if !found { return nil, reader.TypeNotFoundErr } - return godwarf.ReadType(bi.dwarf, off, bi.typeCache) + image := bi.Images[ref.imageIndex] + return godwarf.ReadType(image.dwarf, ref.imageIndex, ref.offset, image.typeCache) } func pointerTo(typ godwarf.Type, arch Arch) godwarf.Type { @@ -77,7 +78,6 @@ func pointerTo(typ godwarf.Type, arch Arch) godwarf.Type { } func (bi *BinaryInfo) findTypeExpr(expr ast.Expr) (godwarf.Type, error) { - bi.loadPackageMap() if lit, islit := expr.(*ast.BasicLit); islit && lit.Kind == token.STRING { // Allow users to specify type names verbatim as quoted // string. Useful as a catch-all workaround for cases where we don't @@ -136,39 +136,27 @@ func complexType(typename string) bool { return false } -func (bi *BinaryInfo) loadPackageMap() error { - if bi.packageMap != nil { - return nil +func (bi *BinaryInfo) registerTypeToPackageMap(entry *dwarf.Entry) { + if entry.Tag != dwarf.TagTypedef && entry.Tag != dwarf.TagBaseType && entry.Tag != dwarf.TagClassType && entry.Tag != dwarf.TagStructType { + return } - bi.packageMap = map[string]string{} - reader := bi.DwarfReader() - for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { - if err != nil { - return err - } - if entry.Tag != dwarf.TagTypedef && entry.Tag != dwarf.TagBaseType && entry.Tag != dwarf.TagClassType && entry.Tag != dwarf.TagStructType { - continue - } - - typename, ok := entry.Val(dwarf.AttrName).(string) - if !ok || complexType(typename) { - continue - } + typename, ok := entry.Val(dwarf.AttrName).(string) + if !ok || complexType(typename) { + return + } - dot := strings.LastIndex(typename, ".") - if dot < 0 { - continue - } - path := typename[:dot] - slash := strings.LastIndex(path, "/") - if slash < 0 || slash+1 >= len(path) { - continue - } - name := path[slash+1:] - bi.packageMap[name] = path + dot := strings.LastIndex(typename, ".") + if dot < 0 { + return } - return nil + path := typename[:dot] + slash := strings.LastIndex(path, "/") + if slash < 0 || slash+1 >= len(path) { + return + } + name := path[slash+1:] + bi.packageMap[name] = path } type functionsDebugInfoByEntry []Function @@ -183,13 +171,18 @@ func (v compileUnitsByLowpc) Len() int { return len(v) } func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].lowPC < v[j].lowPC } func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } -type packageVarsByAddr []packageVar +type packageVarsByImageAndAddr []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] } +func (v packageVarsByImageAndAddr) Len() int { return len(v) } +func (v packageVarsByImageAndAddr) Less(i int, j int) bool { + if v[i].cu.image.index == v[j].cu.image.index { + return v[i].addr < v[j].addr + } + return v[i].cu.image.index < v[j].cu.image.index +} +func (v packageVarsByImageAndAddr) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } -func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGroup, cont func()) { +func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg *sync.WaitGroup, cont func()) { if wg != nil { defer wg.Done() } @@ -199,14 +192,16 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou var partialUnits = make(map[dwarf.Offset]*partialUnit) var lastOffset dwarf.Offset - bi.types = make(map[string]dwarf.Offset) - bi.packageVars = []packageVar{} - bi.Functions = []Function{} - bi.compileUnits = []*compileUnit{} - bi.consts = make(map[dwarf.Offset]*constantType) - bi.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE) - reader := bi.DwarfReader() - ardr := bi.DwarfReader() + if !bi.initialized { + bi.types = make(map[string]dwarfRef) + bi.consts = make(map[dwarfRef]*constantType) + bi.packageMap = make(map[string]string) + bi.initialized = true + } + image.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE) + + reader := image.DwarfReader() + ardr := image.DwarfReader() abstractOriginNameTable := make(map[dwarf.Offset]string) outer: @@ -225,6 +220,7 @@ outer: cu.endOffset = entry.Offset } cu = &compileUnit{} + cu.image = image cu.entry = entry cu.startOffset = entry.Offset if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage { @@ -235,10 +231,10 @@ outer: if compdir != "" { cu.name = filepath.Join(compdir, cu.name) } - cu.ranges, _ = bi.dwarf.Ranges(entry) + cu.ranges, _ = image.dwarf.Ranges(entry) for i := range cu.ranges { - cu.ranges[i][0] += bi.staticBase - cu.ranges[i][1] += bi.staticBase + cu.ranges[i][0] += image.StaticBase + cu.ranges[i][1] += image.StaticBase } if len(cu.ranges) >= 1 { cu.lowPC = cu.ranges[0][0] @@ -253,7 +249,7 @@ outer: logger.Printf(fmt, args) } } - cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, bi.staticBase) + cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase) } cu.producer, _ = entry.Val(dwarf.AttrProducer).(string) if cu.isgo && cu.producer != "" { @@ -285,7 +281,7 @@ outer: if !cu.isgo { name = "C." + name } - bi.types[name] = offset + bi.types[name] = dwarfRef{image.index, offset} } } @@ -293,10 +289,12 @@ outer: if pu != nil { pu.variables = append(pu.variables, pVar) } else { + pVar2 := pVar if !cu.isgo { - pVar.name = "C." + pVar.name + pVar2.name = "C." + pVar2.name } - bi.packageVars = append(bi.packageVars, pVar) + pVar2.cu = cu + bi.packageVars = append(bi.packageVars, pVar2) } } @@ -307,10 +305,10 @@ outer: if !cu.isgo { pCt.name = "C." + pCt.name } - ct := bi.consts[pCt.typ] + ct := bi.consts[dwarfRef{image.index, pCt.typ}] if ct == nil { ct = &constantType{} - bi.consts[pCt.typ] = ct + bi.consts[dwarfRef{image.index, pCt.typ}] = ct } ct.values = append(ct.values, constantValue{name: pCt.name, fullName: pCt.name, value: pCt.value}) } @@ -340,11 +338,14 @@ outer: name = "C." + name } if _, exists := bi.types[name]; !exists { - bi.types[name] = entry.Offset + bi.types[name] = dwarfRef{image.index, entry.Offset} } } } - bi.registerRuntimeTypeToDIE(entry, ardr) + if cu != nil && cu.isgo { + bi.registerTypeToPackageMap(entry) + } + image.registerRuntimeTypeToDIE(entry, ardr) reader.SkipChildren() case dwarf.TagVariable: @@ -356,12 +357,12 @@ outer: } } if pu != nil { - pu.variables = append(pu.variables, packageVar{n, entry.Offset, addr + bi.staticBase}) + pu.variables = append(pu.variables, packageVar{n, nil, entry.Offset, addr + image.StaticBase}) } else { if !cu.isgo { n = "C." + n } - bi.packageVars = append(bi.packageVars, packageVar{n, entry.Offset, addr + bi.staticBase}) + bi.packageVars = append(bi.packageVars, packageVar{n, cu, entry.Offset, addr + image.StaticBase}) } } @@ -376,10 +377,10 @@ outer: if !cu.isgo { name = "C." + name } - ct := bi.consts[typ] + ct := bi.consts[dwarfRef{image.index, typ}] if ct == nil { ct = &constantType{} - bi.consts[typ] = ct + bi.consts[dwarfRef{image.index, typ}] = ct } ct.values = append(ct.values, constantValue{name: name, fullName: name, value: val}) } @@ -392,10 +393,10 @@ outer: if inval, ok := entry.Val(dwarf.AttrInline).(int64); ok { inlined = inval == 1 } - if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 { + if ranges, _ := image.dwarf.Ranges(entry); len(ranges) == 1 { ok1 = true - lowpc = ranges[0][0] + bi.staticBase - highpc = ranges[0][1] + bi.staticBase + lowpc = ranges[0][0] + image.StaticBase + highpc = ranges[0][1] + image.StaticBase } name, ok2 := entry.Val(dwarf.AttrName).(string) if !ok2 { @@ -443,7 +444,7 @@ outer: if entry.Tag == dwarf.TagInlinedSubroutine { originOffset := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset) name := abstractOriginNameTable[originOffset] - if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 { + if ranges, _ := image.dwarf.Ranges(entry); len(ranges) == 1 { ok1 = true lowpc = ranges[0][0] highpc = ranges[0][1] @@ -454,8 +455,8 @@ outer: callfile := cu.lineInfo.FileNames[callfileidx-1].Path cu.concreteInlinedFns = append(cu.concreteInlinedFns, inlinedFn{ Name: name, - LowPC: lowpc + bi.staticBase, - HighPC: highpc + bi.staticBase, + LowPC: lowpc + image.StaticBase, + HighPC: highpc + image.StaticBase, CallFile: callfile, CallLine: callline, Parent: &fn, @@ -474,7 +475,7 @@ outer: sort.Sort(compileUnitsByLowpc(bi.compileUnits)) sort.Sort(functionsDebugInfoByEntry(bi.Functions)) - sort.Sort(packageVarsByAddr(bi.packageVars)) + sort.Sort(packageVarsByImageAndAddr(bi.packageVars)) bi.LookupFunc = make(map[string]*Function) for i := range bi.Functions { @@ -548,10 +549,10 @@ func (bi *BinaryInfo) expandPackagesInType(expr ast.Expr) { } } -func (bi *BinaryInfo) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.Reader) { +func (image *Image) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.Reader) { if off, ok := entry.Val(godwarf.AttrGoRuntimeType).(uint64); ok { - if _, ok := bi.runtimeTypeToDIE[off]; !ok { - bi.runtimeTypeToDIE[off+bi.staticBase] = runtimeTypeDIE{entry.Offset, -1} + if _, ok := image.runtimeTypeToDIE[off]; !ok { + image.runtimeTypeToDIE[off+image.StaticBase] = runtimeTypeDIE{entry.Offset, -1} } } } @@ -609,13 +610,16 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind // go 1.11 implementation: use extended attribute in debug_info - md, err := findModuleDataForType(bi, _type.Addr, _type.mem) + mds, err := loadModuleData(bi, _type.mem) if err != nil { return nil, 0, fmt.Errorf("error loading module data: %v", err) } + + md := findModuleDataForType(bi, mds, _type.Addr, _type.mem) if md != nil { - if rtdie, ok := bi.runtimeTypeToDIE[uint64(_type.Addr-md.types)]; ok { - typ, err := godwarf.ReadType(bi.dwarf, rtdie.offset, bi.typeCache) + so := bi.moduleDataToImage(md) + if rtdie, ok := so.runtimeTypeToDIE[uint64(_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) } @@ -630,7 +634,7 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind // go1.7 to go1.10 implementation: convert runtime._type structs to type names - typename, kind, err := nameOfRuntimeType(_type) + typename, kind, err := nameOfRuntimeType(mds, _type) if err != nil { return nil, 0, fmt.Errorf("invalid interface type: %v", err) } @@ -651,7 +655,7 @@ type nameOfRuntimeTypeEntry struct { // Returns the type name of the type described in _type. // _type is a non-loaded Variable pointing to runtime._type struct in the target. // The returned string is in the format that's used in DWARF data -func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) { +func nameOfRuntimeType(mds []moduleData, _type *Variable) (typename string, kind int64, err error) { if e, ok := _type.bi.nameOfRuntimeType[_type.Addr]; ok { return e.typename, e.kind, nil } @@ -668,11 +672,17 @@ func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) // Named types are defined by a 'type' expression, everything else // (for example pointers to named types) are not considered named. if tflag&tflagNamed != 0 { - typename, err = nameOfNamedRuntimeType(_type, kind, tflag) + typename, err = nameOfNamedRuntimeType(mds, _type, kind, tflag) + if err == nil { + _type.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename: typename, kind: kind} + } return typename, kind, err } - typename, err = nameOfUnnamedRuntimeType(_type, kind, tflag) + typename, err = nameOfUnnamedRuntimeType(mds, _type, kind, tflag) + if err == nil { + _type.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename: typename, kind: kind} + } return typename, kind, err } @@ -692,7 +702,7 @@ func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) // to a struct specific to the type's kind (for example, if the type // being described is a slice type the variable will be specialized // to a runtime.slicetype). -func nameOfNamedRuntimeType(_type *Variable, kind, tflag int64) (typename string, err error) { +func nameOfNamedRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (typename string, err error) { var strOff int64 if strField := _type.loadFieldNamed("str"); strField != nil && strField.Value != nil { strOff, _ = constant.Int64Val(strField.Value) @@ -704,7 +714,7 @@ func nameOfNamedRuntimeType(_type *Variable, kind, tflag int64) (typename string // For a description of how memory is organized for type names read // the comment to 'type name struct' in $GOROOT/src/reflect/type.go - typename, _, _, err = resolveNameOff(_type.bi, _type.Addr, uintptr(strOff), _type.mem) + typename, _, _, err = resolveNameOff(_type.bi, mds, _type.Addr, uintptr(strOff), _type.mem) if err != nil { return "", err } @@ -730,7 +740,7 @@ func nameOfNamedRuntimeType(_type *Variable, kind, tflag int64) (typename string if ut := uncommon(_type, tflag); ut != nil { if pkgPathField := ut.loadFieldNamed("pkgpath"); pkgPathField != nil && pkgPathField.Value != nil { pkgPathOff, _ := constant.Int64Val(pkgPathField.Value) - pkgPath, _, _, err := resolveNameOff(_type.bi, _type.Addr, uintptr(pkgPathOff), _type.mem) + pkgPath, _, _, err := resolveNameOff(_type.bi, mds, _type.Addr, uintptr(pkgPathOff), _type.mem) if err != nil { return "", err } @@ -747,7 +757,7 @@ func nameOfNamedRuntimeType(_type *Variable, kind, tflag int64) (typename string return typename, nil } -func nameOfUnnamedRuntimeType(_type *Variable, kind, tflag int64) (string, error) { +func nameOfUnnamedRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (string, error) { _type, err := specificRuntimeType(_type, kind) if err != nil { return "", err @@ -760,47 +770,47 @@ func nameOfUnnamedRuntimeType(_type *Variable, kind, tflag int64) (string, error if lenField := _type.loadFieldNamed("len"); lenField != nil && lenField.Value != nil { len, _ = constant.Int64Val(lenField.Value) } - elemname, err := fieldToType(_type, "elem") + elemname, err := fieldToType(mds, _type, "elem") if err != nil { return "", err } return fmt.Sprintf("[%d]%s", len, elemname), nil case reflect.Chan: - elemname, err := fieldToType(_type, "elem") + elemname, err := fieldToType(mds, _type, "elem") if err != nil { return "", err } return "chan " + elemname, nil case reflect.Func: - return nameOfFuncRuntimeType(_type, tflag, true) + return nameOfFuncRuntimeType(mds, _type, tflag, true) case reflect.Interface: - return nameOfInterfaceRuntimeType(_type, kind, tflag) + return nameOfInterfaceRuntimeType(mds, _type, kind, tflag) case reflect.Map: - keyname, err := fieldToType(_type, "key") + keyname, err := fieldToType(mds, _type, "key") if err != nil { return "", err } - elemname, err := fieldToType(_type, "elem") + elemname, err := fieldToType(mds, _type, "elem") if err != nil { return "", err } return "map[" + keyname + "]" + elemname, nil case reflect.Ptr: - elemname, err := fieldToType(_type, "elem") + elemname, err := fieldToType(mds, _type, "elem") if err != nil { return "", err } return "*" + elemname, nil case reflect.Slice: - elemname, err := fieldToType(_type, "elem") + elemname, err := fieldToType(mds, _type, "elem") if err != nil { return "", err } return "[]" + elemname, nil case reflect.Struct: - return nameOfStructRuntimeType(_type, kind, tflag) + return nameOfStructRuntimeType(mds, _type, kind, tflag) default: - return nameOfNamedRuntimeType(_type, kind, tflag) + return nameOfNamedRuntimeType(mds, _type, kind, tflag) } } @@ -808,7 +818,7 @@ func nameOfUnnamedRuntimeType(_type *Variable, kind, tflag int64) (string, error // A runtime.functype is followed by a runtime.uncommontype // (optional) and then by an array of pointers to runtime._type, // one for each input and output argument. -func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string, error) { +func nameOfFuncRuntimeType(mds []moduleData, _type *Variable, tflag int64, anonymous bool) (string, error) { rtyp, err := _type.bi.findType("runtime._type") if err != nil { return "", err @@ -841,7 +851,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string for i := int64(0); i < inCount; i++ { argtype := cursortyp.maybeDereference() cursortyp.Addr += uintptr(_type.bi.Arch.PtrSize()) - argtypename, _, err := nameOfRuntimeType(argtype) + argtypename, _, err := nameOfRuntimeType(mds, argtype) if err != nil { return "", err } @@ -858,7 +868,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string case 1: buf.WriteString(" ") argtype := cursortyp.maybeDereference() - argtypename, _, err := nameOfRuntimeType(argtype) + argtypename, _, err := nameOfRuntimeType(mds, argtype) if err != nil { return "", err } @@ -868,7 +878,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string for i := int64(0); i < outCount; i++ { argtype := cursortyp.maybeDereference() cursortyp.Addr += uintptr(_type.bi.Arch.PtrSize()) - argtypename, _, err := nameOfRuntimeType(argtype) + argtypename, _, err := nameOfRuntimeType(mds, argtype) if err != nil { return "", err } @@ -882,7 +892,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string return buf.String(), nil } -func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, error) { +func nameOfInterfaceRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (string, error) { var buf bytes.Buffer buf.WriteString("interface {") @@ -905,14 +915,14 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err case imethodFieldName: nameoff, _ := constant.Int64Val(im.Children[i].Value) var err error - methodname, _, _, err = resolveNameOff(_type.bi, _type.Addr, uintptr(nameoff), _type.mem) + methodname, _, _, err = resolveNameOff(_type.bi, mds, _type.Addr, uintptr(nameoff), _type.mem) if err != nil { return "", err } case imethodFieldItyp: typeoff, _ := constant.Int64Val(im.Children[i].Value) - typ, err := resolveTypeOff(_type.bi, _type.Addr, uintptr(typeoff), _type.mem) + typ, err := resolveTypeOff(_type.bi, mds, _type.Addr, uintptr(typeoff), _type.mem) if err != nil { return "", err } @@ -924,7 +934,7 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err if tflagField := typ.loadFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil { tflag, _ = constant.Int64Val(tflagField.Value) } - methodtype, err = nameOfFuncRuntimeType(typ, tflag, false) + methodtype, err = nameOfFuncRuntimeType(mds, typ, tflag, false) if err != nil { return "", err } @@ -943,7 +953,7 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err return buf.String(), nil } -func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error) { +func nameOfStructRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (string, error) { var buf bytes.Buffer buf.WriteString("struct {") @@ -983,7 +993,7 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error) case "typ": typeField = field.Children[i].maybeDereference() var err error - fieldtypename, _, err = nameOfRuntimeType(typeField) + fieldtypename, _, err = nameOfRuntimeType(mds, typeField) if err != nil { return "", err } @@ -1017,13 +1027,13 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error) return buf.String(), nil } -func fieldToType(_type *Variable, fieldName string) (string, error) { +func fieldToType(mds []moduleData, _type *Variable, fieldName string) (string, error) { typeField, err := _type.structMember(fieldName) if err != nil { return "", err } typeField = typeField.maybeDereference() - typename, _, err := nameOfRuntimeType(typeField) + typename, _, err := nameOfRuntimeType(mds, typeField) return typename, err } @@ -1262,7 +1272,8 @@ func constructTypeForKind(kind int64, bi *BinaryInfo) (*godwarf.StructType, erro } func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type) (typeAddr uint64, typeKind uint64, found bool, err error) { - rdr := bi.DwarfReader() + so := bi.typeToImage(typ) + rdr := so.DwarfReader() rdr.Seek(typ.Common().Offset) e, err := rdr.Next() if err != nil { @@ -1273,13 +1284,21 @@ func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type) return 0, 0, false, nil } - if err := loadModuleData(bi, mem); err != nil { + mds, err := loadModuleData(bi, mem) + if err != nil { return 0, 0, false, err } - //TODO(aarzilli): when we support plugins this should be the plugin - //corresponding to the shared object containing entry 'e'. - typeAddr = uint64(bi.moduleData[0].types) + off + 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 = uint64(md.types) + off rtyp, err := bi.findType("runtime._type") if err != nil { diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 1b4d64917b..707c83601d 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -223,8 +223,8 @@ func (err *IsNilErr) Error() string { return fmt.Sprintf("%s is nil", err.name) } -func globalScope(bi *BinaryInfo, mem MemoryReadWriter) *EvalScope { - return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: bi.staticBase}, Mem: mem, Gvar: nil, BinInfo: bi, frameOffset: 0} +func globalScope(bi *BinaryInfo, image *Image, mem MemoryReadWriter) *EvalScope { + return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: image.StaticBase}, Mem: mem, Gvar: nil, BinInfo: bi, frameOffset: 0} } func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType godwarf.Type, mem MemoryReadWriter) *Variable { @@ -425,10 +425,15 @@ func (v *Variable) toField(field *godwarf.StructField) (*Variable, error) { return v.newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.mem), nil } +// image returns the image containing the current function. +func (scope *EvalScope) image() *Image { + return scope.BinInfo.funcToImage(scope.Fn) +} + // DwarfReader returns the DwarfReader containing the // Dwarf information for the target process. func (scope *EvalScope) DwarfReader() *reader.Reader { - return scope.BinInfo.DwarfReader() + return scope.image().DwarfReader() } // PtrSize returns the size of a pointer. @@ -721,30 +726,35 @@ func filterVariables(vars []*Variable, pred func(v *Variable) bool) []*Variable // PackageVariables returns the name, value, and type of all package variables in the application. func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { var vars []*Variable - reader := scope.DwarfReader() - - var utypoff dwarf.Offset - utypentry, err := reader.SeekToTypeNamed("") - if err == nil { - utypoff = utypentry.Offset - } - - for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() { - if err != nil { - return nil, err + for _, image := range scope.BinInfo.Images { + if image.loadErr != nil { + continue } + reader := reader.New(image.dwarf) - if typoff, ok := entry.Val(dwarf.AttrType).(dwarf.Offset); !ok || typoff == utypoff { - continue + var utypoff dwarf.Offset + utypentry, err := reader.SeekToTypeNamed("") + if err == nil { + utypoff = utypentry.Offset } - // Ignore errors trying to extract values - val, err := scope.extractVarInfoFromEntry(entry) - if err != nil { - continue + for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() { + if err != nil { + return nil, err + } + + if typoff, ok := entry.Val(dwarf.AttrType).(dwarf.Offset); !ok || typoff == utypoff { + continue + } + + // Ignore errors trying to extract values + val, err := scope.extractVarInfoFromEntry(entry, image) + if err != nil { + continue + } + val.loadValue(cfg) + vars = append(vars, val) } - val.loadValue(cfg) - vars = append(vars, val) } return vars, nil @@ -753,13 +763,13 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { func (scope *EvalScope) findGlobal(name string) (*Variable, error) { for _, pkgvar := range scope.BinInfo.packageVars { if pkgvar.name == name || strings.HasSuffix(pkgvar.name, "/"+name) { - reader := scope.DwarfReader() + reader := pkgvar.cu.image.dwarfReader reader.Seek(pkgvar.offset) entry, err := reader.Next() if err != nil { return nil, err } - return scope.extractVarInfoFromEntry(entry) + return scope.extractVarInfoFromEntry(entry, pkgvar.cu.image) } } for _, fn := range scope.BinInfo.Functions { @@ -772,10 +782,10 @@ func (scope *EvalScope) findGlobal(name string) (*Variable, error) { return r, nil } } - for offset, ctyp := range scope.BinInfo.consts { + for dwref, ctyp := range scope.BinInfo.consts { for _, cval := range ctyp.values { if cval.fullName == name || strings.HasSuffix(cval.fullName, "/"+name) { - t, err := scope.BinInfo.Type(offset) + t, err := scope.BinInfo.Images[dwref.imageIndex].Type(dwref.offset) if err != nil { return nil, err } @@ -867,8 +877,8 @@ func (v *Variable) structMember(memberName string) (*Variable, error) { } } -func readVarEntry(varEntry *dwarf.Entry, bi *BinaryInfo) (entry reader.Entry, name string, typ godwarf.Type, err error) { - entry, _ = reader.LoadAbstractOrigin(varEntry, bi.dwarfReader) +func readVarEntry(varEntry *dwarf.Entry, image *Image) (entry reader.Entry, name string, typ godwarf.Type, err error) { + entry, _ = reader.LoadAbstractOrigin(varEntry, image.dwarfReader) name, ok := entry.Val(dwarf.AttrName).(string) if !ok { @@ -880,7 +890,7 @@ func readVarEntry(varEntry *dwarf.Entry, bi *BinaryInfo) (entry reader.Entry, na return nil, "", nil, fmt.Errorf("malformed variable DIE (offset)") } - typ, err = bi.Type(offset) + typ, err = image.Type(offset) if err != nil { return nil, "", nil, err } @@ -890,7 +900,7 @@ func readVarEntry(varEntry *dwarf.Entry, bi *BinaryInfo) (entry reader.Entry, na // 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 (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variable, error) { +func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry, image *Image) (*Variable, error) { if varEntry == nil { return nil, fmt.Errorf("invalid entry") } @@ -899,7 +909,7 @@ func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variabl return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", varEntry.Tag.String()) } - entry, n, t, err := readVarEntry(varEntry, scope.BinInfo) + entry, n, t, err := readVarEntry(varEntry, image) if err != nil { return nil, err } @@ -2024,7 +2034,7 @@ func popcnt(x uint64) int { } func (cm constantsMap) Get(typ godwarf.Type) *constantType { - ctyp := cm[typ.Common().Offset] + ctyp := cm[dwarfRef{typ.Common().Index, typ.Common().Offset}] if ctyp == nil { return nil } @@ -2096,11 +2106,11 @@ func (scope *EvalScope) Locals() ([]*Variable, error) { var vars []*Variable var depths []int - varReader := reader.Variables(scope.BinInfo.dwarf, scope.Fn.offset, reader.ToRelAddr(scope.PC, scope.BinInfo.staticBase), scope.Line, true) + varReader := reader.Variables(scope.image().dwarf, scope.Fn.offset, reader.ToRelAddr(scope.PC, scope.image().StaticBase), scope.Line, true) hasScopes := false for varReader.Next() { entry := varReader.Entry() - val, err := scope.extractVarInfoFromEntry(entry) + val, err := scope.extractVarInfoFromEntry(entry, scope.image()) if err != nil { // skip variables that we can't parse yet continue diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index b76a0c779b..81cda7a1b7 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -1586,7 +1586,7 @@ func libraries(t *Term, ctx callContext, args string) error { } d := digits(len(libs)) for i := range libs { - fmt.Printf("%"+strconv.Itoa(d)+"d. %s\n", i, libs[i].Path) + fmt.Printf("%"+strconv.Itoa(d)+"d. %#x %s\n", i, libs[i].Address, libs[i].Path) } return nil } diff --git a/service/api/conversions.go b/service/api/conversions.go index 986226bc5c..09f0aae803 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -316,5 +316,5 @@ func ConvertCheckpoint(in proc.Checkpoint) (out Checkpoint) { } func ConvertImage(image *proc.Image) Image { - return Image{Path: image.Path} + return Image{Path: image.Path, Address: image.StaticBase} } diff --git a/service/api/types.go b/service/api/types.go index aa5f8f3398..66e53e0518 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -445,5 +445,6 @@ type Checkpoint struct { // Image represents a loaded shared object (go plugin or shared library) type Image struct { - Path string + Path string + Address uint64 } diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index bed94804b0..9c649f444b 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -1123,9 +1123,10 @@ func (d *Debugger) ListDynamicLibraries() []api.Image { d.processMutex.Lock() defer d.processMutex.Unlock() bi := d.target.BinInfo() - r := make([]api.Image, len(bi.Images)) - for i := range bi.Images { - r[i] = api.ConvertImage(bi.Images[i]) + r := make([]api.Image, 0, len(bi.Images)-1) + // skips the first image because it's the executable file + for i := range bi.Images[1:] { + r = append(r, api.ConvertImage(bi.Images[i])) } return r } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 8e9e8837d3..0acdb6f209 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -71,20 +71,19 @@ func findFirstNonRuntimeFrame(p proc.Process) (proc.Stackframe, error) { return proc.Stackframe{}, fmt.Errorf("non-runtime frame not found") } -func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { - var scope *proc.EvalScope - var err error - - if testBackend == "rr" { - var frame proc.Stackframe - frame, err = findFirstNonRuntimeFrame(p) - if err == nil { - scope = proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame) - } - } else { - scope, err = proc.GoroutineScope(p.CurrentThread()) +func evalScope(p proc.Process) (*proc.EvalScope, error) { + if testBackend != "rr" { + return proc.GoroutineScope(p.CurrentThread()) } + frame, err := findFirstNonRuntimeFrame(p) + if err != nil { + return nil, err + } + return proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame), nil +} +func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { + scope, err := evalScope(p) if err != nil { return nil, err } @@ -107,9 +106,12 @@ func setVariable(p proc.Process, symbol, value string) error { } func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture protest.Fixture)) { - var buildFlags protest.BuildFlags + withTestProcessArgs(name, t, ".", []string{}, 0, fn) +} + +func withTestProcessArgs(name string, t *testing.T, wd string, args []string, buildFlags protest.BuildFlags, fn func(p proc.Process, fixture protest.Fixture)) { if buildMode == "pie" { - buildFlags = protest.BuildModePIE + buildFlags |= protest.BuildModePIE } fixture := protest.BuildFixture(name, buildFlags) var p proc.Process @@ -117,13 +119,13 @@ func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture var tracedir string switch testBackend { case "native": - p, err = native.Launch([]string{fixture.Path}, ".", false, []string{}) + p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}) case "lldb": - p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".", false, []string{}) + p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}) case "rr": protest.MustHaveRecordingAllowed(t) t.Log("recording") - p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}) + p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}) t.Logf("replaying %q", tracedir) default: t.Fatalf("unknown backend %q", testBackend) @@ -1197,3 +1199,99 @@ func TestCallFunction(t *testing.T) { } }) } + +func setFileLineBreakpoint(p proc.Process, t *testing.T, path string, lineno int) *proc.Breakpoint { + addr, err := proc.FindFileLocation(p, path, lineno) + if err != nil { + t.Fatalf("FindFileLocation: %v", err) + } + bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil) + if err != nil { + t.Fatalf("SetBreakpoint: %v", err) + } + return bp +} + +func currentLocation(p proc.Process, t *testing.T) (pc uint64, f string, ln int, fn *proc.Function) { + regs, err := p.CurrentThread().Registers(false) + if err != nil { + t.Fatalf("Registers error: %v", err) + } + f, l, fn := p.BinInfo().PCToLine(regs.PC()) + t.Logf("at %#x %s:%d %v", regs.PC(), f, l, fn) + return regs.PC(), f, l, fn +} + +func assertCurrentLocationFunction(p proc.Process, t *testing.T, fnname string) { + _, _, _, fn := currentLocation(p, t) + if fn == nil { + t.Fatalf("Not in a function") + } + if fn.Name != fnname { + t.Fatalf("Wrong function %s %s", fn.Name, fnname) + } +} + +func TestPluginVariables(t *testing.T) { + pluginFixtures := protest.WithPlugins(t, "plugin1/", "plugin2/") + + withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, 0, func(p proc.Process, fixture protest.Fixture) { + setFileLineBreakpoint(p, t, fixture.Source, 41) + assertNoError(proc.Continue(p), t, "Continue 1") + + bp, err := setFunctionBreakpoint(p, "github.com/derekparker/delve/_fixtures/plugin2.TypesTest") + assertNoError(err, t, "SetBreakpoint(TypesTest)") + t.Logf("bp.Addr = %#x", bp.Addr) + _, err = setFunctionBreakpoint(p, "github.com/derekparker/delve/_fixtures/plugin2.aIsNotNil") + assertNoError(err, t, "SetBreakpoint(aIsNotNil)") + + for _, image := range p.BinInfo().Images { + t.Logf("%#x %s\n", image.StaticBase, image.Path) + } + + assertNoError(proc.Continue(p), t, "Continue 2") + + // test that PackageVariables returns variables from the executable and plugins + scope, err := evalScope(p) + assertNoError(err, t, "evalScope") + allvars, err := scope.PackageVariables(pnormalLoadConfig) + assertNoError(err, t, "PackageVariables") + var plugin2AFound, mainExeGlobalFound bool + for _, v := range allvars { + switch v.Name { + case "github.com/derekparker/delve/_fixtures/plugin2.A": + plugin2AFound = true + case "main.ExeGlobal": + mainExeGlobalFound = true + } + } + if !plugin2AFound { + t.Fatalf("variable plugin2.A not found in the output of PackageVariables") + } + if !mainExeGlobalFound { + t.Fatalf("variable main.ExeGlobal not found in the output of PackageVariables") + } + + // read interface variable, inside plugin code, with a concrete type defined in the executable + vs, err := evalVariable(p, "s", pnormalLoadConfig) + assertNoError(err, t, "Eval(s)") + assertVariable(t, vs, varTest{"s", true, `github.com/derekparker/delve/_fixtures/internal/pluginsupport.Something(*main.asomething) *{n: 2}`, ``, `github.com/derekparker/delve/_fixtures/internal/pluginsupport.Something`, nil}) + + // test that the concrete type -> interface{} conversion works across plugins (mostly tests proc.dwarfToRuntimeType) + assertNoError(setVariable(p, "plugin2.A", "main.ExeGlobal"), t, "setVariable(plugin2.A = main.ExeGlobal)") + assertNoError(proc.Continue(p), t, "Continue 3") + assertCurrentLocationFunction(p, t, "github.com/derekparker/delve/_fixtures/plugin2.aIsNotNil") + vstr, err := evalVariable(p, "str", pnormalLoadConfig) + assertNoError(err, t, "Eval(str)") + assertVariable(t, vstr, varTest{"str", true, `"success"`, ``, `string`, nil}) + + assertNoError(proc.StepOut(p), t, "StepOut") + assertNoError(proc.StepOut(p), t, "StepOut") + assertNoError(proc.Next(p), t, "Next") + + // read interface variable, inside executable code, with a concrete type defined in a plugin + vb, err := evalVariable(p, "b", pnormalLoadConfig) + assertNoError(err, t, "Eval(b)") + assertVariable(t, vb, varTest{"b", true, `github.com/derekparker/delve/_fixtures/internal/pluginsupport.SomethingElse(*github.com/derekparker/delve/_fixtures/plugin2.asomethingelse) *{x: 1, y: 4}`, ``, `github.com/derekparker/delve/_fixtures/internal/pluginsupport.SomethingElse`, nil}) + }) +}