Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#[cfg(any(target_os = "linux", target_os = "android"))]
extern crate rust_minidump_writer_linux;
mod breakpad_crash_generator;
mod crash_generation;
mod ipc_server;
mod logging;
mod phc;
mod platform;
use crash_helper_common::{BreakpadData, BreakpadRawData, IPCConnector, IPCListener, Pid};
use std::ffi::{c_char, CStr, OsString};
use crash_generation::finalize_breakpad_minidump;
use ipc_server::{IPCServer, IPCServerState};
/// Runs the crash generator process logic, this includes the IPC used by
/// processes to signal that they crashed, the IPC used to retrieve crash
/// reports from the crash helper process and the logic used to generate the
/// actual minidumps. This function will return when the main process has
/// disconnected from the crash helper.
///
/// # Safety
///
/// `minidump_data`, `listener` and `pipe` must point to valid,
/// nul-terminated C strings. `breakpad_data` must be a valid file descriptor
/// (Linux) or point to a nul-terminated C string using either byte (macOS)
/// or wide characters (Windows).
#[cfg(not(target_os = "android"))]
#[no_mangle]
pub unsafe extern "C" fn crash_generator_logic_desktop(
client_pid: Pid,
client_handle: *const c_char,
breakpad_data: BreakpadRawData,
minidump_path: *const c_char,
build_id: *const c_char,
listener: *const c_char,
pipe: *const c_char,
) -> i32 {
// HACK: This constant is declared in the `mach2` crate but using a `c_uint`
// type which makes it incompatible with the return value of
// `bootstrap_look_up()` and thus prevents the `match` expression below from
// compiling correctly. We re-declare it here as a `c_int` until upstream
// issue #67 is fixed.
#[cfg(any(target_os = "ios", target_os = "macos"))]
const BOOTSTRAP_UNKNOWN_SERVICE: std::ffi::c_int = 1102;
// SAFETY: We have not spawned any other threads at this point.
unsafe {
platform::daemonize();
}
logging::init();
let client_handle = unsafe { CStr::from_ptr(client_handle) };
let client_handle = unwrap_with_message(
platform::get_client_handle(client_handle),
"Could not deserialize the client process handle",
);
let breakpad_data = BreakpadData::new(breakpad_data);
let minidump_path = unsafe { CStr::from_ptr(minidump_path) }
.to_owned()
.into_string()
.unwrap();
let minidump_path = OsString::from(minidump_path);
let build_id = unsafe { CStr::from_ptr(build_id) };
let build_id = unwrap_with_message(
build_id.to_str(),
"BuildID is not a valid UTF-8 string"
).to_string();
let listener = unsafe { CStr::from_ptr(listener) };
let listener = unwrap_with_message(
IPCListener::deserialize(listener, client_pid),
"Could not parse the crash generator's listener",
);
let pipe = unsafe { CStr::from_ptr(pipe) };
let connector = IPCConnector::deserialize(pipe);
let connector = match connector {
// If the main process went down before we could deserialize the
// connector then deserialization will fail, handle this case as an
// expected error rather than a panic.
#[cfg(any(target_os = "ios", target_os = "macos"))]
Err(crash_helper_common::errors::IPCError::Deserialize(
crash_helper_common::PlatformError::BootstrapLookUp(_rv @ BOOTSTRAP_UNKNOWN_SERVICE),
)) => {
log::error!("Could not reach out to the main process, shutting down");
return -1;
}
Err(e) => {
log::error!("Could not deserialize connector: {e:?}");
return -1;
}
Ok(connector) => connector,
};
let ipc_server = IPCServer::new(
client_pid,
client_handle,
listener,
connector,
breakpad_data,
minidump_path,
build_id,
);
match ipc_server {
Ok(ipc_server) => main_loop(ipc_server),
Err(e) => {
log::error!("Could not create the IPC server (error: {e:?})");
#[cfg(not(target_os = "android"))]
panic!("Could not create the IPC server (error: {e:?})");
#[allow(unreachable_code)]
-1
}
}
}
/// Runs the crash generator process logic, this includes the IPC used by
/// processes to signal that they crashed, the IPC used to retrieve crash
/// reports from the crash helper process and the logic used to generate the
/// actual minidumps. The logic will run in a separate thread and this
/// function will return immediately after launching it.
///
/// # Safety
///
/// `minidump_data` must point to valid, nul-terminated C strings. `server_pipe`
/// must be a valid file descriptor and `breakpad_data` must also be a valid
/// file descriptor compatible with Breakpad's crash generation server.
#[cfg(target_os = "android")]
#[no_mangle]
pub unsafe extern "C" fn crash_generator_logic_android(
build_id: *const c_char,
pid: Pid,
breakpad_data: BreakpadRawData,
minidump_path: *const c_char,
pipe: crash_helper_common::RawIPCConnector,
) {
logging::init();
let build_id = unsafe { CStr::from_ptr(build_id) }
.to_owned()
.into_string()
.unwrap();
let breakpad_data = BreakpadData::new(breakpad_data);
let minidump_path = unsafe { CStr::from_ptr(minidump_path) }
.to_owned()
.into_string()
.unwrap();
let minidump_path = OsString::from(minidump_path);
// On Android the main thread is used to respond to the intents so we
// can't block it. Run the crash generation loop in a separate thread.
let _ = std::thread::spawn(move || {
let listener = IPCListener::new(0)
.expect("IPCListener creation failed!");
// SAFETY: The `pipe` file descriptor passed in from the caller is
// guaranteed to be valid.
let connector = match unsafe { IPCConnector::from_raw_connector(pipe) } {
Ok(connector) => connector,
Err(error) => {
log::error!("Could not use the pipe (error: {error})");
return;
}
};
let ipc_server = match IPCServer::new(
pid,
/* client_handle */ None,
listener,
connector,
breakpad_data,
minidump_path,
build_id,
) {
Ok(ipc_server) => ipc_server,
Err(error) => {
log::error!("Could not create the IPC server (error: {error})");
return;
}
};
main_loop(ipc_server);
});
}
fn main_loop(mut ipc_server: IPCServer) -> i32 {
loop {
match ipc_server.run() {
Ok(_result @ IPCServerState::ClientDisconnected) => {
return 0;
}
Err(error) => {
log::error!("The crashhelper encountered an error, exiting (error: {error})");
return -1;
}
_ => {} // Go on
}
}
}
#[cfg(not(target_os = "android"))]
fn unwrap_with_message<T, E: std::fmt::Display>(res: Result<T, E>, error_string: &str) -> T {
match res {
Ok(value) => value,
Err(error) => {
log::error!("{error_string} (error: {error})");
panic!("{} (error: {})", error_string, error);
}
}
}