diff options
author | Zach Reizner <zachr@google.com> | 2019-01-28 14:05:23 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-02-07 03:02:13 -0800 |
commit | b3fa5c95af68c66835a5c5670552eea84ad346ce (patch) | |
tree | 136472ca3ae046c6816c2009d3b8f1ebf412988a /src/panic_hook.rs | |
parent | bae43dd4c98182736ea601b313e03620a72ccf83 (diff) | |
download | crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.tar crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.tar.gz crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.tar.bz2 crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.tar.lz crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.tar.xz crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.tar.zst crosvm-b3fa5c95af68c66835a5c5670552eea84ad346ce.zip |
main: add panic hook that redirects to syslog
The default panic hook prints panic information and a backtrace to stderr, where it gets dropped into /dev/null in the typical crostini context. This change adds a panic hook that will call the default panic hook with stderr redirected to a pipe, which will then get forwarded to syslog. The new hook also forces an abort at the end to ensure the crash reporter sees the panicked crosvm process, which will generate a minidump for later debugging. TEST=manually add panic!() observe /var/log/messages BUG=None Change-Id: I4e76afe811943e55cec91761447e03b949a674a4 Reviewed-on: https://chromium-review.googlesource.com/1440881 Commit-Ready: Zach Reizner <zachr@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Tested-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Stephen Barber <smbarber@chromium.org>
Diffstat (limited to 'src/panic_hook.rs')
-rw-r--r-- | src/panic_hook.rs | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/src/panic_hook.rs b/src/panic_hook.rs new file mode 100644 index 0000000..d61b7d5 --- /dev/null +++ b/src/panic_hook.rs @@ -0,0 +1,105 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::env; +use std::fs::File; +use std::io::{stderr, Read}; +use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::panic::{self, PanicInfo}; +use std::process::abort; +use std::string::String; + +use libc::{close, dup, dup2, pipe2, O_NONBLOCK, STDERR_FILENO}; + +// Opens a pipe and puts the write end into the stderr FD slot. On success, returns the read end of +// the pipe and the old stderr as a pair of files. +fn redirect_stderr() -> Option<(File, File)> { + let mut fds = [-1, -1]; + unsafe { + // Trivially safe because the return value is checked. + let old_stderr = dup(STDERR_FILENO); + if old_stderr == -1 { + return None; + } + // Safe because pipe2 will only ever write two integers to our array and we check output. + let mut ret = pipe2(fds.as_mut_ptr(), O_NONBLOCK); + if ret != 0 { + // Leaks FDs, but not important right before abort. + return None; + } + // Safe because the FD we are duplicating is owned by us. + ret = dup2(fds[1], STDERR_FILENO); + if ret == -1 { + // Leaks FDs, but not important right before abort. + return None; + } + // The write end is no longer needed. + close(fds[1]); + // Safe because each of the fds was the result of a successful FD creation syscall. + Some((File::from_raw_fd(fds[0]), File::from_raw_fd(old_stderr))) + } +} + +// Sets stderr to the given file. Returns true on success. +fn restore_stderr(stderr: File) -> bool { + let fd = stderr.into_raw_fd(); + + // Safe because fd is guaranteed to be valid and replacing stderr should be an atomic operation. + unsafe { dup2(fd, STDERR_FILENO) != -1 } +} + +// Sends as much information about the panic as possible to syslog. +fn log_panic_info( + default_panic: &Box<dyn Fn(&PanicInfo) + Sync + Send + 'static>, + info: &PanicInfo, +) { + // Grab a lock of stderr to prevent concurrent threads from trampling on our stderr capturing + // procedure. The default_panic procedure likely uses stderr.lock as well, but the mutex inside + // stderr is reentrant, so it will not dead-lock on this thread. + let stderr = stderr(); + let _stderr_lock = stderr.lock(); + + // Redirect stderr to a pipe we can read from later. + let (mut read_file, old_stderr) = match redirect_stderr() { + Some(f) => f, + None => { + error!("failed to capture stderr during panic"); + return; + } + }; + // Only through the default panic handler can we get a stacktrace. It only ever prints to + // stderr, hence all the previous code to redirect it to a pipe we can read. + env::set_var("RUST_BACKTRACE", "1"); + default_panic(info); + + // Closes the write end of the pipe so that we can reach EOF in read_to_string. Also allows + // others to write to stderr without failure. + if !restore_stderr(old_stderr) { + error!("failed to restore stderr during panic"); + return; + } + drop(_stderr_lock); + + let mut panic_output = String::new(); + // Ignore errors and print what we got. + let _ = read_file.read_to_string(&mut panic_output); + // Split by line because the logging facilities do not handle embedded new lines well. + for line in panic_output.lines() { + error!("{}", line); + } +} + +/// The intent of our panic hook is to get panic info and a stacktrace into the syslog, even for +/// jailed subprocesses. It will always abort on panic to ensure a minidump is generated. +/// +/// Note that jailed processes will usually have a stacktrace of <unknown> because the backtrace +/// routines attempt to open this binary and are unable to do so in a jail. +pub fn set_panic_hook() { + let default_panic = panic::take_hook(); + panic::set_hook(Box::new(move |info| { + log_panic_info(&default_panic, info); + // Abort to trigger the crash reporter so that a minidump is generated. + abort(); + })); +} |