From 587e354c5e14a0560461e8fcc0eefea7ecc4e750 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Fri, 5 Jun 2026 21:26:25 +0100 Subject: [PATCH 1/3] Add support for cygwin cc1.exe/as.exe --- dll/kernel32/fileapi.cpp | 92 ++++++ dll/kernel32/fileapi.h | 12 + dll/kernel32/processthreadsapi.cpp | 57 +++- dll/kernel32/processthreadsapi.h | 2 + dll/kernel32/wincon.cpp | 82 +++++- dll/kernel32/wincon.h | 2 + dll/ntdll.cpp | 434 +++++++++++++++++++++++++++++ dll/ntdll.h | 78 ++++++ src/errors.cpp | 9 + src/errors.h | 3 + src/loader.cpp | 4 +- src/main.cpp | 2 +- src/modules.cpp | 59 ++-- src/modules.h | 3 +- 14 files changed, 805 insertions(+), 34 deletions(-) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index 6147472..99291bd 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -1597,6 +1597,98 @@ DWORD WINAPI GetFileType(HANDLE hFile) { return type; } +BOOL WINAPI ClearCommBreak(HANDLE hFile) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("ClearCommBreak(%p) -> ERROR_INVALID_HANDLE\n", hFile); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI ClearCommError(HANDLE hFile, LPDWORD lpErrors, LPVOID lpStat) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("ClearCommError(%p, %p, %p) -> ERROR_INVALID_HANDLE\n", hFile, lpErrors, lpStat); + if (lpErrors) { + *lpErrors = 0; + } + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI EscapeCommFunction(HANDLE hFile, DWORD dwFunc) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("EscapeCommFunction(%p, %u) -> ERROR_INVALID_HANDLE\n", hFile, dwFunc); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI GetCommModemStatus(HANDLE hFile, LPDWORD lpModemStat) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetCommModemStatus(%p, %p) -> ERROR_INVALID_HANDLE\n", hFile, lpModemStat); + if (lpModemStat) { + *lpModemStat = 0; + } + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI GetCommState(HANDLE hFile, LPVOID lpDCB) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetCommState(%p, %p) -> ERROR_INVALID_HANDLE\n", hFile, lpDCB); + // Cygwin probes std handles as possible serial devices during fd setup. + // wibo does not emulate COM ports, so non-console pipes/files must fail this probe. + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI PurgeComm(HANDLE hFile, DWORD dwFlags) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("PurgeComm(%p, 0x%x) -> ERROR_INVALID_HANDLE\n", hFile, dwFlags); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI SetCommBreak(HANDLE hFile) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("SetCommBreak(%p) -> ERROR_INVALID_HANDLE\n", hFile); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI SetCommMask(HANDLE hFile, DWORD dwEvtMask) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("SetCommMask(%p, 0x%x) -> ERROR_INVALID_HANDLE\n", hFile, dwEvtMask); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI SetCommState(HANDLE hFile, LPVOID lpDCB) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("SetCommState(%p, %p) -> ERROR_INVALID_HANDLE\n", hFile, lpDCB); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI SetCommTimeouts(HANDLE hFile, LPVOID lpCommTimeouts) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("SetCommTimeouts(%p, %p) -> ERROR_INVALID_HANDLE\n", hFile, lpCommTimeouts); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI TransmitCommChar(HANDLE hFile, char cChar) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("TransmitCommChar(%p, 0x%x) -> ERROR_INVALID_HANDLE\n", hFile, static_cast(cChar)); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + +BOOL WINAPI WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("WaitCommEvent(%p, %p, %p) -> ERROR_INVALID_HANDLE\n", hFile, lpEvtMask, lpOverlapped); + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + DWORD WINAPI GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, GUEST_PTR *lpFilePart) { HOST_CONTEXT_GUARD(); DEBUG_LOG("GetFullPathNameA(%s, %u)\n", lpFileName ? lpFileName : "(null)", nBufferLength); diff --git a/dll/kernel32/fileapi.h b/dll/kernel32/fileapi.h index 7531963..32d3a6d 100644 --- a/dll/kernel32/fileapi.h +++ b/dll/kernel32/fileapi.h @@ -104,6 +104,18 @@ BOOL WINAPI SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FILE const FILETIME *lpLastWriteTime); BOOL WINAPI GetFileInformationByHandle(HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation); DWORD WINAPI GetFileType(HANDLE hFile); +BOOL WINAPI ClearCommBreak(HANDLE hFile); +BOOL WINAPI ClearCommError(HANDLE hFile, LPDWORD lpErrors, LPVOID lpStat); +BOOL WINAPI EscapeCommFunction(HANDLE hFile, DWORD dwFunc); +BOOL WINAPI GetCommModemStatus(HANDLE hFile, LPDWORD lpModemStat); +BOOL WINAPI GetCommState(HANDLE hFile, LPVOID lpDCB); +BOOL WINAPI PurgeComm(HANDLE hFile, DWORD dwFlags); +BOOL WINAPI SetCommBreak(HANDLE hFile); +BOOL WINAPI SetCommMask(HANDLE hFile, DWORD dwEvtMask); +BOOL WINAPI SetCommState(HANDLE hFile, LPVOID lpDCB); +BOOL WINAPI SetCommTimeouts(HANDLE hFile, LPVOID lpCommTimeouts); +BOOL WINAPI TransmitCommChar(HANDLE hFile, char cChar); +BOOL WINAPI WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped); LONG WINAPI CompareFileTime(const FILETIME *lpFileTime1, const FILETIME *lpFileTime2); BOOL WINAPI GetVolumeInformationA(LPCSTR lpRootPathName, LPSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength, diff --git a/dll/kernel32/processthreadsapi.cpp b/dll/kernel32/processthreadsapi.cpp index 46e4a8d..5f2964b 100644 --- a/dll/kernel32/processthreadsapi.cpp +++ b/dll/kernel32/processthreadsapi.cpp @@ -145,11 +145,28 @@ void *threadTrampoline(void *param) { } } + LPTHREAD_START_ROUTINE entry = data.entry; + void **entrySlot = nullptr; + void *savedGuestStack = nullptr; + if (threadTib && entry) { + // Cygwin 1.x scans the initial thread stack during DLL_THREAD_ATTACH + // and rewrites the start routine slot to its own signal-aware wrapper. + savedGuestStack = threadTib->CurrentStackPointer; + entrySlot = static_cast(savedGuestStack) - 1; + *entrySlot = reinterpret_cast(entry); + threadTib->CurrentStackPointer = entrySlot; + } + wibo::notifyDllThreadAttach(); - DEBUG_LOG("Calling thread entry %p with userData %p\n", data.entry, data.userData); + + if (entrySlot) { + entry = reinterpret_cast(*entrySlot); + threadTib->CurrentStackPointer = savedGuestStack; + } + DEBUG_LOG("Calling thread entry %p with userData %p\n", entry, data.userData); DWORD result = 0; - if (data.entry) { - result = call_LPTHREAD_START_ROUTINE(data.entry, data.userData); + if (entry) { + result = call_LPTHREAD_START_ROUTINE(entry, data.userData); } DEBUG_LOG("Thread exiting with code %u\n", result); { @@ -577,6 +594,40 @@ DWORD WINAPI GetPriorityClass(HANDLE hProcess) { return NORMAL_PRIORITY_CLASS; } +BOOL WINAPI GetProcessTimes(HANDLE hProcess, FILETIME *lpCreationTime, FILETIME *lpExitTime, FILETIME *lpKernelTime, + FILETIME *lpUserTime) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetProcessTimes(%p, %p, %p, %p, %p)\n", hProcess, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime); + if (!lpKernelTime || !lpUserTime) { + setLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (!isPseudoCurrentProcessHandle(hProcess) && !wibo::handles().getAs(hProcess)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (lpCreationTime) { + *lpCreationTime = kDefaultThreadFileTime; + } + if (lpExitTime) { + lpExitTime->dwLowDateTime = 0; + lpExitTime->dwHighDateTime = 0; + } + + struct rusage usage{}; + if (getrusage(RUSAGE_SELF, &usage) == 0) { + *lpKernelTime = fileTimeFromTimeval(usage.ru_stime); + *lpUserTime = fileTimeFromTimeval(usage.ru_utime); + return TRUE; + } + + kernel32::setLastErrorFromErrno(); + *lpKernelTime = fileTimeFromDuration(0); + *lpUserTime = fileTimeFromDuration(0); + return FALSE; +} + BOOL WINAPI GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME *lpExitTime, FILETIME *lpKernelTime, FILETIME *lpUserTime) { HOST_CONTEXT_GUARD(); diff --git a/dll/kernel32/processthreadsapi.h b/dll/kernel32/processthreadsapi.h index 1d8e3ac..f79e119 100644 --- a/dll/kernel32/processthreadsapi.h +++ b/dll/kernel32/processthreadsapi.h @@ -91,6 +91,8 @@ BOOL WINAPI GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode); BOOL WINAPI SetThreadPriority(HANDLE hThread, int nPriority); int WINAPI GetThreadPriority(HANDLE hThread); DWORD WINAPI GetPriorityClass(HANDLE hProcess); +BOOL WINAPI GetProcessTimes(HANDLE hProcess, FILETIME *lpCreationTime, FILETIME *lpExitTime, FILETIME *lpKernelTime, + FILETIME *lpUserTime); BOOL WINAPI GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME *lpExitTime, FILETIME *lpKernelTime, FILETIME *lpUserTime); BOOL WINAPI CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, diff --git a/dll/kernel32/wincon.cpp b/dll/kernel32/wincon.cpp index 8a26a6a..54a599f 100644 --- a/dll/kernel32/wincon.cpp +++ b/dll/kernel32/wincon.cpp @@ -6,11 +6,36 @@ #include "handles.h" #include "strutil.h" +#include + +namespace { + +Pin getValidFileHandle(HANDLE handle) { + auto file = wibo::handles().getAs(handle); + if (!file || !file->valid()) { + return {}; + } + return file; +} + +bool isConsoleFileHandle(HANDLE handle) { + auto file = getValidFileHandle(handle); + // Console probe APIs must fail for redirected pipes/files. Old Cygwin + // uses these failures to choose its POSIX pipe/file fhandlers for stdio. + return file && isatty(file->fd); +} + +} // namespace + namespace kernel32 { BOOL WINAPI GetConsoleMode(HANDLE hConsoleHandle, LPDWORD lpMode) { HOST_CONTEXT_GUARD(); - DEBUG_LOG("STUB: GetConsoleMode(%p)\n", hConsoleHandle); + DEBUG_LOG("GetConsoleMode(%p, %p)\n", hConsoleHandle, lpMode); + if (!isConsoleFileHandle(hConsoleHandle)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } if (lpMode) { *lpMode = 0; } @@ -19,9 +44,12 @@ BOOL WINAPI GetConsoleMode(HANDLE hConsoleHandle, LPDWORD lpMode) { BOOL WINAPI SetConsoleMode(HANDLE hConsoleHandle, DWORD dwMode) { HOST_CONTEXT_GUARD(); - DEBUG_LOG("STUB: SetConsoleMode(%p, 0x%x)\n", hConsoleHandle, dwMode); - (void)hConsoleHandle; + DEBUG_LOG("SetConsoleMode(%p, 0x%x)\n", hConsoleHandle, dwMode); (void)dwMode; + if (!isConsoleFileHandle(hConsoleHandle)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } return TRUE; } @@ -47,8 +75,11 @@ BOOL WINAPI SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine, BOOL Add) { BOOL WINAPI GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *lpConsoleScreenBufferInfo) { HOST_CONTEXT_GUARD(); - DEBUG_LOG("STUB: GetConsoleScreenBufferInfo(%p, %p)\n", hConsoleOutput, lpConsoleScreenBufferInfo); - (void)hConsoleOutput; + DEBUG_LOG("GetConsoleScreenBufferInfo(%p, %p)\n", hConsoleOutput, lpConsoleScreenBufferInfo); + if (!isConsoleFileHandle(hConsoleOutput)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } if (!lpConsoleScreenBufferInfo) { setLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -61,6 +92,28 @@ BOOL WINAPI GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUF return TRUE; } +BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput, COORD dwCursorPosition) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("SetConsoleCursorPosition(%p, {%d, %d})\n", hConsoleOutput, dwCursorPosition.X, dwCursorPosition.Y); + auto file = wibo::handles().getAs(hConsoleOutput); + if (!file || !file->valid()) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + if (dwCursorPosition.X == 0 && dwCursorPosition.Y > 0) { + // Old Cygwin console output advances lines by moving the cursor + // instead of writing newline bytes. Preserve that when the console + // handle is really backed by a host stream or redirected file. + const char newline = '\n'; + auto io = files::write(file.get(), &newline, sizeof(newline), std::nullopt, true); + if (io.unixError != 0) { + setLastError(wibo::winErrorFromErrno(io.unixError)); + return FALSE; + } + } + return TRUE; +} + BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved) { HOST_CONTEXT_GUARD(); @@ -76,6 +129,10 @@ BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumber } auto file = wibo::handles().getAs(hConsoleOutput); + if (!file || !file->valid()) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } if (file->fd == STDOUT_FILENO || file->fd == STDERR_FILENO) { auto str = wideStringToString(lpBuffer, static_cast(nNumberOfCharsToWrite)); auto io = files::write(file.get(), str.c_str(), str.size(), std::nullopt, true); @@ -93,6 +150,21 @@ BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumber return FALSE; } +BOOL WINAPI GetNumberOfConsoleInputEvents(HANDLE hConsoleInput, LPDWORD lpNumberOfEvents) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetNumberOfConsoleInputEvents(%p, %p)\n", hConsoleInput, lpNumberOfEvents); + if (!isConsoleFileHandle(hConsoleInput)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + if (!lpNumberOfEvents) { + setLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + *lpNumberOfEvents = 0; + return TRUE; +} + DWORD WINAPI GetConsoleTitleA(LPSTR lpConsoleTitle, DWORD nSize) { HOST_CONTEXT_GUARD(); DEBUG_LOG("GetConsoleTitleA(%p, %u)\n", lpConsoleTitle, nSize); diff --git a/dll/kernel32/wincon.h b/dll/kernel32/wincon.h index a34b8ac..b4d7167 100644 --- a/dll/kernel32/wincon.h +++ b/dll/kernel32/wincon.h @@ -34,8 +34,10 @@ UINT WINAPI GetConsoleCP(); UINT WINAPI GetConsoleOutputCP(); BOOL WINAPI SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine, BOOL Add); BOOL WINAPI GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *lpConsoleScreenBufferInfo); +BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput, COORD dwCursorPosition); BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved); +BOOL WINAPI GetNumberOfConsoleInputEvents(HANDLE hConsoleInput, LPDWORD lpNumberOfEvents); DWORD WINAPI GetConsoleTitleA(LPSTR lpConsoleTitle, DWORD nSize); DWORD WINAPI GetConsoleTitleW(LPWSTR lpConsoleTitle, DWORD nSize); BOOL WINAPI PeekConsoleInputA(HANDLE hConsoleInput, INPUT_RECORD *lpBuffer, DWORD nLength, diff --git a/dll/ntdll.cpp b/dll/ntdll.cpp index 17309d4..6f2b972 100644 --- a/dll/ntdll.cpp +++ b/dll/ntdll.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,12 @@ constexpr BYTE kProductTypeWorkstation = 1; // VER_NT_WORKSTATION constexpr ULONGLONG kHundredNanosecondsPerSecond = 10'000'000ULL; constexpr ULONGLONG kUnixEpochAsFileTime = 116'444'736'000'000'000ULL; +constexpr ULONG kDefaultVolumeSerialNumber = 0x12345678; +constexpr ULONG kDefaultMaximumComponentNameLength = 255; +constexpr ULONG kFileDeviceDisk = 0x00000007; +constexpr ULONG kFileCaseSensitiveSearch = 0x00000001; +constexpr ULONG kFileCasePreservedNames = 0x00000002; +constexpr ULONG kFileUnicodeOnDisk = 0x00000004; struct StatFetchResult { bool ok = false; @@ -147,6 +154,40 @@ StatFetchResult fetchStat(kernel32::FsObject *fs, struct stat &st) { return StatFetchResult{}; } +StatFetchResult fetchStatvfs(kernel32::FsObject *fs, struct statvfs &st) { + if (!fs) { + return {}; + } + if (fs->valid()) { + if (fstatvfs(fs->fd, &st) == 0) { + return StatFetchResult{.ok = true, .err = 0}; + } + if (errno != EBADF) { + return StatFetchResult{.ok = false, .err = errno}; + } + } + if (!fs->canonicalPath.empty()) { + std::filesystem::path queryPath = fs->canonicalPath; + while (true) { + if (statvfs(queryPath.c_str(), &st) == 0) { + return StatFetchResult{.ok = true, .err = 0}; + } + + int savedErrno = errno; + if (savedErrno != ENOENT && savedErrno != ENOTDIR) { + return StatFetchResult{.ok = false, .err = savedErrno}; + } + + std::filesystem::path parent = queryPath.parent_path(); + if (parent == queryPath || parent.empty()) { + return StatFetchResult{.ok = false, .err = savedErrno}; + } + queryPath = parent; + } + } + return StatFetchResult{}; +} + bool resolveProcessDetails(HANDLE processHandle, ProcessHandleDetails &details) { if (kernel32::isPseudoCurrentProcessHandle(processHandle)) { details.pid = getpid(); @@ -182,6 +223,48 @@ std::string windowsImagePathFor(const ProcessHandleDetails &details) { return {}; } +std::string ntObjectPathToWin32(const UNICODE_STRING *objectName) { + if (!objectName || !objectName->Buffer) { + return {}; + } + const auto *buffer = fromGuestPtr(objectName->Buffer); + if (!buffer) { + return {}; + } + std::string path = wideStringToString(buffer, objectName->Length / sizeof(uint16_t)); + if (path.rfind("\\??\\", 0) == 0) { + path.erase(0, 4); + } else if (path.rfind("\\DosDevices\\", 0) == 0) { + path.erase(0, 12); + } + return path; +} + +bool ntPathIsRooted(const std::string &path) { + return (path.size() >= 2 && path[1] == ':') || (!path.empty() && (path.front() == '\\' || path.front() == '/')); +} + +NTSTATUS statusFromLastError() { return wibo::statusFromWinError(kernel32::getLastError()); } + +std::pair volumeSectorGeometry(const struct statvfs &st) { + uint64_t blockSize = st.f_frsize ? st.f_frsize : st.f_bsize; + if (blockSize == 0) { + blockSize = 4096; + } + + ULONG bytesPerSector = 512; + if (blockSize % bytesPerSector != 0) { + bytesPerSector = static_cast(std::min(blockSize, std::numeric_limits::max())); + } + + ULONG sectorsPerAllocationUnit = static_cast(blockSize / bytesPerSector); + if (sectorsPerAllocationUnit == 0) { + sectorsPerAllocationUnit = 1; + bytesPerSector = static_cast(std::min(blockSize, std::numeric_limits::max())); + } + return {sectorsPerAllocationUnit, bytesPerSector}; +} + } // namespace namespace kernel32 { @@ -554,6 +637,270 @@ NTSTATUS WINAPI NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoSta return status; } +NTSTATUS WINAPI NtQueryVolumeInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation, + ULONG Length, FS_INFORMATION_CLASS FsInformationClass) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("NtQueryVolumeInformationFile(%p, %p, %p, %u, %u) ", FileHandle, IoStatusBlock, FsInformation, Length, + static_cast(FsInformationClass)); + + if (!IoStatusBlock) { + DEBUG_LOG("-> 0x%x\n", STATUS_ACCESS_VIOLATION); + return STATUS_ACCESS_VIOLATION; + } + IoStatusBlock->Information = 0; + + if (Length != 0 && !FsInformation) { + IoStatusBlock->Status = STATUS_ACCESS_VIOLATION; + DEBUG_LOG("-> 0x%x\n", STATUS_ACCESS_VIOLATION); + return STATUS_ACCESS_VIOLATION; + } + + if (reinterpret_cast(FileHandle) < 0) { + IoStatusBlock->Status = STATUS_OBJECT_TYPE_MISMATCH; + DEBUG_LOG("-> 0x%x\n", STATUS_OBJECT_TYPE_MISMATCH); + return STATUS_OBJECT_TYPE_MISMATCH; + } + + auto obj = wibo::handles().getAs(FileHandle); + if (!obj || !obj->valid()) { + IoStatusBlock->Status = STATUS_INVALID_HANDLE; + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_HANDLE); + return STATUS_INVALID_HANDLE; + } + + NTSTATUS status = STATUS_SUCCESS; + switch (FsInformationClass) { + case FileFsVolumeInformation: { + constexpr size_t fixedSize = offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel); + if (Length < fixedSize) { + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + auto info = reinterpret_cast(FsInformation); + info->VolumeCreationTime.QuadPart = 0; + info->VolumeSerialNumber = kDefaultVolumeSerialNumber; + info->VolumeLabelLength = 0; + info->SupportsObjects = FALSE; + info->Reserved = 0; + IoStatusBlock->Information = fixedSize; + break; + } + case FileFsSizeInformation: { + if (Length < sizeof(FILE_FS_SIZE_INFORMATION)) { + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + struct statvfs st{}; + StatFetchResult statRes = fetchStatvfs(obj.get(), st); + if (!statRes.ok) { + status = wibo::statusFromErrno(statRes.err != 0 ? statRes.err : EINVAL); + break; + } + auto [sectorsPerAllocationUnit, bytesPerSector] = volumeSectorGeometry(st); + auto info = reinterpret_cast(FsInformation); + info->TotalAllocationUnits.QuadPart = static_cast(st.f_blocks); + info->AvailableAllocationUnits.QuadPart = static_cast(st.f_bavail); + info->SectorsPerAllocationUnit = sectorsPerAllocationUnit; + info->BytesPerSector = bytesPerSector; + IoStatusBlock->Information = sizeof(FILE_FS_SIZE_INFORMATION); + break; + } + case FileFsDeviceInformation: { + if (Length < sizeof(FILE_FS_DEVICE_INFORMATION)) { + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + auto info = reinterpret_cast(FsInformation); + info->DeviceType = kFileDeviceDisk; + info->Characteristics = 0; + IoStatusBlock->Information = sizeof(FILE_FS_DEVICE_INFORMATION); + break; + } + case FileFsAttributeInformation: { + // Cygwin mostly needs stable capability bits and a plausible FS name; + // match the existing GetVolumeInformation* shim's NTFS personality. + const char *fsName = "NTFS"; + auto wideName = stringToWideString(fsName); + ULONG nameBytes = static_cast((wideName.size() > 0 ? wideName.size() - 1 : 0) * sizeof(WCHAR)); + size_t fixedSize = offsetof(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName); + if (Length < fixedSize + nameBytes) { + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + auto info = reinterpret_cast(FsInformation); + info->FileSystemAttributes = kFileCaseSensitiveSearch | kFileCasePreservedNames | kFileUnicodeOnDisk; + info->MaximumComponentNameLength = kDefaultMaximumComponentNameLength; + info->FileSystemNameLength = nameBytes; + if (nameBytes > 0) { + std::memcpy(info->FileSystemName, wideName.data(), nameBytes); + } + IoStatusBlock->Information = fixedSize + nameBytes; + break; + } + case FileFsFullSizeInformation: { + if (Length < sizeof(FILE_FS_FULL_SIZE_INFORMATION)) { + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + struct statvfs st{}; + StatFetchResult statRes = fetchStatvfs(obj.get(), st); + if (!statRes.ok) { + status = wibo::statusFromErrno(statRes.err != 0 ? statRes.err : EINVAL); + break; + } + auto [sectorsPerAllocationUnit, bytesPerSector] = volumeSectorGeometry(st); + auto info = reinterpret_cast(FsInformation); + info->TotalAllocationUnits.QuadPart = static_cast(st.f_blocks); + info->CallerAvailableAllocationUnits.QuadPart = static_cast(st.f_bavail); + info->ActualAvailableAllocationUnits.QuadPart = static_cast(st.f_bfree); + info->SectorsPerAllocationUnit = sectorsPerAllocationUnit; + info->BytesPerSector = bytesPerSector; + IoStatusBlock->Information = sizeof(FILE_FS_FULL_SIZE_INFORMATION); + break; + } + default: + DEBUG_LOG("FIXME: NtQueryVolumeInformationFile: Unsupported info class"); + status = STATUS_INVALID_INFO_CLASS; + break; + } + + IoStatusBlock->Status = status; + DEBUG_LOG("-> 0x%x\n", status); + return status; +} + +NTSTATUS WINAPI NtCreateFile(PHANDLE FileHandle, DWORD DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, + ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, + ULONG EaLength) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("NtCreateFile(%p, 0x%x, %p, %p, %p, 0x%x, 0x%x, %u, 0x%x, %p, %u) ", FileHandle, DesiredAccess, + ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, + CreateOptions, EaBuffer, EaLength); + (void)AllocationSize; + (void)EaBuffer; + (void)EaLength; + + if (!FileHandle || !ObjectAttributes || !IoStatusBlock) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + auto *objectName = fromGuestPtr(ObjectAttributes->ObjectName); + std::string path = ntObjectPathToWin32(objectName); + if (path.empty()) { + DEBUG_LOG("-> 0x%x (empty path)\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + if (ObjectAttributes->RootDirectory && !ntPathIsRooted(path)) { + auto root = wibo::handles().getAs(ObjectAttributes->RootDirectory); + if (!root || root->canonicalPath.empty()) { + DEBUG_LOG("-> 0x%x (bad RootDirectory)\n", STATUS_INVALID_HANDLE); + return STATUS_INVALID_HANDLE; + } + std::filesystem::path relative = files::pathFromWindows(path.c_str()); + path = files::pathToWindows(root->canonicalPath / relative); + } + + DWORD winDisposition = 0; + switch (CreateDisposition) { + case 0: // FILE_SUPERSEDE + case 5: // FILE_OVERWRITE_IF + winDisposition = CREATE_ALWAYS; + break; + case 1: // FILE_OPEN + winDisposition = OPEN_EXISTING; + break; + case 2: // FILE_CREATE + winDisposition = CREATE_NEW; + break; + case 3: // FILE_OPEN_IF + winDisposition = OPEN_ALWAYS; + break; + case 4: // FILE_OVERWRITE + winDisposition = TRUNCATE_EXISTING; + break; + default: + DEBUG_LOG("-> 0x%x (bad disposition)\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + + constexpr ULONG FILE_DIRECTORY_FILE = 0x00000001; + constexpr ULONG FILE_DELETE_ON_CLOSE = 0x00001000; + + DWORD flagsAndAttributes = FileAttributes ? FileAttributes : FILE_ATTRIBUTE_NORMAL; + if (CreateOptions & FILE_DIRECTORY_FILE) { + flagsAndAttributes |= FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS; + } + if (CreateOptions & FILE_DELETE_ON_CLOSE) { + flagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE; + } + + HANDLE handle = + kernel32::CreateFileA(path.c_str(), DesiredAccess, ShareAccess, nullptr, winDisposition, flagsAndAttributes, 0); + if (handle == INVALID_HANDLE_VALUE) { + NTSTATUS status = statusFromLastError(); + IoStatusBlock->Status = status; + IoStatusBlock->Information = 0; + DEBUG_LOG("-> 0x%x\n", status); + return status; + } + + *FileHandle = handle; + IoStatusBlock->Status = STATUS_SUCCESS; + IoStatusBlock->Information = 0; + DEBUG_LOG("-> 0x%x, handle=%p\n", STATUS_SUCCESS, handle); + return STATUS_SUCCESS; +} + +NTSTATUS WINAPI NtQueryObject(HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass, PVOID ObjectInformation, + ULONG ObjectInformationLength, PULONG ReturnLength) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("NtQueryObject(%p, %u, %p, %u, %p) ", Handle, static_cast(ObjectInformationClass), + ObjectInformation, ObjectInformationLength, ReturnLength); + + if (ReturnLength) { + *ReturnLength = 0; + } + if (ObjectInformationClass != ObjectNameInformation) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_INFO_CLASS); + return STATUS_INVALID_INFO_CLASS; + } + + auto obj = wibo::handles().getAs(Handle); + if (!obj || !obj->valid()) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_HANDLE); + return STATUS_INVALID_HANDLE; + } + if (obj->canonicalPath.empty()) { + DEBUG_LOG("-> 0x%x\n", STATUS_OBJECT_NAME_NOT_FOUND); + return STATUS_OBJECT_NAME_NOT_FOUND; + } + + std::string ntName = "\\??\\" + files::pathToWindows(obj->canonicalPath); + auto wideName = stringToWideString(ntName.c_str()); + ULONG nameBytes = static_cast((wideName.size() > 0 ? wideName.size() - 1 : 0) * sizeof(WCHAR)); + ULONG required = static_cast(sizeof(UNICODE_STRING) + nameBytes + sizeof(WCHAR)); + if (ReturnLength) { + *ReturnLength = required; + } + if (!ObjectInformation || ObjectInformationLength < required) { + DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH); + return STATUS_INFO_LENGTH_MISMATCH; + } + + auto *info = reinterpret_cast(ObjectInformation); + auto *buffer = reinterpret_cast(reinterpret_cast(ObjectInformation) + sizeof(UNICODE_STRING)); + info->Length = static_cast(nameBytes); + info->MaximumLength = static_cast(nameBytes + sizeof(WCHAR)); + info->Buffer = toGuestPtr(buffer); + if (nameBytes > 0) { + std::memcpy(buffer, wideName.data(), nameBytes); + } + buffer[nameBytes / sizeof(WCHAR)] = 0; + DEBUG_LOG("-> 0x%x (%s)\n", STATUS_SUCCESS, ntName.c_str()); + return STATUS_SUCCESS; +} + NTSTATUS WINAPI NtQuerySystemTime(PLARGE_INTEGER SystemTime) { HOST_CONTEXT_GUARD(); DEBUG_LOG("NtQuerySystemTime(%p) ", SystemTime); @@ -726,6 +1073,93 @@ NTSTATUS WINAPI RtlGetVersion(PRTL_OSVERSIONINFOW lpVersionInformation) { return STATUS_SUCCESS; } +ULONG WINAPI RtlIsDosDeviceName_U(PWSTR DeviceName) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("RtlIsDosDeviceName_U(%p) ", DeviceName); + if (!DeviceName) { + DEBUG_LOG("-> 0\n"); + return 0; + } + + auto asciiUpper = [](WCHAR ch) -> char { + if (ch >= u'a' && ch <= u'z') { + return static_cast(ch - u'a' + u'A'); + } + if (ch <= 0x7f) { + return static_cast(ch); + } + return '\0'; + }; + + size_t length = 0; + while (DeviceName[length]) { + ++length; + } + if (length == 0) { + DEBUG_LOG("-> 0\n"); + return 0; + } + + size_t start = 0; + for (size_t i = 0; i < length; ++i) { + WCHAR ch = DeviceName[i]; + if (ch == u'\\' || ch == u'/' || ch == u':') { + start = i + 1; + } + } + + size_t end = length; + while (end > start && DeviceName[end - 1] == u' ') { + --end; + } + size_t dot = start; + while (dot < end && DeviceName[dot] != u'.') { + ++dot; + } + end = dot; + + size_t componentLength = end - start; + bool match = false; + if (componentLength == 3) { + char a = asciiUpper(DeviceName[start]); + char b = asciiUpper(DeviceName[start + 1]); + char c = asciiUpper(DeviceName[start + 2]); + match = (a == 'C' && b == 'O' && c == 'N') || (a == 'P' && b == 'R' && c == 'N') || + (a == 'A' && b == 'U' && c == 'X') || (a == 'N' && b == 'U' && c == 'L'); + } else if (componentLength == 4) { + char a = asciiUpper(DeviceName[start]); + char b = asciiUpper(DeviceName[start + 1]); + char c = asciiUpper(DeviceName[start + 2]); + WCHAR d = DeviceName[start + 3]; + match = ((a == 'C' && b == 'O' && c == 'M') || (a == 'L' && b == 'P' && c == 'T')) && d >= u'1' && d <= u'9'; + } else if (componentLength == 6) { + match = asciiUpper(DeviceName[start]) == 'C' && asciiUpper(DeviceName[start + 1]) == 'O' && + asciiUpper(DeviceName[start + 2]) == 'N' && asciiUpper(DeviceName[start + 3]) == 'I' && + asciiUpper(DeviceName[start + 4]) == 'N' && DeviceName[start + 5] == u'$'; + } else if (componentLength == 7) { + match = asciiUpper(DeviceName[start]) == 'C' && asciiUpper(DeviceName[start + 1]) == 'O' && + asciiUpper(DeviceName[start + 2]) == 'N' && asciiUpper(DeviceName[start + 3]) == 'O' && + asciiUpper(DeviceName[start + 4]) == 'U' && asciiUpper(DeviceName[start + 5]) == 'T' && + DeviceName[start + 6] == u'$'; + } + + if (!match) { + DEBUG_LOG("-> 0\n"); + return 0; + } + ULONG result = + static_cast((componentLength * sizeof(WCHAR)) << 16) | static_cast(start * sizeof(WCHAR)); + DEBUG_LOG("-> 0x%x\n", result); + return result; +} + +ULONG WINAPI RtlNtStatusToDosError(NTSTATUS Status) { + HOST_CONTEXT_GUARD(); + ULONG result = wibo::winErrorFromNtStatus(Status); + DEBUG_LOG("RtlNtStatusToDosError(0x%x) -> %u\n", Status, result); + return result; +} + NTSTATUS WINAPI NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength) { diff --git a/dll/ntdll.h b/dll/ntdll.h index 86fbe60..81ecc10 100644 --- a/dll/ntdll.h +++ b/dll/ntdll.h @@ -18,6 +18,74 @@ enum PROCESSINFOCLASS { ProcessImageFileName = 27, }; +enum FS_INFORMATION_CLASS { + FileFsVolumeInformation = 1, + FileFsLabelInformation = 2, + FileFsSizeInformation = 3, + FileFsDeviceInformation = 4, + FileFsAttributeInformation = 5, + FileFsControlInformation = 6, + FileFsFullSizeInformation = 7, +}; + +enum OBJECT_INFORMATION_CLASS { + ObjectBasicInformation = 0, + ObjectNameInformation = 1, + ObjectTypeInformation = 2, +}; + +struct OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + GUEST_PTR ObjectName; + ULONG Attributes; + GUEST_PTR SecurityDescriptor; + GUEST_PTR SecurityQualityOfService; +}; + +using POBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES *; + +struct FILE_FS_VOLUME_INFORMATION { + LARGE_INTEGER VolumeCreationTime; + ULONG VolumeSerialNumber; + ULONG VolumeLabelLength; + BOOLEAN SupportsObjects; + BOOLEAN Reserved; + WCHAR VolumeLabel[1]; +}; +using PFILE_FS_VOLUME_INFORMATION = FILE_FS_VOLUME_INFORMATION *; + +struct FILE_FS_SIZE_INFORMATION { + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER AvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +}; +using PFILE_FS_SIZE_INFORMATION = FILE_FS_SIZE_INFORMATION *; + +struct FILE_FS_DEVICE_INFORMATION { + ULONG DeviceType; + ULONG Characteristics; +}; +using PFILE_FS_DEVICE_INFORMATION = FILE_FS_DEVICE_INFORMATION *; + +struct FILE_FS_ATTRIBUTE_INFORMATION { + ULONG FileSystemAttributes; + ULONG MaximumComponentNameLength; + ULONG FileSystemNameLength; + WCHAR FileSystemName[1]; +}; +using PFILE_FS_ATTRIBUTE_INFORMATION = FILE_FS_ATTRIBUTE_INFORMATION *; + +struct FILE_FS_FULL_SIZE_INFORMATION { + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER CallerAvailableAllocationUnits; + LARGE_INTEGER ActualAvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +}; +using PFILE_FS_FULL_SIZE_INFORMATION = FILE_FS_FULL_SIZE_INFORMATION *; + struct RTL_OSVERSIONINFOW { ULONG dwOSVersionInfoSize; ULONG dwMajorVersion; @@ -44,6 +112,14 @@ NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, guest_ptr<> *BaseAd ULONG NewAccessProtection, PULONG OldAccessProtection); NTSTATUS WINAPI NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); +NTSTATUS WINAPI NtQueryVolumeInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation, + ULONG Length, FS_INFORMATION_CLASS FsInformationClass); +NTSTATUS WINAPI NtCreateFile(PHANDLE FileHandle, DWORD DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, + ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, + ULONG EaLength); +NTSTATUS WINAPI NtQueryObject(HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass, PVOID ObjectInformation, + ULONG ObjectInformationLength, PULONG ReturnLength); NTSTATUS WINAPI NtQuerySystemTime(PLARGE_INTEGER SystemTime); BOOLEAN WINAPI RtlTimeToSecondsSince1970(PLARGE_INTEGER Time, PULONG ElapsedSeconds); VOID WINAPI RtlInitializeBitMap(PRTL_BITMAP BitMapHeader, PULONG BitMapBuffer, ULONG SizeOfBitMap); @@ -51,6 +127,8 @@ VOID WINAPI RtlSetBits(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Numb BOOLEAN WINAPI RtlAreBitsSet(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Length); BOOLEAN WINAPI RtlAreBitsClear(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Length); NTSTATUS WINAPI RtlGetVersion(PRTL_OSVERSIONINFOW lpVersionInformation); +ULONG WINAPI RtlIsDosDeviceName_U(PWSTR DeviceName); +ULONG WINAPI RtlNtStatusToDosError(NTSTATUS Status); NTSTATUS WINAPI NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); diff --git a/src/errors.cpp b/src/errors.cpp index b442612..09c5b61 100644 --- a/src/errors.cpp +++ b/src/errors.cpp @@ -42,6 +42,10 @@ NTSTATUS statusFromWinError(DWORD error) { return STATUS_SUCCESS; case ERROR_INVALID_HANDLE: return STATUS_INVALID_HANDLE; + case ERROR_FILE_NOT_FOUND: + return STATUS_OBJECT_NAME_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return STATUS_OBJECT_PATH_NOT_FOUND; case ERROR_INVALID_PARAMETER: return STATUS_INVALID_PARAMETER; case ERROR_HANDLE_EOF: @@ -65,6 +69,11 @@ DWORD winErrorFromNtStatus(NTSTATUS status) { return ERROR_HANDLE_EOF; case STATUS_INVALID_HANDLE: return ERROR_INVALID_HANDLE; + case STATUS_NO_SUCH_FILE: + case STATUS_OBJECT_NAME_NOT_FOUND: + return ERROR_FILE_NOT_FOUND; + case STATUS_OBJECT_PATH_NOT_FOUND: + return ERROR_PATH_NOT_FOUND; case STATUS_INVALID_PARAMETER: return ERROR_INVALID_PARAMETER; case STATUS_PIPE_BROKEN: diff --git a/src/errors.h b/src/errors.h index 422db2d..bcaf601 100644 --- a/src/errors.h +++ b/src/errors.h @@ -60,6 +60,9 @@ #define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002) #define STATUS_ACCESS_VIOLATION ((NTSTATUS)0xC0000005) #define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011) +#define STATUS_NO_SUCH_FILE ((NTSTATUS)0xC000000F) +#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034) +#define STATUS_OBJECT_PATH_NOT_FOUND ((NTSTATUS)0xC000003A) #define STATUS_OBJECT_TYPE_MISMATCH ((NTSTATUS)0xC0000024) #define STATUS_PENDING ((NTSTATUS)0x00000103) #define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BB) diff --git a/src/loader.cpp b/src/loader.cpp index ea6e69c..4051386 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -620,7 +620,7 @@ bool wibo::Executable::loadPE(std::span image, bool exec) { return loadPEFromSource(*this, PeInputView(image), exec); } -bool wibo::Executable::resolveImports() { +bool wibo::Executable::resolveImports(bool staticAttach) { auto finalizeSections = [this]() -> bool { if (!execMapped || sectionsProtected) { return true; @@ -681,7 +681,7 @@ bool wibo::Executable::resolveImports() { uint32_t *lookupTable = fromRVA(dir->importLookupTable); uint32_t *addressTable = fromRVA(dir->importAddressTable); - ModuleInfo *module = loadModule(dllName); + ModuleInfo *module = loadModuleForImport(dllName, staticAttach); if (!module && kernel32::getLastError() != ERROR_MOD_NOT_FOUND) { DEBUG_LOG("Failed to load import module %s\n", dllName); // lastError is set by loadModule diff --git a/src/main.cpp b/src/main.cpp index bccce60..0effb3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -461,7 +461,7 @@ int main(int argc, char **argv) { DEBUG_LOG("Registered main module %s at %p\n", wibo::mainModule->normalizedName.c_str(), wibo::mainModule->executable->imageBase); - if (!wibo::mainModule->executable->resolveImports()) { + if (!wibo::mainModule->executable->resolveImports(true)) { fprintf(stderr, "Failed to resolve imports for main module (DLL initialization failure?)\n"); abort(); } diff --git a/src/modules.cpp b/src/modules.cpp index 5c930d2..abcd144 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -305,6 +305,18 @@ std::string normalizedBaseKey(const ParsedModuleName &parsed) { return normalizeAlias(base); } +std::string_view resolveApiSetAlias(std::string_view requested) { + const auto parsed = parseModuleName(requested); + std::string normalized = normalizedBaseKey(parsed); + for (auto &[alias, module] : kApiSet) { + if (alias == normalized) { + DEBUG_LOG(" resolved api set %s -> %s\n", alias.data(), module.data()); + return module; + } + } + return requested; +} + struct ImageTlsDirectory32 { uint32_t StartAddressOfRawData; uint32_t EndAddressOfRawData; @@ -768,7 +780,7 @@ void ensureExportsInitialized(wibo::ModuleInfo &info) { info.exportsInitialized = true; } -bool ensureModuleReady(wibo::ModuleInfo &info) { +bool ensureModuleReady(wibo::ModuleInfo &info, bool staticAttach) { if (info.moduleStub && !info.moduleStub->dllData.empty() && !info.executable) { DEBUG_LOG("registerBuiltinModule: loading PE for %s\n", info.originalName.c_str()); auto executable = std::make_unique(); @@ -782,13 +794,16 @@ bool ensureModuleReady(wibo::ModuleInfo &info) { if (!info.executable) { return true; } - if (!info.executable->resolveImports()) { + if (!info.executable->resolveImports(staticAttach)) { return false; } if (!wibo::initializeModuleTls(info)) { return false; } - if (!callDllMain(info, DLL_PROCESS_ATTACH, nullptr)) { + // Windows passes a non-null reserved value for process startup loads. + // Cygwin uses this to distinguish static imports from runtime LoadLibrary. + LPVOID reserved = staticAttach ? reinterpret_cast(1) : nullptr; + if (!callDllMain(info, DLL_PROCESS_ATTACH, reserved)) { kernel32::setLastError(ERROR_DLL_INIT_FAILED); return false; } @@ -1161,7 +1176,7 @@ ModuleInfo *findLoadedModule(const char *name) { return info; } -static ModuleInfo *loadModuleInternal(const std::string &dllName) { +static ModuleInfo *loadModuleInternal(const std::string &dllName, bool staticAttach) { auto reg = registry(); ParsedModuleName parsed = parseModuleName(dllName); DWORD diskError = ERROR_SUCCESS; @@ -1216,7 +1231,7 @@ static ModuleInfo *loadModuleInternal(const std::string &dllName) { if (raw->executable->isDll) { reg.lock.unlock(); ensureExportsInitialized(*raw); - if (!raw->executable->resolveImports()) { + if (!raw->executable->resolveImports(staticAttach)) { DEBUG_LOG(" resolveImports failed for %s\n", raw->originalName.c_str()); reg.lock.lock(); reg->modulesByKey.erase(key); @@ -1231,7 +1246,8 @@ static ModuleInfo *loadModuleInternal(const std::string &dllName) { return nullptr; } reg.lock.lock(); - if (!callDllMain(*raw, DLL_PROCESS_ATTACH, nullptr)) { + LPVOID reserved = staticAttach ? reinterpret_cast(1) : nullptr; + if (!callDllMain(*raw, DLL_PROCESS_ATTACH, reserved)) { DEBUG_LOG(" DllMain failed for %s\n", raw->originalName.c_str()); releaseModuleTls(*raw); // runPendingOnExit(*raw); @@ -1289,7 +1305,7 @@ static ModuleInfo *loadModuleInternal(const std::string &dllName) { DEBUG_LOG(" returning builtin module %s\n", existing->originalName.c_str()); ModuleInfo *builtin = existing; reg.lock.unlock(); - if (!ensureModuleReady(*builtin)) { + if (!ensureModuleReady(*builtin, staticAttach)) { return nullptr; } return builtin; @@ -1318,7 +1334,7 @@ static ModuleInfo *loadModuleInternal(const std::string &dllName) { if (builtin && builtin->moduleStub != nullptr) { DEBUG_LOG(" falling back to builtin module %s\n", builtin->originalName.c_str()); reg.lock.unlock(); - if (!ensureModuleReady(*builtin)) { + if (!ensureModuleReady(*builtin, staticAttach)) { return nullptr; } return builtin; @@ -1335,21 +1351,10 @@ ModuleInfo *loadModule(const char *dllName) { } DEBUG_LOG("loadModule(%s)\n", dllName); std::string_view requested{dllName}; - - const auto parsed = parseModuleName(requested); - std::string normalized = normalizedBaseKey(parsed); - - for (auto &[alias, module] : kApiSet) { - if (alias == normalized) { - DEBUG_LOG(" resolved api set %s -> %s\n", alias.data(), module.data()); - requested = module; - normalized = module; - break; - } - } + requested = resolveApiSetAlias(requested); // DWORD lastError = kernel32::getLastError(); - if (auto *info = loadModuleInternal(std::string{requested})) { + if (auto *info = loadModuleInternal(std::string{requested}, false)) { return info; } if (kernel32::getLastError() != ERROR_MOD_NOT_FOUND) { @@ -1360,7 +1365,7 @@ ModuleInfo *loadModule(const char *dllName) { // for (auto &[module, fallback] : kFallbacks) { // if (module == normalized) { // DEBUG_LOG(" trying fallback %s -> %s\n", module.data(), fallback.data()); - // return loadModuleInternal(std::string{fallback}); + // return loadModuleInternal(std::string{fallback}, false); // } // } @@ -1368,6 +1373,16 @@ ModuleInfo *loadModule(const char *dllName) { return nullptr; } +ModuleInfo *loadModuleForImport(const char *dllName, bool staticAttach) { + if (!dllName || *dllName == '\0') { + kernel32::setLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + std::string_view requested{dllName}; + requested = resolveApiSetAlias(requested); + return loadModuleInternal(std::string{requested}, staticAttach); +} + void freeModule(ModuleInfo *info) { auto reg = registry(); if (!info || info->refCount == UINT_MAX) { diff --git a/src/modules.h b/src/modules.h index ca3c0fe..bc77652 100644 --- a/src/modules.h +++ b/src/modules.h @@ -38,7 +38,7 @@ class Executable { bool loadPE(FILE *file, bool exec); bool loadPE(std::span image, bool exec); - bool resolveImports(); + bool resolveImports(bool staticAttach = false); bool findResource(const ResourceIdentifier &type, const ResourceIdentifier &name, std::optional language, ResourceLocation &out) const; @@ -127,6 +127,7 @@ bool initializeModuleTls(ModuleInfo &module); void releaseModuleTls(ModuleInfo &module); ModuleInfo *loadModule(const char *name); +ModuleInfo *loadModuleForImport(const char *name, bool staticAttach); void freeModule(ModuleInfo *info); void *resolveFuncByName(ModuleInfo *info, const char *funcName); void *resolveFuncByOrdinal(ModuleInfo *info, uint16_t ordinal); From 83002366ef9428214749d1aeec490a235004f72c Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 6 Jun 2026 09:09:59 +0100 Subject: [PATCH 2/3] Fixup stderr redirection --- dll/kernel32/fileapi.cpp | 24 ++++++++++++++++++++++++ dll/kernel32/wincon.cpp | 26 +++++++++++++------------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index 99291bd..fad10f6 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -539,11 +539,35 @@ std::optional stdHandleForConsoleDevice(const std::string &name, DWORD de return std::nullopt; } +bool isConsoleOutputDevice(const std::string &name, DWORD desiredAccess) { + std::string lowered = stringToLower(name); + return lowered == "conout$" || (lowered == "con" && (desiredAccess & GENERIC_WRITE) != 0); +} + +bool tryOpenHostConsoleOutput(DWORD desiredAccess, HANDLE &outHandle) { + int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC); + if (fd < 0) { + return false; + } + + auto fileObj = make_pin(fd); + fileObj->appendOnly = true; + uint32_t grantedAccess = FILE_GENERIC_WRITE; + if ((desiredAccess & GENERIC_READ) != 0) { + grantedAccess |= FILE_GENERIC_READ; + } + outHandle = wibo::handles().alloc(std::move(fileObj), grantedAccess, 0); + return true; +} + bool tryOpenConsoleDevice(DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE &outHandle, const std::string &originalName) { (void)dwShareMode; (void)dwCreationDisposition; (void)dwFlagsAndAttributes; + if (isConsoleOutputDevice(originalName, dwDesiredAccess) && tryOpenHostConsoleOutput(dwDesiredAccess, outHandle)) { + return true; + } auto stdHandleKind = stdHandleForConsoleDevice(originalName, dwDesiredAccess); if (!stdHandleKind) { return false; diff --git a/dll/kernel32/wincon.cpp b/dll/kernel32/wincon.cpp index 54a599f..09ef1ef 100644 --- a/dll/kernel32/wincon.cpp +++ b/dll/kernel32/wincon.cpp @@ -133,21 +133,21 @@ BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumber setLastError(ERROR_INVALID_HANDLE); return FALSE; } - if (file->fd == STDOUT_FILENO || file->fd == STDERR_FILENO) { - auto str = wideStringToString(lpBuffer, static_cast(nNumberOfCharsToWrite)); - auto io = files::write(file.get(), str.c_str(), str.size(), std::nullopt, true); - if (lpNumberOfCharsWritten) { - *lpNumberOfCharsWritten = io.bytesTransferred; - } - if (io.unixError != 0) { - setLastError(wibo::winErrorFromErrno(io.unixError)); - return FALSE; - } - return TRUE; + if (!isatty(file->fd)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; } - setLastError(ERROR_INVALID_HANDLE); - return FALSE; + auto str = wideStringToString(lpBuffer, static_cast(nNumberOfCharsToWrite)); + auto io = files::write(file.get(), str.c_str(), str.size(), std::nullopt, true); + if (io.unixError != 0) { + setLastError(wibo::winErrorFromErrno(io.unixError)); + return FALSE; + } + if (lpNumberOfCharsWritten) { + *lpNumberOfCharsWritten = nNumberOfCharsToWrite; + } + return TRUE; } BOOL WINAPI GetNumberOfConsoleInputEvents(HANDLE hConsoleInput, LPDWORD lpNumberOfEvents) { From 8ac2509884f9f493ac7cfa8867dc55220d2a1ce1 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Sat, 6 Jun 2026 09:23:13 +0100 Subject: [PATCH 3/3] fix WIBO_DEBUG=1 segfaulting --- CMakeLists.txt | 4 ++ dll/psapi.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ dll/psapi.h | 36 ++++++++++++++ dll/winmm.cpp | 46 +++++++++++++++++ dll/winmm.h | 19 +++++++ src/modules.cpp | 8 +-- 6 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 dll/psapi.cpp create mode 100644 dll/psapi.h create mode 100644 dll/winmm.cpp create mode 100644 dll/winmm.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 50cae49..962300f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,11 +220,13 @@ add_executable(wibo dll/mscoree.cpp dll/ntdll.cpp dll/ole32.cpp + dll/psapi.cpp dll/rpcrt4.cpp dll/shlwapi.cpp dll/user32.cpp dll/vcruntime.cpp dll/version.cpp + dll/winmm.cpp dll/ws2.cpp src/access.cpp src/async_io.cpp @@ -376,8 +378,10 @@ wibo_codegen_module(NAME bcrypt HEADERS dll/bcrypt.h) wibo_codegen_module(NAME entry HEADERS src/entry.h) wibo_codegen_module(NAME mscoree HEADERS dll/mscoree.h) wibo_codegen_module(NAME version HEADERS dll/version.h) +wibo_codegen_module(NAME psapi HEADERS dll/psapi.h) wibo_codegen_module(NAME rpcrt4 HEADERS dll/rpcrt4.h) wibo_codegen_module(NAME shlwapi HEADERS dll/shlwapi.h) +wibo_codegen_module(NAME winmm HEADERS dll/winmm.h) wibo_codegen_module(NAME ws2 HEADERS dll/ws2.h) wibo_codegen_module(NAME vcruntime HEADERS dll/vcruntime.h) wibo_codegen_module(NAME lmgr HEADERS dll/lmgr.h) diff --git a/dll/psapi.cpp b/dll/psapi.cpp new file mode 100644 index 0000000..8fccac4 --- /dev/null +++ b/dll/psapi.cpp @@ -0,0 +1,129 @@ +#include "psapi.h" + +#include "common.h" +#include "context.h" +#include "errors.h" +#include "files.h" +#include "kernel32/internal.h" +#include "modules.h" + +#include +#include +#include +#include + +namespace { + +bool isCurrentProcessHandle(HANDLE hProcess) { return kernel32::isPseudoCurrentProcessHandle(hProcess); } + +wibo::ModuleInfo *moduleForHandleOrMain(HMODULE hModule) { + if (hModule == NO_HANDLE) { + return wibo::mainModule; + } + return wibo::moduleInfoFromHandle(hModule); +} + +} // namespace + +namespace psapi { + +BOOL WINAPI EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("EnumProcessModules(%p, %p, %u, %p)\n", hProcess, lphModule, cb, lpcbNeeded); + if (!isCurrentProcessHandle(hProcess) || !lpcbNeeded) { + kernel32::setLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + *lpcbNeeded = sizeof(HMODULE); + if (lphModule && cb >= sizeof(HMODULE)) { + *lphModule = wibo::mainModule ? wibo::mainModule->handle : NO_HANDLE; + } + return TRUE; +} + +DWORD WINAPI GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetModuleFileNameExA(%p, %p, %p, %u)\n", hProcess, hModule, lpFilename, nSize); + if (!isCurrentProcessHandle(hProcess) || !lpFilename || nSize == 0) { + kernel32::setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + auto *module = moduleForHandleOrMain(hModule); + if (!module) { + kernel32::setLastError(ERROR_INVALID_HANDLE); + return 0; + } + std::string path = + !module->resolvedPath.empty() ? files::pathToWindows(module->resolvedPath) : module->originalName; + size_t copyLen = std::min(path.size(), static_cast(nSize - 1)); + std::memcpy(lpFilename, path.c_str(), copyLen); + lpFilename[copyLen] = '\0'; + if (copyLen < path.size()) { + kernel32::setLastError(ERROR_INSUFFICIENT_BUFFER); + return nSize; + } + return static_cast(copyLen); +} + +BOOL WINAPI GetModuleInformation(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetModuleInformation(%p, %p, %p, %u)\n", hProcess, hModule, lpmodinfo, cb); + if (!isCurrentProcessHandle(hProcess) || !lpmodinfo || cb < sizeof(MODULEINFO)) { + kernel32::setLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + auto *module = moduleForHandleOrMain(hModule); + if (!module || !module->executable) { + kernel32::setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + lpmodinfo->lpBaseOfDll = module->executable->imageBase; + lpmodinfo->SizeOfImage = module->executable->imageSize; + lpmodinfo->EntryPoint = module->executable->entryPoint; + return TRUE; +} + +BOOL WINAPI GetProcessMemoryInfo(HANDLE Process, PPROCESS_MEMORY_COUNTERS ppsmemCounters, DWORD cb) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("GetProcessMemoryInfo(%p, %p, %u)\n", Process, ppsmemCounters, cb); + if (!isCurrentProcessHandle(Process) || !ppsmemCounters || cb < sizeof(PROCESS_MEMORY_COUNTERS)) { + kernel32::setLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + std::memset(ppsmemCounters, 0, sizeof(PROCESS_MEMORY_COUNTERS)); + ppsmemCounters->cb = sizeof(PROCESS_MEMORY_COUNTERS); + + struct rusage usage{}; + if (getrusage(RUSAGE_SELF, &usage) == 0) { + ppsmemCounters->PeakWorkingSetSize = static_cast(usage.ru_maxrss) * 1024; + ppsmemCounters->WorkingSetSize = ppsmemCounters->PeakWorkingSetSize; + ppsmemCounters->PageFaultCount = static_cast(usage.ru_minflt + usage.ru_majflt); + } + return TRUE; +} + +BOOL WINAPI QueryWorkingSet(HANDLE hProcess, PVOID pv, DWORD cb) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("QueryWorkingSet(%p, %p, %u)\n", hProcess, pv, cb); + if (!isCurrentProcessHandle(hProcess) || !pv || cb < sizeof(ULONG_PTR)) { + kernel32::setLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + *static_cast(pv) = 0; + return TRUE; +} + +} // namespace psapi + +#include "psapi_trampolines.h" + +extern const wibo::ModuleStub lib_psapi = { + (const char *[]){ + "psapi", + "psapi.dll", + nullptr, + }, + psapiThunkByName, + nullptr, + {}, +}; diff --git a/dll/psapi.h b/dll/psapi.h new file mode 100644 index 0000000..94037a1 --- /dev/null +++ b/dll/psapi.h @@ -0,0 +1,36 @@ +#pragma once + +#include "types.h" + +struct MODULEINFO { + LPVOID lpBaseOfDll; + DWORD SizeOfImage; + LPVOID EntryPoint; +}; + +using LPMODULEINFO = MODULEINFO *; + +struct PROCESS_MEMORY_COUNTERS { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +}; + +using PPROCESS_MEMORY_COUNTERS = PROCESS_MEMORY_COUNTERS *; + +namespace psapi { + +BOOL WINAPI EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded); +DWORD WINAPI GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize); +BOOL WINAPI GetModuleInformation(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb); +BOOL WINAPI GetProcessMemoryInfo(HANDLE Process, PPROCESS_MEMORY_COUNTERS ppsmemCounters, DWORD cb); +BOOL WINAPI QueryWorkingSet(HANDLE hProcess, PVOID pv, DWORD cb); + +} // namespace psapi diff --git a/dll/winmm.cpp b/dll/winmm.cpp new file mode 100644 index 0000000..ac574d9 --- /dev/null +++ b/dll/winmm.cpp @@ -0,0 +1,46 @@ +#include "winmm.h" + +#include "common.h" +#include "context.h" +#include "errors.h" +#include "modules.h" + +#include + +namespace winmm { + +DWORD WINAPI timeGetTime() { + HOST_CONTEXT_GUARD(); + struct timespec ts{}; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t milliseconds = static_cast(ts.tv_sec) * 1000ULL + static_cast(ts.tv_nsec) / 1000000ULL; + DWORD value = static_cast(milliseconds & 0xFFFFFFFFULL); + VERBOSE_LOG("timeGetTime() -> %u\n", value); + return value; +} + +MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("timeGetDevCaps(%p, %u)\n", ptc, cbtc); + if (!ptc || cbtc < sizeof(TIMECAPS)) { + return 11; // TIMERR_NOCANDO + } + ptc->wPeriodMin = 1; + ptc->wPeriodMax = 1000; + return 0; // TIMERR_NOERROR +} + +} // namespace winmm + +#include "winmm_trampolines.h" + +extern const wibo::ModuleStub lib_winmm = { + (const char *[]){ + "winmm", + "winmm.dll", + nullptr, + }, + winmmThunkByName, + nullptr, + {}, +}; diff --git a/dll/winmm.h b/dll/winmm.h new file mode 100644 index 0000000..853a3ab --- /dev/null +++ b/dll/winmm.h @@ -0,0 +1,19 @@ +#pragma once + +#include "types.h" + +using MMRESULT = UINT; + +struct TIMECAPS { + UINT wPeriodMin; + UINT wPeriodMax; +}; + +using LPTIMECAPS = TIMECAPS *; + +namespace winmm { + +DWORD WINAPI timeGetTime(); +MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc); + +} // namespace winmm diff --git a/src/modules.cpp b/src/modules.cpp index abcd144..4d3ed23 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -53,10 +53,12 @@ extern const wibo::ModuleStub lib_ucrtbase; extern const wibo::ModuleStub lib_ntdll; extern const wibo::ModuleStub lib_rpcrt4; extern const wibo::ModuleStub lib_ole32; +extern const wibo::ModuleStub lib_psapi; extern const wibo::ModuleStub lib_shlwapi; extern const wibo::ModuleStub lib_user32; extern const wibo::ModuleStub lib_vcruntime; extern const wibo::ModuleStub lib_version; +extern const wibo::ModuleStub lib_winmm; extern const wibo::ModuleStub lib_ws2; // setup.S @@ -203,9 +205,9 @@ LockedRegistry registry() { if (!reg.initialized) { reg.initialized = true; const wibo::ModuleStub *builtins[] = { - &lib_advapi32, &lib_bcrypt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_ntdll, - &lib_ole32, &lib_rpcrt4, &lib_shlwapi, &lib_user32, &lib_vcruntime, &lib_version, - &lib_ws2, + &lib_advapi32, &lib_bcrypt, &lib_kernel32, &lib_lmgr, &lib_mscoree, + &lib_ntdll, &lib_ole32, &lib_psapi, &lib_rpcrt4, &lib_shlwapi, + &lib_user32, &lib_vcruntime, &lib_version, &lib_winmm, &lib_ws2, #if WIBO_HAS_MSVCRT &lib_msvcrt, #endif