diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 492b486e..0f49cc48 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -374,13 +374,17 @@ If regex is specified only the source files matching it will be returned. ## stack Print stack trace. - [goroutine ] [frame ] stack [] [-full] [-offsets] [-defer] [-a ] [-adepth ] + [goroutine ] [frame ] stack [] [-full] [-offsets] [-defer] [-a ] [-adepth ] [-mode ] -full every stackframe is decorated with the value of its local variables and arguments. -offsets prints frame offset of each frame. -defer prints deferred function call stack for each frame. -a prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled) -adepth configures depth of ancestor stacktrace + -mode specifies the stacktrace mode, possible values are: + normal - attempts to automatically switch between cgo frames and go frames + simple - disables automatic switch between cgo and go + fromg - starts from the registers stored in the runtime.g struct Aliases: bt diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index 02e1eba1..5338b713 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -51,7 +51,7 @@ process_pid() | Equivalent to API call [ProcessPid](https://godoc.org/github.com recorded() | Equivalent to API call [Recorded](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Recorded) restart(Position, ResetArgs, NewArgs) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart) set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set) -stacktrace(Id, Depth, Full, Defers, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace) +stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace) state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State) dlv_command(command) | Executes the specified command as if typed at the dlv_prompt read_file(path) | Reads the file as a string diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 68eeb1c4..3f708d40 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -199,7 +199,7 @@ func TestCore(t *testing.T) { var panickingStack []proc.Stackframe for _, g := range gs { t.Logf("Goroutine %d", g.ID) - stack, err := g.Stacktrace(10, false) + stack, err := g.Stacktrace(10, 0) if err != nil { t.Errorf("Stacktrace() on goroutine %v = %v", g, err) } @@ -343,7 +343,7 @@ func TestCoreWithEmptyString(t *testing.T) { var mainFrame *proc.Stackframe mainSearch: for _, g := range gs { - stack, err := g.Stacktrace(10, false) + stack, err := g.Stacktrace(10, 0) assertNoError(err, t, "Stacktrace()") for _, frame := range stack { if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" { @@ -390,7 +390,7 @@ func TestMinidump(t *testing.T) { t.Logf("%d goroutines", len(gs)) foundMain, foundTime := false, false for _, g := range gs { - stack, err := g.Stacktrace(10, false) + stack, err := g.Stacktrace(10, 0) if err != nil { t.Errorf("Stacktrace() on goroutine %v = %v", g, err) } diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 09538a48..fa4d9b38 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -646,7 +646,12 @@ func ConvertEvalScope(dbp Process, gid, frame, deferCall int) (*EvalScope, error thread = g.Thread } - locs, err := g.Stacktrace(frame+1, deferCall > 0) + var opts StacktraceOptions + if deferCall > 0 { + opts = StacktraceReadDefers + } + + locs, err := g.Stacktrace(frame+1, opts) if err != nil { return nil, err } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index fef516d4..a5370f35 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -917,7 +917,7 @@ func TestStacktraceGoroutine(t *testing.T) { mainCount := 0 for i, g := range gs { - locations, err := g.Stacktrace(40, false) + locations, err := g.Stacktrace(40, 0) if err != nil { // On windows we do not have frame information for goroutines doing system calls. t.Logf("Could not retrieve goroutine stack for goid=%d: %v", g.ID, err) @@ -1226,7 +1226,7 @@ func TestFrameEvaluation(t *testing.T) { found := make([]bool, 10) for _, g := range gs { frame := -1 - frames, err := g.Stacktrace(10, false) + frames, err := g.Stacktrace(10, 0) if err != nil { t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err) continue @@ -1925,7 +1925,7 @@ func TestNextParked(t *testing.T) { if g.Thread != nil { continue } - frames, _ := g.Stacktrace(5, false) + frames, _ := g.Stacktrace(5, 0) for _, frame := range frames { // line 11 is the line where wg.Done is called if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.sayhi" && frame.Current.Line < 11 { @@ -1980,7 +1980,7 @@ func TestStepParked(t *testing.T) { } t.Logf("Parked g is: %v\n", parkedg) - frames, _ := parkedg.Stacktrace(20, false) + frames, _ := parkedg.Stacktrace(20, 0) for _, frame := range frames { name := "" if frame.Call.Fn != nil { @@ -2686,7 +2686,7 @@ func TestStacktraceWithBarriers(t *testing.T) { goid, _ := constant.Int64Val(goidVar.Value) if g := getg(int(goid), gs); g != nil { - stack, err := g.Stacktrace(50, false) + stack, err := g.Stacktrace(50, 0) assertNoError(err, t, fmt.Sprintf("Stacktrace(goroutine = %d)", goid)) for _, frame := range stack { if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" { @@ -2712,7 +2712,7 @@ func TestStacktraceWithBarriers(t *testing.T) { for _, goid := range stackBarrierGoids { g := getg(goid, gs) - stack, err := g.Stacktrace(200, false) + stack, err := g.Stacktrace(200, 0) assertNoError(err, t, "Stacktrace()") // Check that either main.main or main.main.func1 appear in the @@ -3260,7 +3260,7 @@ func TestCgoStacktrace(t *testing.T) { } } - frames, err := g.Stacktrace(100, false) + frames, err := g.Stacktrace(100, 0) assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx)) t.Logf("iteration step %d", itidx) @@ -3347,7 +3347,7 @@ func TestSystemstackStacktrace(t *testing.T) { assertNoError(proc.Continue(p), t, "second continue") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") - frames, err := g.Stacktrace(100, false) + frames, err := g.Stacktrace(100, 0) assertNoError(err, t, "stacktrace") logStacktrace(t, p.BinInfo(), frames) m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames) @@ -3380,7 +3380,7 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) { break } } - frames, err := g.Stacktrace(100, false) + frames, err := g.Stacktrace(100, 0) assertNoError(err, t, "stacktrace") logStacktrace(t, p.BinInfo(), frames) m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, frames) @@ -3396,7 +3396,7 @@ func TestIssue1034(t *testing.T) { withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") assertNoError(proc.Continue(p), t, "Continue()") - frames, err := p.SelectedGoroutine().Stacktrace(10, false) + frames, err := p.SelectedGoroutine().Stacktrace(10, 0) assertNoError(err, t, "Stacktrace") scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frames[2:]...) args, _ := scope.FunctionArguments(normalLoadConfig) @@ -3949,7 +3949,7 @@ func TestIssue1264(t *testing.T) { func TestReadDefer(t *testing.T) { withTestProcess("deferstack", t, func(p proc.Process, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue") - frames, err := p.SelectedGoroutine().Stacktrace(10, true) + frames, err := p.SelectedGoroutine().Stacktrace(10, proc.StacktraceReadDefers) assertNoError(err, t, "Stacktrace") logStacktrace(t, p.BinInfo(), frames) diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index de7de20c..eec9f6de 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -101,13 +101,13 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) { return nil, err } so := thread.BinInfo().PCToImage(regs.PC()) - it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs), 0, nil, -1, nil) + it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs), 0, nil, -1, nil, 0) return it.stacktrace(depth) } - return g.Stacktrace(depth, false) + return g.Stacktrace(depth, 0) } -func (g *G) stackIterator() (*stackIterator, error) { +func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) { stkbar, err := g.stkbar() if err != nil { return nil, err @@ -122,19 +122,35 @@ func (g *G) stackIterator() (*stackIterator, error) { return newStackIterator( g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs), - g.stackhi, stkbar, g.stkbarPos, g), nil + g.stackhi, stkbar, g.stkbarPos, g, opts), nil } so := g.variable.bi.PCToImage(g.PC) return newStackIterator( g.variable.bi, g.variable.mem, g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP), - g.stackhi, stkbar, g.stkbarPos, g), nil + g.stackhi, stkbar, g.stkbarPos, g, opts), nil } +type StacktraceOptions uint16 + +const ( + // StacktraceReadDefers requests a stacktrace decorated with deferred calls + // for each frame. + StacktraceReadDefers StacktraceOptions = 1 << iota + + // StacktraceSimple requests a stacktrace where no stack switches will be + // attempted. + StacktraceSimple + + // StacktraceG requests a stacktrace starting with the register + // values saved in the runtime.g structure. + StacktraceG +) + // Stacktrace returns the stack trace for a goroutine. // Note the locations in the array are return addresses not call addresses. -func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) { - it, err := g.stackIterator() +func (g *G) Stacktrace(depth int, opts StacktraceOptions) ([]Stackframe, error) { + it, err := g.stackIterator(opts) if err != nil { return nil, err } @@ -142,7 +158,7 @@ func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) { if err != nil { return nil, err } - if readDefers { + if opts&StacktraceReadDefers != 0 { g.readDefers(frames) } return frames, nil @@ -177,6 +193,8 @@ type stackIterator struct { g *G // the goroutine being stacktraced, nil if we are stacktracing a goroutine-less thread g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use) + + opts StacktraceOptions } type savedLR struct { @@ -184,7 +202,7 @@ type savedLR struct { val uint64 } -func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int, g *G) *stackIterator { +func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int, g *G, opts StacktraceOptions) *stackIterator { stackBarrierFunc := bi.LookupFunc["runtime.stackBarrier"] // stack barriers were removed in Go 1.9 var stackBarrierPC uint64 if stackBarrierFunc != nil && stkbar != nil { @@ -216,7 +234,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegiste } } } - return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp} + return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp, opts: opts} } // Next points the iterator to the next stack frame. @@ -233,8 +251,10 @@ func (it *stackIterator) Next() bool { it.stkbar = it.stkbar[1:] } - if it.switchStack() { - return true + if it.opts&StacktraceSimple == 0 { + if it.switchStack() { + return true + } } if it.frame.Ret <= 0 { @@ -435,6 +455,10 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) { if depth < 0 { return nil, errors.New("negative maximum stack depth") } + if it.opts&StacktraceG != 0 && it.g != nil { + it.switchToGoroutineStack() + it.top = true + } frames := make([]Stackframe, 0, depth+1) for it.Next() { frames = it.appendInlineCalls(frames, it.Frame()) diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 886eb371..d5b0a7f2 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -90,7 +90,7 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) { } frames, err = ThreadStacktrace(thread, 1) } else { - frames, err = g.Stacktrace(1, true) + frames, err = g.Stacktrace(1, StacktraceReadDefers) } if err != nil { return Stackframe{}, Stackframe{}, err diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 7f01c71d..1243ccf2 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -229,7 +229,7 @@ func (g *G) Defer() *Defer { // UserCurrent returns the location the users code is at, // or was at before entering a runtime function. func (g *G) UserCurrent() Location { - it, err := g.stackIterator() + it, err := g.stackIterator(0) if err != nil { return g.CurrentLoc } diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index f3eb8701..2a1b52ec 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -258,13 +258,17 @@ When connected to a headless instance started with the --accept-multiclient, pas Show source around current point or provided linespec.`}, {aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace. - [goroutine ] [frame ] stack [] [-full] [-offsets] [-defer] [-a ] [-adepth ] + [goroutine ] [frame ] stack [] [-full] [-offsets] [-defer] [-a ] [-adepth ] [-mode ] -full every stackframe is decorated with the value of its local variables and arguments. -offsets prints frame offset of each frame. -defer prints deferred function call stack for each frame. -a prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled) -adepth configures depth of ancestor stacktrace + -mode specifies the stacktrace mode, possible values are: + normal - attempts to automatically switch between cgo frames and go frames + simple - disables automatic switch between cgo and go + fromg - starts from the registers stored in the runtime.g struct `}, {aliases: []string{"frame"}, cmdFn: func(t *Term, ctx callContext, arg string) error { @@ -601,7 +605,7 @@ func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, bPrin } fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) if bPrintStack { - stack, err := t.client.Stacktrace(g.ID, 10, false, nil) + stack, err := t.client.Stacktrace(g.ID, 10, 0, nil) if err != nil { return err } @@ -745,7 +749,7 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi if frame < 0 { return fmt.Errorf("Invalid frame %d", frame) } - stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, frame, false, nil) + stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, frame, 0, nil) if err != nil { return err } @@ -1473,7 +1477,7 @@ func stackCommand(t *Term, ctx callContext, args string) error { if sa.full { cfg = &ShortLoadConfig } - stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, sa.depth, sa.readDefers, cfg) + stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, sa.depth, sa.opts, cfg) if err != nil { return err } @@ -1496,10 +1500,10 @@ func stackCommand(t *Term, ctx callContext, args string) error { } type stackArgs struct { - depth int - full bool - offsets bool - readDefers bool + depth int + full bool + offsets bool + opts api.StacktraceOptions ancestors int ancestorDepth int @@ -1530,7 +1534,23 @@ func parseStackArgs(argstr string) (stackArgs, error) { case "-offsets": r.offsets = true case "-defer": - r.readDefers = true + r.opts |= api.StacktraceReadDefers + case "-mode": + i++ + if i >= len(args) { + return stackArgs{}, fmt.Errorf("expected normal, simple or fromg after -mode") + } + switch args[i] { + case "normal": + r.opts &^= api.StacktraceSimple + r.opts &^= api.StacktraceG + case "simple": + r.opts |= api.StacktraceSimple + case "fromg": + r.opts |= api.StacktraceG | api.StacktraceSimple + default: + return stackArgs{}, fmt.Errorf("expected normal, simple or fromg after -mode") + } case "-a": i++ n, err := numarg("-a") @@ -1578,7 +1598,7 @@ func getLocation(t *Term, ctx callContext, args string, showContext bool) (file return state.CurrentThread.File, state.CurrentThread.Line, true, nil case len(args) == 0 && ctx.scoped(): - locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, false, nil) + locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, 0, nil) if err != nil { return "", 0, false, err } diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go index bd93d339..51ed0c5d 100644 --- a/pkg/terminal/starbind/starlark_mapping.go +++ b/pkg/terminal/starbind/starlark_mapping.go @@ -1099,7 +1099,13 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { } } if len(args) > 4 && args[4] != starlark.None { - err := unmarshalStarlarkValue(args[4], &rpcArgs.Cfg, "Cfg") + err := unmarshalStarlarkValue(args[4], &rpcArgs.Opts, "Opts") + if err != nil { + return starlark.None, decorateError(thread, err) + } + } + if len(args) > 5 && args[5] != starlark.None { + err := unmarshalStarlarkValue(args[5], &rpcArgs.Cfg, "Cfg") if err != nil { return starlark.None, decorateError(thread, err) } @@ -1115,6 +1121,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { err = unmarshalStarlarkValue(kv[1], &rpcArgs.Full, "Full") case "Defers": err = unmarshalStarlarkValue(kv[1], &rpcArgs.Defers, "Defers") + case "Opts": + err = unmarshalStarlarkValue(kv[1], &rpcArgs.Opts, "Opts") case "Cfg": err = unmarshalStarlarkValue(kv[1], &rpcArgs.Cfg, "Cfg") default: diff --git a/service/api/types.go b/service/api/types.go index cc2117ff..e532e66e 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -479,3 +479,22 @@ type Ancestor struct { Unreadable string } + +// StacktraceOptions is the type of the Opts field of StacktraceIn that +// configures the stacktrace. +// Tracks proc.StacktraceOptions +type StacktraceOptions uint16 + +const ( + // StacktraceReadDefers requests a stacktrace decorated with deferred calls + // for each frame. + StacktraceReadDefers StacktraceOptions = 1 << iota + + // StacktraceSimple requests a stacktrace where no stack switches will be + // attempted. + StacktraceSimple + + // StacktraceG requests a stacktrace starting with the register + // values saved in the runtime.g structure. + StacktraceG +) diff --git a/service/client.go b/service/client.go index 0b3a59e6..f0e9cf63 100644 --- a/service/client.go +++ b/service/client.go @@ -100,7 +100,7 @@ type Client interface { ListGoroutines(start, count int) ([]*api.Goroutine, int, error) // Returns stacktrace - Stacktrace(goroutineID int, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) + Stacktrace(goroutineID int, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error) // Returns ancestor stacktraces Ancestors(goroutineID int, numAncestors int, depth int) ([]api.Ancestor, error) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 98b3e4d8..1b724c7b 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -989,7 +989,7 @@ func (d *Debugger) Goroutines(start, count int) ([]*api.Goroutine, int, error) { // Stacktrace returns a list of Stackframes for the given goroutine. The // length of the returned list will be min(stack_len, depth). // If 'full' is true, then local vars, function args, etc will be returned as well. -func (d *Debugger) Stacktrace(goroutineID, depth int, readDefers bool, cfg *proc.LoadConfig) ([]api.Stackframe, error) { +func (d *Debugger) Stacktrace(goroutineID, depth int, opts api.StacktraceOptions, cfg *proc.LoadConfig) ([]api.Stackframe, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -1007,7 +1007,7 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, readDefers bool, cfg *proc if g == nil { rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth) } else { - rawlocs, err = g.Stacktrace(depth, readDefers) + rawlocs, err = g.Stacktrace(depth, proc.StacktraceOptions(opts)) } if err != nil { return nil, err diff --git a/service/rpc1/server.go b/service/rpc1/server.go index ef746374..371887df 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -87,7 +87,7 @@ func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations if args.Full { loadcfg = &defaultLoadConfig } - locs, err := s.debugger.Stacktrace(args.Id, args.Depth, false, loadcfg) + locs, err := s.debugger.Stacktrace(args.Id, args.Depth, 0, loadcfg) if err != nil { return err } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 835cd199..44b07ba3 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -310,9 +310,9 @@ func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, err return out.Goroutines, out.Nextg, err } -func (c *RPCClient) Stacktrace(goroutineId, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) { +func (c *RPCClient) Stacktrace(goroutineId, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error) { var out StacktraceOut - err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, readDefers, cfg}, &out) + err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, false, opts, cfg}, &out) return out.Locations, err } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 0460f925..711d987f 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -155,7 +155,8 @@ type StacktraceIn struct { Id int Depth int Full bool - Defers bool // read deferred functions + Defers bool // read deferred functions (equivalent to passing StacktraceReadDefers in Opts) + Opts api.StacktraceOptions Cfg *api.LoadConfig } @@ -172,8 +173,11 @@ func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error { if cfg == nil && arg.Full { cfg = &api.LoadConfig{true, 1, 64, 64, -1} } + if arg.Defers { + arg.Opts |= api.StacktraceReadDefers + } var err error - out.Locations, err = s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Defers, api.LoadConfigToProc(cfg)) + out.Locations, err = s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Opts, api.LoadConfigToProc(cfg)) return err } diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 47084b24..523e7a43 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -813,7 +813,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { assertNoError(err, t, "GoroutinesInfo()") found := make([]bool, 10) for _, g := range gs { - frames, err := c.Stacktrace(g.ID, 10, false, &normalLoadConfig) + frames, err := c.Stacktrace(g.ID, 10, 0, &normalLoadConfig) assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID)) for i, frame := range frames { if frame.Function == nil { @@ -847,7 +847,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { t.Fatalf("Continue(): %v\n", state.Err) } - frames, err := c.Stacktrace(-1, 10, false, &normalLoadConfig) + frames, err := c.Stacktrace(-1, 10, 0, &normalLoadConfig) assertNoError(err, t, "Stacktrace") cur := 3 @@ -926,7 +926,7 @@ func TestIssue355(t *testing.T) { assertError(err, t, "ListRegisters()") _, _, err = c.ListGoroutines(0, 0) assertError(err, t, "ListGoroutines()") - _, err = c.Stacktrace(gid, 10, false, &normalLoadConfig) + _, err = c.Stacktrace(gid, 10, 0, &normalLoadConfig) assertError(err, t, "Stacktrace()") _, err = c.FindLocation(api.EvalScope{gid, 0, 0}, "+1") assertError(err, t, "FindLocation()") @@ -1049,7 +1049,7 @@ func TestNegativeStackDepthBug(t *testing.T) { ch := c.Continue() state := <-ch assertNoError(state.Err, t, "Continue()") - _, err = c.Stacktrace(-1, -2, false, &normalLoadConfig) + _, err = c.Stacktrace(-1, -2, 0, &normalLoadConfig) assertError(err, t, "Stacktrace()") }) }