Skip to content

Commit 900dc41

Browse files
committed
std: eagerly run TLS destructors on UNIX to properly handle stack overflows
1 parent b3b8ff3 commit 900dc41

File tree

5 files changed

+46
-112
lines changed

5 files changed

+46
-112
lines changed

library/std/src/sys/unix/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,13 @@ pub(crate) fn unix_sigpipe_attr_specified() -> bool {
217217
// SAFETY: must be called only once during runtime cleanup.
218218
// NOTE: this is not guaranteed to run, for example when the program aborts.
219219
pub unsafe fn cleanup() {
220+
// Eagerly run TLS destructors while the stack overflow handler is still
221+
// active. Since this operation is outside any user code scope, there can
222+
// be no outstanding to the data. The TLS destructors would otherwise be
223+
// run directly after this function returned, so there should be no
224+
// observable differences in behaviour.
225+
#[cfg(target_thread_local)]
226+
thread_local_dtor::run_dtors();
220227
stack_overflow::cleanup();
221228
}
222229

library/std/src/sys/unix/thread.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ impl Thread {
106106
let _handler = stack_overflow::Handler::new();
107107
// Finally, let's run some code.
108108
Box::from_raw(main as *mut Box<dyn FnOnce()>)();
109+
// Eagerly run TLS destructors while the stack overflow
110+
// handler is still active. Since this operation is outside
111+
// any user code scope, there can be no outstanding to the
112+
// data. The TLS destructors would otherwise be run directly
113+
// after this function returned, so there should be no observable
114+
// differences in behaviour.
115+
#[cfg(target_thread_local)]
116+
super::thread_local_dtor::run_dtors();
109117
}
110118
ptr::null_mut()
111119
}
Lines changed: 3 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,6 @@
11
#![cfg(target_thread_local)]
22
#![unstable(feature = "thread_local_internals", issue = "none")]
33

4-
//! Provides thread-local destructors without an associated "key", which
5-
//! can be more efficient.
6-
7-
// Since what appears to be glibc 2.18 this symbol has been shipped which
8-
// GCC and clang both use to invoke destructors in thread_local globals, so
9-
// let's do the same!
10-
//
11-
// Note, however, that we run on lots older linuxes, as well as cross
12-
// compiling from a newer linux to an older linux, so we also have a
13-
// fallback implementation to use as well.
14-
#[cfg(any(target_os = "linux", target_os = "fuchsia", target_os = "redox"))]
15-
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
16-
use crate::mem;
17-
use crate::sys_common::thread_local_dtor::register_dtor_fallback;
18-
19-
extern "C" {
20-
#[linkage = "extern_weak"]
21-
static __dso_handle: *mut u8;
22-
#[linkage = "extern_weak"]
23-
static __cxa_thread_atexit_impl: *const libc::c_void;
24-
}
25-
if !__cxa_thread_atexit_impl.is_null() {
26-
type F = unsafe extern "C" fn(
27-
dtor: unsafe extern "C" fn(*mut u8),
28-
arg: *mut u8,
29-
dso_handle: *mut u8,
30-
) -> libc::c_int;
31-
mem::transmute::<*const libc::c_void, F>(__cxa_thread_atexit_impl)(
32-
dtor,
33-
t,
34-
&__dso_handle as *const _ as *mut _,
35-
);
36-
return;
37-
}
38-
register_dtor_fallback(t, dtor);
39-
}
40-
41-
// This implementation is very similar to register_dtor_fallback in
42-
// sys_common/thread_local.rs. The main difference is that we want to hook into
43-
// macOS's analog of the above linux function, _tlv_atexit. OSX will run the
44-
// registered dtors before any TLS slots get freed, and when the main thread
45-
// exits.
46-
//
47-
// Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The
48-
// workaround below is to register, via _tlv_atexit, a custom DTOR list once per
49-
// thread. thread_local dtors are pushed to the DTOR list without calling
50-
// _tlv_atexit.
51-
#[cfg(target_os = "macos")]
52-
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
53-
use crate::cell::Cell;
54-
use crate::mem;
55-
use crate::ptr;
56-
57-
#[thread_local]
58-
static REGISTERED: Cell<bool> = Cell::new(false);
59-
60-
#[thread_local]
61-
static mut DTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new();
62-
63-
if !REGISTERED.get() {
64-
_tlv_atexit(run_dtors, ptr::null_mut());
65-
REGISTERED.set(true);
66-
}
67-
68-
extern "C" {
69-
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
70-
}
71-
72-
let list = &mut DTORS;
73-
list.push((t, dtor));
74-
75-
unsafe extern "C" fn run_dtors(_: *mut u8) {
76-
let mut list = mem::take(&mut DTORS);
77-
while !list.is_empty() {
78-
for (ptr, dtor) in list {
79-
dtor(ptr);
80-
}
81-
list = mem::take(&mut DTORS);
82-
}
83-
}
84-
}
85-
86-
#[cfg(any(target_os = "vxworks", target_os = "horizon", target_os = "emscripten"))]
87-
#[cfg_attr(target_family = "wasm", allow(unused))] // might remain unused depending on target details (e.g. wasm32-unknown-emscripten)
88-
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
89-
use crate::sys_common::thread_local_dtor::register_dtor_fallback;
90-
register_dtor_fallback(t, dtor);
91-
}
4+
pub use crate::sys_common::thread_local_dtor::{
5+
register_dtor_fallback as register_dtor, run_dtors,
6+
};

library/std/src/sys_common/thread_local_dtor.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,41 @@
1616
use crate::ptr;
1717
use crate::sys_common::thread_local_key::StaticKey;
1818

19-
pub unsafe fn register_dtor_fallback(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
20-
// The fallback implementation uses a vanilla OS-based TLS key to track
21-
// the list of destructors that need to be run for this thread. The key
22-
// then has its own destructor which runs all the other destructors.
23-
//
24-
// The destructor for DTORS is a little special in that it has a `while`
25-
// loop to continuously drain the list of registered destructors. It
26-
// *should* be the case that this loop always terminates because we
27-
// provide the guarantee that a TLS key cannot be set after it is
28-
// flagged for destruction.
19+
type List = Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>;
20+
21+
// The fallback implementation uses a vanilla OS-based TLS key to track
22+
// the list of destructors that need to be run for this thread. The key
23+
// then has its own destructor which runs all the other destructors.
24+
//
25+
// The destructor for DTORS is a little special in that it has a `while`
26+
// loop to continuously drain the list of registered destructors. It
27+
// *should* be the case that this loop always terminates because we
28+
// provide the guarantee that a TLS key cannot be set after it is
29+
// flagged for destruction.
30+
static DTORS: StaticKey = StaticKey::new(Some(run_dtors_internal));
2931

30-
static DTORS: StaticKey = StaticKey::new(Some(run_dtors));
31-
type List = Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>;
32+
pub unsafe fn register_dtor_fallback(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
3233
if DTORS.get().is_null() {
3334
let v: Box<List> = Box::new(Vec::new());
3435
DTORS.set(Box::into_raw(v) as *mut u8);
3536
}
3637
let list: &mut List = &mut *(DTORS.get() as *mut List);
3738
list.push((t, dtor));
39+
}
3840

39-
unsafe extern "C" fn run_dtors(mut ptr: *mut u8) {
40-
while !ptr.is_null() {
41-
let list: Box<List> = Box::from_raw(ptr as *mut List);
42-
for (ptr, dtor) in list.into_iter() {
43-
dtor(ptr);
44-
}
45-
ptr = DTORS.get();
46-
DTORS.set(ptr::null_mut());
41+
unsafe extern "C" fn run_dtors_internal(mut ptr: *mut u8) {
42+
while !ptr.is_null() {
43+
let list: Box<List> = Box::from_raw(ptr as *mut List);
44+
for (ptr, dtor) in list.into_iter() {
45+
dtor(ptr);
4746
}
47+
ptr = DTORS.get();
48+
DTORS.set(ptr::null_mut());
4849
}
4950
}
51+
52+
pub unsafe fn run_dtors() {
53+
let ptr = DTORS.get();
54+
DTORS.set(ptr::null_mut());
55+
run_dtors_internal(ptr);
56+
}

tests/ui/abi/stack-probes.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,7 @@ fn main() {
4444
// details
4545
if cfg!(not(target_os = "linux")) {
4646
assert_overflow(Command::new(&me).arg("main-recurse"));
47-
// FIXME: This does not seem to work on macOS.
48-
if cfg!(not(target_os = "macos")) {
49-
assert_overflow(Command::new(&me).arg("main-tls-recurse"));
50-
}
47+
assert_overflow(Command::new(&me).arg("main-tls-recurse"));
5148
}
5249
assert_overflow(Command::new(&me).arg("child-recurse"));
5350
assert_overflow(Command::new(&me).arg("child-tls-recurse"));

0 commit comments

Comments
 (0)