From 9a3acece30414a2d306102ec4e6faac0b6f3c482 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 31 Aug 2015 10:28:07 -0700 Subject: [PATCH] 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 --- src/libstd/sys/windows/thread_local.rs | 38 ++++++++++++++----- src/test/run-pass/down-with-thread-dtors.rs | 9 +++++ .../tls-dtors-are-run-in-a-static-binary.rs | 30 +++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 src/test/run-pass/tls-dtors-are-run-in-a-static-binary.rs diff --git a/src/libstd/sys/windows/thread_local.rs b/src/libstd/sys/windows/thread_local.rs index 7550b7ce6c352..d9b7a59712b0c 100644 --- a/src/libstd/sys/windows/thread_local.rs +++ b/src/libstd/sys/windows/thread_local.rs @@ -221,8 +221,24 @@ unsafe fn unregister_dtor(key: Key) -> bool { // // # The article mentions crazy stuff about "/INCLUDE"? // -// It sure does! We include it below for MSVC targets, but it look like for GNU -// targets we don't require it. +// It sure does! Specifically we're talking about this quote: +// +// The Microsoft run-time library facilitates this process by defining a +// memory image of the TLS Directory and giving it the special name +// “__tls_used” (Intel x86 platforms) or “_tls_used” (other platforms). The +// linker looks for this memory image and uses the data there to create the +// TLS Directory. Other compilers that support TLS and work with the +// Microsoft linker must use this same technique. +// +// Basically what this means is that if we want support for our TLS +// destructors/our hook being called then we need to make sure the linker does +// not omit this symbol. Otherwise it will omit it and our callback won't be +// wired up. +// +// We don't actually use the `/INCLUDE` linker flag here like the article +// mentions because the Rust compiler doesn't propagate linker flags, but +// instead we use a shim function which performs a volatile 1-byte load from +// the address of the symbol to ensure it sticks around. #[link_section = ".CRT$XLB"] #[linkage = "external"] @@ -231,13 +247,6 @@ pub static p_thread_callback: unsafe extern "system" fn(LPVOID, DWORD, LPVOID) = on_tls_callback; -#[cfg(all(target_env = "msvc", target_pointer_width = "64"))] -#[link_args = "/INCLUDE:_tls_used"] -extern {} -#[cfg(all(target_env = "msvc", target_pointer_width = "32"))] -#[link_args = "/INCLUDE:__tls_used"] -extern {} - #[allow(warnings)] unsafe extern "system" fn on_tls_callback(h: LPVOID, dwReason: DWORD, @@ -247,6 +256,17 @@ unsafe extern "system" fn on_tls_callback(h: LPVOID, if dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH { run_dtors(); } + + // See comments above for what this is doing. Note that we don't need this + // trickery on GNU windows, just on MSVC. + reference_tls_used(); + #[cfg(target_env = "msvc")] + unsafe fn reference_tls_used() { + extern { static _tls_used: u8; } + ::intrinsics::volatile_load(&_tls_used); + } + #[cfg(not(target_env = "msvc"))] + unsafe fn reference_tls_used() {} } #[allow(dead_code)] // actually called above diff --git a/src/test/run-pass/down-with-thread-dtors.rs b/src/test/run-pass/down-with-thread-dtors.rs index ee835785cbe5d..5c449d511d53b 100644 --- a/src/test/run-pass/down-with-thread-dtors.rs +++ b/src/test/run-pass/down-with-thread-dtors.rs @@ -12,6 +12,8 @@ thread_local!(static FOO: Foo = Foo); thread_local!(static BAR: Bar = Bar(1)); thread_local!(static BAZ: Baz = Baz); +static mut HIT: bool = false; + struct Foo; struct Bar(i32); struct Baz; @@ -31,8 +33,15 @@ impl Drop for Bar { } } +impl Drop for Baz { + fn drop(&mut self) { + unsafe { HIT = true; } + } +} + fn main() { std::thread::spawn(|| { FOO.with(|_| {}); }).join().unwrap(); + assert!(unsafe { HIT }); } diff --git a/src/test/run-pass/tls-dtors-are-run-in-a-static-binary.rs b/src/test/run-pass/tls-dtors-are-run-in-a-static-binary.rs new file mode 100644 index 0000000000000..da30100f67ff9 --- /dev/null +++ b/src/test/run-pass/tls-dtors-are-run-in-a-static-binary.rs @@ -0,0 +1,30 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// no-prefer-dynamic + +static mut HIT: bool = false; + +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + unsafe { HIT = true; } + } +} + +thread_local!(static FOO: Foo = Foo); + +fn main() { + std::thread::spawn(|| { + FOO.with(|_| {}); + }).join().unwrap(); + assert!(unsafe { HIT }); +}