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

COR-1: Structured Exception Handling #4

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/target
/Cargo.lock
*.dll
*.lib
*.obj
*.exp
14 changes: 13 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
7 changes: 7 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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");
}
25 changes: 25 additions & 0 deletions cpp/seh_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <windows.h>
#include <cstring>

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;
}
}
27 changes: 0 additions & 27 deletions examples/bevy.rs

This file was deleted.

10 changes: 10 additions & 0 deletions examples/windows_seh.rs
Original file line number Diff line number Diff line change
@@ -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(); }
68 changes: 58 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Arc<dyn Fn() + Send + Sync>>> =
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<dyn Fn()>,
process_closure: Arc<dyn Fn() + Send + Sync + 'static>,
/// Command line flag that identifies a child process (and prevents infinite
/// recursion of spawning subprocesses).
child_flag: String,
Expand All @@ -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);
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
}

Expand Down