From 2c431cb961f739d8325002e7c87798e00cc1eef9 Mon Sep 17 00:00:00 2001 From: Ted Mielczarek Date: Mon, 27 Mar 2017 14:40:22 -0400 Subject: [PATCH] bug 1275780 - capture Rust panic message in crash reports. r=froydnj MozReview-Commit-ID: IUlYqPEtkgg --HG-- extra : rebase_source : c328db9b979eb2ff1f469b6ebc4a63e7ac337c63 --- toolkit/crashreporter/nsExceptionHandler.cpp | 21 ++++++++- .../test/unit/test_crash_rust_panic.js | 2 +- toolkit/library/rust/shared/lib.rs | 47 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index f7ccda818481..78a20aa54c3d 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -116,6 +116,13 @@ using google_breakpad::PageAllocator; using namespace mozilla; using mozilla::ipc::CrashReporterClient; +// From toolkit/library/rust/shared/lib.rs +extern "C" { + void install_rust_panic_hook(); + bool get_rust_panic_reason(char** reason, size_t* length); +} + + namespace CrashReporter { #ifdef XP_WIN32 @@ -1131,7 +1138,17 @@ bool MinidumpCallback( WriteGlobalMemoryStatus(&apiData, &eventFile); #endif // XP_WIN - if (gMozCrashReason) { + char* rust_panic_reason; + size_t rust_panic_len; + if (get_rust_panic_reason(&rust_panic_reason, &rust_panic_len)) { + // rust_panic_reason is not null-terminated. + WriteLiteral(apiData, "MozCrashReason="); + apiData.WriteBuffer(rust_panic_reason, rust_panic_len); + WriteLiteral(apiData, "\n"); + WriteLiteral(eventFile, "MozCrashReason="); + eventFile.WriteBuffer(rust_panic_reason, rust_panic_len); + WriteLiteral(eventFile, "\n"); + } else if (gMozCrashReason) { WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason); WriteAnnotation(eventFile, "MozCrashReason", gMozCrashReason); } @@ -1577,6 +1594,8 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, if (gExceptionHandler) return NS_ERROR_ALREADY_INITIALIZED; + install_rust_panic_hook(); + #if !defined(DEBUG) || defined(MOZ_WIDGET_GONK) // In non-debug builds, enable the crash reporter by default, and allow // disabling it with the MOZ_CRASHREPORTER_DISABLE environment variable. diff --git a/toolkit/crashreporter/test/unit/test_crash_rust_panic.js b/toolkit/crashreporter/test/unit/test_crash_rust_panic.js index c8691c74c2b0..68c50a8b5365 100644 --- a/toolkit/crashreporter/test/unit/test_crash_rust_panic.js +++ b/toolkit/crashreporter/test/unit/test_crash_rust_panic.js @@ -4,7 +4,7 @@ function run_test() { Components.classes["@mozilla.org/xpcom/debug;1"].getService(Components.interfaces.nsIDebug2).rustPanic("OH NO"); }, function(mdump, extra) { - //TODO: check some extra things? + do_check_eq(extra.MozCrashReason, "OH NO"); }, // process will exit with a zero exit status true); diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index f0c1a487ecce..936fd1875511 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -11,11 +11,58 @@ extern crate rust_url_capi; #[cfg(feature = "quantum_render")] extern crate webrender_bindings; +use std::boxed::Box; use std::ffi::CStr; use std::os::raw::c_char; +use std::panic; /// Used to implement `nsIDebug2::RustPanic` for testing purposes. #[no_mangle] pub extern "C" fn intentional_panic(message: *const c_char) { panic!("{}", unsafe { CStr::from_ptr(message) }.to_string_lossy()); } + +/// Contains the panic message, if set. +static mut PANIC_REASON: Option<(*const str, usize)> = None; + +/// Configure a panic hook to capture panic messages for crash reports. +/// +/// We don't store this in `gMozCrashReason` because: +/// a) Rust strings aren't null-terminated, so we'd have to allocate +/// memory to get a null-terminated string +/// b) The panic=abort handler is going to call `abort()` on non-Windows, +/// which is `mozalloc_abort` for us, which will use `MOZ_CRASH` and +/// overwrite `gMozCrashReason` with an unhelpful string. +#[no_mangle] +pub extern "C" fn install_rust_panic_hook() { + panic::set_hook(Box::new(|info| { + // Try to handle &str/String payloads, which should handle 99% of cases. + let payload = info.payload(); + // We'll hold a raw *const str here, but it will be OK because + // Rust is going to abort the process before the payload could be + // deallocated. + if let Some(s) = payload.downcast_ref::<&str>() { + unsafe { PANIC_REASON = Some((*s as *const str, s.len())) } + } else if let Some(s) = payload.downcast_ref::() { + unsafe { PANIC_REASON = Some((s.as_str() as *const str, s.len())) } + } else { + // Not the most helpful thing, but seems unlikely to happen + // in practice. + println!("Unhandled panic payload!"); + } + })); +} + +#[no_mangle] +pub extern "C" fn get_rust_panic_reason(reason: *mut *const c_char, length: *mut usize) -> bool { + unsafe { + match PANIC_REASON { + Some((s, len)) => { + *reason = s as *const c_char; + *length = len; + true + } + None => false, + } + } +}