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:
parent
e994047355
commit
efd628616b
@ -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()")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user