Rollup merge of #56979 - VardhanThigle:Vardhan/rust-sgx-unwind-support, r=alexcrichton

Adding unwinding support for x86_64_fortanix_unknown_sgx target.

Unwinding support is provided by our port of LLVM's libunwind which is available from https://github.com/fortanix/libunwind/tree/release_50.

libunwind requires support for rwlock and printing to stderr, which is only provided by `std` for this target. This poses two problems: 1) how to expose the `std` functionality to C and 2) dependency inversion.

### Exposing `std`

For exposing the functionality we chose to expose the following symbols:

* __rust_rwlock_rdlock
* __rust_rwlock_wrlock
* __rust_rwlock_unlock
* __rust_print_err
* __rust_abort

Also, the following are needed from `alloc`:

* __rust_alloc
* __rust_dealloc

#### Rust RWLock in C

In `libunwind`, RWLock is initialized as a templated static variable:

```c
pthread_rwlock_t DwarfFDECache<A>::_lock = PTHREAD_RWLOCK_INITIALIZER;
```

I don't know of a good way to use the Rust sys::rwlock::RWLock type and initializer there. We could have a static global variable in Rust, but that doesn't work with the templating. The variable needs to be initialized statically, since this target doesn't support the .init section. Currently, I just used a byte array and standard C array initialization. The mapping between this C type and the Rust type needs to be manually maintained. There is a compile-time check and a unit test to make sure the Rust versions of these C definitions match the actual Rust type. If any reviewer knows of a better solution, please do tell.

### Dependency inversion issue

`std` depends on `panic_unwind` which depends on `libunwind`, and `libunwind` depends on `std`. This is not normally supported by Rust's linking system. Therefore we use raw C exports from `std` *and* `libunwind.a` is linked last in the target `post_link_objects` instead of being built as part of the Rust `libunwind`. Currently, all C exports are defined in `src/libstd/sys/sgx/rwlock.rs` to overcome LTO issues. Only the `__rust_rwlock_*` definitions *need* to live there for privacy reasons. Once again, if any reviewer knows of a better solution, please do tell.

r? @alexcrichton
This commit is contained in:
kennytm 2018-12-23 00:07:47 +08:00
commit 10d949aa3e
No known key found for this signature in database
GPG Key ID: FEF6C8051D0E013C
5 changed files with 168 additions and 30 deletions

View File

@ -62,7 +62,7 @@
if #[cfg(target_os = "emscripten")] {
#[path = "emcc.rs"]
mod imp;
} else if #[cfg(any(target_arch = "wasm32", target_env = "sgx"))] {
} else if #[cfg(target_arch = "wasm32")] {
#[path = "dummy.rs"]
mod imp;
} else if #[cfg(all(target_env = "msvc", target_arch = "aarch64"))] {

View File

@ -10,28 +10,29 @@
use std::iter;
use super::{LinkerFlavor, Target, TargetOptions, PanicStrategy};
use super::{LinkerFlavor, PanicStrategy, Target, TargetOptions};
pub fn target() -> Result<Target, String> {
const PRE_LINK_ARGS: &[&str] = &[
"-Wl,--as-needed",
"-Wl,-z,noexecstack",
"-m64",
"-fuse-ld=gold",
"-nostdlib",
"-shared",
"-Wl,-e,sgx_entry",
"-Wl,-Bstatic",
"-Wl,--gc-sections",
"-Wl,-z,text",
"-Wl,-z,norelro",
"-Wl,--rosegment",
"-Wl,--no-undefined",
"-Wl,--error-unresolved-symbols",
"-Wl,--no-undefined-version",
"-Wl,-Bsymbolic",
"-Wl,--export-dynamic",
"-fuse-ld=gold",
"-nostdlib",
"-shared",
"-Wl,-e,sgx_entry",
"-Wl,-Bstatic",
"-Wl,--gc-sections",
"-Wl,-z,text",
"-Wl,-z,norelro",
"-Wl,--rosegment",
"-Wl,--no-undefined",
"-Wl,--error-unresolved-symbols",
"-Wl,--no-undefined-version",
"-Wl,-Bsymbolic",
"-Wl,--export-dynamic",
];
const EXPORT_SYMBOLS: &[&str] = &[
"sgx_entry",
"HEAP_BASE",
@ -41,19 +42,26 @@ pub fn target() -> Result<Target, String> {
"ENCLAVE_SIZE",
"CFGDATA_BASE",
"DEBUG",
"EH_FRM_HDR_BASE",
"EH_FRM_HDR_SIZE",
"TEXT_BASE",
"TEXT_SIZE",
];
let opts = TargetOptions {
dynamic_linking: false,
executables: true,
linker_is_gnu: true,
max_atomic_width: Some(64),
panic_strategy: PanicStrategy::Abort,
panic_strategy: PanicStrategy::Unwind,
cpu: "x86-64".into(),
features: "+rdrnd,+rdseed".into(),
position_independent_executables: true,
pre_link_args: iter::once(
(LinkerFlavor::Gcc, PRE_LINK_ARGS.iter().cloned().map(String::from).collect())
).collect(),
pre_link_args: iter::once((
LinkerFlavor::Gcc,
PRE_LINK_ARGS.iter().cloned().map(String::from).collect(),
))
.collect(),
post_link_objects: vec!["libunwind.a".into()],
override_export_symbols: Some(EXPORT_SYMBOLS.iter().cloned().map(String::from).collect()),
..Default::default()
};

View File

@ -56,6 +56,14 @@ IMAGE_BASE:
globvar CFGDATA_BASE 8
/* Non-zero if debugging is enabled, zero otherwise */
globvar DEBUG 1
/* The base address (relative to enclave start) of the enclave text section */
globvar TEXT_BASE 8
/* The size in bytes of enclacve text section */
globvar TEXT_SIZE 8
/* The base address (relative to enclave start) of the enclave EH_FRM_HDR section */
globvar EH_FRM_HDR_BASE 8
/* The size in bytes of enclacve EH_FRM_HDR section */
globvar EH_FRM_HDR_SIZE 8
.Lreentry_panic_msg:
.asciz "Re-entered panicked enclave!"

View File

@ -9,14 +9,25 @@
// except according to those terms.
use num::NonZeroUsize;
use slice;
use str;
use super::waitqueue::{WaitVariable, WaitQueue, SpinMutex, NotifiedTcs, try_lock_or_false};
use super::waitqueue::{
try_lock_or_false, NotifiedTcs, SpinMutex, SpinMutexGuard, WaitQueue, WaitVariable,
};
use mem;
pub struct RWLock {
readers: SpinMutex<WaitVariable<Option<NonZeroUsize>>>,
writer: SpinMutex<WaitVariable<bool>>,
}
// Below is to check at compile time, that RWLock has size of 128 bytes.
#[allow(dead_code)]
unsafe fn rw_lock_size_assert(r: RWLock) {
mem::transmute::<RWLock, [u8; 128]>(r);
}
//unsafe impl Send for RWLock {}
//unsafe impl Sync for RWLock {} // FIXME
@ -24,7 +35,7 @@ impl RWLock {
pub const fn new() -> RWLock {
RWLock {
readers: SpinMutex::new(WaitVariable::new(None)),
writer: SpinMutex::new(WaitVariable::new(false))
writer: SpinMutex::new(WaitVariable::new(false)),
}
}
@ -89,9 +100,11 @@ pub unsafe fn try_write(&self) -> bool {
}
#[inline]
pub unsafe fn read_unlock(&self) {
let mut rguard = self.readers.lock();
let wguard = self.writer.lock();
unsafe fn __read_unlock(
&self,
mut rguard: SpinMutexGuard<WaitVariable<Option<NonZeroUsize>>>,
wguard: SpinMutexGuard<WaitVariable<bool>>,
) {
*rguard.lock_var_mut() = NonZeroUsize::new(rguard.lock_var().unwrap().get() - 1);
if rguard.lock_var().is_some() {
// There are other active readers
@ -107,9 +120,18 @@ pub unsafe fn read_unlock(&self) {
}
#[inline]
pub unsafe fn write_unlock(&self) {
pub unsafe fn read_unlock(&self) {
let rguard = self.readers.lock();
let wguard = self.writer.lock();
self.__read_unlock(rguard, wguard);
}
#[inline]
unsafe fn __write_unlock(
&self,
rguard: SpinMutexGuard<WaitVariable<Option<NonZeroUsize>>>,
wguard: SpinMutexGuard<WaitVariable<bool>>,
) {
if let Err(mut wguard) = WaitQueue::notify_one(wguard) {
// No writers waiting, release the write lock
*wguard.lock_var_mut() = false;
@ -128,6 +150,109 @@ pub unsafe fn write_unlock(&self) {
}
}
#[inline]
pub unsafe fn write_unlock(&self) {
let rguard = self.readers.lock();
let wguard = self.writer.lock();
self.__write_unlock(rguard, wguard);
}
#[inline]
unsafe fn unlock(&self) {
let rguard = self.readers.lock();
let wguard = self.writer.lock();
if *wguard.lock_var() == true {
self.__write_unlock(rguard, wguard);
} else {
self.__read_unlock(rguard, wguard);
}
}
#[inline]
pub unsafe fn destroy(&self) {}
}
const EINVAL: i32 = 22;
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_rdlock(p: *mut RWLock) -> i32 {
if p.is_null() {
return EINVAL;
}
(*p).read();
return 0;
}
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_wrlock(p: *mut RWLock) -> i32 {
if p.is_null() {
return EINVAL;
}
(*p).write();
return 0;
}
#[no_mangle]
pub unsafe extern "C" fn __rust_rwlock_unlock(p: *mut RWLock) -> i32 {
if p.is_null() {
return EINVAL;
}
(*p).unlock();
return 0;
}
#[no_mangle]
pub unsafe extern "C" fn __rust_print_err(m: *mut u8, s: i32) {
if s < 0 {
return;
}
let buf = slice::from_raw_parts(m as *const u8, s as _);
if let Ok(s) = str::from_utf8(&buf[..buf.iter().position(|&b| b == 0).unwrap_or(buf.len())]) {
eprint!("{}", s);
}
}
#[no_mangle]
pub unsafe extern "C" fn __rust_abort() {
::sys::abort_internal();
}
#[cfg(test)]
mod tests {
use super::*;
use core::array::FixedSizeArray;
use mem::MaybeUninit;
use {mem, ptr};
// The below test verifies that the bytes of initialized RWLock are the ones
// we use in libunwind.
// If they change we need to update src/UnwindRustSgx.h in libunwind.
#[test]
fn test_c_rwlock_initializer() {
const RWLOCK_INIT: &[u8] = &[
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let mut init = MaybeUninit::<RWLock>::zeroed();
init.set(RWLock::new());
assert_eq!(
mem::transmute::<_, [u8; 128]>(init.into_inner()).as_slice(),
RWLOCK_INIT
);
}
}

View File

@ -26,10 +26,7 @@
cfg_if! {
if #[cfg(target_env = "msvc")] {
// no extra unwinder support needed
} else if #[cfg(any(
all(target_arch = "wasm32", not(target_os = "emscripten")),
target_env = "sgx"
))] {
} else if #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] {
// no unwinder on the system!
} else {
extern crate libc;