Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log backtrace on sigsegv and sigbus signals #22

Merged
merged 5 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/ark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to disable this in release builds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be a downside to leave it on? On the upside it could be useful for R developers to get a backtrace of their native code on crash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right -- let's leave it on.


// Initialize harp.
harp::initialize();

Expand Down
5 changes: 5 additions & 0 deletions crates/stdext/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions crates/stdext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
48 changes: 48 additions & 0 deletions crates/stdext/src/traps.rs
Original file line number Diff line number Diff line change
@@ -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);
}