miri/shims/windows/
foreign_items.rs

1use std::ffi::OsStr;
2use std::path::{self, Path, PathBuf};
3use std::{io, iter, str};
4
5use rustc_abi::{Align, Size};
6use rustc_middle::ty::Ty;
7use rustc_span::Symbol;
8use rustc_target::callconv::{Conv, FnAbi};
9
10use self::shims::windows::handle::{Handle, PseudoHandle};
11use crate::shims::os_str::bytes_to_os_str;
12use crate::shims::windows::*;
13use crate::*;
14
15pub fn is_dyn_sym(name: &str) -> bool {
16    // std does dynamic detection for these symbols
17    matches!(
18        name,
19        "SetThreadDescription" | "GetThreadDescription" | "WaitOnAddress" | "WakeByAddressSingle"
20    )
21}
22
23#[cfg(windows)]
24fn win_get_full_path_name<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
25    // We are on Windows so we can simply let the host do this.
26    interp_ok(path::absolute(path))
27}
28
29#[cfg(unix)]
30#[expect(clippy::get_first, clippy::arithmetic_side_effects)]
31fn win_get_full_path_name<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
32    use std::sync::LazyLock;
33
34    use rustc_data_structures::fx::FxHashSet;
35
36    // We are on Unix, so we need to implement parts of the logic ourselves. `path` will use `/`
37    // separators, and the result should also use `/`.
38    // See <https://chrisdenton.github.io/omnipath/Overview.html#absolute-win32-paths> for more
39    // information about Windows paths.
40    // This does not handle all corner cases correctly, see
41    // <https://github.com/rust-lang/miri/pull/4262#issuecomment-2792168853> for more cursed
42    // examples.
43    let bytes = path.as_os_str().as_encoded_bytes();
44    // If it starts with `//./` or `//?/` then this is a magic special path, we just leave it
45    // unchanged.
46    if bytes.get(0).copied() == Some(b'/')
47        && bytes.get(1).copied() == Some(b'/')
48        && matches!(bytes.get(2), Some(b'.' | b'?'))
49        && bytes.get(3).copied() == Some(b'/')
50    {
51        return interp_ok(Ok(path.into()));
52    };
53    let is_unc = bytes.starts_with(b"//");
54    // Special treatment for Windows' magic filenames: they are treated as being relative to `//./`.
55    static MAGIC_FILENAMES: LazyLock<FxHashSet<&'static str>> = LazyLock::new(|| {
56        FxHashSet::from_iter([
57            "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",
58            "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
59        ])
60    });
61    if str::from_utf8(bytes).is_ok_and(|s| MAGIC_FILENAMES.contains(&*s.to_ascii_uppercase())) {
62        let mut result: Vec<u8> = b"//./".into();
63        result.extend(bytes);
64        return interp_ok(Ok(bytes_to_os_str(&result)?.into()));
65    }
66    // Otherwise we try to do something kind of close to what Windows does, but this is probably not
67    // right in all cases.
68    let mut result: Vec<&[u8]> = vec![]; // will be a vector of components, joined by `/`.
69    let mut bytes = bytes; // the remaining bytes to process
70    let mut stop = false;
71    while !stop {
72        // Find next component, and advance `bytes`.
73        let mut component = match bytes.iter().position(|&b| b == b'/') {
74            Some(pos) => {
75                let (component, tail) = bytes.split_at(pos);
76                bytes = &tail[1..]; // remove the `/`.
77                component
78            }
79            None => {
80                // There's no more `/`.
81                stop = true;
82                let component = bytes;
83                bytes = &[];
84                component
85            }
86        };
87        // `NUL` and only `NUL` also gets changed to be relative to `//./` later in the path.
88        // (This changed with Windows 11; previously, all magic filenames behaved like this.)
89        // Also, this does not apply to UNC paths.
90        if !is_unc && component.eq_ignore_ascii_case(b"NUL") {
91            let mut result: Vec<u8> = b"//./".into();
92            result.extend(component);
93            return interp_ok(Ok(bytes_to_os_str(&result)?.into()));
94        }
95        // Deal with `..` -- Windows handles this entirely syntactically.
96        if component == b".." {
97            // Remove previous component, unless we are at the "root" already, then just ignore the `..`.
98            let is_root = {
99                // Paths like `/C:`.
100                result.len() == 2 && matches!(result[0], []) && matches!(result[1], [_, b':'])
101            } || {
102                // Paths like `//server/share`
103                result.len() == 4 && matches!(result[0], []) && matches!(result[1], [])
104            };
105            if !is_root {
106                result.pop();
107            }
108            continue;
109        }
110        // Preserve this component.
111        // Strip trailing `.`, but preserve trailing `..`. But not for UNC paths!
112        let len = component.len();
113        if !is_unc && len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' {
114            component = &component[..len - 1];
115        }
116        // Add this component to output.
117        result.push(component);
118    }
119    // Drive letters must be followed by a `/`.
120    if result.len() == 2 && matches!(result[0], []) && matches!(result[1], [_, b':']) {
121        result.push(&[]);
122    }
123    // Let the host `absolute` function do working-dir handling.
124    let result = result.join(&b'/');
125    interp_ok(path::absolute(bytes_to_os_str(&result)?))
126}
127
128impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
129pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
130    fn emulate_foreign_item_inner(
131        &mut self,
132        link_name: Symbol,
133        abi: &FnAbi<'tcx, Ty<'tcx>>,
134        args: &[OpTy<'tcx>],
135        dest: &MPlaceTy<'tcx>,
136    ) -> InterpResult<'tcx, EmulateItemResult> {
137        let this = self.eval_context_mut();
138
139        // According to
140        // https://github.com/rust-lang/rust/blob/fb00adbdb69266f10df95a4527b767b0ad35ea48/compiler/rustc_target/src/spec/mod.rs#L2766-L2768,
141        // x86-32 Windows uses a different calling convention than other Windows targets
142        // for the "system" ABI.
143        let sys_conv = if this.tcx.sess.target.arch == "x86" { Conv::X86Stdcall } else { Conv::C };
144
145        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
146
147        // Windows API stubs.
148        // HANDLE = isize
149        // NTSTATUS = LONH = i32
150        // DWORD = ULONG = u32
151        // BOOL = i32
152        // BOOLEAN = u8
153        match link_name.as_str() {
154            // Environment related shims
155            "GetEnvironmentVariableW" => {
156                let [name, buf, size] = this.check_shim(abi, sys_conv, link_name, args)?;
157                let result = this.GetEnvironmentVariableW(name, buf, size)?;
158                this.write_scalar(result, dest)?;
159            }
160            "SetEnvironmentVariableW" => {
161                let [name, value] = this.check_shim(abi, sys_conv, link_name, args)?;
162                let result = this.SetEnvironmentVariableW(name, value)?;
163                this.write_scalar(result, dest)?;
164            }
165            "GetEnvironmentStringsW" => {
166                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
167                let result = this.GetEnvironmentStringsW()?;
168                this.write_pointer(result, dest)?;
169            }
170            "FreeEnvironmentStringsW" => {
171                let [env_block] = this.check_shim(abi, sys_conv, link_name, args)?;
172                let result = this.FreeEnvironmentStringsW(env_block)?;
173                this.write_scalar(result, dest)?;
174            }
175            "GetCurrentDirectoryW" => {
176                let [size, buf] = this.check_shim(abi, sys_conv, link_name, args)?;
177                let result = this.GetCurrentDirectoryW(size, buf)?;
178                this.write_scalar(result, dest)?;
179            }
180            "SetCurrentDirectoryW" => {
181                let [path] = this.check_shim(abi, sys_conv, link_name, args)?;
182                let result = this.SetCurrentDirectoryW(path)?;
183                this.write_scalar(result, dest)?;
184            }
185            "GetUserProfileDirectoryW" => {
186                let [token, buf, size] = this.check_shim(abi, sys_conv, link_name, args)?;
187                let result = this.GetUserProfileDirectoryW(token, buf, size)?;
188                this.write_scalar(result, dest)?;
189            }
190            "GetCurrentProcessId" => {
191                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
192                let result = this.GetCurrentProcessId()?;
193                this.write_scalar(result, dest)?;
194            }
195
196            // File related shims
197            "NtWriteFile" => {
198                if !this.frame_in_std() {
199                    throw_unsup_format!(
200                        "`NtWriteFile` support is crude and just enough for stdout to work"
201                    );
202                }
203
204                let [
205                    handle,
206                    _event,
207                    _apc_routine,
208                    _apc_context,
209                    io_status_block,
210                    buf,
211                    n,
212                    byte_offset,
213                    _key,
214                ] = this.check_shim(abi, sys_conv, link_name, args)?;
215                let handle = this.read_target_isize(handle)?;
216                let buf = this.read_pointer(buf)?;
217                let n = this.read_scalar(n)?.to_u32()?;
218                let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer
219                let io_status_block = this
220                    .deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
221
222                if byte_offset != 0 {
223                    throw_unsup_format!(
224                        "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
225                    );
226                }
227
228                let written = if handle == -11 || handle == -12 {
229                    // stdout/stderr
230                    use io::Write;
231
232                    let buf_cont =
233                        this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?;
234                    let res = if this.machine.mute_stdout_stderr {
235                        Ok(buf_cont.len())
236                    } else if handle == -11 {
237                        io::stdout().write(buf_cont)
238                    } else {
239                        io::stderr().write(buf_cont)
240                    };
241                    // We write at most `n` bytes, which is a `u32`, so we cannot have written more than that.
242                    res.ok().map(|n| u32::try_from(n).unwrap())
243                } else {
244                    throw_unsup_format!(
245                        "on Windows, writing to anything except stdout/stderr is not supported"
246                    )
247                };
248                // We have to put the result into io_status_block.
249                if let Some(n) = written {
250                    let io_status_information =
251                        this.project_field_named(&io_status_block, "Information")?;
252                    this.write_scalar(
253                        Scalar::from_target_usize(n.into(), this),
254                        &io_status_information,
255                    )?;
256                }
257                // Return whether this was a success. >= 0 is success.
258                // For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR.
259                this.write_scalar(
260                    Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }),
261                    dest,
262                )?;
263            }
264            "GetFullPathNameW" => {
265                let [filename, size, buffer, filepart] =
266                    this.check_shim(abi, sys_conv, link_name, args)?;
267                this.check_no_isolation("`GetFullPathNameW`")?;
268
269                let filename = this.read_pointer(filename)?;
270                let size = this.read_scalar(size)?.to_u32()?;
271                let buffer = this.read_pointer(buffer)?;
272                let filepart = this.read_pointer(filepart)?;
273
274                if !this.ptr_is_null(filepart)? {
275                    throw_unsup_format!("GetFullPathNameW: non-null `lpFilePart` is not supported");
276                }
277
278                let filename = this.read_path_from_wide_str(filename)?;
279                let result = match win_get_full_path_name(&filename)? {
280                    Err(err) => {
281                        this.set_last_error(err)?;
282                        Scalar::from_u32(0) // return zero upon failure
283                    }
284                    Ok(abs_filename) => {
285                        Scalar::from_u32(helpers::windows_check_buffer_size(
286                            this.write_path_to_wide_str(&abs_filename, buffer, size.into())?,
287                        ))
288                        // This can in fact return 0. It is up to the caller to set last_error to 0
289                        // beforehand and check it afterwards to exclude that case.
290                    }
291                };
292                this.write_scalar(result, dest)?;
293            }
294            "CreateFileW" => {
295                let [
296                    file_name,
297                    desired_access,
298                    share_mode,
299                    security_attributes,
300                    creation_disposition,
301                    flags_and_attributes,
302                    template_file,
303                ] = this.check_shim(abi, sys_conv, link_name, args)?;
304                let handle = this.CreateFileW(
305                    file_name,
306                    desired_access,
307                    share_mode,
308                    security_attributes,
309                    creation_disposition,
310                    flags_and_attributes,
311                    template_file,
312                )?;
313                this.write_scalar(handle.to_scalar(this), dest)?;
314            }
315            "GetFileInformationByHandle" => {
316                let [handle, info] = this.check_shim(abi, sys_conv, link_name, args)?;
317                let res = this.GetFileInformationByHandle(handle, info)?;
318                this.write_scalar(res, dest)?;
319            }
320            "DeleteFileW" => {
321                let [file_name] = this.check_shim(abi, sys_conv, link_name, args)?;
322                let res = this.DeleteFileW(file_name)?;
323                this.write_scalar(res, dest)?;
324            }
325
326            // Allocation
327            "HeapAlloc" => {
328                let [handle, flags, size] = this.check_shim(abi, sys_conv, link_name, args)?;
329                this.read_target_isize(handle)?;
330                let flags = this.read_scalar(flags)?.to_u32()?;
331                let size = this.read_target_usize(size)?;
332                const HEAP_ZERO_MEMORY: u32 = 0x00000008;
333                let init = if (flags & HEAP_ZERO_MEMORY) == HEAP_ZERO_MEMORY {
334                    AllocInit::Zero
335                } else {
336                    AllocInit::Uninit
337                };
338                // Alignment is twice the pointer size.
339                // Source: <https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc>
340                let align = this.tcx.pointer_size().bytes().strict_mul(2);
341                let ptr = this.allocate_ptr(
342                    Size::from_bytes(size),
343                    Align::from_bytes(align).unwrap(),
344                    MiriMemoryKind::WinHeap.into(),
345                    init,
346                )?;
347                this.write_pointer(ptr, dest)?;
348            }
349            "HeapFree" => {
350                let [handle, flags, ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
351                this.read_target_isize(handle)?;
352                this.read_scalar(flags)?.to_u32()?;
353                let ptr = this.read_pointer(ptr)?;
354                // "This pointer can be NULL." It doesn't say what happens then, but presumably nothing.
355                // (https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapfree)
356                if !this.ptr_is_null(ptr)? {
357                    this.deallocate_ptr(ptr, None, MiriMemoryKind::WinHeap.into())?;
358                }
359                this.write_scalar(Scalar::from_i32(1), dest)?;
360            }
361            "HeapReAlloc" => {
362                let [handle, flags, old_ptr, size] =
363                    this.check_shim(abi, sys_conv, link_name, args)?;
364                this.read_target_isize(handle)?;
365                this.read_scalar(flags)?.to_u32()?;
366                let old_ptr = this.read_pointer(old_ptr)?;
367                let size = this.read_target_usize(size)?;
368                let align = this.tcx.pointer_size().bytes().strict_mul(2); // same as above
369                // The docs say that `old_ptr` must come from an earlier HeapAlloc or HeapReAlloc,
370                // so unlike C `realloc` we do *not* allow a NULL here.
371                // (https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heaprealloc)
372                let new_ptr = this.reallocate_ptr(
373                    old_ptr,
374                    None,
375                    Size::from_bytes(size),
376                    Align::from_bytes(align).unwrap(),
377                    MiriMemoryKind::WinHeap.into(),
378                    AllocInit::Uninit,
379                )?;
380                this.write_pointer(new_ptr, dest)?;
381            }
382            "LocalFree" => {
383                let [ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
384                let ptr = this.read_pointer(ptr)?;
385                // "If the hMem parameter is NULL, LocalFree ignores the parameter and returns NULL."
386                // (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree)
387                if !this.ptr_is_null(ptr)? {
388                    this.deallocate_ptr(ptr, None, MiriMemoryKind::WinLocal.into())?;
389                }
390                this.write_null(dest)?;
391            }
392
393            // errno
394            "SetLastError" => {
395                let [error] = this.check_shim(abi, sys_conv, link_name, args)?;
396                let error = this.read_scalar(error)?;
397                this.set_last_error(error)?;
398            }
399            "GetLastError" => {
400                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
401                let last_error = this.get_last_error()?;
402                this.write_scalar(last_error, dest)?;
403            }
404            "RtlNtStatusToDosError" => {
405                let [status] = this.check_shim(abi, sys_conv, link_name, args)?;
406                let status = this.read_scalar(status)?.to_u32()?;
407                let err = match status {
408                    // STATUS_MEDIA_WRITE_PROTECTED => ERROR_WRITE_PROTECT
409                    0xC00000A2 => 19,
410                    // STATUS_FILE_INVALID => ERROR_FILE_INVALID
411                    0xC0000098 => 1006,
412                    // STATUS_DISK_FULL => ERROR_DISK_FULL
413                    0xC000007F => 112,
414                    // STATUS_IO_DEVICE_ERROR => ERROR_IO_DEVICE
415                    0xC0000185 => 1117,
416                    // STATUS_ACCESS_DENIED => ERROR_ACCESS_DENIED
417                    0xC0000022 => 5,
418                    // Anything without an error code => ERROR_MR_MID_NOT_FOUND
419                    _ => 317,
420                };
421                this.write_scalar(Scalar::from_i32(err), dest)?;
422            }
423
424            // Querying system information
425            "GetSystemInfo" => {
426                // Also called from `page_size` crate.
427                let [system_info] = this.check_shim(abi, sys_conv, link_name, args)?;
428                let system_info =
429                    this.deref_pointer_as(system_info, this.windows_ty_layout("SYSTEM_INFO"))?;
430                // Initialize with `0`.
431                this.write_bytes_ptr(
432                    system_info.ptr(),
433                    iter::repeat_n(0u8, system_info.layout.size.bytes_usize()),
434                )?;
435                // Set selected fields.
436                this.write_int_fields_named(
437                    &[
438                        ("dwPageSize", this.machine.page_size.into()),
439                        ("dwNumberOfProcessors", this.machine.num_cpus.into()),
440                    ],
441                    &system_info,
442                )?;
443            }
444
445            // Thread-local storage
446            "TlsAlloc" => {
447                // This just creates a key; Windows does not natively support TLS destructors.
448
449                // Create key and return it.
450                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
451                let key = this.machine.tls.create_tls_key(None, dest.layout.size)?;
452                this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?;
453            }
454            "TlsGetValue" => {
455                let [key] = this.check_shim(abi, sys_conv, link_name, args)?;
456                let key = u128::from(this.read_scalar(key)?.to_u32()?);
457                let active_thread = this.active_thread();
458                let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
459                this.write_scalar(ptr, dest)?;
460            }
461            "TlsSetValue" => {
462                let [key, new_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
463                let key = u128::from(this.read_scalar(key)?.to_u32()?);
464                let active_thread = this.active_thread();
465                let new_data = this.read_scalar(new_ptr)?;
466                this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?;
467
468                // Return success (`1`).
469                this.write_int(1, dest)?;
470            }
471            "TlsFree" => {
472                let [key] = this.check_shim(abi, sys_conv, link_name, args)?;
473                let key = u128::from(this.read_scalar(key)?.to_u32()?);
474                this.machine.tls.delete_tls_key(key)?;
475
476                // Return success (`1`).
477                this.write_int(1, dest)?;
478            }
479
480            // Access to command-line arguments
481            "GetCommandLineW" => {
482                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
483                this.write_pointer(
484                    this.machine.cmd_line.expect("machine must be initialized"),
485                    dest,
486                )?;
487            }
488
489            // Time related shims
490            "GetSystemTimeAsFileTime" | "GetSystemTimePreciseAsFileTime" => {
491                #[allow(non_snake_case)]
492                let [LPFILETIME] = this.check_shim(abi, sys_conv, link_name, args)?;
493                this.GetSystemTimeAsFileTime(link_name.as_str(), LPFILETIME)?;
494            }
495            "QueryPerformanceCounter" => {
496                #[allow(non_snake_case)]
497                let [lpPerformanceCount] = this.check_shim(abi, sys_conv, link_name, args)?;
498                let result = this.QueryPerformanceCounter(lpPerformanceCount)?;
499                this.write_scalar(result, dest)?;
500            }
501            "QueryPerformanceFrequency" => {
502                #[allow(non_snake_case)]
503                let [lpFrequency] = this.check_shim(abi, sys_conv, link_name, args)?;
504                let result = this.QueryPerformanceFrequency(lpFrequency)?;
505                this.write_scalar(result, dest)?;
506            }
507            "Sleep" => {
508                let [timeout] = this.check_shim(abi, sys_conv, link_name, args)?;
509
510                this.Sleep(timeout)?;
511            }
512            "CreateWaitableTimerExW" => {
513                let [attributes, name, flags, access] =
514                    this.check_shim(abi, sys_conv, link_name, args)?;
515                this.read_pointer(attributes)?;
516                this.read_pointer(name)?;
517                this.read_scalar(flags)?.to_u32()?;
518                this.read_scalar(access)?.to_u32()?;
519                // Unimplemented. Always return failure.
520                let not_supported = this.eval_windows("c", "ERROR_NOT_SUPPORTED");
521                this.set_last_error(not_supported)?;
522                this.write_null(dest)?;
523            }
524
525            // Synchronization primitives
526            "InitOnceBeginInitialize" => {
527                let [ptr, flags, pending, context] =
528                    this.check_shim(abi, sys_conv, link_name, args)?;
529                this.InitOnceBeginInitialize(ptr, flags, pending, context, dest)?;
530            }
531            "InitOnceComplete" => {
532                let [ptr, flags, context] = this.check_shim(abi, sys_conv, link_name, args)?;
533                let result = this.InitOnceComplete(ptr, flags, context)?;
534                this.write_scalar(result, dest)?;
535            }
536            "WaitOnAddress" => {
537                let [ptr_op, compare_op, size_op, timeout_op] =
538                    this.check_shim(abi, sys_conv, link_name, args)?;
539
540                this.WaitOnAddress(ptr_op, compare_op, size_op, timeout_op, dest)?;
541            }
542            "WakeByAddressSingle" => {
543                let [ptr_op] = this.check_shim(abi, sys_conv, link_name, args)?;
544
545                this.WakeByAddressSingle(ptr_op)?;
546            }
547            "WakeByAddressAll" => {
548                let [ptr_op] = this.check_shim(abi, sys_conv, link_name, args)?;
549
550                this.WakeByAddressAll(ptr_op)?;
551            }
552
553            // Dynamic symbol loading
554            "GetProcAddress" => {
555                #[allow(non_snake_case)]
556                let [hModule, lpProcName] = this.check_shim(abi, sys_conv, link_name, args)?;
557                this.read_target_isize(hModule)?;
558                let name = this.read_c_str(this.read_pointer(lpProcName)?)?;
559                if let Ok(name) = str::from_utf8(name)
560                    && is_dyn_sym(name)
561                {
562                    let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name)));
563                    this.write_pointer(ptr, dest)?;
564                } else {
565                    this.write_null(dest)?;
566                }
567            }
568
569            // Threading
570            "CreateThread" => {
571                let [security, stacksize, start, arg, flags, thread] =
572                    this.check_shim(abi, sys_conv, link_name, args)?;
573
574                let thread_id =
575                    this.CreateThread(security, stacksize, start, arg, flags, thread)?;
576
577                this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
578            }
579            "WaitForSingleObject" => {
580                let [handle, timeout] = this.check_shim(abi, sys_conv, link_name, args)?;
581
582                let ret = this.WaitForSingleObject(handle, timeout)?;
583                this.write_scalar(ret, dest)?;
584            }
585            "GetCurrentThread" => {
586                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
587
588                this.write_scalar(
589                    Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this),
590                    dest,
591                )?;
592            }
593            "SetThreadDescription" => {
594                let [handle, name] = this.check_shim(abi, sys_conv, link_name, args)?;
595
596                let handle = this.read_handle(handle, "SetThreadDescription")?;
597                let name = this.read_wide_str(this.read_pointer(name)?)?;
598
599                let thread = match handle {
600                    Handle::Thread(thread) => thread,
601                    Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
602                    _ => this.invalid_handle("SetThreadDescription")?,
603                };
604                // FIXME: use non-lossy conversion
605                this.set_thread_name(thread, String::from_utf16_lossy(&name).into_bytes());
606                this.write_scalar(Scalar::from_u32(0), dest)?;
607            }
608            "GetThreadDescription" => {
609                let [handle, name_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
610
611                let handle = this.read_handle(handle, "GetThreadDescription")?;
612                let name_ptr = this.deref_pointer_as(name_ptr, this.machine.layouts.mut_raw_ptr)?; // the pointer where we should store the ptr to the name
613
614                let thread = match handle {
615                    Handle::Thread(thread) => thread,
616                    Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
617                    _ => this.invalid_handle("GetThreadDescription")?,
618                };
619                // Looks like the default thread name is empty.
620                let name = this.get_thread_name(thread).unwrap_or(b"").to_owned();
621                let name = this.alloc_os_str_as_wide_str(
622                    bytes_to_os_str(&name)?,
623                    MiriMemoryKind::WinLocal.into(),
624                )?;
625                let name = Scalar::from_maybe_pointer(name, this);
626                let res = Scalar::from_u32(0);
627
628                this.write_scalar(name, &name_ptr)?;
629                this.write_scalar(res, dest)?;
630            }
631
632            // Miscellaneous
633            "ExitProcess" => {
634                let [code] = this.check_shim(abi, sys_conv, link_name, args)?;
635                // Windows technically uses u32, but we unify everything to a Unix-style i32.
636                let code = this.read_scalar(code)?.to_i32()?;
637                throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
638            }
639            "SystemFunction036" => {
640                // used by getrandom 0.1
641                // This is really 'RtlGenRandom'.
642                let [ptr, len] = this.check_shim(abi, sys_conv, link_name, args)?;
643                let ptr = this.read_pointer(ptr)?;
644                let len = this.read_scalar(len)?.to_u32()?;
645                this.gen_random(ptr, len.into())?;
646                this.write_scalar(Scalar::from_bool(true), dest)?;
647            }
648            "ProcessPrng" => {
649                // used by `std`
650                let [ptr, len] = this.check_shim(abi, sys_conv, link_name, args)?;
651                let ptr = this.read_pointer(ptr)?;
652                let len = this.read_target_usize(len)?;
653                this.gen_random(ptr, len)?;
654                this.write_int(1, dest)?;
655            }
656            "BCryptGenRandom" => {
657                // used by getrandom 0.2
658                let [algorithm, ptr, len, flags] =
659                    this.check_shim(abi, sys_conv, link_name, args)?;
660                let algorithm = this.read_scalar(algorithm)?;
661                let algorithm = algorithm.to_target_usize(this)?;
662                let ptr = this.read_pointer(ptr)?;
663                let len = this.read_scalar(len)?.to_u32()?;
664                let flags = this.read_scalar(flags)?.to_u32()?;
665                match flags {
666                    0 => {
667                        if algorithm != 0x81 {
668                            // BCRYPT_RNG_ALG_HANDLE
669                            throw_unsup_format!(
670                                "BCryptGenRandom algorithm must be BCRYPT_RNG_ALG_HANDLE when the flag is 0"
671                            );
672                        }
673                    }
674                    2 => {
675                        // BCRYPT_USE_SYSTEM_PREFERRED_RNG
676                        if algorithm != 0 {
677                            throw_unsup_format!(
678                                "BCryptGenRandom algorithm must be NULL when the flag is BCRYPT_USE_SYSTEM_PREFERRED_RNG"
679                            );
680                        }
681                    }
682                    _ => {
683                        throw_unsup_format!(
684                            "BCryptGenRandom is only supported with BCRYPT_USE_SYSTEM_PREFERRED_RNG or BCRYPT_RNG_ALG_HANDLE"
685                        );
686                    }
687                }
688                this.gen_random(ptr, len.into())?;
689                this.write_null(dest)?; // STATUS_SUCCESS
690            }
691            "GetConsoleScreenBufferInfo" => {
692                // `term` needs this, so we fake it.
693                let [console, buffer_info] = this.check_shim(abi, sys_conv, link_name, args)?;
694                this.read_target_isize(console)?;
695                // FIXME: this should use deref_pointer_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std
696                this.deref_pointer(buffer_info)?;
697                // Indicate an error.
698                // FIXME: we should set last_error, but to what?
699                this.write_null(dest)?;
700            }
701            "GetStdHandle" => {
702                let [which] = this.check_shim(abi, sys_conv, link_name, args)?;
703                let which = this.read_scalar(which)?.to_i32()?;
704                // We just make this the identity function, so we know later in `NtWriteFile` which
705                // one it is. This is very fake, but libtest needs it so we cannot make it a
706                // std-only shim.
707                // FIXME: this should return real HANDLEs when io support is added
708                this.write_scalar(Scalar::from_target_isize(which.into(), this), dest)?;
709            }
710            "CloseHandle" => {
711                let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
712
713                let ret = this.CloseHandle(handle)?;
714
715                this.write_scalar(ret, dest)?;
716            }
717            "GetModuleFileNameW" => {
718                let [handle, filename, size] = this.check_shim(abi, sys_conv, link_name, args)?;
719                this.check_no_isolation("`GetModuleFileNameW`")?;
720
721                let handle = this.read_handle(handle, "GetModuleFileNameW")?;
722                let filename = this.read_pointer(filename)?;
723                let size = this.read_scalar(size)?.to_u32()?;
724
725                if handle != Handle::Null {
726                    throw_unsup_format!("`GetModuleFileNameW` only supports the NULL handle");
727                }
728
729                // Using the host current_exe is a bit off, but consistent with Linux
730                // (where stdlib reads /proc/self/exe).
731                let path = std::env::current_exe().unwrap();
732                let (all_written, size_needed) =
733                    this.write_path_to_wide_str_truncated(&path, filename, size.into())?;
734
735                if all_written {
736                    // If the function succeeds, the return value is the length of the string that
737                    // is copied to the buffer, in characters, not including the terminating null
738                    // character.
739                    this.write_int(size_needed.strict_sub(1), dest)?;
740                } else {
741                    // If the buffer is too small to hold the module name, the string is truncated
742                    // to nSize characters including the terminating null character, the function
743                    // returns nSize, and the function sets the last error to
744                    // ERROR_INSUFFICIENT_BUFFER.
745                    this.write_int(size, dest)?;
746                    let insufficient_buffer = this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER");
747                    this.set_last_error(insufficient_buffer)?;
748                }
749            }
750            "FormatMessageW" => {
751                let [flags, module, message_id, language_id, buffer, size, arguments] =
752                    this.check_shim(abi, sys_conv, link_name, args)?;
753
754                let flags = this.read_scalar(flags)?.to_u32()?;
755                let _module = this.read_pointer(module)?; // seems to contain a module name
756                let message_id = this.read_scalar(message_id)?;
757                let _language_id = this.read_scalar(language_id)?.to_u32()?;
758                let buffer = this.read_pointer(buffer)?;
759                let size = this.read_scalar(size)?.to_u32()?;
760                let _arguments = this.read_pointer(arguments)?;
761
762                // We only support `FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS`
763                // This also means `arguments` can be ignored.
764                if flags != 4096u32 | 512u32 {
765                    throw_unsup_format!("FormatMessageW: unsupported flags {flags:#x}");
766                }
767
768                let error = this.try_errnum_to_io_error(message_id)?;
769                let formatted = match error {
770                    Some(err) => format!("{err}"),
771                    None => format!("<unknown error in FormatMessageW: {message_id}>"),
772                };
773                let (complete, length) =
774                    this.write_os_str_to_wide_str(OsStr::new(&formatted), buffer, size.into())?;
775                if !complete {
776                    // The API docs don't say what happens when the buffer is not big enough...
777                    // Let's just bail.
778                    throw_unsup_format!("FormatMessageW: buffer not big enough");
779                }
780                // The return value is the number of characters stored *excluding* the null terminator.
781                this.write_int(length.strict_sub(1), dest)?;
782            }
783
784            // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
785            // These shims are enabled only when the caller is in the standard library.
786            "GetProcessHeap" if this.frame_in_std() => {
787                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
788                // Just fake a HANDLE
789                // It's fine to not use the Handle type here because its a stub
790                this.write_int(1, dest)?;
791            }
792            "GetModuleHandleA" if this.frame_in_std() => {
793                #[allow(non_snake_case)]
794                let [_lpModuleName] = this.check_shim(abi, sys_conv, link_name, args)?;
795                // We need to return something non-null here to make `compat_fn!` work.
796                this.write_int(1, dest)?;
797            }
798            "SetConsoleTextAttribute" if this.frame_in_std() => {
799                #[allow(non_snake_case)]
800                let [_hConsoleOutput, _wAttribute] =
801                    this.check_shim(abi, sys_conv, link_name, args)?;
802                // Pretend these does not exist / nothing happened, by returning zero.
803                this.write_null(dest)?;
804            }
805            "GetConsoleMode" if this.frame_in_std() => {
806                let [console, mode] = this.check_shim(abi, sys_conv, link_name, args)?;
807                this.read_target_isize(console)?;
808                this.deref_pointer_as(mode, this.machine.layouts.u32)?;
809                // Indicate an error.
810                this.write_null(dest)?;
811            }
812            "GetFileType" if this.frame_in_std() => {
813                #[allow(non_snake_case)]
814                let [_hFile] = this.check_shim(abi, sys_conv, link_name, args)?;
815                // Return unknown file type.
816                this.write_null(dest)?;
817            }
818            "AddVectoredExceptionHandler" if this.frame_in_std() => {
819                #[allow(non_snake_case)]
820                let [_First, _Handler] = this.check_shim(abi, sys_conv, link_name, args)?;
821                // Any non zero value works for the stdlib. This is just used for stack overflows anyway.
822                this.write_int(1, dest)?;
823            }
824            "SetThreadStackGuarantee" if this.frame_in_std() => {
825                #[allow(non_snake_case)]
826                let [_StackSizeInBytes] = this.check_shim(abi, sys_conv, link_name, args)?;
827                // Any non zero value works for the stdlib. This is just used for stack overflows anyway.
828                this.write_int(1, dest)?;
829            }
830            // this is only callable from std because we know that std ignores the return value
831            "SwitchToThread" if this.frame_in_std() => {
832                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
833
834                this.yield_active_thread();
835
836                // FIXME: this should return a nonzero value if this call does result in switching to another thread.
837                this.write_null(dest)?;
838            }
839
840            "_Unwind_RaiseException" => {
841                // This is not formally part of POSIX, but it is very wide-spread on POSIX systems.
842                // It was originally specified as part of the Itanium C++ ABI:
843                // https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-throw.
844                // MinGW implements _Unwind_RaiseException on top of SEH exceptions.
845                if this.tcx.sess.target.env != "gnu" {
846                    throw_unsup_format!(
847                        "`_Unwind_RaiseException` is not supported on non-MinGW Windows",
848                    );
849                }
850                // This function looks and behaves excatly like miri_start_unwind.
851                let [payload] = this.check_shim(abi, Conv::C, link_name, args)?;
852                this.handle_miri_start_unwind(payload)?;
853                return interp_ok(EmulateItemResult::NeedsUnwind);
854            }
855
856            _ => return interp_ok(EmulateItemResult::NotSupported),
857        }
858
859        interp_ok(EmulateItemResult::NeedsReturn)
860    }
861}