proc: add options to bypass smart stacktraces (#1686)

Add options to start a stacktrace from the values saved in the
runtime.g struct as well as a way to disable the stackSwitch logic and
just get a normal stacktrace.
This commit is contained in:
Alessandro Arzilli 2019-09-25 19:21:20 +02:00 committed by Derek Parker
parent e994047355
commit efd628616b
17 changed files with 138 additions and 54 deletions

@ -374,13 +374,17 @@ If regex is specified only the source files matching it will be returned.
## stack ## stack
Print stack trace. Print stack trace.
[goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>] [goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>] [-mode <mode>]
-full every stackframe is decorated with the value of its local variables and arguments. -full every stackframe is decorated with the value of its local variables and arguments.
-offsets prints frame offset of each frame. -offsets prints frame offset of each frame.
-defer prints deferred function call stack for each frame. -defer prints deferred function call stack for each frame.
-a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled) -a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled)
-adepth <depth> configures depth of ancestor stacktrace -adepth <depth> configures depth of ancestor stacktrace
-mode <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 Aliases: bt

@ -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) 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) 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) 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) 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 dlv_command(command) | Executes the specified command as if typed at the dlv_prompt
read_file(path) | Reads the file as a string read_file(path) | Reads the file as a string

@ -199,7 +199,7 @@ func TestCore(t *testing.T) {
var panickingStack []proc.Stackframe var panickingStack []proc.Stackframe
for _, g := range gs { for _, g := range gs {
t.Logf("Goroutine %d", g.ID) t.Logf("Goroutine %d", g.ID)
stack, err := g.Stacktrace(10, false) stack, err := g.Stacktrace(10, 0)
if err != nil { if err != nil {
t.Errorf("Stacktrace() on goroutine %v = %v", g, err) t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
} }
@ -343,7 +343,7 @@ func TestCoreWithEmptyString(t *testing.T) {
var mainFrame *proc.Stackframe var mainFrame *proc.Stackframe
mainSearch: mainSearch:
for _, g := range gs { for _, g := range gs {
stack, err := g.Stacktrace(10, false) stack, err := g.Stacktrace(10, 0)
assertNoError(err, t, "Stacktrace()") assertNoError(err, t, "Stacktrace()")
for _, frame := range stack { for _, frame := range stack {
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" { 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)) t.Logf("%d goroutines", len(gs))
foundMain, foundTime := false, false foundMain, foundTime := false, false
for _, g := range gs { for _, g := range gs {
stack, err := g.Stacktrace(10, false) stack, err := g.Stacktrace(10, 0)
if err != nil { if err != nil {
t.Errorf("Stacktrace() on goroutine %v = %v", g, err) t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
} }

@ -646,7 +646,12 @@ func ConvertEvalScope(dbp Process, gid, frame, deferCall int) (*EvalScope, error
thread = g.Thread 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 { if err != nil {
return nil, err return nil, err
} }

@ -917,7 +917,7 @@ func TestStacktraceGoroutine(t *testing.T) {
mainCount := 0 mainCount := 0
for i, g := range gs { for i, g := range gs {
locations, err := g.Stacktrace(40, false) locations, err := g.Stacktrace(40, 0)
if err != nil { if err != nil {
// On windows we do not have frame information for goroutines doing system calls. // 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) 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) found := make([]bool, 10)
for _, g := range gs { for _, g := range gs {
frame := -1 frame := -1
frames, err := g.Stacktrace(10, false) frames, err := g.Stacktrace(10, 0)
if err != nil { if err != nil {
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err) t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
continue continue
@ -1925,7 +1925,7 @@ func TestNextParked(t *testing.T) {
if g.Thread != nil { if g.Thread != nil {
continue continue
} }
frames, _ := g.Stacktrace(5, false) frames, _ := g.Stacktrace(5, 0)
for _, frame := range frames { for _, frame := range frames {
// line 11 is the line where wg.Done is called // 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 { 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) t.Logf("Parked g is: %v\n", parkedg)
frames, _ := parkedg.Stacktrace(20, false) frames, _ := parkedg.Stacktrace(20, 0)
for _, frame := range frames { for _, frame := range frames {
name := "" name := ""
if frame.Call.Fn != nil { if frame.Call.Fn != nil {
@ -2686,7 +2686,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
goid, _ := constant.Int64Val(goidVar.Value) goid, _ := constant.Int64Val(goidVar.Value)
if g := getg(int(goid), gs); g != nil { 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)) assertNoError(err, t, fmt.Sprintf("Stacktrace(goroutine = %d)", goid))
for _, frame := range stack { for _, frame := range stack {
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" { if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" {
@ -2712,7 +2712,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
for _, goid := range stackBarrierGoids { for _, goid := range stackBarrierGoids {
g := getg(goid, gs) g := getg(goid, gs)
stack, err := g.Stacktrace(200, false) stack, err := g.Stacktrace(200, 0)
assertNoError(err, t, "Stacktrace()") assertNoError(err, t, "Stacktrace()")
// Check that either main.main or main.main.func1 appear in the // 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)) assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx))
t.Logf("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") assertNoError(proc.Continue(p), t, "second continue")
g, err := proc.GetG(p.CurrentThread()) g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, "GetG") assertNoError(err, t, "GetG")
frames, err := g.Stacktrace(100, false) frames, err := g.Stacktrace(100, 0)
assertNoError(err, t, "stacktrace") assertNoError(err, t, "stacktrace")
logStacktrace(t, p.BinInfo(), frames) logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames) m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames)
@ -3380,7 +3380,7 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) {
break break
} }
} }
frames, err := g.Stacktrace(100, false) frames, err := g.Stacktrace(100, 0)
assertNoError(err, t, "stacktrace") assertNoError(err, t, "stacktrace")
logStacktrace(t, p.BinInfo(), frames) logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, 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) { withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "main.main") setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
frames, err := p.SelectedGoroutine().Stacktrace(10, false) frames, err := p.SelectedGoroutine().Stacktrace(10, 0)
assertNoError(err, t, "Stacktrace") assertNoError(err, t, "Stacktrace")
scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frames[2:]...) scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frames[2:]...)
args, _ := scope.FunctionArguments(normalLoadConfig) args, _ := scope.FunctionArguments(normalLoadConfig)
@ -3949,7 +3949,7 @@ func TestIssue1264(t *testing.T) {
func TestReadDefer(t *testing.T) { func TestReadDefer(t *testing.T) {
withTestProcess("deferstack", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("deferstack", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue") 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") assertNoError(err, t, "Stacktrace")
logStacktrace(t, p.BinInfo(), frames) logStacktrace(t, p.BinInfo(), frames)

@ -101,13 +101,13 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
return nil, err return nil, err
} }
so := thread.BinInfo().PCToImage(regs.PC()) 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 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() stkbar, err := g.stkbar()
if err != nil { if err != nil {
return nil, err return nil, err
@ -122,19 +122,35 @@ func (g *G) stackIterator() (*stackIterator, error) {
return newStackIterator( return newStackIterator(
g.variable.bi, g.Thread, g.variable.bi, g.Thread,
g.variable.bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs), 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) so := g.variable.bi.PCToImage(g.PC)
return newStackIterator( return newStackIterator(
g.variable.bi, g.variable.mem, g.variable.bi, g.variable.mem,
g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP), 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. // Stacktrace returns the stack trace for a goroutine.
// Note the locations in the array are return addresses not call addresses. // Note the locations in the array are return addresses not call addresses.
func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) { func (g *G) Stacktrace(depth int, opts StacktraceOptions) ([]Stackframe, error) {
it, err := g.stackIterator() it, err := g.stackIterator(opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -142,7 +158,7 @@ func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if readDefers { if opts&StacktraceReadDefers != 0 {
g.readDefers(frames) g.readDefers(frames)
} }
return frames, nil 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 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) g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use)
opts StacktraceOptions
} }
type savedLR struct { type savedLR struct {
@ -184,7 +202,7 @@ type savedLR struct {
val uint64 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 stackBarrierFunc := bi.LookupFunc["runtime.stackBarrier"] // stack barriers were removed in Go 1.9
var stackBarrierPC uint64 var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil { 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. // Next points the iterator to the next stack frame.
@ -233,8 +251,10 @@ func (it *stackIterator) Next() bool {
it.stkbar = it.stkbar[1:] it.stkbar = it.stkbar[1:]
} }
if it.switchStack() { if it.opts&StacktraceSimple == 0 {
return true if it.switchStack() {
return true
}
} }
if it.frame.Ret <= 0 { if it.frame.Ret <= 0 {
@ -435,6 +455,10 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) {
if depth < 0 { if depth < 0 {
return nil, errors.New("negative maximum stack depth") 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) frames := make([]Stackframe, 0, depth+1)
for it.Next() { for it.Next() {
frames = it.appendInlineCalls(frames, it.Frame()) frames = it.appendInlineCalls(frames, it.Frame())

@ -90,7 +90,7 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
} }
frames, err = ThreadStacktrace(thread, 1) frames, err = ThreadStacktrace(thread, 1)
} else { } else {
frames, err = g.Stacktrace(1, true) frames, err = g.Stacktrace(1, StacktraceReadDefers)
} }
if err != nil { if err != nil {
return Stackframe{}, Stackframe{}, err return Stackframe{}, Stackframe{}, err

@ -229,7 +229,7 @@ func (g *G) Defer() *Defer {
// UserCurrent returns the location the users code is at, // UserCurrent returns the location the users code is at,
// or was at before entering a runtime function. // or was at before entering a runtime function.
func (g *G) UserCurrent() Location { func (g *G) UserCurrent() Location {
it, err := g.stackIterator() it, err := g.stackIterator(0)
if err != nil { if err != nil {
return g.CurrentLoc return g.CurrentLoc
} }

@ -258,13 +258,17 @@ When connected to a headless instance started with the --accept-multiclient, pas
Show source around current point or provided linespec.`}, Show source around current point or provided linespec.`},
{aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace. {aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace.
[goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>] [goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>] [-mode <mode>]
-full every stackframe is decorated with the value of its local variables and arguments. -full every stackframe is decorated with the value of its local variables and arguments.
-offsets prints frame offset of each frame. -offsets prints frame offset of each frame.
-defer prints deferred function call stack for each frame. -defer prints deferred function call stack for each frame.
-a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled) -a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled)
-adepth <depth> configures depth of ancestor stacktrace -adepth <depth> configures depth of ancestor stacktrace
-mode <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"}, {aliases: []string{"frame"},
cmdFn: func(t *Term, ctx callContext, arg string) error { 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)) fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
if bPrintStack { 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 { if err != nil {
return err return err
} }
@ -745,7 +749,7 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi
if frame < 0 { if frame < 0 {
return fmt.Errorf("Invalid frame %d", frame) 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 { if err != nil {
return err return err
} }
@ -1473,7 +1477,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
if sa.full { if sa.full {
cfg = &ShortLoadConfig 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 { if err != nil {
return err return err
} }
@ -1496,10 +1500,10 @@ func stackCommand(t *Term, ctx callContext, args string) error {
} }
type stackArgs struct { type stackArgs struct {
depth int depth int
full bool full bool
offsets bool offsets bool
readDefers bool opts api.StacktraceOptions
ancestors int ancestors int
ancestorDepth int ancestorDepth int
@ -1530,7 +1534,23 @@ func parseStackArgs(argstr string) (stackArgs, error) {
case "-offsets": case "-offsets":
r.offsets = true r.offsets = true
case "-defer": 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": case "-a":
i++ i++
n, err := numarg("-a") 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 return state.CurrentThread.File, state.CurrentThread.Line, true, nil
case len(args) == 0 && ctx.scoped(): 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 { if err != nil {
return "", 0, false, err return "", 0, false, err
} }

@ -1099,7 +1099,13 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
} }
} }
if len(args) > 4 && args[4] != starlark.None { 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 { if err != nil {
return starlark.None, decorateError(thread, err) return starlark.None, decorateError(thread, err)
} }
@ -1115,6 +1121,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Full, "Full") err = unmarshalStarlarkValue(kv[1], &rpcArgs.Full, "Full")
case "Defers": case "Defers":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Defers, "Defers") err = unmarshalStarlarkValue(kv[1], &rpcArgs.Defers, "Defers")
case "Opts":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Opts, "Opts")
case "Cfg": case "Cfg":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Cfg, "Cfg") err = unmarshalStarlarkValue(kv[1], &rpcArgs.Cfg, "Cfg")
default: default:

@ -479,3 +479,22 @@ type Ancestor struct {
Unreadable string 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
)

@ -100,7 +100,7 @@ type Client interface {
ListGoroutines(start, count int) ([]*api.Goroutine, int, error) ListGoroutines(start, count int) ([]*api.Goroutine, int, error)
// Returns stacktrace // 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 // Returns ancestor stacktraces
Ancestors(goroutineID int, numAncestors int, depth int) ([]api.Ancestor, error) Ancestors(goroutineID int, numAncestors int, depth int) ([]api.Ancestor, error)

@ -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 // Stacktrace returns a list of Stackframes for the given goroutine. The
// length of the returned list will be min(stack_len, depth). // 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. // 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() d.processMutex.Lock()
defer d.processMutex.Unlock() defer d.processMutex.Unlock()
@ -1007,7 +1007,7 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, readDefers bool, cfg *proc
if g == nil { if g == nil {
rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth) rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth)
} else { } else {
rawlocs, err = g.Stacktrace(depth, readDefers) rawlocs, err = g.Stacktrace(depth, proc.StacktraceOptions(opts))
} }
if err != nil { if err != nil {
return nil, err return nil, err

@ -87,7 +87,7 @@ func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations
if args.Full { if args.Full {
loadcfg = &defaultLoadConfig 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 { if err != nil {
return err return err
} }

@ -310,9 +310,9 @@ func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, err
return out.Goroutines, out.Nextg, 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 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 return out.Locations, err
} }

@ -155,7 +155,8 @@ type StacktraceIn struct {
Id int Id int
Depth int Depth int
Full bool Full bool
Defers bool // read deferred functions Defers bool // read deferred functions (equivalent to passing StacktraceReadDefers in Opts)
Opts api.StacktraceOptions
Cfg *api.LoadConfig Cfg *api.LoadConfig
} }
@ -172,8 +173,11 @@ func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error {
if cfg == nil && arg.Full { if cfg == nil && arg.Full {
cfg = &api.LoadConfig{true, 1, 64, 64, -1} cfg = &api.LoadConfig{true, 1, 64, 64, -1}
} }
if arg.Defers {
arg.Opts |= api.StacktraceReadDefers
}
var err error 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 return err
} }

@ -813,7 +813,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
found := make([]bool, 10) found := make([]bool, 10)
for _, g := range gs { 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)) assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID))
for i, frame := range frames { for i, frame := range frames {
if frame.Function == nil { if frame.Function == nil {
@ -847,7 +847,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
t.Fatalf("Continue(): %v\n", state.Err) 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") assertNoError(err, t, "Stacktrace")
cur := 3 cur := 3
@ -926,7 +926,7 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListRegisters()") assertError(err, t, "ListRegisters()")
_, _, err = c.ListGoroutines(0, 0) _, _, err = c.ListGoroutines(0, 0)
assertError(err, t, "ListGoroutines()") assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, false, &normalLoadConfig) _, err = c.Stacktrace(gid, 10, 0, &normalLoadConfig)
assertError(err, t, "Stacktrace()") assertError(err, t, "Stacktrace()")
_, err = c.FindLocation(api.EvalScope{gid, 0, 0}, "+1") _, err = c.FindLocation(api.EvalScope{gid, 0, 0}, "+1")
assertError(err, t, "FindLocation()") assertError(err, t, "FindLocation()")
@ -1049,7 +1049,7 @@ func TestNegativeStackDepthBug(t *testing.T) {
ch := c.Continue() ch := c.Continue()
state := <-ch state := <-ch
assertNoError(state.Err, t, "Continue()") assertNoError(state.Err, t, "Continue()")
_, err = c.Stacktrace(-1, -2, false, &normalLoadConfig) _, err = c.Stacktrace(-1, -2, 0, &normalLoadConfig)
assertError(err, t, "Stacktrace()") assertError(err, t, "Stacktrace()")
}) })
} }