Catching exception of FFI

I'm implementing a "wrapper" (on Windows) for unsafe extern functions, the problem is that the wrapped functions are not safe (actually they're really unsafe). They may even throw exceptions, something like that:

// hypothesized external function
#[inline] unsafe fn ud2() -> ! {
  asm!("ud2")
}

Now I want to wrap such a function in a "safe" wrapper so that when the exception is raised, the wrapper should be capable to catch it, like that:

fn wrapper() {
   wrap(ud2).catch(|_| {
     println!("illegal instruction executed");
   });
}

Is this possible?

You have to catch the exception before you cross the FFI boundary. Maybe you can add your own C or C++ wrapper around the library?

5 Likes

cc @BatmanAoD

1 Like

Thank you, it may be possible to wrap them using __try {}...__except {} of Windows SEH (then passing wrapped functions into Rust). Is is possible to do that directly in Rust?

Rust's panic_unwind\src\seh.rs code might be of interest. Though keep this in mind:

Note that throwing an exception into Rust is undefined behavior...

So it would indeed be much much better if you can handle it on the other side of the FFI boundary.

1 Like

Thank you, but it's may not be what I'm looking for. Since the exception to capture is at a lower level than C++'s exception. On Windows x32, I've managed to do that by overwriting the exception handler chain, like that:

#[inline(always)]
unsafe fn current_tib() -> *mut ::winapi::um::winnt::NT_TIB {
    let teb: u32;
    asm!(
        "mov {teb}, dword ptr fs:[0x30]", teb = out(reg) teb
    );
    teb as _
}

and in the program:

unsafe extern "system" fn exception_filter(
    exception_record: *mut EXCEPTION_RECORD,
    _establish_frame: PVOID,
    _context_record: *mut CONTEXT,
    _dispatcher_context: PVOID,
) -> EXCEPTION_DISPOSITION {
    if (*exception_record).ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION {
        // capture it
        println!("captured");
    }

    ExceptionContinueExecution
}

// overwrite the head of the exception handler chain
let tib = current_tib();
let mut registration = MaybeUninit::<EXCEPTION_REGISTRATION_RECORD>::uninit();
(*registration.as_mut_ptr()).Handler = Some(exception_filter);
(*registration.as_mut_ptr()).Next = (*tib).ExceptionList;
(*tib).ExceptionList = registration.as_mut_ptr();

// invoke an exception
asm!("ud2");

// restore the exception handler chain
(*tib).ExceptionList = (*(*tib).ExceptionList).Next;

Unfortunately, the technique doesn't work for the Windows x64 SEH since the handler table is now on the PE file itself, and built by the compiler. I'be heard that Rust compiler supports Windows x64 SEH, so it would be able to do that too. It may be possible to modify this table, but I'm don't know how to do it yet.

Would ::winapi::im::errhandlingapi::AddVectoredExceptionHandler be helpful here?

5 Likes

Thanks for the ping, @Yandros. @alice isccorrect that there's no well-defined way to catch C++ exceptions in Rust; technically, it is undefined for such an exception to enter a Rust stack frame at all, so the compiler currently emits LLVM's nounwind annotation for "C" function declarations and call sites.

There is an RFC for a new ABI string to ellide this annotation: https://github.com/rust-lang/rfcs/pull/2945

Here is the tracking issue: https://github.com/rust-lang/rust/issues/74990

Unfortunately, we don't plan to support catching C++ exceptions in the language proper or the standard library. But I don't know of any reason why a custom panic handler like the one you've defined couldn't do that (though of course that behavior could change out from under you if the target platform's implementation details change).

3 Likes

Thank you @Yandros, it works. And your solution is better than mine, since it doesn't need any special trick (like the TIB overriding).

1 Like

We are not always in a shellcode environment :wink:

1 Like