From 87e016768830071ca55a59ff041fcd0c9a764255 Mon Sep 17 00:00:00 2001 From: Luke Carr Date: Fri, 15 Sep 2023 01:09:04 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Proof=20of=20concept=20for=20SEH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++ Cargo.toml | 14 ++++++++- build.rs | 7 +++++ cpp/seh_wrapper.cpp | 25 +++++++++++++++ examples/bevy.rs | 27 ---------------- examples/windows_seh.rs | 10 ++++++ src/lib.rs | 68 +++++++++++++++++++++++++++++++++++------ 7 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 build.rs create mode 100644 cpp/seh_wrapper.cpp delete mode 100644 examples/bevy.rs create mode 100644 examples/windows_seh.rs diff --git a/.gitignore b/.gitignore index 4fffb2f..7838d83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /target /Cargo.lock +*.dll +*.lib +*.obj +*.exp diff --git a/Cargo.toml b/Cargo.toml index 54883c8..0dc95d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,18 @@ repository = "https://github.com/subtalegames/cortex" license = "MIT OR Apache-2.0" readme = "README.md" +[features] +seh = ["lazy_static"] + +[dependencies] +lazy_static = { version = "1.4", optional = true } + [dev-dependencies] -bevy = "0.11" native-dialog = "0.6" + +[build-dependencies] +cc = "1.0" + +[[example]] +name = "windows_seh" +required-features = ["seh"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..5a47d83 --- /dev/null +++ b/build.rs @@ -0,0 +1,7 @@ +fn main() { + println!("cargo:rerun-if-changed=cpp/seh_wrapper.cpp"); + cc::Build::new() + .cpp(true) + .file("cpp/seh_wrapper.cpp") + .compile("seh_wrapper"); +} diff --git a/cpp/seh_wrapper.cpp b/cpp/seh_wrapper.cpp new file mode 100644 index 0000000..433e9c8 --- /dev/null +++ b/cpp/seh_wrapper.cpp @@ -0,0 +1,25 @@ +#include +#include + +typedef void (*RustCallback)(); + +extern "C" { + struct ExceptionInfo { + DWORD code; + PVOID exception_address; + }; + + __declspec(dllexport) ExceptionInfo run_with_seh(RustCallback callback) { + ExceptionInfo exception_info = {0, nullptr}; + EXCEPTION_POINTERS* pException = nullptr; + + __try { + callback(); + } __except(pException = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) { + exception_info.code = pException->ExceptionRecord->ExceptionCode; + exception_info.exception_address = pException->ExceptionRecord->ExceptionAddress; + } + + return exception_info; + } +} \ No newline at end of file diff --git a/examples/bevy.rs b/examples/bevy.rs deleted file mode 100644 index 36fb19f..0000000 --- a/examples/bevy.rs +++ /dev/null @@ -1,27 +0,0 @@ -use bevy::prelude::*; -use subtale_cortex::CrashHandler; - -fn run_game() { - let mut app = App::new(); - - app.add_systems(Startup, hello_world); - - app.run(); -} - -fn hello_world() { - info!("Hello world from Bevy!"); -} - -fn crash_handler(output: std::process::Output) -> Result<(), Box> { - // Here you would handle the process output that resulted in - // the game crashing. - - Ok(()) -} - -fn main() { - let _ = CrashHandler::with_process(run_game) - .crash_handler(crash_handler) - .run(); -} diff --git a/examples/windows_seh.rs b/examples/windows_seh.rs new file mode 100644 index 0000000..fa5eaeb --- /dev/null +++ b/examples/windows_seh.rs @@ -0,0 +1,10 @@ +use subtale_cortex::CrashHandler; + +fn run_application() { + println!("Running application!"); + unsafe { + *(0 as *mut i32) = 42; + } +} + +fn main() { let _ = CrashHandler::with_process(run_application).run(); } diff --git a/src/lib.rs b/src/lib.rs index bd7ba98..f1c73e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,39 @@ +use std::sync::Arc; + +#[cfg(feature = "seh")] +use {lazy_static::lazy_static, std::sync::Mutex}; + +#[cfg(feature = "seh")] +#[repr(C)] +pub struct ExceptionInfo { + code: u32, + exception_address: *const (), +} + +#[cfg(feature = "seh")] +extern "C" { + fn run_with_seh(callback: extern "C" fn()) -> ExceptionInfo; +} + +#[cfg(feature = "seh")] +lazy_static! { + static ref GLOBAL_PROCESS_CLOSURE: Mutex>> = + Mutex::new(None); +} + +#[cfg(feature = "seh")] +extern "C" fn invoke_global_closure() { + if let Some(ref closure) = *GLOBAL_PROCESS_CLOSURE.lock().unwrap() { + closure(); + } +} + /// A wrapper around a process closure that handles crashes by running the /// closure as a subprocess and invoking a crash handler closure if the /// subprocess fails. pub struct CrashHandler { /// Closure that runs a process. - process_closure: Box, + process_closure: Arc, /// Command line flag that identifies a child process (and prevents infinite /// recursion of spawning subprocesses). child_flag: String, @@ -22,7 +52,7 @@ impl Default for CrashHandler { /// `eprintln!`, and `RUST_BACKTRACE=full`). fn default() -> Self { Self { - process_closure: Box::new(|| println!("Hello, world!")), + process_closure: Arc::new(|| println!("Hello, world!")), child_flag: "--cortex-child".to_string(), crash_handler_closure: Box::new(|output| { let status = output.status.code().unwrap_or(-1); @@ -41,9 +71,9 @@ impl CrashHandler { pub fn new() -> Self { Self::default() } /// Create a new crash reporter from the given closure that runs a process. - pub fn with_process(process: impl Fn() + 'static) -> Self { + pub fn with_process(process: impl Fn() + Send + Sync + 'static) -> Self { Self { - process_closure: Box::new(process), + process_closure: Arc::new(process), ..Default::default() } } @@ -97,8 +127,25 @@ impl CrashHandler { args.remove(0); args.push(self.child_flag.clone()); - // Spawn current exe as subprocess and read process output - let output = std::process::Command::new(std::env::current_exe()?) + #[cfg(all(target_os = "windows", feature = "seh"))] + { + *GLOBAL_PROCESS_CLOSURE.lock().unwrap() = Some(self.process_closure.clone()); + + let exception_info = unsafe { run_with_seh(invoke_global_closure) }; + + if exception_info.code != 0 { + eprintln!( + "Error code: {:#x}\nException Address: {:?}", + exception_info.code, exception_info.exception_address + ); + return Ok(false); + } + } + + #[cfg(not(all(target_os = "windows", feature = "seh")))] + { + // Spawn current exe as subprocess and read process output + let output = std::process::Command::new(std::env::current_exe()?) // Passthrough the current arguments .args(args) // Passthrough the current environment @@ -108,10 +155,11 @@ impl CrashHandler { // Spawn the subprocess and capture its output .output()?; - // If the subprocess failed, call the crash handler closure - if !output.status.success() { - (self.crash_handler_closure)(output)?; - return Ok(false); + // If the subprocess failed, call the crash handler closure + if !output.status.success() { + (self.crash_handler_closure)(output)?; + return Ok(false); + } } }