Skip to content

Commit 4105144

Browse files
committed
rustc: Use C++ personalities on MSVC
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
1 parent adcfd8a commit 4105144

File tree

7 files changed

+301
-188
lines changed

7 files changed

+301
-188
lines changed

src/libpanic_unwind/seh.rs

+253-74
Original file line numberDiff line numberDiff line change
@@ -18,122 +18,301 @@
1818
//!
1919
//! In a nutshell, what happens here is:
2020
//!
21-
//! 1. The `panic` function calls the standard Windows function `RaiseException`
22-
//! with a Rust-specific code, triggering the unwinding process.
21+
//! 1. The `panic` function calls the standard Windows function
22+
//! `_CxxThrowException` to throw a C++-like exception, triggering the
23+
//! unwinding process.
2324
//! 2. All landing pads generated by the compiler use the personality function
24-
//! `__C_specific_handler` on 64-bit and `__except_handler3` on 32-bit,
25-
//! functions in the CRT, and the unwinding code in Windows will use this
26-
//! personality function to execute all cleanup code on the stack.
25+
//! `__CxxFrameHandler3`, a function in the CRT, and the unwinding code in
26+
//! Windows will use this personality function to execute all cleanup code on
27+
//! the stack.
2728
//! 3. All compiler-generated calls to `invoke` have a landing pad set as a
2829
//! `cleanuppad` LLVM instruction, which indicates the start of the cleanup
2930
//! routine. The personality (in step 2, defined in the CRT) is responsible
3031
//! for running the cleanup routines.
3132
//! 4. Eventually the "catch" code in the `try` intrinsic (generated by the
32-
//! compiler) is executed, which will ensure that the exception being caught
33-
//! is indeed a Rust exception, indicating that control should come back to
33+
//! compiler) is executed and indicates that control should come back to
3434
//! Rust. This is done via a `catchswitch` plus a `catchpad` instruction in
3535
//! LLVM IR terms, finally returning normal control to the program with a
36-
//! `catchret` instruction. The `try` intrinsic uses a filter function to
37-
//! detect what kind of exception is being thrown, and this detection is
38-
//! implemented as the msvc_try_filter language item below.
36+
//! `catchret` instruction.
3937
//!
4038
//! Some specific differences from the gcc-based exception handling are:
4139
//!
4240
//! * Rust has no custom personality function, it is instead *always*
43-
//! __C_specific_handler or __except_handler3, so the filtering is done in a
44-
//! C++-like manner instead of in the personality function itself. Note that
45-
//! the precise codegen for this was lifted from an LLVM test case for SEH
46-
//! (this is the `__rust_try_filter` function below).
41+
//! `__CxxFrameHandler3`. Additionally, no extra filtering is performed, so we
42+
//! end up catching any C++ exceptions that happen to look like the kind we're
43+
//! throwing. Note that throwing an exception into Rust is undefined behavior
44+
//! anyway, so this should be fine.
4745
//! * We've got some data to transmit across the unwinding boundary,
4846
//! specifically a `Box<Any + Send>`. Like with Dwarf exceptions
4947
//! these two pointers are stored as a payload in the exception itself. On
50-
//! MSVC, however, there's no need for an extra allocation because the call
51-
//! stack is preserved while filter functions are being executed. This means
52-
//! that the pointers are passed directly to `RaiseException` which are then
53-
//! recovered in the filter function to be written to the stack frame of the
54-
//! `try` intrinsic.
48+
//! MSVC, however, there's no need for an extra heap allocation because the
49+
//! call stack is preserved while filter functions are being executed. This
50+
//! means that the pointers are passed directly to `_CxxThrowException` which
51+
//! are then recovered in the filter function to be written to the stack frame
52+
//! of the `try` intrinsic.
5553
//!
5654
//! [win64]: http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx
5755
//! [llvm]: http://llvm.org/docs/ExceptionHandling.html#background-on-windows-exceptions
5856
57+
#![allow(bad_style)]
58+
#![allow(private_no_mangle_fns)]
59+
5960
use alloc::boxed::Box;
6061
use core::any::Any;
61-
use core::intrinsics;
6262
use core::mem;
6363
use core::raw;
6464

6565
use windows as c;
66+
use libc::{c_int, c_uint};
67+
68+
// First up, a whole bunch of type definitions. There's a few platform-specific
69+
// oddities here, and a lot that's just blatantly copied from LLVM. The purpose
70+
// of all this is to implement the `panic` function below through a call to
71+
// `_CxxThrowException`.
72+
//
73+
// This function takes two arguments. The first is a pointer to the data we're
74+
// passing in, which in this case is our trait object. Pretty easy to find! The
75+
// next, however, is more complicated. This is a pointer to a `_ThrowInfo`
76+
// structure, and it generally is just intended to just describe the exception
77+
// being thrown.
78+
//
79+
// Currently the definition of this type [1] is a little hairy, and the main
80+
// oddity (and difference from the online article) is that on 32-bit the
81+
// pointers are pointers but on 64-bit the pointers are expressed as 32-bit
82+
// offsets from the `__ImageBase` symbol. The `ptr_t` and `ptr!` macro in the
83+
// modules below are used to express this.
84+
//
85+
// The maze of type definitions also closely follows what LLVM emits for this
86+
// sort of operation. For example, if you compile this C++ code on MSVC and emit
87+
// the LLVM IR:
88+
//
89+
// #include <stdin.h>
90+
//
91+
// void foo() {
92+
// uint64_t a[2] = {0, 1};
93+
// throw a;
94+
// }
95+
//
96+
// That's essentially what we're trying to emulate. Most of the constant values
97+
// below were just copied from LLVM, I'm at least not 100% sure what's going on
98+
// everywhere. For example the `.PA_K\0` and `.PEA_K\0` strings below (stuck in
99+
// the names of a few of these) I'm not actually sure what they do, but it seems
100+
// to mirror what LLVM does!
101+
//
102+
// In any case, these structures are all constructed in a similar manner, and
103+
// it's just somewhat verbose for us.
104+
//
105+
// [1]: http://www.geoffchappell.com/studies/msvc/language/predefined/
106+
107+
#[cfg(target_arch = "x86")]
108+
#[macro_use]
109+
mod imp {
110+
pub type ptr_t = *mut u8;
111+
pub const OFFSET: i32 = 4;
112+
113+
pub const NAME1: [u8; 7] = [b'.', b'P', b'A', b'_', b'K', 0, 0];
114+
pub const NAME2: [u8; 7] = [b'.', b'P', b'A', b'X', 0, 0, 0];
115+
116+
macro_rules! ptr {
117+
(0) => (0 as *mut u8);
118+
($e:expr) => ($e as *mut u8);
119+
}
120+
}
121+
122+
#[cfg(target_arch = "x86_64")]
123+
#[macro_use]
124+
mod imp {
125+
pub type ptr_t = u32;
126+
pub const OFFSET: i32 = 8;
127+
128+
pub const NAME1: [u8; 7] = [b'.', b'P', b'E', b'A', b'_', b'K', 0];
129+
pub const NAME2: [u8; 7] = [b'.', b'P', b'E', b'A', b'X', 0, 0];
130+
131+
extern {
132+
pub static __ImageBase: u8;
133+
}
134+
135+
macro_rules! ptr {
136+
(0) => (0);
137+
($e:expr) => {
138+
(($e as usize) - (&imp::__ImageBase as *const _ as usize)) as u32
139+
}
140+
}
141+
}
142+
143+
#[repr(C)]
144+
pub struct _ThrowInfo {
145+
pub attribues: c_uint,
146+
pub pnfnUnwind: imp::ptr_t,
147+
pub pForwardCompat: imp::ptr_t,
148+
pub pCatchableTypeArray: imp::ptr_t,
149+
}
150+
151+
#[repr(C)]
152+
pub struct _CatchableTypeArray {
153+
pub nCatchableTypes: c_int,
154+
pub arrayOfCatchableTypes: [imp::ptr_t; 2],
155+
}
66156

67-
// A code which indicates panics that originate from Rust. Note that some of the
68-
// upper bits are used by the system so we just set them to 0 and ignore them.
69-
// 0x 0 R S T
70-
const RUST_PANIC: c::DWORD = 0x00525354;
157+
#[repr(C)]
158+
pub struct _CatchableType {
159+
pub properties: c_uint,
160+
pub pType: imp::ptr_t,
161+
pub thisDisplacement: _PMD,
162+
pub sizeOrOffset: c_int,
163+
pub copy_function: imp::ptr_t,
164+
}
165+
166+
#[repr(C)]
167+
pub struct _PMD {
168+
pub mdisp: c_int,
169+
pub pdisp: c_int,
170+
pub vdisp: c_int,
171+
}
172+
173+
#[repr(C)]
174+
pub struct _TypeDescriptor {
175+
pub pVFTable: *const u8,
176+
pub spare: *mut u8,
177+
pub name: [u8; 7],
178+
}
179+
180+
static mut THROW_INFO: _ThrowInfo = _ThrowInfo {
181+
attribues: 0,
182+
pnfnUnwind: ptr!(0),
183+
pForwardCompat: ptr!(0),
184+
pCatchableTypeArray: ptr!(0),
185+
};
186+
187+
static mut CATCHABLE_TYPE_ARRAY: _CatchableTypeArray = _CatchableTypeArray {
188+
nCatchableTypes: 2,
189+
arrayOfCatchableTypes: [
190+
ptr!(0),
191+
ptr!(0),
192+
],
193+
};
194+
195+
static mut CATCHABLE_TYPE1: _CatchableType = _CatchableType {
196+
properties: 1,
197+
pType: ptr!(0),
198+
thisDisplacement: _PMD {
199+
mdisp: 0,
200+
pdisp: -1,
201+
vdisp: 0,
202+
},
203+
sizeOrOffset: imp::OFFSET,
204+
copy_function: ptr!(0),
205+
};
206+
207+
static mut CATCHABLE_TYPE2: _CatchableType = _CatchableType {
208+
properties: 1,
209+
pType: ptr!(0),
210+
thisDisplacement: _PMD {
211+
mdisp: 0,
212+
pdisp: -1,
213+
vdisp: 0,
214+
},
215+
sizeOrOffset: imp::OFFSET,
216+
copy_function: ptr!(0),
217+
};
218+
219+
extern {
220+
// The leading `\x01` byte here is actually a magical signal to LLVM to
221+
// *not* apply any other mangling like prefixing with a `_` character.
222+
//
223+
// This symbol is the vtable used by C++'s `std::type_info`. Objects of type
224+
// `std::type_info`, type descriptors, have a pointer to this table. Type
225+
// descriptors are referenced by the C++ EH structures defined above and
226+
// that we construct below.
227+
#[link_name = "\x01??_7type_info@@6B@"]
228+
static TYPE_INFO_VTABLE: *const u8;
229+
}
230+
231+
// We use #[lang = "msvc_try_filter"] here as this is the type descriptor which
232+
// we'll use in LLVM's `catchpad` instruction which ends up also being passed as
233+
// an argument to the C++ personality function.
234+
//
235+
// Again, I'm not entirely sure what this is describing, it just seems to work.
236+
#[cfg_attr(all(not(test), not(stage0)),
237+
lang = "msvc_try_filter")]
238+
static mut TYPE_DESCRIPTOR1: _TypeDescriptor = _TypeDescriptor {
239+
pVFTable: &TYPE_INFO_VTABLE as *const _ as *const _,
240+
spare: 0 as *mut _,
241+
name: imp::NAME1,
242+
};
243+
244+
static mut TYPE_DESCRIPTOR2: _TypeDescriptor = _TypeDescriptor {
245+
pVFTable: &TYPE_INFO_VTABLE as *const _ as *const _,
246+
spare: 0 as *mut _,
247+
name: imp::NAME2,
248+
};
71249

72250
pub unsafe fn panic(data: Box<Any + Send>) -> u32 {
73-
// As mentioned above, the call stack here is preserved while the filter
74-
// functions are running, so it's ok to pass stack-local arrays into
75-
// `RaiseException`.
251+
use core::intrinsics::atomic_store;
252+
253+
// _CxxThrowException executes entirely on this stack frame, so there's no
254+
// need to otherwise transfer `data` to the heap. We just pass a stack
255+
// pointer to this function.
76256
//
77-
// The two pointers of the `data` trait object are written to the stack,
78-
// passed to `RaiseException`, and they're later extracted by the filter
79-
// function below in the "custom exception information" section of the
80-
// `EXCEPTION_RECORD` type.
257+
// The first argument is the payload being thrown (our two pointers), and
258+
// the second argument is the type information object describing the
259+
// exception (constructed above).
81260
let ptrs = mem::transmute::<_, raw::TraitObject>(data);
82-
let ptrs = [ptrs.data, ptrs.vtable];
83-
c::RaiseException(RUST_PANIC, 0, 2, ptrs.as_ptr() as *mut _);
261+
let mut ptrs = [ptrs.data as u64, ptrs.vtable as u64];
262+
let mut ptrs_ptr = ptrs.as_mut_ptr();
263+
264+
// This... may seems surprising, and justifiably so. On 32-bit MSVC the
265+
// pointers between these structure are just that, pointers. On 64-bit MSVC,
266+
// however, the pointers between structures are rather expressed as 32-bit
267+
// offsets from `__ImageBase`.
268+
//
269+
// Consequently, on 32-bit MSVC we can declare all these pointers in the
270+
// `static`s above. On 64-bit MSVC, we would have to express subtraction of
271+
// pointers in statics, which Rust does not currently allow, so we can't
272+
// actually do that.
273+
//
274+
// The next best thing, then is to fill in these structures at runtime
275+
// (panicking is already the "slow path" anyway). So here we reinterpret all
276+
// of these pointer fields as 32-bit integers and then store the
277+
// relevant value into it (atomically, as concurrent panics may be
278+
// happening). Technically the runtime will probably do a nonatomic read of
279+
// these fields, but in theory they never read the *wrong* value so it
280+
// shouldn't be too bad...
281+
//
282+
// In any case, we basically need to do something like this until we can
283+
// express more operations in statics (and we may never be able to).
284+
atomic_store(&mut THROW_INFO.pCatchableTypeArray as *mut _ as *mut u32,
285+
ptr!(&CATCHABLE_TYPE_ARRAY as *const _) as u32);
286+
atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[0] as *mut _ as *mut u32,
287+
ptr!(&CATCHABLE_TYPE1 as *const _) as u32);
288+
atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[1] as *mut _ as *mut u32,
289+
ptr!(&CATCHABLE_TYPE2 as *const _) as u32);
290+
atomic_store(&mut CATCHABLE_TYPE1.pType as *mut _ as *mut u32,
291+
ptr!(&TYPE_DESCRIPTOR1 as *const _) as u32);
292+
atomic_store(&mut CATCHABLE_TYPE2.pType as *mut _ as *mut u32,
293+
ptr!(&TYPE_DESCRIPTOR2 as *const _) as u32);
294+
295+
c::_CxxThrowException(&mut ptrs_ptr as *mut _ as *mut _,
296+
&mut THROW_INFO as *mut _ as *mut _);
84297
u32::max_value()
85298
}
86299

87-
pub fn payload() -> [usize; 2] {
300+
pub fn payload() -> [u64; 2] {
88301
[0; 2]
89302
}
90303

91-
pub unsafe fn cleanup(payload: [usize; 2]) -> Box<Any + Send> {
304+
pub unsafe fn cleanup(payload: [u64; 2]) -> Box<Any + Send> {
92305
mem::transmute(raw::TraitObject {
93306
data: payload[0] as *mut _,
94307
vtable: payload[1] as *mut _,
95308
})
96309
}
97310

98-
// This is quite a special function, and it's not literally passed in as the
99-
// filter function for the `catchpad` of the `try` intrinsic. The compiler
100-
// actually generates its own filter function wrapper which will delegate to
101-
// this for the actual execution logic for whether the exception should be
102-
// caught. The reasons for this are:
103-
//
104-
// * Each architecture has a slightly different ABI for the filter function
105-
// here. For example on x86 there are no arguments but on x86_64 there are
106-
// two.
107-
// * This function needs access to the stack frame of the `try` intrinsic
108-
// which is using this filter as a catch pad. This is because the payload
109-
// of this exception, `Box<Any>`, needs to be transmitted to that
110-
// location.
111-
//
112-
// Both of these differences end up using a ton of weird llvm-specific
113-
// intrinsics, so it's actually pretty difficult to express the entire
114-
// filter function in Rust itself. As a compromise, the compiler takes care
115-
// of all the weird LLVM-specific and platform-specific stuff, getting to
116-
// the point where this function makes the actual decision about what to
117-
// catch given two parameters.
118-
//
119-
// The first parameter is `*mut EXCEPTION_POINTERS` which is some contextual
120-
// information about the exception being filtered, and the second pointer is
121-
// `*mut *mut [usize; 2]` (the payload here). This value points directly
122-
// into the stack frame of the `try` intrinsic itself, and we use it to copy
123-
// information from the exception onto the stack.
124311
#[lang = "msvc_try_filter"]
125-
#[cfg(not(test))]
126-
unsafe extern fn __rust_try_filter(eh_ptrs: *mut u8,
127-
payload: *mut u8) -> i32 {
128-
let eh_ptrs = eh_ptrs as *mut c::EXCEPTION_POINTERS;
129-
let payload = payload as *mut *mut [usize; 2];
130-
let record = &*(*eh_ptrs).ExceptionRecord;
131-
if record.ExceptionCode != RUST_PANIC {
132-
return 0
133-
}
134-
(**payload)[0] = record.ExceptionInformation[0] as usize;
135-
(**payload)[1] = record.ExceptionInformation[1] as usize;
136-
return 1
312+
#[cfg(stage0)]
313+
unsafe extern fn __rust_try_filter(_eh_ptrs: *mut u8,
314+
_payload: *mut u8) -> i32 {
315+
return 0
137316
}
138317

139318
// This is required by the compiler to exist (e.g. it's a lang item), but
@@ -143,5 +322,5 @@ unsafe extern fn __rust_try_filter(eh_ptrs: *mut u8,
143322
#[lang = "eh_personality"]
144323
#[cfg(not(test))]
145324
fn rust_eh_personality() {
146-
unsafe { intrinsics::abort() }
325+
unsafe { ::core::intrinsics::abort() }
147326
}

0 commit comments

Comments
 (0)