diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index 4031fd83..84ae4660 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -1,7 +1,5 @@ package proc -import "runtime" - // Arch defines an interface for representing a // CPU architecture. type Arch interface { @@ -19,11 +17,12 @@ type AMD64 struct { breakInstructionLen int gStructOffset uint64 hardwareBreakpointUsage []bool + goos string } // AMD64Arch returns an initialized AMD64 // struct. -func AMD64Arch() *AMD64 { +func AMD64Arch(goos string) *AMD64 { var breakInstr = []byte{0xCC} return &AMD64{ @@ -31,6 +30,7 @@ func AMD64Arch() *AMD64 { breakInstruction: breakInstr, breakInstructionLen: len(breakInstr), hardwareBreakpointUsage: make([]bool, 4), + goos: goos, } } @@ -38,7 +38,7 @@ func AMD64Arch() *AMD64 { // arch struct. The offset is dependent on the Go compiler Version // and whether or not the target program was externally linked. func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) { - switch runtime.GOOS { + switch a.goos { case "darwin": a.gStructOffset = 0x8a0 case "linux": diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go new file mode 100644 index 00000000..7a808092 --- /dev/null +++ b/pkg/proc/bininfo.go @@ -0,0 +1,513 @@ +package proc + +import ( + "debug/gosym" + "debug/pe" + "errors" + "fmt" + "io" + "os" + "sync" + "time" + + "github.com/derekparker/delve/pkg/dwarf/frame" + "github.com/derekparker/delve/pkg/dwarf/line" + "github.com/derekparker/delve/pkg/dwarf/reader" + + "golang.org/x/debug/dwarf" + "golang.org/x/debug/elf" + "golang.org/x/debug/macho" +) + +type BinaryInfo struct { + lastModified time.Time // Time the executable of this process was last modified + + goos string + closer io.Closer + + // Maps package names to package paths, needed to lookup types inside DWARF info + packageMap map[string]string + + arch Arch + dwarf *dwarf.Data + frameEntries frame.FrameDescriptionEntries + lineInfo line.DebugLines + goSymTable *gosym.Table + types map[string]dwarf.Offset + functions []functionDebugInfo + + loadModuleDataOnce sync.Once + moduleData []moduleData + nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry +} + +var UnsupportedLinuxArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") +var UnsupportedWindowsArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") +var UnsupportedDarwinArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported") + +func NewBinaryInfo(goos, goarch string) BinaryInfo { + r := BinaryInfo{goos: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry)} + + // TODO: find better way to determine proc arch (perhaps use executable file info) + switch goarch { + case "amd64": + r.arch = AMD64Arch(goos) + } + + return r +} + +func (bininfo *BinaryInfo) LoadBinaryInfo(path string, wg *sync.WaitGroup) error { + fi, err := os.Stat(path) + if err == nil { + bininfo.lastModified = fi.ModTime() + } + + switch bininfo.goos { + case "linux": + return bininfo.LoadBinaryInfoElf(path, wg) + case "windows": + return bininfo.LoadBinaryInfoPE(path, wg) + case "darwin": + return bininfo.LoadBinaryInfoMacho(path, wg) + } + return errors.New("unsupported operating system") +} + +func (bi *BinaryInfo) LastModified() time.Time { + return bi.lastModified +} + +// DwarfReader returns a reader for the dwarf data +func (bi *BinaryInfo) DwarfReader() *reader.Reader { + return reader.New(bi.dwarf) +} + +// Sources returns list of source files that comprise the debugged binary. +func (bi *BinaryInfo) Sources() map[string]*gosym.Obj { + return bi.goSymTable.Files +} + +// Funcs returns list of functions present in the debugged program. +func (bi *BinaryInfo) Funcs() []gosym.Func { + return bi.goSymTable.Funcs +} + +// Types returns list of types present in the debugged program. +func (bi *BinaryInfo) Types() ([]string, error) { + types := make([]string, 0, len(bi.types)) + for k := range bi.types { + types = append(types, k) + } + return types, nil +} + +// PCToLine converts an instruction address to a file/line/function. +func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *gosym.Func) { + return bi.goSymTable.PCToLine(pc) +} + +func (bi *BinaryInfo) Close() error { + return bi.closer.Close() +} + +// ELF /////////////////////////////////////////////////////////////// + +func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { + exe, err := os.OpenFile(path, 0, os.ModePerm) + if err != nil { + return err + } + bi.closer = exe + elfFile, err := elf.NewFile(exe) + if err != nil { + return err + } + if elfFile.Machine != elf.EM_X86_64 { + return UnsupportedLinuxArchErr + } + bi.dwarf, err = elfFile.DWARF() + if err != nil { + return err + } + + wg.Add(4) + go bi.parseDebugFrameElf(elfFile, wg) + go bi.obtainGoSymbolsElf(elfFile, wg) + go bi.parseDebugLineInfoElf(elfFile, wg) + go bi.loadDebugInfoMaps(wg) + return nil +} + +func (bi *BinaryInfo) parseDebugFrameElf(exe *elf.File, wg *sync.WaitGroup) { + defer wg.Done() + + debugFrameSec := exe.Section(".debug_frame") + debugInfoSec := exe.Section(".debug_info") + + if debugFrameSec != nil && debugInfoSec != nil { + debugFrame, err := exe.Section(".debug_frame").Data() + if err != nil { + fmt.Println("could not get .debug_frame section", err) + os.Exit(1) + } + dat, err := debugInfoSec.Data() + if err != nil { + fmt.Println("could not get .debug_info section", err) + os.Exit(1) + } + bi.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) + } else { + fmt.Println("could not find .debug_frame section in binary") + os.Exit(1) + } +} + +func (bi *BinaryInfo) obtainGoSymbolsElf(exe *elf.File, wg *sync.WaitGroup) { + defer wg.Done() + + var ( + symdat []byte + pclndat []byte + err error + ) + + if sec := exe.Section(".gosymtab"); sec != nil { + symdat, err = sec.Data() + if err != nil { + fmt.Println("could not get .gosymtab section", err) + os.Exit(1) + } + } + + if sec := exe.Section(".gopclntab"); sec != nil { + pclndat, err = sec.Data() + if err != nil { + fmt.Println("could not get .gopclntab section", err) + os.Exit(1) + } + } + + pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr) + tab, err := gosym.NewTable(symdat, pcln) + if err != nil { + fmt.Println("could not get initialize line table", err) + os.Exit(1) + } + + bi.goSymTable = tab +} + +func (bi *BinaryInfo) parseDebugLineInfoElf(exe *elf.File, wg *sync.WaitGroup) { + defer wg.Done() + + if sec := exe.Section(".debug_line"); sec != nil { + debugLine, err := exe.Section(".debug_line").Data() + if err != nil { + fmt.Println("could not get .debug_line section", err) + os.Exit(1) + } + bi.lineInfo = line.Parse(debugLine) + } else { + fmt.Println("could not find .debug_line section in binary") + os.Exit(1) + } +} + +// PE //////////////////////////////////////////////////////////////// + +func (bi *BinaryInfo) LoadBinaryInfoPE(path string, wg *sync.WaitGroup) error { + peFile, closer, err := openExecutablePathPE(path) + if err != nil { + return err + } + bi.closer = closer + if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { + return UnsupportedWindowsArchErr + } + bi.dwarf, err = dwarfFromPE(peFile) + if err != nil { + return err + } + + wg.Add(4) + go bi.parseDebugFramePE(peFile, wg) + go bi.obtainGoSymbolsPE(peFile, wg) + go bi.parseDebugLineInfoPE(peFile, wg) + go bi.loadDebugInfoMaps(wg) + return nil +} + +func openExecutablePathPE(path string) (*pe.File, io.Closer, error) { + f, err := os.OpenFile(path, 0, os.ModePerm) + if err != nil { + return nil, nil, err + } + peFile, err := pe.NewFile(f) + if err != nil { + f.Close() + return nil, nil, err + } + return peFile, f, nil +} + +// Adapted from src/debug/pe/file.go: pe.(*File).DWARF() +func dwarfFromPE(f *pe.File) (*dwarf.Data, error) { + // There are many other DWARF sections, but these + // are the ones the debug/dwarf package uses. + // Don't bother loading others. + var names = [...]string{"abbrev", "info", "line", "str"} + var dat [len(names)][]byte + for i, name := range names { + name = ".debug_" + name + s := f.Section(name) + if s == nil { + continue + } + b, err := s.Data() + if err != nil && uint32(len(b)) < s.Size { + return nil, err + } + if 0 < s.VirtualSize && s.VirtualSize < s.Size { + b = b[:s.VirtualSize] + } + dat[i] = b + } + + abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3] + return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str) +} + +func (bi *BinaryInfo) parseDebugFramePE(exe *pe.File, wg *sync.WaitGroup) { + defer wg.Done() + + debugFrameSec := exe.Section(".debug_frame") + debugInfoSec := exe.Section(".debug_info") + + if debugFrameSec != nil && debugInfoSec != nil { + debugFrame, err := debugFrameSec.Data() + if err != nil && uint32(len(debugFrame)) < debugFrameSec.Size { + fmt.Println("could not get .debug_frame section", err) + os.Exit(1) + } + if 0 < debugFrameSec.VirtualSize && debugFrameSec.VirtualSize < debugFrameSec.Size { + debugFrame = debugFrame[:debugFrameSec.VirtualSize] + } + dat, err := debugInfoSec.Data() + if err != nil { + fmt.Println("could not get .debug_info section", err) + os.Exit(1) + } + bi.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) + } else { + fmt.Println("could not find .debug_frame section in binary") + os.Exit(1) + } +} + +func (dbp *BinaryInfo) obtainGoSymbolsPE(exe *pe.File, wg *sync.WaitGroup) { + defer wg.Done() + + _, symdat, pclndat, err := pclnPE(exe) + if err != nil { + fmt.Println("could not get Go symbols", err) + os.Exit(1) + } + + pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset)) + tab, err := gosym.NewTable(symdat, pcln) + if err != nil { + fmt.Println("could not get initialize line table", err) + os.Exit(1) + } + + dbp.goSymTable = tab +} + +// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go +func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { + for _, s := range f.Symbols { + if s.Name != name { + continue + } + if s.SectionNumber <= 0 { + return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber) + } + if len(f.Sections) < int(s.SectionNumber) { + return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections)) + } + return s, nil + } + return nil, fmt.Errorf("no %s symbol found", name) +} + +// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go +func loadPETable(f *pe.File, sname, ename string) ([]byte, error) { + ssym, err := findPESymbol(f, sname) + if err != nil { + return nil, err + } + esym, err := findPESymbol(f, ename) + if err != nil { + return nil, err + } + if ssym.SectionNumber != esym.SectionNumber { + return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename) + } + sect := f.Sections[ssym.SectionNumber-1] + data, err := sect.Data() + if err != nil { + return nil, err + } + return data[ssym.Value:esym.Value], nil +} + +// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go +func pclnPE(exe *pe.File) (textStart uint64, symtab, pclntab []byte, err error) { + var imageBase uint64 + switch oh := exe.OptionalHeader.(type) { + case *pe.OptionalHeader32: + imageBase = uint64(oh.ImageBase) + case *pe.OptionalHeader64: + imageBase = oh.ImageBase + default: + return 0, nil, nil, fmt.Errorf("pe file format not recognized") + } + if sect := exe.Section(".text"); sect != nil { + textStart = imageBase + uint64(sect.VirtualAddress) + } + if pclntab, err = loadPETable(exe, "runtime.pclntab", "runtime.epclntab"); err != nil { + // We didn't find the symbols, so look for the names used in 1.3 and earlier. + // TODO: Remove code looking for the old symbols when we no longer care about 1.3. + var err2 error + if pclntab, err2 = loadPETable(exe, "pclntab", "epclntab"); err2 != nil { + return 0, nil, nil, err + } + } + if symtab, err = loadPETable(exe, "runtime.symtab", "runtime.esymtab"); err != nil { + // Same as above. + var err2 error + if symtab, err2 = loadPETable(exe, "symtab", "esymtab"); err2 != nil { + return 0, nil, nil, err + } + } + return textStart, symtab, pclntab, nil +} + +func (bi *BinaryInfo) parseDebugLineInfoPE(exe *pe.File, wg *sync.WaitGroup) { + defer wg.Done() + + if sec := exe.Section(".debug_line"); sec != nil { + debugLine, err := sec.Data() + if err != nil && uint32(len(debugLine)) < sec.Size { + fmt.Println("could not get .debug_line section", err) + os.Exit(1) + } + if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size { + debugLine = debugLine[:sec.VirtualSize] + } + bi.lineInfo = line.Parse(debugLine) + } else { + fmt.Println("could not find .debug_line section in binary") + os.Exit(1) + } +} + +// MACH-O //////////////////////////////////////////////////////////// + +func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, wg *sync.WaitGroup) error { + exe, err := macho.Open(path) + if err != nil { + return err + } + bi.closer = exe + if exe.Cpu != macho.CpuAmd64 { + return UnsupportedDarwinArchErr + } + bi.dwarf, err = exe.DWARF() + if err != nil { + return err + } + + wg.Add(4) + go bi.parseDebugFrameMacho(exe, wg) + go bi.obtainGoSymbolsMacho(exe, wg) + go bi.parseDebugLineInfoMacho(exe, wg) + go bi.loadDebugInfoMaps(wg) + return nil +} + +func (bi *BinaryInfo) parseDebugFrameMacho(exe *macho.File, wg *sync.WaitGroup) { + defer wg.Done() + + debugFrameSec := exe.Section("__debug_frame") + debugInfoSec := exe.Section("__debug_info") + + if debugFrameSec != nil && debugInfoSec != nil { + debugFrame, err := exe.Section("__debug_frame").Data() + if err != nil { + fmt.Println("could not get __debug_frame section", err) + os.Exit(1) + } + dat, err := debugInfoSec.Data() + if err != nil { + fmt.Println("could not get .debug_info section", err) + os.Exit(1) + } + bi.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) + } else { + fmt.Println("could not find __debug_frame section in binary") + os.Exit(1) + } +} + +func (bi *BinaryInfo) obtainGoSymbolsMacho(exe *macho.File, wg *sync.WaitGroup) { + defer wg.Done() + + var ( + symdat []byte + pclndat []byte + err error + ) + + if sec := exe.Section("__gosymtab"); sec != nil { + symdat, err = sec.Data() + if err != nil { + fmt.Println("could not get .gosymtab section", err) + os.Exit(1) + } + } + + if sec := exe.Section("__gopclntab"); sec != nil { + pclndat, err = sec.Data() + if err != nil { + fmt.Println("could not get .gopclntab section", err) + os.Exit(1) + } + } + + pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr) + tab, err := gosym.NewTable(symdat, pcln) + if err != nil { + fmt.Println("could not get initialize line table", err) + os.Exit(1) + } + + bi.goSymTable = tab +} + +func (bi *BinaryInfo) parseDebugLineInfoMacho(exe *macho.File, wg *sync.WaitGroup) { + defer wg.Done() + + if sec := exe.Section("__debug_line"); sec != nil { + debugLine, err := exe.Section("__debug_line").Data() + if err != nil { + fmt.Println("could not get __debug_line section", err) + os.Exit(1) + } + bi.lineInfo = line.Parse(debugLine) + } else { + fmt.Println("could not find __debug_line section in binary") + os.Exit(1) + } +} diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index 8f13d479..6d89aa92 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -104,7 +104,7 @@ func (iae InvalidAddressError) Error() string { } func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error { - _, err := thread.writeMemory(uintptr(addr), dbp.arch.BreakpointInstruction()) + _, err := thread.writeMemory(uintptr(addr), dbp.bi.arch.BreakpointInstruction()) return err } diff --git a/pkg/proc/disasm.go b/pkg/proc/disasm.go index 731336e2..8eae53bd 100644 --- a/pkg/proc/disasm.go +++ b/pkg/proc/disasm.go @@ -47,7 +47,7 @@ func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool) mem[i] = bp.OriginalData[i] } } - file, line, fn := thread.dbp.PCToLine(pc) + file, line, fn := thread.dbp.bi.PCToLine(pc) loc := Location{PC: pc, File: file, Line: line, Fn: fn} inst, err := asmDecode(mem, pc) if err == nil { diff --git a/pkg/proc/disasm_amd64.go b/pkg/proc/disasm_amd64.go index ebe085be..a7619b1f 100644 --- a/pkg/proc/disasm_amd64.go +++ b/pkg/proc/disasm_amd64.go @@ -109,7 +109,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs return nil } - file, line, fn := thread.dbp.PCToLine(pc) + file, line, fn := thread.dbp.bi.PCToLine(pc) if fn == nil { return nil } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 0b50cb87..33d48624 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -141,7 +141,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { fnnode = p.X } - styp, err := scope.Thread.dbp.findTypeExpr(fnnode) + styp, err := scope.Thread.dbp.bi.findTypeExpr(fnnode) if err != nil { return nil, err } @@ -454,7 +454,7 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { return v, nil } // if it's not a local variable then it could be a package variable w/o explicit package name - _, _, fn := scope.Thread.dbp.PCToLine(scope.PC) + _, _, fn := scope.Thread.dbp.bi.PCToLine(scope.PC) if fn != nil { if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil { v.Name = node.Name @@ -492,7 +492,7 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err if xv.Children[0].Addr == 0 { return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type)) } - typ, err := scope.Thread.dbp.findTypeExpr(node.Type) + typ, err := scope.Thread.dbp.bi.findTypeExpr(node.Type) if err != nil { return nil, err } @@ -637,7 +637,7 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) { xev.OnlyAddr = true typename := "*" + xev.DwarfType.Common().Name - rv := scope.newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, Type: xev.DwarfType}) + rv := scope.newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.bi.arch.PtrSize()), Name: typename}, Type: xev.DwarfType}) rv.Children = []Variable{*xev} rv.loaded = true diff --git a/pkg/proc/moduledata.go b/pkg/proc/moduledata.go index ae9d8402..2d92994b 100644 --- a/pkg/proc/moduledata.go +++ b/pkg/proc/moduledata.go @@ -11,9 +11,9 @@ type moduleData struct { typemapVar *Variable } -func (dbp *Process) loadModuleData() (err error) { - dbp.loadModuleDataOnce.Do(func() { - scope := &EvalScope{Thread: dbp.currentThread, PC: 0, CFA: 0} +func (bi *BinaryInfo) loadModuleData(thread *Thread) (err error) { + bi.loadModuleDataOnce.Do(func() { + scope, _ := thread.Scope() var md *Variable md, err = scope.packageVarAddr("runtime.firstmoduledata") if err != nil { @@ -43,7 +43,7 @@ func (dbp *Process) loadModuleData() (err error) { return } - dbp.moduleData = append(dbp.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar}) + bi.moduleData = append(bi.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar}) md = nextVar.maybeDereference() if md.Unreadable != nil { @@ -56,26 +56,27 @@ func (dbp *Process) loadModuleData() (err error) { return } -func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, error) { +func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thread) (*Variable, error) { + var mem memoryReadWriter = thread // See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go - if err := dbp.loadModuleData(); err != nil { + if err := bi.loadModuleData(thread); err != nil { return nil, err } var md *moduleData - for i := range dbp.moduleData { - if typeAddr >= dbp.moduleData[i].types && typeAddr < dbp.moduleData[i].etypes { - md = &dbp.moduleData[i] + for i := range bi.moduleData { + if typeAddr >= bi.moduleData[i].types && typeAddr < bi.moduleData[i].etypes { + md = &bi.moduleData[i] } } - rtyp, err := dbp.findType("runtime._type") + rtyp, err := bi.findType("runtime._type") if err != nil { return nil, err } if md == nil { - v, err := dbp.reflectOffsMapAccess(off) + v, err := bi.reflectOffsMapAccess(off, thread) if err != nil { return nil, err } @@ -84,28 +85,29 @@ func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, er return v.newVariable(v.Name, uintptr(addr), rtyp), nil } - if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.currentThread)); t != nil { + if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem)); t != nil { return t, nil } res := md.types + uintptr(off) - return dbp.currentThread.newVariable("", res, rtyp), nil + return newVariable("", res, rtyp, thread.dbp, thread), nil } -func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag string, pkgpathoff int32, err error) { +func (bi *BinaryInfo) resolveNameOff(typeAddr uintptr, off uintptr, thread *Thread) (name, tag string, pkgpathoff int32, err error) { + var mem memoryReadWriter = thread // See runtime.resolveNameOff in $GOROOT/src/runtime/type.go - if err = dbp.loadModuleData(); err != nil { + if err = bi.loadModuleData(thread); err != nil { return "", "", 0, err } - for _, md := range dbp.moduleData { + for _, md := range bi.moduleData { if typeAddr >= md.types && typeAddr < md.etypes { - return dbp.loadName(md.types + off) + return bi.loadName(md.types+off, mem) } } - v, err := dbp.reflectOffsMapAccess(off) + v, err := bi.reflectOffsMapAccess(off, thread) if err != nil { return "", "", 0, err } @@ -115,11 +117,11 @@ func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag str return "", "", 0, resv.Unreadable } - return dbp.loadName(resv.Addr) + return bi.loadName(resv.Addr, mem) } -func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) { - scope := &EvalScope{Thread: dbp.currentThread, PC: 0, CFA: 0} +func (bi *BinaryInfo) reflectOffsMapAccess(off uintptr, thread *Thread) (*Variable, error) { + scope, _ := thread.Scope() reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") if err != nil { return nil, err @@ -130,7 +132,7 @@ func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) { return nil, err } - return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.currentThread)) + return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), thread)) } const ( @@ -140,9 +142,9 @@ const ( nameflagHasPkg = 1 << 2 ) -func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, err error) { +func (bi *BinaryInfo) loadName(addr uintptr, mem memoryReadWriter) (name, tag string, pkgpathoff int32, err error) { off := addr - namedata, err := dbp.currentThread.readMemory(off, 3) + namedata, err := mem.readMemory(off, 3) off += 3 if err != nil { return "", "", 0, err @@ -150,7 +152,7 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, namelen := uint16(namedata[1]<<8) | uint16(namedata[2]) - rawstr, err := dbp.currentThread.readMemory(off, int(namelen)) + rawstr, err := mem.readMemory(off, int(namelen)) off += uintptr(namelen) if err != nil { return "", "", 0, err @@ -159,14 +161,14 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, name = string(rawstr) if namedata[0]&nameflagHasTag != 0 { - taglendata, err := dbp.currentThread.readMemory(off, 2) + taglendata, err := mem.readMemory(off, 2) off += 2 if err != nil { return "", "", 0, err } taglen := uint16(taglendata[0]<<8) | uint16(taglendata[1]) - rawstr, err := dbp.currentThread.readMemory(off, int(taglen)) + rawstr, err := mem.readMemory(off, int(taglen)) off += uintptr(taglen) if err != nil { return "", "", 0, err @@ -176,7 +178,7 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, } if namedata[0]&nameflagHasPkg != 0 { - pkgdata, err := dbp.currentThread.readMemory(off, 4) + pkgdata, err := mem.readMemory(off, 4) if err != nil { return "", "", 0, err } diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index c9199bcc..25092aef 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -1,7 +1,6 @@ package proc import ( - "debug/gosym" "encoding/binary" "errors" "fmt" @@ -14,20 +13,16 @@ import ( "strconv" "strings" "sync" - "time" - "github.com/derekparker/delve/pkg/dwarf/frame" - "github.com/derekparker/delve/pkg/dwarf/line" - "github.com/derekparker/delve/pkg/dwarf/reader" "golang.org/x/debug/dwarf" ) // Process represents all of the information the debugger // is holding onto regarding the process we are debugging. type Process struct { - pid int // Process Pid - Process *os.Process // Pointer to process struct for the actual process we are debugging - lastModified time.Time // Time the executable of this process was last modified + bi BinaryInfo + pid int // Process Pid + Process *os.Process // Pointer to process struct for the actual process we are debugging // Breakpoint table, holds information on breakpoints. // Maps instruction address to Breakpoint struct. @@ -43,16 +38,8 @@ type Process struct { // Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread selectedGoroutine *G - // Maps package names to package paths, needed to lookup types inside DWARF info - packageMap map[string]string - allGCache []*G - dwarf *dwarf.Data - goSymTable *gosym.Table - frameEntries frame.FrameDescriptionEntries - lineInfo line.DebugLines os *OSProcessDetails - arch Arch breakpointIDCounter int internalBreakpointIDCounter int firstStart bool @@ -60,12 +47,6 @@ type Process struct { exited bool ptraceChan chan func() ptraceDoneChan chan interface{} - types map[string]dwarf.Offset - functions []functionDebugInfo - - loadModuleDataOnce sync.Once - moduleData []moduleData - nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry } type functionDebugInfo struct { @@ -81,19 +62,14 @@ var NotExecutableErr = errors.New("not an executable file") // `handlePtraceFuncs`. func New(pid int) *Process { dbp := &Process{ - pid: pid, - threads: make(map[int]*Thread), - breakpoints: make(map[uint64]*Breakpoint), - firstStart: true, - os: new(OSProcessDetails), - ptraceChan: make(chan func()), - ptraceDoneChan: make(chan interface{}), - nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), - } - // TODO: find better way to determine proc arch (perhaps use executable file info) - switch runtime.GOARCH { - case "amd64": - dbp.arch = AMD64Arch() + pid: pid, + threads: make(map[int]*Thread), + breakpoints: make(map[uint64]*Breakpoint), + firstStart: true, + os: new(OSProcessDetails), + ptraceChan: make(chan func()), + ptraceDoneChan: make(chan interface{}), + bi: NewBinaryInfo(runtime.GOOS, runtime.GOARCH), } go dbp.handlePtraceFuncs() return dbp @@ -110,6 +86,10 @@ func (pe ProcessExitedError) Error() string { return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status) } +func (dbp *Process) BinInfo() *BinaryInfo { + return &dbp.bi +} + // Detach from the process being debugged, optionally killing it. func (dbp *Process) Detach(kill bool) (err error) { if dbp.exited { @@ -132,7 +112,7 @@ func (dbp *Process) Detach(kill bool) (err error) { } } dbp.execPtraceFunc(func() { - err = dbp.detach() + err = dbp.detach(kill) if err != nil { return } @@ -140,6 +120,7 @@ func (dbp *Process) Detach(kill bool) (err error) { err = killProcess(dbp.pid) } }) + dbp.bi.Close() return } @@ -160,10 +141,6 @@ func (dbp *Process) Running() bool { return false } -func (dbp *Process) LastModified() time.Time { - return dbp.lastModified -} - func (dbp *Process) Pid() int { return dbp.pid } @@ -192,21 +169,11 @@ func (dbp *Process) Breakpoints() map[uint64]*Breakpoint { func (dbp *Process) LoadInformation(path string) error { var wg sync.WaitGroup - exe, path, err := dbp.findExecutable(path) - if err != nil { - return err - } - fi, err := os.Stat(path) - if err == nil { - dbp.lastModified = fi.ModTime() - } + path = findExecutable(path, dbp.pid) - wg.Add(5) + wg.Add(1) go dbp.loadProcessInformation(&wg) - go dbp.parseDebugFrame(exe, &wg) - go dbp.obtainGoSymbols(exe, &wg) - go dbp.parseDebugLineInfo(exe, &wg) - go dbp.loadDebugInfoMaps(&wg) + dbp.bi.LoadBinaryInfo(path, &wg) wg.Wait() return nil @@ -215,7 +182,7 @@ func (dbp *Process) LoadInformation(path string) error { // FindFileLocation returns the PC for a given file:line. // Assumes that `file` is normailzed to lower case and '/' on Windows. func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) { - pc, fn, err := dbp.goSymTable.LineToPC(fileName, lineno) + pc, fn, err := dbp.bi.goSymTable.LineToPC(fileName, lineno) if err != nil { return 0, err } @@ -232,7 +199,7 @@ func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error // Note that setting breakpoints at that address will cause surprising behavior: // https://github.com/derekparker/delve/issues/170 func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) { - origfn := dbp.goSymTable.LookupFunc(funcName) + origfn := dbp.bi.goSymTable.LookupFunc(funcName) if origfn == nil { return 0, fmt.Errorf("Could not find function %s\n", funcName) } @@ -240,8 +207,8 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf if firstLine { return dbp.FirstPCAfterPrologue(origfn, false) } else if lineOffset > 0 { - filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry) - breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset) + filename, lineno, _ := dbp.bi.goSymTable.PCToLine(origfn.Entry) + breakAddr, _, err := dbp.bi.goSymTable.LineToPC(filename, lineno+lineOffset) return breakAddr, err } @@ -273,7 +240,7 @@ func (dbp *Process) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Exp return nil, BreakpointExistsError{bp.File, bp.Line, bp.Addr} } - f, l, fn := dbp.goSymTable.PCToLine(uint64(addr)) + f, l, fn := dbp.bi.goSymTable.PCToLine(uint64(addr)) if fn == nil { return nil, InvalidAddressError{address: addr} } @@ -297,7 +264,7 @@ func (dbp *Process) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Exp } thread := dbp.threads[tid] - originalData, err := thread.readMemory(uintptr(addr), dbp.arch.BreakpointSize()) + originalData, err := thread.readMemory(uintptr(addr), dbp.bi.arch.BreakpointSize()) if err != nil { return nil, err } @@ -572,7 +539,7 @@ func (dbp *Process) StepOut() error { if dbp.selectedGoroutine != nil { deferPCEntry := dbp.selectedGoroutine.DeferPC() if deferPCEntry != 0 { - _, _, deferfn := dbp.goSymTable.PCToLine(deferPCEntry) + _, _, deferfn := dbp.bi.goSymTable.PCToLine(deferPCEntry) deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false) if err != nil { return err @@ -658,7 +625,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { var ( threadg = map[int]*Thread{} allg []*G - rdr = dbp.DwarfReader() + rdr = dbp.bi.DwarfReader() ) for i := range dbp.threads { @@ -690,11 +657,11 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { return nil, err } } - faddr, err := dbp.currentThread.readMemory(uintptr(allgentryaddr), dbp.arch.PtrSize()) + faddr, err := dbp.currentThread.readMemory(uintptr(allgentryaddr), dbp.bi.arch.PtrSize()) allgptr := binary.LittleEndian.Uint64(faddr) for i := uint64(0); i < allglen; i++ { - gvar, err := dbp.currentThread.newGVariable(uintptr(allgptr+(i*uint64(dbp.arch.PtrSize()))), true) + gvar, err := dbp.currentThread.newGVariable(uintptr(allgptr+(i*uint64(dbp.bi.arch.PtrSize()))), true) if err != nil { return nil, err } @@ -753,35 +720,6 @@ func (dbp *Process) CurrentBreakpoint() *Breakpoint { return dbp.currentThread.CurrentBreakpoint } -// DwarfReader returns a reader for the dwarf data -func (dbp *Process) DwarfReader() *reader.Reader { - return reader.New(dbp.dwarf) -} - -// Sources returns list of source files that comprise the debugged binary. -func (dbp *Process) Sources() map[string]*gosym.Obj { - return dbp.goSymTable.Files -} - -// Funcs returns list of functions present in the debugged program. -func (dbp *Process) Funcs() []gosym.Func { - return dbp.goSymTable.Funcs -} - -// Types returns list of types present in the debugged program. -func (dbp *Process) Types() ([]string, error) { - types := make([]string, 0, len(dbp.types)) - for k := range dbp.types { - types = append(types, k) - } - return types, nil -} - -// PCToLine converts an instruction address to a file/line/function. -func (dbp *Process) PCToLine(pc uint64) (string, int, *gosym.Func) { - return dbp.goSymTable.PCToLine(pc) -} - // FindBreakpointByID finds the breakpoint for the given ID. func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) { for _, bp := range dbp.breakpoints { @@ -795,7 +733,7 @@ func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) { // FindBreakpoint finds the breakpoint for the given pc. func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) { // Check to see if address is past the breakpoint, (i.e. breakpoint was hit). - if bp, ok := dbp.breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok { + if bp, ok := dbp.breakpoints[pc-uint64(dbp.bi.arch.BreakpointSize())]; ok { return bp, true } // Directly use addr to lookup breakpoint. @@ -839,7 +777,7 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e return nil, err } - dbp.arch.SetGStructOffset(ver, isextld) + dbp.bi.arch.SetGStructOffset(ver, isextld) // selectedGoroutine can not be set correctly by the call to updateThreadList // because without calling SetGStructOffset we can not read the G struct of currentThread // but without calling updateThreadList we can not examine memory to determine @@ -910,7 +848,7 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) return } - rdr := dbp.DwarfReader() + rdr := dbp.bi.DwarfReader() rdr.Seek(0) for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() { if err != nil { diff --git a/pkg/proc/proc_darwin.go b/pkg/proc/proc_darwin.go index 5882e94b..0d0474b3 100644 --- a/pkg/proc/proc_darwin.go +++ b/pkg/proc/proc_darwin.go @@ -6,7 +6,6 @@ package proc // #include import "C" import ( - "debug/gosym" "errors" "fmt" "os" @@ -15,10 +14,6 @@ import ( "sync" "unsafe" - "golang.org/x/debug/macho" - - "github.com/derekparker/delve/pkg/dwarf/frame" - "github.com/derekparker/delve/pkg/dwarf/line" sys "golang.org/x/sys/unix" ) @@ -259,99 +254,11 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) { return thread, nil } -func (dbp *Process) parseDebugFrame(exe *macho.File, wg *sync.WaitGroup) { - defer wg.Done() - - debugFrameSec := exe.Section("__debug_frame") - debugInfoSec := exe.Section("__debug_info") - - if debugFrameSec != nil && debugInfoSec != nil { - debugFrame, err := exe.Section("__debug_frame").Data() - if err != nil { - fmt.Println("could not get __debug_frame section", err) - os.Exit(1) - } - dat, err := debugInfoSec.Data() - if err != nil { - fmt.Println("could not get .debug_info section", err) - os.Exit(1) - } - dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) - } else { - fmt.Println("could not find __debug_frame section in binary") - os.Exit(1) - } -} - -func (dbp *Process) obtainGoSymbols(exe *macho.File, wg *sync.WaitGroup) { - defer wg.Done() - - var ( - symdat []byte - pclndat []byte - err error - ) - - if sec := exe.Section("__gosymtab"); sec != nil { - symdat, err = sec.Data() - if err != nil { - fmt.Println("could not get .gosymtab section", err) - os.Exit(1) - } - } - - if sec := exe.Section("__gopclntab"); sec != nil { - pclndat, err = sec.Data() - if err != nil { - fmt.Println("could not get .gopclntab section", err) - os.Exit(1) - } - } - - pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr) - tab, err := gosym.NewTable(symdat, pcln) - if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) - } - - dbp.goSymTable = tab -} - -func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) { - defer wg.Done() - - if sec := exe.Section("__debug_line"); sec != nil { - debugLine, err := exe.Section("__debug_line").Data() - if err != nil { - fmt.Println("could not get __debug_line section", err) - os.Exit(1) - } - dbp.lineInfo = line.Parse(debugLine) - } else { - fmt.Println("could not find __debug_line section in binary") - os.Exit(1) - } -} - -var UnsupportedArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported") - -func (dbp *Process) findExecutable(path string) (*macho.File, string, error) { +func findExecutable(path string, pid int) string { if path == "" { - path = C.GoString(C.find_executable(C.int(dbp.pid))) + path = C.GoString(C.find_executable(C.int(pid))) } - exe, err := macho.Open(path) - if err != nil { - return nil, path, err - } - if exe.Cpu != macho.CpuAmd64 { - return nil, path, UnsupportedArchErr - } - dbp.dwarf, err = exe.DWARF() - if err != nil { - return nil, path, err - } - return exe, path, nil + return path } func (dbp *Process) trapWait(pid int) (*Thread, error) { @@ -510,6 +417,6 @@ func (dbp *Process) resume() error { return nil } -func (dbp *Process) detach() error { +func (dbp *Process) detach(kill bool) error { return PtraceDetach(dbp.pid, 0) } diff --git a/pkg/proc/proc_linux.go b/pkg/proc/proc_linux.go index f1dd8cd7..574e44d2 100644 --- a/pkg/proc/proc_linux.go +++ b/pkg/proc/proc_linux.go @@ -2,7 +2,6 @@ package proc import ( "bytes" - "debug/gosym" "errors" "fmt" "io/ioutil" @@ -17,10 +16,6 @@ import ( "time" sys "golang.org/x/sys/unix" - - "github.com/derekparker/delve/pkg/dwarf/frame" - "github.com/derekparker/delve/pkg/dwarf/line" - "golang.org/x/debug/elf" ) // Process statuses @@ -171,103 +166,11 @@ func (dbp *Process) updateThreadList() error { return nil } -var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") - -func (dbp *Process) findExecutable(path string) (*elf.File, string, error) { +func findExecutable(path string, pid int) string { if path == "" { - path = fmt.Sprintf("/proc/%d/exe", dbp.pid) - } - f, err := os.OpenFile(path, 0, os.ModePerm) - if err != nil { - return nil, path, err - } - elfFile, err := elf.NewFile(f) - if err != nil { - return nil, path, err - } - if elfFile.Machine != elf.EM_X86_64 { - return nil, path, UnsupportedArchErr - } - dbp.dwarf, err = elfFile.DWARF() - if err != nil { - return nil, path, err - } - return elfFile, path, nil -} - -func (dbp *Process) parseDebugFrame(exe *elf.File, wg *sync.WaitGroup) { - defer wg.Done() - - debugFrameSec := exe.Section(".debug_frame") - debugInfoSec := exe.Section(".debug_info") - - if debugFrameSec != nil && debugInfoSec != nil { - debugFrame, err := exe.Section(".debug_frame").Data() - if err != nil { - fmt.Println("could not get .debug_frame section", err) - os.Exit(1) - } - dat, err := debugInfoSec.Data() - if err != nil { - fmt.Println("could not get .debug_info section", err) - os.Exit(1) - } - dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) - } else { - fmt.Println("could not find .debug_frame section in binary") - os.Exit(1) - } -} - -func (dbp *Process) obtainGoSymbols(exe *elf.File, wg *sync.WaitGroup) { - defer wg.Done() - - var ( - symdat []byte - pclndat []byte - err error - ) - - if sec := exe.Section(".gosymtab"); sec != nil { - symdat, err = sec.Data() - if err != nil { - fmt.Println("could not get .gosymtab section", err) - os.Exit(1) - } - } - - if sec := exe.Section(".gopclntab"); sec != nil { - pclndat, err = sec.Data() - if err != nil { - fmt.Println("could not get .gopclntab section", err) - os.Exit(1) - } - } - - pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr) - tab, err := gosym.NewTable(symdat, pcln) - if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) - } - - dbp.goSymTable = tab -} - -func (dbp *Process) parseDebugLineInfo(exe *elf.File, wg *sync.WaitGroup) { - defer wg.Done() - - if sec := exe.Section(".debug_line"); sec != nil { - debugLine, err := exe.Section(".debug_line").Data() - if err != nil { - fmt.Println("could not get .debug_line section", err) - os.Exit(1) - } - dbp.lineInfo = line.Parse(debugLine) - } else { - fmt.Println("could not find .debug_line section in binary") - os.Exit(1) + path = fmt.Sprintf("/proc/%d/exe", pid) } + return path } func (dbp *Process) trapWait(pid int) (*Thread, error) { @@ -484,13 +387,16 @@ func (dbp *Process) resume() error { return nil } -func (dbp *Process) detach() error { +func (dbp *Process) detach(kill bool) error { for threadID := range dbp.threads { err := PtraceDetach(threadID, 0) if err != nil { return err } } + if kill { + return nil + } // For some reason the process will sometimes enter stopped state after a // detach, this doesn't happen immediately either. // We have to wait a bit here, then check if the main thread is stopped and diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 313d5e1b..614fd8ae 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -94,7 +94,7 @@ func currentPC(p *Process, t *testing.T) uint64 { func currentLineNumber(p *Process, t *testing.T) (string, int) { pc := currentPC(p, t) - f, l, _ := p.goSymTable.PCToLine(pc) + f, l, _ := p.BinInfo().goSymTable.PCToLine(pc) return f, l } @@ -198,7 +198,7 @@ func TestHalt(t *testing.T) { func TestStep(t *testing.T) { withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { - helloworldfunc := p.goSymTable.LookupFunc("main.helloworld") + helloworldfunc := p.BinInfo().goSymTable.LookupFunc("main.helloworld") helloworldaddr := helloworldfunc.Entry _, err := p.SetBreakpoint(helloworldaddr, UserBreakpoint, nil) @@ -220,7 +220,7 @@ func TestStep(t *testing.T) { func TestBreakpoint(t *testing.T) { withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { - helloworldfunc := p.goSymTable.LookupFunc("main.helloworld") + helloworldfunc := p.BinInfo().goSymTable.LookupFunc("main.helloworld") helloworldaddr := helloworldfunc.Entry bp, err := p.SetBreakpoint(helloworldaddr, UserBreakpoint, nil) @@ -237,7 +237,7 @@ func TestBreakpoint(t *testing.T) { } if pc-1 != bp.Addr && pc != bp.Addr { - f, l, _ := p.goSymTable.PCToLine(pc) + f, l, _ := p.BinInfo().goSymTable.PCToLine(pc) t.Fatalf("Break not respected:\nPC:%#v %s:%d\nFN:%#v \n", pc, f, l, bp.Addr) } }) @@ -245,7 +245,7 @@ func TestBreakpoint(t *testing.T) { func TestBreakpointInSeperateGoRoutine(t *testing.T) { withTestProcess("testthreads", t, func(p *Process, fixture protest.Fixture) { - fn := p.goSymTable.LookupFunc("main.anotherthread") + fn := p.BinInfo().goSymTable.LookupFunc("main.anotherthread") if fn == nil { t.Fatal("No fn exists") } @@ -265,7 +265,7 @@ func TestBreakpointInSeperateGoRoutine(t *testing.T) { t.Fatal(err) } - f, l, _ := p.goSymTable.PCToLine(pc) + f, l, _ := p.BinInfo().goSymTable.PCToLine(pc) if f != "testthreads.go" && l != 8 { t.Fatal("Program did not hit breakpoint") } @@ -283,7 +283,7 @@ func TestBreakpointWithNonExistantFunction(t *testing.T) { func TestClearBreakpointBreakpoint(t *testing.T) { withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { - fn := p.goSymTable.LookupFunc("main.sleepytime") + fn := p.BinInfo().goSymTable.LookupFunc("main.sleepytime") bp, err := p.SetBreakpoint(fn.Entry, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -579,7 +579,7 @@ func TestRuntimeBreakpoint(t *testing.T) { if err != nil { t.Fatal(err) } - _, l, _ := p.PCToLine(pc) + _, l, _ := p.BinInfo().PCToLine(pc) if l != 10 { t.Fatal("did not respect breakpoint") } @@ -588,7 +588,7 @@ func TestRuntimeBreakpoint(t *testing.T) { func TestFindReturnAddress(t *testing.T) { withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.goSymTable.LineToPC(fixture.Source, 24) + start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 24) if err != nil { t.Fatal(err) } @@ -604,7 +604,7 @@ func TestFindReturnAddress(t *testing.T) { if err != nil { t.Fatal(err) } - _, l, _ := p.goSymTable.PCToLine(addr) + _, l, _ := p.BinInfo().goSymTable.PCToLine(addr) if l != 40 { t.Fatalf("return address not found correctly, expected line 40") } @@ -614,7 +614,7 @@ func TestFindReturnAddress(t *testing.T) { func TestFindReturnAddressTopOfStackFn(t *testing.T) { withTestProcess("testreturnaddress", t, func(p *Process, fixture protest.Fixture) { fnName := "runtime.rt0_go" - fn := p.goSymTable.LookupFunc(fnName) + fn := p.BinInfo().goSymTable.LookupFunc(fnName) if fn == nil { t.Fatalf("could not find function %s", fnName) } @@ -1002,7 +1002,7 @@ func TestProcessReceivesSIGCHLD(t *testing.T) { func TestIssue239(t *testing.T) { withTestProcess("is sue239", t, func(p *Process, fixture protest.Fixture) { - pos, _, err := p.goSymTable.LineToPC(fixture.Source, 17) + pos, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 17) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(pos, UserBreakpoint, nil) assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%d)", pos)) @@ -1266,7 +1266,7 @@ func TestIssue325(t *testing.T) { func TestBreakpointCounts(t *testing.T) { withTestProcess("bpcountstest", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 12) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 12) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1317,7 +1317,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) { } m := map[int64]int64{} withTestProcess("bpcountstest", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 12) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 12) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1412,7 +1412,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) { func TestIssue262(t *testing.T) { // Continue does not work when the current breakpoint is set on a NOP instruction withTestProcess("issue262", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 11) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 11) assertNoError(err, t, "LineToPC") _, err = p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1434,7 +1434,7 @@ func TestIssue305(t *testing.T) { // the internal breakpoints aren't cleared preventing further use of // 'next' command withTestProcess("issue305", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 5) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 5) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1477,7 +1477,7 @@ func BenchmarkLocalVariables(b *testing.B) { func TestCondBreakpoint(t *testing.T) { withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 9) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 9) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1501,7 +1501,7 @@ func TestCondBreakpoint(t *testing.T) { func TestCondBreakpointError(t *testing.T) { withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 9) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 9) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1581,7 +1581,7 @@ func TestStepIntoFunction(t *testing.T) { func TestIssue384(t *testing.T) { // Crash related to reading uninitialized memory, introduced by the memory prefetching optimization withTestProcess("issue384", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.goSymTable.LineToPC(fixture.Source, 13) + start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 13) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1594,7 +1594,7 @@ func TestIssue384(t *testing.T) { func TestIssue332_Part1(t *testing.T) { // Next shouldn't step inside a function call withTestProcess("issue332", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.goSymTable.LineToPC(fixture.Source, 8) + start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 8) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1620,7 +1620,7 @@ func TestIssue332_Part2(t *testing.T) { // which leads to 'next' and 'stack' failing with error "could not find FDE for PC: " // because the incorrect FDE data leads to reading the wrong stack address as the return address withTestProcess("issue332", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.goSymTable.LineToPC(fixture.Source, 8) + start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 8) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1671,7 +1671,7 @@ func TestIssue396(t *testing.T) { func TestIssue414(t *testing.T) { // Stepping until the program exits withTestProcess("math", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.goSymTable.LineToPC(fixture.Source, 9) + start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 9) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1936,7 +1936,7 @@ func TestUnsupportedArch(t *testing.T) { p, err := Launch([]string{outfile}, ".") switch err { - case UnsupportedArchErr: + case UnsupportedLinuxArchErr, UnsupportedWindowsArchErr, UnsupportedDarwinArchErr: // all good case nil: p.Halt() @@ -1951,7 +1951,7 @@ func TestIssue573(t *testing.T) { // calls to runtime.duffzero and runtime.duffcopy jump directly into the middle // of the function and the internal breakpoint set by StepInto may be missed. withTestProcess("issue573", t, func(p *Process, fixture protest.Fixture) { - f := p.goSymTable.LookupFunc("main.foo") + f := p.BinInfo().goSymTable.LookupFunc("main.foo") _, err := p.SetBreakpoint(f.Entry, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") assertNoError(p.Continue(), t, "Continue()") @@ -2271,7 +2271,7 @@ func TestStepOutDefer(t *testing.T) { assertNoError(p.StepOut(), t, "StepOut()") - f, l, _ := p.goSymTable.PCToLine(currentPC(p, t)) + f, l, _ := p.BinInfo().goSymTable.PCToLine(currentPC(p, t)) if f == fixture.Source || l == 6 { t.Fatalf("wrong location %s:%d, expected to end somewhere in runtime", f, l) } @@ -2375,7 +2375,7 @@ func TestWorkDir(t *testing.T) { wd = "/private/tmp" } withTestProcessArgs("workdir", t, wd, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.goSymTable.LineToPC(fixture.Source, 14) + addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 14) assertNoError(err, t, "LineToPC") p.SetBreakpoint(addr, UserBreakpoint, nil) p.Continue() diff --git a/pkg/proc/proc_windows.go b/pkg/proc/proc_windows.go index e3fff9f2..bcfb0531 100644 --- a/pkg/proc/proc_windows.go +++ b/pkg/proc/proc_windows.go @@ -1,8 +1,6 @@ package proc import ( - "debug/gosym" - "debug/pe" "errors" "fmt" "os" @@ -13,10 +11,6 @@ import ( "unsafe" sys "golang.org/x/sys/windows" - - "github.com/derekparker/delve/pkg/dwarf/frame" - "github.com/derekparker/delve/pkg/dwarf/line" - "golang.org/x/debug/dwarf" ) // OSProcessDetails holds Windows specific information. @@ -39,11 +33,11 @@ func Launch(cmd []string, wd string) (*Process, error) { } } - peFile, err := openExecutablePath(argv0Go) + _, closer, err := openExecutablePathPE(argv0Go) if err != nil { return nil, NotExecutableErr } - peFile.Close() + closer.Close() // Duplicate the stdin/stdout/stderr handles files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)} @@ -253,191 +247,8 @@ func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, susp return thread, nil } -func (dbp *Process) parseDebugFrame(exe *pe.File, wg *sync.WaitGroup) { - defer wg.Done() - - debugFrameSec := exe.Section(".debug_frame") - debugInfoSec := exe.Section(".debug_info") - - if debugFrameSec != nil && debugInfoSec != nil { - debugFrame, err := debugFrameSec.Data() - if err != nil && uint32(len(debugFrame)) < debugFrameSec.Size { - fmt.Println("could not get .debug_frame section", err) - os.Exit(1) - } - if 0 < debugFrameSec.VirtualSize && debugFrameSec.VirtualSize < debugFrameSec.Size { - debugFrame = debugFrame[:debugFrameSec.VirtualSize] - } - dat, err := debugInfoSec.Data() - if err != nil { - fmt.Println("could not get .debug_info section", err) - os.Exit(1) - } - dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) - } else { - fmt.Println("could not find .debug_frame section in binary") - os.Exit(1) - } -} - -// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go -func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { - for _, s := range f.Symbols { - if s.Name != name { - continue - } - if s.SectionNumber <= 0 { - return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber) - } - if len(f.Sections) < int(s.SectionNumber) { - return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections)) - } - return s, nil - } - return nil, fmt.Errorf("no %s symbol found", name) -} - -// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go -func loadPETable(f *pe.File, sname, ename string) ([]byte, error) { - ssym, err := findPESymbol(f, sname) - if err != nil { - return nil, err - } - esym, err := findPESymbol(f, ename) - if err != nil { - return nil, err - } - if ssym.SectionNumber != esym.SectionNumber { - return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename) - } - sect := f.Sections[ssym.SectionNumber-1] - data, err := sect.Data() - if err != nil { - return nil, err - } - return data[ssym.Value:esym.Value], nil -} - -// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go -func pcln(exe *pe.File) (textStart uint64, symtab, pclntab []byte, err error) { - var imageBase uint64 - switch oh := exe.OptionalHeader.(type) { - case *pe.OptionalHeader32: - imageBase = uint64(oh.ImageBase) - case *pe.OptionalHeader64: - imageBase = oh.ImageBase - default: - return 0, nil, nil, fmt.Errorf("pe file format not recognized") - } - if sect := exe.Section(".text"); sect != nil { - textStart = imageBase + uint64(sect.VirtualAddress) - } - if pclntab, err = loadPETable(exe, "runtime.pclntab", "runtime.epclntab"); err != nil { - // We didn't find the symbols, so look for the names used in 1.3 and earlier. - // TODO: Remove code looking for the old symbols when we no longer care about 1.3. - var err2 error - if pclntab, err2 = loadPETable(exe, "pclntab", "epclntab"); err2 != nil { - return 0, nil, nil, err - } - } - if symtab, err = loadPETable(exe, "runtime.symtab", "runtime.esymtab"); err != nil { - // Same as above. - var err2 error - if symtab, err2 = loadPETable(exe, "symtab", "esymtab"); err2 != nil { - return 0, nil, nil, err - } - } - return textStart, symtab, pclntab, nil -} - -func (dbp *Process) obtainGoSymbols(exe *pe.File, wg *sync.WaitGroup) { - defer wg.Done() - - _, symdat, pclndat, err := pcln(exe) - if err != nil { - fmt.Println("could not get Go symbols", err) - os.Exit(1) - } - - pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset)) - tab, err := gosym.NewTable(symdat, pcln) - if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) - } - - dbp.goSymTable = tab -} - -func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) { - defer wg.Done() - - if sec := exe.Section(".debug_line"); sec != nil { - debugLine, err := sec.Data() - if err != nil && uint32(len(debugLine)) < sec.Size { - fmt.Println("could not get .debug_line section", err) - os.Exit(1) - } - if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size { - debugLine = debugLine[:sec.VirtualSize] - } - dbp.lineInfo = line.Parse(debugLine) - } else { - fmt.Println("could not find .debug_line section in binary") - os.Exit(1) - } -} - -var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") - -func (dbp *Process) findExecutable(path string) (*pe.File, string, error) { - peFile, err := openExecutablePath(path) - if err != nil { - return nil, path, err - } - if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { - return nil, path, UnsupportedArchErr - } - dbp.dwarf, err = dwarfFromPE(peFile) - if err != nil { - return nil, path, err - } - return peFile, path, nil -} - -func openExecutablePath(path string) (*pe.File, error) { - f, err := os.OpenFile(path, 0, os.ModePerm) - if err != nil { - return nil, err - } - return pe.NewFile(f) -} - -// Adapted from src/debug/pe/file.go: pe.(*File).DWARF() -func dwarfFromPE(f *pe.File) (*dwarf.Data, error) { - // There are many other DWARF sections, but these - // are the ones the debug/dwarf package uses. - // Don't bother loading others. - var names = [...]string{"abbrev", "info", "line", "str"} - var dat [len(names)][]byte - for i, name := range names { - name = ".debug_" + name - s := f.Section(name) - if s == nil { - continue - } - b, err := s.Data() - if err != nil && uint32(len(b)) < s.Size { - return nil, err - } - if 0 < s.VirtualSize && s.VirtualSize < s.Size { - b = b[:s.VirtualSize] - } - dat[i] = b - } - - abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3] - return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str) +func findExecutable(path string, pid int) string { + return path } type waitForDebugEventFlags int @@ -519,8 +330,8 @@ func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, e // this exception anymore. atbp := true if thread, found := dbp.threads[tid]; found { - if data, err := thread.readMemory(exception.ExceptionRecord.ExceptionAddress, dbp.arch.BreakpointSize()); err == nil { - instr := dbp.arch.BreakpointInstruction() + if data, err := thread.readMemory(exception.ExceptionRecord.ExceptionAddress, dbp.bi.arch.BreakpointSize()); err == nil { + instr := dbp.bi.arch.BreakpointInstruction() for i := range instr { if data[i] != instr[i] { atbp = false @@ -663,11 +474,13 @@ func (dbp *Process) resume() error { return nil } -func (dbp *Process) detach() error { - for _, thread := range dbp.threads { - _, err := _ResumeThread(thread.os.hThread) - if err != nil { - return err +func (dbp *Process) detach(kill bool) error { + if !kill { + for _, thread := range dbp.threads { + _, err := _ResumeThread(thread.os.hThread) + if err != nil { + return err + } } } return PtraceDetach(dbp.pid, 0) diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index df5cd671..cfcd58c9 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -98,7 +98,7 @@ func (g *G) Stacktrace(depth int) ([]Stackframe, error) { // GoroutineLocation returns the location of the given // goroutine. func (dbp *Process) GoroutineLocation(g *G) *Location { - f, l, fn := dbp.PCToLine(g.PC) + f, l, fn := dbp.bi.PCToLine(g.PC) return &Location{PC: g.PC, File: f, Line: l, Fn: fn} } @@ -130,11 +130,11 @@ type savedLR struct { } func newStackIterator(dbp *Process, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator { - stackBarrierFunc := dbp.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9 + stackBarrierFunc := dbp.bi.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9 var stackBarrierPC uint64 if stackBarrierFunc != nil && stkbar != nil { stackBarrierPC = stackBarrierFunc.Entry - fn := dbp.goSymTable.PCToFunc(pc) + fn := dbp.bi.goSymTable.PCToFunc(pc) if fn != nil && fn.Name == runtimeStackBarrier { // We caught the goroutine as it's executing the stack barrier, we must // determine whether or not g.stackPos has already been incremented or not. @@ -188,7 +188,7 @@ func (it *stackIterator) Next() bool { it.top = false it.pc = it.frame.Ret it.sp = uint64(it.frame.CFA) - it.bp, _ = readUintRaw(it.dbp.currentThread, uintptr(it.bp), int64(it.dbp.arch.PtrSize())) + it.bp, _ = readUintRaw(it.dbp.currentThread, uintptr(it.bp), int64(it.dbp.bi.arch.PtrSize())) return true } @@ -206,14 +206,14 @@ func (it *stackIterator) Err() error { } func (dbp *Process) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) { - fde, err := dbp.frameEntries.FDEForPC(pc) + fde, err := dbp.bi.frameEntries.FDEForPC(pc) if _, nofde := err.(*frame.NoFDEForPCError); nofde { if bp == 0 { return Stackframe{}, err } // When no FDE is available attempt to use BP instead - retaddr := uintptr(int(bp) + dbp.arch.PtrSize()) - cfa := int64(retaddr) + int64(dbp.arch.PtrSize()) + retaddr := uintptr(int(bp) + dbp.bi.arch.PtrSize()) + cfa := int64(retaddr) + int64(dbp.bi.arch.PtrSize()) return dbp.newStackframe(pc, cfa, retaddr, nil, top) } @@ -228,14 +228,14 @@ func (dbp *Process) newStackframe(pc uint64, cfa int64, retaddr uintptr, fde *fr if retaddr == 0 { return Stackframe{}, NullAddrError{} } - f, l, fn := dbp.PCToLine(pc) - ret, err := readUintRaw(dbp.currentThread, retaddr, int64(dbp.arch.PtrSize())) + f, l, fn := dbp.bi.PCToLine(pc) + ret, err := readUintRaw(dbp.currentThread, retaddr, int64(dbp.bi.arch.PtrSize())) if err != nil { return Stackframe{}, err } r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr)} if !top { - r.Call.File, r.Call.Line, r.Call.Fn = dbp.PCToLine(pc - 1) + r.Call.File, r.Call.Line, r.Call.Fn = dbp.bi.PCToLine(pc - 1) r.Call.PC = r.Current.PC } else { r.Call = r.Current diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 4e44efea..82833449 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -112,7 +112,7 @@ func (thread *Thread) Location() (*Location, error) { if err != nil { return nil, err } - f, l, fn := thread.dbp.PCToLine(pc) + f, l, fn := thread.dbp.bi.PCToLine(pc) return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil } @@ -218,7 +218,7 @@ func (dbp *Process) next(stepInto bool) error { if dbp.selectedGoroutine != nil { deferPCEntry := dbp.selectedGoroutine.DeferPC() if deferPCEntry != 0 { - _, _, deferfn := dbp.goSymTable.PCToLine(deferPCEntry) + _, _, deferfn := dbp.bi.goSymTable.PCToLine(deferPCEntry) var err error deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false) if err != nil { @@ -240,7 +240,7 @@ func (dbp *Process) next(stepInto bool) error { } // Add breakpoints on all the lines in the current function - pcs, err := dbp.lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File) + pcs, err := dbp.bi.lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File) if err != nil { return err } @@ -255,7 +255,7 @@ func (dbp *Process) next(stepInto bool) error { } if !covered { - fn := dbp.goSymTable.PCToFunc(topframe.Ret) + fn := dbp.bi.goSymTable.PCToFunc(topframe.Ret) if dbp.selectedGoroutine != nil && fn != nil && fn.Name == "runtime.goexit" { return nil } @@ -340,13 +340,13 @@ func (thread *Thread) getGVariable() (*Variable, error) { return nil, err } - if thread.dbp.arch.GStructOffset() == 0 { + if thread.dbp.bi.arch.GStructOffset() == 0 { // GetG was called through SwitchThread / updateThreadList during initialization // thread.dbp.arch isn't setup yet (it needs a current thread to read global variables from) return nil, fmt.Errorf("g struct offset not initialized") } - gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.arch.PtrSize()) + gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.bi.arch.GStructOffset()), thread.dbp.bi.arch.PtrSize()) if err != nil { return nil, err } @@ -360,7 +360,7 @@ func (thread *Thread) getGVariable() (*Variable, error) { } func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) { - typ, err := thread.dbp.findType("runtime.g") + typ, err := thread.dbp.bi.findType("runtime.g") if err != nil { return nil, err } @@ -368,7 +368,7 @@ func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) name := "" if deref { - typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.dbp.arch.PtrSize()), "", reflect.Ptr, 0}, typ} + typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.dbp.bi.arch.PtrSize()), "", reflect.Ptr, 0}, typ} } else { name = "runtime.curg" } diff --git a/pkg/proc/threads_darwin.go b/pkg/proc/threads_darwin.go index 71c025bc..1e176857 100644 --- a/pkg/proc/threads_darwin.go +++ b/pkg/proc/threads_darwin.go @@ -87,7 +87,7 @@ func (t *Thread) blocked() bool { if err != nil { return false } - fn := t.dbp.goSymTable.PCToFunc(pc) + fn := t.dbp.bi.goSymTable.PCToFunc(pc) if fn == nil { return false } diff --git a/pkg/proc/threads_linux.go b/pkg/proc/threads_linux.go index 8592a2c5..1eeaadec 100644 --- a/pkg/proc/threads_linux.go +++ b/pkg/proc/threads_linux.go @@ -69,7 +69,7 @@ func (t *Thread) singleStep() (err error) { func (t *Thread) blocked() bool { pc, _ := t.PC() - fn := t.dbp.goSymTable.PCToFunc(pc) + fn := t.dbp.bi.goSymTable.PCToFunc(pc) if fn != nil && ((fn.Name == "runtime.futex") || (fn.Name == "runtime.usleep") || (fn.Name == "runtime.clone")) { return true } diff --git a/pkg/proc/threads_windows.go b/pkg/proc/threads_windows.go index 536a4114..b7de8c1e 100644 --- a/pkg/proc/threads_windows.go +++ b/pkg/proc/threads_windows.go @@ -110,7 +110,7 @@ func (t *Thread) blocked() bool { if err != nil { return false } - fn := t.dbp.goSymTable.PCToFunc(pc) + fn := t.dbp.bi.goSymTable.PCToFunc(pc) if fn == nil { return false } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 65b8b383..1badada0 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -38,40 +38,40 @@ const ( ) // Do not call this function directly it isn't able to deal correctly with package paths -func (dbp *Process) findType(name string) (dwarf.Type, error) { - off, found := dbp.types[name] +func (bi *BinaryInfo) findType(name string) (dwarf.Type, error) { + off, found := bi.types[name] if !found { return nil, reader.TypeNotFoundErr } - return dbp.dwarf.Type(off) + return bi.dwarf.Type(off) } -func (dbp *Process) pointerTo(typ dwarf.Type) dwarf.Type { - return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), "*" + typ.Common().Name, reflect.Ptr, 0}, typ} +func pointerTo(typ dwarf.Type, arch Arch) dwarf.Type { + return &dwarf.PtrType{dwarf.CommonType{int64(arch.PtrSize()), "*" + typ.Common().Name, reflect.Ptr, 0}, typ} } -func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) { - dbp.loadPackageMap() +func (bi *BinaryInfo) findTypeExpr(expr ast.Expr) (dwarf.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 // parse/serialize types correctly or can not resolve package paths. typn, _ := strconv.Unquote(lit.Value) - return dbp.findType(typn) + return bi.findType(typn) } - dbp.expandPackagesInType(expr) + bi.expandPackagesInType(expr) if snode, ok := expr.(*ast.StarExpr); ok { // Pointer types only appear in the dwarf informations when // a pointer to the type is used in the target program, here // we create a pointer type on the fly so that the user can // specify a pointer to any variable used in the target program - ptyp, err := dbp.findTypeExpr(snode.X) + ptyp, err := bi.findTypeExpr(snode.X) if err != nil { return nil, err } - return dbp.pointerTo(ptyp), nil + return pointerTo(ptyp, bi.arch), nil } - return dbp.findType(exprToString(expr)) + return bi.findType(exprToString(expr)) } func complexType(typename string) bool { @@ -84,12 +84,12 @@ func complexType(typename string) bool { return false } -func (dbp *Process) loadPackageMap() error { - if dbp.packageMap != nil { +func (bi *BinaryInfo) loadPackageMap() error { + if bi.packageMap != nil { return nil } - dbp.packageMap = map[string]string{} - reader := dbp.DwarfReader() + bi.packageMap = map[string]string{} + reader := bi.DwarfReader() for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { return err @@ -114,7 +114,7 @@ func (dbp *Process) loadPackageMap() error { continue } name := path[slash+1:] - dbp.packageMap[name] = path + bi.packageMap[name] = path } return nil } @@ -129,11 +129,11 @@ func (v sortFunctionsDebugInfoByLowpc) Swap(i, j int) { v[j] = temp } -func (dbp *Process) loadDebugInfoMaps(wg *sync.WaitGroup) { +func (bi *BinaryInfo) loadDebugInfoMaps(wg *sync.WaitGroup) { defer wg.Done() - dbp.types = make(map[string]dwarf.Offset) - dbp.functions = []functionDebugInfo{} - reader := dbp.DwarfReader() + bi.types = make(map[string]dwarf.Offset) + bi.functions = []functionDebugInfo{} + reader := bi.DwarfReader() for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { break @@ -144,8 +144,8 @@ func (dbp *Process) loadDebugInfoMaps(wg *sync.WaitGroup) { if !ok { continue } - if _, exists := dbp.types[name]; !exists { - dbp.types[name] = entry.Offset + if _, exists := bi.types[name]; !exists { + bi.types[name] = entry.Offset } case dwarf.TagSubprogram: lowpc, ok := entry.Val(dwarf.AttrLowpc).(uint64) @@ -156,19 +156,19 @@ func (dbp *Process) loadDebugInfoMaps(wg *sync.WaitGroup) { if !ok { continue } - dbp.functions = append(dbp.functions, functionDebugInfo{lowpc, highpc, entry.Offset}) + bi.functions = append(bi.functions, functionDebugInfo{lowpc, highpc, entry.Offset}) } } - sort.Sort(sortFunctionsDebugInfoByLowpc(dbp.functions)) + sort.Sort(sortFunctionsDebugInfoByLowpc(bi.functions)) } -func (dbp *Process) findFunctionDebugInfo(pc uint64) (dwarf.Offset, error) { - i := sort.Search(len(dbp.functions), func(i int) bool { - fn := dbp.functions[i] +func (bi *BinaryInfo) findFunctionDebugInfo(pc uint64) (dwarf.Offset, error) { + i := sort.Search(len(bi.functions), func(i int) bool { + fn := bi.functions[i] return pc <= fn.lowpc || (fn.lowpc <= pc && pc < fn.highpc) }) - if i != len(dbp.functions) { - fn := dbp.functions[i] + if i != len(bi.functions) { + fn := bi.functions[i] if fn.lowpc <= pc && pc < fn.highpc { return fn.offset, nil } @@ -176,37 +176,37 @@ func (dbp *Process) findFunctionDebugInfo(pc uint64) (dwarf.Offset, error) { return 0, errors.New("unable to find function context") } -func (dbp *Process) expandPackagesInType(expr ast.Expr) { +func (bi *BinaryInfo) expandPackagesInType(expr ast.Expr) { switch e := expr.(type) { case *ast.ArrayType: - dbp.expandPackagesInType(e.Elt) + bi.expandPackagesInType(e.Elt) case *ast.ChanType: - dbp.expandPackagesInType(e.Value) + bi.expandPackagesInType(e.Value) case *ast.FuncType: for i := range e.Params.List { - dbp.expandPackagesInType(e.Params.List[i].Type) + bi.expandPackagesInType(e.Params.List[i].Type) } if e.Results != nil { for i := range e.Results.List { - dbp.expandPackagesInType(e.Results.List[i].Type) + bi.expandPackagesInType(e.Results.List[i].Type) } } case *ast.MapType: - dbp.expandPackagesInType(e.Key) - dbp.expandPackagesInType(e.Value) + bi.expandPackagesInType(e.Key) + bi.expandPackagesInType(e.Value) case *ast.ParenExpr: - dbp.expandPackagesInType(e.X) + bi.expandPackagesInType(e.X) case *ast.SelectorExpr: switch x := e.X.(type) { case *ast.Ident: - if path, ok := dbp.packageMap[x.Name]; ok { + if path, ok := bi.packageMap[x.Name]; ok { x.Name = path } default: - dbp.expandPackagesInType(e.X) + bi.expandPackagesInType(e.X) } case *ast.StarExpr: - dbp.expandPackagesInType(e.X) + bi.expandPackagesInType(e.X) default: // nothing to do } @@ -221,7 +221,7 @@ type nameOfRuntimeTypeEntry struct { // _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) { - if e, ok := _type.dbp.nameOfRuntimeType[_type.Addr]; ok { + if e, ok := _type.dbp.bi.nameOfRuntimeType[_type.Addr]; ok { return e.typename, e.kind, nil } @@ -244,7 +244,7 @@ func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) return typename, kind, err } - _type.dbp.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename, kind} + _type.dbp.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename, kind} return typename, kind, nil } @@ -277,7 +277,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 = _type.dbp.resolveNameOff(_type.Addr, uintptr(strOff)) + typename, _, _, err = _type.dbp.bi.resolveNameOff(_type.Addr, uintptr(strOff), _type.dbp.currentThread) if err != nil { return "", err } @@ -303,7 +303,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 := _type.dbp.resolveNameOff(_type.Addr, uintptr(pkgPathOff)) + pkgPath, _, _, err := _type.dbp.bi.resolveNameOff(_type.Addr, uintptr(pkgPathOff), _type.dbp.currentThread) if err != nil { return "", err } @@ -376,11 +376,11 @@ func nameOfUnnamedRuntimeType(_type *Variable, kind, tflag int64) (string, error // (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) { - rtyp, err := _type.dbp.findType("runtime._type") + rtyp, err := _type.dbp.bi.findType("runtime._type") if err != nil { return "", err } - prtyp := _type.dbp.pointerTo(rtyp) + prtyp := pointerTo(rtyp, _type.dbp.bi.arch) uadd := _type.RealType.Common().ByteSize if ut := uncommon(_type, tflag); ut != nil { @@ -407,7 +407,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string for i := int64(0); i < inCount; i++ { argtype := cursortyp.maybeDereference() - cursortyp.Addr += uintptr(_type.dbp.arch.PtrSize()) + cursortyp.Addr += uintptr(_type.dbp.bi.arch.PtrSize()) argtypename, _, err := nameOfRuntimeType(argtype) if err != nil { return "", err @@ -434,7 +434,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string buf.WriteString(" (") for i := int64(0); i < outCount; i++ { argtype := cursortyp.maybeDereference() - cursortyp.Addr += uintptr(_type.dbp.arch.PtrSize()) + cursortyp.Addr += uintptr(_type.dbp.bi.arch.PtrSize()) argtypename, _, err := nameOfRuntimeType(argtype) if err != nil { return "", err @@ -473,14 +473,14 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err case "name": nameoff, _ := constant.Int64Val(im.Children[i].Value) var err error - methodname, _, _, err = _type.dbp.resolveNameOff(_type.Addr, uintptr(nameoff)) + methodname, _, _, err = _type.dbp.bi.resolveNameOff(_type.Addr, uintptr(nameoff), _type.dbp.currentThread) if err != nil { return "", err } case "typ": typeoff, _ := constant.Int64Val(im.Children[i].Value) - typ, err := _type.dbp.resolveTypeOff(_type.Addr, uintptr(typeoff)) + typ, err := _type.dbp.bi.resolveTypeOff(_type.Addr, uintptr(typeoff), _type.dbp.currentThread) if err != nil { return "", err } @@ -536,7 +536,7 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error) case "name": nameoff, _ := constant.Int64Val(field.Children[i].Value) var err error - fieldname, _, _, err = _type.dbp.loadName(uintptr(nameoff)) + fieldname, _, _, err = _type.dbp.bi.loadName(uintptr(nameoff), _type.mem) if err != nil { return "", err } @@ -578,13 +578,13 @@ func fieldToType(_type *Variable, fieldName string) (string, error) { } func specificRuntimeType(_type *Variable, kind int64) (*Variable, error) { - rtyp, err := _type.dbp.findType("runtime._type") + rtyp, err := _type.dbp.bi.findType("runtime._type") if err != nil { return nil, err } - prtyp := _type.dbp.pointerTo(rtyp) + prtyp := pointerTo(rtyp, _type.dbp.bi.arch) - uintptrtyp, err := _type.dbp.findType("uintptr") + uintptrtyp, err := _type.dbp.bi.findType("uintptr") if err != nil { return nil, err } @@ -602,7 +602,7 @@ func specificRuntimeType(_type *Variable, kind int64) (*Variable, error) { newSliceType := func(elemtype dwarf.Type) *dwarf.SliceType { r := newStructType("[]"+elemtype.Common().Name, uintptr(3*uintptrtyp.Size())) - appendField(r, "array", _type.dbp.pointerTo(elemtype), 0) + appendField(r, "array", pointerTo(elemtype, _type.dbp.bi.arch), 0) appendField(r, "len", uintptrtyp, uintptr(uintptrtyp.Size())) appendField(r, "cap", uintptrtyp, uintptr(2*uintptrtyp.Size())) return &dwarf.SliceType{StructType: *r, ElemType: elemtype} @@ -746,7 +746,7 @@ func uncommon(_type *Variable, tflag int64) *Variable { return nil } - typ, err := _type.dbp.findType("runtime.uncommontype") + typ, err := _type.dbp.bi.findType("runtime.uncommontype") if err != nil { return nil } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index f243d045..7be24beb 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -190,7 +190,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, dbp *Process, v.stride = 1 v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}} if v.Addr != 0 { - v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.dbp.arch, v.Addr) + v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.dbp.bi.arch, v.Addr) } case *dwarf.SliceType: v.Kind = reflect.Slice @@ -321,17 +321,17 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) { // DwarfReader returns the DwarfReader containing the // Dwarf information for the target process. func (scope *EvalScope) DwarfReader() *reader.Reader { - return scope.Thread.dbp.DwarfReader() + return scope.Thread.dbp.bi.DwarfReader() } // Type returns the Dwarf type entry at `offset`. func (scope *EvalScope) Type(offset dwarf.Offset) (dwarf.Type, error) { - return scope.Thread.dbp.dwarf.Type(offset) + return scope.Thread.dbp.bi.dwarf.Type(offset) } // PtrSize returns the size of a pointer. func (scope *EvalScope) PtrSize() int { - return scope.Thread.dbp.arch.PtrSize() + return scope.Thread.dbp.bi.arch.PtrSize() } // ChanRecvBlocked returns whether the goroutine is blocked on @@ -367,7 +367,7 @@ func (gvar *Variable) parseG() (*G, error) { _, deref := gvar.RealType.(*dwarf.PtrType) if deref { - gaddrbytes, err := mem.readMemory(uintptr(gaddr), dbp.arch.PtrSize()) + gaddrbytes, err := mem.readMemory(uintptr(gaddr), dbp.bi.arch.PtrSize()) if err != nil { return nil, fmt.Errorf("error derefing *G %s", err) } @@ -405,7 +405,7 @@ func (gvar *Variable) parseG() (*G, error) { } status, _ := constant.Int64Val(gvar.fieldVariable("atomicstatus").Value) - f, l, fn := gvar.dbp.goSymTable.PCToLine(uint64(pc)) + f, l, fn := gvar.dbp.bi.goSymTable.PCToLine(uint64(pc)) g := &G{ ID: int(id), GoPC: uint64(gopc), @@ -498,7 +498,7 @@ func (g *G) UserCurrent() Location { // Go returns the location of the 'go' statement // that spawned this goroutine. func (g *G) Go() Location { - f, l, fn := g.dbp.goSymTable.PCToLine(g.GoPC) + f, l, fn := g.dbp.bi.goSymTable.PCToLine(g.GoPC) return Location{PC: g.GoPC, File: f, Line: l, Fn: fn} } @@ -586,7 +586,7 @@ func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry, cfg LoadCon func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) { reader := scope.DwarfReader() - off, err := scope.Thread.dbp.findFunctionDebugInfo(scope.PC) + off, err := scope.Thread.dbp.bi.findFunctionDebugInfo(scope.PC) if err != nil { return nil, err } @@ -1207,7 +1207,7 @@ func (v *Variable) writeBool(value bool) error { } func (v *Variable) readFunctionPtr() { - val, err := v.mem.readMemory(v.Addr, v.dbp.arch.PtrSize()) + val, err := v.mem.readMemory(v.Addr, v.dbp.bi.arch.PtrSize()) if err != nil { v.Unreadable = err return @@ -1221,14 +1221,14 @@ func (v *Variable) readFunctionPtr() { return } - val, err = v.mem.readMemory(fnaddr, v.dbp.arch.PtrSize()) + val, err = v.mem.readMemory(fnaddr, v.dbp.bi.arch.PtrSize()) if err != nil { v.Unreadable = err return } v.Base = uintptr(binary.LittleEndian.Uint64(val)) - fn := v.dbp.goSymTable.PCToFunc(uint64(v.Base)) + fn := v.dbp.bi.goSymTable.PCToFunc(uint64(v.Base)) if fn == nil { v.Unreadable = fmt.Errorf("could not find function for %#v", v.Base) return @@ -1603,7 +1603,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig return } - typ, err = v.dbp.findType(typename) + typ, err = v.dbp.bi.findType(typename) if err != nil { v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err) return @@ -1627,7 +1627,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig return } - typ, err = v.dbp.findTypeExpr(t) + typ, err = v.dbp.bi.findTypeExpr(t) if err != nil { v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err) return @@ -1637,7 +1637,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig if kind&kindDirectIface == 0 { realtyp := resolveTypedef(typ) if _, isptr := realtyp.(*dwarf.PtrType); !isptr { - typ = v.dbp.pointerTo(typ) + typ = pointerTo(typ, v.dbp.bi.arch) } } @@ -1655,7 +1655,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig // Fetches all variables of a specific type in the current function scope func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variable, error) { reader := scope.DwarfReader() - off, err := scope.Thread.dbp.findFunctionDebugInfo(scope.PC) + off, err := scope.Thread.dbp.bi.findFunctionDebugInfo(scope.PC) if err != nil { return nil, err } diff --git a/pkg/target/target.go b/pkg/target/target.go index 44551603..f6ba21f7 100644 --- a/pkg/target/target.go +++ b/pkg/target/target.go @@ -3,7 +3,6 @@ package target import ( "debug/gosym" "go/ast" - "time" "github.com/derekparker/delve/pkg/proc" ) @@ -22,23 +21,14 @@ type Info interface { Pid() int Exited() bool Running() bool + BinInfo() *proc.BinaryInfo - BinaryInfo ThreadInfo GoroutineInfo -} -// BinaryInfo is an interface for accessing information on the binary file -// and the contents of binary sections. -type BinaryInfo interface { - LastModified() time.Time - Sources() map[string]*gosym.Obj FindFileLocation(fileName string, lineNumber int) (uint64, error) - FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) - Funcs() []gosym.Func - Types() ([]string, error) - PCToLine(uint64) (string, int, *gosym.Func) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) + FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) } // ThreadInfo is an interface for getting information on active threads @@ -82,3 +72,5 @@ type BreakpointManipulation interface { type VariableEval interface { ConvertEvalScope(gid, frame int) (*proc.EvalScope, error) } + +var _ Interface = &proc.Process{} diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 705a2f1f..601f4e51 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -68,7 +68,7 @@ func New(config *Config) (*Debugger, error) { log.Printf("launching process with args: %v", d.config.ProcessArgs) p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) if err != nil { - if err != proc.NotExecutableErr && err != proc.UnsupportedArchErr { + if err != proc.NotExecutableErr && err != proc.UnsupportedLinuxArchErr && err != proc.UnsupportedWindowsArchErr && err != proc.UnsupportedDarwinArchErr { err = fmt.Errorf("could not launch process: %s", err) } return nil, err @@ -87,7 +87,7 @@ func (d *Debugger) ProcessPid() int { // LastModified returns the time that the process' executable was last // modified. func (d *Debugger) LastModified() time.Time { - return d.target.LastModified() + return d.target.BinInfo().LastModified() } // Detach detaches from the target process. @@ -223,7 +223,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin if runtime.GOOS == "windows" { // Accept fileName which is case-insensitive and slash-insensitive match fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName)) - for symFile := range d.target.Sources() { + for symFile := range d.target.BinInfo().Sources() { if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) { fileName = symFile break @@ -531,7 +531,7 @@ func (d *Debugger) Sources(filter string) ([]string, error) { } files := []string{} - for f := range d.target.Sources() { + for f := range d.target.BinInfo().Sources() { if regex.Match([]byte(f)) { files = append(files, f) } @@ -544,7 +544,7 @@ func (d *Debugger) Functions(filter string) ([]string, error) { d.processMutex.Lock() defer d.processMutex.Unlock() - return regexFilterFuncs(filter, d.target.Funcs()) + return regexFilterFuncs(filter, d.target.BinInfo().Funcs()) } func (d *Debugger) Types(filter string) ([]string, error) { @@ -556,7 +556,7 @@ func (d *Debugger) Types(filter string) ([]string, error) { return nil, fmt.Errorf("invalid filter argument: %s", err.Error()) } - types, err := d.target.Types() + types, err := d.target.BinInfo().Types() if err != nil { return nil, err } @@ -785,7 +785,7 @@ func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Locat locs, err := loc.Find(d, s, locStr) for i := range locs { - file, line, fn := d.target.PCToLine(locs[i].PC) + file, line, fn := d.target.BinInfo().PCToLine(locs[i].PC) locs[i].File = file locs[i].Line = line locs[i].Function = api.ConvertFunction(fn) @@ -800,7 +800,7 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo defer d.processMutex.Unlock() if endPC == 0 { - _, _, fn := d.target.PCToLine(startPC) + _, _, fn := d.target.BinInfo().PCToLine(startPC) if fn == nil { return nil, fmt.Errorf("Address 0x%x does not belong to any function", startPC) } diff --git a/service/debugger/locations.go b/service/debugger/locations.go index ddc55760..ba1e95f8 100644 --- a/service/debugger/locations.go +++ b/service/debugger/locations.go @@ -243,7 +243,7 @@ func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool { } func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) { - funcs := d.target.Funcs() + funcs := d.target.BinInfo().Funcs() matches, err := regexFilterFuncs(loc.FuncRegex, funcs) if err != nil { return nil, err @@ -278,7 +278,7 @@ func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str addr, _ := constant.Uint64Val(v.Value) return []api.Location{{PC: addr}}, nil case reflect.Func: - _, _, fn := d.target.PCToLine(uint64(v.Base)) + _, _, fn := d.target.BinInfo().PCToLine(uint64(v.Base)) pc, err := d.target.FirstPCAfterPrologue(fn, false) if err != nil { return nil, err @@ -327,8 +327,8 @@ func (ale AmbiguousLocationError) Error() string { } func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) { - funcs := d.target.Funcs() - files := d.target.Sources() + funcs := d.target.BinInfo().Funcs() + files := d.target.BinInfo().Sources() candidates := []string{} for file := range files { @@ -390,7 +390,7 @@ func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s if scope == nil { return nil, fmt.Errorf("could not determine current location (scope is nil)") } - file, line, fn := d.target.PCToLine(scope.PC) + file, line, fn := d.target.BinInfo().PCToLine(scope.PC) if fn == nil { return nil, fmt.Errorf("could not determine current location") } @@ -402,7 +402,7 @@ func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str if scope == nil { return nil, fmt.Errorf("could not determine current location (scope is nil)") } - file, _, fn := d.target.PCToLine(scope.PC) + file, _, fn := d.target.BinInfo().PCToLine(scope.PC) if fn == nil { return nil, fmt.Errorf("could not determine current location") }