proc: catch fatal runtime errors (#1502)

Like we do with unrecovered panics, create a default breakpoint to
catch runtime errors that will cause the program to terminate.
Primarily intended to give users the opportunity to examine the state
of a deadlocked process.
This commit is contained in:
Alessandro Arzilli 2019-02-27 23:28:25 +01:00 committed by Derek Parker
parent 520d792422
commit ac3b1c7a78
3 changed files with 51 additions and 3 deletions

12
_fixtures/testdeadlock.go Normal file

@ -0,0 +1,12 @@
package main
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
<-ch1
ch2 <- "done"
}()
<-ch2
ch1 <- "done"
}

@ -19,8 +19,16 @@ var ErrNotExecutable = errors.New("not an executable file")
// only possible on recorded (traced) programs.
var ErrNotRecorded = errors.New("not a recording")
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
const UnrecoveredPanic = "unrecovered-panic"
const (
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
UnrecoveredPanic = "unrecovered-panic"
// FatalThrow is the name given to the breakpoint triggered when the target process dies because of a fatal runtime error
FatalThrow = "runtime-fatal-throw"
unrecoveredPanicID = -1
fatalThrowID = -2
)
// ErrProcessExited indicates that the process has exited and contains both
// process id and exit status.
@ -61,6 +69,7 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri
p.SetSelectedGoroutine(g)
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
createFatalThrowBreakpoint(p, writeBreakpoint)
return nil
}
@ -732,13 +741,22 @@ func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpoint
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
}
if err == nil {
bp, err := p.Breakpoints().SetWithID(-1, panicpc, writeBreakpoint)
bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpc, writeBreakpoint)
if err == nil {
bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
}
}
}
func createFatalThrowBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
fatalpc, err := FindFunctionLocation(p, "runtime.fatalthrow", true, 0)
if err == nil {
bp, err := p.Breakpoints().SetWithID(fatalThrowID, fatalpc, writeBreakpoint)
if err == nil {
bp.Name = FatalThrow
}
}
}
// FirstPCAfterPrologue returns the address of the first

@ -4196,3 +4196,21 @@ func TestIssue1469(t *testing.T) {
}
})
}
func TestDeadlockBreakpoint(t *testing.T) {
if buildMode == "pie" {
t.Skip("See https://github.com/golang/go/issues/29322")
}
deadlockBp := proc.FatalThrow
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
deadlockBp = proc.UnrecoveredPanic
}
withTestProcess("testdeadlock", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
bp := p.CurrentThread().Breakpoint()
if bp.Breakpoint == nil || bp.Name != deadlockBp {
t.Fatalf("did not stop at deadlock breakpoint %v", bp)
}
})
}