Skip to content

Commit 698d4a8

Browse files
Rewrite Windows compat_fn macro
This allows using most delay loaded functions before the init code initializes them. It also only preloads a select few functions, rather than all functions. Co-Authored-By: Mark Rousskov <[email protected]>
1 parent 29c5a02 commit 698d4a8

File tree

2 files changed

+205
-64
lines changed

2 files changed

+205
-64
lines changed

library/std/src/sys/windows/c.rs

+10-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![cfg_attr(test, allow(dead_code))]
55
#![unstable(issue = "none", feature = "windows_c")]
66

7+
use crate::ffi::CStr;
78
use crate::mem;
89
use crate::os::raw::{c_char, c_int, c_long, c_longlong, c_uint, c_ulong, c_ushort};
910
use crate::os::windows::io::{BorrowedHandle, HandleOrInvalid, HandleOrNull};
@@ -1219,8 +1220,8 @@ extern "system" {
12191220

12201221
// Functions that aren't available on every version of Windows that we support,
12211222
// but we still use them and just provide some form of a fallback implementation.
1222-
compat_fn! {
1223-
"kernel32":
1223+
compat_fn_with_fallback! {
1224+
pub static KERNEL32: &CStr = ansi_str!("kernel32");
12241225

12251226
// >= Win10 1607
12261227
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription
@@ -1243,8 +1244,8 @@ compat_fn! {
12431244
}
12441245
}
12451246

1246-
compat_fn! {
1247-
"api-ms-win-core-synch-l1-2-0":
1247+
compat_fn_optional! {
1248+
pub static SYNCH_API: &CStr = ansi_str!("api-ms-win-core-synch-l1-2-0");
12481249

12491250
// >= Windows 8 / Server 2012
12501251
// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitonaddress
@@ -1253,17 +1254,13 @@ compat_fn! {
12531254
CompareAddress: LPVOID,
12541255
AddressSize: SIZE_T,
12551256
dwMilliseconds: DWORD
1256-
) -> BOOL {
1257-
panic!("WaitOnAddress not available")
1258-
}
1259-
pub fn WakeByAddressSingle(Address: LPVOID) -> () {
1260-
// If this api is unavailable, there cannot be anything waiting, because
1261-
// WaitOnAddress would've panicked. So it's fine to do nothing here.
1262-
}
1257+
) -> BOOL;
1258+
pub fn WakeByAddressSingle(Address: LPVOID) -> ();
12631259
}
12641260

1265-
compat_fn! {
1266-
"ntdll":
1261+
compat_fn_with_fallback! {
1262+
pub static NTDLL: &CStr = ansi_str!("ntdll");
1263+
12671264
pub fn NtCreateFile(
12681265
FileHandle: *mut HANDLE,
12691266
DesiredAccess: ACCESS_MASK,

library/std/src/sys/windows/compat.rs

+195-51
Original file line numberDiff line numberDiff line change
@@ -49,81 +49,225 @@
4949
//! * call any Rust function or CRT function that touches any static
5050
//! (global) state.
5151
52-
macro_rules! compat_fn {
53-
($module:literal: $(
52+
use crate::ffi::{c_void, CStr};
53+
use crate::ptr::NonNull;
54+
use crate::sys::c;
55+
56+
/// Helper macro for creating CStrs from literals and symbol names.
57+
macro_rules! ansi_str {
58+
(sym $ident:ident) => {{
59+
#[allow(unused_unsafe)]
60+
crate::sys::compat::const_cstr_from_bytes(concat!(stringify!($ident), "\0").as_bytes())
61+
}};
62+
($lit:literal) => {{ crate::sys::compat::const_cstr_from_bytes(concat!($lit, "\0").as_bytes()) }};
63+
}
64+
65+
/// Creates a C string wrapper from a byte slice, in a constant context.
66+
///
67+
/// This is a utility function used by the [`ansi_str`] macro.
68+
///
69+
/// # Panics
70+
///
71+
/// Panics if the slice is not null terminated or contains nulls, except as the last item
72+
pub(crate) const fn const_cstr_from_bytes(bytes: &'static [u8]) -> &'static CStr {
73+
if !matches!(bytes.last(), Some(&0)) {
74+
panic!("A CStr must be null terminated");
75+
}
76+
let mut i = 0;
77+
// At this point `len()` is at least 1.
78+
while i < bytes.len() - 1 {
79+
if bytes[i] == 0 {
80+
panic!("A CStr must not have interior nulls")
81+
}
82+
i += 1;
83+
}
84+
// SAFETY: The safety is ensured by the above checks.
85+
unsafe { crate::ffi::CStr::from_bytes_with_nul_unchecked(bytes) }
86+
}
87+
88+
#[used]
89+
#[link_section = ".CRT$XCU"]
90+
static INIT_TABLE_ENTRY: unsafe extern "C" fn() = init;
91+
92+
/// This is where the magic preloading of symbols happens.
93+
///
94+
/// Note that any functions included here will be unconditionally included in
95+
/// the final binary, regardless of whether or not they're actually used.
96+
///
97+
/// Therefore, this is limited to `compat_fn_optional` functions which must be
98+
/// preloaded and any functions which may be more time sensitive, even for the first call.
99+
unsafe extern "C" fn init() {
100+
// There is no locking here. This code is executed before main() is entered, and
101+
// is guaranteed to be single-threaded.
102+
//
103+
// DO NOT do anything interesting or complicated in this function! DO NOT call
104+
// any Rust functions or CRT functions if those functions touch any global state,
105+
// because this function runs during global initialization. For example, DO NOT
106+
// do any dynamic allocation, don't call LoadLibrary, etc.
107+
108+
if let Some(synch) = Module::new(c::SYNCH_API) {
109+
// These are optional and so we must manually attempt to load them
110+
// before they can be used.
111+
c::WaitOnAddress::preload(synch);
112+
c::WakeByAddressSingle::preload(synch);
113+
}
114+
115+
if let Some(kernel32) = Module::new(c::KERNEL32) {
116+
// Preloading this means getting a precise time will be as fast as possible.
117+
c::GetSystemTimePreciseAsFileTime::preload(kernel32);
118+
}
119+
}
120+
121+
/// Represents a loaded module.
122+
///
123+
/// Note that the modules std depends on must not be unloaded.
124+
/// Therefore a `Module` is always valid for the lifetime of std.
125+
#[derive(Copy, Clone)]
126+
pub(in crate::sys) struct Module(NonNull<c_void>);
127+
impl Module {
128+
/// Try to get a handle to a loaded module.
129+
///
130+
/// # SAFETY
131+
///
132+
/// This should only be use for modules that exist for the lifetime of std
133+
/// (e.g. kernel32 and ntdll).
134+
pub unsafe fn new(name: &CStr) -> Option<Self> {
135+
// SAFETY: A CStr is always null terminated.
136+
let module = c::GetModuleHandleA(name.as_ptr());
137+
NonNull::new(module).map(Self)
138+
}
139+
140+
// Try to get the address of a function.
141+
pub fn proc_address(self, name: &CStr) -> Option<NonNull<c_void>> {
142+
// SAFETY:
143+
// `self.0` will always be a valid module.
144+
// A CStr is always null terminated.
145+
let proc = unsafe { c::GetProcAddress(self.0.as_ptr(), name.as_ptr()) };
146+
NonNull::new(proc)
147+
}
148+
}
149+
150+
/// Load a function or use a fallback implementation if that fails.
151+
macro_rules! compat_fn_with_fallback {
152+
(pub static $module:ident: &CStr = $name:expr; $(
54153
$(#[$meta:meta])*
55154
pub fn $symbol:ident($($argname:ident: $argtype:ty),*) -> $rettype:ty $fallback_body:block
56-
)*) => ($(
155+
)*) => (
156+
pub static $module: &CStr = $name;
157+
$(
57158
$(#[$meta])*
58159
pub mod $symbol {
59160
#[allow(unused_imports)]
60161
use super::*;
61162
use crate::mem;
163+
use crate::ffi::CStr;
164+
use crate::sync::atomic::{AtomicPtr, Ordering};
165+
use crate::sys::compat::Module;
62166

63167
type F = unsafe extern "system" fn($($argtype),*) -> $rettype;
64168

65-
/// Points to the DLL import, or the fallback function.
66-
///
67-
/// This static can be an ordinary, unsynchronized, mutable static because
68-
/// we guarantee that all of the writes finish during CRT initialization,
69-
/// and all of the reads occur after CRT initialization.
70-
static mut PTR: Option<F> = None;
71-
72-
/// This symbol is what allows the CRT to find the `init` function and call it.
73-
/// It is marked `#[used]` because otherwise Rust would assume that it was not
74-
/// used, and would remove it.
75-
#[used]
76-
#[link_section = ".CRT$XCU"]
77-
static INIT_TABLE_ENTRY: unsafe extern "C" fn() = init;
78-
79-
unsafe extern "C" fn init() {
80-
PTR = get_f();
169+
/// `PTR` contains a function pointer to one of three functions.
170+
/// It starts with the `load` function.
171+
/// When that is called it attempts to load the requested symbol.
172+
/// If it succeeds, `PTR` is set to the address of that symbol.
173+
/// If it fails, then `PTR` is set to `fallback`.
174+
static PTR: AtomicPtr<c_void> = AtomicPtr::new(load as *mut _);
175+
176+
unsafe extern "system" fn load($($argname: $argtype),*) -> $rettype {
177+
let func = load_from_module(Module::new($module));
178+
func($($argname),*)
81179
}
82180

83-
unsafe extern "C" fn get_f() -> Option<F> {
84-
// There is no locking here. This code is executed before main() is entered, and
85-
// is guaranteed to be single-threaded.
86-
//
87-
// DO NOT do anything interesting or complicated in this function! DO NOT call
88-
// any Rust functions or CRT functions, if those functions touch any global state,
89-
// because this function runs during global initialization. For example, DO NOT
90-
// do any dynamic allocation, don't call LoadLibrary, etc.
91-
let module_name: *const u8 = concat!($module, "\0").as_ptr();
92-
let symbol_name: *const u8 = concat!(stringify!($symbol), "\0").as_ptr();
93-
let module_handle = $crate::sys::c::GetModuleHandleA(module_name as *const i8);
94-
if !module_handle.is_null() {
95-
let ptr = $crate::sys::c::GetProcAddress(module_handle, symbol_name as *const i8);
96-
if !ptr.is_null() {
97-
// Transmute to the right function pointer type.
98-
return Some(mem::transmute(ptr));
181+
fn load_from_module(module: Option<Module>) -> F {
182+
unsafe {
183+
static symbol_name: &CStr = ansi_str!(sym $symbol);
184+
if let Some(f) = module.and_then(|m| m.proc_address(symbol_name)) {
185+
PTR.store(f.as_ptr(), Ordering::Relaxed);
186+
mem::transmute(f)
187+
} else {
188+
PTR.store(fallback as *mut _, Ordering::Relaxed);
189+
fallback
99190
}
100191
}
101-
return None;
102192
}
103193

104-
#[allow(dead_code)]
194+
#[allow(unused_variables)]
195+
unsafe extern "system" fn fallback($($argname: $argtype),*) -> $rettype {
196+
$fallback_body
197+
}
198+
199+
#[allow(unused)]
200+
pub(in crate::sys) fn preload(module: Module) {
201+
load_from_module(Some(module));
202+
}
203+
204+
#[inline(always)]
205+
pub unsafe fn call($($argname: $argtype),*) -> $rettype {
206+
let func: F = mem::transmute(PTR.load(Ordering::Relaxed));
207+
func($($argname),*)
208+
}
209+
}
210+
$(#[$meta])*
211+
pub use $symbol::call as $symbol;
212+
)*)
213+
}
214+
215+
/// A function that either exists or doesn't.
216+
///
217+
/// NOTE: Optional functions must be preloaded in the `init` function above, or they will always be None.
218+
macro_rules! compat_fn_optional {
219+
(pub static $module:ident: &CStr = $name:expr; $(
220+
$(#[$meta:meta])*
221+
pub fn $symbol:ident($($argname:ident: $argtype:ty),*) -> $rettype:ty;
222+
)*) => (
223+
pub static $module: &CStr = $name;
224+
$(
225+
$(#[$meta])*
226+
pub mod $symbol {
227+
#[allow(unused_imports)]
228+
use super::*;
229+
use crate::mem;
230+
use crate::sync::atomic::{AtomicPtr, Ordering};
231+
use crate::sys::compat::Module;
232+
use crate::ptr::{self, NonNull};
233+
234+
type F = unsafe extern "system" fn($($argtype),*) -> $rettype;
235+
236+
/// `PTR` will either be `null()` or set to the loaded function.
237+
static PTR: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
238+
239+
/// Only allow access to the function if it has loaded successfully.
105240
#[inline(always)]
241+
#[cfg(not(miri))]
106242
pub fn option() -> Option<F> {
107243
unsafe {
108-
if cfg!(miri) {
109-
// Miri does not run `init`, so we just call `get_f` each time.
110-
get_f()
111-
} else {
112-
PTR
113-
}
244+
NonNull::new(PTR.load(Ordering::Relaxed)).map(|f| mem::transmute(f))
114245
}
115246
}
116247

117-
#[allow(dead_code)]
118-
pub unsafe fn call($($argname: $argtype),*) -> $rettype {
119-
if let Some(ptr) = option() {
120-
return ptr($($argname),*);
248+
// Miri does not understand the way we do preloading
249+
// therefore load the function here instead.
250+
#[cfg(miri)]
251+
pub fn option() -> Option<F> {
252+
let mut func = NonNull::new(PTR.load(Ordering::Relaxed));
253+
if func.is_none() {
254+
Module::new($module).map(preload);
255+
func = NonNull::new(PTR.load(Ordering::Relaxed));
256+
}
257+
unsafe {
258+
func.map(|f| mem::transmute(f))
121259
}
122-
$fallback_body
123260
}
124-
}
125261

126-
$(#[$meta])*
127-
pub use $symbol::call as $symbol;
262+
#[allow(unused)]
263+
pub(in crate::sys) fn preload(module: Module) {
264+
unsafe {
265+
let symbol_name = ansi_str!(sym $symbol);
266+
if let Some(f) = module.proc_address(symbol_name) {
267+
PTR.store(f.as_ptr(), Ordering::Relaxed);
268+
}
269+
}
270+
}
271+
}
128272
)*)
129273
}

0 commit comments

Comments
 (0)