From c17953b3184ae1f16140fca1366be2da8d842ee0 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 29 Sep 2025 13:50:27 -0600 Subject: [PATCH] Refactor main module resolution & HMODULE handle behavior --- AGENTS.md | 2 +- README.md | 4 +- common.h | 80 ++++++++++++++--------- dll/advapi32.cpp | 2 +- dll/crt.cpp | 4 ++ dll/kernel32.cpp | 129 ++++++++++++++++++------------------ dll/msvcrt.cpp | 4 +- dll/psapi.cpp | 2 +- loader.cpp | 119 ++++++++++++++++++--------------- main.cpp | 80 ++++++++++++++++------- module_registry.cpp | 138 ++++++++++++++++++++++++++------------- test/external_exports.c | 6 ++ test/test_external_dll.c | 9 +++ 13 files changed, 355 insertions(+), 224 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ad50cfa..40df020 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ - Sample fixtures for exercising the loader live in `test/`; keep new repros small and self-contained. ## Build, Test, and Development Commands -- `cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` configures a 32-bit toolchain; ensure multilib packages are present. +- `cmake -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` configures a 32-bit toolchain; ensure multilib packages are present. - `cmake --build build --target wibo` compiles the shim; switch to `-DCMAKE_BUILD_TYPE=Release` for optimised binaries. - `./build/wibo /path/to/program.exe` runs a Windows binary. Use `WIBO_DEBUG=1` (or `--debug`/`-D`) for verbose logging. Use `--chdir`/`-C` to set the working directory. - `cmake -B build -DBUILD_TESTING=ON` + `ctest --test-dir build --output-on-failure` runs the self-checking WinAPI fixtures (requires `i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). diff --git a/README.md b/README.md index e980d20..b7a2257 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Don't run this on any untrusted executables, I implore you. (Or probably just do ## Building ```sh -cmake -B build -DCMAKE_BUILD_TYPE=Debug +cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug cmake --build build --target wibo ``` -`cmake -B build -DCMAKE_BUILD_TYPE=Release` to produce an optimized binary instead. +Set `-DCMAKE_BUILD_TYPE=Release` to produce an optimized binary instead. ## Running diff --git a/common.h b/common.h index c182475..dd0170d 100644 --- a/common.h +++ b/common.h @@ -70,6 +70,7 @@ typedef unsigned char BYTE; #define ERROR_RESOURCE_NAME_NOT_FOUND 1814 #define ERROR_RESOURCE_LANG_NOT_FOUND 1815 #define ERROR_MOD_NOT_FOUND 126 +#define ERROR_PROC_NOT_FOUND 127 #define ERROR_NEGATIVE_SEEK 131 #define ERROR_BAD_EXE_FORMAT 193 #define ERROR_ALREADY_EXISTS 183 @@ -102,6 +103,7 @@ namespace wibo { extern uint32_t lastError; extern char **argv; extern int argc; + extern std::filesystem::path guestExecutablePath; extern std::string executableName; extern std::string commandLine; extern std::vector commandLineW; @@ -125,16 +127,16 @@ namespace wibo { void setDllDirectoryOverride(const std::filesystem::path &path); void clearDllDirectoryOverride(); std::optional dllDirectoryOverride(); - HMODULE findLoadedModule(const char *name); + ModuleInfo *findLoadedModule(const char *name); void registerOnExitTable(void *table); void addOnExitFunction(void *table, void (*func)()); void executeOnExitTable(void *table); void runPendingOnExit(ModuleInfo &info); - HMODULE loadModule(const char *name); - void freeModule(HMODULE module); - void *resolveFuncByName(HMODULE module, const char *funcName); - void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal); + ModuleInfo *loadModule(const char *name); + void freeModule(ModuleInfo *info); + void *resolveFuncByName(ModuleInfo *info, const char *funcName); + void *resolveFuncByOrdinal(ModuleInfo *info, uint16_t ordinal); void *resolveMissingImportByName(const char *dllName, const char *funcName); void *resolveMissingImportByOrdinal(const char *dllName, uint16_t ordinal); @@ -172,51 +174,63 @@ namespace wibo { }; struct Executable { - Executable(); + Executable() = default; ~Executable(); bool loadPE(FILE *file, bool exec); + bool resolveImports(); - void *imageBuffer; - size_t imageSize; - void *entryPoint; - void *rsrcBase; - uint32_t rsrcSize; - uintptr_t preferredImageBase; - intptr_t relocationDelta; - uint32_t exportDirectoryRVA; - uint32_t exportDirectorySize; - uint32_t relocationDirectoryRVA; - uint32_t relocationDirectorySize; + void *imageBase = nullptr; + size_t imageSize = 0; + void *entryPoint = nullptr; + void *rsrcBase = nullptr; + uint32_t rsrcSize = 0; + uintptr_t preferredImageBase = 0; + intptr_t relocationDelta = 0; + uint32_t exportDirectoryRVA = 0; + uint32_t exportDirectorySize = 0; + uint32_t relocationDirectoryRVA = 0; + uint32_t relocationDirectorySize = 0; + uint32_t importDirectoryRVA = 0; + uint32_t importDirectorySize = 0; + uint32_t delayImportDirectoryRVA = 0; + uint32_t delayImportDirectorySize = 0; + bool execMapped = false; + bool importsResolved = false; + bool importsResolving = false; - bool findResource(const ResourceIdentifier &type, - const ResourceIdentifier &name, - std::optional language, - ResourceLocation &out) const; + bool findResource(const ResourceIdentifier &type, const ResourceIdentifier &name, + std::optional language, ResourceLocation &out) const; template - T *fromRVA(uint32_t rva) const { - return (T *) (rva + (uint8_t *) imageBuffer); + T *fromRVA(uintptr_t rva) const { + return (T *) (rva + (uint8_t *) imageBase); } template T *fromRVA(T *rva) const { - return fromRVA((uint32_t) rva); + return fromRVA((uintptr_t) rva); } }; + + extern ModuleInfo *mainModule; struct ModuleInfo { + // Windows-style handle to the module. For the main module, this is the image base. + // For other modules, this is a pointer to the ModuleInfo structure. + HMODULE handle; + // Original name used to load the module std::string originalName; + // Normalized module name std::string normalizedName; + // Full path to the loaded module std::filesystem::path resolvedPath; + // Pointer to the built-in module, nullptr if loaded from file const wibo::Module *module = nullptr; + // Loaded PE executable std::unique_ptr executable; - void *entryPoint = nullptr; - void *imageBase = nullptr; - size_t imageSize = 0; + // Reference count, or UINT_MAX for built-in modules unsigned int refCount = 0; - bool dataFile = false; bool processAttachCalled = false; bool processAttachSucceeded = false; - bool dontResolveReferences = false; uint32_t exportOrdinalBase = 0; std::vector exportsByOrdinal; std::unordered_map exportNameToOrdinal; @@ -224,14 +238,16 @@ namespace wibo { std::vector onExitFunctions; }; - extern Executable *mainModule; + ModuleInfo *registerProcessModule(std::unique_ptr executable, std::filesystem::path resolvedPath, + std::string originalName); Executable *executableFromModule(HMODULE module); /** - * HMODULE will be `nullptr` or `mainModule->imageBuffer` if it's the main module, + * HMODULE will be `nullptr` or `mainModule->imageBase` if it's the main module, * otherwise it will be a pointer to a `wibo::ModuleInfo`. */ inline bool isMainModule(HMODULE hModule) { - return hModule == nullptr || hModule == mainModule->imageBuffer; + return hModule == nullptr || hModule == reinterpret_cast(mainModule) || + (mainModule && mainModule->executable && hModule == mainModule->executable->imageBase); } } // namespace wibo diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index 0263be0..8c0a2f6 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -522,7 +522,7 @@ namespace advapi32 { return FALSE; } auto *stats = reinterpret_cast(TokenInformation); - memset(stats, 0, required); + *stats = {}; stats->tokenType = 1; // TokenPrimary stats->impersonationLevel = 0; // SecurityAnonymous stats->tokenId.LowPart = 1; diff --git a/dll/crt.cpp b/dll/crt.cpp index ec5dd16..f89b963 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -122,6 +122,8 @@ int WIN_ENTRY strcmp(const char *lhs, const char *rhs) { return ::strcmp(lhs, rh int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } +char *WIN_ENTRY strcpy(char *dest, const char *src) { return ::strcpy(dest, src); } + void *WIN_ENTRY malloc(size_t size) { return ::malloc(size); } void *WIN_ENTRY calloc(size_t count, size_t size) { return ::calloc(count, size); } @@ -262,6 +264,8 @@ static void *resolveByName(const char *name) { return (void *)crt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)crt::strncmp; + if (strcmp(name, "strcpy") == 0) + return (void *)crt::strcpy; if (strcmp(name, "malloc") == 0) return (void *)crt::malloc; if (strcmp(name, "calloc") == 0) diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 6a31f34..aaaa8d0 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -2685,17 +2685,13 @@ namespace kernel32 { HMODULE WIN_FUNC GetModuleHandleA(LPCSTR lpModuleName) { DEBUG_LOG("GetModuleHandleA(%s)\n", lpModuleName); - - if (!lpModuleName) { - // If lpModuleName is NULL, GetModuleHandle returns a handle to the file - // used to create the calling process (.exe file). - // This handle needs to equal the actual image buffer, from which data can be read. - return wibo::mainModule->imageBuffer; + const auto* module = wibo::findLoadedModule(lpModuleName); + if (!module) { + wibo::lastError = ERROR_MOD_NOT_FOUND; + return nullptr; } - - HMODULE module = wibo::findLoadedModule(lpModuleName); - wibo::lastError = module ? ERROR_SUCCESS : ERROR_MOD_NOT_FOUND; - return module; + wibo::lastError = ERROR_SUCCESS; + return module->handle; } HMODULE WIN_FUNC GetModuleHandleW(LPCWSTR lpModuleName) { @@ -2715,22 +2711,16 @@ namespace kernel32 { return 0; } + auto *info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } std::string path; - if (wibo::isMainModule(hModule)) { - const auto exePath = files::pathFromWindows(wibo::argv[0]); - const auto absPath = std::filesystem::absolute(exePath); - path = files::pathToWindows(absPath); + if (!info->resolvedPath.empty()) { + path = files::pathToWindows(info->resolvedPath); } else { - auto *info = wibo::moduleInfoFromHandle(hModule); - if (!info) { - wibo::lastError = ERROR_INVALID_PARAMETER; - return 0; - } - if (!info->resolvedPath.empty()) { - path = files::pathToWindows(info->resolvedPath); - } else { - path = info->originalName; - } + path = info->originalName; } const size_t len = path.size(); if (nSize == 0) { @@ -2759,22 +2749,16 @@ namespace kernel32 { return 0; } + auto *info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } std::string path; - if (wibo::isMainModule(hModule)) { - const auto exePath = files::pathFromWindows(wibo::argv[0]); - const auto absPath = std::filesystem::absolute(exePath); - path = files::pathToWindows(absPath); + if (!info->resolvedPath.empty()) { + path = files::pathToWindows(info->resolvedPath); } else { - auto *info = wibo::moduleInfoFromHandle(hModule); - if (!info) { - wibo::lastError = ERROR_INVALID_PARAMETER; - return 0; - } - if (!info->resolvedPath.empty()) { - path = files::pathToWindows(info->resolvedPath); - } else { - path = info->originalName; - } + path = info->originalName; } if (nSize == 0) { wibo::lastError = ERROR_INSUFFICIENT_BUFFER; @@ -2802,18 +2786,9 @@ namespace kernel32 { return copyLen; } - static wibo::Executable *module_executable_for_resource(void *hModule) { - if (!hModule) { - hModule = GetModuleHandleA(nullptr); - } - return wibo::executableFromModule((HMODULE) hModule); - } - - static void *find_resource_internal(void *hModule, - const wibo::ResourceIdentifier &type, - const wibo::ResourceIdentifier &name, - std::optional language) { - auto *exe = module_executable_for_resource(hModule); + static void *findResourceInternal(HMODULE hModule, const wibo::ResourceIdentifier &type, + const wibo::ResourceIdentifier &name, std::optional language) { + auto *exe = wibo::executableFromModule(hModule); if (!exe) { wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; return nullptr; @@ -2825,41 +2800,41 @@ namespace kernel32 { return const_cast(loc.dataEntry); } - void *WIN_FUNC FindResourceA(void *hModule, const char *lpName, const char *lpType) { + void *WIN_FUNC FindResourceA(HMODULE hModule, const char *lpName, const char *lpType) { DEBUG_LOG("FindResourceA %p %p %p\n", hModule, lpName, lpType); auto type = wibo::resourceIdentifierFromAnsi(lpType); auto name = wibo::resourceIdentifierFromAnsi(lpName); - return find_resource_internal(hModule, type, name, std::nullopt); + return findResourceInternal(hModule, type, name, std::nullopt); } - void *WIN_FUNC FindResourceExA(void *hModule, const char *lpType, const char *lpName, uint16_t wLanguage) { + void *WIN_FUNC FindResourceExA(HMODULE hModule, const char *lpType, const char *lpName, uint16_t wLanguage) { DEBUG_LOG("FindResourceExA %p %p %p %u\n", hModule, lpName, lpType, wLanguage); auto type = wibo::resourceIdentifierFromAnsi(lpType); auto name = wibo::resourceIdentifierFromAnsi(lpName); - return find_resource_internal(hModule, type, name, wLanguage); + return findResourceInternal(hModule, type, name, wLanguage); } - void *WIN_FUNC FindResourceW(void *hModule, const uint16_t *lpName, const uint16_t *lpType) { + void *WIN_FUNC FindResourceW(HMODULE hModule, const uint16_t *lpName, const uint16_t *lpType) { DEBUG_LOG("FindResourceW %p\n", hModule); auto type = wibo::resourceIdentifierFromWide(lpType); auto name = wibo::resourceIdentifierFromWide(lpName); - return find_resource_internal(hModule, type, name, std::nullopt); + return findResourceInternal(hModule, type, name, std::nullopt); } - void *WIN_FUNC FindResourceExW(void *hModule, const uint16_t *lpType, const uint16_t *lpName, uint16_t wLanguage) { + void *WIN_FUNC FindResourceExW(HMODULE hModule, const uint16_t *lpType, const uint16_t *lpName, uint16_t wLanguage) { DEBUG_LOG("FindResourceExW %p %u\n", hModule, wLanguage); auto type = wibo::resourceIdentifierFromWide(lpType); auto name = wibo::resourceIdentifierFromWide(lpName); - return find_resource_internal(hModule, type, name, wLanguage); + return findResourceInternal(hModule, type, name, wLanguage); } - void* WIN_FUNC LoadResource(void* hModule, void* res) { + void *WIN_FUNC LoadResource(HMODULE hModule, void *res) { DEBUG_LOG("LoadResource %p %p\n", hModule, res); if (!res) { wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; return nullptr; } - auto *exe = module_executable_for_resource(hModule); + auto *exe = wibo::executableFromModule(hModule); if (!exe || !exe->rsrcBase) { wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; return nullptr; @@ -2901,13 +2876,13 @@ namespace kernel32 { return res; } - unsigned int WIN_FUNC SizeofResource(void* hModule, void* res) { + unsigned int WIN_FUNC SizeofResource(HMODULE hModule, void* res) { DEBUG_LOG("SizeofResource %p %p\n", hModule, res); if (!res) { wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; return 0; } - auto *exe = module_executable_for_resource(hModule); + auto *exe = wibo::executableFromModule(hModule); if (!exe || !exe->rsrcBase) { wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; return 0; @@ -2922,7 +2897,13 @@ namespace kernel32 { HMODULE WIN_FUNC LoadLibraryA(LPCSTR lpLibFileName) { DEBUG_LOG("LoadLibraryA(%s)\n", lpLibFileName); - return wibo::loadModule(lpLibFileName); + const auto *info = wibo::loadModule(lpLibFileName); + if (!info) { + // loadModule already sets lastError + return nullptr; + } + wibo::lastError = ERROR_SUCCESS; + return info->handle; } HMODULE WIN_FUNC LoadLibraryW(LPCWSTR lpLibFileName) { @@ -2943,7 +2924,12 @@ namespace kernel32 { BOOL WIN_FUNC FreeLibrary(HMODULE hLibModule) { DEBUG_LOG("FreeLibrary(%p)\n", hLibModule); - wibo::freeModule(hLibModule); + auto *info = wibo::moduleInfoFromHandle(hLibModule); + if (!info) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + wibo::freeModule(info); return TRUE; } @@ -3664,15 +3650,26 @@ namespace kernel32 { FARPROC WIN_FUNC GetProcAddress(HMODULE hModule, LPCSTR lpProcName) { FARPROC result; + const auto info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + DEBUG_LOG("GetProcAddress: invalid module handle %p\n", hModule); + wibo::lastError = ERROR_INVALID_HANDLE; + return nullptr; + } const auto proc = reinterpret_cast(lpProcName); if (proc & ~0xFFFF) { DEBUG_LOG("GetProcAddress(%p, %s) ", hModule, lpProcName); - result = wibo::resolveFuncByName(hModule, lpProcName); + result = wibo::resolveFuncByName(info, lpProcName); } else { DEBUG_LOG("GetProcAddress(%p, %u) ", hModule, proc); - result = wibo::resolveFuncByOrdinal(hModule, static_cast(proc)); + result = wibo::resolveFuncByOrdinal(info, static_cast(proc)); } DEBUG_LOG("-> %p\n", result); + if (!result) { + wibo::lastError = ERROR_PROC_NOT_FOUND; + } else { + wibo::lastError = ERROR_SUCCESS; + } return result; } diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 2aa2bdb..b72fb17 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -169,8 +169,8 @@ namespace msvcrt { std::string converted = files::hostPathListToWindows(value); std::string result = converted.empty() ? value : converted; std::string exeDir; - if (wibo::argv && wibo::argv[0]) { - std::filesystem::path exePath = std::filesystem::absolute(std::filesystem::path(wibo::argv[0])).parent_path(); + if (!wibo::guestExecutablePath.empty()) { + auto exePath = wibo::guestExecutablePath.parent_path(); if (!exePath.empty()) { exeDir = files::pathToWindows(exePath); } diff --git a/dll/psapi.cpp b/dll/psapi.cpp index 70657f3..65dd6f7 100644 --- a/dll/psapi.cpp +++ b/dll/psapi.cpp @@ -17,7 +17,7 @@ BOOL WIN_FUNC EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, return FALSE; } - HMODULE currentModule = wibo::mainModule ? reinterpret_cast(wibo::mainModule->imageBuffer) : nullptr; + HMODULE currentModule = wibo::mainModule ? wibo::mainModule->handle : nullptr; DWORD required = currentModule ? sizeof(HMODULE) : 0; if (lpcbNeeded) { *lpcbNeeded = required; diff --git a/loader.cpp b/loader.cpp index ec1a531..76b9b91 100644 --- a/loader.cpp +++ b/loader.cpp @@ -52,12 +52,12 @@ struct PE32Header { uint32_t loaderFlags; uint32_t numberOfRvaAndSizes; PEImageDataDirectory exportTable; - PEImageDataDirectory importTable; // * + PEImageDataDirectory importTable; // * PEImageDataDirectory resourceTable; // * PEImageDataDirectory exceptionTable; PEImageDataDirectory certificateTable; PEImageDataDirectory baseRelocationTable; // * - PEImageDataDirectory debug; // * + PEImageDataDirectory debug; // * PEImageDataDirectory architecture; PEImageDataDirectory globalPtr; PEImageDataDirectory tlsTable; @@ -122,24 +122,10 @@ uint32_t read32(FILE *file) { return v; } - -wibo::Executable::Executable() { - imageBuffer = nullptr; - imageSize = 0; - entryPoint = nullptr; - rsrcBase = 0; - rsrcSize = 0; - preferredImageBase = 0; - relocationDelta = 0; - exportDirectoryRVA = 0; - exportDirectorySize = 0; - relocationDirectoryRVA = 0; - relocationDirectorySize = 0; -} - wibo::Executable::~Executable() { - if (imageBuffer) { - munmap(imageBuffer, imageSize); + if (imageBase) { + munmap(imageBase, imageSize); + imageBase = nullptr; } } @@ -167,7 +153,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { PE32Header header32; memset(&header32, 0, sizeof header32); - fread(&header32, std::min(sizeof(header32), (size_t) header.sizeOfOptionalHeader), 1, file); + fread(&header32, std::min(sizeof(header32), (size_t)header.sizeOfOptionalHeader), 1, file); if (header32.magic != 0x10B) return false; @@ -181,6 +167,13 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { exportDirectorySize = header32.exportTable.size; relocationDirectoryRVA = header32.baseRelocationTable.virtualAddress; relocationDirectorySize = header32.baseRelocationTable.size; + importDirectoryRVA = header32.importTable.virtualAddress; + importDirectorySize = header32.importTable.size; + delayImportDirectoryRVA = header32.delayImportDescriptor.virtualAddress; + delayImportDirectorySize = header32.delayImportDescriptor.size; + execMapped = exec; + importsResolved = false; + importsResolving = false; // Build buffer imageSize = header32.sizeOfImage; @@ -188,17 +181,17 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { if (exec) prot |= PROT_EXEC; void *preferredBase = (void *)(uintptr_t)header32.imageBase; - imageBuffer = mmap(preferredBase, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (imageBuffer == MAP_FAILED) { - imageBuffer = mmap(nullptr, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + imageBase = mmap(preferredBase, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (imageBase == MAP_FAILED) { + imageBase = mmap(nullptr, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); } - if (imageBuffer == MAP_FAILED) { + if (imageBase == MAP_FAILED) { perror("Image mapping failed!"); - imageBuffer = nullptr; + imageBase = nullptr; return false; } - relocationDelta = (intptr_t)((uintptr_t)imageBuffer - (uintptr_t)header32.imageBase); - memset(imageBuffer, 0, header32.sizeOfImage); + relocationDelta = (intptr_t)((uintptr_t)imageBase - (uintptr_t)header32.imageBase); + memset(imageBase, 0, header32.sizeOfImage); // Read the sections fseek(file, offsetToPE + sizeof header + header.sizeOfOptionalHeader, SEEK_SET); @@ -210,9 +203,10 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { char name[9]; memcpy(name, section.name, 8); name[8] = 0; - DEBUG_LOG("Section %d: name=%s addr=%x size=%x (raw=%x) ptr=%x\n", i, name, section.virtualAddress, section.virtualSize, section.sizeOfRawData, section.pointerToRawData); + DEBUG_LOG("Section %d: name=%s addr=%x size=%x (raw=%x) ptr=%x\n", i, name, section.virtualAddress, + section.virtualSize, section.sizeOfRawData, section.pointerToRawData); - void *sectionBase = (void *) ((uintptr_t) imageBuffer + section.virtualAddress); + void *sectionBase = (void *)((uintptr_t)imageBase + section.virtualAddress); if (section.pointerToRawData > 0 && section.sizeOfRawData > 0) { // Grab this data long savePos = ftell(file); @@ -230,8 +224,8 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { if (exec && relocationDelta != 0) { if (relocationDirectoryRVA == 0 || relocationDirectorySize == 0) { DEBUG_LOG("Relocation required but no relocation directory present\n"); - munmap(imageBuffer, imageSize); - imageBuffer = nullptr; + munmap(imageBase, imageSize); + imageBase = nullptr; return false; } @@ -239,7 +233,8 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { uint8_t *relocEnd = relocCursor + relocationDirectorySize; while (relocCursor < relocEnd) { auto *block = reinterpret_cast(relocCursor); - if (block->sizeOfBlock < sizeof(PEBaseRelocationBlock) || block->sizeOfBlock > static_cast(relocEnd - relocCursor)) { + if (block->sizeOfBlock < sizeof(PEBaseRelocationBlock) || + block->sizeOfBlock > static_cast(relocEnd - relocCursor)) { break; } if (block->sizeOfBlock == sizeof(PEBaseRelocationBlock)) { @@ -253,7 +248,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { uint16_t offset = entry & 0x0FFF; if (type == IMAGE_REL_BASED_ABSOLUTE) continue; - uintptr_t target = reinterpret_cast(imageBuffer) + block->virtualAddress + offset; + uintptr_t target = reinterpret_cast(imageBase) + block->virtualAddress + offset; switch (type) { case IMAGE_REL_BASED_HIGHLOW: { auto *addr = reinterpret_cast(target); @@ -269,13 +264,34 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { } } - if (!exec) { - // No need to resolve imports + entryPoint = header32.addressOfEntryPoint ? fromRVA(header32.addressOfEntryPoint) : nullptr; + + return true; +} + +bool wibo::Executable::resolveImports() { + if (importsResolved || !execMapped) { + importsResolved = true; + importsResolving = false; + return true; + } + if (importsResolving) { + return true; + } + importsResolving = true; + + if (!importDirectoryRVA) { + importsResolved = true; + importsResolving = false; return true; } - // Handle imports - PEImportDirectoryEntry *dir = fromRVA(header32.importTable.virtualAddress); + PEImportDirectoryEntry *dir = fromRVA(importDirectoryRVA); + if (!dir) { + importsResolved = true; + importsResolving = false; + return true; + } while (dir->name) { char *dllName = fromRVA(dir->name); @@ -283,15 +299,15 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { uint32_t *lookupTable = fromRVA(dir->importLookupTable); uint32_t *addressTable = fromRVA(dir->importAddressTable); - HMODULE module = loadModule(dllName); + ModuleInfo *module = loadModule(dllName); while (*lookupTable) { uint32_t lookup = *lookupTable; if (lookup & 0x80000000) { // Import by ordinal uint16_t ordinal = lookup & 0xFFFF; DEBUG_LOG(" Ordinal: %d\n", ordinal); - void *func = module ? resolveFuncByOrdinal(module, ordinal) - : resolveMissingImportByOrdinal(dllName, ordinal); + void *func = + module ? resolveFuncByOrdinal(module, ordinal) : resolveMissingImportByOrdinal(dllName, ordinal); DEBUG_LOG(" -> %p\n", func); *addressTable = reinterpret_cast(func); } else { @@ -299,7 +315,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { PEHintNameTableEntry *hintName = fromRVA(lookup); DEBUG_LOG(" Name: %s (IAT=%p)\n", hintName->name, addressTable); void *func = module ? resolveFuncByName(module, hintName->name) - : resolveMissingImportByName(dllName, hintName->name); + : resolveMissingImportByName(dllName, hintName->name); DEBUG_LOG(" -> %p\n", func); *addressTable = reinterpret_cast(func); } @@ -309,28 +325,29 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { ++dir; } - if (header32.delayImportDescriptor.virtualAddress) { - DEBUG_LOG("Processing delay import table at RVA %x\n", header32.delayImportDescriptor.virtualAddress); - PEDelayImportDescriptor *delay = fromRVA(header32.delayImportDescriptor.virtualAddress); - while (delay->name) { + // TODO: actual delay loading from __delayLoadHelper2 + if (delayImportDirectoryRVA) { + DEBUG_LOG("Processing delay import table at RVA %x\n", delayImportDirectoryRVA); + PEDelayImportDescriptor *delay = fromRVA(delayImportDirectoryRVA); + while (delay && delay->name) { char *dllName = fromRVA(delay->name); DEBUG_LOG("Delay DLL Name: %s\n", dllName); uint32_t *lookupTable = fromRVA(delay->importNameTable); uint32_t *addressTable = fromRVA(delay->importAddressTable); - HMODULE module = loadModule(dllName); + ModuleInfo *module = loadModule(dllName); while (*lookupTable) { uint32_t lookup = *lookupTable; if (lookup & 0x80000000) { uint16_t ordinal = lookup & 0xFFFF; - DEBUG_LOG(" Ordinal: %d (IAT=%p)\n", ordinal, addressTable); + DEBUG_LOG(" Ordinal: %d (IAT=%p)\n", ordinal, addressTable); void *func = module ? resolveFuncByOrdinal(module, ordinal) - : resolveMissingImportByOrdinal(dllName, ordinal); + : resolveMissingImportByOrdinal(dllName, ordinal); *addressTable = reinterpret_cast(func); } else { PEHintNameTableEntry *hintName = fromRVA(lookup); DEBUG_LOG(" Name: %s\n", hintName->name); void *func = module ? resolveFuncByName(module, hintName->name) - : resolveMissingImportByName(dllName, hintName->name); + : resolveMissingImportByName(dllName, hintName->name); *addressTable = reinterpret_cast(func); } ++lookupTable; @@ -346,7 +363,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { } } - entryPoint = header32.addressOfEntryPoint ? fromRVA(header32.addressOfEntryPoint) : nullptr; - + importsResolved = true; + importsResolving = false; return true; } diff --git a/main.cpp b/main.cpp index aeb35a7..817ee9d 100644 --- a/main.cpp +++ b/main.cpp @@ -1,26 +1,28 @@ #include "common.h" #include "files.h" +#include "processes.h" #include "strutil.h" #include #include +#include #include #include #include #include -#include -#include #include #include +#include #include #include uint32_t wibo::lastError = 0; -char** wibo::argv; +char **wibo::argv; int wibo::argc; +std::filesystem::path wibo::guestExecutablePath; std::string wibo::executableName; std::string wibo::commandLine; std::vector wibo::commandLineW; -wibo::Executable *wibo::mainModule = 0; +wibo::ModuleInfo *wibo::mainModule = nullptr; bool wibo::debugEnabled = false; unsigned int wibo::debugIndent = 0; uint16_t wibo::tibSelector = 0; @@ -109,7 +111,7 @@ static void printHelp(const char *argv0) { * @param buffer The buffer to read into. * @return The number of bytes read. */ -static size_t readMaps(char* buffer) { +static size_t readMaps(char *buffer) { int fd = open("/proc/self/maps", O_RDONLY); if (fd == -1) { perror("Failed to open /proc/self/maps"); @@ -187,7 +189,8 @@ static void blockUpper2GB() { holdingMapStart = std::max(holdingMapStart, FILL_MEMORY_ABOVE); // DEBUG_LOG("Mapping %08x-%08x\n", holdingMapStart, holdingMapEnd); - void* holdingMap = mmap((void*) holdingMapStart, holdingMapEnd - holdingMapStart, PROT_READ | PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0); + void *holdingMap = mmap((void *)holdingMapStart, holdingMapEnd - holdingMapStart, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0); if (holdingMap == MAP_FAILED) { perror("Failed to create holding map"); @@ -288,13 +291,13 @@ int main(int argc, char **argv) { // Create TIB memset(&tib, 0, sizeof(tib)); tib.tib = &tib; - tib.peb = (PEB*)calloc(sizeof(PEB), 1); - tib.peb->ProcessParameters = (RTL_USER_PROCESS_PARAMETERS*)calloc(sizeof(RTL_USER_PROCESS_PARAMETERS), 1); + tib.peb = (PEB *)calloc(sizeof(PEB), 1); + tib.peb->ProcessParameters = (RTL_USER_PROCESS_PARAMETERS *)calloc(sizeof(RTL_USER_PROCESS_PARAMETERS), 1); struct user_desc tibDesc; memset(&tibDesc, 0, sizeof tibDesc); tibDesc.entry_number = 0; - tibDesc.base_addr = (unsigned int) &tib; + tibDesc.base_addr = (unsigned int)&tib; tibDesc.limit = 0x1000; tibDesc.seg_32bit = 1; tibDesc.contents = 0; // hopefully this is ok @@ -312,12 +315,25 @@ int main(int argc, char **argv) { char **guestArgv = argv + programIndex; int guestArgc = argc - programIndex; + const char *pePath = guestArgv[0]; + if (!pePath || pePath[0] == '\0') { + fprintf(stderr, "No guest binary specified\n"); + return 1; + } + + std::string originalName = pePath; + std::filesystem::path resolvedGuestPath = processes::resolveExecutable(originalName, true).value_or({}); + if (resolvedGuestPath.empty()) { + fprintf(stderr, "Failed to resolve path to guest binary %s\n", originalName.c_str()); + return 1; + } + // Build a command line std::string cmdLine; for (int i = 0; i < guestArgc; ++i) { std::string arg; if (i == 0) { - arg = files::pathToWindows(std::filesystem::absolute(guestArgv[0])); + arg = files::pathToWindows(resolvedGuestPath); } else { cmdLine += ' '; arg = guestArgv[i]; @@ -326,7 +342,7 @@ int main(int argc, char **argv) { if (needQuotes) cmdLine += '"'; int backslashes = 0; - for (const char *p = arg.c_str(); ; p++) { + for (const char *p = arg.c_str();; p++) { char c = *p; if (c == '\\') { backslashes++; @@ -356,32 +372,50 @@ int main(int argc, char **argv) { wibo::commandLineW = stringToWideString(wibo::commandLine.c_str()); DEBUG_LOG("Command line: %s\n", wibo::commandLine.c_str()); + wibo::guestExecutablePath = resolvedGuestPath; wibo::executableName = executablePath; wibo::argv = guestArgv; wibo::argc = guestArgc; wibo::initializeModuleRegistry(); - wibo::Executable exec; - wibo::mainModule = &exec; - - char* pe_path = guestArgv[0]; - FILE *f = fopen(pe_path, "rb"); + FILE *f = fopen(resolvedGuestPath.c_str(), "rb"); if (!f) { - std::string mesg = std::string("Failed to open file ") + pe_path; + std::string mesg = std::string("Failed to open file ") + resolvedGuestPath.string(); perror(mesg.c_str()); return 1; } - exec.loadPE(f, true); + auto executable = std::make_unique(); + if (!executable->loadPE(f, true)) { + fclose(f); + fprintf(stderr, "Failed to load PE image %s\n", resolvedGuestPath.c_str()); + return 1; + } fclose(f); + const auto entryPoint = executable->entryPoint; + if (!entryPoint) { + fprintf(stderr, "Executable %s has no entry point\n", resolvedGuestPath.c_str()); + return 1; + } + + wibo::mainModule = + wibo::registerProcessModule(std::move(executable), std::move(resolvedGuestPath), std::move(originalName)); + if (!wibo::mainModule || !wibo::mainModule->executable) { + fprintf(stderr, "Failed to register process module\n"); + return 1; + } + DEBUG_LOG("Registered main module %s at %p\n", wibo::mainModule->normalizedName.c_str(), + wibo::mainModule->executable->imageBase); + + if (!wibo::mainModule->executable->resolveImports()) { + fprintf(stderr, "Failed to resolve imports for main module\n"); + return 1; + } + // Invoke the damn thing - asm( - "movw %0, %%fs; call *%1" - : - : "r"(wibo::tibSelector), "r"(exec.entryPoint) - ); + asm("movw %0, %%fs; call *%1" : : "r"(wibo::tibSelector), "r"(entryPoint)); DEBUG_LOG("We came back\n"); wibo::shutdownModuleRegistry(); diff --git a/module_registry.cpp b/module_registry.cpp index 645c309..1cde806 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -253,10 +252,10 @@ std::vector collectSearchDirectories(ModuleRegistry ®, } }; - if (wibo::argv && wibo::argc > 0 && wibo::argv[0]) { - std::filesystem::path mainBinary = std::filesystem::absolute(wibo::argv[0]); - if (mainBinary.has_parent_path()) { - addDirectory(mainBinary.parent_path()); + if (!wibo::guestExecutablePath.empty()) { + auto parent = wibo::guestExecutablePath.parent_path(); + if (!parent.empty()) { + addDirectory(parent); } } @@ -369,6 +368,7 @@ void registerBuiltinModule(ModuleRegistry ®, const wibo::Module *module) { return; } ModulePtr entry = std::make_unique(); + entry->handle = entry.get(); entry->module = module; entry->refCount = UINT_MAX; entry->originalName = module->names[0] ? module->names[0] : ""; @@ -406,24 +406,24 @@ void registerBuiltinModule(ModuleRegistry ®, const wibo::Module *module) { } void callDllMain(wibo::ModuleInfo &info, DWORD reason) { - if (!info.entryPoint || info.module) { + if (!info.executable) { return; } using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID); - auto dllMain = reinterpret_cast(info.entryPoint); + auto dllMain = reinterpret_cast(info.executable->entryPoint); if (!dllMain) { return; } auto invokeWithGuestTIB = [&](DWORD callReason) -> BOOL { if (!wibo::tibSelector) { - return dllMain(reinterpret_cast(info.imageBase), callReason, nullptr); + return dllMain(reinterpret_cast(info.executable->imageBase), callReason, nullptr); } uint16_t previousSegment = 0; asm volatile("mov %%fs, %0" : "=r"(previousSegment)); asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); - BOOL result = dllMain(reinterpret_cast(info.imageBase), callReason, nullptr); + BOOL result = dllMain(reinterpret_cast(info.executable->imageBase), callReason, nullptr); asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); return result; }; @@ -455,20 +455,13 @@ wibo::ModuleInfo *moduleFromAddress(ModuleRegistry ®, void *addr) { return nullptr; for (auto &pair : reg.modulesByKey) { wibo::ModuleInfo *info = pair.second.get(); - if (!info) + if (!info || !info->executable) continue; - uint8_t *base = nullptr; - size_t size = 0; - if (info->imageBase && info->imageSize) { - base = static_cast(info->imageBase); - size = info->imageSize; - } else if (info->executable) { - base = static_cast(info->executable->imageBuffer); - size = info->executable->imageSize; - } + const auto *base = static_cast(info->executable->imageBase); + size_t size = info->executable->imageSize; if (!base || size == 0) continue; - auto *ptr = static_cast(addr); + const auto *ptr = static_cast(addr); if (ptr >= base && ptr < base + size) { return info; } @@ -530,6 +523,68 @@ namespace wibo { void initializeModuleRegistry() { registry(); } +ModuleInfo *registerProcessModule(std::unique_ptr executable, std::filesystem::path resolvedPath, + std::string originalName) { + if (!executable) { + return nullptr; + } + + if (originalName.empty() && !resolvedPath.empty()) { + originalName = resolvedPath.filename().string(); + } + + ParsedModuleName parsed = parseModuleName(originalName); + std::string normalizedName = normalizedBaseKey(parsed); + + ModulePtr info = std::make_unique(); + info->handle = executable->imageBase; // Use image base as handle for main module + info->module = nullptr; + info->originalName = std::move(originalName); + info->normalizedName = std::move(normalizedName); + info->resolvedPath = std::move(resolvedPath); + info->executable = std::move(executable); + info->refCount = UINT_MAX; + + ModuleInfo *raw = info.get(); + + std::string storageKey; + if (!raw->resolvedPath.empty()) { + storageKey = storageKeyForPath(raw->resolvedPath); + } else if (!raw->normalizedName.empty()) { + storageKey = storageKeyForBuiltin(raw->normalizedName); + } + if (storageKey.empty()) { + storageKey = normalizeAlias(raw->originalName); + } + + auto reg = registry(); + reg->modulesByKey[storageKey] = std::move(info); + + if (!raw->resolvedPath.empty()) { + registerExternalModuleAliases(*reg, raw->originalName, raw->resolvedPath, raw); + } else { + registerAlias(*reg, normalizeAlias(raw->originalName), raw); + std::string baseAlias = normalizedBaseKey(parsed); + if (baseAlias != raw->originalName) { + registerAlias(*reg, baseAlias, raw); + } + } + + ensureExportsInitialized(*raw); + + auto pinAlias = [&](const std::string &alias) { + if (!alias.empty()) { + reg->pinnedAliases.insert(alias); + } + }; + reg->pinnedModules.insert(raw); + pinAlias(storageKey); + pinAlias(normalizeAlias(raw->originalName)); + pinAlias(normalizedName); + + return raw; +} + void shutdownModuleRegistry() { auto reg = registry(); for (auto &pair : reg->modulesByKey) { @@ -549,7 +604,12 @@ void shutdownModuleRegistry() { reg->onExitTables.clear(); } -ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast(module); } +ModuleInfo *moduleInfoFromHandle(HMODULE module) { + if (isMainModule(module)) { + return wibo::mainModule; + } + return static_cast(module); +} void setDllDirectoryOverride(const std::filesystem::path &path) { auto canonical = files::canonicalPath(path); @@ -598,7 +658,7 @@ void addOnExitFunction(void *table, void (*func)()) { void runPendingOnExit(ModuleInfo &info) { for (auto it = info.onExitFunctions.rbegin(); it != info.onExitFunctions.rend(); ++it) { - auto fn = reinterpret_cast(*it); + auto fn = reinterpret_cast(*it); if (fn) { fn(); } @@ -623,9 +683,9 @@ void executeOnExitTable(void *table) { } } -HMODULE findLoadedModule(const char *name) { - if (!name) { - return nullptr; +ModuleInfo *findLoadedModule(const char *name) { + if (!name || *name == '\0') { + return wibo::mainModule; } auto reg = registry(); ParsedModuleName parsed = parseModuleName(name); @@ -637,8 +697,8 @@ HMODULE findLoadedModule(const char *name) { return info; } -HMODULE loadModule(const char *dllName) { - if (!dllName) { +ModuleInfo *loadModule(const char *dllName) { + if (!dllName || *dllName == '\0') { lastError = ERROR_INVALID_PARAMETER; return nullptr; } @@ -680,22 +740,19 @@ HMODULE loadModule(const char *dllName) { fclose(file); ModulePtr info = std::make_unique(); + info->handle = info.get(); info->module = nullptr; info->originalName = requested; info->normalizedName = normalizedBaseKey(parsed); info->resolvedPath = files::canonicalPath(path); info->executable = std::move(executable); - info->entryPoint = info->executable->entryPoint; - info->imageBase = info->executable->imageBuffer; - info->imageSize = info->executable->imageSize; info->refCount = 1; - info->dataFile = false; - info->dontResolveReferences = false; ModuleInfo *raw = info.get(); reg->modulesByKey[key] = std::move(info); registerExternalModuleAliases(*reg, requested, raw->resolvedPath, raw); ensureExportsInitialized(*raw); + raw->executable->resolveImports(); callDllMain(*raw, DLL_PROCESS_ATTACH); return raw; }; @@ -765,12 +822,8 @@ HMODULE loadModule(const char *dllName) { return nullptr; } -void freeModule(HMODULE module) { - if (!module) { - return; - } +void freeModule(ModuleInfo *info) { auto reg = registry(); - ModuleInfo *info = moduleInfoFromHandle(module); if (!info || info->refCount == UINT_MAX) { return; } @@ -801,8 +854,7 @@ void freeModule(HMODULE module) { } } -void *resolveFuncByName(HMODULE module, const char *funcName) { - ModuleInfo *info = moduleInfoFromHandle(module); +void *resolveFuncByName(ModuleInfo *info, const char *funcName) { if (!info) { return nullptr; } @@ -816,14 +868,13 @@ void *resolveFuncByName(HMODULE module, const char *funcName) { if (!info->module) { auto it = info->exportNameToOrdinal.find(funcName); if (it != info->exportNameToOrdinal.end()) { - return resolveFuncByOrdinal(module, it->second); + return resolveFuncByOrdinal(info, it->second); } } return reinterpret_cast(resolveMissingFuncName(info->originalName.c_str(), funcName)); } -void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { - ModuleInfo *info = moduleInfoFromHandle(module); +void *resolveFuncByOrdinal(ModuleInfo *info, uint16_t ordinal) { if (!info) { return nullptr; } @@ -862,9 +913,6 @@ void *resolveMissingImportByOrdinal(const char *dllName, uint16_t ordinal) { } Executable *executableFromModule(HMODULE module) { - if (isMainModule(module)) { - return mainModule; - } ModuleInfo *info = moduleInfoFromHandle(module); if (!info) { return nullptr; diff --git a/test/external_exports.c b/test/external_exports.c index 8057edf..fa53b03 100644 --- a/test/external_exports.c +++ b/test/external_exports.c @@ -1,12 +1,14 @@ #include static int attached = 0; +static HMODULE observedMainModule = NULL; BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { (void) hinstDLL; (void) lpReserved; if (fdwReason == DLL_PROCESS_ATTACH) { attached = 1; + observedMainModule = GetModuleHandleW(NULL); } else if (fdwReason == DLL_PROCESS_DETACH) { attached = 2; } @@ -20,3 +22,7 @@ __declspec(dllexport) int __stdcall add_numbers(int a, int b) { __declspec(dllexport) int __stdcall was_attached(void) { return attached; } + +__declspec(dllexport) HMODULE __stdcall observed_main_module(void) { + return observedMainModule; +} diff --git a/test/test_external_dll.c b/test/test_external_dll.c index 6c44755..5b48af1 100644 --- a/test/test_external_dll.c +++ b/test/test_external_dll.c @@ -7,23 +7,32 @@ int main(void) { typedef int(__stdcall *add_numbers_fn)(int, int); typedef int(__stdcall *was_attached_fn)(void); + typedef HMODULE(__stdcall *observed_main_module_fn)(void); + + HMODULE initial_main = GetModuleHandleW(NULL); + TEST_CHECK(initial_main != NULL); HMODULE mod = LoadLibraryA("external_exports.dll"); TEST_CHECK_MSG(mod != NULL, "LoadLibraryA failed: %lu", (unsigned long)GetLastError()); FARPROC raw_add_numbers = GetProcAddress(mod, "add_numbers@8"); FARPROC raw_was_attached = GetProcAddress(mod, "was_attached@0"); + FARPROC raw_observed_main = GetProcAddress(mod, "observed_main_module@0"); TEST_CHECK_MSG(raw_add_numbers != NULL, "GetProcAddress(add_numbers@8) failed: %lu", (unsigned long)GetLastError()); TEST_CHECK_MSG(raw_was_attached != NULL, "GetProcAddress(was_attached@0) failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK_MSG(raw_observed_main != NULL, "GetProcAddress(observed_main_module@0) failed: %lu", (unsigned long)GetLastError()); add_numbers_fn add_numbers = (add_numbers_fn)(uintptr_t)raw_add_numbers; was_attached_fn was_attached = (was_attached_fn)(uintptr_t)raw_was_attached; + observed_main_module_fn observed_main = (observed_main_module_fn)(uintptr_t)raw_observed_main; int sum = add_numbers(2, 40); int attached = was_attached(); + HMODULE observed_main_module = observed_main(); TEST_CHECK_EQ(42, sum); TEST_CHECK_EQ(1, attached); + TEST_CHECK_EQ(initial_main, observed_main_module); TEST_CHECK_MSG(FreeLibrary(mod) != 0, "FreeLibrary failed: %lu", (unsigned long)GetLastError());