├── .vscode └── settings.json ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── examples ├── minimal.rs ├── registers.rs ├── raii.rs └── functions.rs ├── Cargo.toml ├── LICENSE ├── src ├── exception.rs ├── registers.rs ├── code.rs ├── stub.c └── lib.rs └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "windows.h": "c" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo Files 2 | /target 3 | /Cargo.lock 4 | 5 | # macOS Desktop Files 6 | *.DS_Store 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build_and_test: 14 | runs-on: windows-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build 18 | run: cargo build --verbose 19 | - name: Run Tests 20 | run: cargo test --tests --verbose 21 | -------------------------------------------------------------------------------- /examples/minimal.rs: -------------------------------------------------------------------------------- 1 | // Disable deny unconditional_panics 2 | 3 | const INVALID_PTR: *mut i32 = core::mem::align_of::() as _; 4 | 5 | fn with_propagation() -> Result<(), microseh::Exception> { 6 | // ... 7 | 8 | microseh::try_seh(|| unsafe { 9 | INVALID_PTR.read_volatile(); 10 | })?; 11 | 12 | // Execution will not reach this point if an exception is thrown. 13 | 14 | // ... 15 | 16 | Ok(()) 17 | } 18 | 19 | fn main() { 20 | if let Err(ex) = with_propagation() { 21 | println!("{:?}: {}", ex.address(), ex); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/registers.rs: -------------------------------------------------------------------------------- 1 | // Disable deny unconditional_panics 2 | 3 | const INVALID_PTR: *mut i32 = core::mem::align_of::() as _; 4 | 5 | fn main() { 6 | if let Err(ex) = microseh::try_seh(|| unsafe { 7 | INVALID_PTR.read_volatile(); 8 | }) { 9 | // You can access registers with the following syntax: 10 | // ex.registers().eax() - x86 11 | // ex.registers().rax() - x64 12 | // ex.registers().x0() - aarch64 13 | 14 | println!("register dump:"); 15 | for register in ex.registers().list().iter() { 16 | println!("{:x}", register); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "microseh" 3 | version = "1.1.2" 4 | authors = ["sonodima"] 5 | edition = "2021" 6 | 7 | description = "Structured Exception Handling (SEH) for Rust" 8 | homepage = "https://github.com/sonodima/microseh" 9 | repository = "https://github.com/sonodima/microseh" 10 | readme = "README.md" 11 | license = "MIT" 12 | 13 | keywords = ["seh", "windows", "hardware", "exception", "handler"] 14 | categories = ["os"] 15 | 16 | 17 | [build-dependencies] 18 | cc = "1.1" 19 | 20 | [features] 21 | default = ["std"] 22 | std = [] 23 | 24 | [package.metadata.docs.rs] 25 | default-target = "x86_64-pc-windows-msvc" 26 | rustc-args = ["--cfg", "docsrs"] 27 | rustdoc-args = ["--cfg", "docsrs"] 28 | targets = [ 29 | "i686-pc-windows-msvc", 30 | "x86_64-pc-windows-msvc", 31 | "aarch64-pc-windows-msvc", 32 | ] 33 | -------------------------------------------------------------------------------- /examples/raii.rs: -------------------------------------------------------------------------------- 1 | const INVALID_PTR: *mut i32 = core::mem::align_of::() as _; 2 | 3 | struct Resource { 4 | data: i32, 5 | } 6 | 7 | impl Resource { 8 | fn new() -> Self { 9 | Self { data: 1337 } 10 | } 11 | } 12 | 13 | impl Drop for Resource { 14 | fn drop(&mut self) { 15 | println!("resource dropped"); 16 | } 17 | } 18 | 19 | fn main() { 20 | let ex = microseh::try_seh(|| unsafe { 21 | let res = Resource::new(); 22 | println!("data: {}", res.data); 23 | INVALID_PTR.read_volatile(); 24 | }); 25 | 26 | // U.B. if an exception is thrown, the resource will not be dropped! 27 | // You could choose to move the resource out of the closure like so: 28 | // let res = Resource::new(); 29 | // let ex = microseh::try_seh(|| unsafe { 30 | // println!("data: {}", res.data); 31 | // INVALID_PTR.read_volatile(); 32 | // }); 33 | 34 | if let Err(ex) = ex { 35 | println!("{:?}: {}", ex.address(), ex); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sonodima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/functions.rs: -------------------------------------------------------------------------------- 1 | const INVALID_PTR: *mut i32 = core::mem::align_of::() as _; 2 | 3 | unsafe fn unsafe_func() { 4 | INVALID_PTR.write_volatile(0); 5 | } 6 | 7 | fn rust_func() { 8 | unsafe { unsafe_func() }; 9 | } 10 | 11 | extern "system" fn system_func() { 12 | rust_func(); 13 | } 14 | 15 | fn main() { 16 | // You can pass in closures: 17 | let ex = microseh::try_seh(|| unsafe { INVALID_PTR.write_volatile(0) }); 18 | if let Err(ex) = ex { 19 | println!("closure: {:?}", ex); 20 | } 21 | 22 | // Or functions: 23 | let ex = microseh::try_seh(rust_func); 24 | if let Err(ex) = ex { 25 | println!("rust_func: {:?}", ex); 26 | } 27 | 28 | // But if you want to use it with FFI: 29 | let ex = microseh::try_seh(|| system_func()); 30 | if let Err(ex) = ex { 31 | println!("system_func: {:?}", ex); 32 | } 33 | 34 | // Or you want to call an unsafe function: 35 | let ex = microseh::try_seh(|| unsafe { unsafe_func() }); 36 | if let Err(ex) = ex { 37 | println!("unsafe_func: {:?}", ex); 38 | } 39 | 40 | // And you can also pass any return value: 41 | let ex = microseh::try_seh(|| 1337); 42 | if let Ok(val) = ex { 43 | println!("ret_val: {}", val); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/exception.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | use crate::{code::ExceptionCode, registers::Registers}; 4 | 5 | /// Represents an exception that occurs during program execution, along with additional 6 | /// context information. 7 | #[repr(C)] 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 9 | pub struct Exception { 10 | code: ExceptionCode, 11 | address: *mut c_void, 12 | #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] 13 | registers: Registers, 14 | } 15 | 16 | impl Exception { 17 | /// Creates a new exception with default values. 18 | /// 19 | /// Exceptions created with this function are to be considered invalid, and should 20 | /// only be used as a placeholder. 21 | pub(crate) fn empty() -> Self { 22 | Self { 23 | code: ExceptionCode::Invalid, 24 | address: core::ptr::null_mut(), 25 | #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] 26 | registers: Registers::empty(), 27 | } 28 | } 29 | 30 | /// # Returns 31 | /// 32 | /// The system-specific code of the exception. 33 | pub fn code(&self) -> ExceptionCode { 34 | self.code 35 | } 36 | 37 | /// # Returns 38 | /// 39 | /// A pointer to the memory address where the exception occurred. 40 | pub fn address(&self) -> *mut c_void { 41 | self.address 42 | } 43 | 44 | /// # Returns 45 | /// 46 | /// The dump of the CPU registers at the time of the exception. 47 | #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] 48 | pub fn registers(&self) -> &Registers { 49 | &self.registers 50 | } 51 | } 52 | 53 | impl core::fmt::Display for Exception { 54 | /// Formats the exception into a human-readable string. 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `f` - The formatter to write to. 59 | /// 60 | /// # Returns 61 | /// 62 | /// Whether the formatting operation succeeded. 63 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 64 | write!(f, "{}", self.code) 65 | } 66 | } 67 | 68 | /// In case the `std` feature is enabled, this implementation allows the exception to be 69 | /// treated as a standard error. 70 | #[cfg(feature = "std")] 71 | impl std::error::Error for Exception {} 72 | -------------------------------------------------------------------------------- /src/registers.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "x86")] 2 | const NUM_REGISTERS: usize = 9; 3 | #[cfg(target_arch = "x86_64")] 4 | const NUM_REGISTERS: usize = 17; 5 | #[cfg(target_arch = "aarch64")] 6 | const NUM_REGISTERS: usize = 33; 7 | 8 | /// Represents a saved state of the CPU registers. 9 | #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] 10 | #[repr(C)] 11 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 12 | pub struct Registers { 13 | list: [usize; NUM_REGISTERS], 14 | } 15 | 16 | #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] 17 | impl Registers { 18 | /// Creates an instance of `Registers` with all registers initialized to zero. 19 | pub fn empty() -> Self { 20 | Self { 21 | list: [0; NUM_REGISTERS], 22 | } 23 | } 24 | 25 | /// # Returns 26 | /// 27 | /// All the registers in the CPU state, in the order they were saved. 28 | pub fn list(&self) -> &[usize] { 29 | &self.list 30 | } 31 | } 32 | 33 | macro_rules! get_reg { 34 | ($reg:ident, $index:expr) => { 35 | #[inline] 36 | #[doc = concat!("# Returns\n\nThe value of the `", stringify!($reg), "` register.")] 37 | pub fn $reg(&self) -> usize { 38 | self.list[$index] 39 | } 40 | }; 41 | } 42 | 43 | #[cfg(target_arch = "x86")] 44 | impl Registers { 45 | get_reg!(eax, 0); 46 | get_reg!(ecx, 1); 47 | get_reg!(edx, 2); 48 | get_reg!(ebx, 3); 49 | get_reg!(esp, 4); 50 | get_reg!(ebp, 5); 51 | get_reg!(esi, 6); 52 | get_reg!(edi, 7); 53 | get_reg!(eip, 8); 54 | } 55 | 56 | #[cfg(target_arch = "x86_64")] 57 | impl Registers { 58 | get_reg!(rax, 0); 59 | get_reg!(rcx, 1); 60 | get_reg!(rdx, 2); 61 | get_reg!(rbx, 3); 62 | get_reg!(rsp, 4); 63 | get_reg!(rbp, 5); 64 | get_reg!(rsi, 6); 65 | get_reg!(rdi, 7); 66 | get_reg!(r8, 8); 67 | get_reg!(r9, 9); 68 | get_reg!(r10, 10); 69 | get_reg!(r11, 11); 70 | get_reg!(r12, 12); 71 | get_reg!(r13, 13); 72 | get_reg!(r14, 14); 73 | get_reg!(r15, 15); 74 | get_reg!(rip, 16); 75 | } 76 | 77 | #[cfg(target_arch = "aarch64")] 78 | impl Registers { 79 | get_reg!(x0, 0); 80 | get_reg!(x1, 1); 81 | get_reg!(x2, 2); 82 | get_reg!(x3, 3); 83 | get_reg!(x4, 4); 84 | get_reg!(x5, 5); 85 | get_reg!(x6, 6); 86 | get_reg!(x7, 7); 87 | get_reg!(x8, 8); 88 | get_reg!(x9, 9); 89 | get_reg!(x10, 10); 90 | get_reg!(x11, 11); 91 | get_reg!(x12, 12); 92 | get_reg!(x13, 13); 93 | get_reg!(x14, 14); 94 | get_reg!(x15, 15); 95 | get_reg!(x16, 16); 96 | get_reg!(x17, 17); 97 | get_reg!(x18, 18); 98 | get_reg!(x19, 19); 99 | get_reg!(x20, 20); 100 | get_reg!(x21, 21); 101 | get_reg!(x22, 22); 102 | get_reg!(x23, 23); 103 | get_reg!(x24, 24); 104 | get_reg!(x25, 25); 105 | get_reg!(x26, 26); 106 | get_reg!(x27, 27); 107 | get_reg!(x28, 28); 108 | get_reg!(fp, 29); 109 | get_reg!(lr, 30); 110 | get_reg!(sp, 31); 111 | get_reg!(pc, 32); 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

MicroSEH 🔴

2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | > MicroSEH is a tiny library that implements Structured Exception Handling (SEH) in Rust and can catch 15 | > and handle hardware exceptions. 16 | 17 | ## Why? 18 | 19 | Hardware exceptions are a very powerful tool for specific use cases. One such use case is to 20 | detect and handle illegal instructions at runtime. 21 | 22 | ## Implementation 23 | 24 | It turns out that implementing SEH in pure Rust has its own issues (as seen in 25 | [this article from NAMAZSO](https://engineering.zeroitlab.com/2022/03/13/rust-seh)) 26 | 27 | This library uses a different, simpler approach, which is to use a `C` stub that calls back into Rust, wrapping 28 | the call in a `__try __except` block. 29 | 30 | ## Usage 31 | 32 | Add this to your `Cargo.toml`: 33 | 34 | ```toml 35 | [dependencies] 36 | microseh = "1.1" 37 | ``` 38 | 39 | **Minimal Example:** Dereference an invalid pointer without crashing the program, and return the handled exception. 40 | 41 | ```rust 42 | fn guarded() -> Result<(), microseh::Exception> { 43 | microseh::try_seh(|| unsafe { 44 | // Read from an unallocated memory region. (we create an aligned not-null 45 | // pointer to skip the checks in read_volatile that would raise a panic) 46 | core::ptr::read_volatile(core::mem::align_of::() as *const i32); 47 | }) 48 | } 49 | ``` 50 | 51 | **Accessing Exception Data:** You can obtain the address and register dump of an exception. 52 | 53 | ```rust 54 | if let Err(ex) = microseh::try_seh(|| unsafe { 55 | // *questionable life choices go here* 56 | }) { 57 | println!("address: {:x}", ex.address()); 58 | println!("rax: {:x}", ex.registers().rax()); 59 | } 60 | ``` 61 | 62 | _For additional examples and practical use cases, please visit the [examples](./examples) directory!_ 63 | 64 | ## Portability 65 | 66 | SEH is an extension to the C language developed by Microsoft, and it is exclusively available 67 | on Windows when using Microsoft Visual C++ (MSVC). 68 | 69 | MicroSEH is compatible with and has been tested on Windows platforms with the following 70 | architectures: **x86**, **x86_64** and **aarch64**. 71 | 72 | When building for other unsupported platforms, the library will disable exception 73 | handling and panic when `try_seh` is called. 74 | 75 | ## Usage on Kernel Drivers 76 | 77 | This library can compile to `no_std` and supports running in Windows Kernel Drivers using 78 | Microsoft's [windows-drivers-rs](https://github.com/microsoft/windows-drivers-rs) project. 79 | 80 | ## Cross-Compiling 81 | 82 | Cross-compiling for Windows is possible with full support for SEH using the 83 | [cargo-xwin](https://github.com/rust-cross/cargo-xwin) project. 84 | 85 | ## License 86 | 87 | This work is released under the MIT license. A copy of the license is provided in the 88 | [LICENSE](./LICENSE) file. 89 | -------------------------------------------------------------------------------- /src/code.rs: -------------------------------------------------------------------------------- 1 | /// Represents a system-specific exception code. 2 | /// 3 | /// This enum encapsulates the various exception codes that can be returned by the 4 | /// `GetExceptionCode` Windows API function. 5 | /// 6 | /// See: 7 | #[repr(u32)] 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 9 | pub enum ExceptionCode { 10 | Invalid = 0x0, 11 | AccessViolation = 0xC0000005, 12 | ArrayBoundsExceeded = 0xC000008C, 13 | Breakpoint = 0x80000003, 14 | DataTypeMisalignment = 0x80000002, 15 | FltDenormalOperand = 0xC000008D, 16 | FltDivideByZero = 0xC000008E, 17 | FltInexactResult = 0xC000008F, 18 | FltInvalidOperation = 0xC0000090, 19 | FltOverflow = 0xC0000091, 20 | FltStackCheck = 0xC0000092, 21 | FltUnderflow = 0xC0000093, 22 | GuardPage = 0x80000001, 23 | IllegalInstruction = 0xC000001D, 24 | InPageError = 0xC0000006, 25 | IntDivideByZero = 0xC0000094, 26 | IntOverflow = 0xC0000095, 27 | InvalidDisposition = 0xC0000026, 28 | InvalidHandle = 0xC0000008, 29 | NonContinuableException = 0xC0000025, 30 | PrivilegedInstruction = 0xC0000096, 31 | SingleStep = 0x80000004, 32 | StackOverflow = 0xC00000FD, 33 | UnwindConsolidate = 0x80000029, 34 | } 35 | 36 | impl core::fmt::Display for ExceptionCode { 37 | /// Formats the exception code into a human-readable string. 38 | /// 39 | /// # Arguments 40 | /// 41 | /// * `f` - The formatter to write to. 42 | /// 43 | /// # Returns 44 | /// 45 | /// Whether the formatting operation succeeded. 46 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 47 | match self { 48 | ExceptionCode::Invalid => write!(f, "invalid exception"), 49 | ExceptionCode::AccessViolation => write!(f, "the thread attempts to read from or write to a virtual address for which it does not have access"), 50 | ExceptionCode::ArrayBoundsExceeded => write!(f, "the thread attempts to access an array element that is out of bounds and the underlying hardware supports bounds checking"), 51 | ExceptionCode::Breakpoint => write!(f, "a breakpoint was encountered"), 52 | ExceptionCode::DataTypeMisalignment => write!(f, "the thread attempts to read or write data that is misaligned on hardware that does not provide alignment"), 53 | ExceptionCode::FltDenormalOperand => write!(f, "one of the operands in a floating point operation is denormal"), 54 | ExceptionCode::FltDivideByZero => write!(f, "the thread attempts to divide a floating point value by a floating point divisor of 0"), 55 | ExceptionCode::FltInexactResult => write!(f, "the result of a floating point operation cannot be represented exactly as a decimal fraction"), 56 | ExceptionCode::FltInvalidOperation => write!(f, "this exception represents any floating point exception not included in this list"), 57 | ExceptionCode::FltOverflow => write!(f, "the exponent of a floating point operation is greater than the magnitude allowed by the corresponding type"), 58 | ExceptionCode::FltStackCheck => write!(f, "the stack has overflowed or underflowed, because of a floating point operation"), 59 | ExceptionCode::FltUnderflow => write!(f, "the exponent of a floating point operation is less than the magnitude allowed by the corresponding type"), 60 | ExceptionCode::GuardPage => write!(f, "the thread accessed memory allocated with the PAGE_GUARD modifier"), 61 | ExceptionCode::IllegalInstruction => write!(f, "the thread tries to execute an invalid instruction"), 62 | ExceptionCode::InPageError => write!(f, "the thread tries to access a page that is not present, and the system is unable to load the page"), 63 | ExceptionCode::IntDivideByZero => write!(f, "the thread attempts to divide an integer value by an integer divisor of 0"), 64 | ExceptionCode::IntOverflow => write!(f, "the result of an integer operation creates a value that is too large to be held by the destination register"), 65 | ExceptionCode::InvalidDisposition => write!(f, "an exception handler returns an invalid disposition to the exception dispatcher"), 66 | ExceptionCode::InvalidHandle => write!(f, "the thread used a handle to a kernel object that was invalid"), 67 | ExceptionCode::NonContinuableException => write!(f, "the thread attempts to continue execution after a non-continuable exception occurs"), 68 | ExceptionCode::PrivilegedInstruction => write!(f, "the thread attempts to execute an instruction with an operation that is not allowed in the current computer mode"), 69 | ExceptionCode::SingleStep => write!(f, "a trace trap or other single instruction mechanism signals that one instruction is executed"), 70 | ExceptionCode::StackOverflow => write!(f, "the thread used up its stack"), 71 | ExceptionCode::UnwindConsolidate => write!(f, "a frame consolidation has been executed") 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/stub.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | #define MS_SUCCEEDED 0x0 7 | #define MS_CATCHED 0x1 8 | 9 | #define TG_ARCH_X86 1 10 | #define TG_ARCH_X64 2 11 | #define TG_ARCH_ARM64 3 12 | #define TG_ARCH_UNKNOWN 4 13 | 14 | #if defined(_M_IX86) 15 | #define TG_ARCH TG_ARCH_X86 16 | #elif defined(_M_X64) || defined(_M_AMD64) 17 | #define TG_ARCH TG_ARCH_X64 18 | #elif defined(_M_ARM64) 19 | #define TG_ARCH TG_ARCH_ARM64 20 | #else 21 | #define TG_ARCH TG_ARCH_UNKNOWN 22 | #endif 23 | 24 | #define HAS_REGISTERS TG_ARCH != TG_ARCH_UNKNOWN 25 | 26 | #if HAS_REGISTERS 27 | 28 | #if TG_ARCH == TG_ARCH_X86 29 | #define NUM_REGISTERS 9 30 | #elif TG_ARCH == TG_ARCH_X64 31 | #define NUM_REGISTERS 17 32 | #elif TG_ARCH == TG_ARCH_ARM64 33 | #define NUM_REGISTERS 33 34 | #endif 35 | 36 | typedef struct _REGISTERS 37 | { 38 | DWORD_PTR List[NUM_REGISTERS]; 39 | } REGISTERS, *PREGISTERS; 40 | 41 | __forceinline REGISTERS __microseh_MakeRegisters(_In_ PCONTEXT Context) 42 | { 43 | REGISTERS Registers; 44 | 45 | #if TG_ARCH == TG_ARCH_X86 46 | Registers.List[0] = Context->Eax; 47 | Registers.List[1] = Context->Ecx; 48 | Registers.List[2] = Context->Edx; 49 | Registers.List[3] = Context->Ebx; 50 | Registers.List[4] = Context->Esp; 51 | Registers.List[5] = Context->Ebp; 52 | Registers.List[6] = Context->Esi; 53 | Registers.List[7] = Context->Edi; 54 | Registers.List[8] = Context->Eip; 55 | #elif TG_ARCH == TG_ARCH_X64 56 | Registers.List[0] = Context->Rax; 57 | Registers.List[1] = Context->Rcx; 58 | Registers.List[2] = Context->Rdx; 59 | Registers.List[3] = Context->Rbx; 60 | Registers.List[4] = Context->Rsp; 61 | Registers.List[5] = Context->Rbp; 62 | Registers.List[6] = Context->Rsi; 63 | Registers.List[7] = Context->Rdi; 64 | Registers.List[8] = Context->R8; 65 | Registers.List[9] = Context->R9; 66 | Registers.List[10] = Context->R10; 67 | Registers.List[11] = Context->R11; 68 | Registers.List[12] = Context->R12; 69 | Registers.List[13] = Context->R13; 70 | Registers.List[14] = Context->R14; 71 | Registers.List[15] = Context->R15; 72 | Registers.List[16] = Context->Rip; 73 | #elif TG_ARCH == TG_ARCH_ARM64 74 | Registers.List[0] = Context->X0; 75 | Registers.List[1] = Context->X1; 76 | Registers.List[2] = Context->X2; 77 | Registers.List[3] = Context->X3; 78 | Registers.List[4] = Context->X4; 79 | Registers.List[5] = Context->X5; 80 | Registers.List[6] = Context->X6; 81 | Registers.List[7] = Context->X7; 82 | Registers.List[8] = Context->X8; 83 | Registers.List[9] = Context->X9; 84 | Registers.List[10] = Context->X10; 85 | Registers.List[11] = Context->X11; 86 | Registers.List[12] = Context->X12; 87 | Registers.List[13] = Context->X13; 88 | Registers.List[14] = Context->X14; 89 | Registers.List[15] = Context->X15; 90 | Registers.List[16] = Context->X16; 91 | Registers.List[17] = Context->X17; 92 | Registers.List[18] = Context->X18; 93 | Registers.List[19] = Context->X19; 94 | Registers.List[20] = Context->X20; 95 | Registers.List[21] = Context->X21; 96 | Registers.List[22] = Context->X22; 97 | Registers.List[23] = Context->X23; 98 | Registers.List[24] = Context->X24; 99 | Registers.List[25] = Context->X25; 100 | Registers.List[26] = Context->X26; 101 | Registers.List[27] = Context->X27; 102 | Registers.List[28] = Context->X28; 103 | Registers.List[29] = Context->Fp; 104 | Registers.List[30] = Context->Lr; 105 | Registers.List[31] = Context->Sp; 106 | Registers.List[32] = Context->Pc; 107 | #endif 108 | 109 | return Registers; 110 | } 111 | 112 | #endif // HAS_REGISTERS 113 | 114 | 115 | typedef void(CALLBACK *PPROC_EXECUTOR)(PVOID Proc); 116 | 117 | typedef struct _EXCEPTION 118 | { 119 | DWORD Code; 120 | PVOID Address; 121 | #if HAS_REGISTERS 122 | REGISTERS Registers; 123 | #endif 124 | } EXCEPTION, *PEXCEPTION; 125 | 126 | uint32_t __microseh_HandlerStub( 127 | _In_ PPROC_EXECUTOR ProcExecutor, 128 | _In_ PVOID Proc, 129 | _Inout_ PEXCEPTION Exception 130 | ) { 131 | uint32_t Result = MS_SUCCEEDED; 132 | LPEXCEPTION_POINTERS Pointers = NULL; 133 | DWORD Code = 0; 134 | 135 | __try 136 | { 137 | ProcExecutor(Proc); 138 | } 139 | __except (Code = GetExceptionCode(), Pointers = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) 140 | { 141 | Result = MS_CATCHED; 142 | if (Exception != NULL) 143 | { 144 | // Use GetExceptionCode() instead of Record->ExceptionCode as it is more reliable. 145 | Exception->Code = Code; 146 | Exception->Address = Pointers->ExceptionRecord->ExceptionAddress; 147 | #if HAS_REGISTERS 148 | Exception->Registers = __microseh_MakeRegisters(Pointers->ContextRecord); 149 | #endif 150 | } 151 | } 152 | 153 | return Result; 154 | } 155 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![cfg_attr(not(feature = "std"), no_std)] 3 | 4 | use core::{ffi::c_void, mem::MaybeUninit}; 5 | 6 | mod code; 7 | mod exception; 8 | mod registers; 9 | 10 | pub use code::ExceptionCode; 11 | pub use exception::Exception; 12 | pub use registers::Registers; 13 | 14 | const MS_SUCCEEDED: u32 = 0x0; 15 | 16 | /// Type alias for a function that converts a pointer to a function and executes it. 17 | type ProcExecutor = unsafe extern "system" fn(*mut c_void); 18 | 19 | /// Internal function that converts a pointer to a function and executes it. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `proc` - A pointer to the procedure to execute. 24 | #[inline(always)] 25 | unsafe extern "system" fn proc_executor(proc: *mut c_void) 26 | where 27 | F: FnMut(), 28 | { 29 | // The procedure may be equal to std::ptr::null_mut() if the compiler optimized it away. 30 | // This also means that if you have some code that is optimized away, any exception it 31 | // contained will not get thrown. 32 | if let Some(proc) = proc.cast::().as_mut() { 33 | proc(); 34 | } 35 | } 36 | 37 | #[cfg(all(windows, not(docsrs)))] 38 | extern "C" { 39 | /// External function that is responsible for handling exceptions. 40 | /// 41 | /// # Arguments 42 | /// 43 | /// * `proc_executor` - The wrapper function that will execute the procedure. 44 | /// * `proc` - A pointer to the procedure to be executed within the handled context. 45 | /// * `exception` - Where the exception information will be stored if one occurs. 46 | /// 47 | /// # Returns 48 | /// 49 | /// * `0x0` - If the procedure executed without throwing any exceptions. 50 | /// * `0x1` - If an exception occurred during the execution of the procedure. 51 | #[link_name = "__microseh_HandlerStub"] 52 | fn handler_stub( 53 | proc_executor: ProcExecutor, 54 | proc: *mut c_void, 55 | exception: *mut Exception, 56 | ) -> u32; 57 | } 58 | 59 | /// Primary execution orchestrator that calls the exception handling stub. 60 | /// 61 | /// # Arguments 62 | /// 63 | /// * `proc` - The procedure to be executed within the handled context. 64 | /// 65 | /// # Returns 66 | /// 67 | /// * `Ok(())` - If the procedure executed without throwing any exceptions. 68 | /// * `Err(Exception)` - If an exception occurred during the execution of the procedure. 69 | #[cfg(all(windows, not(docsrs)))] 70 | fn do_call_stub(mut proc: F) -> Result<(), Exception> 71 | where 72 | F: FnMut(), 73 | { 74 | let mut exception = Exception::empty(); 75 | let proc = &mut proc as *mut _ as *mut c_void; 76 | 77 | match unsafe { handler_stub(proc_executor::, proc, &mut exception) } { 78 | MS_SUCCEEDED => Ok(()), 79 | _ => Err(exception), 80 | } 81 | } 82 | 83 | /// Fallback execution orchestrator to be used when exception handling is disabled. 84 | /// 85 | /// # Panics 86 | /// 87 | /// This function will always panic, notifying the user that exception handling is not 88 | /// available in the current build. 89 | #[cfg(any(not(windows), docsrs))] 90 | fn do_call_stub(_proc: F) -> Result<(), Exception> 91 | where 92 | F: FnMut(), 93 | { 94 | panic!("exception handling is not available in this build of microseh") 95 | } 96 | 97 | /// Executes the provided procedure in a context where exceptions are handled, catching any\ 98 | /// hardware exceptions that occur. 99 | /// 100 | /// Any value returned by the procedure is returned by this function, if no exceptions occur. 101 | /// 102 | /// # Arguments 103 | /// 104 | /// * `proc` - The procedure to be executed within the handled context. 105 | /// 106 | /// # Returns 107 | /// 108 | /// * `Ok(R)` - If the procedure executed without throwing any exceptions. 109 | /// * `Err(Exception)` - If an exception occurred during the execution of the procedure. 110 | /// 111 | /// # Examples 112 | /// 113 | /// ``` 114 | /// use microseh::try_seh; 115 | /// 116 | /// if let Err(e) = try_seh(|| unsafe { 117 | /// core::ptr::read_volatile(core::mem::align_of::() as *const i32); 118 | /// }) { 119 | /// println!("an exception occurred: {:?}", e); 120 | /// } 121 | /// ``` 122 | /// 123 | /// # Caveats 124 | /// 125 | /// If an exception occours within the procedure, resources that require cleanup via\ 126 | /// the `Drop` trait will not be released. 127 | /// 128 | /// As a rule of thumb, it's recommended not to define resources that implement\ 129 | /// the `Drop` trait inside the procedure. Instead, allocate and manage these resources\ 130 | /// outside, ensuring proper cleanup even if an exception occurs. 131 | /// 132 | /// # Panics 133 | /// 134 | /// If exception handling is disabled in the build, which occurs when the library is\ 135 | /// not built on Windows with Microsoft Visual C++. 136 | #[inline(always)] 137 | pub fn try_seh(mut proc: F) -> Result 138 | where 139 | F: FnMut() -> R, 140 | { 141 | let mut ret_val = MaybeUninit::::uninit(); 142 | do_call_stub(|| { 143 | ret_val.write(proc()); 144 | }) 145 | // SAFETY: We should only reach this point if the inner closure has returned 146 | // without throwing an exception, so `ret_val` should be initialized. 147 | .map(|_| unsafe { ret_val.assume_init() }) 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | 154 | const INVALID_PTR: *mut i32 = core::mem::align_of::() as _; 155 | 156 | #[test] 157 | #[cfg(feature = "std")] 158 | fn all_good() { 159 | let ex = try_seh(|| { 160 | let _ = *Box::new(1337); 161 | }); 162 | 163 | assert_eq!(ex.is_ok(), true); 164 | } 165 | 166 | #[test] 167 | fn access_violation_rs() { 168 | let ex = try_seh(|| unsafe { 169 | INVALID_PTR.read_volatile(); 170 | }); 171 | 172 | assert_eq!(ex.is_err(), true); 173 | assert_eq!(ex.unwrap_err().code(), ExceptionCode::AccessViolation); 174 | } 175 | 176 | #[test] 177 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 178 | fn access_violation_asm() { 179 | let ex = try_seh(|| unsafe { 180 | core::arch::asm!("mov eax, DWORD PTR [0]"); 181 | }); 182 | 183 | assert_eq!(ex.is_err(), true); 184 | assert_eq!(ex.unwrap_err().code(), ExceptionCode::AccessViolation); 185 | } 186 | 187 | #[test] 188 | #[cfg(target_arch = "aarch64")] 189 | fn access_violation_asm() { 190 | let ex = try_seh(|| unsafe { 191 | core::arch::asm!("ldr x0, xzr"); 192 | }); 193 | 194 | assert_eq!(ex.is_err(), true); 195 | assert_eq!(ex.unwrap_err().code(), ExceptionCode::AccessViolation); 196 | } 197 | 198 | #[test] 199 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 200 | fn breakpoint() { 201 | let ex = try_seh(|| unsafe { 202 | core::arch::asm!("int3"); 203 | }); 204 | 205 | assert_eq!(ex.is_err(), true); 206 | assert_eq!(ex.unwrap_err().code(), ExceptionCode::Breakpoint); 207 | } 208 | 209 | #[test] 210 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 211 | fn illegal_instruction() { 212 | let ex = try_seh(|| unsafe { 213 | core::arch::asm!("ud2"); 214 | }); 215 | 216 | assert_eq!(ex.is_err(), true); 217 | assert_eq!(ex.unwrap_err().code(), ExceptionCode::IllegalInstruction); 218 | } 219 | 220 | #[test] 221 | #[cfg(target_arch = "aarch64")] 222 | fn illegal_instruction() { 223 | let ex = try_seh(|| unsafe { 224 | core::arch::asm!("udf #0"); 225 | }); 226 | 227 | assert_eq!(ex.is_err(), true); 228 | assert_eq!( 229 | ex.as_ref().unwrap_err().code(), 230 | ExceptionCode::IllegalInstruction 231 | ); 232 | } 233 | 234 | #[test] 235 | #[cfg(target_arch = "x86")] 236 | fn reg_state_check() { 237 | let ex = try_seh(|| unsafe { 238 | core::arch::asm!("mov eax, 0xbadc0de", "ud2"); 239 | }); 240 | 241 | assert_eq!(ex.is_err(), true); 242 | assert_eq!( 243 | ex.as_ref().unwrap_err().code(), 244 | ExceptionCode::IllegalInstruction 245 | ); 246 | 247 | assert_eq!(ex.unwrap_err().registers().eax(), 0xbadc0de); 248 | } 249 | 250 | #[test] 251 | #[cfg(target_arch = "x86_64")] 252 | fn reg_state_check() { 253 | let ex = try_seh(|| unsafe { 254 | core::arch::asm!("mov rax, 0xbadc0debabefffff", "ud2"); 255 | }); 256 | 257 | assert_eq!(ex.is_err(), true); 258 | assert_eq!( 259 | ex.as_ref().unwrap_err().code(), 260 | ExceptionCode::IllegalInstruction 261 | ); 262 | 263 | assert_eq!(ex.unwrap_err().registers().rax(), 0xbadc0debabefffff); 264 | } 265 | 266 | #[test] 267 | #[cfg(target_arch = "aarch64")] 268 | fn reg_state_check() { 269 | let ex = try_seh(|| unsafe { 270 | core::arch::asm!( 271 | "movz x0, #0xbadc, LSL 48", 272 | "movk x0, #0x0deb, LSL 32", 273 | "movk x0, #0xabef, LSL 16", 274 | "movk x0, #0xffff", 275 | "udf #0" 276 | ); 277 | }); 278 | 279 | assert_eq!(ex.is_err(), true); 280 | assert_eq!( 281 | ex.as_ref().unwrap_err().code(), 282 | ExceptionCode::IllegalInstruction 283 | ); 284 | 285 | assert_eq!(ex.unwrap_err().registers().x0(), 0xbadc0debabefffff); 286 | } 287 | 288 | #[test] 289 | fn ret_vals() { 290 | let a = try_seh(|| 1337); 291 | assert_eq!(a.unwrap(), 1337); 292 | 293 | let b = try_seh(|| "hello"); 294 | assert_eq!(b.unwrap(), "hello"); 295 | 296 | let c = try_seh(|| {}); 297 | assert_eq!(core::mem::size_of_val(&c.unwrap()), 0x0); 298 | } 299 | } 300 | --------------------------------------------------------------------------------