std\sys\thread_local\guard/windows.rs
1//! Support for Windows TLS destructors.
2//!
3//! Unfortunately, Windows does not provide a nice API to provide a destructor
4//! for a TLS variable. Thus, the solution here ended up being a little more
5//! obscure, but fear not, the internet has informed me [1][2] that this solution
6//! is not unique (no way I could have thought of it as well!). The key idea is
7//! to insert some hook somewhere to run arbitrary code on thread termination.
8//! With this in place we'll be able to run anything we like, including all
9//! TLS destructors!
10//!
11//! In order to realize this, all TLS destructors are tracked by *us*, not the
12//! Windows runtime. This means that we have a global list of destructors for
13//! each TLS key or variable that we know about.
14//!
15//! # What's up with CRT$XLB?
16//!
17//! For anything about TLS destructors to work on Windows, we have to be able
18//! to run *something* when a thread exits. To do so, we place a very special
19//! static in a very special location. If this is encoded in just the right
20//! way, the kernel's loader is apparently nice enough to run some function
21//! of ours whenever a thread exits! How nice of the kernel!
22//!
23//! Lots of detailed information can be found in source [1] above, but the
24//! gist of it is that this is leveraging a feature of Microsoft's PE format
25//! (executable format) which is not actually used by any compilers today.
26//! This apparently translates to any callbacks in the ".CRT$XLB" section
27//! being run on certain events.
28//!
29//! So after all that, we use the compiler's `#[link_section]` feature to place
30//! a callback pointer into the magic section so it ends up being called.
31//!
32//! # What's up with this callback?
33//!
34//! The callback specified receives a number of parameters from... someone!
35//! (the kernel? the runtime? I'm not quite sure!) There are a few events that
36//! this gets invoked for, but we're currently only interested on when a
37//! thread or a process "detaches" (exits). The process part happens for the
38//! last thread and the thread part happens for any normal thread.
39//!
40//! # The article mentions weird stuff about "/INCLUDE"?
41//!
42//! It sure does! Specifically we're talking about this quote:
43//!
44//! ```quote
45//! The Microsoft run-time library facilitates this process by defining a
46//! memory image of the TLS Directory and giving it the special name
47//! “__tls_used” (Intel x86 platforms) or “_tls_used” (other platforms). The
48//! linker looks for this memory image and uses the data there to create the
49//! TLS Directory. Other compilers that support TLS and work with the
50//! Microsoft linker must use this same technique.
51//! ```
52//!
53//! Basically what this means is that if we want support for our TLS
54//! destructors/our hook being called then we need to make sure the linker does
55//! not omit this symbol. Otherwise it will omit it and our callback won't be
56//! wired up.
57//!
58//! We don't actually use the `/INCLUDE` linker flag here like the article
59//! mentions because the Rust compiler doesn't propagate linker flags, but
60//! instead we use a shim function which performs a volatile 1-byte load from
61//! the address of the _tls_used symbol to ensure it sticks around.
62//!
63//! [1]: https://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
64//! [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base/threading/thread_local_storage_win.cc#L42
65
66use core::ffi::c_void;
67
68use crate::ptr;
69use crate::sys::c;
70
71unsafe extern "C" {
72 #[link_name = "_tls_used"]
73 static TLS_USED: u8;
74}
75pub fn enable() {
76 // When destructors are used, we need to add a reference to the _tls_used
77 // symbol provided by the CRT, otherwise the TLS support code will get
78 // GC'd by the linker and our callback won't be called.
79 unsafe { ptr::from_ref(&TLS_USED).read_volatile() };
80 // We also need to reference CALLBACK to make sure it does not get GC'd
81 // by the compiler/LLVM. The callback will end up inside the TLS
82 // callback array pointed to by _TLS_USED through linker shenanigans,
83 // but as far as the compiler is concerned, it looks like the data is
84 // unused, so we need this hack to prevent it from disappearing.
85 unsafe { ptr::from_ref(&CALLBACK).read_volatile() };
86}
87
88#[unsafe(link_section = ".CRT$XLB")]
89#[cfg_attr(miri, used)] // Miri only considers explicitly `#[used]` statics for `lookup_link_section`
90pub static CALLBACK: unsafe extern "system" fn(*mut c_void, u32, *mut c_void) = tls_callback;
91
92unsafe extern "system" fn tls_callback(_h: *mut c_void, dw_reason: u32, _pv: *mut c_void) {
93 if dw_reason == c::DLL_THREAD_DETACH || dw_reason == c::DLL_PROCESS_DETACH {
94 unsafe {
95 #[cfg(target_thread_local)]
96 super::super::destructors::run();
97 #[cfg(not(target_thread_local))]
98 super::super::key::run_dtors();
99
100 crate::rt::thread_cleanup();
101 }
102 }
103}