From b5a06f7aa83f009b083f67ea8bd939bb2e473483 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Fri, 14 Apr 2017 01:19:57 +0200 Subject: [PATCH] proc refactoring: make stack, disassemble and eval independent of proc.Process (#786) * proc: Refactor stackIterator to use memoryReadWriter and BinaryInfo * proc: refactor EvalScope to use memoryReadWriter and BinaryInfo * proc: refactor Disassemble to use memoryReadWriter and BinaryInfo --- pkg/proc/breakpoints.go | 2 +- pkg/proc/disasm.go | 39 ++++++++++++++--------- pkg/proc/disasm_amd64.go | 18 +++++------ pkg/proc/eval.go | 19 +++++++----- pkg/proc/moduledata.go | 32 +++++++++---------- pkg/proc/proc.go | 56 +++++++++++++++++++++------------- pkg/proc/proc_test.go | 18 ++++++----- pkg/proc/stack.go | 45 ++++++++++++++------------- pkg/proc/threads.go | 31 +++++++++++++++---- pkg/proc/types.go | 32 +++++++++---------- pkg/proc/variables.go | 55 ++++++++++++++++----------------- pkg/target/target.go | 25 +++++++++++++++ service/debugger/debugger.go | 22 +++++-------- service/test/variables_test.go | 6 ++-- 14 files changed, 230 insertions(+), 170 deletions(-) diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index 6d89aa92..f98835de 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -130,7 +130,7 @@ func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) { } } } - scope, err := thread.Scope() + scope, err := thread.GoroutineScope() if err != nil { return true, err } diff --git a/pkg/proc/disasm.go b/pkg/proc/disasm.go index 8eae53bd..6abf0e46 100644 --- a/pkg/proc/disasm.go +++ b/pkg/proc/disasm.go @@ -16,14 +16,27 @@ const ( IntelFlavour ) +func (dbp *Process) Disassemble(g *G, startPC, endPC uint64) ([]AsmInstruction, error) { + if g == nil { + regs, _ := dbp.currentThread.Registers(false) + return Disassemble(dbp.currentThread, regs, dbp.breakpoints, &dbp.bi, startPC, endPC) + } + + var regs Registers + thread := dbp.currentThread + if g.thread != nil { + thread = g.thread + regs, _ = g.thread.Registers(false) + } + + return Disassemble(thread, regs, dbp.breakpoints, &dbp.bi, startPC, endPC) +} + // Disassemble disassembles target memory between startPC and endPC // If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble will evaluate the argument of the CALL instruction using the thread's registers // Be aware that the Bytes field of each returned instruction is a slice of a larger array of size endPC - startPC -func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool) ([]AsmInstruction, error) { - if thread.dbp.exited { - return nil, &ProcessExitedError{} - } - mem, err := thread.readMemory(uintptr(startPC), int(endPC-startPC)) +func Disassemble(memrw memoryReadWriter, regs Registers, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, startPC, endPC uint64) ([]AsmInstruction, error) { + mem, err := memrw.readMemory(uintptr(startPC), int(endPC-startPC)) if err != nil { return nil, err } @@ -32,27 +45,23 @@ func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool) pc := startPC var curpc uint64 - var regs Registers - if currentGoroutine { - regs, _ = thread.Registers(false) - if regs != nil { - curpc = regs.PC() - } + if regs != nil { + curpc = regs.PC() } for len(mem) > 0 { - bp, atbp := thread.dbp.breakpoints[pc] + bp, atbp := breakpoints[pc] if atbp { for i := range bp.OriginalData { mem[i] = bp.OriginalData[i] } } - file, line, fn := thread.dbp.bi.PCToLine(pc) + file, line, fn := bi.PCToLine(pc) loc := Location{PC: pc, File: file, Line: line, Fn: fn} inst, err := asmDecode(mem, pc) if err == nil { - atpc := currentGoroutine && (curpc == pc) - destloc := thread.resolveCallArg(inst, atpc, regs) + atpc := (regs != nil) && (curpc == pc) + destloc := resolveCallArg(inst, atpc, regs, memrw, bi) r = append(r, AsmInstruction{Loc: loc, DestLoc: destloc, Bytes: mem[:inst.Len], Breakpoint: atbp, AtPC: atpc, Inst: inst}) pc += uint64(inst.Size()) diff --git a/pkg/proc/disasm_amd64.go b/pkg/proc/disasm_amd64.go index a7619b1f..44d75f01 100644 --- a/pkg/proc/disasm_amd64.go +++ b/pkg/proc/disasm_amd64.go @@ -63,7 +63,7 @@ func (inst *AsmInstruction) IsCall() bool { return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL } -func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs Registers) *Location { +func resolveCallArg(inst *ArchInst, currentGoroutine bool, regs Registers, mem memoryReadWriter, bininfo *BinaryInfo) *Location { if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL { return nil } @@ -89,10 +89,6 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs if arg.Segment != 0 { return nil } - regs, err := thread.Registers(false) - if err != nil { - return nil - } base, err1 := regs.Get(int(arg.Base)) index, err2 := regs.Get(int(arg.Index)) if err1 != nil || err2 != nil { @@ -100,7 +96,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs } addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp) //TODO: should this always be 64 bits instead of inst.MemBytes? - pcbytes, err := thread.readMemory(addr, inst.MemBytes) + pcbytes, err := mem.readMemory(addr, inst.MemBytes) if err != nil { return nil } @@ -109,7 +105,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs return nil } - file, line, fn := thread.dbp.bi.PCToLine(pc) + file, line, fn := bininfo.PCToLine(pc) if fn == nil { return nil } @@ -143,10 +139,14 @@ func init() { } } +func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) { + return FirstPCAfterPrologue(dbp.currentThread, dbp.breakpoints, &dbp.bi, fn, sameline) +} + // FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn // If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry -func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) { - text, err := dbp.CurrentThread().Disassemble(fn.Entry, fn.End, false) +func FirstPCAfterPrologue(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, fn *gosym.Func, sameline bool) (uint64, error) { + text, err := Disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End) if err != nil { return fn.Entry, err } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 33d48624..3a39230e 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -67,7 +67,10 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { // try to interpret the selector as a package variable if maybePkg, ok := node.X.(*ast.Ident); ok { if maybePkg.Name == "runtime" && node.Sel.Name == "curg" { - return scope.Thread.getGVariable() + if scope.gvar == nil { + return nilVariable, nil + } + return scope.gvar.clone(), nil } else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { return v, nil } @@ -105,7 +108,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { return scope.evalBinary(node) case *ast.BasicLit: - return newConstant(constant.MakeFromLiteral(node.Value, node.Kind, 0), scope.Thread), nil + return newConstant(constant.MakeFromLiteral(node.Value, node.Kind, 0), scope.Mem), nil default: return nil, fmt.Errorf("expression %T not implemented", t) @@ -141,7 +144,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { fnnode = p.X } - styp, err := scope.Thread.dbp.bi.findTypeExpr(fnnode) + styp, err := scope.bi.findTypeExpr(fnnode) if err != nil { return nil, err } @@ -149,7 +152,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { converr := fmt.Errorf("can not convert %q to %s", exprToString(node.Args[0]), typ.String()) - v := newVariable("", 0, styp, scope.Thread.dbp, scope.Thread) + v := newVariable("", 0, styp, scope.bi, scope.Mem) v.loaded = true switch ttyp := typ.(type) { @@ -435,7 +438,7 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { switch node.Name { case "true", "false": - return newConstant(constant.MakeBool(node.Name == "true"), scope.Thread), nil + return newConstant(constant.MakeBool(node.Name == "true"), scope.Mem), nil case "nil": return nilVariable, nil } @@ -454,7 +457,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.bi.PCToLine(scope.PC) + _, _, fn := scope.bi.PCToLine(scope.PC) if fn != nil { if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil { v.Name = node.Name @@ -492,7 +495,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.bi.findTypeExpr(node.Type) + typ, err := scope.bi.findTypeExpr(node.Type) if err != nil { return nil, err } @@ -637,7 +640,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.bi.arch.PtrSize()), Name: typename}, Type: xev.DwarfType}) + rv := scope.newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.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 2d92994b..914b4aa6 100644 --- a/pkg/proc/moduledata.go +++ b/pkg/proc/moduledata.go @@ -11,9 +11,9 @@ type moduleData struct { typemapVar *Variable } -func (bi *BinaryInfo) loadModuleData(thread *Thread) (err error) { +func loadModuleData(bi *BinaryInfo, mem memoryReadWriter) (err error) { bi.loadModuleDataOnce.Do(func() { - scope, _ := thread.Scope() + scope := &EvalScope{0, 0, mem, nil, bi} var md *Variable md, err = scope.packageVarAddr("runtime.firstmoduledata") if err != nil { @@ -56,10 +56,9 @@ func (bi *BinaryInfo) loadModuleData(thread *Thread) (err error) { return } -func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thread) (*Variable, error) { - var mem memoryReadWriter = thread +func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem memoryReadWriter) (*Variable, error) { // See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go - if err := bi.loadModuleData(thread); err != nil { + if err := loadModuleData(bi, mem); err != nil { return nil, err } @@ -76,7 +75,7 @@ func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thre } if md == nil { - v, err := bi.reflectOffsMapAccess(off, thread) + v, err := reflectOffsMapAccess(bi, off, mem) if err != nil { return nil, err } @@ -91,23 +90,22 @@ func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thre res := md.types + uintptr(off) - return newVariable("", res, rtyp, thread.dbp, thread), nil + return newVariable("", res, rtyp, bi, mem), nil } -func (bi *BinaryInfo) resolveNameOff(typeAddr uintptr, off uintptr, thread *Thread) (name, tag string, pkgpathoff int32, err error) { - var mem memoryReadWriter = thread +func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem memoryReadWriter) (name, tag string, pkgpathoff int32, err error) { // See runtime.resolveNameOff in $GOROOT/src/runtime/type.go - if err = bi.loadModuleData(thread); err != nil { + if err = loadModuleData(bi, mem); err != nil { return "", "", 0, err } for _, md := range bi.moduleData { if typeAddr >= md.types && typeAddr < md.etypes { - return bi.loadName(md.types+off, mem) + return loadName(bi, md.types+off, mem) } } - v, err := bi.reflectOffsMapAccess(off, thread) + v, err := reflectOffsMapAccess(bi, off, mem) if err != nil { return "", "", 0, err } @@ -117,11 +115,11 @@ func (bi *BinaryInfo) resolveNameOff(typeAddr uintptr, off uintptr, thread *Thre return "", "", 0, resv.Unreadable } - return bi.loadName(resv.Addr, mem) + return loadName(bi, resv.Addr, mem) } -func (bi *BinaryInfo) reflectOffsMapAccess(off uintptr, thread *Thread) (*Variable, error) { - scope, _ := thread.Scope() +func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem memoryReadWriter) (*Variable, error) { + scope := &EvalScope{0, 0, mem, nil, bi} reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") if err != nil { return nil, err @@ -132,7 +130,7 @@ func (bi *BinaryInfo) reflectOffsMapAccess(off uintptr, thread *Thread) (*Variab return nil, err } - return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), thread)) + return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem)) } const ( @@ -142,7 +140,7 @@ const ( nameflagHasPkg = 1 << 2 ) -func (bi *BinaryInfo) loadName(addr uintptr, mem memoryReadWriter) (name, tag string, pkgpathoff int32, err error) { +func loadName(bi *BinaryInfo, addr uintptr, mem memoryReadWriter) (name, tag string, pkgpathoff int32, err error) { off := addr namedata, err := mem.readMemory(off, 3) off += 3 diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 25092aef..f01cce1a 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -179,36 +179,44 @@ func (dbp *Process) LoadInformation(path string) error { return nil } +func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) { + return FindFileLocation(dbp.currentThread, dbp.breakpoints, &dbp.bi, fileName, lineno) +} + // 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.bi.goSymTable.LineToPC(fileName, lineno) +func FindFileLocation(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, fileName string, lineno int) (uint64, error) { + pc, fn, err := bi.goSymTable.LineToPC(fileName, lineno) if err != nil { return 0, err } if fn.Entry == pc { - pc, _ = dbp.FirstPCAfterPrologue(fn, true) + pc, _ = FirstPCAfterPrologue(mem, breakpoints, bi, fn, true) } return pc, nil } +func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) { + return FindFunctionLocation(dbp.currentThread, dbp.breakpoints, &dbp.bi, funcName, firstLine, lineOffset) +} + // FindFunctionLocation finds address of a function's line // If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function // If lineOffset is passed FindFunctionLocation will return the address of that line // Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point // 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.bi.goSymTable.LookupFunc(funcName) +func FindFunctionLocation(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, funcName string, firstLine bool, lineOffset int) (uint64, error) { + origfn := bi.goSymTable.LookupFunc(funcName) if origfn == nil { return 0, fmt.Errorf("Could not find function %s\n", funcName) } if firstLine { - return dbp.FirstPCAfterPrologue(origfn, false) + return FirstPCAfterPrologue(mem, breakpoints, bi, origfn, false) } else if lineOffset > 0 { - filename, lineno, _ := dbp.bi.goSymTable.PCToLine(origfn.Entry) - breakAddr, _, err := dbp.bi.goSymTable.LineToPC(filename, lineno+lineOffset) + filename, lineno, _ := bi.goSymTable.PCToLine(origfn.Entry) + breakAddr, _, err := bi.goSymTable.LineToPC(filename, lineno+lineOffset) return breakAddr, err } @@ -384,7 +392,11 @@ func (dbp *Process) Continue() error { if err != nil { return err } - text, err := dbp.currentThread.Disassemble(pc, pc+maxInstructionLength, true) + regs, err := dbp.currentThread.Registers(false) + if err != nil { + return err + } + text, err := Disassemble(dbp.currentThread, regs, dbp.breakpoints, &dbp.bi, pc, pc+maxInstructionLength) if err != nil { return err } @@ -772,7 +784,8 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e return nil, err } - ver, isextld, err := dbp.getGoInformation() + scope := &EvalScope{0, 0, dbp.currentThread, nil, dbp.BinInfo()} + ver, isextld, err := scope.getGoInformation() if err != nil { return nil, err } @@ -831,12 +844,12 @@ func (dbp *Process) execPtraceFunc(fn func()) { <-dbp.ptraceDoneChan } -func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) { - vv, err := dbp.EvalPackageVariable("runtime.buildVersion", LoadConfig{true, 0, 64, 0, 0}) +func (scope *EvalScope) getGoInformation() (ver GoVersion, isextld bool, err error) { + vv, err := scope.packageVarAddr("runtime.buildVersion") if err != nil { - err = fmt.Errorf("Could not determine version number: %v\n", err) - return + return ver, false, fmt.Errorf("Could not determine version number: %v", err) } + vv.loadValue(LoadConfig{true, 0, 64, 0, 0}) if vv.Unreadable != nil { err = fmt.Errorf("Unreadable version number: %v\n", vv.Unreadable) return @@ -848,7 +861,7 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) return } - rdr := dbp.bi.DwarfReader() + rdr := scope.bi.DwarfReader() rdr.Seek(0) for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() { if err != nil { @@ -892,15 +905,14 @@ func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) { return nil, err } if g == nil { - return dbp.currentThread.Scope() + return dbp.currentThread.ThreadScope() } - var out EvalScope - + var thread *Thread if g.thread == nil { - out.Thread = dbp.currentThread + thread = dbp.currentThread } else { - out.Thread = g.thread + thread = g.thread } locs, err := g.Stacktrace(frame) @@ -912,9 +924,9 @@ func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) { return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid) } - out.PC, out.CFA = locs[frame].Current.PC, locs[frame].CFA + PC, CFA := locs[frame].Current.PC, locs[frame].CFA - return &out, nil + return &EvalScope{PC, CFA, thread, g.variable, dbp.BinInfo()}, nil } func (dbp *Process) postExit() { diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 614fd8ae..1948e63d 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -1011,7 +1011,7 @@ func TestIssue239(t *testing.T) { } func evalVariable(p *Process, symbol string) (*Variable, error) { - scope, err := p.currentThread.Scope() + scope, err := p.currentThread.GoroutineScope() if err != nil { return nil, err } @@ -1019,7 +1019,7 @@ func evalVariable(p *Process, symbol string) (*Variable, error) { } func setVariable(p *Process, symbol, value string) error { - scope, err := p.currentThread.Scope() + scope, err := p.currentThread.GoroutineScope() if err != nil { return err } @@ -1194,7 +1194,7 @@ func TestPointerSetting(t *testing.T) { pval(1) // change p1 to point to i2 - scope, err := p.currentThread.Scope() + scope, err := p.currentThread.GoroutineScope() assertNoError(err, t, "Scope()") i2addr, err := scope.EvalExpression("i2", normalLoadConfig) assertNoError(err, t, "EvalExpression()") @@ -1333,7 +1333,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) { if th.CurrentBreakpoint == nil { continue } - scope, err := th.Scope() + scope, err := th.GoroutineScope() assertNoError(err, t, "Scope()") v, err := scope.EvalVariable("i", normalLoadConfig) assertNoError(err, t, "evalVariable") @@ -1466,7 +1466,7 @@ func TestPointerLoops(t *testing.T) { func BenchmarkLocalVariables(b *testing.B) { withTestProcess("testvariables", b, func(p *Process, fixture protest.Fixture) { assertNoError(p.Continue(), b, "Continue() returned an error") - scope, err := p.currentThread.Scope() + scope, err := p.currentThread.GoroutineScope() assertNoError(err, b, "Scope()") for i := 0; i < b.N; i++ { _, err := scope.LocalVariables(normalLoadConfig) @@ -1692,7 +1692,7 @@ func TestPackageVariables(t *testing.T) { withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { err := p.Continue() assertNoError(err, t, "Continue()") - scope, err := p.currentThread.Scope() + scope, err := p.currentThread.GoroutineScope() assertNoError(err, t, "Scope()") vars, err := scope.PackageVariables(normalLoadConfig) assertNoError(err, t, "PackageVariables()") @@ -2314,7 +2314,9 @@ func TestStepOnCallPtrInstr(t *testing.T) { } pc, err := p.currentThread.PC() assertNoError(err, t, "PC()") - text, err := p.currentThread.Disassemble(pc, pc+maxInstructionLength, true) + regs, err := p.currentThread.Registers(false) + assertNoError(err, t, "Registers()") + text, err := Disassemble(p.currentThread, regs, p.breakpoints, p.BinInfo(), pc, pc+maxInstructionLength) assertNoError(err, t, "Disassemble()") if text[0].IsCall() { found = true @@ -2450,7 +2452,7 @@ func BenchmarkTrace(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { assertNoError(p.Continue(), b, "Continue()") - s, err := p.currentThread.Scope() + s, err := p.currentThread.GoroutineScope() assertNoError(err, b, "Scope()") _, err = s.FunctionArguments(LoadConfig{false, 0, 64, 0, 3}) assertNoError(err, b, "FunctionArguments()") diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index cfcd58c9..fa31261f 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -38,9 +38,9 @@ type Stackframe struct { addrret uint64 } -// Scope returns a new EvalScope using this frame. -func (frame *Stackframe) Scope(thread *Thread) *EvalScope { - return &EvalScope{Thread: thread, PC: frame.Current.PC, CFA: frame.CFA} +// FrameToScope returns a new EvalScope for this frame +func (p *Process) FrameToScope(frame Stackframe) *EvalScope { + return &EvalScope{frame.Current.PC, frame.CFA, p.currentThread, nil, p.BinInfo()} } // ReturnAddress returns the return address of the function @@ -61,7 +61,7 @@ func (t *Thread) stackIterator(stkbar []savedLR, stkbarPos int) (*stackIterator, if err != nil { return nil, err } - return newStackIterator(t.dbp, regs.PC(), regs.SP(), regs.BP(), stkbar, stkbarPos), nil + return newStackIterator(&t.dbp.bi, t, regs.PC(), regs.SP(), regs.BP(), stkbar, stkbarPos), nil } // Stacktrace returns the stack trace for thread. @@ -82,7 +82,7 @@ func (g *G) stackIterator() (*stackIterator, error) { if g.thread != nil { return g.thread.stackIterator(stkbar, g.stkbarPos) } - return newStackIterator(g.dbp, g.PC, g.SP, 0, stkbar, g.stkbarPos), nil + return newStackIterator(g.variable.bi, g.variable.mem, g.PC, g.SP, 0, stkbar, g.stkbarPos), nil } // Stacktrace returns the stack trace for a goroutine. @@ -117,7 +117,8 @@ type stackIterator struct { top bool atend bool frame Stackframe - dbp *Process + bi *BinaryInfo + mem memoryReadWriter err error stackBarrierPC uint64 @@ -129,12 +130,12 @@ type savedLR struct { val uint64 } -func newStackIterator(dbp *Process, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator { - stackBarrierFunc := dbp.bi.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9 +func newStackIterator(bi *BinaryInfo, mem memoryReadWriter, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator { + stackBarrierFunc := 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.bi.goSymTable.PCToFunc(pc) + fn := 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. @@ -149,7 +150,7 @@ func newStackIterator(dbp *Process, pc, sp, bp uint64, stkbar []savedLR, stkbarP } stkbar = stkbar[stkbarPos:] } - return &stackIterator{pc: pc, sp: sp, bp: bp, top: true, dbp: dbp, err: nil, atend: false, stackBarrierPC: stackBarrierPC, stkbar: stkbar} + return &stackIterator{pc: pc, sp: sp, bp: bp, top: true, bi: bi, mem: mem, err: nil, atend: false, stackBarrierPC: stackBarrierPC, stkbar: stkbar} } // Next points the iterator to the next stack frame. @@ -157,7 +158,7 @@ func (it *stackIterator) Next() bool { if it.err != nil || it.atend { return false } - it.frame, it.err = it.dbp.frameInfo(it.pc, it.sp, it.bp, it.top) + it.frame, it.err = it.frameInfo(it.pc, it.sp, it.bp, it.top) if it.err != nil { if _, nofde := it.err.(*frame.NoFDEForPCError); nofde && !it.top { it.frame = Stackframe{Current: Location{PC: it.pc, File: "?", Line: -1}, Call: Location{PC: it.pc, File: "?", Line: -1}, CFA: 0, Ret: 0} @@ -188,7 +189,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.bi.arch.PtrSize())) + it.bp, _ = readUintRaw(it.mem, uintptr(it.bp), int64(it.bi.arch.PtrSize())) return true } @@ -205,37 +206,37 @@ func (it *stackIterator) Err() error { return it.err } -func (dbp *Process) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) { - fde, err := dbp.bi.frameEntries.FDEForPC(pc) +func (it *stackIterator) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) { + fde, err := it.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.bi.arch.PtrSize()) - cfa := int64(retaddr) + int64(dbp.bi.arch.PtrSize()) - return dbp.newStackframe(pc, cfa, retaddr, nil, top) + retaddr := uintptr(int(bp) + it.bi.arch.PtrSize()) + cfa := int64(retaddr) + int64(it.bi.arch.PtrSize()) + return it.newStackframe(pc, cfa, retaddr, nil, top) } spoffset, retoffset := fde.ReturnAddressOffset(pc) cfa := int64(sp) + spoffset retaddr := uintptr(cfa + retoffset) - return dbp.newStackframe(pc, cfa, retaddr, fde, top) + return it.newStackframe(pc, cfa, retaddr, fde, top) } -func (dbp *Process) newStackframe(pc uint64, cfa int64, retaddr uintptr, fde *frame.FrameDescriptionEntry, top bool) (Stackframe, error) { +func (it *stackIterator) newStackframe(pc uint64, cfa int64, retaddr uintptr, fde *frame.FrameDescriptionEntry, top bool) (Stackframe, error) { if retaddr == 0 { return Stackframe{}, NullAddrError{} } - f, l, fn := dbp.bi.PCToLine(pc) - ret, err := readUintRaw(dbp.currentThread, retaddr, int64(dbp.bi.arch.PtrSize())) + f, l, fn := it.bi.PCToLine(pc) + ret, err := readUintRaw(it.mem, retaddr, int64(it.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.bi.PCToLine(pc - 1) + r.Call.File, r.Call.Line, r.Call.Fn = it.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 82833449..ccf1594a 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -168,13 +168,16 @@ func (dbp *Process) next(stepInto bool) error { csource := filepath.Ext(topframe.Current.File) != ".go" thread := dbp.currentThread - currentGoroutine := false + var regs Registers if dbp.selectedGoroutine != nil && dbp.selectedGoroutine.thread != nil { thread = dbp.selectedGoroutine.thread - currentGoroutine = true + regs, err = thread.Registers(false) + if err != nil { + return err + } } - text, err := thread.Disassemble(topframe.FDE.Begin(), topframe.FDE.End(), currentGoroutine) + text, err := Disassemble(thread, regs, dbp.breakpoints, &dbp.bi, topframe.FDE.Begin(), topframe.FDE.End()) if err != nil && stepInto { return err } @@ -429,8 +432,8 @@ func (thread *Thread) Halt() (err error) { return } -// Scope returns the current EvalScope for this thread. -func (thread *Thread) Scope() (*EvalScope, error) { +// ThreadScope returns an EvalScope for this thread. +func (thread *Thread) ThreadScope() (*EvalScope, error) { locations, err := thread.Stacktrace(0) if err != nil { return nil, err @@ -438,7 +441,23 @@ func (thread *Thread) Scope() (*EvalScope, error) { if len(locations) < 1 { return nil, errors.New("could not decode first frame") } - return locations[0].Scope(thread), nil + return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.dbp.BinInfo()}, nil +} + +// GoroutineScope returns an EvalScope for the goroutine running on this thread. +func (thread *Thread) GoroutineScope() (*EvalScope, error) { + locations, err := thread.Stacktrace(0) + if err != nil { + return nil, err + } + if len(locations) < 1 { + return nil, errors.New("could not decode first frame") + } + gvar, err := thread.getGVariable() + if err != nil { + return nil, err + } + return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.dbp.BinInfo()}, nil } // SetCurrentBreakpoint sets the current breakpoint that this diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 1badada0..3fe7fc3b 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -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.bi.nameOfRuntimeType[_type.Addr]; ok { + if e, ok := _type.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.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename, kind} + _type.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.bi.resolveNameOff(_type.Addr, uintptr(strOff), _type.dbp.currentThread) + typename, _, _, err = resolveNameOff(_type.bi, _type.Addr, uintptr(strOff), _type.mem) 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.bi.resolveNameOff(_type.Addr, uintptr(pkgPathOff), _type.dbp.currentThread) + pkgPath, _, _, err := resolveNameOff(_type.bi, _type.Addr, uintptr(pkgPathOff), _type.mem) 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.bi.findType("runtime._type") + rtyp, err := _type.bi.findType("runtime._type") if err != nil { return "", err } - prtyp := pointerTo(rtyp, _type.dbp.bi.arch) + prtyp := pointerTo(rtyp, _type.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.bi.arch.PtrSize()) + cursortyp.Addr += uintptr(_type.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.bi.arch.PtrSize()) + cursortyp.Addr += uintptr(_type.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.bi.resolveNameOff(_type.Addr, uintptr(nameoff), _type.dbp.currentThread) + methodname, _, _, err = resolveNameOff(_type.bi, _type.Addr, uintptr(nameoff), _type.mem) if err != nil { return "", err } case "typ": typeoff, _ := constant.Int64Val(im.Children[i].Value) - typ, err := _type.dbp.bi.resolveTypeOff(_type.Addr, uintptr(typeoff), _type.dbp.currentThread) + typ, err := resolveTypeOff(_type.bi, _type.Addr, uintptr(typeoff), _type.mem) 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.bi.loadName(uintptr(nameoff), _type.mem) + fieldname, _, _, err = loadName(_type.bi, 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.bi.findType("runtime._type") + rtyp, err := _type.bi.findType("runtime._type") if err != nil { return nil, err } - prtyp := pointerTo(rtyp, _type.dbp.bi.arch) + prtyp := pointerTo(rtyp, _type.bi.arch) - uintptrtyp, err := _type.dbp.bi.findType("uintptr") + uintptrtyp, err := _type.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", pointerTo(elemtype, _type.dbp.bi.arch), 0) + appendField(r, "array", pointerTo(elemtype, _type.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.bi.findType("runtime.uncommontype") + typ, err := _type.bi.findType("runtime.uncommontype") if err != nil { return nil } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 7be24beb..b9d208a0 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -51,7 +51,7 @@ type Variable struct { RealType dwarf.Type Kind reflect.Kind mem memoryReadWriter - dbp *Process + bi *BinaryInfo Value constant.Value FloatSpecial FloatSpecial @@ -132,15 +132,16 @@ type G struct { thread *Thread variable *Variable - dbp *Process } // EvalScope is the scope for variable evaluation. Contains the thread, // current location (PC), and canonical frame address. type EvalScope struct { - Thread *Thread - PC uint64 - CFA int64 + PC uint64 // Current instruction of the evaluation frame + CFA int64 // Stack address of the evaluation frame + Mem memoryReadWriter // Target's memory + gvar *Variable + bi *BinaryInfo } // IsNilErr is returned when a variable is nil. @@ -153,24 +154,24 @@ func (err *IsNilErr) Error() string { } func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { - return newVariable(name, addr, dwarfType, scope.Thread.dbp, scope.Thread) + return newVariable(name, addr, dwarfType, scope.bi, scope.Mem) } func (t *Thread) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { - return newVariable(name, addr, dwarfType, t.dbp, t) + return newVariable(name, addr, dwarfType, t.dbp.BinInfo(), t) } func (v *Variable) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { - return newVariable(name, addr, dwarfType, v.dbp, v.mem) + return newVariable(name, addr, dwarfType, v.bi, v.mem) } -func newVariable(name string, addr uintptr, dwarfType dwarf.Type, dbp *Process, mem memoryReadWriter) *Variable { +func newVariable(name string, addr uintptr, dwarfType dwarf.Type, bi *BinaryInfo, mem memoryReadWriter) *Variable { v := &Variable{ Name: name, Addr: addr, DwarfType: dwarfType, mem: mem, - dbp: dbp, + bi: bi, } v.RealType = resolveTypedef(v.DwarfType) @@ -190,7 +191,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.bi.arch, v.Addr) + v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.bi.arch, v.Addr) } case *dwarf.SliceType: v.Kind = reflect.Slice @@ -321,17 +322,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.bi.DwarfReader() + return scope.bi.DwarfReader() } // Type returns the Dwarf type entry at `offset`. func (scope *EvalScope) Type(offset dwarf.Offset) (dwarf.Type, error) { - return scope.Thread.dbp.bi.dwarf.Type(offset) + return scope.bi.dwarf.Type(offset) } // PtrSize returns the size of a pointer. func (scope *EvalScope) PtrSize() int { - return scope.Thread.dbp.bi.arch.PtrSize() + return scope.bi.arch.PtrSize() } // ChanRecvBlocked returns whether the goroutine is blocked on @@ -362,12 +363,11 @@ func (ng NoGError) Error() string { func (gvar *Variable) parseG() (*G, error) { mem := gvar.mem - dbp := gvar.dbp gaddr := uint64(gvar.Addr) _, deref := gvar.RealType.(*dwarf.PtrType) if deref { - gaddrbytes, err := mem.readMemory(uintptr(gaddr), dbp.bi.arch.PtrSize()) + gaddrbytes, err := mem.readMemory(uintptr(gaddr), gvar.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.bi.goSymTable.PCToLine(uint64(pc)) + f, l, fn := gvar.bi.PCToLine(uint64(pc)) g := &G{ ID: int(id), GoPC: uint64(gopc), @@ -417,7 +417,6 @@ func (gvar *Variable) parseG() (*G, error) { variable: gvar, stkbarVar: stkbarVar, stkbarPos: int(stkbarPos), - dbp: gvar.dbp, } return g, nil } @@ -498,7 +497,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.bi.goSymTable.PCToLine(g.GoPC) + f, l, fn := g.variable.bi.goSymTable.PCToLine(g.GoPC) return Location{PC: g.GoPC, File: f, Line: l, Fn: fn} } @@ -586,7 +585,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.bi.findFunctionDebugInfo(scope.PC) + off, err := scope.bi.findFunctionDebugInfo(scope.PC) if err != nil { return nil, err } @@ -658,7 +657,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { // EvalPackageVariable will evaluate the package level variable // specified by 'name'. func (dbp *Process) EvalPackageVariable(name string, cfg LoadConfig) (*Variable, error) { - scope := &EvalScope{Thread: dbp.currentThread, PC: 0, CFA: 0} + scope := &EvalScope{0, 0, dbp.currentThread, nil, dbp.BinInfo()} v, err := scope.packageVarAddr(name) if err != nil { @@ -1207,7 +1206,7 @@ func (v *Variable) writeBool(value bool) error { } func (v *Variable) readFunctionPtr() { - val, err := v.mem.readMemory(v.Addr, v.dbp.bi.arch.PtrSize()) + val, err := v.mem.readMemory(v.Addr, v.bi.arch.PtrSize()) if err != nil { v.Unreadable = err return @@ -1221,14 +1220,14 @@ func (v *Variable) readFunctionPtr() { return } - val, err = v.mem.readMemory(fnaddr, v.dbp.bi.arch.PtrSize()) + val, err = v.mem.readMemory(fnaddr, v.bi.arch.PtrSize()) if err != nil { v.Unreadable = err return } v.Base = uintptr(binary.LittleEndian.Uint64(val)) - fn := v.dbp.bi.goSymTable.PCToFunc(uint64(v.Base)) + fn := v.bi.goSymTable.PCToFunc(uint64(v.Base)) if fn == nil { v.Unreadable = fmt.Errorf("could not find function for %#v", v.Base) return @@ -1603,7 +1602,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig return } - typ, err = v.dbp.bi.findType(typename) + typ, err = v.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 +1626,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig return } - typ, err = v.dbp.bi.findTypeExpr(t) + typ, err = v.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 +1636,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 = pointerTo(typ, v.dbp.bi.arch) + typ = pointerTo(typ, v.bi.arch) } } @@ -1655,7 +1654,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.bi.findFunctionDebugInfo(scope.PC) + off, err := scope.bi.findFunctionDebugInfo(scope.PC) if err != nil { return nil, err } diff --git a/pkg/target/target.go b/pkg/target/target.go index f6ba21f7..157d1795 100644 --- a/pkg/target/target.go +++ b/pkg/target/target.go @@ -26,8 +26,32 @@ type Info interface { ThreadInfo GoroutineInfo + // Disassemble disassembles target memory between startPC and endPC, marking + // the current instruction being executed in goroutine g. + Disassemble(g *proc.G, startPC, endPC uint64) ([]proc.AsmInstruction, error) + + // FindFileLocation returns the address of the first instruction belonging + // to line lineNumber in file fileName. FindFileLocation(fileName string, lineNumber int) (uint64, error) + + // FirstPCAfterPrologue returns the first instruction address after fn's + // prologue. + // If sameline is true and the first instruction after the prologue belongs + // to a different source line the entry point will be returned instead. FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) + + // FindFunctionLocation finds address of a function's line + // + // If firstLine == true is passed FindFunctionLocation will attempt to find + // the first line of the function. + // + // If lineOffset is passed FindFunctionLocation will return the address of + // that line. + // + // Pass lineOffset == 0 and firstLine == false if you want the address for + // the function's entry point. Note that setting breakpoints at that + // address will cause surprising behavior: + // https://github.com/derekparker/delve/issues/170 FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) } @@ -70,6 +94,7 @@ type BreakpointManipulation interface { // VariableEval is an interface for dealing with eval scopes. type VariableEval interface { + FrameToScope(proc.Stackframe) *proc.EvalScope ConvertEvalScope(gid, frame int) (*proc.EvalScope, error) } diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 601f4e51..90d97b4c 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -490,7 +490,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error } } - s, err := d.target.Threads()[state.Threads[i].ID].Scope() + s, err := d.target.Threads()[state.Threads[i].ID].GoroutineScope() if err != nil { return err } @@ -602,7 +602,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadCo if !found { return nil, fmt.Errorf("couldn't find thread %d", threadID) } - scope, err := thread.Scope() + scope, err := thread.ThreadScope() if err != nil { return nil, err } @@ -752,7 +752,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)} if cfg != nil && rawlocs[i].Current.Fn != nil { var err error - scope := rawlocs[i].Scope(d.target.CurrentThread()) + scope := d.target.FrameToScope(rawlocs[i]) locals, err := scope.LocalVariables(*cfg) if err != nil { return nil, err @@ -808,20 +808,12 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo endPC = fn.End } - currentGoroutine := true - thread := d.target.CurrentThread() - - if s, err := d.target.ConvertEvalScope(scope.GoroutineID, scope.Frame); err == nil { - thread = s.Thread - if scope.GoroutineID != -1 { - g, _ := s.Thread.GetG() - if g == nil || g.ID != scope.GoroutineID { - currentGoroutine = false - } - } + g, err := d.target.FindGoroutine(scope.GoroutineID) + if err != nil { + return nil, err } - insts, err := thread.Disassemble(startPC, endPC, currentGoroutine) + insts, err := d.target.Disassemble(g, startPC, endPC) if err != nil { return nil, err } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index ff1eb290..a704559b 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -54,7 +54,7 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { } func evalVariable(p *proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { - scope, err := p.CurrentThread().Scope() + scope, err := p.CurrentThread().GoroutineScope() if err != nil { return nil, err } @@ -68,7 +68,7 @@ func (tc *varTest) alternateVarTest() varTest { } func setVariable(p *proc.Process, symbol, value string) error { - scope, err := p.CurrentThread().Scope() + scope, err := p.CurrentThread().GoroutineScope() if err != nil { return err } @@ -348,7 +348,7 @@ func TestLocalVariables(t *testing.T) { assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { - scope, err := p.CurrentThread().Scope() + scope, err := p.CurrentThread().GoroutineScope() assertNoError(err, t, "AsScope()") vars, err := tc.fn(scope, pnormalLoadConfig) assertNoError(err, t, "LocalVariables() returned an error")