Skip to content

Commit 9a3acec

Browse files
committed
std: Run TLS destructors in a statically linked binary
Running TLS destructors for a MSVC Windows binary requires the linker doesn't elide the `_tls_used` or `__tls_used` symbols (depending on the architecture). This is currently achieved via a `#[link_args]` hack but this only works for dynamically linked binaries because the link arguments aren't propagated to statically linked binaries. This commit alters the strategy to instead emit a volatile load from those symbols so LLVM can't elide it, forcing the reference to the symbol to stay alive as long as the callback function stays alive (which we've made sure of with the `#[linkage]` attribute). Closes #28111
1 parent 8dba06a commit 9a3acec

File tree

3 files changed

+68
-9
lines changed

3 files changed

+68
-9
lines changed

src/libstd/sys/windows/thread_local.rs

+29-9
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,24 @@ unsafe fn unregister_dtor(key: Key) -> bool {
221221
//
222222
// # The article mentions crazy stuff about "/INCLUDE"?
223223
//
224-
// It sure does! We include it below for MSVC targets, but it look like for GNU
225-
// targets we don't require it.
224+
// It sure does! Specifically we're talking about this quote:
225+
//
226+
// The Microsoft run-time library facilitates this process by defining a
227+
// memory image of the TLS Directory and giving it the special name
228+
// “__tls_used” (Intel x86 platforms) or “_tls_used” (other platforms). The
229+
// linker looks for this memory image and uses the data there to create the
230+
// TLS Directory. Other compilers that support TLS and work with the
231+
// Microsoft linker must use this same technique.
232+
//
233+
// Basically what this means is that if we want support for our TLS
234+
// destructors/our hook being called then we need to make sure the linker does
235+
// not omit this symbol. Otherwise it will omit it and our callback won't be
236+
// wired up.
237+
//
238+
// We don't actually use the `/INCLUDE` linker flag here like the article
239+
// mentions because the Rust compiler doesn't propagate linker flags, but
240+
// instead we use a shim function which performs a volatile 1-byte load from
241+
// the address of the symbol to ensure it sticks around.
226242

227243
#[link_section = ".CRT$XLB"]
228244
#[linkage = "external"]
@@ -231,13 +247,6 @@ pub static p_thread_callback: unsafe extern "system" fn(LPVOID, DWORD,
231247
LPVOID) =
232248
on_tls_callback;
233249

234-
#[cfg(all(target_env = "msvc", target_pointer_width = "64"))]
235-
#[link_args = "/INCLUDE:_tls_used"]
236-
extern {}
237-
#[cfg(all(target_env = "msvc", target_pointer_width = "32"))]
238-
#[link_args = "/INCLUDE:__tls_used"]
239-
extern {}
240-
241250
#[allow(warnings)]
242251
unsafe extern "system" fn on_tls_callback(h: LPVOID,
243252
dwReason: DWORD,
@@ -247,6 +256,17 @@ unsafe extern "system" fn on_tls_callback(h: LPVOID,
247256
if dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH {
248257
run_dtors();
249258
}
259+
260+
// See comments above for what this is doing. Note that we don't need this
261+
// trickery on GNU windows, just on MSVC.
262+
reference_tls_used();
263+
#[cfg(target_env = "msvc")]
264+
unsafe fn reference_tls_used() {
265+
extern { static _tls_used: u8; }
266+
::intrinsics::volatile_load(&_tls_used);
267+
}
268+
#[cfg(not(target_env = "msvc"))]
269+
unsafe fn reference_tls_used() {}
250270
}
251271

252272
#[allow(dead_code)] // actually called above

src/test/run-pass/down-with-thread-dtors.rs

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ thread_local!(static FOO: Foo = Foo);
1212
thread_local!(static BAR: Bar = Bar(1));
1313
thread_local!(static BAZ: Baz = Baz);
1414

15+
static mut HIT: bool = false;
16+
1517
struct Foo;
1618
struct Bar(i32);
1719
struct Baz;
@@ -31,8 +33,15 @@ impl Drop for Bar {
3133
}
3234
}
3335

36+
impl Drop for Baz {
37+
fn drop(&mut self) {
38+
unsafe { HIT = true; }
39+
}
40+
}
41+
3442
fn main() {
3543
std::thread::spawn(|| {
3644
FOO.with(|_| {});
3745
}).join().unwrap();
46+
assert!(unsafe { HIT });
3847
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// no-prefer-dynamic
12+
13+
static mut HIT: bool = false;
14+
15+
struct Foo;
16+
17+
impl Drop for Foo {
18+
fn drop(&mut self) {
19+
unsafe { HIT = true; }
20+
}
21+
}
22+
23+
thread_local!(static FOO: Foo = Foo);
24+
25+
fn main() {
26+
std::thread::spawn(|| {
27+
FOO.with(|_| {});
28+
}).join().unwrap();
29+
assert!(unsafe { HIT });
30+
}

0 commit comments

Comments
 (0)