Skip to content

Commit 1613fe2

Browse files
committed
rustc: Use custom personality functions on MSVC
Exception handling on MSVC for Rust has at this point a long and storied history to it. We currently use the exception mechanisms on MSVC to implement panics in Rust. On MSVC both 32 and 64-bit exceptions are based on SEH, structured exception handling. LLVM long ago added special instructions for MSVC, and we've been using those for as long as MSVC panics have been implemented! Exception handling at the system layer is typically guided by "personality functions" which are in theory intended to be language specific and allow programming languages to implement custom logic. Since the beginning, however, LLVM has had a hardcoded list of "known personalities" as they behave quite differently. As a result, Rust has historically shoehorned our desired panic behavior into preexisting personality functions defined on MSVC. Originally Rust actually used the functions `__C_specific_handler` (64-bit) and `_except_handler3` (32-bit). Using these personalities was relatively easy in Rust and only required a "filter function" on our "catch" equivalent to only catch Rust exceptions. Unfortunately these personalities suffered two [fatal][f1] [flaws][f2], which caused us to subsequently [switch] to the `__CxxFrameHandler3` personality. This personality is intended for C++, but we're mostly like C++ in any case so it worked quite well for a long time! The default C++ personality didn't run cleanups on faults and LLVM optimized invokes of nounwind functions well. The only downside at this point was that the support was [sort of scary][scary]. Fast forward to the 1.24.0 release and another [fatal flaw][f3] is found in our strategy. Historically we've always declared "unwinding into Rust code from other languages is undefined behavior" (or unwinding out of Rust code). In 1.24.0 we changed `extern` functions defined in Rust to enforce this behavior, forcibly aborting the process if the function panics. Everything was ship shape until it was discovered that `longjmp` across Rust frames caused the process to abort! It turns out that on MSVC `longjmp` is implemented with SEH! Furthermore it turns out that the personality we're using, `__CxxFrameHandler3`, recognized the `longjmp` exception enough to run cleanups. Consequently, when SEH ran past an `extern` function in Rust it aborted the process. Sounds like "cleanups run on segfaults" v2! Well in any case, that's a long-winded way of describing how shoehorning Rust's desired behavior into existing personality functions is getting more and more difficult. As a result, this commit starts taking a new strategy of defining custom personality functions in Rust (like we do for all other platforms) and teaching LLVM about these personalities so they're classified correctly and don't suffer from [old bugs][f2]. Alright, so with all that information in your head now this commit can be described with: * New personality functions are added for MSVC: `rust_seh{32,64}_personality`. * These functions are shims around `__C_specific_handler` and `_except_handler3` like how on Unix we're typically a shim around a gcc personality. * Rust's personality functions defer on all exceptions that *aren't* Rust-related. We choose an arbitrary code to represent a Rust exception and only exceptions with matching codes execute our cleanups/catches. (the prevents [previous bugs][f1] with the personalities these functions are wrapping). * LLVM is updated with a small-ish commit to learn about these personality functions. The largest change here is, again, [ensuring old bugs don't resurface][f2] by teaching LLVM that it can simplify invokes of nounwind functions in Rust. * Finally, bedbad6 is partially reverted to restore the old translation behavior of the `try` intrinsic, bringing some scary code back into the compiler about `llvm.localescape` and such. Overall the intent of this commit is to preserve all existing behavior with panics on MSVC (aka keep old bugs closed and use the same system in general) but enable longjmps across Rust code. To this effect a test is checked in which asserts that we can indeed longjmp across Rust code with destructors and such. [f1]: #33112 [f2]: #33116 [switch]: 38e6e5d0 [scary]: https://github.com/rust-lang/rust/blob/bedbad61195d2eae69b43eca49c6d3e2aee8f208/src/libpanic_unwind/seh.rs#L107-L294 [f3]: #48251
1 parent 322d7f7 commit 1613fe2

File tree

15 files changed

+667
-303
lines changed

15 files changed

+667
-303
lines changed

.gitmodules

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[submodule "src/llvm"]
22
path = src/llvm
3-
url = https://github.com/rust-lang/llvm.git
3+
url = https://github.com/alexcrichton/llvm.git
44
branch = master
55
[submodule "src/jemalloc"]
66
path = src/jemalloc

src/libcore/panicking.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ pub fn panic_fmt(fmt: fmt::Arguments, file_line_col: &(&'static str, u32, u32))
6565
extern {
6666
#[lang = "panic_fmt"]
6767
#[unwind]
68-
fn panic_impl(fmt: fmt::Arguments, file: &'static str, line: u32, col: u32) -> !;
68+
fn rust_begin_unwind(fmt: fmt::Arguments, file: &'static str, line: u32, col: u32) -> !;
6969
}
7070
let (file, line, col) = *file_line_col;
71-
unsafe { panic_impl(fmt, file, line, col) }
71+
unsafe { rust_begin_unwind(fmt, file, line, col) }
7272
}

src/libpanic_abort/lib.rs

+22-6
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,33 @@ pub unsafe extern fn __rust_start_panic(_data: usize, _vtable: usize) -> u32 {
9898
// runtime at all.
9999
pub mod personalities {
100100
#[no_mangle]
101-
#[cfg(not(all(target_os = "windows",
102-
target_env = "gnu",
103-
target_arch = "x86_64")))]
101+
#[cfg(not(all(
102+
target_os = "windows",
103+
any(
104+
target_env = "msvc",
105+
all(target_env = "gnu", target_arch = "x86_64")
106+
)
107+
)))]
104108
pub extern fn rust_eh_personality() {}
105109

106110
// On x86_64-pc-windows-gnu we use our own personality function that needs
107111
// to return `ExceptionContinueSearch` as we're passing on all our frames.
108112
#[no_mangle]
109-
#[cfg(all(target_os = "windows",
110-
target_env = "gnu",
111-
target_arch = "x86_64"))]
113+
#[cfg(all(
114+
target_os = "windows",
115+
any(
116+
target_env = "msvc",
117+
all(target_env = "gnu", target_arch = "x86_64")
118+
)
119+
))]
120+
#[cfg_attr(
121+
all(target_env = "msvc", target_arch = "x86"),
122+
export_name = "rust_seh32_personality"
123+
)]
124+
#[cfg_attr(
125+
all(target_env = "msvc", target_arch = "x86_64"),
126+
export_name = "rust_seh64_personality"
127+
)]
112128
pub extern fn rust_eh_personality(_record: usize,
113129
_frame: usize,
114130
_context: usize,

src/libpanic_unwind/lib.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ use core::raw;
5757
pub use imp::eh_frame_registry::*;
5858

5959
// *-pc-windows-msvc
60-
#[cfg(target_env = "msvc")]
60+
#[cfg(all(target_env = "msvc", stage0))]
61+
#[path = "seh_stage0.rs"]
62+
mod imp;
63+
64+
// *-pc-windows-msvc
65+
#[cfg(all(target_env = "msvc", not(stage0)))]
6166
#[path = "seh.rs"]
6267
mod imp;
6368

0 commit comments

Comments
 (0)