Skip to content

Commit

Permalink
std: Run TLS destructors in a statically linked binary
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alexcrichton committed Sep 1, 2015
1 parent 8dba06a commit 9a3acec
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 9 deletions.
38 changes: 29 additions & 9 deletions src/libstd/sys/windows/thread_local.rs
Expand Up @@ -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"]
Expand All @@ -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,
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/test/run-pass/down-with-thread-dtors.rs
Expand Up @@ -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;
Expand All @@ -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 });
}
30 changes: 30 additions & 0 deletions 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 });
}

0 comments on commit 9a3acec

Please sign in to comment.