proc: avoid scanning system stack if it's not executing cgo
The runtime calls into g0 in many places, not necessarily using runtime.systemstack or runtime.asmcgocall. One example of this is the call to runtime.newstack inside runtime.morestack. If we stop the process while one goroutine is executing runtime.newstack we would be unable to fully scan its stack because we don't know that we have to switch back to the goroutine stack after runtime.newstack. Instead of tracking down every possible way that the runtime switches to g0 we switch to the goroutine stack immediately after the top of the stack, unless cgo is being executed on the systemstack. Fixes #1066
This commit is contained in:
parent
1acc1547eb
commit
7bec20e5fc
@ -3348,7 +3348,32 @@ func TestSystemstackStacktrace(t *testing.T) {
|
||||
frames, err := g.Stacktrace(100)
|
||||
assertNoError(err, t, "stacktrace")
|
||||
logStacktrace(t, frames)
|
||||
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "!runtime.systemstack", "runtime.startpanic", "main.main"}, frames)
|
||||
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.startpanic", "main.main"}, frames)
|
||||
if m == nil {
|
||||
t.Fatal("see previous loglines")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSystemstackOnRuntimeNewstack(t *testing.T) {
|
||||
// The bug being tested here manifests as follows:
|
||||
// - set a breakpoint somewhere or interrupt the program with Ctrl-C
|
||||
// - try to look at stacktraces of other goroutines
|
||||
// If one of the other goroutines is resizing its own stack the stack
|
||||
// command won't work for it.
|
||||
withTestProcess("binarytrees", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
_, err := setFunctionBreakpoint(p, "main.main")
|
||||
assertNoError(err, t, "setFunctionBreakpoint(main.main)")
|
||||
assertNoError(proc.Continue(p), t, "first continue")
|
||||
_, err = setFunctionBreakpoint(p, "runtime.newstack")
|
||||
assertNoError(err, t, "setFunctionBreakpoint(runtime.newstack)")
|
||||
assertNoError(proc.Continue(p), t, "second continue")
|
||||
g, err := proc.GetG(p.CurrentThread())
|
||||
assertNoError(err, t, "GetG")
|
||||
frames, err := g.Stacktrace(100)
|
||||
assertNoError(err, t, "stacktrace")
|
||||
logStacktrace(t, frames)
|
||||
m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, frames)
|
||||
if m == nil {
|
||||
t.Fatal("see previous loglines")
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"debug/dwarf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
@ -245,34 +246,6 @@ func (it *stackIterator) switchStack() bool {
|
||||
it.top = false
|
||||
return true
|
||||
|
||||
case "runtime.mstart", "runtime.sigtramp":
|
||||
if it.top || !it.systemstack || it.g == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Calls to runtime.systemstack will switch to the systemstack then:
|
||||
// 1. alter the goroutine stack so that it looks like systemstack_switch
|
||||
// was called
|
||||
// 2. alter the system stack so that it looks like the bottom-most frame
|
||||
// belongs to runtime.mstart
|
||||
// If we find a runtime.mstart frame on the system stack of a goroutine
|
||||
// parked on runtime.systemstack_switch we assume runtime.systemstack was
|
||||
// called and continue tracing from the parked position.
|
||||
//
|
||||
// OS Signals are processed on a special signal handling stack that works
|
||||
// similarly to the system stack, runtime.sigtramp is at the bottom of
|
||||
// this stack.
|
||||
|
||||
if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
|
||||
return false
|
||||
}
|
||||
it.systemstack = false
|
||||
it.pc = it.g.PC
|
||||
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP
|
||||
it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP
|
||||
it.top = false
|
||||
return true
|
||||
|
||||
case "runtime.cgocallback_gofunc":
|
||||
// For a detailed description of how this works read the long comment at
|
||||
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
|
||||
@ -309,6 +282,22 @@ func (it *stackIterator) switchStack() bool {
|
||||
return true
|
||||
|
||||
default:
|
||||
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") {
|
||||
// The runtime switches to the system stack in multiple places.
|
||||
// This usually happens through a call to runtime.systemstack but there
|
||||
// are functions that switch to the system stack manually (for example
|
||||
// runtime.morestack).
|
||||
// Since we are only interested in printing the system stack for cgo
|
||||
// calls we switch directly to the goroutine stack if we detect that the
|
||||
// function at the top of the stack is a runtime function.
|
||||
it.systemstack = false
|
||||
it.top = false
|
||||
it.pc = it.g.PC
|
||||
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP
|
||||
it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user