Skip to content

Commit 34419bc

Browse files
committed
implement support for multiple TLS destructors on macOS
1 parent 521422a commit 34419bc

File tree

2 files changed

+31
-34
lines changed

2 files changed

+31
-34
lines changed

src/shims/tls.rs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ pub struct TlsData<'tcx> {
3636
/// pthreads-style thread-local storage.
3737
keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
3838

39-
/// A single per thread destructor of the thread local storage (that's how
40-
/// things work on macOS) with a data argument.
41-
macos_thread_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar)>,
39+
/// On macOS, each thread holds a list of destructor functions with their
40+
/// respective data arguments.
41+
macos_thread_dtors: BTreeMap<ThreadId, Vec<(ty::Instance<'tcx>, Scalar)>>,
4242
}
4343

4444
impl<'tcx> Default for TlsData<'tcx> {
@@ -119,26 +119,18 @@ impl<'tcx> TlsData<'tcx> {
119119
}
120120
}
121121

122-
/// Set the thread wide destructor of the thread local storage for the given
123-
/// thread. This function is used to implement `_tlv_atexit` shim on MacOS.
124-
///
125-
/// Thread wide dtors are available only on MacOS. There is one destructor
126-
/// per thread as can be guessed from the following comment in the
127-
/// [`_tlv_atexit`
128-
/// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389):
129-
///
130-
/// NOTE: this does not need locks because it only operates on current thread data
131-
pub fn set_macos_thread_dtor(
122+
/// Add a thread local storage for the given thread. This function is used to
123+
/// implement `_tlv_atexit` shim on MacOS.
124+
/// FIXME: also implement `__cxa_thread_atexit_impl` for Linux using this, see
125+
/// https://sourceware.org/glibc/wiki/Destructor%20support%20for%20thread_local%20variables
126+
pub fn add_macos_thread_dtor(
132127
&mut self,
133128
thread: ThreadId,
134129
dtor: ty::Instance<'tcx>,
135130
data: Scalar,
136131
) -> InterpResult<'tcx> {
137-
if self.macos_thread_dtors.insert(thread, (dtor, data)).is_some() {
138-
throw_unsup_format!(
139-
"setting more than one thread local storage destructor for the same thread is not supported"
140-
);
141-
}
132+
let dtors = self.macos_thread_dtors.entry(thread).or_default();
133+
dtors.push((dtor, data));
142134
Ok(())
143135
}
144136

@@ -212,7 +204,7 @@ impl VisitProvenance for TlsData<'_> {
212204
for scalar in keys.values().flat_map(|v| v.data.values()) {
213205
scalar.visit_provenance(visit);
214206
}
215-
for (_, scalar) in macos_thread_dtors.values() {
207+
for (_, scalar) in macos_thread_dtors.values().flatten() {
216208
scalar.visit_provenance(visit);
217209
}
218210
}
@@ -245,7 +237,7 @@ impl<'tcx> TlsDtorsState<'tcx> {
245237
"macos" => {
246238
// The macOS thread wide destructor runs "before any TLS slots get
247239
// freed", so do that first.
248-
this.schedule_macos_tls_dtor()?;
240+
this.schedule_macos_tls_dtors()?;
249241
// When that destructor is done, go on with the pthread dtors.
250242
break 'new_state PthreadDtors(Default::default());
251243
}
@@ -328,21 +320,26 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
328320
Ok(())
329321
}
330322

331-
/// Schedule the MacOS thread destructor of the thread local storage to be
332-
/// executed.
333-
fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx> {
323+
/// Schedule the macOS thread local storage destructors to be executed.
324+
fn schedule_macos_tls_dtors(&mut self) -> InterpResult<'tcx> {
334325
let this = self.eval_context_mut();
335326
let thread_id = this.active_thread();
336-
if let Some((instance, data)) = this.machine.tls.macos_thread_dtors.remove(&thread_id) {
337-
trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
338-
339-
this.call_function(
340-
instance,
341-
Abi::C { unwind: false },
342-
&[data.into()],
343-
None,
344-
StackPopCleanup::Root { cleanup: true },
345-
)?;
327+
if let Some(dtors) = this.machine.tls.macos_thread_dtors.remove(&thread_id) {
328+
// macOS runs TLS destructors in the reverse order of their registration.
329+
// See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277
330+
// FIXME: Old macOS versions had a use-after-free bug that triggered when
331+
// _tlv_atexit was run inside a destructor. Maybe miri should catch that?
332+
for (instance, data) in dtors.into_iter().rev() {
333+
trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
334+
335+
this.call_function(
336+
instance,
337+
Abi::C { unwind: false },
338+
&[data.into()],
339+
None,
340+
StackPopCleanup::Root { cleanup: true },
341+
)?;
342+
}
346343
}
347344
Ok(())
348345
}

src/shims/unix/macos/foreign_items.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
132132
let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
133133
let data = this.read_scalar(data)?;
134134
let active_thread = this.active_thread();
135-
this.machine.tls.set_macos_thread_dtor(active_thread, dtor, data)?;
135+
this.machine.tls.add_macos_thread_dtor(active_thread, dtor, data)?;
136136
}
137137

138138
// Querying system information

0 commit comments

Comments
 (0)