From 1613fe2a9e5e72cef6d6a0bfae9a6a7004f2f67b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 25 Feb 2018 13:03:45 -0800 Subject: [PATCH] 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, bedbad6119 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]: https://github.com/rust-lang/rust/issues/33112 [f2]: https://github.com/rust-lang/rust/issues/33116 [switch]: https://github.com/rust-lang/rust/commit/38e6e5d0 [scary]: https://github.com/rust-lang/rust/blob/bedbad61195d2eae69b43eca49c6d3e2aee8f208/src/libpanic_unwind/seh.rs#L107-L294 [f3]: https://github.com/rust-lang/rust/issues/48251 --- .gitmodules | 2 +- src/libcore/panicking.rs | 4 +- src/libpanic_abort/lib.rs | 28 +- src/libpanic_unwind/lib.rs | 7 +- src/libpanic_unwind/seh.rs | 361 ++++++------------ src/libpanic_unwind/seh_stage0.rs | 315 +++++++++++++++ src/libpanic_unwind/windows.rs | 20 + src/librustc_trans/back/symbol_export.rs | 2 + src/librustc_trans/context.rs | 8 +- src/librustc_trans/intrinsic.rs | 139 +++++-- src/librustc_trans_utils/symbol_names.rs | 8 +- src/llvm | 2 +- .../run-make/longjmp-across-rust/Makefile | 5 + src/test/run-make/longjmp-across-rust/foo.c | 28 ++ src/test/run-make/longjmp-across-rust/main.rs | 41 ++ 15 files changed, 667 insertions(+), 303 deletions(-) create mode 100644 src/libpanic_unwind/seh_stage0.rs create mode 100644 src/test/run-make/longjmp-across-rust/Makefile create mode 100644 src/test/run-make/longjmp-across-rust/foo.c create mode 100644 src/test/run-make/longjmp-across-rust/main.rs diff --git a/.gitmodules b/.gitmodules index fc2f8bbc8a350..1224146aa7351 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "src/llvm"] path = src/llvm - url = https://github.com/rust-lang/llvm.git + url = https://github.com/alexcrichton/llvm.git branch = master [submodule "src/jemalloc"] path = src/jemalloc diff --git a/src/libcore/panicking.rs b/src/libcore/panicking.rs index 4170d91e5fce2..ff444a6051a77 100644 --- a/src/libcore/panicking.rs +++ b/src/libcore/panicking.rs @@ -65,8 +65,8 @@ pub fn panic_fmt(fmt: fmt::Arguments, file_line_col: &(&'static str, u32, u32)) extern { #[lang = "panic_fmt"] #[unwind] - fn panic_impl(fmt: fmt::Arguments, file: &'static str, line: u32, col: u32) -> !; + fn rust_begin_unwind(fmt: fmt::Arguments, file: &'static str, line: u32, col: u32) -> !; } let (file, line, col) = *file_line_col; - unsafe { panic_impl(fmt, file, line, col) } + unsafe { rust_begin_unwind(fmt, file, line, col) } } diff --git a/src/libpanic_abort/lib.rs b/src/libpanic_abort/lib.rs index 5f768ef4399e8..61f85439935d2 100644 --- a/src/libpanic_abort/lib.rs +++ b/src/libpanic_abort/lib.rs @@ -98,17 +98,33 @@ pub unsafe extern fn __rust_start_panic(_data: usize, _vtable: usize) -> u32 { // runtime at all. pub mod personalities { #[no_mangle] - #[cfg(not(all(target_os = "windows", - target_env = "gnu", - target_arch = "x86_64")))] + #[cfg(not(all( + target_os = "windows", + any( + target_env = "msvc", + all(target_env = "gnu", target_arch = "x86_64") + ) + )))] pub extern fn rust_eh_personality() {} // 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"))] + #[cfg(all( + target_os = "windows", + any( + target_env = "msvc", + all(target_env = "gnu", target_arch = "x86_64") + ) + ))] + #[cfg_attr( + all(target_env = "msvc", target_arch = "x86"), + export_name = "rust_seh32_personality" + )] + #[cfg_attr( + all(target_env = "msvc", target_arch = "x86_64"), + export_name = "rust_seh64_personality" + )] pub extern fn rust_eh_personality(_record: usize, _frame: usize, _context: usize, diff --git a/src/libpanic_unwind/lib.rs b/src/libpanic_unwind/lib.rs index 92e40e8f26d40..fdb0bfc2afbba 100644 --- a/src/libpanic_unwind/lib.rs +++ b/src/libpanic_unwind/lib.rs @@ -57,7 +57,12 @@ use core::raw; pub use imp::eh_frame_registry::*; // *-pc-windows-msvc -#[cfg(target_env = "msvc")] +#[cfg(all(target_env = "msvc", stage0))] +#[path = "seh_stage0.rs"] +mod imp; + +// *-pc-windows-msvc +#[cfg(all(target_env = "msvc", not(stage0)))] #[path = "seh.rs"] mod imp; diff --git a/src/libpanic_unwind/seh.rs b/src/libpanic_unwind/seh.rs index 5896421493008..5ee40d6c46571 100644 --- a/src/libpanic_unwind/seh.rs +++ b/src/libpanic_unwind/seh.rs @@ -18,38 +18,40 @@ //! //! In a nutshell, what happens here is: //! -//! 1. The `panic` function calls the standard Windows function -//! `_CxxThrowException` to throw a C++-like exception, triggering the -//! unwinding process. +//! 1. The `panic` function calls the standard Windows function `RaiseException` +//! with a Rust-specific code, triggering the unwinding process. //! 2. All landing pads generated by the compiler use the personality function -//! `__CxxFrameHandler3`, a function in the CRT, and the unwinding code in -//! Windows will use this personality function to execute all cleanup code on -//! the stack. +//! `__C_specific_handler` on 64-bit and `__except_handler3` on 32-bit, +//! functions in the CRT, and the unwinding code in Windows will use this +//! personality function to execute all cleanup code on the stack. //! 3. All compiler-generated calls to `invoke` have a landing pad set as a //! `cleanuppad` LLVM instruction, which indicates the start of the cleanup //! routine. The personality (in step 2, defined in the CRT) is responsible //! for running the cleanup routines. //! 4. Eventually the "catch" code in the `try` intrinsic (generated by the -//! compiler) is executed and indicates that control should come back to +//! compiler) is executed, which will ensure that the exception being caught +//! is indeed a Rust exception, indicating that control should come back to //! Rust. This is done via a `catchswitch` plus a `catchpad` instruction in //! LLVM IR terms, finally returning normal control to the program with a -//! `catchret` instruction. +//! `catchret` instruction. The `try` intrinsic uses a filter function to +//! detect what kind of exception is being thrown, and this detection is +//! implemented as the msvc_try_filter language item below. //! //! Some specific differences from the gcc-based exception handling are: //! //! * Rust has no custom personality function, it is instead *always* -//! `__CxxFrameHandler3`. Additionally, no extra filtering is performed, so we -//! end up catching any C++ exceptions that happen to look like the kind we're -//! throwing. Note that throwing an exception into Rust is undefined behavior -//! anyway, so this should be fine. +//! __C_specific_handler or __except_handler3, so the filtering is done in a +//! C++-like manner instead of in the personality function itself. Note that +//! the precise codegen for this was lifted from an LLVM test case for SEH +//! (this is the `__rust_try_filter` function below). //! * We've got some data to transmit across the unwinding boundary, //! specifically a `Box`. Like with Dwarf exceptions //! these two pointers are stored as a payload in the exception itself. On -//! MSVC, however, there's no need for an extra heap allocation because the -//! call stack is preserved while filter functions are being executed. This -//! means that the pointers are passed directly to `_CxxThrowException` which -//! are then recovered in the filter function to be written to the stack frame -//! of the `try` intrinsic. +//! MSVC, however, there's no need for an extra allocation because the call +//! stack is preserved while filter functions are being executed. This means +//! that the pointers are passed directly to `RaiseException` which are then +//! recovered in the filter function to be written to the stack frame of the +//! `try` intrinsic. //! //! [win64]: http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx //! [llvm]: http://llvm.org/docs/ExceptionHandling.html#background-on-windows-exceptions @@ -63,253 +65,116 @@ use core::mem; use core::raw; use windows as c; -use libc::{c_int, c_uint}; -// First up, a whole bunch of type definitions. There's a few platform-specific -// oddities here, and a lot that's just blatantly copied from LLVM. The purpose -// of all this is to implement the `panic` function below through a call to -// `_CxxThrowException`. -// -// This function takes two arguments. The first is a pointer to the data we're -// passing in, which in this case is our trait object. Pretty easy to find! The -// next, however, is more complicated. This is a pointer to a `_ThrowInfo` -// structure, and it generally is just intended to just describe the exception -// being thrown. -// -// Currently the definition of this type [1] is a little hairy, and the main -// oddity (and difference from the online article) is that on 32-bit the -// pointers are pointers but on 64-bit the pointers are expressed as 32-bit -// offsets from the `__ImageBase` symbol. The `ptr_t` and `ptr!` macro in the -// modules below are used to express this. -// -// The maze of type definitions also closely follows what LLVM emits for this -// sort of operation. For example, if you compile this C++ code on MSVC and emit -// the LLVM IR: -// -// #include -// -// void foo() { -// uint64_t a[2] = {0, 1}; -// throw a; -// } -// -// That's essentially what we're trying to emulate. Most of the constant values -// below were just copied from LLVM, I'm at least not 100% sure what's going on -// everywhere. For example the `.PA_K\0` and `.PEA_K\0` strings below (stuck in -// the names of a few of these) I'm not actually sure what they do, but it seems -// to mirror what LLVM does! -// -// In any case, these structures are all constructed in a similar manner, and -// it's just somewhat verbose for us. -// -// [1]: http://www.geoffchappell.com/studies/msvc/language/predefined/ - -#[cfg(target_arch = "x86")] -#[macro_use] -mod imp { - pub type ptr_t = *mut u8; - pub const OFFSET: i32 = 4; - - pub const NAME1: [u8; 7] = [b'.', b'P', b'A', b'_', b'K', 0, 0]; - pub const NAME2: [u8; 7] = [b'.', b'P', b'A', b'X', 0, 0, 0]; - - macro_rules! ptr { - (0) => (0 as *mut u8); - ($e:expr) => ($e as *mut u8); - } -} - -#[cfg(target_arch = "x86_64")] -#[macro_use] -mod imp { - pub type ptr_t = u32; - pub const OFFSET: i32 = 8; - - pub const NAME1: [u8; 7] = [b'.', b'P', b'E', b'A', b'_', b'K', 0]; - pub const NAME2: [u8; 7] = [b'.', b'P', b'E', b'A', b'X', 0, 0]; - - extern "C" { - pub static __ImageBase: u8; - } - - macro_rules! ptr { - (0) => (0); - ($e:expr) => { - (($e as usize) - (&imp::__ImageBase as *const _ as usize)) as u32 - } - } -} - -#[repr(C)] -pub struct _ThrowInfo { - pub attribues: c_uint, - pub pnfnUnwind: imp::ptr_t, - pub pForwardCompat: imp::ptr_t, - pub pCatchableTypeArray: imp::ptr_t, -} - -#[repr(C)] -pub struct _CatchableTypeArray { - pub nCatchableTypes: c_int, - pub arrayOfCatchableTypes: [imp::ptr_t; 2], -} - -#[repr(C)] -pub struct _CatchableType { - pub properties: c_uint, - pub pType: imp::ptr_t, - pub thisDisplacement: _PMD, - pub sizeOrOffset: c_int, - pub copy_function: imp::ptr_t, -} - -#[repr(C)] -pub struct _PMD { - pub mdisp: c_int, - pub pdisp: c_int, - pub vdisp: c_int, -} - -#[repr(C)] -pub struct _TypeDescriptor { - pub pVFTable: *const u8, - pub spare: *mut u8, - pub name: [u8; 7], -} - -static mut THROW_INFO: _ThrowInfo = _ThrowInfo { - attribues: 0, - pnfnUnwind: ptr!(0), - pForwardCompat: ptr!(0), - pCatchableTypeArray: ptr!(0), -}; - -static mut CATCHABLE_TYPE_ARRAY: _CatchableTypeArray = _CatchableTypeArray { - nCatchableTypes: 2, - arrayOfCatchableTypes: [ptr!(0), ptr!(0)], -}; - -static mut CATCHABLE_TYPE1: _CatchableType = _CatchableType { - properties: 1, - pType: ptr!(0), - thisDisplacement: _PMD { - mdisp: 0, - pdisp: -1, - vdisp: 0, - }, - sizeOrOffset: imp::OFFSET, - copy_function: ptr!(0), -}; - -static mut CATCHABLE_TYPE2: _CatchableType = _CatchableType { - properties: 1, - pType: ptr!(0), - thisDisplacement: _PMD { - mdisp: 0, - pdisp: -1, - vdisp: 0, - }, - sizeOrOffset: imp::OFFSET, - copy_function: ptr!(0), -}; - -extern "C" { - // The leading `\x01` byte here is actually a magical signal to LLVM to - // *not* apply any other mangling like prefixing with a `_` character. - // - // This symbol is the vtable used by C++'s `std::type_info`. Objects of type - // `std::type_info`, type descriptors, have a pointer to this table. Type - // descriptors are referenced by the C++ EH structures defined above and - // that we construct below. - #[link_name = "\x01??_7type_info@@6B@"] - static TYPE_INFO_VTABLE: *const u8; -} - -// We use #[lang = "msvc_try_filter"] here as this is the type descriptor which -// we'll use in LLVM's `catchpad` instruction which ends up also being passed as -// an argument to the C++ personality function. -// -// Again, I'm not entirely sure what this is describing, it just seems to work. -#[cfg_attr(not(test), lang = "msvc_try_filter")] -static mut TYPE_DESCRIPTOR1: _TypeDescriptor = _TypeDescriptor { - pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _, - spare: 0 as *mut _, - name: imp::NAME1, -}; - -static mut TYPE_DESCRIPTOR2: _TypeDescriptor = _TypeDescriptor { - pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _, - spare: 0 as *mut _, - name: imp::NAME2, -}; +// A code which indicates panics that originate from Rust. Note that some of the +// upper bits are used by the system so we just set them to 0 and ignore them. +// 0x 0 R S T +const RUST_PANIC: c::DWORD = 0x00525354; pub unsafe fn panic(data: Box) -> u32 { - use core::intrinsics::atomic_store; - - // _CxxThrowException executes entirely on this stack frame, so there's no - // need to otherwise transfer `data` to the heap. We just pass a stack - // pointer to this function. + // As mentioned above, the call stack here is preserved while the filter + // functions are running, so it's ok to pass stack-local arrays into + // `RaiseException`. // - // The first argument is the payload being thrown (our two pointers), and - // the second argument is the type information object describing the - // exception (constructed above). + // The two pointers of the `data` trait object are written to the stack, + // passed to `RaiseException`, and they're later extracted by the filter + // function below in the "custom exception information" section of the + // `EXCEPTION_RECORD` type. let ptrs = mem::transmute::<_, raw::TraitObject>(data); - let mut ptrs = [ptrs.data as u64, ptrs.vtable as u64]; - let mut ptrs_ptr = ptrs.as_mut_ptr(); - - // This... may seems surprising, and justifiably so. On 32-bit MSVC the - // pointers between these structure are just that, pointers. On 64-bit MSVC, - // however, the pointers between structures are rather expressed as 32-bit - // offsets from `__ImageBase`. - // - // Consequently, on 32-bit MSVC we can declare all these pointers in the - // `static`s above. On 64-bit MSVC, we would have to express subtraction of - // pointers in statics, which Rust does not currently allow, so we can't - // actually do that. - // - // The next best thing, then is to fill in these structures at runtime - // (panicking is already the "slow path" anyway). So here we reinterpret all - // of these pointer fields as 32-bit integers and then store the - // relevant value into it (atomically, as concurrent panics may be - // happening). Technically the runtime will probably do a nonatomic read of - // these fields, but in theory they never read the *wrong* value so it - // shouldn't be too bad... - // - // In any case, we basically need to do something like this until we can - // express more operations in statics (and we may never be able to). - atomic_store(&mut THROW_INFO.pCatchableTypeArray as *mut _ as *mut u32, - ptr!(&CATCHABLE_TYPE_ARRAY as *const _) as u32); - atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[0] as *mut _ as *mut u32, - ptr!(&CATCHABLE_TYPE1 as *const _) as u32); - atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[1] as *mut _ as *mut u32, - ptr!(&CATCHABLE_TYPE2 as *const _) as u32); - atomic_store(&mut CATCHABLE_TYPE1.pType as *mut _ as *mut u32, - ptr!(&TYPE_DESCRIPTOR1 as *const _) as u32); - atomic_store(&mut CATCHABLE_TYPE2.pType as *mut _ as *mut u32, - ptr!(&TYPE_DESCRIPTOR2 as *const _) as u32); - - c::_CxxThrowException(&mut ptrs_ptr as *mut _ as *mut _, - &mut THROW_INFO as *mut _ as *mut _); + let ptrs = [ptrs.data, ptrs.vtable]; + c::RaiseException(RUST_PANIC, 0, 2, ptrs.as_ptr() as *mut _); u32::max_value() } -pub fn payload() -> [u64; 2] { +pub fn payload() -> [usize; 2] { [0; 2] } -pub unsafe fn cleanup(payload: [u64; 2]) -> Box { +pub unsafe fn cleanup(payload: [usize; 2]) -> Box { mem::transmute(raw::TraitObject { data: payload[0] as *mut _, vtable: payload[1] as *mut _, }) } -// This is required by the compiler to exist (e.g. it's a lang item), but -// it's never actually called by the compiler because __C_specific_handler -// or _except_handler3 is the personality function that is always used. -// Hence this is just an aborting stub. +// This is quite a special function, and it's not literally passed in as the +// filter function for the `catchpad` of the `try` intrinsic. The compiler +// actually generates its own filter function wrapper which will delegate to +// this for the actual execution logic for whether the exception should be +// caught. The reasons for this are: +// +// * Each architecture has a slightly different ABI for the filter function +// here. For example on x86 there are no arguments but on x86_64 there are +// two. +// * This function needs access to the stack frame of the `try` intrinsic +// which is using this filter as a catch pad. This is because the payload +// of this exception, `Box`, needs to be transmitted to that +// location. +// +// Both of these differences end up using a ton of weird llvm-specific +// intrinsics, so it's actually pretty difficult to express the entire +// filter function in Rust itself. As a compromise, the compiler takes care +// of all the weird LLVM-specific and platform-specific stuff, getting to +// the point where this function makes the actual decision about what to +// catch given two parameters. +// +// The first parameter is `*mut EXCEPTION_POINTERS` which is some contextual +// information about the exception being filtered, and the second pointer is +// `*mut *mut [usize; 2]` (the payload here). This value points directly +// into the stack frame of the `try` intrinsic itself, and we use it to copy +// information from the exception onto the stack. +#[lang = "msvc_try_filter"] +unsafe extern fn __rust_try_filter(eh_ptrs: *mut u8, + payload: *mut u8) -> i32 { + let eh_ptrs = eh_ptrs as *mut c::EXCEPTION_POINTERS; + let payload = payload as *mut *mut [usize; 2]; + let record = &*(*eh_ptrs).ExceptionRecord; + if record.ExceptionCode != RUST_PANIC { + return 0 + } + (**payload)[0] = record.ExceptionInformation[0] as usize; + (**payload)[1] = record.ExceptionInformation[1] as usize; + return 1 +} + #[lang = "eh_personality"] -#[cfg(not(test))] -fn rust_eh_personality() { - unsafe { ::core::intrinsics::abort() } +#[cfg(target_arch = "x86_64")] +#[no_mangle] +#[allow(unused)] +unsafe extern fn rust_seh64_personality( + ExceptionRecord: *mut c::EXCEPTION_RECORD, + EstablisherFrame: *mut u8, + ContextRecord: *mut u8, + DispatcherContext: *mut u8, +) -> c::EXCEPTION_DISPOSITION { + if (*ExceptionRecord).ExceptionCode != RUST_PANIC { + c::ExceptionContinueSearch + } else { + c::__C_specific_handler(ExceptionRecord, + EstablisherFrame, + ContextRecord, + DispatcherContext) + } +} + +#[lang = "eh_personality"] +#[cfg(target_arch = "x86")] +#[no_mangle] +#[allow(unused)] +unsafe extern fn rust_seh32_personality( + exception_record: *mut c::EXCEPTION_RECORD, + registration: *mut u8, + context: *mut u8, + dispatcher: *mut u8, +) -> i32 { + if (*exception_record).ExceptionCode != RUST_PANIC && + (*exception_record).ExceptionCode != c::STATUS_UNWIND + { + c::DISPOSITION_CONTINUE_SEARCH + } else { + c::_except_handler3(exception_record, + registration, + context, + dispatcher) + } } diff --git a/src/libpanic_unwind/seh_stage0.rs b/src/libpanic_unwind/seh_stage0.rs new file mode 100644 index 0000000000000..5896421493008 --- /dev/null +++ b/src/libpanic_unwind/seh_stage0.rs @@ -0,0 +1,315 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Windows SEH +//! +//! On Windows (currently only on MSVC), the default exception handling +//! mechanism is Structured Exception Handling (SEH). This is quite different +//! than Dwarf-based exception handling (e.g. what other unix platforms use) in +//! terms of compiler internals, so LLVM is required to have a good deal of +//! extra support for SEH. +//! +//! In a nutshell, what happens here is: +//! +//! 1. The `panic` function calls the standard Windows function +//! `_CxxThrowException` to throw a C++-like exception, triggering the +//! unwinding process. +//! 2. All landing pads generated by the compiler use the personality function +//! `__CxxFrameHandler3`, a function in the CRT, and the unwinding code in +//! Windows will use this personality function to execute all cleanup code on +//! the stack. +//! 3. All compiler-generated calls to `invoke` have a landing pad set as a +//! `cleanuppad` LLVM instruction, which indicates the start of the cleanup +//! routine. The personality (in step 2, defined in the CRT) is responsible +//! for running the cleanup routines. +//! 4. Eventually the "catch" code in the `try` intrinsic (generated by the +//! compiler) is executed and indicates that control should come back to +//! Rust. This is done via a `catchswitch` plus a `catchpad` instruction in +//! LLVM IR terms, finally returning normal control to the program with a +//! `catchret` instruction. +//! +//! Some specific differences from the gcc-based exception handling are: +//! +//! * Rust has no custom personality function, it is instead *always* +//! `__CxxFrameHandler3`. Additionally, no extra filtering is performed, so we +//! end up catching any C++ exceptions that happen to look like the kind we're +//! throwing. Note that throwing an exception into Rust is undefined behavior +//! anyway, so this should be fine. +//! * We've got some data to transmit across the unwinding boundary, +//! specifically a `Box`. Like with Dwarf exceptions +//! these two pointers are stored as a payload in the exception itself. On +//! MSVC, however, there's no need for an extra heap allocation because the +//! call stack is preserved while filter functions are being executed. This +//! means that the pointers are passed directly to `_CxxThrowException` which +//! are then recovered in the filter function to be written to the stack frame +//! of the `try` intrinsic. +//! +//! [win64]: http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx +//! [llvm]: http://llvm.org/docs/ExceptionHandling.html#background-on-windows-exceptions + +#![allow(bad_style)] +#![allow(private_no_mangle_fns)] + +use alloc::boxed::Box; +use core::any::Any; +use core::mem; +use core::raw; + +use windows as c; +use libc::{c_int, c_uint}; + +// First up, a whole bunch of type definitions. There's a few platform-specific +// oddities here, and a lot that's just blatantly copied from LLVM. The purpose +// of all this is to implement the `panic` function below through a call to +// `_CxxThrowException`. +// +// This function takes two arguments. The first is a pointer to the data we're +// passing in, which in this case is our trait object. Pretty easy to find! The +// next, however, is more complicated. This is a pointer to a `_ThrowInfo` +// structure, and it generally is just intended to just describe the exception +// being thrown. +// +// Currently the definition of this type [1] is a little hairy, and the main +// oddity (and difference from the online article) is that on 32-bit the +// pointers are pointers but on 64-bit the pointers are expressed as 32-bit +// offsets from the `__ImageBase` symbol. The `ptr_t` and `ptr!` macro in the +// modules below are used to express this. +// +// The maze of type definitions also closely follows what LLVM emits for this +// sort of operation. For example, if you compile this C++ code on MSVC and emit +// the LLVM IR: +// +// #include +// +// void foo() { +// uint64_t a[2] = {0, 1}; +// throw a; +// } +// +// That's essentially what we're trying to emulate. Most of the constant values +// below were just copied from LLVM, I'm at least not 100% sure what's going on +// everywhere. For example the `.PA_K\0` and `.PEA_K\0` strings below (stuck in +// the names of a few of these) I'm not actually sure what they do, but it seems +// to mirror what LLVM does! +// +// In any case, these structures are all constructed in a similar manner, and +// it's just somewhat verbose for us. +// +// [1]: http://www.geoffchappell.com/studies/msvc/language/predefined/ + +#[cfg(target_arch = "x86")] +#[macro_use] +mod imp { + pub type ptr_t = *mut u8; + pub const OFFSET: i32 = 4; + + pub const NAME1: [u8; 7] = [b'.', b'P', b'A', b'_', b'K', 0, 0]; + pub const NAME2: [u8; 7] = [b'.', b'P', b'A', b'X', 0, 0, 0]; + + macro_rules! ptr { + (0) => (0 as *mut u8); + ($e:expr) => ($e as *mut u8); + } +} + +#[cfg(target_arch = "x86_64")] +#[macro_use] +mod imp { + pub type ptr_t = u32; + pub const OFFSET: i32 = 8; + + pub const NAME1: [u8; 7] = [b'.', b'P', b'E', b'A', b'_', b'K', 0]; + pub const NAME2: [u8; 7] = [b'.', b'P', b'E', b'A', b'X', 0, 0]; + + extern "C" { + pub static __ImageBase: u8; + } + + macro_rules! ptr { + (0) => (0); + ($e:expr) => { + (($e as usize) - (&imp::__ImageBase as *const _ as usize)) as u32 + } + } +} + +#[repr(C)] +pub struct _ThrowInfo { + pub attribues: c_uint, + pub pnfnUnwind: imp::ptr_t, + pub pForwardCompat: imp::ptr_t, + pub pCatchableTypeArray: imp::ptr_t, +} + +#[repr(C)] +pub struct _CatchableTypeArray { + pub nCatchableTypes: c_int, + pub arrayOfCatchableTypes: [imp::ptr_t; 2], +} + +#[repr(C)] +pub struct _CatchableType { + pub properties: c_uint, + pub pType: imp::ptr_t, + pub thisDisplacement: _PMD, + pub sizeOrOffset: c_int, + pub copy_function: imp::ptr_t, +} + +#[repr(C)] +pub struct _PMD { + pub mdisp: c_int, + pub pdisp: c_int, + pub vdisp: c_int, +} + +#[repr(C)] +pub struct _TypeDescriptor { + pub pVFTable: *const u8, + pub spare: *mut u8, + pub name: [u8; 7], +} + +static mut THROW_INFO: _ThrowInfo = _ThrowInfo { + attribues: 0, + pnfnUnwind: ptr!(0), + pForwardCompat: ptr!(0), + pCatchableTypeArray: ptr!(0), +}; + +static mut CATCHABLE_TYPE_ARRAY: _CatchableTypeArray = _CatchableTypeArray { + nCatchableTypes: 2, + arrayOfCatchableTypes: [ptr!(0), ptr!(0)], +}; + +static mut CATCHABLE_TYPE1: _CatchableType = _CatchableType { + properties: 1, + pType: ptr!(0), + thisDisplacement: _PMD { + mdisp: 0, + pdisp: -1, + vdisp: 0, + }, + sizeOrOffset: imp::OFFSET, + copy_function: ptr!(0), +}; + +static mut CATCHABLE_TYPE2: _CatchableType = _CatchableType { + properties: 1, + pType: ptr!(0), + thisDisplacement: _PMD { + mdisp: 0, + pdisp: -1, + vdisp: 0, + }, + sizeOrOffset: imp::OFFSET, + copy_function: ptr!(0), +}; + +extern "C" { + // The leading `\x01` byte here is actually a magical signal to LLVM to + // *not* apply any other mangling like prefixing with a `_` character. + // + // This symbol is the vtable used by C++'s `std::type_info`. Objects of type + // `std::type_info`, type descriptors, have a pointer to this table. Type + // descriptors are referenced by the C++ EH structures defined above and + // that we construct below. + #[link_name = "\x01??_7type_info@@6B@"] + static TYPE_INFO_VTABLE: *const u8; +} + +// We use #[lang = "msvc_try_filter"] here as this is the type descriptor which +// we'll use in LLVM's `catchpad` instruction which ends up also being passed as +// an argument to the C++ personality function. +// +// Again, I'm not entirely sure what this is describing, it just seems to work. +#[cfg_attr(not(test), lang = "msvc_try_filter")] +static mut TYPE_DESCRIPTOR1: _TypeDescriptor = _TypeDescriptor { + pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _, + spare: 0 as *mut _, + name: imp::NAME1, +}; + +static mut TYPE_DESCRIPTOR2: _TypeDescriptor = _TypeDescriptor { + pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _, + spare: 0 as *mut _, + name: imp::NAME2, +}; + +pub unsafe fn panic(data: Box) -> u32 { + use core::intrinsics::atomic_store; + + // _CxxThrowException executes entirely on this stack frame, so there's no + // need to otherwise transfer `data` to the heap. We just pass a stack + // pointer to this function. + // + // The first argument is the payload being thrown (our two pointers), and + // the second argument is the type information object describing the + // exception (constructed above). + let ptrs = mem::transmute::<_, raw::TraitObject>(data); + let mut ptrs = [ptrs.data as u64, ptrs.vtable as u64]; + let mut ptrs_ptr = ptrs.as_mut_ptr(); + + // This... may seems surprising, and justifiably so. On 32-bit MSVC the + // pointers between these structure are just that, pointers. On 64-bit MSVC, + // however, the pointers between structures are rather expressed as 32-bit + // offsets from `__ImageBase`. + // + // Consequently, on 32-bit MSVC we can declare all these pointers in the + // `static`s above. On 64-bit MSVC, we would have to express subtraction of + // pointers in statics, which Rust does not currently allow, so we can't + // actually do that. + // + // The next best thing, then is to fill in these structures at runtime + // (panicking is already the "slow path" anyway). So here we reinterpret all + // of these pointer fields as 32-bit integers and then store the + // relevant value into it (atomically, as concurrent panics may be + // happening). Technically the runtime will probably do a nonatomic read of + // these fields, but in theory they never read the *wrong* value so it + // shouldn't be too bad... + // + // In any case, we basically need to do something like this until we can + // express more operations in statics (and we may never be able to). + atomic_store(&mut THROW_INFO.pCatchableTypeArray as *mut _ as *mut u32, + ptr!(&CATCHABLE_TYPE_ARRAY as *const _) as u32); + atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[0] as *mut _ as *mut u32, + ptr!(&CATCHABLE_TYPE1 as *const _) as u32); + atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[1] as *mut _ as *mut u32, + ptr!(&CATCHABLE_TYPE2 as *const _) as u32); + atomic_store(&mut CATCHABLE_TYPE1.pType as *mut _ as *mut u32, + ptr!(&TYPE_DESCRIPTOR1 as *const _) as u32); + atomic_store(&mut CATCHABLE_TYPE2.pType as *mut _ as *mut u32, + ptr!(&TYPE_DESCRIPTOR2 as *const _) as u32); + + c::_CxxThrowException(&mut ptrs_ptr as *mut _ as *mut _, + &mut THROW_INFO as *mut _ as *mut _); + u32::max_value() +} + +pub fn payload() -> [u64; 2] { + [0; 2] +} + +pub unsafe fn cleanup(payload: [u64; 2]) -> Box { + mem::transmute(raw::TraitObject { + data: payload[0] as *mut _, + vtable: payload[1] as *mut _, + }) +} + +// This is required by the compiler to exist (e.g. it's a lang item), but +// it's never actually called by the compiler because __C_specific_handler +// or _except_handler3 is the personality function that is always used. +// Hence this is just an aborting stub. +#[lang = "eh_personality"] +#[cfg(not(test))] +fn rust_eh_personality() { + unsafe { ::core::intrinsics::abort() } +} diff --git a/src/libpanic_unwind/windows.rs b/src/libpanic_unwind/windows.rs index a7e90071ceae8..eaf33288545d1 100644 --- a/src/libpanic_unwind/windows.rs +++ b/src/libpanic_unwind/windows.rs @@ -28,6 +28,8 @@ pub const EXCEPTION_COLLIDED_UNWIND: DWORD = 0x40; // Collided exception handler pub const EXCEPTION_UNWIND: DWORD = EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND; +pub const DISPOSITION_CONTINUE_SEARCH: i32 = 1; +pub const STATUS_UNWIND: DWORD = 0xc0000027; #[repr(C)] pub struct EXCEPTION_RECORD { @@ -93,4 +95,22 @@ extern "system" { HistoryTable: *const UNWIND_HISTORY_TABLE); #[unwind] pub fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8); + + #[cfg(target_arch = "x86_64")] + pub fn __C_specific_handler( + ExceptionRecord: *mut EXCEPTION_RECORD, + EstablisherFrame: *mut u8, + ContextRecord: *mut u8, + DispatcherContext: *mut u8, + ) -> EXCEPTION_DISPOSITION; +} + +extern { + #[cfg(target_arch = "x86")] + pub fn _except_handler3( + exception_record: *mut EXCEPTION_RECORD, + registration: *mut u8, + context: *mut u8, + dispatcher: *mut u8, + ) -> i32; } diff --git a/src/librustc_trans/back/symbol_export.rs b/src/librustc_trans/back/symbol_export.rs index 989ef8a953746..c7e69ac032a24 100644 --- a/src/librustc_trans/back/symbol_export.rs +++ b/src/librustc_trans/back/symbol_export.rs @@ -184,6 +184,8 @@ pub fn provide_extern(providers: &mut Providers) { // In general though we won't link right if these // symbols are stripped, and LTO currently strips them. if &*name == "rust_eh_personality" || + &*name == "rust_seh32_personality" || + &*name == "rust_seh64_personality" || &*name == "rust_eh_register_frames" || &*name == "rust_eh_unregister_frames" { SymbolExportLevel::C diff --git a/src/librustc_trans/context.rs b/src/librustc_trans/context.rs index a285e5f263ab7..e40349373e579 100644 --- a/src/librustc_trans/context.rs +++ b/src/librustc_trans/context.rs @@ -373,12 +373,16 @@ impl<'b, 'tcx> CodegenCx<'b, 'tcx> { } let tcx = self.tcx; let llfn = match tcx.lang_items().eh_personality() { - Some(def_id) if !base::wants_msvc_seh(self.sess()) => { + Some(def_id) => { callee::resolve_and_get_fn(self, def_id, tcx.intern_substs(&[])) } _ => { let name = if base::wants_msvc_seh(self.sess()) { - "__CxxFrameHandler3" + if self.tcx.sess.target.target.arch == "x86" { + "rust_seh32_personality" + } else { + "rust_seh64_personality" + } } else { "rust_eh_personality" }; diff --git a/src/librustc_trans/intrinsic.rs b/src/librustc_trans/intrinsic.rs index b1f1fb52c907d..7287c0a7a3df6 100644 --- a/src/librustc_trans/intrinsic.rs +++ b/src/librustc_trans/intrinsic.rs @@ -10,6 +10,7 @@ #![allow(non_upper_case_globals)] +use callee; use intrinsics::{self, Intrinsic}; use llvm; use llvm::{ValueRef}; @@ -797,7 +798,9 @@ fn trans_msvc_try<'a, 'tcx>(bx: &Builder<'a, 'tcx>, // We're generating an IR snippet that looks like: // // declare i32 @rust_try(%func, %data, %ptr) { - // %slot = alloca i64* + // %slot = alloca i8* + // call @llvm.localescape(%slot) + // store %ptr, %slot // invoke %func(%data) to label %normal unwind label %catchswitch // // normal: @@ -807,58 +810,38 @@ fn trans_msvc_try<'a, 'tcx>(bx: &Builder<'a, 'tcx>, // %cs = catchswitch within none [%catchpad] unwind to caller // // catchpad: - // %tok = catchpad within %cs [%type_descriptor, 0, %slot] - // %ptr[0] = %slot[0] - // %ptr[1] = %slot[1] + // %tok = catchpad within %cs [%rust_try_filter] // catchret from %tok to label %caught // // caught: // ret i32 1 // } // - // This structure follows the basic usage of throw/try/catch in LLVM. - // For example, compile this C++ snippet to see what LLVM generates: - // - // #include - // - // int bar(void (*foo)(void), uint64_t *ret) { - // try { - // foo(); - // return 0; - // } catch(uint64_t a[2]) { - // ret[0] = a[0]; - // ret[1] = a[1]; - // return 1; - // } - // } + // This structure follows the basic usage of the instructions in LLVM + // (see their documentation/test cases for examples), but a + // perhaps-surprising part here is the usage of the `localescape` + // intrinsic. This is used to allow the filter function (also generated + // here) to access variables on the stack of this intrinsic. This + // ability enables us to transfer information about the exception being + // thrown to this point, where we're catching the exception. // // More information can be found in libstd's seh.rs implementation. - let i64p = Type::i64(cx).ptr_to(); + let ptr_align = bx.tcx().data_layout.pointer_align; - let slot = bx.alloca(i64p, "slot", ptr_align); - bx.invoke(func, &[data], normal.llbb(), catchswitch.llbb(), - None); + let slot = bx.alloca(Type::i8p(cx), "slot", ptr_align); + let localescape = cx.get_intrinsic("llvm.localescape"); + bx.call(localescape, &[slot], None); + bx.store(local_ptr, slot, ptr_align); + bx.invoke(func, &[data], normal.llbb(), catchswitch.llbb(), None); normal.ret(C_i32(cx, 0)); let cs = catchswitch.catch_switch(None, None, 1); catchswitch.add_handler(cs, catchpad.llbb()); - let tcx = cx.tcx; - let tydesc = match tcx.lang_items().msvc_try_filter() { - Some(did) => ::consts::get_static(cx, did), - None => bug!("msvc_try_filter not defined"), - }; - let tok = catchpad.catch_pad(cs, &[tydesc, C_i32(cx, 0), slot]); - let addr = catchpad.load(slot, ptr_align); - - let i64_align = bx.tcx().data_layout.i64_align; - let arg1 = catchpad.load(addr, i64_align); - let val1 = C_i32(cx, 1); - let arg2 = catchpad.load(catchpad.inbounds_gep(addr, &[val1]), i64_align); - let local_ptr = catchpad.bitcast(local_ptr, i64p); - catchpad.store(arg1, local_ptr, i64_align); - catchpad.store(arg2, catchpad.inbounds_gep(local_ptr, &[val1]), i64_align); + let filter = generate_filter_fn(cx, bx.llfn()); + let filter = catchpad.bitcast(filter, Type::i8p(cx)); + let tok = catchpad.catch_pad(cs, &[filter]); catchpad.catch_ret(tok, caught.llbb()); caught.ret(C_i32(cx, 1)); @@ -871,6 +854,86 @@ fn trans_msvc_try<'a, 'tcx>(bx: &Builder<'a, 'tcx>, bx.store(ret, dest, i32_align); } +// For MSVC-style exceptions (SEH), the compiler generates a filter function +// which is used to determine whether an exception is being caught (e.g. if it's +// a Rust exception or some other). +// +// This function is used to generate said filter function. The shim generated +// here is actually just a thin wrapper to call the real implementation in the +// standard library itself. For reasons as to why, see seh.rs in the standard +// library. +fn generate_filter_fn<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>, rust_try_fn: ValueRef) + -> ValueRef +{ + let rust_try_filter = match cx.tcx.lang_items().msvc_try_filter() { + Some(did) => { + callee::resolve_and_get_fn(cx, did, cx.tcx.intern_substs(&[])) + } + None => bug!("msvc_try_filter not defined"), + }; + + let output = cx.tcx.types.i32; + let i8p = cx.tcx.mk_mut_ptr(cx.tcx.types.i8); + + let frameaddress = cx.get_intrinsic("llvm.frameaddress"); + let recoverfp = cx.get_intrinsic("llvm.x86.seh.recoverfp"); + let localrecover = cx.get_intrinsic("llvm.localrecover"); + + // On all platforms, once we have the EXCEPTION_POINTERS handle as well as + // the base pointer, we follow the standard layout of: + // + // block: + // %parentfp = call i8* llvm.x86.seh.recoverfp(@rust_try_fn, %bp) + // %arg = call i8* llvm.localrecover(@rust_try_fn, %parentfp, 0) + // %ret = call i32 @the_real_filter_function(%ehptrs, %arg) + // ret i32 %ret + // + // The recoverfp intrinsic is used to recover the frame pointer of the + // `rust_try_fn` function, which is then in turn passed to the + // `localrecover` intrinsic (pairing with the `localescape` intrinsic + // mentioned above). Putting all this together means that we now have a + // handle to the arguments passed into the `try` function, allowing writing + // to the stack over there. + // + // For more info, see seh.rs in the standard library. + let do_trans = |bx: Builder, ehptrs, base_pointer| { + let rust_try_fn = bx.bitcast(rust_try_fn, Type::i8p(cx)); + let parentfp = bx.call(recoverfp, &[rust_try_fn, base_pointer], None); + let arg = bx.call(localrecover, + &[rust_try_fn, parentfp, C_i32(cx, 0)], None); + let ret = bx.call(rust_try_filter, &[ehptrs, arg], None); + bx.ret(ret); + }; + + if cx.tcx.sess.target.target.arch == "x86" { + // On x86 the filter function doesn't actually receive any arguments. + // Instead the %ebp register contains some contextual information. + // + // Unfortunately I don't know of any great documentation as to what's + // going on here, all I can say is that there's a few tests cases in + // LLVM's test suite which follow this pattern of instructions, so we + // just do the same. + gen_fn(cx, "__rustc_try_filter", vec![], output, &mut |bx| { + let ebp = bx.call(frameaddress, &[C_i32(cx, 1)], None); + let exn = bx.inbounds_gep(ebp, &[C_i32(cx, -20)]); + let ptr_align = bx.tcx().data_layout.pointer_align; + let exn = bx.load(bx.bitcast(exn, Type::i8p(cx).ptr_to()), ptr_align); + do_trans(bx, exn, ebp); + }) + } else if cx.tcx.sess.target.target.arch == "x86_64" { + // Conveniently on x86_64 the EXCEPTION_POINTERS handle and base pointer + // are passed in as arguments to the filter function, so we just pass + // those along. + gen_fn(cx, "__rustc_try_filter", vec![i8p, i8p], output, &mut |bx| { + let exn = llvm::get_param(bx.llfn(), 0); + let rbp = llvm::get_param(bx.llfn(), 1); + do_trans(bx, exn, rbp); + }) + } else { + bug!("unknown target to generate a filter function") + } +} + // Definition of the standard "try" function for Rust using the GNU-like model // of exceptions (e.g. the normal semantics of LLVM's landingpad and invoke // instructions). diff --git a/src/librustc_trans_utils/symbol_names.rs b/src/librustc_trans_utils/symbol_names.rs index fb299bf7eea0c..f9bc67bc108d9 100644 --- a/src/librustc_trans_utils/symbol_names.rs +++ b/src/librustc_trans_utils/symbol_names.rs @@ -275,10 +275,6 @@ fn compute_symbol_name<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, instance: Instance tcx.is_foreign_item(def_id) }; - if let Some(name) = weak_lang_items::link_name(&attrs) { - return name.to_string(); - } - if is_foreign { if let Some(name) = attr::first_attr_value_str_by_name(&attrs, "link_name") { return name.to_string(); @@ -297,6 +293,10 @@ fn compute_symbol_name<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, instance: Instance return tcx.item_name(def_id).to_string(); } + if let Some(name) = weak_lang_items::link_name(&attrs) { + return name.to_string(); + } + // We want to compute the "type" of this item. Unfortunately, some // kinds of items (e.g., closures) don't have an entry in the // item-type array. So walk back up the find the closest parent diff --git a/src/llvm b/src/llvm index 9f81beaf32608..6e690ae043b84 160000 --- a/src/llvm +++ b/src/llvm @@ -1 +1 @@ -Subproject commit 9f81beaf32608fbe1fe0f2a82f974e800e9d8c62 +Subproject commit 6e690ae043b84e704b6dda22763149f93493b6d0 diff --git a/src/test/run-make/longjmp-across-rust/Makefile b/src/test/run-make/longjmp-across-rust/Makefile new file mode 100644 index 0000000000000..9d71ed8fcf3ab --- /dev/null +++ b/src/test/run-make/longjmp-across-rust/Makefile @@ -0,0 +1,5 @@ +-include ../tools.mk + +all: $(call NATIVE_STATICLIB,foo) + $(RUSTC) main.rs + $(call RUN,main) diff --git a/src/test/run-make/longjmp-across-rust/foo.c b/src/test/run-make/longjmp-across-rust/foo.c new file mode 100644 index 0000000000000..eb9939576741b --- /dev/null +++ b/src/test/run-make/longjmp-across-rust/foo.c @@ -0,0 +1,28 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include +#include + +static jmp_buf ENV; + +extern void test_middle(); + +void test_start(void(*f)()) { + if (setjmp(ENV) != 0) + return; + f(); + assert(0); +} + +void test_end() { + longjmp(ENV, 1); + assert(0); +} diff --git a/src/test/run-make/longjmp-across-rust/main.rs b/src/test/run-make/longjmp-across-rust/main.rs new file mode 100644 index 0000000000000..0bf5f41d984b1 --- /dev/null +++ b/src/test/run-make/longjmp-across-rust/main.rs @@ -0,0 +1,41 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[link(name = "foo", kind = "static")] +extern { + fn test_start(f: extern fn()); + fn test_end(); +} + +fn main() { + unsafe { + test_start(test_middle); + } +} + +struct A; + +impl Drop for A { + fn drop(&mut self) { + panic!() + } +} + +extern fn test_middle() { + let _a = A; + foo(); +} + +fn foo() { + let _a = A; + unsafe { + test_end(); + } +}