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
This commit is contained in:
Alessandro Arzilli 2017-04-14 01:19:57 +02:00 committed by Derek Parker
parent f605716160
commit b5a06f7aa8
14 changed files with 230 additions and 170 deletions

@ -130,7 +130,7 @@ func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) {
} }
} }
} }
scope, err := thread.Scope() scope, err := thread.GoroutineScope()
if err != nil { if err != nil {
return true, err return true, err
} }

@ -16,14 +16,27 @@ const (
IntelFlavour 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 // 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 // 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 // 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) { func Disassemble(memrw memoryReadWriter, regs Registers, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, startPC, endPC uint64) ([]AsmInstruction, error) {
if thread.dbp.exited { mem, err := memrw.readMemory(uintptr(startPC), int(endPC-startPC))
return nil, &ProcessExitedError{}
}
mem, err := thread.readMemory(uintptr(startPC), int(endPC-startPC))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -32,27 +45,23 @@ func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool)
pc := startPC pc := startPC
var curpc uint64 var curpc uint64
var regs Registers if regs != nil {
if currentGoroutine { curpc = regs.PC()
regs, _ = thread.Registers(false)
if regs != nil {
curpc = regs.PC()
}
} }
for len(mem) > 0 { for len(mem) > 0 {
bp, atbp := thread.dbp.breakpoints[pc] bp, atbp := breakpoints[pc]
if atbp { if atbp {
for i := range bp.OriginalData { for i := range bp.OriginalData {
mem[i] = bp.OriginalData[i] 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} loc := Location{PC: pc, File: file, Line: line, Fn: fn}
inst, err := asmDecode(mem, pc) inst, err := asmDecode(mem, pc)
if err == nil { if err == nil {
atpc := currentGoroutine && (curpc == pc) atpc := (regs != nil) && (curpc == pc)
destloc := thread.resolveCallArg(inst, atpc, regs) 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}) r = append(r, AsmInstruction{Loc: loc, DestLoc: destloc, Bytes: mem[:inst.Len], Breakpoint: atbp, AtPC: atpc, Inst: inst})
pc += uint64(inst.Size()) pc += uint64(inst.Size())

@ -63,7 +63,7 @@ func (inst *AsmInstruction) IsCall() bool {
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL 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 { if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
return nil return nil
} }
@ -89,10 +89,6 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
if arg.Segment != 0 { if arg.Segment != 0 {
return nil return nil
} }
regs, err := thread.Registers(false)
if err != nil {
return nil
}
base, err1 := regs.Get(int(arg.Base)) base, err1 := regs.Get(int(arg.Base))
index, err2 := regs.Get(int(arg.Index)) index, err2 := regs.Get(int(arg.Index))
if err1 != nil || err2 != nil { 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) addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
//TODO: should this always be 64 bits instead of inst.MemBytes? //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 { if err != nil {
return nil return nil
} }
@ -109,7 +105,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
return nil return nil
} }
file, line, fn := thread.dbp.bi.PCToLine(pc) file, line, fn := bininfo.PCToLine(pc)
if fn == nil { if fn == nil {
return 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 // 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 // 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) { func FirstPCAfterPrologue(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, fn *gosym.Func, sameline bool) (uint64, error) {
text, err := dbp.CurrentThread().Disassemble(fn.Entry, fn.End, false) text, err := Disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End)
if err != nil { if err != nil {
return fn.Entry, err return fn.Entry, err
} }

@ -67,7 +67,10 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
// try to interpret the selector as a package variable // try to interpret the selector as a package variable
if maybePkg, ok := node.X.(*ast.Ident); ok { if maybePkg, ok := node.X.(*ast.Ident); ok {
if maybePkg.Name == "runtime" && node.Sel.Name == "curg" { 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 { } else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil {
return v, nil return v, nil
} }
@ -105,7 +108,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
return scope.evalBinary(node) return scope.evalBinary(node)
case *ast.BasicLit: 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: default:
return nil, fmt.Errorf("expression %T not implemented", t) 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 fnnode = p.X
} }
styp, err := scope.Thread.dbp.bi.findTypeExpr(fnnode) styp, err := scope.bi.findTypeExpr(fnnode)
if err != nil { if err != nil {
return nil, err 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()) 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 v.loaded = true
switch ttyp := typ.(type) { 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) { func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
switch node.Name { switch node.Name {
case "true", "false": 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": case "nil":
return nilVariable, nil return nilVariable, nil
} }
@ -454,7 +457,7 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
return v, nil return v, nil
} }
// if it's not a local variable then it could be a package variable w/o explicit package name // 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 fn != nil {
if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil { if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil {
v.Name = node.Name v.Name = node.Name
@ -492,7 +495,7 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err
if xv.Children[0].Addr == 0 { if xv.Children[0].Addr == 0 {
return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type)) 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 { if err != nil {
return nil, err return nil, err
} }
@ -637,7 +640,7 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) {
xev.OnlyAddr = true xev.OnlyAddr = true
typename := "*" + xev.DwarfType.Common().Name 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.Children = []Variable{*xev}
rv.loaded = true rv.loaded = true

@ -11,9 +11,9 @@ type moduleData struct {
typemapVar *Variable typemapVar *Variable
} }
func (bi *BinaryInfo) loadModuleData(thread *Thread) (err error) { func loadModuleData(bi *BinaryInfo, mem memoryReadWriter) (err error) {
bi.loadModuleDataOnce.Do(func() { bi.loadModuleDataOnce.Do(func() {
scope, _ := thread.Scope() scope := &EvalScope{0, 0, mem, nil, bi}
var md *Variable var md *Variable
md, err = scope.packageVarAddr("runtime.firstmoduledata") md, err = scope.packageVarAddr("runtime.firstmoduledata")
if err != nil { if err != nil {
@ -56,10 +56,9 @@ func (bi *BinaryInfo) loadModuleData(thread *Thread) (err error) {
return return
} }
func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thread) (*Variable, error) { func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem memoryReadWriter) (*Variable, error) {
var mem memoryReadWriter = thread
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go // 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 return nil, err
} }
@ -76,7 +75,7 @@ func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thre
} }
if md == nil { if md == nil {
v, err := bi.reflectOffsMapAccess(off, thread) v, err := reflectOffsMapAccess(bi, off, mem)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,23 +90,22 @@ func (bi *BinaryInfo) resolveTypeOff(typeAddr uintptr, off uintptr, thread *Thre
res := md.types + uintptr(off) 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) { func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem memoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
var mem memoryReadWriter = thread
// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go // 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 return "", "", 0, err
} }
for _, md := range bi.moduleData { for _, md := range bi.moduleData {
if typeAddr >= md.types && typeAddr < md.etypes { 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 { if err != nil {
return "", "", 0, err return "", "", 0, err
} }
@ -117,11 +115,11 @@ func (bi *BinaryInfo) resolveNameOff(typeAddr uintptr, off uintptr, thread *Thre
return "", "", 0, resv.Unreadable 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) { func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem memoryReadWriter) (*Variable, error) {
scope, _ := thread.Scope() scope := &EvalScope{0, 0, mem, nil, bi}
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
if err != nil { if err != nil {
return nil, err return nil, err
@ -132,7 +130,7 @@ func (bi *BinaryInfo) reflectOffsMapAccess(off uintptr, thread *Thread) (*Variab
return nil, err return nil, err
} }
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), thread)) return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem))
} }
const ( const (
@ -142,7 +140,7 @@ const (
nameflagHasPkg = 1 << 2 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 off := addr
namedata, err := mem.readMemory(off, 3) namedata, err := mem.readMemory(off, 3)
off += 3 off += 3

@ -179,36 +179,44 @@ func (dbp *Process) LoadInformation(path string) error {
return nil 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. // FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normailzed to lower case and '/' on Windows. // Assumes that `file` is normailzed to lower case and '/' on Windows.
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) { func FindFileLocation(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, fileName string, lineno int) (uint64, error) {
pc, fn, err := dbp.bi.goSymTable.LineToPC(fileName, lineno) pc, fn, err := bi.goSymTable.LineToPC(fileName, lineno)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if fn.Entry == pc { if fn.Entry == pc {
pc, _ = dbp.FirstPCAfterPrologue(fn, true) pc, _ = FirstPCAfterPrologue(mem, breakpoints, bi, fn, true)
} }
return pc, nil 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 // 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 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 // 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 // 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: // Note that setting breakpoints at that address will cause surprising behavior:
// https://github.com/derekparker/delve/issues/170 // https://github.com/derekparker/delve/issues/170
func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) { func FindFunctionLocation(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, funcName string, firstLine bool, lineOffset int) (uint64, error) {
origfn := dbp.bi.goSymTable.LookupFunc(funcName) origfn := bi.goSymTable.LookupFunc(funcName)
if origfn == nil { if origfn == nil {
return 0, fmt.Errorf("Could not find function %s\n", funcName) return 0, fmt.Errorf("Could not find function %s\n", funcName)
} }
if firstLine { if firstLine {
return dbp.FirstPCAfterPrologue(origfn, false) return FirstPCAfterPrologue(mem, breakpoints, bi, origfn, false)
} else if lineOffset > 0 { } else if lineOffset > 0 {
filename, lineno, _ := dbp.bi.goSymTable.PCToLine(origfn.Entry) filename, lineno, _ := bi.goSymTable.PCToLine(origfn.Entry)
breakAddr, _, err := dbp.bi.goSymTable.LineToPC(filename, lineno+lineOffset) breakAddr, _, err := bi.goSymTable.LineToPC(filename, lineno+lineOffset)
return breakAddr, err return breakAddr, err
} }
@ -384,7 +392,11 @@ func (dbp *Process) Continue() error {
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -772,7 +784,8 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -831,12 +844,12 @@ func (dbp *Process) execPtraceFunc(fn func()) {
<-dbp.ptraceDoneChan <-dbp.ptraceDoneChan
} }
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) { func (scope *EvalScope) getGoInformation() (ver GoVersion, isextld bool, err error) {
vv, err := dbp.EvalPackageVariable("runtime.buildVersion", LoadConfig{true, 0, 64, 0, 0}) vv, err := scope.packageVarAddr("runtime.buildVersion")
if err != nil { if err != nil {
err = fmt.Errorf("Could not determine version number: %v\n", err) return ver, false, fmt.Errorf("Could not determine version number: %v", err)
return
} }
vv.loadValue(LoadConfig{true, 0, 64, 0, 0})
if vv.Unreadable != nil { if vv.Unreadable != nil {
err = fmt.Errorf("Unreadable version number: %v\n", vv.Unreadable) err = fmt.Errorf("Unreadable version number: %v\n", vv.Unreadable)
return return
@ -848,7 +861,7 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error)
return return
} }
rdr := dbp.bi.DwarfReader() rdr := scope.bi.DwarfReader()
rdr.Seek(0) rdr.Seek(0)
for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() { for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() {
if err != nil { if err != nil {
@ -892,15 +905,14 @@ func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) {
return nil, err return nil, err
} }
if g == nil { if g == nil {
return dbp.currentThread.Scope() return dbp.currentThread.ThreadScope()
} }
var out EvalScope var thread *Thread
if g.thread == nil { if g.thread == nil {
out.Thread = dbp.currentThread thread = dbp.currentThread
} else { } else {
out.Thread = g.thread thread = g.thread
} }
locs, err := g.Stacktrace(frame) 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) 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() { func (dbp *Process) postExit() {

@ -1011,7 +1011,7 @@ func TestIssue239(t *testing.T) {
} }
func evalVariable(p *Process, symbol string) (*Variable, error) { func evalVariable(p *Process, symbol string) (*Variable, error) {
scope, err := p.currentThread.Scope() scope, err := p.currentThread.GoroutineScope()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1019,7 +1019,7 @@ func evalVariable(p *Process, symbol string) (*Variable, error) {
} }
func setVariable(p *Process, symbol, value string) error { func setVariable(p *Process, symbol, value string) error {
scope, err := p.currentThread.Scope() scope, err := p.currentThread.GoroutineScope()
if err != nil { if err != nil {
return err return err
} }
@ -1194,7 +1194,7 @@ func TestPointerSetting(t *testing.T) {
pval(1) pval(1)
// change p1 to point to i2 // change p1 to point to i2
scope, err := p.currentThread.Scope() scope, err := p.currentThread.GoroutineScope()
assertNoError(err, t, "Scope()") assertNoError(err, t, "Scope()")
i2addr, err := scope.EvalExpression("i2", normalLoadConfig) i2addr, err := scope.EvalExpression("i2", normalLoadConfig)
assertNoError(err, t, "EvalExpression()") assertNoError(err, t, "EvalExpression()")
@ -1333,7 +1333,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
if th.CurrentBreakpoint == nil { if th.CurrentBreakpoint == nil {
continue continue
} }
scope, err := th.Scope() scope, err := th.GoroutineScope()
assertNoError(err, t, "Scope()") assertNoError(err, t, "Scope()")
v, err := scope.EvalVariable("i", normalLoadConfig) v, err := scope.EvalVariable("i", normalLoadConfig)
assertNoError(err, t, "evalVariable") assertNoError(err, t, "evalVariable")
@ -1466,7 +1466,7 @@ func TestPointerLoops(t *testing.T) {
func BenchmarkLocalVariables(b *testing.B) { func BenchmarkLocalVariables(b *testing.B) {
withTestProcess("testvariables", b, func(p *Process, fixture protest.Fixture) { withTestProcess("testvariables", b, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), b, "Continue() returned an error") assertNoError(p.Continue(), b, "Continue() returned an error")
scope, err := p.currentThread.Scope() scope, err := p.currentThread.GoroutineScope()
assertNoError(err, b, "Scope()") assertNoError(err, b, "Scope()")
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := scope.LocalVariables(normalLoadConfig) _, err := scope.LocalVariables(normalLoadConfig)
@ -1692,7 +1692,7 @@ func TestPackageVariables(t *testing.T) {
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
err := p.Continue() err := p.Continue()
assertNoError(err, t, "Continue()") assertNoError(err, t, "Continue()")
scope, err := p.currentThread.Scope() scope, err := p.currentThread.GoroutineScope()
assertNoError(err, t, "Scope()") assertNoError(err, t, "Scope()")
vars, err := scope.PackageVariables(normalLoadConfig) vars, err := scope.PackageVariables(normalLoadConfig)
assertNoError(err, t, "PackageVariables()") assertNoError(err, t, "PackageVariables()")
@ -2314,7 +2314,9 @@ func TestStepOnCallPtrInstr(t *testing.T) {
} }
pc, err := p.currentThread.PC() pc, err := p.currentThread.PC()
assertNoError(err, t, "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()") assertNoError(err, t, "Disassemble()")
if text[0].IsCall() { if text[0].IsCall() {
found = true found = true
@ -2450,7 +2452,7 @@ func BenchmarkTrace(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
assertNoError(p.Continue(), b, "Continue()") assertNoError(p.Continue(), b, "Continue()")
s, err := p.currentThread.Scope() s, err := p.currentThread.GoroutineScope()
assertNoError(err, b, "Scope()") assertNoError(err, b, "Scope()")
_, err = s.FunctionArguments(LoadConfig{false, 0, 64, 0, 3}) _, err = s.FunctionArguments(LoadConfig{false, 0, 64, 0, 3})
assertNoError(err, b, "FunctionArguments()") assertNoError(err, b, "FunctionArguments()")

@ -38,9 +38,9 @@ type Stackframe struct {
addrret uint64 addrret uint64
} }
// Scope returns a new EvalScope using this frame. // FrameToScope returns a new EvalScope for this frame
func (frame *Stackframe) Scope(thread *Thread) *EvalScope { func (p *Process) FrameToScope(frame Stackframe) *EvalScope {
return &EvalScope{Thread: thread, PC: frame.Current.PC, CFA: frame.CFA} return &EvalScope{frame.Current.PC, frame.CFA, p.currentThread, nil, p.BinInfo()}
} }
// ReturnAddress returns the return address of the function // ReturnAddress returns the return address of the function
@ -61,7 +61,7 @@ func (t *Thread) stackIterator(stkbar []savedLR, stkbarPos int) (*stackIterator,
if err != nil { if err != nil {
return nil, err 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. // Stacktrace returns the stack trace for thread.
@ -82,7 +82,7 @@ func (g *G) stackIterator() (*stackIterator, error) {
if g.thread != nil { if g.thread != nil {
return g.thread.stackIterator(stkbar, g.stkbarPos) 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. // Stacktrace returns the stack trace for a goroutine.
@ -117,7 +117,8 @@ type stackIterator struct {
top bool top bool
atend bool atend bool
frame Stackframe frame Stackframe
dbp *Process bi *BinaryInfo
mem memoryReadWriter
err error err error
stackBarrierPC uint64 stackBarrierPC uint64
@ -129,12 +130,12 @@ type savedLR struct {
val uint64 val uint64
} }
func newStackIterator(dbp *Process, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator { func newStackIterator(bi *BinaryInfo, mem memoryReadWriter, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
stackBarrierFunc := dbp.bi.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9 stackBarrierFunc := bi.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9
var stackBarrierPC uint64 var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil { if stackBarrierFunc != nil && stkbar != nil {
stackBarrierPC = stackBarrierFunc.Entry stackBarrierPC = stackBarrierFunc.Entry
fn := dbp.bi.goSymTable.PCToFunc(pc) fn := bi.goSymTable.PCToFunc(pc)
if fn != nil && fn.Name == runtimeStackBarrier { if fn != nil && fn.Name == runtimeStackBarrier {
// We caught the goroutine as it's executing the stack barrier, we must // 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. // 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:] 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. // Next points the iterator to the next stack frame.
@ -157,7 +158,7 @@ func (it *stackIterator) Next() bool {
if it.err != nil || it.atend { if it.err != nil || it.atend {
return false 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 it.err != nil {
if _, nofde := it.err.(*frame.NoFDEForPCError); nofde && !it.top { 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} 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.top = false
it.pc = it.frame.Ret it.pc = it.frame.Ret
it.sp = uint64(it.frame.CFA) 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 return true
} }
@ -205,37 +206,37 @@ func (it *stackIterator) Err() error {
return it.err return it.err
} }
func (dbp *Process) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) { func (it *stackIterator) frameInfo(pc, sp, bp uint64, top bool) (Stackframe, error) {
fde, err := dbp.bi.frameEntries.FDEForPC(pc) fde, err := it.bi.frameEntries.FDEForPC(pc)
if _, nofde := err.(*frame.NoFDEForPCError); nofde { if _, nofde := err.(*frame.NoFDEForPCError); nofde {
if bp == 0 { if bp == 0 {
return Stackframe{}, err return Stackframe{}, err
} }
// When no FDE is available attempt to use BP instead // When no FDE is available attempt to use BP instead
retaddr := uintptr(int(bp) + dbp.bi.arch.PtrSize()) retaddr := uintptr(int(bp) + it.bi.arch.PtrSize())
cfa := int64(retaddr) + int64(dbp.bi.arch.PtrSize()) cfa := int64(retaddr) + int64(it.bi.arch.PtrSize())
return dbp.newStackframe(pc, cfa, retaddr, nil, top) return it.newStackframe(pc, cfa, retaddr, nil, top)
} }
spoffset, retoffset := fde.ReturnAddressOffset(pc) spoffset, retoffset := fde.ReturnAddressOffset(pc)
cfa := int64(sp) + spoffset cfa := int64(sp) + spoffset
retaddr := uintptr(cfa + retoffset) 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 { if retaddr == 0 {
return Stackframe{}, NullAddrError{} return Stackframe{}, NullAddrError{}
} }
f, l, fn := dbp.bi.PCToLine(pc) f, l, fn := it.bi.PCToLine(pc)
ret, err := readUintRaw(dbp.currentThread, retaddr, int64(dbp.bi.arch.PtrSize())) ret, err := readUintRaw(it.mem, retaddr, int64(it.bi.arch.PtrSize()))
if err != nil { if err != nil {
return Stackframe{}, err return Stackframe{}, err
} }
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr)} r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr)}
if !top { 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 r.Call.PC = r.Current.PC
} else { } else {
r.Call = r.Current r.Call = r.Current

@ -168,13 +168,16 @@ func (dbp *Process) next(stepInto bool) error {
csource := filepath.Ext(topframe.Current.File) != ".go" csource := filepath.Ext(topframe.Current.File) != ".go"
thread := dbp.currentThread thread := dbp.currentThread
currentGoroutine := false var regs Registers
if dbp.selectedGoroutine != nil && dbp.selectedGoroutine.thread != nil { if dbp.selectedGoroutine != nil && dbp.selectedGoroutine.thread != nil {
thread = dbp.selectedGoroutine.thread 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 { if err != nil && stepInto {
return err return err
} }
@ -429,8 +432,8 @@ func (thread *Thread) Halt() (err error) {
return return
} }
// Scope returns the current EvalScope for this thread. // ThreadScope returns an EvalScope for this thread.
func (thread *Thread) Scope() (*EvalScope, error) { func (thread *Thread) ThreadScope() (*EvalScope, error) {
locations, err := thread.Stacktrace(0) locations, err := thread.Stacktrace(0)
if err != nil { if err != nil {
return nil, err return nil, err
@ -438,7 +441,23 @@ func (thread *Thread) Scope() (*EvalScope, error) {
if len(locations) < 1 { if len(locations) < 1 {
return nil, errors.New("could not decode first frame") 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 // SetCurrentBreakpoint sets the current breakpoint that this

@ -221,7 +221,7 @@ type nameOfRuntimeTypeEntry struct {
// _type is a non-loaded Variable pointing to runtime._type struct in the target. // _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 // The returned string is in the format that's used in DWARF data
func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) { func nameOfRuntimeType(_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 return e.typename, e.kind, nil
} }
@ -244,7 +244,7 @@ func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error)
return typename, kind, err 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 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 // For a description of how memory is organized for type names read
// the comment to 'type name struct' in $GOROOT/src/reflect/type.go // 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 { if err != nil {
return "", err return "", err
} }
@ -303,7 +303,7 @@ func nameOfNamedRuntimeType(_type *Variable, kind, tflag int64) (typename string
if ut := uncommon(_type, tflag); ut != nil { if ut := uncommon(_type, tflag); ut != nil {
if pkgPathField := ut.loadFieldNamed("pkgpath"); pkgPathField != nil && pkgPathField.Value != nil { if pkgPathField := ut.loadFieldNamed("pkgpath"); pkgPathField != nil && pkgPathField.Value != nil {
pkgPathOff, _ := constant.Int64Val(pkgPathField.Value) 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 { if err != nil {
return "", err 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, // (optional) and then by an array of pointers to runtime._type,
// one for each input and output argument. // one for each input and output argument.
func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string, error) { 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 { if err != nil {
return "", err return "", err
} }
prtyp := pointerTo(rtyp, _type.dbp.bi.arch) prtyp := pointerTo(rtyp, _type.bi.arch)
uadd := _type.RealType.Common().ByteSize uadd := _type.RealType.Common().ByteSize
if ut := uncommon(_type, tflag); ut != nil { 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++ { for i := int64(0); i < inCount; i++ {
argtype := cursortyp.maybeDereference() argtype := cursortyp.maybeDereference()
cursortyp.Addr += uintptr(_type.dbp.bi.arch.PtrSize()) cursortyp.Addr += uintptr(_type.bi.arch.PtrSize())
argtypename, _, err := nameOfRuntimeType(argtype) argtypename, _, err := nameOfRuntimeType(argtype)
if err != nil { if err != nil {
return "", err return "", err
@ -434,7 +434,7 @@ func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string
buf.WriteString(" (") buf.WriteString(" (")
for i := int64(0); i < outCount; i++ { for i := int64(0); i < outCount; i++ {
argtype := cursortyp.maybeDereference() argtype := cursortyp.maybeDereference()
cursortyp.Addr += uintptr(_type.dbp.bi.arch.PtrSize()) cursortyp.Addr += uintptr(_type.bi.arch.PtrSize())
argtypename, _, err := nameOfRuntimeType(argtype) argtypename, _, err := nameOfRuntimeType(argtype)
if err != nil { if err != nil {
return "", err return "", err
@ -473,14 +473,14 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err
case "name": case "name":
nameoff, _ := constant.Int64Val(im.Children[i].Value) nameoff, _ := constant.Int64Val(im.Children[i].Value)
var err error 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 { if err != nil {
return "", err return "", err
} }
case "typ": case "typ":
typeoff, _ := constant.Int64Val(im.Children[i].Value) 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 { if err != nil {
return "", err return "", err
} }
@ -536,7 +536,7 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error)
case "name": case "name":
nameoff, _ := constant.Int64Val(field.Children[i].Value) nameoff, _ := constant.Int64Val(field.Children[i].Value)
var err error 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 { if err != nil {
return "", err return "", err
} }
@ -578,13 +578,13 @@ func fieldToType(_type *Variable, fieldName string) (string, error) {
} }
func specificRuntimeType(_type *Variable, kind int64) (*Variable, 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -602,7 +602,7 @@ func specificRuntimeType(_type *Variable, kind int64) (*Variable, error) {
newSliceType := func(elemtype dwarf.Type) *dwarf.SliceType { newSliceType := func(elemtype dwarf.Type) *dwarf.SliceType {
r := newStructType("[]"+elemtype.Common().Name, uintptr(3*uintptrtyp.Size())) 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, "len", uintptrtyp, uintptr(uintptrtyp.Size()))
appendField(r, "cap", uintptrtyp, uintptr(2*uintptrtyp.Size())) appendField(r, "cap", uintptrtyp, uintptr(2*uintptrtyp.Size()))
return &dwarf.SliceType{StructType: *r, ElemType: elemtype} return &dwarf.SliceType{StructType: *r, ElemType: elemtype}
@ -746,7 +746,7 @@ func uncommon(_type *Variable, tflag int64) *Variable {
return nil return nil
} }
typ, err := _type.dbp.bi.findType("runtime.uncommontype") typ, err := _type.bi.findType("runtime.uncommontype")
if err != nil { if err != nil {
return nil return nil
} }

@ -51,7 +51,7 @@ type Variable struct {
RealType dwarf.Type RealType dwarf.Type
Kind reflect.Kind Kind reflect.Kind
mem memoryReadWriter mem memoryReadWriter
dbp *Process bi *BinaryInfo
Value constant.Value Value constant.Value
FloatSpecial FloatSpecial FloatSpecial FloatSpecial
@ -132,15 +132,16 @@ type G struct {
thread *Thread thread *Thread
variable *Variable variable *Variable
dbp *Process
} }
// EvalScope is the scope for variable evaluation. Contains the thread, // EvalScope is the scope for variable evaluation. Contains the thread,
// current location (PC), and canonical frame address. // current location (PC), and canonical frame address.
type EvalScope struct { type EvalScope struct {
Thread *Thread PC uint64 // Current instruction of the evaluation frame
PC uint64 CFA int64 // Stack address of the evaluation frame
CFA int64 Mem memoryReadWriter // Target's memory
gvar *Variable
bi *BinaryInfo
} }
// IsNilErr is returned when a variable is nil. // 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 { 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 { 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 { 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{ v := &Variable{
Name: name, Name: name,
Addr: addr, Addr: addr,
DwarfType: dwarfType, DwarfType: dwarfType,
mem: mem, mem: mem,
dbp: dbp, bi: bi,
} }
v.RealType = resolveTypedef(v.DwarfType) v.RealType = resolveTypedef(v.DwarfType)
@ -190,7 +191,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, dbp *Process,
v.stride = 1 v.stride = 1
v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}} v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}}
if v.Addr != 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: case *dwarf.SliceType:
v.Kind = reflect.Slice v.Kind = reflect.Slice
@ -321,17 +322,17 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) {
// DwarfReader returns the DwarfReader containing the // DwarfReader returns the DwarfReader containing the
// Dwarf information for the target process. // Dwarf information for the target process.
func (scope *EvalScope) DwarfReader() *reader.Reader { func (scope *EvalScope) DwarfReader() *reader.Reader {
return scope.Thread.dbp.bi.DwarfReader() return scope.bi.DwarfReader()
} }
// Type returns the Dwarf type entry at `offset`. // Type returns the Dwarf type entry at `offset`.
func (scope *EvalScope) Type(offset dwarf.Offset) (dwarf.Type, error) { 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. // PtrSize returns the size of a pointer.
func (scope *EvalScope) PtrSize() int { 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 // ChanRecvBlocked returns whether the goroutine is blocked on
@ -362,12 +363,11 @@ func (ng NoGError) Error() string {
func (gvar *Variable) parseG() (*G, error) { func (gvar *Variable) parseG() (*G, error) {
mem := gvar.mem mem := gvar.mem
dbp := gvar.dbp
gaddr := uint64(gvar.Addr) gaddr := uint64(gvar.Addr)
_, deref := gvar.RealType.(*dwarf.PtrType) _, deref := gvar.RealType.(*dwarf.PtrType)
if deref { 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 { if err != nil {
return nil, fmt.Errorf("error derefing *G %s", err) 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) 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{ g := &G{
ID: int(id), ID: int(id),
GoPC: uint64(gopc), GoPC: uint64(gopc),
@ -417,7 +417,6 @@ func (gvar *Variable) parseG() (*G, error) {
variable: gvar, variable: gvar,
stkbarVar: stkbarVar, stkbarVar: stkbarVar,
stkbarPos: int(stkbarPos), stkbarPos: int(stkbarPos),
dbp: gvar.dbp,
} }
return g, nil return g, nil
} }
@ -498,7 +497,7 @@ func (g *G) UserCurrent() Location {
// Go returns the location of the 'go' statement // Go returns the location of the 'go' statement
// that spawned this goroutine. // that spawned this goroutine.
func (g *G) Go() Location { 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} 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) { func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) {
reader := scope.DwarfReader() reader := scope.DwarfReader()
off, err := scope.Thread.dbp.bi.findFunctionDebugInfo(scope.PC) off, err := scope.bi.findFunctionDebugInfo(scope.PC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -658,7 +657,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
// EvalPackageVariable will evaluate the package level variable // EvalPackageVariable will evaluate the package level variable
// specified by 'name'. // specified by 'name'.
func (dbp *Process) EvalPackageVariable(name string, cfg LoadConfig) (*Variable, error) { 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) v, err := scope.packageVarAddr(name)
if err != nil { if err != nil {
@ -1207,7 +1206,7 @@ func (v *Variable) writeBool(value bool) error {
} }
func (v *Variable) readFunctionPtr() { 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 { if err != nil {
v.Unreadable = err v.Unreadable = err
return return
@ -1221,14 +1220,14 @@ func (v *Variable) readFunctionPtr() {
return 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 { if err != nil {
v.Unreadable = err v.Unreadable = err
return return
} }
v.Base = uintptr(binary.LittleEndian.Uint64(val)) 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 { if fn == nil {
v.Unreadable = fmt.Errorf("could not find function for %#v", v.Base) v.Unreadable = fmt.Errorf("could not find function for %#v", v.Base)
return return
@ -1603,7 +1602,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig
return return
} }
typ, err = v.dbp.bi.findType(typename) typ, err = v.bi.findType(typename)
if err != nil { if err != nil {
v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err) v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err)
return return
@ -1627,7 +1626,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig
return return
} }
typ, err = v.dbp.bi.findTypeExpr(t) typ, err = v.bi.findTypeExpr(t)
if err != nil { if err != nil {
v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err) v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err)
return return
@ -1637,7 +1636,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig
if kind&kindDirectIface == 0 { if kind&kindDirectIface == 0 {
realtyp := resolveTypedef(typ) realtyp := resolveTypedef(typ)
if _, isptr := realtyp.(*dwarf.PtrType); !isptr { 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 // Fetches all variables of a specific type in the current function scope
func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variable, error) { func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variable, error) {
reader := scope.DwarfReader() reader := scope.DwarfReader()
off, err := scope.Thread.dbp.bi.findFunctionDebugInfo(scope.PC) off, err := scope.bi.findFunctionDebugInfo(scope.PC)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -26,8 +26,32 @@ type Info interface {
ThreadInfo ThreadInfo
GoroutineInfo 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) 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) 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) 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. // VariableEval is an interface for dealing with eval scopes.
type VariableEval interface { type VariableEval interface {
FrameToScope(proc.Stackframe) *proc.EvalScope
ConvertEvalScope(gid, frame int) (*proc.EvalScope, error) ConvertEvalScope(gid, frame int) (*proc.EvalScope, error)
} }

@ -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 { if err != nil {
return err return err
} }
@ -602,7 +602,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadCo
if !found { if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID) return nil, fmt.Errorf("couldn't find thread %d", threadID)
} }
scope, err := thread.Scope() scope, err := thread.ThreadScope()
if err != nil { if err != nil {
return nil, err 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)} frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
if cfg != nil && rawlocs[i].Current.Fn != nil { if cfg != nil && rawlocs[i].Current.Fn != nil {
var err error var err error
scope := rawlocs[i].Scope(d.target.CurrentThread()) scope := d.target.FrameToScope(rawlocs[i])
locals, err := scope.LocalVariables(*cfg) locals, err := scope.LocalVariables(*cfg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -808,20 +808,12 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo
endPC = fn.End endPC = fn.End
} }
currentGoroutine := true g, err := d.target.FindGoroutine(scope.GoroutineID)
thread := d.target.CurrentThread() if err != nil {
return nil, err
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
}
}
} }
insts, err := thread.Disassemble(startPC, endPC, currentGoroutine) insts, err := d.target.Disassemble(g, startPC, endPC)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -68,7 +68,7 @@ func (tc *varTest) alternateVarTest() varTest {
} }
func setVariable(p *proc.Process, symbol, value string) error { func setVariable(p *proc.Process, symbol, value string) error {
scope, err := p.CurrentThread().Scope() scope, err := p.CurrentThread().GoroutineScope()
if err != nil { if err != nil {
return err return err
} }
@ -348,7 +348,7 @@ func TestLocalVariables(t *testing.T) {
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases { for _, tc := range testcases {
scope, err := p.CurrentThread().Scope() scope, err := p.CurrentThread().GoroutineScope()
assertNoError(err, t, "AsScope()") assertNoError(err, t, "AsScope()")
vars, err := tc.fn(scope, pnormalLoadConfig) vars, err := tc.fn(scope, pnormalLoadConfig)
assertNoError(err, t, "LocalVariables() returned an error") assertNoError(err, t, "LocalVariables() returned an error")