提交 35405583 编写于 作者: A Alessandro Arzilli 提交者: Derek Parker

proc: next, stepout should work on recursive goroutines (#831)

Before this commit our temp breakpoints only checked that we would stay
on the same goroutine.
However this isn't enough for recursive functions we must check that we
stay on the same goroutine AND on the same stack frame (or, in the case
of the StepOut breakpoint, the previous stack frame).

This commit:
1. adds a new synthetic variable runtime.frameoff that returns the
   offset of the current frame from the base of the call stack.
   This is similar to runtime.curg
2. Changes the condition used for breakpoints on the lines of the
   current function to check that runtime.frameoff hasn't changed.
3. Changes the condition used for breakpoints on the return address to
   check that runtime.frameoff corresponds to the previous frame in the
   stack.
4. All other temporary breakpoints (the step-into breakpoints and defer
   breakpoints) remain unchanged.

Fixes #828
上级 8d3e74a4
package main
import "fmt"
// Increment Natural number y
func Increment(y uint) uint {
if y == 0 {
return 1
}
if y%2 == 1 {
return (2 * Increment(y/2))
}
return y + 1
}
func main() {
fmt.Printf("%d\n", Increment(3))
}
...@@ -117,11 +117,15 @@ func (bp *Breakpoint) CheckCondition(thread Thread) (bool, error) { ...@@ -117,11 +117,15 @@ func (bp *Breakpoint) CheckCondition(thread Thread) (bool, error) {
} }
} }
} }
return evalBreakpointCondition(thread, bp.Cond)
}
func evalBreakpointCondition(thread Thread, cond ast.Expr) (bool, error) {
scope, err := GoroutineScope(thread) scope, err := GoroutineScope(thread)
if err != nil { if err != nil {
return true, err return true, err
} }
v, err := scope.evalAST(bp.Cond) v, err := scope.evalAST(cond)
if err != nil { if err != nil {
return true, fmt.Errorf("error evaluating expression: %v", err) return true, fmt.Errorf("error evaluating expression: %v", err)
} }
......
...@@ -248,6 +248,10 @@ func (t *Thread) Blocked() bool { ...@@ -248,6 +248,10 @@ func (t *Thread) Blocked() bool {
return false return false
} }
func (t *Thread) SetCurrentBreakpoint() error {
return nil
}
func (p *Process) Breakpoints() map[uint64]*proc.Breakpoint { func (p *Process) Breakpoints() map[uint64]*proc.Breakpoint {
return p.breakpoints return p.breakpoints
} }
......
...@@ -71,6 +71,8 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { ...@@ -71,6 +71,8 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
return nilVariable, nil return nilVariable, nil
} }
return scope.Gvar.clone(), nil return scope.Gvar.clone(), nil
} else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" {
return newConstant(constant.MakeInt64(scope.CFA-int64(scope.StackHi)), scope.Mem), 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
} }
...@@ -851,7 +853,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { ...@@ -851,7 +853,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
r := xv.newVariable("", 0, typ) r := xv.newVariable("", 0, typ)
r.Value = rc r.Value = rc
if r.Kind == reflect.String { if r.Kind == reflect.String {
r.Len = xv.Len+yv.Len r.Len = xv.Len + yv.Len
} }
return r, nil return r, nil
} }
......
...@@ -13,7 +13,7 @@ type moduleData struct { ...@@ -13,7 +13,7 @@ type moduleData struct {
func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) { func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
bi.loadModuleDataOnce.Do(func() { bi.loadModuleDataOnce.Do(func() {
scope := &EvalScope{0, 0, mem, nil, bi} scope := &EvalScope{0, 0, mem, nil, bi, 0}
var md *Variable var md *Variable
md, err = scope.packageVarAddr("runtime.firstmoduledata") md, err = scope.packageVarAddr("runtime.firstmoduledata")
if err != nil { if err != nil {
...@@ -119,7 +119,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea ...@@ -119,7 +119,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
} }
func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) { func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
scope := &EvalScope{0, 0, mem, nil, bi} scope := &EvalScope{0, 0, mem, nil, bi, 0}
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -254,19 +254,41 @@ func SameGoroutineCondition(g *G) ast.Expr { ...@@ -254,19 +254,41 @@ func SameGoroutineCondition(g *G) ast.Expr {
} }
} }
func frameoffCondition(frameoff int64) ast.Expr {
return &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "frameoff"},
},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.FormatInt(frameoff, 10)},
}
}
func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
if cond == nil {
return nil
}
return &ast.BinaryExpr{
Op: token.LAND,
X: cond,
Y: frameoffCondition(frameoff),
}
}
// StepOut will continue until the current goroutine exits the // StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed // function currently being executed or a deferred function is executed
func StepOut(dbp Process) error { func StepOut(dbp Process) error {
selg := dbp.SelectedGoroutine() selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread() curthread := dbp.CurrentThread()
cond := SameGoroutineCondition(selg)
topframe, err := topframe(selg, curthread) topframe, retframe, err := topframe(selg, curthread)
if err != nil { if err != nil {
return err return err
} }
pcs := []uint64{} sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.CFA-int64(retframe.StackHi))
var deferpc uint64 = 0 var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" { if filepath.Ext(topframe.Current.File) == ".go" {
...@@ -278,7 +300,6 @@ func StepOut(dbp Process) error { ...@@ -278,7 +300,6 @@ func StepOut(dbp Process) error {
if err != nil { if err != nil {
return err return err
} }
pcs = append(pcs, deferpc)
} }
} }
} }
...@@ -288,7 +309,7 @@ func StepOut(dbp Process) error { ...@@ -288,7 +309,7 @@ func StepOut(dbp Process) error {
} }
if deferpc != 0 && deferpc != topframe.Current.PC { if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond) bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil { if err != nil {
if _, ok := err.(BreakpointExistsError); !ok { if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints() dbp.ClearInternalBreakpoints()
...@@ -304,11 +325,19 @@ func StepOut(dbp Process) error { ...@@ -304,11 +325,19 @@ func StepOut(dbp Process) error {
} }
if topframe.Ret != 0 { if topframe.Ret != 0 {
if err := setInternalBreakpoints(dbp, topframe.Current.PC, []uint64{topframe.Ret}, NextBreakpoint, cond); err != nil { _, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
return err if err != nil {
if _, isexists := err.(BreakpointExistsError); !isexists {
dbp.ClearInternalBreakpoints()
return err
}
} }
} }
if bp, _, _ := curthread.Breakpoint(); bp == nil {
curthread.SetCurrentBreakpoint()
}
return Continue(dbp) return Continue(dbp)
} }
...@@ -402,7 +431,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -402,7 +431,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
} }
func GetGoInformation(p Process) (ver GoVersion, isextld bool, err error) { func GetGoInformation(p Process) (ver GoVersion, isextld bool, err error) {
scope := &EvalScope{0, 0, p.CurrentThread(), nil, p.BinInfo()} scope := &EvalScope{0, 0, p.CurrentThread(), nil, p.BinInfo(), 0}
vv, err := scope.packageVarAddr("runtime.buildVersion") vv, err := scope.packageVarAddr("runtime.buildVersion")
if err != nil { if err != nil {
return ver, false, fmt.Errorf("Could not determine version number: %v", err) return ver, false, fmt.Errorf("Could not determine version number: %v", err)
...@@ -482,10 +511,10 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) { ...@@ -482,10 +511,10 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
PC, CFA := locs[frame].Current.PC, locs[frame].CFA PC, CFA := locs[frame].Current.PC, locs[frame].CFA
return &EvalScope{PC, CFA, thread, g.variable, dbp.BinInfo()}, nil return &EvalScope{PC, CFA, thread, g.variable, dbp.BinInfo(), g.stackhi}, nil
} }
// FrameToScope returns a new EvalScope for this frame // FrameToScope returns a new EvalScope for this frame
func FrameToScope(p Process, frame Stackframe) *EvalScope { func FrameToScope(p Process, frame Stackframe) *EvalScope {
return &EvalScope{frame.Current.PC, frame.CFA, p.CurrentThread(), nil, p.BinInfo()} return &EvalScope{frame.Current.PC, frame.CFA, p.CurrentThread(), nil, p.BinInfo(), frame.StackHi}
} }
...@@ -533,6 +533,7 @@ func TestNextConcurrentVariant2(t *testing.T) { ...@@ -533,6 +533,7 @@ func TestNextConcurrentVariant2(t *testing.T) {
initVval, _ := constant.Int64Val(initV.Value) initVval, _ := constant.Int64Val(initV.Value)
assertNoError(err, t, "EvalVariable") assertNoError(err, t, "EvalVariable")
for _, tc := range testcases { for _, tc := range testcases {
t.Logf("test case %v", tc)
g, err := proc.GetG(p.CurrentThread()) g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, "GetG()") assertNoError(err, t, "GetG()")
if p.SelectedGoroutine().ID != g.ID { if p.SelectedGoroutine().ID != g.ID {
...@@ -2248,7 +2249,7 @@ func TestStepOut(t *testing.T) { ...@@ -2248,7 +2249,7 @@ func TestStepOut(t *testing.T) {
f, lno = currentLineNumber(p, t) f, lno = currentLineNumber(p, t)
if lno != 35 { if lno != 35 {
t.Fatalf("wrong line number %s:%d, expected %d", f, lno, 34) t.Fatalf("wrong line number %s:%d, expected %d", f, lno, 35)
} }
}) })
} }
...@@ -2880,3 +2881,56 @@ func TestEnvironment(t *testing.T) { ...@@ -2880,3 +2881,56 @@ func TestEnvironment(t *testing.T) {
} }
}) })
} }
func getFrameOff(p proc.Process, t *testing.T) int64 {
frameoffvar, err := evalVariable(p, "runtime.frameoff")
assertNoError(err, t, "EvalVariable(runtime.frameoff)")
frameoff, _ := constant.Int64Val(frameoffvar.Value)
return frameoff
}
func TestRecursiveNext(t *testing.T) {
protest.AllowRecording(t)
testcases := []nextTest{
{6, 7},
{7, 10},
{10, 11},
{11, 17},
}
testseq("increment", contNext, testcases, "main.Increment", t)
withTestProcess("increment", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.Increment")
assertNoError(err, t, "setFunctionBreakpoint")
assertNoError(proc.Continue(p), t, "Continue")
_, err = p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint")
assertNoError(proc.Next(p), t, "Next 1")
assertNoError(proc.Next(p), t, "Next 2")
assertNoError(proc.Next(p), t, "Next 3")
frameoff0 := getFrameOff(p, t)
assertNoError(proc.Step(p), t, "Step")
frameoff1 := getFrameOff(p, t)
if frameoff0 == frameoff1 {
t.Fatalf("did not step into function?")
}
_, ln := currentLineNumber(p, t)
if ln != 6 {
t.Fatalf("program did not continue to expected location %d", ln)
}
assertNoError(proc.Next(p), t, "Next 4")
_, ln = currentLineNumber(p, t)
if ln != 7 {
t.Fatalf("program did not continue to expected location %d", ln)
}
assertNoError(proc.StepOut(p), t, "StepOut")
_, ln = currentLineNumber(p, t)
if ln != 11 {
t.Fatalf("program did not continue to expected location %d", ln)
}
frameoff2 := getFrameOff(p, t)
if frameoff0 != frameoff2 {
t.Fatalf("frame offset mismatch %x != %x", frameoff0, frameoff2)
}
})
}
...@@ -30,6 +30,8 @@ type Stackframe struct { ...@@ -30,6 +30,8 @@ type Stackframe struct {
Call Location Call Location
// Start address of the stack frame. // Start address of the stack frame.
CFA int64 CFA int64
// High address of the stack.
StackHi uint64
// Description of the stack frame. // Description of the stack frame.
FDE *frame.FrameDescriptionEntry FDE *frame.FrameDescriptionEntry
// Return address for this stack frame (as read from the stack frame itself). // Return address for this stack frame (as read from the stack frame itself).
...@@ -45,7 +47,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) { ...@@ -45,7 +47,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
it := newStackIterator(thread.BinInfo(), thread, regs.PC(), regs.SP(), regs.BP(), nil, -1) it := newStackIterator(thread.BinInfo(), thread, regs.PC(), regs.SP(), regs.BP(), 0, nil, -1)
return it.stacktrace(depth) return it.stacktrace(depth)
} }
...@@ -59,9 +61,9 @@ func (g *G) stackIterator() (*stackIterator, error) { ...@@ -59,9 +61,9 @@ func (g *G) stackIterator() (*stackIterator, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newStackIterator(g.variable.bi, g.Thread, regs.PC(), regs.SP(), regs.BP(), stkbar, g.stkbarPos), nil return newStackIterator(g.variable.bi, g.Thread, regs.PC(), regs.SP(), regs.BP(), g.stackhi, stkbar, g.stkbarPos), nil
} }
return newStackIterator(g.variable.bi, g.variable.mem, g.PC, g.SP, 0, stkbar, g.stkbarPos), nil return newStackIterator(g.variable.bi, g.variable.mem, g.PC, g.SP, 0, g.stackhi, stkbar, g.stkbarPos), nil
} }
// Stacktrace returns the stack trace for a goroutine. // Stacktrace returns the stack trace for a goroutine.
...@@ -93,6 +95,7 @@ type stackIterator struct { ...@@ -93,6 +95,7 @@ type stackIterator struct {
mem MemoryReadWriter mem MemoryReadWriter
err error err error
stackhi uint64
stackBarrierPC uint64 stackBarrierPC uint64
stkbar []savedLR stkbar []savedLR
} }
...@@ -102,7 +105,7 @@ type savedLR struct { ...@@ -102,7 +105,7 @@ type savedLR struct {
val uint64 val uint64
} }
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator { func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp, stackhi uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
stackBarrierFunc := 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 {
...@@ -122,7 +125,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp uint64, s ...@@ -122,7 +125,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp uint64, s
} }
stkbar = stkbar[stkbarPos:] stkbar = stkbar[stkbarPos:]
} }
return &stackIterator{pc: pc, sp: sp, bp: bp, top: true, bi: bi, mem: mem, 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, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar}
} }
// Next points the iterator to the next stack frame. // Next points the iterator to the next stack frame.
...@@ -206,7 +209,7 @@ func (it *stackIterator) newStackframe(pc uint64, cfa int64, retaddr uintptr, fd ...@@ -206,7 +209,7 @@ func (it *stackIterator) newStackframe(pc uint64, cfa int64, retaddr uintptr, fd
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), StackHi: it.stackhi}
if !top { if !top {
r.Call.File, r.Call.Line, r.Call.Fn = it.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
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/token"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
...@@ -31,6 +32,8 @@ type Thread interface { ...@@ -31,6 +32,8 @@ type Thread interface {
StepInstruction() error StepInstruction() error
// Blocked returns true if the thread is blocked // Blocked returns true if the thread is blocked
Blocked() bool Blocked() bool
// SetCurrentBreakpoint updates the current breakpoint of this thread
SetCurrentBreakpoint() error
} }
// Location represents the location of a thread. // Location represents the location of a thread.
...@@ -51,26 +54,30 @@ func (tbe ThreadBlockedError) Error() string { ...@@ -51,26 +54,30 @@ func (tbe ThreadBlockedError) Error() string {
return "" return ""
} }
// returns topmost frame of g or thread if g is nil // topframe returns the two topmost frames of g, or thread if g is nil.
func topframe(g *G, thread Thread) (Stackframe, error) { func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
var frames []Stackframe var frames []Stackframe
var err error var err error
if g == nil { if g == nil {
if thread.Blocked() { if thread.Blocked() {
return Stackframe{}, ThreadBlockedError{} return Stackframe{}, Stackframe{}, ThreadBlockedError{}
} }
frames, err = ThreadStacktrace(thread, 0) frames, err = ThreadStacktrace(thread, 1)
} else { } else {
frames, err = g.Stacktrace(0) frames, err = g.Stacktrace(1)
} }
if err != nil { if err != nil {
return Stackframe{}, err return Stackframe{}, Stackframe{}, err
} }
if len(frames) < 1 { switch len(frames) {
return Stackframe{}, errors.New("empty stack trace") case 0:
return Stackframe{}, Stackframe{}, errors.New("empty stack trace")
case 1:
return frames[0], Stackframe{}, nil
default:
return frames[0], frames[1], nil
} }
return frames[0], nil
} }
// Set breakpoints at every line, and the return address. Also look for // Set breakpoints at every line, and the return address. Also look for
...@@ -80,10 +87,20 @@ func topframe(g *G, thread Thread) (Stackframe, error) { ...@@ -80,10 +87,20 @@ func topframe(g *G, thread Thread) (Stackframe, error) {
// a breakpoint of kind StepBreakpoint is set on the CALL instruction, // a breakpoint of kind StepBreakpoint is set on the CALL instruction,
// Continue will take care of setting a breakpoint to the destination // Continue will take care of setting a breakpoint to the destination
// once the CALL is reached. // once the CALL is reached.
//
// Regardless of stepInto the following breakpoints will be set:
// - a breakpoint on the first deferred function with NextDeferBreakpoint
// kind, the list of all the addresses to deferreturn calls in this function
// and condition checking that we remain on the same goroutine
// - a breakpoint on each line of the function, with a condition checking
// that we stay on the same stack frame and goroutine.
// - a breakpoint on the return address of the function, with a condition
// checking that we move to the previous stack frame and stay on the same
// goroutine.
func next(dbp Process, stepInto bool) error { func next(dbp Process, stepInto bool) error {
selg := dbp.SelectedGoroutine() selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread() curthread := dbp.CurrentThread()
topframe, err := topframe(selg, curthread) topframe, retframe, err := topframe(selg, curthread)
if err != nil { if err != nil {
return err return err
} }
...@@ -117,7 +134,21 @@ func next(dbp Process, stepInto bool) error { ...@@ -117,7 +134,21 @@ func next(dbp Process, stepInto bool) error {
} }
} }
cond := SameGoroutineCondition(selg) sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.CFA-int64(retframe.StackHi))
sameFrameCond := andFrameoffCondition(sameGCond, topframe.CFA-int64(topframe.StackHi))
var sameOrRetFrameCond ast.Expr
if sameGCond != nil {
sameOrRetFrameCond = &ast.BinaryExpr{
Op: token.LAND,
X: sameGCond,
Y: &ast.BinaryExpr{
Op: token.LOR,
X: frameoffCondition(topframe.CFA - int64(topframe.StackHi)),
Y: frameoffCondition(retframe.CFA - int64(retframe.StackHi)),
},
}
}
if stepInto { if stepInto {
for _, instr := range text { for _, instr := range text {
...@@ -126,12 +157,12 @@ func next(dbp Process, stepInto bool) error { ...@@ -126,12 +157,12 @@ func next(dbp Process, stepInto bool) error {
} }
if instr.DestLoc != nil && instr.DestLoc.Fn != nil { if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, cond); err != nil { if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
return err return err
} }
} else { } else {
// Non-absolute call instruction, set a StepBreakpoint here // Non-absolute call instruction, set a StepBreakpoint here
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, cond); err != nil { if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok { if _, ok := err.(BreakpointExistsError); !ok {
return err return err
} }
...@@ -165,7 +196,7 @@ func next(dbp Process, stepInto bool) error { ...@@ -165,7 +196,7 @@ func next(dbp Process, stepInto bool) error {
} }
} }
if deferpc != 0 && deferpc != topframe.Current.PC { if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond) bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil { if err != nil {
if _, ok := err.(BreakpointExistsError); !ok { if _, ok := err.(BreakpointExistsError); !ok {
return err return err
...@@ -201,9 +232,33 @@ func next(dbp Process, stepInto bool) error { ...@@ -201,9 +232,33 @@ func next(dbp Process, stepInto bool) error {
} }
// Add a breakpoint on the return address for the current frame // Add a breakpoint on the return address for the current frame
pcs = append(pcs, topframe.Ret) for _, pc := range pcs {
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
}
if bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond); err != nil {
if _, isexists := err.(BreakpointExistsError); isexists {
if bp.Kind == NextBreakpoint {
// If the return address shares the same address with one of the lines
// of the function (because we are stepping through a recursive
// function) then the corresponding breakpoint should be active both on
// this frame and on the return frame.
bp.Cond = sameOrRetFrameCond
}
} else {
return err
}
}
if bp, _, _ := curthread.Breakpoint(); bp == nil {
curthread.SetCurrentBreakpoint()
}
success = true success = true
return setInternalBreakpoints(dbp, topframe.Current.PC, pcs, NextBreakpoint, cond) return nil
} }
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error { func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
...@@ -246,23 +301,6 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er ...@@ -246,23 +301,6 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
return nil return nil
} }
// setInternalBreakpoints sets a breakpoint to all addresses specified in pcs
// skipping over curpc and curpc-1
func setInternalBreakpoints(dbp Process, curpc uint64, pcs []uint64, kind BreakpointKind, cond ast.Expr) error {
for i := range pcs {
if pcs[i] == curpc || pcs[i] == curpc-1 {
continue
}
if _, err := dbp.SetBreakpoint(pcs[i], kind, cond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
}
return nil
}
func getGVariable(thread Thread) (*Variable, error) { func getGVariable(thread Thread) (*Variable, error) {
arch := thread.Arch() arch := thread.Arch()
regs, err := thread.Registers(false) regs, err := thread.Registers(false)
...@@ -345,7 +383,7 @@ func ThreadScope(thread Thread) (*EvalScope, error) { ...@@ -345,7 +383,7 @@ func ThreadScope(thread Thread) (*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 &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.BinInfo()}, nil return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.BinInfo(), 0}, nil
} }
// GoroutineScope returns an EvalScope for the goroutine running on this thread. // GoroutineScope returns an EvalScope for the goroutine running on this thread.
...@@ -357,11 +395,11 @@ func GoroutineScope(thread Thread) (*EvalScope, error) { ...@@ -357,11 +395,11 @@ func GoroutineScope(thread Thread) (*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")
} }
gvar, err := getGVariable(thread) g, err := GetG(thread)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.BinInfo()}, nil return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, g.variable, thread.BinInfo(), g.stackhi}, nil
} }
func onRuntimeBreakpoint(thread Thread) bool { func onRuntimeBreakpoint(thread Thread) bool {
...@@ -384,12 +422,30 @@ func onNextGoroutine(thread Thread, breakpoints map[uint64]*Breakpoint) (bool, e ...@@ -384,12 +422,30 @@ func onNextGoroutine(thread Thread, breakpoints map[uint64]*Breakpoint) (bool, e
if bp == nil { if bp == nil {
return false, nil return false, nil
} }
if bp.Kind == NextDeferBreakpoint { // Internal breakpoint conditions can take multiple different forms:
// we just want to check the condition on the goroutine id here // Step into breakpoints:
bp.Kind = NextBreakpoint // runtime.curg.goid == X
defer func() { // Next or StepOut breakpoints:
bp.Kind = NextDeferBreakpoint // runtime.curg.goid == X && runtime.frameoff == Y
}() // Breakpoints that can be hit either by stepping on a line in the same
// function or by returning from the function:
// runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z)
// Here we are only interested in testing the runtime.curg.goid clause.
w := onNextGoroutineWalker{thread: thread}
ast.Walk(&w, bp.Cond)
return w.ret, w.err
}
type onNextGoroutineWalker struct {
thread Thread
ret bool
err error
}
func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor {
if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL && exprToString(binx.X) == "runtime.curg.goid" {
w.ret, w.err = evalBreakpointCondition(w.thread, n.(ast.Expr))
return nil
} }
return bp.CheckCondition(thread) return w
} }
...@@ -124,6 +124,7 @@ type G struct { ...@@ -124,6 +124,7 @@ type G struct {
Status uint64 Status uint64
stkbarVar *Variable // stkbar field of g struct stkbarVar *Variable // stkbar field of g struct
stkbarPos int // stkbarPos field of g struct stkbarPos int // stkbarPos field of g struct
stackhi uint64 // value of stack.hi
// Information on goroutine location // Information on goroutine location
CurrentLoc Location CurrentLoc Location
...@@ -142,6 +143,7 @@ type EvalScope struct { ...@@ -142,6 +143,7 @@ type EvalScope struct {
Mem MemoryReadWriter // Target's memory Mem MemoryReadWriter // Target's memory
Gvar *Variable Gvar *Variable
BinInfo *BinaryInfo BinInfo *BinaryInfo
StackHi uint64
} }
// IsNilErr is returned when a variable is nil. // IsNilErr is returned when a variable is nil.
...@@ -377,7 +379,7 @@ func (gvar *Variable) parseG() (*G, error) { ...@@ -377,7 +379,7 @@ func (gvar *Variable) parseG() (*G, error) {
} }
gvar = gvar.maybeDereference() gvar = gvar.maybeDereference()
} }
gvar.loadValue(LoadConfig{false, 1, 64, 0, -1}) gvar.loadValue(LoadConfig{false, 2, 64, 0, -1})
if gvar.Unreadable != nil { if gvar.Unreadable != nil {
return nil, gvar.Unreadable return nil, gvar.Unreadable
} }
...@@ -387,6 +389,12 @@ func (gvar *Variable) parseG() (*G, error) { ...@@ -387,6 +389,12 @@ func (gvar *Variable) parseG() (*G, error) {
id, _ := constant.Int64Val(gvar.fieldVariable("goid").Value) id, _ := constant.Int64Val(gvar.fieldVariable("goid").Value)
gopc, _ := constant.Int64Val(gvar.fieldVariable("gopc").Value) gopc, _ := constant.Int64Val(gvar.fieldVariable("gopc").Value)
waitReason := constant.StringVal(gvar.fieldVariable("waitreason").Value) waitReason := constant.StringVal(gvar.fieldVariable("waitreason").Value)
var stackhi uint64
if stackVar := gvar.fieldVariable("stack"); stackVar != nil {
if stackhiVar := stackVar.fieldVariable("hi"); stackhiVar != nil {
stackhi, _ = constant.Uint64Val(stackhiVar.Value)
}
}
stkbarVar, _ := gvar.structMember("stkbar") stkbarVar, _ := gvar.structMember("stkbar")
stkbarVarPosFld := gvar.fieldVariable("stkbarPos") stkbarVarPosFld := gvar.fieldVariable("stkbarPos")
...@@ -408,6 +416,7 @@ func (gvar *Variable) parseG() (*G, error) { ...@@ -408,6 +416,7 @@ func (gvar *Variable) parseG() (*G, error) {
variable: gvar, variable: gvar,
stkbarVar: stkbarVar, stkbarVar: stkbarVar,
stkbarPos: int(stkbarPos), stkbarPos: int(stkbarPos),
stackhi: stackhi,
} }
return g, nil return g, nil
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册