diff --git a/CMakeLists.txt b/CMakeLists.txt index a4a410a..fb998d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,6 +220,27 @@ else() embed_binary(dll/msvcrt.cpp "${MSVCRT_DLL}") endif() +find_program(WIBO_MINGW_CXX i686-w64-mingw32-g++) +if (WIBO_MINGW_CXX) + set(MSPDB_DLL ${CMAKE_CURRENT_BINARY_DIR}/vendor/mspdb80.dll) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/vendor) + add_custom_command(OUTPUT ${MSPDB_DLL} + COMMAND ${WIBO_MINGW_CXX} -shared -O2 -Wall -static + -fno-exceptions -fno-rtti + -o ${MSPDB_DLL} + ${CMAKE_CURRENT_SOURCE_DIR}/dll/mspdb/mspdb_dll.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dll/mspdb/mspdb.def + DEPENDS dll/mspdb/mspdb_dll.cpp dll/mspdb/mspdb.def) + add_custom_target(mspdb_dll DEPENDS ${MSPDB_DLL}) + add_dependencies(wibo mspdb_dll) + target_compile_definitions(wibo PRIVATE WIBO_HAS_MSPDB=1) + target_sources(wibo PRIVATE dll/mspdb_embed.cpp) + embed_binary(dll/mspdb_embed.cpp "${MSPDB_DLL}") +else() + message(WARNING "i686-w64-mingw32-g++ not found; mspdb DLL will not be built") + target_compile_definitions(wibo PRIVATE WIBO_HAS_MSPDB=0) +endif() + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_sources(wibo PRIVATE src/async_io_epoll.cpp @@ -478,6 +499,7 @@ if (WIBO_ENABLE_FIXTURE_TESTS) wibo_add_fixture_bin(NAME test_srw_lock SOURCES test/test_srw_lock.c) wibo_add_fixture_bin(NAME test_init_once SOURCES test/test_init_once.c) wibo_add_fixture_bin(NAME test_wait_on_address SOURCES test/test_wait_on_address.c COMPILE_OPTIONS -lsynchronization) + wibo_add_fixture_bin(NAME test_mspdb SOURCES test/test_mspdb.c) # DLLs for fixture tests wibo_add_fixture_dll(NAME external_exports SOURCES test/external_exports.c) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index a650a70..5504d57 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -928,8 +928,6 @@ HANDLE WINAPI CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShar return INVALID_HANDLE_VALUE; } - // TODO: verify share mode against existing opens - bool allowCreate = false; bool truncateExisting = false; bool existedBefore = pathExists; @@ -1036,9 +1034,9 @@ HANDLE WINAPI CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShar if (dwCreationDisposition == CREATE_NEW) { openFlags |= O_EXCL; } - if (truncateExisting && !isDirectory) { - openFlags |= O_TRUNC; - } + // Don't use O_TRUNC directly — we need to check for active memory mappings first + // (Windows prevents truncation of files with active mapped sections) + bool deferredTruncate = truncateExisting && !isDirectory; if (isDirectory) { openFlags |= O_RDONLY | O_DIRECTORY; @@ -1065,6 +1063,19 @@ HANDLE WINAPI CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShar return INVALID_HANDLE_VALUE; } + // Apply deferred truncation — skip if the file has active memory mappings (Windows behavior) + if (deferredTruncate) { + if (files::isFileMapped(fd)) { + DEBUG_LOG(" skipping truncation: file has active memory mapping\n"); + } else { + if (ftruncate(fd, 0) != 0) { + setLastErrorFromErrno(); + close(fd); + return INVALID_HANDLE_VALUE; + } + } + } + struct stat st{}; if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) { isDirectory = true; @@ -1609,8 +1620,6 @@ DWORD WINAPI GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lp return 0; } - DEBUG_LOG(" -> %s\n", info.path.c_str()); - auto widePath = stringToWideString(info.path.c_str()); const size_t wideLen = widePath.size(); const auto required = static_cast(wideLen); @@ -1836,6 +1845,45 @@ HANDLE WINAPI FindFirstFileExA(LPCSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelI return findFirstFileCommon(std::string(lpFileName), findData); } +HANDLE WINAPI FindFirstFileExW(LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("FindFirstFileExW(%p, %d, %p, %d, %p, 0x%x)\n", lpFileName, fInfoLevelId, + lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); + if (!lpFindFileData) { + setLastError(ERROR_INVALID_PARAMETER); + return INVALID_HANDLE_VALUE; + } + if (!lpFileName) { + setLastError(ERROR_PATH_NOT_FOUND); + return INVALID_HANDLE_VALUE; + } + if (fInfoLevelId != FindExInfoStandard) { + DEBUG_LOG(" -> ERROR_INVALID_PARAMETER (fInfoLevelId=%d)\n", fInfoLevelId); + setLastError(ERROR_INVALID_PARAMETER); + return INVALID_HANDLE_VALUE; + } + if (fSearchOp != FindExSearchNameMatch) { + DEBUG_LOG(" -> ERROR_INVALID_PARAMETER (fSearchOp=%d)\n", fSearchOp); + setLastError(ERROR_INVALID_PARAMETER); + return INVALID_HANDLE_VALUE; + } + if (lpSearchFilter) { + DEBUG_LOG(" -> ERROR_INVALID_PARAMETER (lpSearchFilter=%p)\n", lpSearchFilter); + setLastError(ERROR_INVALID_PARAMETER); + return INVALID_HANDLE_VALUE; + } + if (dwAdditionalFlags != 0) { + DEBUG_LOG(" -> ERROR_INVALID_PARAMETER (dwAdditionalFlags=0x%x)\n", dwAdditionalFlags); + setLastError(ERROR_INVALID_PARAMETER); + return INVALID_HANDLE_VALUE; + } + + std::string narrowName = wideStringToString(lpFileName); + auto *findData = static_cast(lpFindFileData); + return findFirstFileCommon(narrowName, findData); +} + BOOL WINAPI FindNextFileA(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData) { HOST_CONTEXT_GUARD(); DEBUG_LOG("FindNextFileA(%p, %p)\n", hFindFile, lpFindFileData); diff --git a/dll/kernel32/fileapi.h b/dll/kernel32/fileapi.h index a21afc1..a3cd1b7 100644 --- a/dll/kernel32/fileapi.h +++ b/dll/kernel32/fileapi.h @@ -66,6 +66,8 @@ HANDLE WINAPI FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileDat HANDLE WINAPI FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData); HANDLE WINAPI FindFirstFileExA(LPCSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags); +HANDLE WINAPI FindFirstFileExW(LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags); BOOL WINAPI FindNextFileA(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData); BOOL WINAPI FindNextFileW(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData); BOOL WINAPI FindClose(HANDLE hFindFile); diff --git a/dll/kernel32/memoryapi.cpp b/dll/kernel32/memoryapi.cpp index 48a7907..f73f959 100644 --- a/dll/kernel32/memoryapi.cpp +++ b/dll/kernel32/memoryapi.cpp @@ -3,6 +3,7 @@ #include "common.h" #include "context.h" #include "errors.h" +#include "files.h" #include "handles.h" #include "heap.h" #include "internal.h" @@ -17,6 +18,7 @@ #include #include #include +#include #include #include @@ -56,6 +58,9 @@ struct ViewInfo { DWORD allocationProtect = PAGE_NOACCESS; DWORD type = MEM_PRIVATE; bool managed = false; + bool trackedInode = false; + dev_t trackedDev = 0; + ino_t trackedIno = 0; }; std::map g_viewInfo; @@ -222,6 +227,15 @@ HANDLE WINAPI CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappi return NO_HANDLE; } size = static_cast(fileSize); + } else { + // Windows extends the file to the mapping size if it's smaller. + off_t fileSize = lseek(dupFd, 0, SEEK_END); + if (fileSize >= 0 && static_cast(fileSize) < size) { + if (ftruncate(dupFd, static_cast(size)) != 0) { + setLastErrorFromErrno(); + return NO_HANDLE; + } + } } mapping->maxSize = size; } @@ -276,6 +290,10 @@ static LPVOID mapViewOfFileInternal(Pin mapping, DWORD dwDesiredA bool wantAllAccess = (dwDesiredAccess & FILE_MAP_ALL_ACCESS) == FILE_MAP_ALL_ACCESS; if (wantAllAccess) { wantWrite = true; + // FILE_MAP_ALL_ACCESS includes FILE_MAP_COPY as part of its bitmask, + // but on Windows WRITE takes precedence over COPY. Clear wantCopy so + // we use MAP_SHARED (write-through) instead of MAP_PRIVATE (COW). + wantCopy = false; } int prot = PROT_READ; if (mapping->protect == PAGE_READWRITE) { @@ -330,11 +348,7 @@ static LPVOID mapViewOfFileInternal(Pin mapping, DWORD dwDesiredA return nullptr; } requestedBase = reinterpret_cast(mapBaseAddr); -#ifdef MAP_FIXED_NOREPLACE - mapFlags |= MAP_FIXED_NOREPLACE; -#else mapFlags |= MAP_FIXED; -#endif } else { void *candidate = nullptr; wibo::heap::VmStatus reserveStatus = wibo::heap::reserveViewRange(mapLength, 0, 0, &candidate); @@ -386,8 +400,25 @@ static LPVOID mapViewOfFileInternal(Pin mapping, DWORD dwDesiredA view.allocationProtect = protect; view.type = MEM_MAPPED; view.managed = reservedMapping; + // Track inode for file-backed views so we can prevent truncation (Windows behavior) + DEBUG_LOG("mapViewOfFileInternal: mmapFd=%d anonymous=%d\n", mmapFd, view.owner ? view.owner->anonymous : -1); + if (mmapFd != -1) { + struct stat st {}; + int rc = fstat(mmapFd, &st); + DEBUG_LOG("mapViewOfFileInternal: fstat(%d) = %d dev=%lu ino=%lu\n", + mmapFd, rc, rc == 0 ? (unsigned long)st.st_dev : 0, rc == 0 ? (unsigned long)st.st_ino : 0); + if (rc == 0) { + view.trackedInode = true; + view.trackedDev = st.st_dev; + view.trackedIno = st.st_ino; + files::trackMappedFile(st.st_dev, st.st_ino); + } + } if (reservedMapping) { wibo::heap::registerViewRange(mapBase, mapLength, protect, view.protect); + } else if (baseAddress) { + view.managed = true; + wibo::heap::registerViewRange(mapBase, mapLength, protect, view.protect); } { std::lock_guard guard(g_viewInfoMutex); @@ -438,6 +469,9 @@ BOOL WINAPI UnmapViewOfFile(LPCVOID lpBaseAddress) { void *base = reinterpret_cast(it->second.allocationBase); size_t length = it->second.allocationLength; bool managed = it->second.managed; + bool trackedInode = it->second.trackedInode; + dev_t trackedDev = it->second.trackedDev; + ino_t trackedIno = it->second.trackedIno; g_viewInfo.erase(it); lk.unlock(); if (length != 0) { @@ -446,6 +480,9 @@ BOOL WINAPI UnmapViewOfFile(LPCVOID lpBaseAddress) { if (managed) { wibo::heap::releaseViewRange(base); } + if (trackedInode) { + files::untrackMappedFile(trackedDev, trackedIno); + } return TRUE; } @@ -628,4 +665,34 @@ BOOL WINAPI SetProcessWorkingSetSize(HANDLE hProcess, SIZE_T dwMinimumWorkingSet return TRUE; } +void flushAllFileViews() { + std::lock_guard guard(g_viewInfoMutex); + for (const auto &entry : g_viewInfo) { + const ViewInfo &view = entry.second; + if (view.allocationLength == 0) { + continue; + } + void *base = reinterpret_cast(view.allocationBase); + size_t length = view.allocationLength; + // Check first bytes of the view for debugging + const unsigned char *p = static_cast(base); + bool allZero = true; + size_t checkLen = length < 256 ? length : 256; + for (size_t i = 0; i < checkLen; i++) { + if (p[i] != 0) { + allZero = false; + break; + } + } + DEBUG_LOG("flushAllFileViews: syncing view at %p length %zu first256=%s first4=%02x%02x%02x%02x\n", + base, length, allZero ? "ALL_ZERO" : "HAS_DATA", + checkLen > 0 ? p[0] : 0, checkLen > 1 ? p[1] : 0, + checkLen > 2 ? p[2] : 0, checkLen > 3 ? p[3] : 0); + int rc = msync(base, length, MS_SYNC); + if (rc != 0) { + DEBUG_LOG("flushAllFileViews: msync failed errno=%d\n", errno); + } + } +} + } // namespace kernel32 diff --git a/dll/kernel32/memoryapi.h b/dll/kernel32/memoryapi.h index 6faf134..029b4ab 100644 --- a/dll/kernel32/memoryapi.h +++ b/dll/kernel32/memoryapi.h @@ -24,4 +24,6 @@ BOOL WINAPI GetProcessWorkingSetSize(HANDLE hProcess, PSIZE_T lpMinimumWorkingSe PSIZE_T lpMaximumWorkingSetSize); BOOL WINAPI SetProcessWorkingSetSize(HANDLE hProcess, SIZE_T dwMinimumWorkingSetSize, SIZE_T dwMaximumWorkingSetSize); +void flushAllFileViews(); + } // namespace kernel32 diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index 947392a..3ced401 100644 --- a/dll/kernel32/winbase.cpp +++ b/dll/kernel32/winbase.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -982,6 +983,7 @@ DWORD WINAPI GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer) { if (!tryGetCurrentDirectoryPath(path)) { return 0; } + DEBUG_LOG("GetCurrentDirectoryW result: %s\n", path.c_str()); auto widePath = stringToWideString(path.c_str()); const DWORD required = static_cast(widePath.size()); if (nBufferLength == 0) { @@ -1189,4 +1191,78 @@ BOOL WINAPI GetDiskFreeSpaceExW(LPCWSTR lpDirectoryName, PULARGE_INTEGER lpFreeB lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes); } +int WINAPI lstrcmpA(LPCSTR lpString1, LPCSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpA(%s, %s)\n", lpString1 ? lpString1 : "(null)", lpString2 ? lpString2 : "(null)"); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + return std::strcmp(lpString1, lpString2); +} + +int WINAPI lstrcmpW(LPCWSTR lpString1, LPCWSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpW\n"); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + const uint16_t *s1 = reinterpret_cast(lpString1); + const uint16_t *s2 = reinterpret_cast(lpString2); + while (*s1 && *s2) { + if (*s1 != *s2) + return (*s1 > *s2) ? 1 : -1; + ++s1; + ++s2; + } + if (*s1 == *s2) return 0; + return (*s1 > *s2) ? 1 : -1; +} + +int WINAPI lstrcmpiA(LPCSTR lpString1, LPCSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpiA(%s, %s)\n", lpString1 ? lpString1 : "(null)", lpString2 ? lpString2 : "(null)"); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + return strcasecmp(lpString1, lpString2); +} + +int WINAPI lstrcmpiW(LPCWSTR lpString1, LPCWSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpiW\n"); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + const uint16_t *s1 = reinterpret_cast(lpString1); + const uint16_t *s2 = reinterpret_cast(lpString2); + while (*s1 && *s2) { + uint16_t c1 = wcharToLower(*s1); + uint16_t c2 = wcharToLower(*s2); + if (c1 != c2) + return (c1 > c2) ? 1 : -1; + ++s1; + ++s2; + } + uint16_t c1 = wcharToLower(*s1); + uint16_t c2 = wcharToLower(*s2); + if (c1 == c2) return 0; + return (c1 > c2) ? 1 : -1; +} + +int WINAPI lstrlenA(LPCSTR lpString) { + HOST_CONTEXT_GUARD(); + if (!lpString) return 0; + return static_cast(std::strlen(lpString)); +} + +int WINAPI lstrlenW(LPCWSTR lpString) { + HOST_CONTEXT_GUARD(); + if (!lpString) return 0; + return static_cast(wstrlen(reinterpret_cast(lpString))); +} + } // namespace kernel32 diff --git a/dll/kernel32/winbase.h b/dll/kernel32/winbase.h index 6cf1bab..fc9e2a9 100644 --- a/dll/kernel32/winbase.h +++ b/dll/kernel32/winbase.h @@ -118,4 +118,11 @@ BOOL WINAPI GetDiskFreeSpaceExA(LPCSTR lpDirectoryName, PULARGE_INTEGER lpFreeBy BOOL WINAPI GetDiskFreeSpaceExW(LPCWSTR lpDirectoryName, PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes, PULARGE_INTEGER lpTotalNumberOfFreeBytes); +int WINAPI lstrcmpA(LPCSTR lpString1, LPCSTR lpString2); +int WINAPI lstrcmpW(LPCWSTR lpString1, LPCWSTR lpString2); +int WINAPI lstrcmpiA(LPCSTR lpString1, LPCSTR lpString2); +int WINAPI lstrcmpiW(LPCWSTR lpString1, LPCWSTR lpString2); +int WINAPI lstrlenA(LPCSTR lpString); +int WINAPI lstrlenW(LPCWSTR lpString); + } // namespace kernel32 diff --git a/dll/mspdb/mspdb.def b/dll/mspdb/mspdb.def new file mode 100644 index 0000000..4952eb2 --- /dev/null +++ b/dll/mspdb/mspdb.def @@ -0,0 +1,20 @@ +LIBRARY mspdb80 +EXPORTS + ?Open2W@PDB@@SAHPBGPBDPAJPAGIPAPAU1@@Z = PDB_Open2W + ?Open3W@PDB@@SAHPBGPBDKPBU_GUID@@KPAJPAGIPAPAU1@@Z = PDB_Open3W + ?OpenValidate5@PDB@@SAHPBG0PAXP6AP6AHXZ1W4POVC@@@ZPAJPAGIPAPAU1@@Z = PDB_OpenValidate5 + ?open@NameMap@@SAHPAUPDB@@HPAPAU1@@Z = NameMap_open + PDBOpen2W = PDB_Open2W_C + PDBExportValidateInterface + PDBCommit + PDBClose + PDBOpenStreamEx + SigForPbCb + SzCanonFilename + StreamAppend + StreamQueryCb + StreamRead + StreamRelease + StreamReplace + StreamTruncate + StreamWrite diff --git a/dll/mspdb/mspdb_dll.cpp b/dll/mspdb/mspdb_dll.cpp new file mode 100644 index 0000000..a5587f4 --- /dev/null +++ b/dll/mspdb/mspdb_dll.cpp @@ -0,0 +1,616 @@ +// Cross-compiled 32-bit PE DLL providing fake PDB COM interfaces. +// Built by i686-w64-mingw32-g++ -shared, loaded by wibo as an embedded DLL. +// +// The X360 linker requires a working mspdb DLL with COM-style vtable objects. +// Each class has virtual methods matching the interface slots the linker calls. +// No virtual destructors (MSVC: 1 slot, GCC: 2 slots - would shift all indices). + +#include +#include + +#define DLLEXPORT extern "C" __declspec(dllexport) + +// PDB version constants (VS2010 era - matches X360 linker) +static constexpr uint32_t PDB_INTV = 20091201; +static constexpr uint32_t PDB_IMPV = 20091201; + +// Helper: write a uint32_t to an output pointer if non-null +static void writeOut32(void *ptr, uint32_t val) { + if (ptr) *(uint32_t *)ptr = val; +} + +// Forward-declared addresses (defined after all struct definitions) +struct FakePDB; +struct FakeDBI; +struct FakeMod; +struct FakeTPI; +struct FakeGSI; +struct FakeDbg; +struct FakeNameMap; +struct FakeStream; +extern FakePDB g_pdb; +extern FakeDBI g_dbi; +extern FakeMod g_mod; +extern FakeTPI g_tpi; +extern FakeGSI g_gsi; +extern FakeDbg g_dbg; +extern FakeNameMap g_namemap; +extern FakeStream g_stream; + +// --- PDB interface (64 vtable slots) --- +struct FakePDB { + // 0: QueryInterfaceVersion + virtual uint32_t __thiscall QueryInterfaceVersion() { return PDB_INTV; } + // 1: QueryImplementationVersion + virtual uint32_t __thiscall QueryImplementationVersion() { return PDB_IMPV; } + // 2: QueryLastError(char szError[]) + virtual uint32_t __thiscall QueryLastError(char *sz) { if (sz) *sz = 0; return 0; } + // 3: QueryPDBName(char szPDB[]) + virtual char * __thiscall QueryPDBName(char *sz) { if (sz) *sz = 0; return nullptr; } + // 4: QuerySignature + virtual uint32_t __thiscall QuerySignature() { return 0; } + // 5: QueryAge + virtual uint32_t __thiscall QueryAge() { return 1; } + // 6: CreateDBI(szTarget, DBI**) + virtual int __thiscall CreateDBI(const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_dbi); return 1; } + // 7: OpenDBI(szTarget, szMode, DBI**) + virtual int __thiscall OpenDBI(const char *, const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_dbi); return 1; } + // 8: OpenTpi(szMode, TPI**) + virtual int __thiscall OpenTpi(const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_tpi); return 1; } + // 9: OpenIpi(szMode, TPI**) + virtual int __thiscall OpenIpi(const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_tpi); return 1; } + // 10: Commit + virtual int __thiscall Commit() { return 1; } + // 11: Close + virtual int __thiscall Close() { return 1; } + // 12: OpenStream(szStream, Stream**) + virtual int __thiscall OpenStream(const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_stream); return 1; } + // 13: GetEnumStreamNameMap + virtual int __thiscall GetEnumStreamNameMap(void *) { return 0; } + // 14: GetRawBytes + virtual int __thiscall GetRawBytes(void *) { return 0; } + // 15: QueryPdbImplementationVersion + virtual uint32_t __thiscall QueryPdbImplementationVersion() { return PDB_IMPV; } + // 16: OpenDBIEx(szTarget, szMode, DBI**, pfn) + virtual int __thiscall OpenDBIEx(const char *, const char *, void **pp, void *) { writeOut32(pp, (uint32_t)(uintptr_t)&g_dbi); return 1; } + // 17: CopyTo + virtual int __thiscall CopyTo(const char *, uint32_t, uint32_t) { return 1; } + // 18: OpenSrc + virtual int __thiscall OpenSrc(void *) { return 0; } + // 19: QueryLastErrorExW(wchar_t*, uint32_t) + virtual uint32_t __thiscall QueryLastErrorExW(wchar_t *sz, uint32_t) { if (sz) *sz = 0; return 0; } + // 20: QueryPDBNameExW(wchar_t*, uint32_t) + virtual wchar_t * __thiscall QueryPDBNameExW(wchar_t *sz) { if (sz) *sz = 0; return nullptr; } + // 21: QuerySignature2 + virtual int __thiscall QuerySignature2(void *) { return 1; } + // 22: CopyToW + virtual int __thiscall CopyToW(const wchar_t *, uint32_t, uint32_t) { return 1; } + // 23: fIsSZPDB + virtual int __thiscall fIsSZPDB() { return 1; } + // 24: OpenStreamW(szStream, Stream**) + virtual int __thiscall OpenStreamW(const wchar_t *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_stream); return 1; } + // 25: CopyToW2 + virtual int __thiscall CopyToW2(const wchar_t *, uint32_t, void *, void *) { return 1; } + // 26: OpenStreamEx(szStream, szMode, Stream**) + virtual int __thiscall OpenStreamEx(const char *, const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_stream); return 1; } + // 27: RegisterPDBMapping + virtual int __thiscall RegisterPDBMapping(const char *, const char *) { return 1; } + // 28: EnablePrefetching + virtual int __thiscall EnablePrefetching() { return 1; } + // 29: FLazy + virtual int __thiscall FLazy() { return 0; } + // 30: FMinimal + virtual int __thiscall FMinimal() { return 0; } + // 31: ResetGUID + virtual int __thiscall ResetGUID(void *, uint32_t) { return 1; } + // Padding to 64 slots + virtual int __thiscall _pad32() { return 0; } + virtual int __thiscall _pad33() { return 0; } + virtual int __thiscall _pad34() { return 0; } + virtual int __thiscall _pad35() { return 0; } + virtual int __thiscall _pad36() { return 0; } + virtual int __thiscall _pad37() { return 0; } + virtual int __thiscall _pad38() { return 0; } + virtual int __thiscall _pad39() { return 0; } + virtual int __thiscall _pad40() { return 0; } + virtual int __thiscall _pad41() { return 0; } + virtual int __thiscall _pad42() { return 0; } + virtual int __thiscall _pad43() { return 0; } + virtual int __thiscall _pad44() { return 0; } + virtual int __thiscall _pad45() { return 0; } + virtual int __thiscall _pad46() { return 0; } + virtual int __thiscall _pad47() { return 0; } + virtual int __thiscall _pad48() { return 0; } + virtual int __thiscall _pad49() { return 0; } + virtual int __thiscall _pad50() { return 0; } + virtual int __thiscall _pad51() { return 0; } + virtual int __thiscall _pad52() { return 0; } + virtual int __thiscall _pad53() { return 0; } + virtual int __thiscall _pad54() { return 0; } + virtual int __thiscall _pad55() { return 0; } + virtual int __thiscall _pad56() { return 0; } + virtual int __thiscall _pad57() { return 0; } + virtual int __thiscall _pad58() { return 0; } + virtual int __thiscall _pad59() { return 0; } + virtual int __thiscall _pad60() { return 0; } + virtual int __thiscall _pad61() { return 0; } + virtual int __thiscall _pad62() { return 0; } + virtual int __thiscall _pad63() { return 0; } +}; + +// --- DBI interface (64 vtable slots) --- +struct FakeDBI { + // 0: QueryImplementationVersion + virtual uint32_t __thiscall QueryImplementationVersion() { return PDB_IMPV; } + // 1: QueryInterfaceVersion + virtual uint32_t __thiscall QueryInterfaceVersion() { return PDB_INTV; } + // 2: OpenMod(szModule, szFile, Mod**) + virtual int __thiscall OpenMod(const char *, const char *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_mod); return 1; } + // 3: DeleteMod + virtual int __thiscall DeleteMod(const char *) { return 1; } + // 4: QueryNextMod(pmod, ppmodNext) -> *ppmodNext = NULL + virtual int __thiscall QueryNextMod(void *, void **pp) { writeOut32(pp, 0); return 1; } + // 5: OpenGlobals(GSI**) + virtual int __thiscall OpenGlobals(void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_gsi); return 1; } + // 6: OpenPublics(GSI**) + virtual int __thiscall OpenPublics(void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_gsi); return 1; } + // 7: AddSec + virtual int __thiscall AddSec(uint16_t, uint16_t, uint32_t, uint32_t) { return 1; } + // 8: QueryModFromAddr + virtual int __thiscall QueryModFromAddr(uint16_t, uint32_t, void **, uint16_t *, uint32_t *, uint32_t *) { return 0; } + // 9: QuerySecMap(pb, pcb) + virtual int __thiscall QuerySecMap(void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 10: QueryFileInfo(pb, pcb) + virtual int __thiscall QueryFileInfo(void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 11: DumpMods + virtual void __thiscall DumpMods() {} + // 12: DumpSecContribs + virtual void __thiscall DumpSecContribs() {} + // 13: DumpSecMap + virtual void __thiscall DumpSecMap() {} + // 14: Close + virtual int __thiscall Close() { return 1; } + // 15: AddThunkMap(7 args) + virtual int __thiscall AddThunkMap(uint32_t *, uint32_t, uint32_t, void *, uint32_t, uint16_t, uint32_t) { return 1; } + // 16: AddPublic + virtual int __thiscall AddPublic(const char *, uint16_t, uint32_t) { return 1; } + // 17: getEnumContrib + virtual int __thiscall getEnumContrib(void *) { return 0; } + // 18: QueryTypeServer + virtual int __thiscall QueryTypeServer(uint32_t, void **) { return 0; } + // 19: QueryItsmForTi + virtual int __thiscall QueryItsmForTi(uint32_t, uint32_t *) { return 0; } + // 20: QueryNextItsm + virtual int __thiscall QueryNextItsm(uint32_t, uint32_t *) { return 0; } + // 21: QueryLazyTypes + virtual int __thiscall QueryLazyTypes() { return 0; } + // 22: SetLazyTypes + virtual int __thiscall SetLazyTypes(int) { return 1; } + // 23: FindTypeServers + virtual int __thiscall FindTypeServers(void *, char *) { return 1; } + // 24: DumpTypeServers + virtual void __thiscall DumpTypeServers() {} + // 25: OpenDbg(dbgtype, Dbg**) + virtual int __thiscall OpenDbg(uint32_t, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_dbg); return 1; } + // 26: QueryDbgTypes(pdbgtype, pcDbgtype) + virtual int __thiscall QueryDbgTypes(uint32_t *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 27: QueryAddrForSec + virtual int __thiscall QueryAddrForSec(uint16_t, uint32_t, uint16_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 28: QueryAddrForSecEx + virtual int __thiscall QueryAddrForSecEx(uint16_t, uint32_t, uint32_t, uint16_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 29: QuerySupportsEC + virtual int __thiscall QuerySupportsEC() { return 0; } + // 30: QueryPdb(PDB**) + virtual int __thiscall QueryPdb(void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_pdb); return 1; } + // 31: AddLinkInfo + virtual int __thiscall AddLinkInfo(void *) { return 1; } + // 32: QueryLinkInfo(pli, pcb) + virtual int __thiscall QueryLinkInfo(void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 33: QueryAge + virtual uint32_t __thiscall QueryAge() { return 1; } + // 34: QueryHeader + virtual void * __thiscall QueryHeader() { return nullptr; } + // 35: FlushTypeServers + virtual void __thiscall FlushTypeServers() {} + // 36: QueryTypeServerByPdb + virtual int __thiscall QueryTypeServerByPdb(const char *, uint32_t *) { return 0; } + // 37: OpenModW(szModule, szFile, Mod**) + virtual int __thiscall OpenModW(const wchar_t *, const wchar_t *, void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_mod); return 1; } + // 38: DeleteModW + virtual int __thiscall DeleteModW(const wchar_t *) { return 1; } + // 39: AddPublicW + virtual int __thiscall AddPublicW(const wchar_t *, uint16_t, uint32_t, uint32_t) { return 1; } + // 40: QueryTypeServerByPdbW + virtual int __thiscall QueryTypeServerByPdbW(const wchar_t *, uint32_t *) { return 0; } + // 41: AddLinkInfoW + virtual int __thiscall AddLinkInfoW(void *) { return 1; } + // 42: AddPublic2 + virtual int __thiscall AddPublic2(const char *, uint16_t, uint32_t, uint32_t) { return 1; } + // 43: QueryMachineType + virtual uint16_t __thiscall QueryMachineType() { return 0; } + // 44: SetMachineType + virtual void __thiscall SetMachineType(uint16_t) {} + // 45: RemoveDataForRva + virtual void __thiscall RemoveDataForRva(uint32_t, uint32_t) {} + // 46: FStripped + virtual int __thiscall FStripped() { return 0; } + // 47: QueryModFromAddr2 + virtual int __thiscall QueryModFromAddr2(uint16_t, uint32_t, void **, uint16_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 48: QueryNoOfMods(pcMods) + virtual int __thiscall QueryNoOfMods(uint32_t *pcMods) { writeOut32(pcMods, 0); return 1; } + // 49: QueryMods + virtual int __thiscall QueryMods(void **, uint32_t) { return 1; } + // 50: QueryImodFromAddr + virtual int __thiscall QueryImodFromAddr(uint16_t, uint32_t, void **, uint16_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 51: OpenModFromImod + virtual int __thiscall OpenModFromImod(uint32_t, void **) { return 0; } + // 52: QueryHeader2(cb, pb, pcbOut) + virtual int __thiscall QueryHeader2(uint32_t, void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 53: FAddSourceMappingItem + virtual int __thiscall FAddSourceMappingItem(const wchar_t *, const wchar_t *, uint32_t) { return 1; } + // 54: FSetPfnNotePdbUsed + virtual int __thiscall FSetPfnNotePdbUsed(void *, void *) { return 1; } + // 55: FCTypes + virtual int __thiscall FCTypes() { return 0; } + // 56: QueryFileInfo2(pb, pcb) + virtual int __thiscall QueryFileInfo2(void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 57: FSetPfnQueryCallback + virtual int __thiscall FSetPfnQueryCallback(void *, void *) { return 1; } + // 58: FSetPfnNoteTypeMismatch + virtual int __thiscall FSetPfnNoteTypeMismatch(void *, void *) { return 1; } + // 59: FSetPfnTmdTypeFilter + virtual int __thiscall FSetPfnTmdTypeFilter(void *, void *) { return 1; } + // 60: RemovePublic + virtual int __thiscall RemovePublic(const char *) { return 1; } + // 61: getEnumContrib2 + virtual int __thiscall getEnumContrib2(void *) { return 0; } + // 62: QueryModFromAddrEx + virtual int __thiscall QueryModFromAddrEx(uint16_t, uint32_t, void **, uint16_t *, uint32_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 63: QueryImodFromAddrEx + virtual int __thiscall QueryImodFromAddrEx(uint16_t, uint32_t, void **, uint16_t *, uint32_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } +}; + +// --- Mod interface (64 vtable slots) --- +struct FakeMod { + // 0: QueryInterfaceVersion + virtual uint32_t __thiscall QueryInterfaceVersion() { return PDB_INTV; } + // 1: QueryImplementationVersion + virtual uint32_t __thiscall QueryImplementationVersion() { return PDB_IMPV; } + // 2: AddTypes + virtual int __thiscall AddTypes(void *, uint32_t) { return 1; } + // 3: AddSymbols + virtual int __thiscall AddSymbols(void *, uint32_t) { return 1; } + // 4: AddPublic + virtual int __thiscall AddPublic(const char *, uint16_t, uint32_t) { return 1; } + // 5: AddLines(8 args) + virtual int __thiscall AddLines(const char *, uint16_t, uint32_t, uint32_t, uint32_t, uint32_t, void *, uint32_t) { return 1; } + // 6: AddSecContrib + virtual int __thiscall AddSecContrib(uint16_t, uint32_t, uint32_t, uint32_t) { return 1; } + // 7: QueryCBName(pcb) + virtual int __thiscall QueryCBName(uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 8: QueryName(szName, pcb) + virtual int __thiscall QueryName(char *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 9: QuerySymbols(pbSym, pcb) + virtual int __thiscall QuerySymbols(void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 10: QueryLines(pbLines, pcb) + virtual int __thiscall QueryLines(void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 11: SetPvClient + virtual int __thiscall SetPvClient(void *) { return 1; } + // 12: GetPvClient(ppvClient) + virtual int __thiscall GetPvClient(void **pp) { writeOut32(pp, 0); return 1; } + // 13: QueryFirstCodeSecContrib + virtual int __thiscall QueryFirstCodeSecContrib(uint16_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 14: QueryImod(pimod) + virtual int __thiscall QueryImod(uint32_t *p) { writeOut32(p, 0); return 1; } + // 15: QueryDBI(ppdbi) + virtual int __thiscall QueryDBI(void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_dbi); return 1; } + // 16: Close + virtual int __thiscall Close() { return 1; } + // 17: QueryCBFile(pcb) + virtual int __thiscall QueryCBFile(uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // 18: QueryFile(szFile, pcb) + virtual int __thiscall QueryFile(char *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 19: QueryTpi(pptpi) + virtual int __thiscall QueryTpi(void **pp) { writeOut32(pp, (uint32_t)(uintptr_t)&g_tpi); return 1; } + // 20: AddSecContribEx + virtual int __thiscall AddSecContribEx(uint16_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return 1; } + // 21: QueryItsm(pitsm) + virtual int __thiscall QueryItsm(uint32_t *p) { writeOut32(p, 0); return 1; } + // 22: QuerySrcFile(szFile, pcb) + virtual int __thiscall QuerySrcFile(char *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 23: QuerySupportsEC + virtual int __thiscall QuerySupportsEC() { return 0; } + // 24: QueryPdbFile(szFile, pcb) + virtual int __thiscall QueryPdbFile(char *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 25: ReplaceLines + virtual int __thiscall ReplaceLines(void *, uint32_t) { return 1; } + // 26: GetEnumLines + virtual int __thiscall GetEnumLines(void *) { return 0; } + // 27: QueryLineFlags + virtual int __thiscall QueryLineFlags(uint32_t *) { return 0; } + // 28: QueryFileNameInfo + virtual int __thiscall QueryFileNameInfo(uint32_t, wchar_t *, uint32_t *, uint32_t *, uint32_t *) { return 0; } + // 29: AddPublicW + virtual int __thiscall AddPublicW(const wchar_t *, uint16_t, uint32_t, uint32_t) { return 1; } + // 30: AddLinesW(8 args) + virtual int __thiscall AddLinesW(const wchar_t *, uint16_t, uint32_t, uint32_t, uint32_t, uint32_t, void *, uint32_t) { return 1; } + // 31: QueryNameW(szName, pcb) + virtual int __thiscall QueryNameW(wchar_t *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 32: QueryFileW(szFile, pcb) + virtual int __thiscall QueryFileW(wchar_t *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 33: QuerySrcFileW(szFile, pcb) + virtual int __thiscall QuerySrcFileW(wchar_t *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 34: QueryPdbFileW(szFile, pcb) + virtual int __thiscall QueryPdbFileW(wchar_t *sz, uint32_t *) { if (sz) *sz = 0; return 1; } + // 35: AddPublic2 + virtual int __thiscall AddPublic2(const char *, uint16_t, uint32_t, uint32_t) { return 1; } + // 36: InsertLines + virtual int __thiscall InsertLines(void *, uint32_t) { return 1; } + // 37: QueryLines2(cbLines, pbLines, pcbLines) + virtual int __thiscall QueryLines2(uint32_t, void *, uint32_t *pcb) { writeOut32(pcb, 0); return 1; } + // Padding 38-63 + virtual int __thiscall _pad38() { return 0; } + virtual int __thiscall _pad39() { return 0; } + virtual int __thiscall _pad40() { return 0; } + virtual int __thiscall _pad41() { return 0; } + virtual int __thiscall _pad42() { return 0; } + virtual int __thiscall _pad43() { return 0; } + virtual int __thiscall _pad44() { return 0; } + virtual int __thiscall _pad45() { return 0; } + virtual int __thiscall _pad46() { return 0; } + virtual int __thiscall _pad47() { return 0; } + virtual int __thiscall _pad48() { return 0; } + virtual int __thiscall _pad49() { return 0; } + virtual int __thiscall _pad50() { return 0; } + virtual int __thiscall _pad51() { return 0; } + virtual int __thiscall _pad52() { return 0; } + virtual int __thiscall _pad53() { return 0; } + virtual int __thiscall _pad54() { return 0; } + virtual int __thiscall _pad55() { return 0; } + virtual int __thiscall _pad56() { return 0; } + virtual int __thiscall _pad57() { return 0; } + virtual int __thiscall _pad58() { return 0; } + virtual int __thiscall _pad59() { return 0; } + virtual int __thiscall _pad60() { return 0; } + virtual int __thiscall _pad61() { return 0; } + virtual int __thiscall _pad62() { return 0; } + virtual int __thiscall _pad63() { return 0; } +}; + +// --- TPI interface (32 vtable slots) --- +struct FakeTPI { + // 0: QueryInterfaceVersion + virtual uint32_t __thiscall QueryInterfaceVersion() { return PDB_INTV; } + // 1: QueryImplementationVersion + virtual uint32_t __thiscall QueryImplementationVersion() { return PDB_IMPV; } + // 2-4: Ti16 queries + virtual int __thiscall QueryTi16ForCVRecord(void *, void *) { return 0; } + virtual int __thiscall QueryCVRecordForTi16(uint32_t, void *, uint32_t *) { return 0; } + virtual int __thiscall QueryPbCVRecordForTi16(uint32_t, void **) { return 0; } + // 5-7: Ti16 range + size + virtual uint16_t __thiscall QueryTi16Min() { return 0; } + virtual uint16_t __thiscall QueryTi16Mac() { return 0; } + virtual int32_t __thiscall QueryCb() { return 0; } + // 8: Close + virtual int __thiscall Close() { return 1; } + // 9: Commit + virtual int __thiscall Commit() { return 1; } + // 10: QueryTi16ForUDT + virtual int __thiscall QueryTi16ForUDT(const char *, int, void *) { return 0; } + // 11: SupportQueryTiForUDT + virtual int __thiscall SupportQueryTiForUDT() { return 0; } + // 12: fIs16bitTypePool + virtual int __thiscall fIs16bitTypePool() { return 0; } + // 13: QueryTiForUDT + virtual int __thiscall QueryTiForUDT(const char *, int, void *) { return 0; } + // 14: QueryTiForCVRecord + virtual int __thiscall QueryTiForCVRecord(void *, void *) { return 0; } + // 15: QueryCVRecordForTi + virtual int __thiscall QueryCVRecordForTi(uint32_t, void *, uint32_t *) { return 0; } + // 16: QueryPbCVRecordForTi + virtual int __thiscall QueryPbCVRecordForTi(uint32_t, void **) { return 0; } + // 17: QueryTiMin + virtual uint32_t __thiscall QueryTiMin() { return 0x1000; } + // 18: QueryTiMac + virtual uint32_t __thiscall QueryTiMac() { return 0x1000; } + // 19: AreTypesEqual + virtual int __thiscall AreTypesEqual(uint32_t, uint32_t) { return 0; } + // 20: IsTypeServed + virtual int __thiscall IsTypeServed(uint32_t) { return 0; } + // 21: QueryTiForUDTW + virtual int __thiscall QueryTiForUDTW(const wchar_t *, int, void *) { return 0; } + // 22: QueryModSrcLineForUDTDefn + virtual int __thiscall QueryModSrcLineForUDTDefn(uint32_t, void *, void *, uint32_t *) { return 0; } + // Padding 23-31 + virtual int __thiscall _pad23() { return 0; } + virtual int __thiscall _pad24() { return 0; } + virtual int __thiscall _pad25() { return 0; } + virtual int __thiscall _pad26() { return 0; } + virtual int __thiscall _pad27() { return 0; } + virtual int __thiscall _pad28() { return 0; } + virtual int __thiscall _pad29() { return 0; } + virtual int __thiscall _pad30() { return 0; } + virtual int __thiscall _pad31() { return 0; } +}; + +// --- GSI interface (16 vtable slots) --- +struct FakeGSI { + virtual uint32_t __thiscall QueryInterfaceVersion() { return PDB_INTV; } + virtual uint32_t __thiscall QueryImplementationVersion() { return PDB_IMPV; } + virtual void * __thiscall NextSym(void *) { return nullptr; } + virtual void * __thiscall HashSym(const char *, void *) { return nullptr; } + virtual void * __thiscall NearestSym(uint16_t, uint32_t, int32_t *) { return nullptr; } + virtual int __thiscall Close() { return 1; } + virtual int __thiscall getEnumThunk(uint16_t, uint32_t, void **) { return 0; } + virtual uint32_t __thiscall OffForSym(void *) { return 0; } + virtual void * __thiscall SymForOff(uint32_t) { return nullptr; } + virtual void * __thiscall HashSymW(const wchar_t *, void *) { return nullptr; } + virtual int __thiscall getEnumByAddr(void **) { return 0; } + // Padding 11-15 + virtual int __thiscall _pad11() { return 0; } + virtual int __thiscall _pad12() { return 0; } + virtual int __thiscall _pad13() { return 0; } + virtual int __thiscall _pad14() { return 0; } + virtual int __thiscall _pad15() { return 0; } +}; + +// --- Dbg interface (16 vtable slots) --- +struct FakeDbg { + virtual int __thiscall Close() { return 1; } + virtual int32_t __thiscall QuerySize() { return 0; } + virtual void __thiscall Reset() {} + virtual int __thiscall Skip(uint32_t) { return 1; } + virtual int __thiscall QueryNext(uint32_t, void *) { return 0; } + virtual int __thiscall Find(void *) { return 0; } + virtual int __thiscall Clear() { return 1; } + virtual int __thiscall Append(uint32_t, void *) { return 1; } + virtual int __thiscall ReplaceNext(uint32_t, void *) { return 1; } + virtual int __thiscall Clone(void **) { return 0; } + virtual int32_t __thiscall QueryElementSize() { return 0; } + // Padding 11-15 + virtual int __thiscall _pad11() { return 0; } + virtual int __thiscall _pad12() { return 0; } + virtual int __thiscall _pad13() { return 0; } + virtual int __thiscall _pad14() { return 0; } + virtual int __thiscall _pad15() { return 0; } +}; + +// --- NameMap interface (20 vtable slots) --- +struct FakeNameMap { + virtual int __thiscall close() { return 1; } + virtual int __thiscall reinitialize() { return 1; } + virtual int __thiscall getNi(const char *, uint32_t *pni) { writeOut32(pni, 0); return 1; } + virtual int __thiscall getName(uint32_t, const char **) { return 0; } + virtual int __thiscall getEnumNameMap(void **) { return 0; } + virtual int __thiscall contains(const char *, uint32_t *) { return 0; } + virtual int __thiscall commit() { return 1; } + virtual int __thiscall isValidNi(uint32_t) { return 0; } + virtual int __thiscall getNiW(const wchar_t *, uint32_t *pni) { writeOut32(pni, 0); return 1; } + virtual int __thiscall getNameW(uint32_t, const wchar_t *, uint32_t *) { return 0; } + virtual int __thiscall containsW(const wchar_t *, uint32_t *) { return 0; } + virtual int __thiscall containsUTF8(const char *, uint32_t *) { return 0; } + virtual int __thiscall getNiUTF8(const char *, uint32_t *pni) { writeOut32(pni, 0); return 1; } + virtual int __thiscall getNameA(uint32_t, const char **) { return 0; } + virtual int __thiscall getNameW2(uint32_t, const wchar_t **) { return 0; } + // Padding 15-19 + virtual int __thiscall _pad15() { return 0; } + virtual int __thiscall _pad16() { return 0; } + virtual int __thiscall _pad17() { return 0; } + virtual int __thiscall _pad18() { return 0; } + virtual int __thiscall _pad19() { return 0; } +}; + +// --- Stream interface (12 vtable slots) --- +struct FakeStream { + virtual int32_t __thiscall QueryCb() { return 0; } + virtual int __thiscall Read(uint32_t, void *, uint32_t *) { return 1; } + virtual int __thiscall Write(uint32_t, void *, uint32_t) { return 1; } + virtual int __thiscall Replace(void *, uint32_t) { return 1; } + virtual int __thiscall Append(void *, uint32_t) { return 1; } + virtual int __thiscall Delete() { return 1; } + virtual int __thiscall Release() { return 1; } + virtual int __thiscall Read2(uint32_t, void *, uint32_t) { return 1; } + virtual int __thiscall Truncate(uint32_t) { return 1; } + // Padding 9-11 + virtual int __thiscall _pad9() { return 0; } + virtual int __thiscall _pad10() { return 0; } + virtual int __thiscall _pad11() { return 0; } +}; + +// Global instances (matching extern declarations above) +FakePDB g_pdb; +FakeDBI g_dbi; +FakeMod g_mod; +FakeTPI g_tpi; +FakeGSI g_gsi; +FakeDbg g_dbg; +FakeNameMap g_namemap; +FakeStream g_stream; + +// Legacy sentinel for PDBOpenStreamEx +static uint32_t g_fakeStreamLegacy = 0; + +// --- C-style exports --- + +static int openPDB(int32_t *pec, void **ppPDB) { + if (pec) *pec = 0; + if (ppPDB) *(uint32_t *)ppPDB = (uint32_t)(uintptr_t)&g_pdb; + return 1; +} + +DLLEXPORT int __cdecl PDB_Open2W(const wchar_t *, const char *, int32_t *pec, wchar_t *, unsigned int, void **ppPDB) { + return openPDB(pec, ppPDB); +} + +DLLEXPORT int __cdecl PDB_Open3W(const wchar_t *, const char *, uint32_t, void *, uint32_t, int32_t *pec, + wchar_t *, unsigned int, void **ppPDB) { + return openPDB(pec, ppPDB); +} + +DLLEXPORT int __cdecl PDB_OpenValidate5(const wchar_t *, const wchar_t *, void *, void *, void *, uint32_t, + int32_t *pec, wchar_t *, unsigned int, void **ppPDB) { + return openPDB(pec, ppPDB); +} + +DLLEXPORT int __cdecl PDBExportValidateInterface(uint32_t) { + return 1; +} + +DLLEXPORT uint32_t __cdecl SigForPbCb(void *, uint32_t) { + return 0; +} + +DLLEXPORT const char * __cdecl SzCanonFilename(const char *sz) { + return sz; +} + +DLLEXPORT int __cdecl PDB_Open2W_C(const wchar_t *, const char *, int32_t *pec, wchar_t *, unsigned int, void **ppPDB) { + return openPDB(pec, ppPDB); +} + +DLLEXPORT int __cdecl PDBOpenStreamEx(void *, const char *, uint32_t, void **ppStream) { + if (ppStream) *(uint32_t *)ppStream = (uint32_t)(uintptr_t)&g_fakeStreamLegacy; + return 1; +} + +DLLEXPORT int __cdecl PDBCommit(void *) { + return 1; +} + +DLLEXPORT int __cdecl PDBClose(void *) { + return 1; +} + +DLLEXPORT int __cdecl NameMap_open(void *, int, void **ppNameMap) { + if (ppNameMap) *(uint32_t *)ppNameMap = (uint32_t)(uintptr_t)&g_namemap; + return 1; +} + +DLLEXPORT int __cdecl StreamAppend(void *, void *, int32_t) { + return 1; +} + +DLLEXPORT int32_t __cdecl StreamQueryCb(void *) { + return 0; +} + +DLLEXPORT int __cdecl StreamRead(void *, int32_t, void *, int32_t *pcb) { + if (pcb) *pcb = 0; + return 1; +} + +DLLEXPORT int __cdecl StreamRelease(void *) { + return 1; +} + +DLLEXPORT int __cdecl StreamReplace(void *, void *, int32_t) { + return 1; +} + +DLLEXPORT int __cdecl StreamTruncate(void *, int32_t) { + return 1; +} + +DLLEXPORT int __cdecl StreamWrite(void *, int32_t, void *, int32_t) { + return 1; +} diff --git a/dll/mspdb_embed.cpp b/dll/mspdb_embed.cpp new file mode 100644 index 0000000..deb526c --- /dev/null +++ b/dll/mspdb_embed.cpp @@ -0,0 +1,15 @@ +#include "macros.h" +#include "modules.h" + +INCLUDE_BIN(_mspdbDllData, EMBED_PATH) + +extern const wibo::ModuleStub lib_mspdb = { + .names = + (const char *[]){ + "mspdbxx", + "mspdb100", + "mspdb80", + nullptr, + }, + .dllData = INCLUDE_BIN_SPAN(_mspdbDllData), +}; diff --git a/dll/rpcrt4.cpp b/dll/rpcrt4.cpp index b147aea..4627315 100644 --- a/dll/rpcrt4.cpp +++ b/dll/rpcrt4.cpp @@ -17,7 +17,9 @@ namespace { constexpr RPC_STATUS RPC_S_OK = 0; constexpr RPC_STATUS RPC_S_INVALID_STRING_BINDING = 1700; constexpr RPC_STATUS RPC_S_INVALID_BINDING = 1702; +#ifndef __x86_64__ constexpr RPC_STATUS RPC_S_SERVER_UNAVAILABLE = 1722; +#endif constexpr RPC_STATUS RPC_S_INVALID_ARG = 87; constexpr RPC_STATUS RPC_S_OUT_OF_MEMORY = 14; @@ -227,6 +229,7 @@ RPC_STATUS WINAPI RpcStringFreeW(GUEST_PTR *string) { return RPC_S_OK; } +#ifndef __x86_64__ CLIENT_CALL_RETURN CDECL_NO_CONV NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, PFORMAT_STRING format, ...) { DEBUG_LOG("STUB: NdrClientCall2 stubDescriptor=%p format=%p\n", stubDescriptor, format); CLIENT_CALL_RETURN result = {}; @@ -234,6 +237,7 @@ CLIENT_CALL_RETURN CDECL_NO_CONV NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, DEBUG_LOG("NdrClientCall2 returning RPC_S_SERVER_UNAVAILABLE\n"); return result; } +#endif VOID WINAPI NdrServerCall2(PRPC_MESSAGE message) { HOST_CONTEXT_GUARD(); diff --git a/dll/rpcrt4.h b/dll/rpcrt4.h index 56e6dba..6a83631 100644 --- a/dll/rpcrt4.h +++ b/dll/rpcrt4.h @@ -33,7 +33,7 @@ RPC_STATUS WINAPI RpcBindingSetAuthInfoExW(RPC_BINDING_HANDLE binding, RPC_WSTR RPC_SECURITY_QOS *securityQos); RPC_STATUS WINAPI RpcBindingFree(GUEST_PTR *binding); RPC_STATUS WINAPI RpcStringFreeW(GUEST_PTR *string); -#ifndef __x86_64__ // TODO +#ifndef __x86_64__ // TODO: varargs trampoline not supported on 64-bit CLIENT_CALL_RETURN CDECL_NO_CONV NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, PFORMAT_STRING format, ...); #endif VOID WINAPI NdrServerCall2(PRPC_MESSAGE message); diff --git a/src/errors.h b/src/errors.h index 422db2d..170ae3f 100644 --- a/src/errors.h +++ b/src/errors.h @@ -42,6 +42,7 @@ #define ERROR_BAD_EXE_FORMAT 193 #define ERROR_DLL_INIT_FAILED 1114 #define ERROR_INVALID_FLAGS 1004 +#define ERROR_SHARING_VIOLATION 32 #define ERROR_ALREADY_EXISTS 183 #define ERROR_NOT_OWNER 288 #define ERROR_TIMEOUT 1460 diff --git a/src/files.cpp b/src/files.cpp index fd87621..65c12b7 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -421,4 +423,43 @@ std::string windowsPathListToHost(const std::string &value) { } return result; } +static std::mutex gMappedFileMutex; +static std::map, int> gMappedFileCount; + +void trackMappedFile(dev_t dev, ino_t ino) { + std::lock_guard lk(gMappedFileMutex); + int count = ++gMappedFileCount[{dev, ino}]; + DEBUG_LOG("trackMappedFile: dev=%lu ino=%lu count=%d\n", + (unsigned long)dev, (unsigned long)ino, count); +} + +void untrackMappedFile(dev_t dev, ino_t ino) { + std::lock_guard lk(gMappedFileMutex); + auto it = gMappedFileCount.find({dev, ino}); + if (it != gMappedFileCount.end()) { + if (--it->second <= 0) { + DEBUG_LOG("untrackMappedFile: dev=%lu ino=%lu (removed)\n", + (unsigned long)dev, (unsigned long)ino); + gMappedFileCount.erase(it); + } else { + DEBUG_LOG("untrackMappedFile: dev=%lu ino=%lu count=%d\n", + (unsigned long)dev, (unsigned long)ino, it->second); + } + } +} + +bool isFileMapped(int fd) { + struct stat st {}; + if (fstat(fd, &st) != 0) { + DEBUG_LOG("isFileMapped: fstat failed for fd=%d\n", fd); + return false; + } + std::lock_guard lk(gMappedFileMutex); + bool mapped = gMappedFileCount.count({st.st_dev, st.st_ino}) > 0; + DEBUG_LOG("isFileMapped: fd=%d dev=%lu ino=%lu -> %s\n", + fd, (unsigned long)st.st_dev, (unsigned long)st.st_ino, + mapped ? "YES (skip truncation)" : "no"); + return mapped; +} + } // namespace files diff --git a/src/files.h b/src/files.h index df1c73d..735859c 100644 --- a/src/files.h +++ b/src/files.h @@ -33,6 +33,11 @@ std::filesystem::path canonicalPath(const std::filesystem::path &path); std::string hostPathListToWindows(const std::string &value); std::string windowsPathListToHost(const std::string &value); +// Memory-mapped file tracking (prevents truncation of mapped files, matching Windows behavior) +void trackMappedFile(dev_t dev, ino_t ino); +void untrackMappedFile(dev_t dev, ino_t ino); +bool isFileMapped(int fd); + } // namespace files inline bool endsWith(const std::string &str, const std::string &suffix) { diff --git a/src/main.cpp b/src/main.cpp index bccce60..1780113 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,12 +12,15 @@ #include "version_info.h" #include +#include #include #include #include #include #include +#include "kernel32/memoryapi.h" + char **wibo::argv; int wibo::argc; std::filesystem::path wibo::guestExecutablePath; @@ -473,6 +476,23 @@ int main(int argc, char **argv) { // Reset last error kernel32::setLastError(0); + // Install crash handlers so memory-mapped file data gets flushed on SIGSEGV. + // Uses write() instead of fprintf() for async-signal safety. + { + struct sigaction sa = {}; + sa.sa_sigaction = [](int sig, siginfo_t *, void *) { + kernel32::flushAllFileViews(); + const char *msg = sig == SIGSEGV ? "\nwibo: caught SIGSEGV\n" + : sig == SIGTRAP ? "\nwibo: caught SIGTRAP\n" + : "\nwibo: caught signal\n"; + write(STDERR_FILENO, msg, strlen(msg)); + _exit(128 + sig); + }; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, nullptr); + sigaction(SIGTRAP, &sa, nullptr); + } + // Invoke the damn thing call_EntryProc(entryPoint); DEBUG_LOG("We came back\n"); diff --git a/src/modules.cpp b/src/modules.cpp index 67622d8..50c7f66 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -31,6 +31,9 @@ extern const wibo::ModuleStub lib_bcrypt; extern const wibo::ModuleStub lib_kernel32; extern const wibo::ModuleStub lib_lmgr; extern const wibo::ModuleStub lib_mscoree; +#if WIBO_HAS_MSPDB +extern const wibo::ModuleStub lib_mspdb; +#endif #if WIBO_HAS_MSVCRT extern const wibo::ModuleStub lib_msvcrt; #endif @@ -186,8 +189,11 @@ 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_user32, &lib_vcruntime, &lib_version, &lib_ws2, + &lib_advapi32, &lib_bcrypt, &lib_kernel32, &lib_lmgr, &lib_mscoree, + &lib_ntdll, &lib_ole32, &lib_rpcrt4, &lib_user32, &lib_vcruntime, &lib_version, &lib_ws2, +#if WIBO_HAS_MSPDB + &lib_mspdb, +#endif #if WIBO_HAS_MSVCRT &lib_msvcrt, #endif @@ -547,7 +553,7 @@ void registerBuiltinModule(ModuleRegistry ®, const wibo::ModuleStub *module) reg.builtinAliasLists[module] = {}; auto &aliasList = reg.builtinAliasLists[module]; - const bool pinModule = (module == &lib_lmgr); + const bool pinModule = (module == &lib_lmgr || module == &lib_mspdb); if (pinModule) { reg.pinnedModules.insert(raw); } diff --git a/test/test_mspdb.c b/test/test_mspdb.c new file mode 100644 index 0000000..5768b51 --- /dev/null +++ b/test/test_mspdb.c @@ -0,0 +1,128 @@ +#include +#include +#include + +#include "test_assert.h" + +/* Function pointer types for mspdb exports */ +typedef int(__cdecl *PDBExportValidateInterface_fn)(DWORD intv); +typedef int(__cdecl *PDBOpen2W_fn)(LPCWSTR wszPDB, LPCSTR szMode, LONG *pec, + LPWSTR wszError, UINT cchErrMax, void **ppPDB); +typedef int(__cdecl *PDBCommit_fn)(void *pPDB); +typedef int(__cdecl *PDBClose_fn)(void *pPDB); +typedef LONG(__cdecl *StreamQueryCb_fn)(void *pStream); +typedef int(__cdecl *StreamAppend_fn)(void *pStream, void *pvData, LONG cbData); +typedef int(__cdecl *StreamRelease_fn)(void *pStream); + +int main(void) { + /* 1. LoadLibraryA resolves to the wibo builtin module */ + HMODULE mod = LoadLibraryA("mspdb80.dll"); + TEST_CHECK_MSG(mod != NULL, "LoadLibraryA(mspdb80.dll) failed: %lu", + (unsigned long)GetLastError()); + + /* 2. GetProcAddress finds the C-style exports */ + FARPROC raw_validate = GetProcAddress(mod, "PDBExportValidateInterface"); + FARPROC raw_open = GetProcAddress(mod, "PDBOpen2W"); + FARPROC raw_commit = GetProcAddress(mod, "PDBCommit"); + FARPROC raw_close = GetProcAddress(mod, "PDBClose"); + FARPROC raw_querycb = GetProcAddress(mod, "StreamQueryCb"); + FARPROC raw_append = GetProcAddress(mod, "StreamAppend"); + FARPROC raw_release = GetProcAddress(mod, "StreamRelease"); + + TEST_CHECK_MSG(raw_validate != NULL, "GetProcAddress(PDBExportValidateInterface) failed"); + TEST_CHECK_MSG(raw_open != NULL, "GetProcAddress(PDBOpen2W) failed"); + TEST_CHECK_MSG(raw_commit != NULL, "GetProcAddress(PDBCommit) failed"); + TEST_CHECK_MSG(raw_close != NULL, "GetProcAddress(PDBClose) failed"); + TEST_CHECK_MSG(raw_querycb != NULL, "GetProcAddress(StreamQueryCb) failed"); + TEST_CHECK_MSG(raw_append != NULL, "GetProcAddress(StreamAppend) failed"); + TEST_CHECK_MSG(raw_release != NULL, "GetProcAddress(StreamRelease) failed"); + + PDBExportValidateInterface_fn validate_fn = + (PDBExportValidateInterface_fn)(uintptr_t)raw_validate; + PDBOpen2W_fn open_fn = (PDBOpen2W_fn)(uintptr_t)raw_open; + PDBCommit_fn commit_fn = (PDBCommit_fn)(uintptr_t)raw_commit; + PDBClose_fn close_fn = (PDBClose_fn)(uintptr_t)raw_close; + StreamQueryCb_fn querycb_fn = (StreamQueryCb_fn)(uintptr_t)raw_querycb; + StreamAppend_fn append_fn = (StreamAppend_fn)(uintptr_t)raw_append; + StreamRelease_fn release_fn = (StreamRelease_fn)(uintptr_t)raw_release; + + /* 3. PDBExportValidateInterface(20091201) returns 1 */ + int valid = validate_fn(20091201); + TEST_CHECK_EQ(1, valid); + + /* 4. PDBOpen2W returns 1, sets ec=0, ppPDB!=NULL */ + LONG ec = -1; + void *ppPDB = NULL; + int opened = open_fn(L"test.pdb", "w", &ec, NULL, 0, &ppPDB); + TEST_CHECK_EQ(1, opened); + TEST_CHECK_EQ(0, ec); + TEST_CHECK_MSG(ppPDB != NULL, "PDBOpen2W did not return a PDB handle"); + + /* 5. Vtable dispatch: the PDB handle is a COM-style object whose first + * dword is a vtable pointer. This tests the full chain that breaks on + * 64-bit if objects aren't in 32-bit addressable memory: + * object ptr -> vptr -> stub code -> return value */ + { + /* __thiscall: this in ECX, no stack args for slot 0 */ + typedef int (__attribute__((thiscall)) *QueryInterfaceVersion_fn)(void *thisPtr); + typedef int (__attribute__((thiscall)) *QueryAge_fn)(void *thisPtr); + /* __thiscall: this in ECX, 2 stack args for OpenDBI (slot 7) */ + typedef int (__attribute__((thiscall)) *OpenDBI_fn)(void *thisPtr, + const char *szTarget, const char *szMode, void **ppdbi); + + /* Read vtable pointer from the PDB object */ + DWORD *vtable = *(DWORD **)ppPDB; + TEST_CHECK_MSG(vtable != NULL, "PDB object vtable pointer is NULL"); + + /* Slot 0: QueryInterfaceVersion() -> 20091201 */ + QueryInterfaceVersion_fn queryIV = + (QueryInterfaceVersion_fn)(uintptr_t)vtable[0]; + int intv = queryIV(ppPDB); + TEST_CHECK_EQ(20091201, intv); + + /* Slot 5: QueryAge() -> 1 */ + QueryAge_fn queryAge = (QueryAge_fn)(uintptr_t)vtable[5]; + int age = queryAge(ppPDB); + TEST_CHECK_EQ(1, age); + + /* Slot 7: OpenDBI(szTarget, szMode, DBI**) -> 1, writes DBI ptr */ + void *pDBI = NULL; + OpenDBI_fn openDBI = (OpenDBI_fn)(uintptr_t)vtable[7]; + int dbiOk = openDBI(ppPDB, "target", "r", &pDBI); + TEST_CHECK_EQ(1, dbiOk); + TEST_CHECK_MSG(pDBI != NULL, "OpenDBI did not return a DBI handle"); + + /* DBI object should also have a valid vtable - test dispatch */ + DWORD *dbiVtable = *(DWORD **)pDBI; + TEST_CHECK_MSG(dbiVtable != NULL, "DBI object vtable pointer is NULL"); + + /* DBI slot 0: QueryImplementationVersion() -> 20091201 */ + QueryInterfaceVersion_fn dbiQueryIV = + (QueryInterfaceVersion_fn)(uintptr_t)dbiVtable[0]; + int dbiImpv = dbiQueryIV(pDBI); + TEST_CHECK_EQ(20091201, dbiImpv); + } + + /* 6. PDBCommit returns 1 */ + int committed = commit_fn(ppPDB); + TEST_CHECK_EQ(1, committed); + + /* 7. PDBClose returns 1 */ + int closed = close_fn(ppPDB); + TEST_CHECK_EQ(1, closed); + + /* 8. Stream functions */ + void *fakeStream = (void *)(uintptr_t)0x12345678; + LONG cb = querycb_fn(fakeStream); + TEST_CHECK_EQ(0, cb); + + char data[] = "hello"; + int appended = append_fn(fakeStream, data, sizeof(data)); + TEST_CHECK_EQ(1, appended); + + int released = release_fn(fakeStream); + TEST_CHECK_EQ(1, released); + + printf("mspdb: passed\n"); + return EXIT_SUCCESS; +}