mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime/debug: SetCrashOutput sets the FD for fatal panics
This feature makes it possible to record unhandled panics in any goroutine through a watchdog process (e.g. the same application forked+exec'd as a child in a special mode) that can process the panic report, for example by sending it to a crash-reporting system such as Go telemetry or Sentry. Fixes #42888 Change-Id: I5aa7be8f726bbc70fc650540bd1a14ab60c62ecb Reviewed-on: https://go-review.googlesource.com/c/go/+/547978 Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
13766fe7d8
commit
1bb947b2eb
11 changed files with 208 additions and 9 deletions
|
|
@ -7,8 +7,10 @@
|
|||
package debug
|
||||
|
||||
import (
|
||||
"internal/poll"
|
||||
"os"
|
||||
"runtime"
|
||||
_ "unsafe" // for linkname
|
||||
)
|
||||
|
||||
// PrintStack prints to standard error the stack trace returned by runtime.Stack.
|
||||
|
|
@ -28,3 +30,54 @@ func Stack() []byte {
|
|||
buf = make([]byte, 2*len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// SetCrashOutput configures a single additional file where unhandled
|
||||
// panics and other fatal errors are printed, in addition to standard error.
|
||||
// There is only one additional file: calling SetCrashOutput again
|
||||
// overrides any earlier call; it does not close the previous file.
|
||||
// SetCrashOutput(nil) disables the use of any additional file.
|
||||
func SetCrashOutput(f *os.File) error {
|
||||
fd := ^uintptr(0)
|
||||
if f != nil {
|
||||
// The runtime will write to this file descriptor from
|
||||
// low-level routines during a panic, possibly without
|
||||
// a G, so we must call f.Fd() eagerly. This creates a
|
||||
// danger that that the file descriptor is no longer
|
||||
// valid at the time of the write, because the caller
|
||||
// (incorrectly) called f.Close() and the kernel
|
||||
// reissued the fd in a later call to open(2), leading
|
||||
// to crashes being written to the wrong file.
|
||||
//
|
||||
// So, we duplicate the fd to obtain a private one
|
||||
// that cannot be closed by the user.
|
||||
// This also alleviates us from concerns about the
|
||||
// lifetime and finalization of f.
|
||||
// (DupCloseOnExec returns an fd, not a *File, so
|
||||
// there is no finalizer, and we are responsible for
|
||||
// closing it.)
|
||||
//
|
||||
// The new fd must be close-on-exec, otherwise if the
|
||||
// crash monitor is a child process, it may inherit
|
||||
// it, so it will never see EOF from the pipe even
|
||||
// when this process crashes.
|
||||
//
|
||||
// A side effect of Fd() is that it calls SetBlocking,
|
||||
// which is important so that writes of a crash report
|
||||
// to a full pipe buffer don't get lost.
|
||||
fd2, _, err := poll.DupCloseOnExec(int(f.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.KeepAlive(f) // prevent finalization before dup
|
||||
fd = uintptr(fd2)
|
||||
}
|
||||
if prev := runtime_setCrashFD(fd); prev != ^uintptr(0) {
|
||||
// We use NewFile+Close because it is portable
|
||||
// unlike syscall.Close, whose parameter type varies.
|
||||
os.NewFile(prev, "").Close() // ignore error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:linkname runtime_setCrashFD runtime.setCrashFD
|
||||
func runtime_setCrashFD(uintptr) uintptr
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue