| use std::ffi::OsStr; |
| use std::io; |
| use std::iter; |
| use std::path::{self, Path, PathBuf}; |
| use std::str; |
| |
| use rustc_span::Symbol; |
| use rustc_target::abi::Size; |
| use rustc_target::spec::abi::Abi; |
| |
| use crate::shims::alloc::EvalContextExt as _; |
| use crate::shims::os_str::bytes_to_os_str; |
| use crate::shims::windows::*; |
| use crate::*; |
| use shims::windows::handle::{Handle, PseudoHandle}; |
| |
| pub fn is_dyn_sym(name: &str) -> bool { |
| // std does dynamic detection for these symbols |
| matches!( |
| name, |
| "SetThreadDescription" | "GetThreadDescription" | "WaitOnAddress" | "WakeByAddressSingle" |
| ) |
| } |
| |
| #[cfg(windows)] |
| fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> { |
| // We are on Windows so we can simply lte the host do this. |
| return Ok(path::absolute(path)); |
| } |
| |
| #[cfg(unix)] |
| #[allow(clippy::get_first, clippy::arithmetic_side_effects)] |
| fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> { |
| // We are on Unix, so we need to implement parts of the logic ourselves. |
| let bytes = path.as_os_str().as_encoded_bytes(); |
| // If it starts with `//` (these were backslashes but are already converted) |
| // then this is a magic special path, we just leave it unchanged. |
| if bytes.get(0).copied() == Some(b'/') && bytes.get(1).copied() == Some(b'/') { |
| return Ok(Ok(path.into())); |
| }; |
| // Special treatment for Windows' magic filenames: they are treated as being relative to `\\.\`. |
| let magic_filenames = &[ |
| "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", |
| "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", |
| ]; |
| if magic_filenames.iter().any(|m| m.as_bytes() == bytes) { |
| let mut result: Vec<u8> = br"//./".into(); |
| result.extend(bytes); |
| return Ok(Ok(bytes_to_os_str(&result)?.into())); |
| } |
| // Otherwise we try to do something kind of close to what Windows does, but this is probably not |
| // right in all cases. We iterate over the components between `/`, and remove trailing `.`, |
| // except that trailing `..` remain unchanged. |
| let mut result = vec![]; |
| let mut bytes = bytes; // the remaining bytes to process |
| loop { |
| let len = bytes.iter().position(|&b| b == b'/').unwrap_or(bytes.len()); |
| let mut component = &bytes[..len]; |
| if len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' { |
| // Strip trailing `.` |
| component = &component[..len - 1]; |
| } |
| // Add this component to output. |
| result.extend(component); |
| // Prepare next iteration. |
| if len < bytes.len() { |
| // There's a component after this; add `/` and process remaining bytes. |
| result.push(b'/'); |
| bytes = &bytes[len + 1..]; |
| continue; |
| } else { |
| // This was the last component and it did not have a trailing `/`. |
| break; |
| } |
| } |
| // Let the host `absolute` function do working-dir handling |
| Ok(path::absolute(bytes_to_os_str(&result)?)) |
| } |
| |
| impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} |
| pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { |
| fn emulate_foreign_item_inner( |
| &mut self, |
| link_name: Symbol, |
| abi: Abi, |
| args: &[OpTy<'tcx, Provenance>], |
| dest: &MPlaceTy<'tcx, Provenance>, |
| ) -> InterpResult<'tcx, EmulateItemResult> { |
| let this = self.eval_context_mut(); |
| |
| // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. |
| |
| // Windows API stubs. |
| // HANDLE = isize |
| // NTSTATUS = LONH = i32 |
| // DWORD = ULONG = u32 |
| // BOOL = i32 |
| // BOOLEAN = u8 |
| match link_name.as_str() { |
| // Environment related shims |
| "GetEnvironmentVariableW" => { |
| let [name, buf, size] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.GetEnvironmentVariableW(name, buf, size)?; |
| this.write_scalar(result, dest)?; |
| } |
| "SetEnvironmentVariableW" => { |
| let [name, value] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.SetEnvironmentVariableW(name, value)?; |
| this.write_scalar(result, dest)?; |
| } |
| "GetEnvironmentStringsW" => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.GetEnvironmentStringsW()?; |
| this.write_pointer(result, dest)?; |
| } |
| "FreeEnvironmentStringsW" => { |
| let [env_block] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.FreeEnvironmentStringsW(env_block)?; |
| this.write_scalar(result, dest)?; |
| } |
| "GetCurrentDirectoryW" => { |
| let [size, buf] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.GetCurrentDirectoryW(size, buf)?; |
| this.write_scalar(result, dest)?; |
| } |
| "SetCurrentDirectoryW" => { |
| let [path] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.SetCurrentDirectoryW(path)?; |
| this.write_scalar(result, dest)?; |
| } |
| "GetUserProfileDirectoryW" => { |
| let [token, buf, size] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.GetUserProfileDirectoryW(token, buf, size)?; |
| this.write_scalar(result, dest)?; |
| } |
| |
| // File related shims |
| "NtWriteFile" => { |
| if !this.frame_in_std() { |
| throw_unsup_format!( |
| "`NtWriteFile` support is crude and just enough for stdout to work" |
| ); |
| } |
| |
| let [ |
| handle, |
| _event, |
| _apc_routine, |
| _apc_context, |
| io_status_block, |
| buf, |
| n, |
| byte_offset, |
| _key, |
| ] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let handle = this.read_target_isize(handle)?; |
| let buf = this.read_pointer(buf)?; |
| let n = this.read_scalar(n)?.to_u32()?; |
| let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer |
| let io_status_block = this |
| .deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; |
| |
| if byte_offset != 0 { |
| throw_unsup_format!( |
| "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported" |
| ); |
| } |
| |
| let written = if handle == -11 || handle == -12 { |
| // stdout/stderr |
| use io::Write; |
| |
| let buf_cont = |
| this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?; |
| let res = if this.machine.mute_stdout_stderr { |
| Ok(buf_cont.len()) |
| } else if handle == -11 { |
| io::stdout().write(buf_cont) |
| } else { |
| io::stderr().write(buf_cont) |
| }; |
| // We write at most `n` bytes, which is a `u32`, so we cannot have written more than that. |
| res.ok().map(|n| u32::try_from(n).unwrap()) |
| } else { |
| throw_unsup_format!( |
| "on Windows, writing to anything except stdout/stderr is not supported" |
| ) |
| }; |
| // We have to put the result into io_status_block. |
| if let Some(n) = written { |
| let io_status_information = |
| this.project_field_named(&io_status_block, "Information")?; |
| this.write_scalar( |
| Scalar::from_target_usize(n.into(), this), |
| &io_status_information, |
| )?; |
| } |
| // Return whether this was a success. >= 0 is success. |
| // For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. |
| this.write_scalar( |
| Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }), |
| dest, |
| )?; |
| } |
| "GetFullPathNameW" => { |
| let [filename, size, buffer, filepart] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.check_no_isolation("`GetFullPathNameW`")?; |
| |
| let filename = this.read_pointer(filename)?; |
| let size = this.read_scalar(size)?.to_u32()?; |
| let buffer = this.read_pointer(buffer)?; |
| let filepart = this.read_pointer(filepart)?; |
| |
| if !this.ptr_is_null(filepart)? { |
| throw_unsup_format!("GetFullPathNameW: non-null `lpFilePart` is not supported"); |
| } |
| |
| let filename = this.read_path_from_wide_str(filename)?; |
| let result = match win_absolute(&filename)? { |
| Err(err) => { |
| this.set_last_error_from_io_error(err.kind())?; |
| Scalar::from_u32(0) // return zero upon failure |
| } |
| Ok(abs_filename) => { |
| Scalar::from_u32(helpers::windows_check_buffer_size( |
| this.write_path_to_wide_str(&abs_filename, buffer, size.into())?, |
| )) |
| // This can in fact return 0. It is up to the caller to set last_error to 0 |
| // beforehand and check it afterwards to exclude that case. |
| } |
| }; |
| this.write_scalar(result, dest)?; |
| } |
| |
| // Allocation |
| "HeapAlloc" => { |
| let [handle, flags, size] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_target_isize(handle)?; |
| let flags = this.read_scalar(flags)?.to_u32()?; |
| let size = this.read_target_usize(size)?; |
| let heap_zero_memory = 0x00000008; // HEAP_ZERO_MEMORY |
| let zero_init = (flags & heap_zero_memory) == heap_zero_memory; |
| let res = this.malloc(size, zero_init, MiriMemoryKind::WinHeap)?; |
| this.write_pointer(res, dest)?; |
| } |
| "HeapFree" => { |
| let [handle, flags, ptr] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_target_isize(handle)?; |
| this.read_scalar(flags)?.to_u32()?; |
| let ptr = this.read_pointer(ptr)?; |
| this.free(ptr, MiriMemoryKind::WinHeap)?; |
| this.write_scalar(Scalar::from_i32(1), dest)?; |
| } |
| "HeapReAlloc" => { |
| let [handle, flags, ptr, size] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_target_isize(handle)?; |
| this.read_scalar(flags)?.to_u32()?; |
| let ptr = this.read_pointer(ptr)?; |
| let size = this.read_target_usize(size)?; |
| let res = this.realloc(ptr, size, MiriMemoryKind::WinHeap)?; |
| this.write_pointer(res, dest)?; |
| } |
| "LocalFree" => { |
| let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let ptr = this.read_pointer(ptr)?; |
| this.free(ptr, MiriMemoryKind::WinLocal)?; |
| this.write_null(dest)?; |
| } |
| |
| // errno |
| "SetLastError" => { |
| let [error] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let error = this.read_scalar(error)?; |
| this.set_last_error(error)?; |
| } |
| "GetLastError" => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let last_error = this.get_last_error()?; |
| this.write_scalar(last_error, dest)?; |
| } |
| |
| // Querying system information |
| "GetSystemInfo" => { |
| // Also called from `page_size` crate. |
| let [system_info] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let system_info = |
| this.deref_pointer_as(system_info, this.windows_ty_layout("SYSTEM_INFO"))?; |
| // Initialize with `0`. |
| this.write_bytes_ptr( |
| system_info.ptr(), |
| iter::repeat(0u8).take(system_info.layout.size.bytes_usize()), |
| )?; |
| // Set selected fields. |
| this.write_int_fields_named( |
| &[ |
| ("dwPageSize", this.machine.page_size.into()), |
| ("dwNumberOfProcessors", this.machine.num_cpus.into()), |
| ], |
| &system_info, |
| )?; |
| } |
| |
| // Thread-local storage |
| "TlsAlloc" => { |
| // This just creates a key; Windows does not natively support TLS destructors. |
| |
| // Create key and return it. |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let key = this.machine.tls.create_tls_key(None, dest.layout.size)?; |
| this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; |
| } |
| "TlsGetValue" => { |
| let [key] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let key = u128::from(this.read_scalar(key)?.to_u32()?); |
| let active_thread = this.get_active_thread(); |
| let ptr = this.machine.tls.load_tls(key, active_thread, this)?; |
| this.write_scalar(ptr, dest)?; |
| } |
| "TlsSetValue" => { |
| let [key, new_ptr] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let key = u128::from(this.read_scalar(key)?.to_u32()?); |
| let active_thread = this.get_active_thread(); |
| let new_data = this.read_scalar(new_ptr)?; |
| this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; |
| |
| // Return success (`1`). |
| this.write_scalar(Scalar::from_i32(1), dest)?; |
| } |
| |
| // Access to command-line arguments |
| "GetCommandLineW" => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.write_pointer( |
| this.machine.cmd_line.expect("machine must be initialized"), |
| dest, |
| )?; |
| } |
| |
| // Time related shims |
| "GetSystemTimeAsFileTime" | "GetSystemTimePreciseAsFileTime" => { |
| #[allow(non_snake_case)] |
| let [LPFILETIME] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.GetSystemTimeAsFileTime(link_name.as_str(), LPFILETIME)?; |
| } |
| "QueryPerformanceCounter" => { |
| #[allow(non_snake_case)] |
| let [lpPerformanceCount] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.QueryPerformanceCounter(lpPerformanceCount)?; |
| this.write_scalar(result, dest)?; |
| } |
| "QueryPerformanceFrequency" => { |
| #[allow(non_snake_case)] |
| let [lpFrequency] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.QueryPerformanceFrequency(lpFrequency)?; |
| this.write_scalar(result, dest)?; |
| } |
| "Sleep" => { |
| let [timeout] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.Sleep(timeout)?; |
| } |
| "CreateWaitableTimerExW" => { |
| let [attributes, name, flags, access] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_pointer(attributes)?; |
| this.read_pointer(name)?; |
| this.read_scalar(flags)?.to_u32()?; |
| this.read_scalar(access)?.to_u32()?; |
| // Unimplemented. Always return failure. |
| let not_supported = this.eval_windows("c", "ERROR_NOT_SUPPORTED"); |
| this.set_last_error(not_supported)?; |
| this.write_null(dest)?; |
| } |
| |
| // Synchronization primitives |
| "InitOnceBeginInitialize" => { |
| let [ptr, flags, pending, context] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.InitOnceBeginInitialize(ptr, flags, pending, context)?; |
| this.write_scalar(result, dest)?; |
| } |
| "InitOnceComplete" => { |
| let [ptr, flags, context] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.InitOnceComplete(ptr, flags, context)?; |
| this.write_scalar(result, dest)?; |
| } |
| "WaitOnAddress" => { |
| let [ptr_op, compare_op, size_op, timeout_op] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.WaitOnAddress(ptr_op, compare_op, size_op, timeout_op, dest)?; |
| } |
| "WakeByAddressSingle" => { |
| let [ptr_op] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.WakeByAddressSingle(ptr_op)?; |
| } |
| "WakeByAddressAll" => { |
| let [ptr_op] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.WakeByAddressAll(ptr_op)?; |
| } |
| |
| // Dynamic symbol loading |
| "GetProcAddress" => { |
| #[allow(non_snake_case)] |
| let [hModule, lpProcName] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_target_isize(hModule)?; |
| let name = this.read_c_str(this.read_pointer(lpProcName)?)?; |
| if let Ok(name) = str::from_utf8(name) |
| && is_dyn_sym(name) |
| { |
| let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name))); |
| this.write_pointer(ptr, dest)?; |
| } else { |
| this.write_null(dest)?; |
| } |
| } |
| |
| // Threading |
| "CreateThread" => { |
| let [security, stacksize, start, arg, flags, thread] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| let thread_id = |
| this.CreateThread(security, stacksize, start, arg, flags, thread)?; |
| |
| this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?; |
| } |
| "WaitForSingleObject" => { |
| let [handle, timeout] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| let ret = this.WaitForSingleObject(handle, timeout)?; |
| this.write_scalar(Scalar::from_u32(ret), dest)?; |
| } |
| "GetCurrentThread" => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.write_scalar( |
| Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this), |
| dest, |
| )?; |
| } |
| "SetThreadDescription" => { |
| let [handle, name] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| let handle = this.read_scalar(handle)?; |
| let name = this.read_wide_str(this.read_pointer(name)?)?; |
| |
| let thread = match Handle::from_scalar(handle, this)? { |
| Some(Handle::Thread(thread)) => thread, |
| Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(), |
| _ => this.invalid_handle("SetThreadDescription")?, |
| }; |
| |
| // FIXME: use non-lossy conversion |
| this.set_thread_name(thread, String::from_utf16_lossy(&name).into_bytes()); |
| |
| this.write_null(dest)?; |
| } |
| "GetThreadDescription" => { |
| let [handle, name_ptr] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| let handle = this.read_scalar(handle)?; |
| let name_ptr = this.deref_pointer(name_ptr)?; // the pointer where we should store the ptr to the name |
| |
| let thread = match Handle::from_scalar(handle, this)? { |
| Some(Handle::Thread(thread)) => thread, |
| Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.get_active_thread(), |
| _ => this.invalid_handle("SetThreadDescription")?, |
| }; |
| // Looks like the default thread name is empty. |
| let name = this.get_thread_name(thread).unwrap_or(b"").to_owned(); |
| let name = this.alloc_os_str_as_wide_str( |
| bytes_to_os_str(&name)?, |
| MiriMemoryKind::WinLocal.into(), |
| )?; |
| |
| this.write_scalar(Scalar::from_maybe_pointer(name, this), &name_ptr)?; |
| |
| this.write_null(dest)?; |
| } |
| |
| // Miscellaneous |
| "ExitProcess" => { |
| let [code] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let code = this.read_scalar(code)?.to_u32()?; |
| throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false }); |
| } |
| "SystemFunction036" => { |
| // This is really 'RtlGenRandom'. |
| let [ptr, len] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let ptr = this.read_pointer(ptr)?; |
| let len = this.read_scalar(len)?.to_u32()?; |
| this.gen_random(ptr, len.into())?; |
| this.write_scalar(Scalar::from_bool(true), dest)?; |
| } |
| "ProcessPrng" => { |
| let [ptr, len] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let ptr = this.read_pointer(ptr)?; |
| let len = this.read_target_usize(len)?; |
| this.gen_random(ptr, len)?; |
| this.write_scalar(Scalar::from_i32(1), dest)?; |
| } |
| "BCryptGenRandom" => { |
| let [algorithm, ptr, len, flags] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let algorithm = this.read_scalar(algorithm)?; |
| let algorithm = algorithm.to_target_usize(this)?; |
| let ptr = this.read_pointer(ptr)?; |
| let len = this.read_scalar(len)?.to_u32()?; |
| let flags = this.read_scalar(flags)?.to_u32()?; |
| match flags { |
| 0 => { |
| if algorithm != 0x81 { |
| // BCRYPT_RNG_ALG_HANDLE |
| throw_unsup_format!( |
| "BCryptGenRandom algorithm must be BCRYPT_RNG_ALG_HANDLE when the flag is 0" |
| ); |
| } |
| } |
| 2 => { |
| // BCRYPT_USE_SYSTEM_PREFERRED_RNG |
| if algorithm != 0 { |
| throw_unsup_format!( |
| "BCryptGenRandom algorithm must be NULL when the flag is BCRYPT_USE_SYSTEM_PREFERRED_RNG" |
| ); |
| } |
| } |
| _ => { |
| throw_unsup_format!( |
| "BCryptGenRandom is only supported with BCRYPT_USE_SYSTEM_PREFERRED_RNG or BCRYPT_RNG_ALG_HANDLE" |
| ); |
| } |
| } |
| this.gen_random(ptr, len.into())?; |
| this.write_null(dest)?; // STATUS_SUCCESS |
| } |
| "GetConsoleScreenBufferInfo" => { |
| // `term` needs this, so we fake it. |
| let [console, buffer_info] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_target_isize(console)?; |
| // FIXME: this should use deref_pointer_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std |
| this.deref_pointer(buffer_info)?; |
| // Indicate an error. |
| // FIXME: we should set last_error, but to what? |
| this.write_null(dest)?; |
| } |
| "GetStdHandle" => { |
| let [which] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let which = this.read_scalar(which)?.to_i32()?; |
| // We just make this the identity function, so we know later in `NtWriteFile` which |
| // one it is. This is very fake, but libtest needs it so we cannot make it a |
| // std-only shim. |
| // FIXME: this should return real HANDLEs when io support is added |
| this.write_scalar(Scalar::from_target_isize(which.into(), this), dest)?; |
| } |
| "CloseHandle" => { |
| let [handle] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.CloseHandle(handle)?; |
| |
| this.write_scalar(Scalar::from_u32(1), dest)?; |
| } |
| "GetModuleFileNameW" => { |
| let [handle, filename, size] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.check_no_isolation("`GetModuleFileNameW`")?; |
| |
| let handle = this.read_target_usize(handle)?; |
| let filename = this.read_pointer(filename)?; |
| let size = this.read_scalar(size)?.to_u32()?; |
| |
| if handle != 0 { |
| throw_unsup_format!("`GetModuleFileNameW` only supports the NULL handle"); |
| } |
| |
| // Using the host current_exe is a bit off, but consistent with Linux |
| // (where stdlib reads /proc/self/exe). |
| let path = std::env::current_exe().unwrap(); |
| let (all_written, size_needed) = |
| this.write_path_to_wide_str_truncated(&path, filename, size.into())?; |
| |
| if all_written { |
| // If the function succeeds, the return value is the length of the string that |
| // is copied to the buffer, in characters, not including the terminating null |
| // character. |
| this.write_int(size_needed.checked_sub(1).unwrap(), dest)?; |
| } else { |
| // If the buffer is too small to hold the module name, the string is truncated |
| // to nSize characters including the terminating null character, the function |
| // returns nSize, and the function sets the last error to |
| // ERROR_INSUFFICIENT_BUFFER. |
| this.write_int(size, dest)?; |
| let insufficient_buffer = this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"); |
| this.set_last_error(insufficient_buffer)?; |
| } |
| } |
| "FormatMessageW" => { |
| let [flags, module, message_id, language_id, buffer, size, arguments] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| let flags = this.read_scalar(flags)?.to_u32()?; |
| let _module = this.read_pointer(module)?; // seems to contain a module name |
| let message_id = this.read_scalar(message_id)?; |
| let _language_id = this.read_scalar(language_id)?.to_u32()?; |
| let buffer = this.read_pointer(buffer)?; |
| let size = this.read_scalar(size)?.to_u32()?; |
| let _arguments = this.read_pointer(arguments)?; |
| |
| // We only support `FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS` |
| // This also means `arguments` can be ignored. |
| if flags != 4096u32 | 512u32 { |
| throw_unsup_format!("FormatMessageW: unsupported flags {flags:#x}"); |
| } |
| |
| let error = this.try_errnum_to_io_error(message_id)?; |
| let formatted = match error { |
| Some(err) => format!("{err}"), |
| None => format!("<unknown error in FormatMessageW: {message_id}>"), |
| }; |
| let (complete, length) = |
| this.write_os_str_to_wide_str(OsStr::new(&formatted), buffer, size.into())?; |
| if !complete { |
| // The API docs don't say what happens when the buffer is not big enough... |
| // Let's just bail. |
| throw_unsup_format!("FormatMessageW: buffer not big enough"); |
| } |
| // The return value is the number of characters stored *excluding* the null terminator. |
| this.write_int(length.checked_sub(1).unwrap(), dest)?; |
| } |
| |
| // Incomplete shims that we "stub out" just to get pre-main initialization code to work. |
| // These shims are enabled only when the caller is in the standard library. |
| "GetProcessHeap" if this.frame_in_std() => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| // Just fake a HANDLE |
| // It's fine to not use the Handle type here because its a stub |
| this.write_int(1, dest)?; |
| } |
| "GetModuleHandleA" if this.frame_in_std() => { |
| #[allow(non_snake_case)] |
| let [_lpModuleName] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| // We need to return something non-null here to make `compat_fn!` work. |
| this.write_int(1, dest)?; |
| } |
| "SetConsoleTextAttribute" if this.frame_in_std() => { |
| #[allow(non_snake_case)] |
| let [_hConsoleOutput, _wAttribute] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| // Pretend these does not exist / nothing happened, by returning zero. |
| this.write_null(dest)?; |
| } |
| "GetConsoleMode" if this.frame_in_std() => { |
| let [console, mode] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| this.read_target_isize(console)?; |
| this.deref_pointer(mode)?; |
| // Indicate an error. |
| this.write_null(dest)?; |
| } |
| "GetFileType" if this.frame_in_std() => { |
| #[allow(non_snake_case)] |
| let [_hFile] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| // Return unknown file type. |
| this.write_null(dest)?; |
| } |
| "AddVectoredExceptionHandler" if this.frame_in_std() => { |
| #[allow(non_snake_case)] |
| let [_First, _Handler] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| // Any non zero value works for the stdlib. This is just used for stack overflows anyway. |
| this.write_int(1, dest)?; |
| } |
| "SetThreadStackGuarantee" if this.frame_in_std() => { |
| #[allow(non_snake_case)] |
| let [_StackSizeInBytes] = |
| this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| // Any non zero value works for the stdlib. This is just used for stack overflows anyway. |
| this.write_int(1, dest)?; |
| } |
| "GetCurrentProcessId" if this.frame_in_std() => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| let result = this.GetCurrentProcessId()?; |
| this.write_int(result, dest)?; |
| } |
| // this is only callable from std because we know that std ignores the return value |
| "SwitchToThread" if this.frame_in_std() => { |
| let [] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; |
| |
| this.yield_active_thread(); |
| |
| // FIXME: this should return a nonzero value if this call does result in switching to another thread. |
| this.write_null(dest)?; |
| } |
| |
| _ => return Ok(EmulateItemResult::NotSupported), |
| } |
| |
| Ok(EmulateItemResult::NeedsJumping) |
| } |
| } |