Skip to content

Commit ee5da48

Browse files
committed
Auto merge of #160183 - Darksonn:seh-imgrel, r=<try>
panic_unwind: Use global_asm! for IMGREL relocations try-job: *msvc*
2 parents 771916f + 2eacdbd commit ee5da48

1 file changed

Lines changed: 69 additions & 168 deletions

File tree

  • library/panic_unwind/src

‎library/panic_unwind/src/seh.rs‎

Lines changed: 69 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
use alloc::boxed::Box;
5050
use core::any::Any;
51-
use core::ffi::{c_int, c_uint, c_void};
51+
use core::ffi::c_void;
5252
use core::mem::ManuallyDrop;
5353

5454
// NOTE(nbdd0121): The `canary` field is part of stable ABI.
@@ -65,10 +65,8 @@ struct Exception {
6565
data: Option<Box<dyn Any + Send>>,
6666
}
6767

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`.
68+
// The purpose of all this is to implement the `panic` function below through a
69+
// call to `_CxxThrowException`.
7270
//
7371
// This function takes two arguments. The first is a pointer to the data we're
7472
// passing in, which in this case is our trait object. Pretty easy to find! The
@@ -79,8 +77,9 @@ struct Exception {
7977
// Currently the definition of this type [1] is a little hairy, and the main
8078
// oddity (and difference from the online article) is that on 32-bit the
8179
// 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.
80+
// offsets from the image base. It's not currently possible to create a relative
81+
// offset in const Rust code, so this is done using assembly with the `@IMGREL`
82+
// relocation.
8483
//
8584
// The maze of type definitions also closely follows what LLVM emits for this
8685
// sort of operation. For example, if you compile this C++ code on MSVC and emit
@@ -108,128 +107,22 @@ struct Exception {
108107
//
109108
// [1]: https://www.geoffchappell.com/studies/msvc/language/predefined/
110109

111-
#[cfg(target_arch = "x86")]
112-
mod imp {
113-
#[repr(transparent)]
114-
#[derive(Copy, Clone)]
115-
pub(super) struct ptr_t(*mut u8);
116-
117-
impl ptr_t {
118-
pub(super) const fn null() -> Self {
119-
Self(core::ptr::null_mut())
120-
}
121-
122-
pub(super) const fn new(ptr: *mut u8) -> Self {
123-
Self(ptr)
124-
}
125-
126-
pub(super) const fn raw(self) -> *mut u8 {
127-
self.0
128-
}
129-
}
130-
}
131-
132-
#[cfg(not(target_arch = "x86"))]
133-
mod imp {
134-
// On 64-bit systems, SEH represents pointers as 32-bit offsets from `__ImageBase`.
135-
#[repr(transparent)]
136-
#[derive(Copy, Clone)]
137-
pub(super) struct ptr_t(u32);
138-
139-
unsafe extern "C" {
140-
static __ImageBase: u8;
141-
}
142-
143-
impl ptr_t {
144-
pub(super) const fn null() -> Self {
145-
Self(0)
146-
}
147-
148-
pub(super) fn new(ptr: *mut u8) -> Self {
149-
// We need to expose the provenance of the pointer because it is not carried by
150-
// the `u32`, while the FFI needs to have this provenance to excess our statics.
151-
//
152-
// NOTE(niluxv): we could use `MaybeUninit<u32>` instead to leak the provenance
153-
// into the FFI. In theory then the other side would need to do some processing
154-
// to get a pointer with correct provenance, but these system functions aren't
155-
// going to be cross-lang LTOed anyway. However, using expose is shorter and
156-
// requires less unsafe.
157-
let addr: usize = ptr.expose_provenance();
158-
let image_base = (&raw const __ImageBase).addr();
159-
let offset: usize = addr - image_base;
160-
Self(offset as u32)
161-
}
162-
163-
pub(super) const fn raw(self) -> u32 {
164-
self.0
165-
}
166-
}
167-
}
168-
169-
use imp::ptr_t;
170-
171-
#[repr(C)]
172-
struct _ThrowInfo {
173-
pub attributes: c_uint,
174-
pub pmfnUnwind: ptr_t,
175-
pub pForwardCompat: ptr_t,
176-
pub pCatchableTypeArray: ptr_t,
177-
}
178-
179-
#[repr(C)]
180-
struct _CatchableTypeArray {
181-
pub nCatchableTypes: c_int,
182-
pub arrayOfCatchableTypes: [ptr_t; 1],
183-
}
184-
185-
#[repr(C)]
186-
struct _CatchableType {
187-
pub properties: c_uint,
188-
pub pType: ptr_t,
189-
pub thisDisplacement: _PMD,
190-
pub sizeOrOffset: c_int,
191-
pub copyFunction: ptr_t,
192-
}
193-
194-
#[repr(C)]
195-
struct _PMD {
196-
pub mdisp: c_int,
197-
pub pdisp: c_int,
198-
pub vdisp: c_int,
199-
}
200-
201110
#[repr(C)]
202111
struct _TypeDescriptor {
203112
pub pVFTable: *const u8,
204113
pub spare: *mut u8,
205114
pub name: [u8; 11],
206115
}
207116

117+
unsafe impl Sync for _TypeDescriptor {}
118+
208119
// Note that we intentionally ignore name mangling rules here: we don't want C++
209120
// to be able to catch Rust panics by simply declaring a `struct rust_panic`.
210121
//
211122
// When modifying, make sure that the type name string exactly matches
212123
// the one used in `compiler/rustc_codegen_llvm/src/intrinsic.rs`.
213124
const TYPE_NAME: [u8; 11] = *b"rust_panic\0";
214125

215-
static mut THROW_INFO: _ThrowInfo = _ThrowInfo {
216-
attributes: 0,
217-
pmfnUnwind: ptr_t::null(),
218-
pForwardCompat: ptr_t::null(),
219-
pCatchableTypeArray: ptr_t::null(),
220-
};
221-
222-
static mut CATCHABLE_TYPE_ARRAY: _CatchableTypeArray =
223-
_CatchableTypeArray { nCatchableTypes: 1, arrayOfCatchableTypes: [ptr_t::null()] };
224-
225-
static mut CATCHABLE_TYPE: _CatchableType = _CatchableType {
226-
properties: 0,
227-
pType: ptr_t::null(),
228-
thisDisplacement: _PMD { mdisp: 0, pdisp: -1, vdisp: 0 },
229-
sizeOrOffset: size_of::<Exception>() as c_int,
230-
copyFunction: ptr_t::null(),
231-
};
232-
233126
unsafe extern "C" {
234127
// The leading `\x01` byte here is actually a magical signal to LLVM to
235128
// *not* apply any other mangling like prefixing with a `_` character.
@@ -239,16 +132,16 @@ unsafe extern "C" {
239132
// descriptors are referenced by the C++ EH structures defined above and
240133
// that we construct below.
241134
#[link_name = "\x01??_7type_info@@6B@"]
242-
static TYPE_INFO_VTABLE: *const u8;
135+
static TYPE_INFO_VTABLE: u8;
243136
}
244137

245138
// This type descriptor is only used when throwing an exception. The catch part
246139
// is handled by the try intrinsic, which generates its own TypeDescriptor.
247140
//
248141
// This is fine since the MSVC runtime uses string comparison on the type name
249142
// to match TypeDescriptors rather than pointer equality.
250-
static mut TYPE_DESCRIPTOR: _TypeDescriptor = _TypeDescriptor {
251-
pVFTable: (&raw const TYPE_INFO_VTABLE) as *const _,
143+
static TYPE_DESCRIPTOR: _TypeDescriptor = _TypeDescriptor {
144+
pVFTable: &raw const TYPE_INFO_VTABLE,
252145
spare: core::ptr::null_mut(),
253146
name: TYPE_NAME,
254147
};
@@ -303,69 +196,77 @@ pub(crate) unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
303196
}
304197

305198
unsafe fn throw_exception(data: Option<Box<dyn Any + Send>>) -> ! {
306-
use core::intrinsics::{AtomicOrdering, atomic_store};
307-
308199
// _CxxThrowException executes entirely on this stack frame, so there's no
309200
// need to otherwise transfer `data` to the heap. We just pass a stack
310201
// pointer to this function.
311202
//
312203
// The ManuallyDrop is needed here since we don't want Exception to be
313204
// dropped when unwinding. Instead it will be dropped by exception_cleanup
314205
// which is invoked by the C++ runtime.
315-
let mut exception = ManuallyDrop::new(Exception { canary: (&raw const TYPE_DESCRIPTOR), data });
316-
let throw_ptr = (&raw mut exception) as *mut _;
206+
let mut exception = ManuallyDrop::new(Exception { canary: &raw const TYPE_DESCRIPTOR, data });
317207

318-
// This... may seems surprising, and justifiably so. On 32-bit MSVC the
319-
// pointers between these structure are just that, pointers. On 64-bit MSVC,
320-
// however, the pointers between structures are rather expressed as 32-bit
321-
// offsets from `__ImageBase`.
322-
//
323-
// Consequently, on 32-bit MSVC we can declare all these pointers in the
324-
// `static`s above. On 64-bit MSVC, we would have to express subtraction of
325-
// pointers in statics, which Rust does not currently allow, so we can't
326-
// actually do that.
327-
//
328-
// The next best thing, then is to fill in these structures at runtime
329-
// (panicking is already the "slow path" anyway). So here we reinterpret all
330-
// of these pointer fields as 32-bit integers and then store the
331-
// relevant value into it (atomically, as concurrent panics may be
332-
// happening). Technically the runtime will probably do a nonatomic read of
333-
// these fields, but in theory they never read the *wrong* value so it
334-
// shouldn't be too bad...
335-
//
336-
// In any case, we basically need to do something like this until we can
337-
// express more operations in statics (and we may never be able to).
338-
unsafe {
339-
#[allow(function_casts_as_integer)]
340-
atomic_store::<_, { AtomicOrdering::SeqCst }>(
341-
(&raw mut THROW_INFO.pmfnUnwind).cast(),
342-
ptr_t::new(exception_cleanup as *mut u8).raw(),
343-
);
344-
atomic_store::<_, { AtomicOrdering::SeqCst }>(
345-
(&raw mut THROW_INFO.pCatchableTypeArray).cast(),
346-
ptr_t::new((&raw mut CATCHABLE_TYPE_ARRAY).cast()).raw(),
347-
);
348-
atomic_store::<_, { AtomicOrdering::SeqCst }>(
349-
(&raw mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[0]).cast(),
350-
ptr_t::new((&raw mut CATCHABLE_TYPE).cast()).raw(),
351-
);
352-
atomic_store::<_, { AtomicOrdering::SeqCst }>(
353-
(&raw mut CATCHABLE_TYPE.pType).cast(),
354-
ptr_t::new((&raw mut TYPE_DESCRIPTOR).cast()).raw(),
355-
);
356-
#[allow(function_casts_as_integer)]
357-
atomic_store::<_, { AtomicOrdering::SeqCst }>(
358-
(&raw mut CATCHABLE_TYPE.copyFunction).cast(),
359-
ptr_t::new(exception_copy as *mut u8).raw(),
360-
);
208+
unsafe extern "system-unwind" {
209+
fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *const u8) -> !;
361210
}
362211

363-
unsafe extern "system-unwind" {
364-
fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8) -> !;
212+
#[cfg(target_arch = "x86")]
213+
macro_rules! imgrel {
214+
($s:literal) => {
215+
concat!(".long ", $s)
216+
};
217+
}
218+
#[cfg(not(target_arch = "x86"))]
219+
macro_rules! imgrel {
220+
($s:literal) => {
221+
concat!(".long ", $s, "@IMGREL")
222+
};
365223
}
366224

225+
let throw_info: *const u8;
367226
unsafe {
368-
_CxxThrowException(throw_ptr, (&raw mut THROW_INFO) as *mut _);
227+
core::arch::asm!(
228+
cfg_select! { // let throw_info = &THROW_INFO;
229+
target_arch = "x86" => {
230+
"lea {}, [2f]"
231+
}
232+
target_arch = "x86_64" => {
233+
"lea {}, [rip + 2f]"
234+
}
235+
any(target_arch = "arm", target_arch = "aarch64", target_arch = "arm64ec") => {
236+
"adr {}, 2f"
237+
}
238+
},
239+
".pushsection .rdata,\"dr\"",
240+
".p2align 2",
241+
"2:", // static THROW_INFO = _ThrowInfo {
242+
".long 0", // attributes: 0,
243+
imgrel!("{cleanup}"), // pmfnUnwind: exception_cleanup,
244+
".long 0", // pForwardCompat: ptr::null_mut(),
245+
imgrel!("3f"), // pCatchableTypeArray: &CATCHABLE_TYPE_ARRAY,
246+
// }
247+
"3:", // static CATCHABLE_TYPE_ARRAY = _CatchableTypeArray {
248+
".long 1", // nCatchableTypes: 1,
249+
imgrel!("4f"), // arrayOfCatchableTypes: [&CATCHABLE_TYPE],
250+
// }
251+
"4:", // static CATCHABLE_TYPE = _CatchableType {
252+
".long 0", // properties: 0,
253+
imgrel!("{type_desc}"), // pType: &TYPE_DESCRIPTOR,
254+
// thisDisplacement: _PMD {
255+
".long 0", // mdisp: 0,
256+
".long -1", // pdisp: -1,
257+
".long 0", // vdisp: 0,
258+
// }
259+
".long {exception_size}", // sizeOrOffset: size_of::<Exception>(),
260+
imgrel!("{copy}"), // copyFunction: exception_copy,
261+
".popsection", // }
262+
out(reg) throw_info,
263+
cleanup = sym exception_cleanup,
264+
type_desc = sym TYPE_DESCRIPTOR,
265+
exception_size = const size_of::<Exception>(),
266+
copy = sym exception_copy,
267+
options(readonly, nostack),
268+
);
269+
_CxxThrowException((&raw mut exception).cast(), throw_info);
369270
}
370271
}
371272

0 commit comments

Comments
 (0)