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
This commit is contained in:
parent
8d3e74a445
commit
354055836a
18
_fixtures/increment.go
Normal file
18
_fixtures/increment.go
Normal file
@ -0,0 +1,18 @@
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return evalBreakpointCondition(thread, bp.Cond)
|
||||
}
|
||||
|
||||
func evalBreakpointCondition(thread Thread, cond ast.Expr) (bool, error) {
|
||||
scope, err := GoroutineScope(thread)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
v, err := scope.evalAST(bp.Cond)
|
||||
v, err := scope.evalAST(cond)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("error evaluating expression: %v", err)
|
||||
}
|
||||
|
@ -248,6 +248,10 @@ func (t *Thread) Blocked() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Thread) SetCurrentBreakpoint() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Process) Breakpoints() map[uint64]*proc.Breakpoint {
|
||||
return p.breakpoints
|
||||
}
|
||||
|
@ -71,6 +71,8 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
|
||||
return nilVariable, 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 {
|
||||
return v, nil
|
||||
}
|
||||
@ -851,7 +853,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
r := xv.newVariable("", 0, typ)
|
||||
r.Value = rc
|
||||
if r.Kind == reflect.String {
|
||||
r.Len = xv.Len+yv.Len
|
||||
r.Len = xv.Len + yv.Len
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ type moduleData struct {
|
||||
|
||||
func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
|
||||
bi.loadModuleDataOnce.Do(func() {
|
||||
scope := &EvalScope{0, 0, mem, nil, bi}
|
||||
scope := &EvalScope{0, 0, mem, nil, bi, 0}
|
||||
var md *Variable
|
||||
md, err = scope.packageVarAddr("runtime.firstmoduledata")
|
||||
if err != nil {
|
||||
@ -119,7 +119,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
|
||||
}
|
||||
|
||||
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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -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
|
||||
// function currently being executed or a deferred function is executed
|
||||
func StepOut(dbp Process) error {
|
||||
selg := dbp.SelectedGoroutine()
|
||||
curthread := dbp.CurrentThread()
|
||||
cond := SameGoroutineCondition(selg)
|
||||
|
||||
topframe, err := topframe(selg, curthread)
|
||||
topframe, retframe, err := topframe(selg, curthread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pcs := []uint64{}
|
||||
sameGCond := SameGoroutineCondition(selg)
|
||||
retFrameCond := andFrameoffCondition(sameGCond, retframe.CFA-int64(retframe.StackHi))
|
||||
|
||||
var deferpc uint64 = 0
|
||||
if filepath.Ext(topframe.Current.File) == ".go" {
|
||||
@ -278,7 +300,6 @@ func StepOut(dbp Process) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pcs = append(pcs, deferpc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -288,7 +309,7 @@ func StepOut(dbp Process) error {
|
||||
}
|
||||
|
||||
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 _, ok := err.(BreakpointExistsError); !ok {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
@ -304,11 +325,19 @@ func StepOut(dbp Process) error {
|
||||
}
|
||||
|
||||
if topframe.Ret != 0 {
|
||||
if err := setInternalBreakpoints(dbp, topframe.Current.PC, []uint64{topframe.Ret}, NextBreakpoint, cond); err != nil {
|
||||
return err
|
||||
_, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
|
||||
if err != nil {
|
||||
if _, isexists := err.(BreakpointExistsError); !isexists {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bp, _, _ := curthread.Breakpoint(); bp == nil {
|
||||
curthread.SetCurrentBreakpoint()
|
||||
}
|
||||
|
||||
return Continue(dbp)
|
||||
}
|
||||
|
||||
@ -402,7 +431,7 @@ func GoroutinesInfo(dbp Process) ([]*G, 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")
|
||||
if err != nil {
|
||||
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) {
|
||||
|
||||
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
|
||||
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) {
|
||||
initVval, _ := constant.Int64Val(initV.Value)
|
||||
assertNoError(err, t, "EvalVariable")
|
||||
for _, tc := range testcases {
|
||||
t.Logf("test case %v", tc)
|
||||
g, err := proc.GetG(p.CurrentThread())
|
||||
assertNoError(err, t, "GetG()")
|
||||
if p.SelectedGoroutine().ID != g.ID {
|
||||
@ -2248,7 +2249,7 @@ func TestStepOut(t *testing.T) {
|
||||
|
||||
f, lno = currentLineNumber(p, t)
|
||||
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) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
Call Location
|
||||
// Start address of the stack frame.
|
||||
CFA int64
|
||||
// High address of the stack.
|
||||
StackHi uint64
|
||||
// Description of the stack frame.
|
||||
FDE *frame.FrameDescriptionEntry
|
||||
// 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) {
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -59,9 +61,9 @@ func (g *G) stackIterator() (*stackIterator, error) {
|
||||
if err != nil {
|
||||
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.
|
||||
@ -93,6 +95,7 @@ type stackIterator struct {
|
||||
mem MemoryReadWriter
|
||||
err error
|
||||
|
||||
stackhi uint64
|
||||
stackBarrierPC uint64
|
||||
stkbar []savedLR
|
||||
}
|
||||
@ -102,7 +105,7 @@ type savedLR struct {
|
||||
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
|
||||
var stackBarrierPC uint64
|
||||
if stackBarrierFunc != nil && stkbar != nil {
|
||||
@ -122,7 +125,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp uint64, s
|
||||
}
|
||||
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.
|
||||
@ -206,7 +209,7 @@ func (it *stackIterator) newStackframe(pc uint64, cfa int64, retaddr uintptr, fd
|
||||
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)}
|
||||
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 {
|
||||
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(pc - 1)
|
||||
r.Call.PC = r.Current.PC
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -31,6 +32,8 @@ type Thread interface {
|
||||
StepInstruction() error
|
||||
// Blocked returns true if the thread is blocked
|
||||
Blocked() bool
|
||||
// SetCurrentBreakpoint updates the current breakpoint of this thread
|
||||
SetCurrentBreakpoint() error
|
||||
}
|
||||
|
||||
// Location represents the location of a thread.
|
||||
@ -51,26 +54,30 @@ func (tbe ThreadBlockedError) Error() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// returns topmost frame of g or thread if g is nil
|
||||
func topframe(g *G, thread Thread) (Stackframe, error) {
|
||||
// topframe returns the two topmost frames of g, or thread if g is nil.
|
||||
func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
|
||||
var frames []Stackframe
|
||||
var err error
|
||||
|
||||
if g == nil {
|
||||
if thread.Blocked() {
|
||||
return Stackframe{}, ThreadBlockedError{}
|
||||
return Stackframe{}, Stackframe{}, ThreadBlockedError{}
|
||||
}
|
||||
frames, err = ThreadStacktrace(thread, 0)
|
||||
frames, err = ThreadStacktrace(thread, 1)
|
||||
} else {
|
||||
frames, err = g.Stacktrace(0)
|
||||
frames, err = g.Stacktrace(1)
|
||||
}
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
return Stackframe{}, Stackframe{}, err
|
||||
}
|
||||
if len(frames) < 1 {
|
||||
return Stackframe{}, errors.New("empty stack trace")
|
||||
switch len(frames) {
|
||||
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
|
||||
@ -80,10 +87,20 @@ func topframe(g *G, thread Thread) (Stackframe, error) {
|
||||
// a breakpoint of kind StepBreakpoint is set on the CALL instruction,
|
||||
// Continue will take care of setting a breakpoint to the destination
|
||||
// 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 {
|
||||
selg := dbp.SelectedGoroutine()
|
||||
curthread := dbp.CurrentThread()
|
||||
topframe, err := topframe(selg, curthread)
|
||||
topframe, retframe, err := topframe(selg, curthread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -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 {
|
||||
for _, instr := range text {
|
||||
@ -126,12 +157,12 @@ func next(dbp Process, stepInto bool) error {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
} else {
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
@ -165,7 +196,7 @@ func next(dbp Process, stepInto bool) error {
|
||||
}
|
||||
}
|
||||
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 _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
@ -201,9 +232,33 @@ func next(dbp Process, stepInto bool) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
return setInternalBreakpoints(dbp, topframe.Current.PC, pcs, NextBreakpoint, cond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
|
||||
@ -246,23 +301,6 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
|
||||
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) {
|
||||
arch := thread.Arch()
|
||||
regs, err := thread.Registers(false)
|
||||
@ -345,7 +383,7 @@ func ThreadScope(thread Thread) (*EvalScope, error) {
|
||||
if len(locations) < 1 {
|
||||
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.
|
||||
@ -357,11 +395,11 @@ func GoroutineScope(thread Thread) (*EvalScope, error) {
|
||||
if len(locations) < 1 {
|
||||
return nil, errors.New("could not decode first frame")
|
||||
}
|
||||
gvar, err := getGVariable(thread)
|
||||
g, err := GetG(thread)
|
||||
if err != nil {
|
||||
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 {
|
||||
@ -384,12 +422,30 @@ func onNextGoroutine(thread Thread, breakpoints map[uint64]*Breakpoint) (bool, e
|
||||
if bp == nil {
|
||||
return false, nil
|
||||
}
|
||||
if bp.Kind == NextDeferBreakpoint {
|
||||
// we just want to check the condition on the goroutine id here
|
||||
bp.Kind = NextBreakpoint
|
||||
defer func() {
|
||||
bp.Kind = NextDeferBreakpoint
|
||||
}()
|
||||
}
|
||||
return bp.CheckCondition(thread)
|
||||
// Internal breakpoint conditions can take multiple different forms:
|
||||
// Step into breakpoints:
|
||||
// runtime.curg.goid == X
|
||||
// Next or StepOut breakpoints:
|
||||
// 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 w
|
||||
}
|
||||
|
@ -124,6 +124,7 @@ type G struct {
|
||||
Status uint64
|
||||
stkbarVar *Variable // stkbar field of g struct
|
||||
stkbarPos int // stkbarPos field of g struct
|
||||
stackhi uint64 // value of stack.hi
|
||||
|
||||
// Information on goroutine location
|
||||
CurrentLoc Location
|
||||
@ -142,6 +143,7 @@ type EvalScope struct {
|
||||
Mem MemoryReadWriter // Target's memory
|
||||
Gvar *Variable
|
||||
BinInfo *BinaryInfo
|
||||
StackHi uint64
|
||||
}
|
||||
|
||||
// IsNilErr is returned when a variable is nil.
|
||||
@ -377,7 +379,7 @@ func (gvar *Variable) parseG() (*G, error) {
|
||||
}
|
||||
gvar = gvar.maybeDereference()
|
||||
}
|
||||
gvar.loadValue(LoadConfig{false, 1, 64, 0, -1})
|
||||
gvar.loadValue(LoadConfig{false, 2, 64, 0, -1})
|
||||
if gvar.Unreadable != nil {
|
||||
return nil, gvar.Unreadable
|
||||
}
|
||||
@ -387,6 +389,12 @@ func (gvar *Variable) parseG() (*G, error) {
|
||||
id, _ := constant.Int64Val(gvar.fieldVariable("goid").Value)
|
||||
gopc, _ := constant.Int64Val(gvar.fieldVariable("gopc").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")
|
||||
stkbarVarPosFld := gvar.fieldVariable("stkbarPos")
|
||||
@ -408,6 +416,7 @@ func (gvar *Variable) parseG() (*G, error) {
|
||||
variable: gvar,
|
||||
stkbarVar: stkbarVar,
|
||||
stkbarPos: int(stkbarPos),
|
||||
stackhi: stackhi,
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user