about summary refs log tree commit diff
path: root/src/libstd
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-06-19 14:57:06 -0700
committerAlex Crichton <alex@alexcrichton.com>2015-06-25 09:33:15 -0700
commit91d799eab0d7f6784fb4366182b5007cf055519d (patch)
tree55e7a492bc92ed35982c7065767e04dfb59ddca3 /src/libstd
parent1ec520a531b544079690f8178a7660421e8a713a (diff)
downloadrust-91d799eab0d7f6784fb4366182b5007cf055519d.tar.gz
rust-91d799eab0d7f6784fb4366182b5007cf055519d.zip
msvc: Implement runtime support for unwinding
Now that LLVM has been updated, the only remaining roadblock to implementing
unwinding for MSVC is to fill out the runtime support in `std::rt::unwind::seh`.
This commit does precisely that, fixing up some other bits and pieces along the
way:

* The `seh` unwinding module now uses `RaiseException` to initiate a panic.
* The `rust_try.ll` file was rewritten for MSVC (as it's quite different) and is
  located at `rust_try_msvc_64.ll`, only included on MSVC builds for now.
* The personality function for all landing pads generated by LLVM is hard-wired
  to `__C_specific_handler` instead of the standard `rust_eh_personality` lang
  item. This is required to get LLVM to emit SEH unwinding information instead
  of DWARF unwinding information. This also means that on MSVC the
  `rust_eh_personality` function is entirely unused (but is defined as it's a
  lang item).

More details about how panicking works on SEH can be found in the
`rust_try_msvc_64.ll` or `seh.rs` files, but I'm always open to adding more
comments!

A key aspect of this PR is missing, however, which is that **unwinding is still
turned off by default for MSVC**. There is a [bug in llvm][llvm-bug] which
causes optimizations to inline enough landing pads that LLVM chokes. If the
compiler is optimized at `-O1` (where inlining isn't enabled) then it can
bootstrap with unwinding enabled, but when optimized at `-O2` (inlining is
enabled) then it hits a fatal LLVM error.

[llvm-bug]: https://llvm.org/bugs/show_bug.cgi?id=23884
Diffstat (limited to 'src/libstd')
-rw-r--r--src/libstd/rt/unwind/seh.rs131
1 files changed, 122 insertions, 9 deletions
diff --git a/src/libstd/rt/unwind/seh.rs b/src/libstd/rt/unwind/seh.rs
index a72c1debe14..632ab4f8e25 100644
--- a/src/libstd/rt/unwind/seh.rs
+++ b/src/libstd/rt/unwind/seh.rs
@@ -8,23 +8,136 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+//! Win64 SEH (see http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx)
+//!
+//! 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. Currently this support is somewhat lacking, so what's
+//! here is the bare bones of SEH support.
+//!
+//! In a nutshell, what happens here is:
+//!
+//! 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 (just "cleanup" landing pads)
+//!    use the personality function `__C_specific_handler`, 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. Eventually the "catch" code in `rust_try` (located in
+//!    src/rt/rust_try_msvc_64.ll) is executed, which will ensure that the
+//!    exception being caught is indeed a Rust exception, returning control back
+//!    into Rust.
+//!
+//! Some specific differences from the gcc-based exception handling are:
+//!
+//! * Rust has no custom personality function, it is instead *always*
+//!   __C_specific_handler, so the filtering is done in a C++-like manner
+//!   instead of in the personality function itself. Note that the specific
+//!   syntax for this (found in the rust_try_msvc_64.ll) is taken from an LLVM
+//!   test case for SEH.
+//! * We've got some data to transmit across the unwinding boundary,
+//!   specifically a `Box<Any + Send + 'static>`. In Dwarf-based unwinding this
+//!   data is part of the payload of the exception, but I have not currently
+//!   figured out how to do this with LLVM's bindings. Judging by some comments
+//!   in the LLVM test cases this may not even be possible currently with LLVM,
+//!   so this is just abandoned entirely. Instead the data is stored in a
+//!   thread-local in `panic` and retrieved during `cleanup`.
+//!
+//! So given all that, the bindings here are pretty small,
+
+#![allow(bad_style)]
+
 use prelude::v1::*;
 
 use any::Any;
-use intrinsics;
-use libc::c_void;
+use libc::{c_ulong, DWORD, c_void};
+use sys_common::thread_local::StaticKey;
+
+//                        0x R U S T
+const RUST_PANIC: DWORD = 0x52555354;
+static PANIC_DATA: StaticKey = StaticKey::new(None);
+
+// This function is provided by kernel32.dll
+extern "system" {
+    fn RaiseException(dwExceptionCode: DWORD,
+                      dwExceptionFlags: DWORD,
+                      nNumberOfArguments: DWORD,
+                      lpArguments: *const c_ulong);
+}
+
+#[repr(C)]
+pub struct EXCEPTION_POINTERS {
+    ExceptionRecord: *mut EXCEPTION_RECORD,
+    ContextRecord: *mut CONTEXT,
+}
+
+enum CONTEXT {}
+
+#[repr(C)]
+struct EXCEPTION_RECORD {
+    ExceptionCode: DWORD,
+    ExceptionFlags: DWORD,
+    ExceptionRecord: *mut _EXCEPTION_RECORD,
+    ExceptionAddress: *mut c_void,
+    NumberParameters: DWORD,
+    ExceptionInformation: [*mut c_ulong; EXCEPTION_MAXIMUM_PARAMETERS],
+}
 
-pub unsafe fn panic(_data: Box<Any + Send + 'static>) -> ! {
-    intrinsics::abort();
+enum _EXCEPTION_RECORD {}
+
+const EXCEPTION_MAXIMUM_PARAMETERS: usize = 15;
+
+pub unsafe fn panic(data: Box<Any + Send + 'static>) -> ! {
+    // See module docs above for an explanation of why `data` is stored in a
+    // thread local instead of being passed as an argument to the
+    // `RaiseException` function (which can in theory carry along arbitrary
+    // data).
+    let exception = Box::new(data);
+    rtassert!(PANIC_DATA.get().is_null());
+    PANIC_DATA.set(Box::into_raw(exception) as *mut u8);
+
+    RaiseException(RUST_PANIC, 0, 0, 0 as *const _);
+    rtabort!("could not unwind stack");
 }
 
-pub unsafe fn cleanup(_ptr: *mut c_void) -> Box<Any + Send + 'static> {
-    intrinsics::abort();
+pub unsafe fn cleanup(ptr: *mut c_void) -> Box<Any + Send + 'static> {
+    // The `ptr` here actually corresponds to the code of the exception, and our
+    // real data is stored in our thread local.
+    rtassert!(ptr as DWORD == RUST_PANIC);
+
+    let data = PANIC_DATA.get() as *mut Box<Any + Send + 'static>;
+    PANIC_DATA.set(0 as *mut u8);
+    rtassert!(!data.is_null());
+
+    *Box::from_raw(data)
 }
 
+// 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 is the
+// personality function that is always used. Hence this is just an aborting
+// stub.
 #[lang = "eh_personality"]
-#[no_mangle]
-pub extern fn rust_eh_personality() {}
+fn rust_eh_personality() {
+    unsafe { ::intrinsics::abort() }
+}
 
+// This is a function referenced from `rust_try_msvc_64.ll` which is used to
+// filter the exceptions being caught by that function.
+//
+// In theory local variables can be accessed through the `rbp` parameter of this
+// function, but a comment in an LLVM test case indicates that this is not
+// implemented in LLVM, so this is just an idempotent function which doesn't
+// ferry along any other information.
+//
+// This function just takes a look at the current EXCEPTION_RECORD being thrown
+// to ensure that it's code is RUST_PANIC, which was set by the call to
+// `RaiseException` above in the `panic` function.
 #[no_mangle]
-pub extern fn rust_eh_personality_catch() {}
+pub extern fn __rust_try_filter(eh_ptrs: *mut EXCEPTION_POINTERS,
+                                _rbp: *mut c_void) -> i32 {
+    unsafe {
+        ((*(*eh_ptrs).ExceptionRecord).ExceptionCode == RUST_PANIC) as i32
+    }
+}