summary refs log tree commit diff
path: root/src/libstd/sys/common/unwind/seh.rs
blob: a89e8b499acceae3d84c1fa2c8853621d26d775c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// 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 libc::{c_ulong, DWORD, c_void};
use ptr;
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" {
    #[unwind]
    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],
}

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, ptr::null());
    rtabort!("could not unwind stack");
}

pub unsafe fn cleanup(ptr: *mut u8) -> 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(ptr::null_mut());
    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"]
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.
#[lang = "msvc_try_filter"]
#[linkage = "external"]
#[allow(private_no_mangle_fns)]
extern fn __rust_try_filter(eh_ptrs: *mut EXCEPTION_POINTERS,
                            _rbp: *mut u8) -> i32 {
    unsafe {
        ((*(*eh_ptrs).ExceptionRecord).ExceptionCode == RUST_PANIC) as i32
    }
}