Skip to content

Commit

Permalink
rustc: Use C++ personalities on MSVC
Browse files Browse the repository at this point in the history
Currently the compiler has two relatively critical bugs in the implementation of
MSVC unwinding:

* #33112 - faults like segfaults and illegal instructions will run destructors
           in Rust, meaning we keep running code after a super-fatal exception
           has happened.

* #33116 - When compiling with LTO plus `-Z no-landing-pads` (or `-C
           panic=abort` with the previous commit) LLVM won't remove all `invoke`
           instructions, meaning that some landing pads stick around and
           cleanups may be run due to the previous bug.

These both stem from the flavor of "personality function" that Rust uses for
unwinding on MSVC. On 32-bit this is `_except_handler3` and on 64-bit this is
`__C_specific_handler`, but they both essentially are the "most generic"
personality functions for catching exceptions and running cleanups. That is,
thse two personalities will run cleanups for all exceptions unconditionally, so
when we use them we run cleanups for **all SEH exceptions** (include things like
segfaults).

Note that this also explains why LLVM won't optimize away `invoke` instructions.
These functions can legitimately still unwind (the `nounwind` attribute only
seems to apply to "C++ exception-like unwining"). Also note that the standard
library only *catches* Rust exceptions, not others like segfaults and illegal
instructions.

LLVM has support for another personality, `__CxxFrameHandler3`, which does not
run cleanups for general exceptions, only C++ exceptions thrown by
`_CxxThrowException`. This essentially ideally matches our use case, so this
commit moves us over to using this well-known personality function as well as
exception-throwing function.

This doesn't *seem* to pull in any extra runtime dependencies just yet, but if
it does we can perhaps try to work out how to implement more of it in Rust
rather than relying on MSVCRT runtime bits.

More details about how this is actually implemented can be found in the changes
itself, but this...

Closes #33112
Closes #33116
  • Loading branch information
alexcrichton committed May 10, 2016
1 parent 0ec321f commit 38e6e5d
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 201 deletions.
49 changes: 36 additions & 13 deletions src/libpanic_abort/lib.rs
Expand Up @@ -93,20 +93,43 @@ pub unsafe extern fn __rust_start_panic(_data: usize, _vtable: usize) -> u32 {
// Essentially this symbol is just defined to get wired up to libcore/libstd
// binaries, but it should never be called as we don't link in an unwinding
// runtime at all.
#[no_mangle]
#[cfg(not(stage0))]
pub extern fn rust_eh_personality() {}
pub mod personalities {

// Similar to above, this corresponds to the `eh_unwind_resume` lang item that's
// only used on Windows currently.
#[no_mangle]
#[cfg(all(not(stage0), target_os = "windows", target_env = "gnu"))]
pub extern fn rust_eh_unwind_resume() {}
#[no_mangle]
#[cfg(not(all(target_os = "windows",
target_env = "gnu",
target_arch = "x86_64")))]
pub extern fn rust_eh_personality() {}

#[no_mangle]
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
pub extern fn rust_eh_register_frames() {}
// On x86_64-pc-windows-gnu we use our own personality function that needs
// to return `ExceptionContinueSearch` as we're passing on all our frames.
#[no_mangle]
#[cfg(all(target_os = "windows",
target_env = "gnu",
target_arch = "x86_64"))]
pub extern fn rust_eh_personality(_record: usize,
_frame: usize,
_context: usize,
_dispatcher: usize) -> u32 {
1 // `ExceptionContinueSearch`
}

#[no_mangle]
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
pub extern fn rust_eh_unregister_frames() {}
// Similar to above, this corresponds to the `eh_unwind_resume` lang item
// that's only used on Windows currently.
//
// Note that we don't execute landing pads, so this is never called, so it's
// body is empty.
#[no_mangle]
#[cfg(all(target_os = "windows", target_env = "gnu"))]
pub extern fn rust_eh_unwind_resume() {}

// These two are called by our startup objects on i686-pc-windows-gnu, but
// they don't need to do anything so the bodies are nops.
#[no_mangle]
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
pub extern fn rust_eh_register_frames() {}
#[no_mangle]
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
pub extern fn rust_eh_unregister_frames() {}
}

0 comments on commit 38e6e5d

Please sign in to comment.