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:
aarzilli 2018-01-04 15:46:51 +01:00 committed by Derek Parker
parent 1acc1547eb
commit 7bec20e5fc
2 changed files with 43 additions and 29 deletions

@ -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
}
}