Skip to content

Commit f604467

Browse files
committed
Abort in panic_abort eh_personality
This ensures that it is impossible to unwind through rust code in case of -Cpanic=abort
1 parent 64de497 commit f604467

File tree

3 files changed

+284
-67
lines changed

3 files changed

+284
-67
lines changed

library/panic_abort/src/emcc.rs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! Unwinding for *emscripten* target.
2+
//!
3+
//! Whereas Rust's usual unwinding implementation for Unix platforms
4+
//! calls into the libunwind APIs directly, on Emscripten we instead
5+
//! call into the C++ unwinding APIs. This is just an expedience since
6+
//! Emscripten's runtime always implements those APIs and does not
7+
//! implement libunwind.
8+
9+
use libc::c_int;
10+
use unwind as uw;
11+
12+
// This matches the layout of std::type_info in C++
13+
#[repr(C)]
14+
struct TypeInfo {
15+
vtable: *const usize,
16+
name: *const u8,
17+
}
18+
unsafe impl Sync for TypeInfo {}
19+
20+
extern "C" {
21+
// The leading `\x01` byte here is actually a magical signal to LLVM to
22+
// *not* apply any other mangling like prefixing with a `_` character.
23+
//
24+
// This symbol is the vtable used by C++'s `std::type_info`. Objects of type
25+
// `std::type_info`, type descriptors, have a pointer to this table. Type
26+
// descriptors are referenced by the C++ EH structures defined above and
27+
// that we construct below.
28+
//
29+
// Note that the real size is larger than 3 usize, but we only need our
30+
// vtable to point to the third element.
31+
#[link_name = "\x01_ZTVN10__cxxabiv117__class_type_infoE"]
32+
static CLASS_TYPE_INFO_VTABLE: [usize; 3];
33+
}
34+
35+
// std::type_info for a rust_panic class
36+
#[lang = "eh_catch_typeinfo"]
37+
static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
38+
// Normally we would use .as_ptr().add(2) but this doesn't work in a const context.
39+
vtable: unsafe { &CLASS_TYPE_INFO_VTABLE[2] },
40+
// This intentionally doesn't use the normal name mangling scheme because
41+
// we don't want C++ to be able to produce or catch Rust panics.
42+
name: b"rust_panic\0".as_ptr(),
43+
};
44+
45+
#[rustc_std_internal_symbol]
46+
unsafe extern "C" fn rust_eh_personality(
47+
_version: c_int,
48+
_actions: uw::_Unwind_Action,
49+
_exception_class: uw::_Unwind_Exception_Class,
50+
_exception_object: *mut uw::_Unwind_Exception,
51+
_context: *mut uw::_Unwind_Context,
52+
) -> uw::_Unwind_Reason_Code {
53+
crate::do_abort();
54+
}

library/panic_abort/src/gcc.rs

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
//! Implementation of panics backed by libgcc/libunwind (in some form).
2+
//!
3+
//! For background on exception handling and stack unwinding please see
4+
//! "Exception Handling in LLVM" (llvm.org/docs/ExceptionHandling.html) and
5+
//! documents linked from it.
6+
//! These are also good reads:
7+
//! * <https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html>
8+
//! * <https://monoinfinito.wordpress.com/series/exception-handling-in-c/>
9+
//! * <https://www.airs.com/blog/index.php?s=exception+frames>
10+
//!
11+
//! ## A brief summary
12+
//!
13+
//! Exception handling happens in two phases: a search phase and a cleanup
14+
//! phase.
15+
//!
16+
//! In both phases the unwinder walks stack frames from top to bottom using
17+
//! information from the stack frame unwind sections of the current process's
18+
//! modules ("module" here refers to an OS module, i.e., an executable or a
19+
//! dynamic library).
20+
//!
21+
//! For each stack frame, it invokes the associated "personality routine", whose
22+
//! address is also stored in the unwind info section.
23+
//!
24+
//! In the search phase, the job of a personality routine is to examine
25+
//! exception object being thrown, and to decide whether it should be caught at
26+
//! that stack frame. Once the handler frame has been identified, cleanup phase
27+
//! begins.
28+
//!
29+
//! In the cleanup phase, the unwinder invokes each personality routine again.
30+
//! This time it decides which (if any) cleanup code needs to be run for
31+
//! the current stack frame. If so, the control is transferred to a special
32+
//! branch in the function body, the "landing pad", which invokes destructors,
33+
//! frees memory, etc. At the end of the landing pad, control is transferred
34+
//! back to the unwinder and unwinding resumes.
35+
//!
36+
//! Once stack has been unwound down to the handler frame level, unwinding stops
37+
//! and the last personality routine transfers control to the catch block.
38+
39+
#![allow(nonstandard_style)]
40+
41+
use libc::c_int;
42+
43+
// The following code is based on GCC's C and C++ personality routines. For reference, see:
44+
// https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc
45+
// https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c
46+
47+
cfg_if::cfg_if! {
48+
if #[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "netbsd")))] {
49+
// ARM EHABI personality routine.
50+
// https://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf
51+
//
52+
// iOS uses the default routine instead since it uses SjLj unwinding.
53+
#[rustc_std_internal_symbol]
54+
unsafe extern "C" fn rust_eh_personality(_state: _Unwind_State,
55+
_exception_object: *mut _Unwind_Exception,
56+
_context: *mut _Unwind_Context)
57+
-> _Unwind_Reason_Code {
58+
crate::do_abort()
59+
}
60+
} else {
61+
cfg_if::cfg_if! {
62+
if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] {
63+
// On x86_64 MinGW targets, the unwinding mechanism is SEH however the unwind
64+
// handler data (aka LSDA) uses GCC-compatible encoding.
65+
#[rustc_std_internal_symbol]
66+
#[allow(nonstandard_style)]
67+
unsafe extern "C" fn rust_eh_personality(_exceptionRecord: *mut EXCEPTION_RECORD,
68+
_establisherFrame: LPVOID,
69+
_contextRecord: *mut CONTEXT,
70+
_dispatcherContext: *mut DISPATCHER_CONTEXT)
71+
-> EXCEPTION_DISPOSITION {
72+
crate::do_abort();
73+
}
74+
} else {
75+
// The personality routine for most of our targets.
76+
#[rustc_std_internal_symbol]
77+
unsafe extern "C" fn rust_eh_personality(_version: c_int,
78+
_actions: _Unwind_Action,
79+
_exception_class: _Unwind_Exception_Class,
80+
_exception_object: *mut _Unwind_Exception,
81+
_context: *mut _Unwind_Context)
82+
-> _Unwind_Reason_Code {
83+
crate::do_abort();
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
// Frame unwind info registration
91+
//
92+
// Each module's image contains a frame unwind info section (usually
93+
// ".eh_frame"). When a module is loaded/unloaded into the process, the
94+
// unwinder must be informed about the location of this section in memory. The
95+
// methods of achieving that vary by the platform. On some (e.g., Linux), the
96+
// unwinder can discover unwind info sections on its own (by dynamically
97+
// enumerating currently loaded modules via the dl_iterate_phdr() API and
98+
// finding their ".eh_frame" sections); Others, like Windows, require modules
99+
// to actively register their unwind info sections via unwinder API.
100+
//
101+
// This module defines two symbols which are referenced and called from
102+
// rsbegin.rs to register our information with the GCC runtime. The
103+
// implementation of stack unwinding is (for now) deferred to libgcc_eh, however
104+
// Rust crates use these Rust-specific entry points to avoid potential clashes
105+
// with any GCC runtime.
106+
#[cfg(all(target_os = "windows", target_arch = "x86", target_env = "gnu"))]
107+
mod eh_frame_registry {
108+
extern "C" {
109+
fn __register_frame_info(eh_frame_begin: *const u8, object: *mut u8);
110+
fn __deregister_frame_info(eh_frame_begin: *const u8, object: *mut u8);
111+
}
112+
113+
#[rustc_std_internal_symbol]
114+
unsafe extern "C" fn rust_eh_register_frames(eh_frame_begin: *const u8, object: *mut u8) {
115+
__register_frame_info(eh_frame_begin, object);
116+
}
117+
118+
#[rustc_std_internal_symbol]
119+
unsafe extern "C" fn rust_eh_unregister_frames(eh_frame_begin: *const u8, object: *mut u8) {
120+
__deregister_frame_info(eh_frame_begin, object);
121+
}
122+
}
123+
124+
use libc::{c_void, uintptr_t};
125+
126+
#[repr(C)]
127+
#[derive(Debug, Copy, Clone, PartialEq)]
128+
enum _Unwind_Reason_Code {
129+
_URC_NO_REASON = 0,
130+
_URC_FOREIGN_EXCEPTION_CAUGHT = 1,
131+
_URC_FATAL_PHASE2_ERROR = 2,
132+
_URC_FATAL_PHASE1_ERROR = 3,
133+
_URC_NORMAL_STOP = 4,
134+
_URC_END_OF_STACK = 5,
135+
_URC_HANDLER_FOUND = 6,
136+
_URC_INSTALL_CONTEXT = 7,
137+
_URC_CONTINUE_UNWIND = 8,
138+
_URC_FAILURE = 9, // used only by ARM EHABI
139+
}
140+
141+
type _Unwind_Exception_Class = u64;
142+
type _Unwind_Word = uintptr_t;
143+
type _Unwind_Ptr = uintptr_t;
144+
type _Unwind_Trace_Fn =
145+
extern "C" fn(ctx: *mut _Unwind_Context, arg: *mut c_void) -> _Unwind_Reason_Code;
146+
147+
enum _Unwind_Exception {}
148+
149+
enum _Unwind_Context {}
150+
151+
type _Unwind_Exception_Cleanup_Fn =
152+
extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception);
153+
154+
cfg_if::cfg_if! {
155+
if #[cfg(any(target_os = "ios", target_os = "netbsd", not(target_arch = "arm")))] {
156+
// Not ARM EHABI
157+
#[repr(C)]
158+
#[derive(Copy, Clone, PartialEq)]
159+
enum _Unwind_Action {
160+
_UA_SEARCH_PHASE = 1,
161+
_UA_CLEANUP_PHASE = 2,
162+
_UA_HANDLER_FRAME = 4,
163+
_UA_FORCE_UNWIND = 8,
164+
_UA_END_OF_STACK = 16,
165+
}
166+
167+
} else {
168+
// ARM EHABI
169+
#[repr(C)]
170+
#[derive(Copy, Clone, PartialEq)]
171+
enum _Unwind_State {
172+
_US_VIRTUAL_UNWIND_FRAME = 0,
173+
_US_UNWIND_FRAME_STARTING = 1,
174+
_US_UNWIND_FRAME_RESUME = 2,
175+
_US_ACTION_MASK = 3,
176+
_US_FORCE_UNWIND = 8,
177+
_US_END_OF_STACK = 16,
178+
}
179+
}
180+
} // cfg_if!
181+
182+
cfg_if::cfg_if! {
183+
if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] {
184+
// We declare these as opaque types. This is fine since you just need to
185+
// pass them to _GCC_specific_handler and forget about them.
186+
enum EXCEPTION_RECORD {}
187+
type LPVOID = *mut c_void;
188+
enum CONTEXT {}
189+
enum DISPATCHER_CONTEXT {}
190+
type EXCEPTION_DISPOSITION = c_int;
191+
}
192+
} // cfg_if!

library/panic_abort/src/lib.rs

+38-67
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,21 @@ pub unsafe extern "C" fn __rust_panic_cleanup(_: *mut u8) -> *mut (dyn Any + Sen
2828
unreachable!()
2929
}
3030

31-
// "Leak" the payload and shim to the relevant abort on the platform in question.
31+
/// "Leak" the payload and abort.
3232
#[rustc_std_internal_symbol]
3333
pub unsafe extern "C" fn __rust_start_panic(_payload: *mut &mut dyn BoxMeUp) -> u32 {
3434
// Android has the ability to attach a message as part of the abort.
3535
#[cfg(target_os = "android")]
3636
android::android_set_abort_message(_payload);
3737

38-
abort();
38+
do_abort();
39+
}
40+
41+
/// Shim to the relevant abort on the platform in question.
42+
fn do_abort() -> ! {
43+
unsafe {
44+
abort();
45+
}
3946

4047
cfg_if::cfg_if! {
4148
if #[cfg(unix)] {
@@ -86,70 +93,34 @@ pub unsafe extern "C" fn __rust_start_panic(_payload: *mut &mut dyn BoxMeUp) ->
8693
}
8794
}
8895

89-
// This... is a bit of an oddity. The tl;dr; is that this is required to link
90-
// correctly, the longer explanation is below.
91-
//
92-
// Right now the binaries of libcore/libstd that we ship are all compiled with
93-
// `-C panic=unwind`. This is done to ensure that the binaries are maximally
94-
// compatible with as many situations as possible. The compiler, however,
95-
// requires a "personality function" for all functions compiled with `-C
96-
// panic=unwind`. This personality function is hardcoded to the symbol
97-
// `rust_eh_personality` and is defined by the `eh_personality` lang item.
98-
//
99-
// So... why not just define that lang item here? Good question! The way that
100-
// panic runtimes are linked in is actually a little subtle in that they're
101-
// "sort of" in the compiler's crate store, but only actually linked if another
102-
// isn't actually linked. This ends up meaning that both this crate and the
103-
// panic_unwind crate can appear in the compiler's crate store, and if both
104-
// define the `eh_personality` lang item then that'll hit an error.
105-
//
106-
// To handle this the compiler only requires the `eh_personality` is defined if
107-
// the panic runtime being linked in is the unwinding runtime, and otherwise
108-
// it's not required to be defined (rightfully so). In this case, however, this
109-
// library just defines this symbol so there's at least some personality
110-
// somewhere.
111-
//
112-
// Essentially this symbol is just defined to get wired up to libcore/libstd
113-
// binaries, but it should never be called as we don't link in an unwinding
114-
// runtime at all.
115-
pub mod personalities {
116-
#[rustc_std_internal_symbol]
117-
#[cfg(not(any(
118-
all(target_arch = "wasm32", not(target_os = "emscripten"),),
119-
all(target_os = "windows", target_env = "gnu", target_arch = "x86_64",),
120-
)))]
121-
pub extern "C" fn rust_eh_personality() {}
122-
123-
// On x86_64-pc-windows-gnu we use our own personality function that needs
124-
// to return `ExceptionContinueSearch` as we're passing on all our frames.
125-
#[rustc_std_internal_symbol]
126-
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86_64"))]
127-
pub extern "C" fn rust_eh_personality(
128-
_record: usize,
129-
_frame: usize,
130-
_context: usize,
131-
_dispatcher: usize,
132-
) -> u32 {
133-
1 // `ExceptionContinueSearch`
96+
cfg_if::cfg_if! {
97+
if #[cfg(target_os = "emscripten")] {
98+
#[path = "emcc.rs"]
99+
mod imp;
100+
} else if #[cfg(target_env = "msvc")] {
101+
// This is required by the compiler to exist (e.g., it's a lang item), but
102+
// it's never actually called by the compiler because __C_specific_handler
103+
// or _except_handler3 is the personality function that is always used.
104+
// Hence this is just an aborting stub.
105+
#[rustc_std_internal_symbol]
106+
fn rust_eh_personality() {
107+
core::intrinsics::abort()
108+
}
109+
} else if #[cfg(any(
110+
all(target_family = "windows", target_env = "gnu"),
111+
target_os = "psp",
112+
target_family = "unix",
113+
all(target_vendor = "fortanix", target_env = "sgx"),
114+
))] {
115+
#[path = "gcc.rs"]
116+
mod imp;
117+
} else {
118+
// Targets that don't support unwinding.
119+
// - arch=wasm32
120+
// - os=none ("bare metal" targets)
121+
// - os=uefi
122+
// - os=hermit
123+
// - nvptx64-nvidia-cuda
124+
// - arch=avr
134125
}
135-
136-
// Similar to above, this corresponds to the `eh_catch_typeinfo` lang item
137-
// that's only used on Emscripten currently.
138-
//
139-
// Since panics don't generate exceptions and foreign exceptions are
140-
// currently UB with -C panic=abort (although this may be subject to
141-
// change), any catch_unwind calls will never use this typeinfo.
142-
#[rustc_std_internal_symbol]
143-
#[allow(non_upper_case_globals)]
144-
#[cfg(target_os = "emscripten")]
145-
static rust_eh_catch_typeinfo: [usize; 2] = [0; 2];
146-
147-
// These two are called by our startup objects on i686-pc-windows-gnu, but
148-
// they don't need to do anything so the bodies are nops.
149-
#[rustc_std_internal_symbol]
150-
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
151-
pub extern "C" fn rust_eh_register_frames() {}
152-
#[rustc_std_internal_symbol]
153-
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
154-
pub extern "C" fn rust_eh_unregister_frames() {}
155126
}

0 commit comments

Comments
 (0)