diff --git a/Cargo.lock b/Cargo.lock index 7386b70a5..cf84011a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1080,9 +1080,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" @@ -1118,12 +1118,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "lsp-types" @@ -1914,6 +1911,11 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stdext" version = "0.1.0" +dependencies = [ + "backtrace", + "libc", + "log", +] [[package]] name = "str_indices" diff --git a/crates/ark/src/main.rs b/crates/ark/src/main.rs index 932847235..42039a8c9 100644 --- a/crates/ark/src/main.rs +++ b/crates/ark/src/main.rs @@ -258,6 +258,11 @@ fn main() { // Initialize the logger. logger::initialize(log_file.as_deref()); + // Register segfault handler to get a backtrace. Should be after + // initialising `log!`. Note that R will not override this handler + // because we set `R_SignalHandlers` to 0 before startup. + stdext::traps::register_trap_handlers(); + // Initialize harp. harp::initialize(); diff --git a/crates/stdext/Cargo.toml b/crates/stdext/Cargo.toml index e404c3216..85bf3dc86 100644 --- a/crates/stdext/Cargo.toml +++ b/crates/stdext/Cargo.toml @@ -7,4 +7,9 @@ description = """ Useful extensions to the Rust standard library. """ +[dependencies] +backtrace = "0.3.67" +libc = "0.2.146" +log = "0.4.18" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/stdext/src/lib.rs b/crates/stdext/src/lib.rs index cb778566e..cf7037a5a 100644 --- a/crates/stdext/src/lib.rs +++ b/crates/stdext/src/lib.rs @@ -14,6 +14,7 @@ pub mod local; pub mod ok; pub mod push; pub mod spawn; +pub mod traps; pub mod unwrap; pub use crate::join::Joined; diff --git a/crates/stdext/src/traps.rs b/crates/stdext/src/traps.rs new file mode 100644 index 000000000..0ed8627ea --- /dev/null +++ b/crates/stdext/src/traps.rs @@ -0,0 +1,48 @@ +// +// traps.rs +// +// Copyright (C) 2023 Posit Software, PBC. All rights reserved. +// +// + +// Call this after initialising the `log` package. Instruments SIGBUS, +// SIGSEGV, and SIGILL to generate a backtrace with `info` verbosity +// (lowest level so it's always reported). +// +// This uses `signal()` instead of `sigaction()` for Windows support +// (SIGSEGV is one of the rare supported signals) +// +// Note that Rust also has a SIGSEGV handler to catch stack overflows. In +// this case it displays an informative message and aborts the program (no +// segfaults in Rust!). Ideally we'd save the Rust handler and notify +// it. However the only safe way to notify an old handler on Unixes is to +// use `sigaction()` so that we get the information needed to determine the +// type of handler (old or new school). So we'd need to make a different +// implementation for Windows (which only supports old style) and for Unix, +// and this doesn't seem worth it. +pub fn register_trap_handlers() { + unsafe { + libc::signal(libc::SIGBUS, backtrace_handler as libc::sighandler_t); + libc::signal(libc::SIGSEGV, backtrace_handler as libc::sighandler_t); + libc::signal(libc::SIGILL, backtrace_handler as libc::sighandler_t); + } +} + +extern "C" fn backtrace_handler(signum: libc::c_int) { + // Prevent infloop into the handler + unsafe { + libc::signal(signum, libc::SIG_DFL); + } + + let mut header = format!("\n>>> Backtrace for signal {}", signum); + + if let Some(name) = std::thread::current().name() { + header = format!("{}\n>>> In thread {}", header, name); + } + + // Unlike asynchronous signals, SIGSEGV and SIGBUS are synchronous and + // always delivered to the thread that caused it, so we can just + // capture the current thread's backtrace + let bt = backtrace::Backtrace::new(); + log::info!("{}\n{:?}", header, bt); +}