mirror of
https://github.com/decompals/wibo.git
synced 2025-10-16 15:15:10 +00:00
Initial external DLL support
This commit is contained in:
parent
866dd6ddbb
commit
836f485d66
@ -31,6 +31,7 @@ add_executable(wibo
|
||||
files.cpp
|
||||
handles.cpp
|
||||
loader.cpp
|
||||
module_registry.cpp
|
||||
main.cpp
|
||||
processes.cpp
|
||||
strutil.cpp
|
||||
|
@ -16,7 +16,7 @@ Rough to-do list:
|
||||
- Do something intelligent with Windows `HANDLE`s
|
||||
- Convert paths in environment variables (and the structure of `PATH` itself, maybe) to Windows format
|
||||
- Implement PE relocations rather than just failing unceremoniously
|
||||
- Make the PE loader work for DLLs as well in case we ever want to load some
|
||||
- Land external DLL loading support (module registry + search order + export resolution)
|
||||
|
||||
---
|
||||
|
||||
|
45
common.h
45
common.h
@ -3,11 +3,14 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// On Windows, the incoming stack is aligned to a 4 byte boundary.
|
||||
// force_align_arg_pointer will realign the stack to match GCC's 16 byte alignment.
|
||||
@ -57,7 +60,9 @@ typedef unsigned char BYTE;
|
||||
#define ERROR_INVALID_PARAMETER 87
|
||||
#define ERROR_BUFFER_OVERFLOW 111
|
||||
#define ERROR_INSUFFICIENT_BUFFER 122
|
||||
#define ERROR_MOD_NOT_FOUND 126
|
||||
#define ERROR_NEGATIVE_SEEK 131
|
||||
#define ERROR_BAD_EXE_FORMAT 193
|
||||
#define ERROR_ALREADY_EXISTS 183
|
||||
|
||||
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
|
||||
@ -94,7 +99,18 @@ namespace wibo {
|
||||
ResolveByName byName;
|
||||
ResolveByOrdinal byOrdinal;
|
||||
};
|
||||
extern const Module *modules[];
|
||||
struct ModuleInfo;
|
||||
void initializeModuleRegistry();
|
||||
void shutdownModuleRegistry();
|
||||
ModuleInfo *moduleInfoFromHandle(HMODULE module);
|
||||
void setDllDirectoryOverride(const std::filesystem::path &path);
|
||||
void clearDllDirectoryOverride();
|
||||
std::optional<std::filesystem::path> dllDirectoryOverride();
|
||||
HMODULE 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);
|
||||
@ -110,6 +126,12 @@ namespace wibo {
|
||||
size_t imageSize;
|
||||
void *entryPoint;
|
||||
void *rsrcBase;
|
||||
uintptr_t preferredImageBase;
|
||||
intptr_t relocationDelta;
|
||||
uint32_t exportDirectoryRVA;
|
||||
uint32_t exportDirectorySize;
|
||||
uint32_t relocationDirectoryRVA;
|
||||
uint32_t relocationDirectorySize;
|
||||
|
||||
template <typename T>
|
||||
T *fromRVA(uint32_t rva) {
|
||||
@ -122,9 +144,24 @@ namespace wibo {
|
||||
}
|
||||
};
|
||||
struct ModuleInfo {
|
||||
std::string name;
|
||||
const wibo::Module* module = nullptr;
|
||||
std::string originalName;
|
||||
std::string normalizedName;
|
||||
std::filesystem::path resolvedPath;
|
||||
const wibo::Module *module = nullptr;
|
||||
std::unique_ptr<wibo::Executable> executable;
|
||||
void *entryPoint = nullptr;
|
||||
void *imageBase = nullptr;
|
||||
size_t imageSize = 0;
|
||||
unsigned int refCount = 0;
|
||||
bool dataFile = false;
|
||||
bool processAttachCalled = false;
|
||||
bool processAttachSucceeded = false;
|
||||
bool dontResolveReferences = false;
|
||||
uint32_t exportOrdinalBase = 0;
|
||||
std::vector<void *> exportsByOrdinal;
|
||||
std::unordered_map<std::string, uint16_t> exportNameToOrdinal;
|
||||
bool exportsInitialized = false;
|
||||
std::vector<void *> onExitFunctions;
|
||||
};
|
||||
|
||||
extern Executable *mainModule;
|
||||
|
140
dll/crt.cpp
140
dll/crt.cpp
@ -1,9 +1,18 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef void (*_PVFV)();
|
||||
typedef int (*_PIFV)();
|
||||
typedef void (*_invalid_parameter_handler)(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t);
|
||||
|
||||
extern char **environ;
|
||||
|
||||
typedef enum _crt_app_type {
|
||||
_crt_unknown_app,
|
||||
@ -20,8 +29,10 @@ typedef enum _crt_argv_mode {
|
||||
namespace crt {
|
||||
|
||||
int _commode = 0;
|
||||
int _fmode = 0;
|
||||
|
||||
std::vector<_PVFV> atexitFuncs;
|
||||
_invalid_parameter_handler invalidParameterHandler = nullptr;
|
||||
|
||||
void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) {
|
||||
do {
|
||||
@ -45,12 +56,15 @@ int WIN_ENTRY _initterm_e(const _PIFV *ppfn, const _PIFV *end) {
|
||||
void WIN_ENTRY _set_app_type(_crt_app_type type) { DEBUG_LOG("STUB: _set_app_type(%i)\n", type); }
|
||||
|
||||
int WIN_ENTRY _set_fmode(int mode) {
|
||||
DEBUG_LOG("STUB: _set_fmode(%i)\n", mode);
|
||||
DEBUG_LOG("_set_fmode(%i)\n", mode);
|
||||
_fmode = mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int *WIN_ENTRY __p__commode() { return &_commode; }
|
||||
|
||||
int *WIN_ENTRY __p__fmode() { return &_fmode; }
|
||||
|
||||
int WIN_ENTRY _crt_atexit(void (*func)()) {
|
||||
DEBUG_LOG("_crt_atexit(%p)\n", func);
|
||||
atexitFuncs.push_back(func);
|
||||
@ -62,6 +76,13 @@ int WIN_ENTRY _configure_narrow_argv(_crt_argv_mode mode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_invalid_parameter_handler WIN_ENTRY _set_invalid_parameter_handler(_invalid_parameter_handler newHandler) {
|
||||
DEBUG_LOG("STUB: _set_invalid_parameter_handler(%p)\n", newHandler);
|
||||
_invalid_parameter_handler oldHandler = invalidParameterHandler;
|
||||
invalidParameterHandler = newHandler;
|
||||
return oldHandler;
|
||||
}
|
||||
|
||||
int WIN_ENTRY _controlfp_s(unsigned int *currentControl, unsigned int newControl, unsigned int mask) {
|
||||
DEBUG_LOG("STUB: _controlfp_s(%p, %u, %u)\n", currentControl, newControl, mask);
|
||||
return 0;
|
||||
@ -84,12 +105,49 @@ int WIN_ENTRY _set_new_mode(int newhandlermode) {
|
||||
|
||||
char **WIN_ENTRY _get_initial_narrow_environment() { return environ; }
|
||||
|
||||
char ***WIN_ENTRY __p__environ() { return &environ; }
|
||||
|
||||
char ***WIN_ENTRY __p___argv() { return &wibo::argv; }
|
||||
|
||||
int *WIN_ENTRY __p___argc() { return &wibo::argc; }
|
||||
|
||||
size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); }
|
||||
|
||||
int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); }
|
||||
|
||||
void *WIN_ENTRY malloc(size_t size) { return ::malloc(size); }
|
||||
|
||||
void *WIN_ENTRY calloc(size_t count, size_t size) { return ::calloc(count, size); }
|
||||
|
||||
void *WIN_ENTRY realloc(void *ptr, size_t newSize) { return ::realloc(ptr, newSize); }
|
||||
|
||||
void WIN_ENTRY free(void *ptr) { ::free(ptr); }
|
||||
|
||||
void *WIN_ENTRY memcpy(void *dest, const void *src, size_t count) { return std::memcpy(dest, src, count); }
|
||||
|
||||
int WIN_ENTRY __setusermatherr(void *handler) {
|
||||
DEBUG_LOG("STUB: __setusermatherr(%p)\n", handler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WIN_ENTRY _initialize_onexit_table(void *table) {
|
||||
DEBUG_LOG("STUB: _initialize_onexit_table(%p)\n", table);
|
||||
wibo::registerOnExitTable(table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WIN_ENTRY _register_onexit_function(void *table, void (*func)()) {
|
||||
DEBUG_LOG("STUB: _register_onexit_function(%p, %p)\n", table, func);
|
||||
wibo::addOnExitFunction(table, func);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WIN_ENTRY _execute_onexit_table(void *table) {
|
||||
DEBUG_LOG("STUB: _execute_onexit_table(%p)\n", table);
|
||||
wibo::executeOnExitTable(table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WIN_ENTRY exit(int status) {
|
||||
DEBUG_LOG("exit(%i)\n", status);
|
||||
for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) {
|
||||
@ -99,6 +157,42 @@ void WIN_ENTRY exit(int status) {
|
||||
::exit(status);
|
||||
}
|
||||
|
||||
void WIN_ENTRY _cexit(void) {
|
||||
DEBUG_LOG("_cexit()\n");
|
||||
for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) {
|
||||
DEBUG_LOG("Calling atexit function %p\n", *it);
|
||||
(*it)();
|
||||
}
|
||||
}
|
||||
|
||||
void WIN_ENTRY _exit(int status) {
|
||||
DEBUG_LOG("_exit(%i)\n", status);
|
||||
::_exit(status);
|
||||
}
|
||||
|
||||
void WIN_ENTRY abort(void) {
|
||||
DEBUG_LOG("abort()\n");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
using signal_handler = void (*)(int);
|
||||
|
||||
signal_handler WIN_ENTRY signal(int signum, signal_handler handler) { return std::signal(signum, handler); }
|
||||
|
||||
void *WIN_ENTRY __acrt_iob_func(unsigned int index) {
|
||||
if (index == 0)
|
||||
return stdin;
|
||||
if (index == 1)
|
||||
return stdout;
|
||||
if (index == 2)
|
||||
return stderr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stream, const char *format, void * /*locale*/, va_list args) {
|
||||
return vfprintf(stream, format, args);
|
||||
}
|
||||
|
||||
} // namespace crt
|
||||
|
||||
static void *resolveByName(const char *name) {
|
||||
@ -112,10 +206,14 @@ static void *resolveByName(const char *name) {
|
||||
return (void *)crt::_set_fmode;
|
||||
if (strcmp(name, "__p__commode") == 0)
|
||||
return (void *)crt::__p__commode;
|
||||
if (strcmp(name, "__p__fmode") == 0)
|
||||
return (void *)crt::__p__fmode;
|
||||
if (strcmp(name, "_crt_atexit") == 0)
|
||||
return (void *)crt::_crt_atexit;
|
||||
if (strcmp(name, "_configure_narrow_argv") == 0)
|
||||
return (void *)crt::_configure_narrow_argv;
|
||||
if (strcmp(name, "_set_invalid_parameter_handler") == 0)
|
||||
return (void *)crt::_set_invalid_parameter_handler;
|
||||
if (strcmp(name, "_controlfp_s") == 0)
|
||||
return (void *)crt::_controlfp_s;
|
||||
if (strcmp(name, "_configthreadlocale") == 0)
|
||||
@ -126,14 +224,48 @@ static void *resolveByName(const char *name) {
|
||||
return (void *)crt::_set_new_mode;
|
||||
if (strcmp(name, "_get_initial_narrow_environment") == 0)
|
||||
return (void *)crt::_get_initial_narrow_environment;
|
||||
if (strcmp(name, "__p__environ") == 0)
|
||||
return (void *)crt::__p__environ;
|
||||
if (strcmp(name, "__p___argv") == 0)
|
||||
return (void *)crt::__p___argv;
|
||||
if (strcmp(name, "__p___argc") == 0)
|
||||
return (void *)crt::__p___argc;
|
||||
if (strcmp(name, "strlen") == 0)
|
||||
return (void *)crt::strlen;
|
||||
if (strcmp(name, "strncmp") == 0)
|
||||
return (void *)crt::strncmp;
|
||||
if (strcmp(name, "malloc") == 0)
|
||||
return (void *)crt::malloc;
|
||||
if (strcmp(name, "calloc") == 0)
|
||||
return (void *)crt::calloc;
|
||||
if (strcmp(name, "realloc") == 0)
|
||||
return (void *)crt::realloc;
|
||||
if (strcmp(name, "free") == 0)
|
||||
return (void *)crt::free;
|
||||
if (strcmp(name, "memcpy") == 0)
|
||||
return (void *)crt::memcpy;
|
||||
if (strcmp(name, "exit") == 0)
|
||||
return (void *)crt::exit;
|
||||
if (strcmp(name, "_cexit") == 0)
|
||||
return (void *)crt::_cexit;
|
||||
if (strcmp(name, "_exit") == 0)
|
||||
return (void *)crt::_exit;
|
||||
if (strcmp(name, "abort") == 0)
|
||||
return (void *)crt::abort;
|
||||
if (strcmp(name, "signal") == 0)
|
||||
return (void *)crt::signal;
|
||||
if (strcmp(name, "__acrt_iob_func") == 0)
|
||||
return (void *)crt::__acrt_iob_func;
|
||||
if (strcmp(name, "__stdio_common_vfprintf") == 0)
|
||||
return (void *)crt::__stdio_common_vfprintf;
|
||||
if (strcmp(name, "__setusermatherr") == 0)
|
||||
return (void *)crt::__setusermatherr;
|
||||
if (strcmp(name, "_initialize_onexit_table") == 0)
|
||||
return (void *)crt::_initialize_onexit_table;
|
||||
if (strcmp(name, "_register_onexit_function") == 0)
|
||||
return (void *)crt::_register_onexit_function;
|
||||
if (strcmp(name, "_execute_onexit_table") == 0)
|
||||
return (void *)crt::_execute_onexit_table;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -149,6 +281,12 @@ wibo::Module lib_crt = {
|
||||
"api-ms-win-crt-stdio-l1-1-0.dll",
|
||||
"api-ms-win-crt-string-l1-1-0",
|
||||
"api-ms-win-crt-string-l1-1-0.dll",
|
||||
"api-ms-win-crt-environment-l1-1-0",
|
||||
"api-ms-win-crt-environment-l1-1-0.dll",
|
||||
"api-ms-win-crt-math-l1-1-0",
|
||||
"api-ms-win-crt-math-l1-1-0.dll",
|
||||
"api-ms-win-crt-private-l1-1-0",
|
||||
"api-ms-win-crt-private-l1-1-0.dll",
|
||||
nullptr,
|
||||
},
|
||||
resolveByName,
|
||||
|
211
dll/kernel32.cpp
211
dll/kernel32.cpp
@ -1565,8 +1565,9 @@ namespace kernel32 {
|
||||
return wibo::mainModule->imageBuffer;
|
||||
}
|
||||
|
||||
// wibo::lastError = 0;
|
||||
return wibo::loadModule(lpModuleName);
|
||||
HMODULE module = wibo::findLoadedModule(lpModuleName);
|
||||
wibo::lastError = module ? ERROR_SUCCESS : ERROR_MOD_NOT_FOUND;
|
||||
return module;
|
||||
}
|
||||
|
||||
HMODULE WIN_FUNC GetModuleHandleW(LPCWSTR lpModuleName) {
|
||||
@ -1592,7 +1593,16 @@ namespace kernel32 {
|
||||
const auto absPath = std::filesystem::absolute(exePath);
|
||||
path = files::pathToWindows(absPath);
|
||||
} else {
|
||||
path = static_cast<wibo::ModuleInfo *>(hModule)->name;
|
||||
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;
|
||||
}
|
||||
}
|
||||
const size_t len = path.size();
|
||||
if (nSize == 0) {
|
||||
@ -1627,17 +1637,32 @@ namespace kernel32 {
|
||||
const auto absPath = std::filesystem::absolute(exePath);
|
||||
path = files::pathToWindows(absPath);
|
||||
} else {
|
||||
path = static_cast<wibo::ModuleInfo *>(hModule)->name;
|
||||
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;
|
||||
}
|
||||
}
|
||||
const size_t len = path.size();
|
||||
if (nSize == 0) {
|
||||
wibo::lastError = ERROR_INSUFFICIENT_BUFFER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t copyLen = std::min(len, nSize - 1);
|
||||
memcpy(lpFilename, stringToWideString(path.c_str()).data(), copyLen * 2);
|
||||
if (copyLen < nSize) {
|
||||
auto wide = stringToWideString(path.c_str());
|
||||
if (wide.empty()) {
|
||||
wide.push_back(0);
|
||||
}
|
||||
const size_t len = wide.size() - 1;
|
||||
const size_t copyLen = std::min(len, static_cast<size_t>(nSize - 1));
|
||||
for (size_t i = 0; i < copyLen; i++) {
|
||||
lpFilename[i] = wide[i];
|
||||
}
|
||||
if (copyLen < static_cast<size_t>(nSize)) {
|
||||
lpFilename[copyLen] = 0;
|
||||
}
|
||||
if (copyLen < len) {
|
||||
@ -1649,38 +1674,58 @@ namespace kernel32 {
|
||||
return copyLen;
|
||||
}
|
||||
|
||||
void* WIN_FUNC FindResourceA(void* hModule, const char* lpName, const char* lpType) {
|
||||
DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType);
|
||||
return (void*)0x100002;
|
||||
static std::string resource_identifier_to_string(const char *id) {
|
||||
if (!id) {
|
||||
return "";
|
||||
}
|
||||
if ((uintptr_t)id >> 16 == 0) {
|
||||
return std::to_string(static_cast<unsigned int>((uintptr_t)id));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
// https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090
|
||||
// https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097
|
||||
void* WIN_FUNC FindResourceW(void* hModule, const uint16_t* lpName, const uint16_t* lpType) {
|
||||
DEBUG_LOG("FindResourceW %p\n", hModule);
|
||||
std::string name, type;
|
||||
|
||||
if(!hModule) hModule = GetModuleHandleW(0);
|
||||
|
||||
if((uintptr_t)lpName >> 16 == 0){
|
||||
name = std::to_string((unsigned int)(uintptr_t)lpName);
|
||||
static std::string resource_identifier_to_string(const uint16_t *id) {
|
||||
if (!id) {
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
name = wideStringToString(lpName);
|
||||
}
|
||||
|
||||
if((uintptr_t)lpType >> 16 == 0){
|
||||
type = std::to_string((unsigned int)(uintptr_t)lpType);
|
||||
}
|
||||
else {
|
||||
type = wideStringToString(lpType);
|
||||
if ((uintptr_t)id >> 16 == 0) {
|
||||
return std::to_string(static_cast<unsigned int>((uintptr_t)id));
|
||||
}
|
||||
return wideStringToString(id);
|
||||
}
|
||||
|
||||
static FILE *open_resource_stream(const std::string &type, const std::string &name) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "resources/%s/%s.res", type.c_str(), name.c_str());
|
||||
DEBUG_LOG("Created path %s\n", path);
|
||||
return fopen(path, "rb");
|
||||
// return (void*)0x100002;
|
||||
}
|
||||
|
||||
void *WIN_FUNC FindResourceA(void *hModule, const char *lpName, const char *lpType) {
|
||||
DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType);
|
||||
|
||||
if (!hModule) {
|
||||
hModule = GetModuleHandleA(nullptr);
|
||||
}
|
||||
|
||||
const std::string name = resource_identifier_to_string(lpName);
|
||||
const std::string type = resource_identifier_to_string(lpType);
|
||||
|
||||
return open_resource_stream(type, name);
|
||||
}
|
||||
|
||||
// https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090
|
||||
// https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097
|
||||
void *WIN_FUNC FindResourceW(void *hModule, const uint16_t *lpName, const uint16_t *lpType) {
|
||||
DEBUG_LOG("FindResourceW %p\n", hModule);
|
||||
|
||||
if (!hModule)
|
||||
hModule = GetModuleHandleW(0);
|
||||
|
||||
const std::string name = resource_identifier_to_string(lpName);
|
||||
const std::string type = resource_identifier_to_string(lpType);
|
||||
|
||||
return open_resource_stream(type, name);
|
||||
}
|
||||
|
||||
void* WIN_FUNC LoadResource(void* hModule, void* res) {
|
||||
@ -1813,19 +1858,34 @@ namespace kernel32 {
|
||||
return (void *) 0x100006;
|
||||
}
|
||||
|
||||
static int translateProtect(DWORD flProtect) {
|
||||
switch (flProtect) {
|
||||
case 0x01: /* PAGE_NOACCESS */
|
||||
return PROT_NONE;
|
||||
case 0x02: /* PAGE_READONLY */
|
||||
return PROT_READ;
|
||||
case 0x04: /* PAGE_READWRITE */
|
||||
return PROT_READ | PROT_WRITE;
|
||||
case 0x08: /* PAGE_WRITECOPY */
|
||||
return PROT_READ | PROT_WRITE;
|
||||
case 0x10: /* PAGE_EXECUTE */
|
||||
return PROT_EXEC;
|
||||
case 0x20: /* PAGE_EXECUTE_READ */
|
||||
return PROT_READ | PROT_EXEC;
|
||||
case 0x40: /* PAGE_EXECUTE_READWRITE */
|
||||
return PROT_READ | PROT_WRITE | PROT_EXEC;
|
||||
case 0x80: /* PAGE_EXECUTE_WRITECOPY */
|
||||
return PROT_READ | PROT_WRITE | PROT_EXEC;
|
||||
default:
|
||||
DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect);
|
||||
return PROT_READ | PROT_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
void *WIN_FUNC VirtualAlloc(void *lpAddress, unsigned int dwSize, unsigned int flAllocationType, unsigned int flProtect) {
|
||||
DEBUG_LOG("VirtualAlloc %p %u %u %u\n", lpAddress, dwSize, flAllocationType, flProtect);
|
||||
|
||||
int prot = PROT_READ | PROT_WRITE;
|
||||
if (flProtect == 0x04 /* PAGE_READWRITE */) {
|
||||
prot = PROT_READ | PROT_WRITE;
|
||||
} else if (flProtect == 0x02 /* PAGE_READONLY */) {
|
||||
prot = PROT_READ;
|
||||
} else if (flProtect == 0x40 /* PAGE_EXECUTE_READWRITE */) {
|
||||
prot = PROT_READ | PROT_WRITE | PROT_EXEC;
|
||||
} else {
|
||||
DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect);
|
||||
}
|
||||
int prot = translateProtect(flProtect);
|
||||
|
||||
int flags = MAP_PRIVATE | MAP_ANONYMOUS; // MAP_ANONYMOUS ensures the memory is zeroed out
|
||||
if (lpAddress != NULL) {
|
||||
@ -1869,6 +1929,51 @@ namespace kernel32 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
BOOL WIN_FUNC VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) {
|
||||
DEBUG_LOG("VirtualProtect %p %zu %u\n", lpAddress, dwSize, flNewProtect);
|
||||
if (!lpAddress || dwSize == 0) {
|
||||
wibo::lastError = ERROR_INVALID_PARAMETER;
|
||||
return FALSE;
|
||||
}
|
||||
if (lpflOldProtect)
|
||||
*lpflOldProtect = flNewProtect;
|
||||
size_t pageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
|
||||
uintptr_t base = reinterpret_cast<uintptr_t>(lpAddress) & ~(pageSize - 1);
|
||||
size_t length = ((reinterpret_cast<uintptr_t>(lpAddress) + dwSize) - base + pageSize - 1) & ~(pageSize - 1);
|
||||
int prot = translateProtect(flNewProtect);
|
||||
if (mprotect(reinterpret_cast<void *>(base), length, prot) != 0) {
|
||||
perror("VirtualProtect/mprotect");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
typedef struct _MEMORY_BASIC_INFORMATION {
|
||||
void *BaseAddress;
|
||||
void *AllocationBase;
|
||||
DWORD AllocationProtect;
|
||||
size_t RegionSize;
|
||||
DWORD State;
|
||||
DWORD Protect;
|
||||
DWORD Type;
|
||||
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
|
||||
|
||||
SIZE_T WIN_FUNC VirtualQuery(const void *lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) {
|
||||
DEBUG_LOG("VirtualQuery %p %zu\n", lpAddress, dwLength);
|
||||
if (!lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION)) {
|
||||
return 0;
|
||||
}
|
||||
memset(lpBuffer, 0, sizeof(MEMORY_BASIC_INFORMATION));
|
||||
lpBuffer->BaseAddress = const_cast<LPVOID>(lpAddress);
|
||||
lpBuffer->AllocationBase = lpBuffer->BaseAddress;
|
||||
lpBuffer->AllocationProtect = 0x04; // PAGE_READWRITE
|
||||
lpBuffer->RegionSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
|
||||
lpBuffer->State = 0x1000; // MEM_COMMIT
|
||||
lpBuffer->Protect = 0x04; // PAGE_READWRITE
|
||||
lpBuffer->Type = 0x20000; // MEM_PRIVATE
|
||||
return sizeof(MEMORY_BASIC_INFORMATION);
|
||||
}
|
||||
|
||||
unsigned int WIN_FUNC GetProcessWorkingSetSize(void *hProcess, unsigned int *lpMinimumWorkingSetSize, unsigned int *lpMaximumWorkingSetSize) {
|
||||
DEBUG_LOG("GetProcessWorkingSetSize\n");
|
||||
// A pointer to a variable that receives the minimum working set size of the specified process, in bytes.
|
||||
@ -1966,6 +2071,11 @@ namespace kernel32 {
|
||||
return uNumber + 10;
|
||||
}
|
||||
|
||||
void WIN_FUNC Sleep(DWORD dwMilliseconds) {
|
||||
DEBUG_LOG("Sleep(%u)\n", dwMilliseconds);
|
||||
usleep(static_cast<useconds_t>(dwMilliseconds) * 1000);
|
||||
}
|
||||
|
||||
unsigned int WIN_FUNC GetACP() {
|
||||
DEBUG_LOG("GetACP\n");
|
||||
// return 65001; // UTF-8
|
||||
@ -2177,7 +2287,21 @@ namespace kernel32 {
|
||||
}
|
||||
|
||||
BOOL WIN_FUNC SetDllDirectoryA(LPCSTR lpPathName) {
|
||||
DEBUG_LOG("STUB: SetDllDirectoryA(%s)\n", lpPathName);
|
||||
DEBUG_LOG("SetDllDirectoryA(%s)\n", lpPathName);
|
||||
if (!lpPathName || lpPathName[0] == '\0') {
|
||||
wibo::clearDllDirectoryOverride();
|
||||
wibo::lastError = ERROR_SUCCESS;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
auto hostPath = files::pathFromWindows(lpPathName);
|
||||
if (hostPath.empty() || !std::filesystem::exists(hostPath)) {
|
||||
wibo::lastError = ERROR_PATH_NOT_FOUND;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wibo::setDllDirectoryOverride(std::filesystem::absolute(hostPath));
|
||||
wibo::lastError = ERROR_SUCCESS;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -2591,6 +2715,9 @@ static void *resolveByName(const char *name) {
|
||||
if (strcmp(name, "EncodePointer") == 0) return (void *) kernel32::EncodePointer;
|
||||
if (strcmp(name, "DecodePointer") == 0) return (void *) kernel32::DecodePointer;
|
||||
if (strcmp(name, "SetDllDirectoryA") == 0) return (void *) kernel32::SetDllDirectoryA;
|
||||
if (strcmp(name, "Sleep") == 0) return (void *) kernel32::Sleep;
|
||||
if (strcmp(name, "VirtualProtect") == 0) return (void *) kernel32::VirtualProtect;
|
||||
if (strcmp(name, "VirtualQuery") == 0) return (void *) kernel32::VirtualQuery;
|
||||
|
||||
// processenv.h
|
||||
if (strcmp(name, "GetCommandLineA") == 0) return (void *) kernel32::GetCommandLineA;
|
||||
|
81
loader.cpp
81
loader.cpp
@ -92,6 +92,14 @@ struct PEHintNameTableEntry {
|
||||
char name[1]; // variable length
|
||||
};
|
||||
|
||||
struct PEBaseRelocationBlock {
|
||||
uint32_t virtualAddress;
|
||||
uint32_t sizeOfBlock;
|
||||
};
|
||||
|
||||
constexpr uint16_t IMAGE_REL_BASED_ABSOLUTE = 0;
|
||||
constexpr uint16_t IMAGE_REL_BASED_HIGHLOW = 3;
|
||||
|
||||
uint16_t read16(FILE *file) {
|
||||
uint16_t v = 0;
|
||||
fread(&v, 2, 1, file);
|
||||
@ -109,6 +117,12 @@ wibo::Executable::Executable() {
|
||||
imageSize = 0;
|
||||
entryPoint = nullptr;
|
||||
rsrcBase = 0;
|
||||
preferredImageBase = 0;
|
||||
relocationDelta = 0;
|
||||
exportDirectoryRVA = 0;
|
||||
exportDirectorySize = 0;
|
||||
relocationDirectoryRVA = 0;
|
||||
relocationDirectorySize = 0;
|
||||
}
|
||||
|
||||
wibo::Executable::~Executable() {
|
||||
@ -150,20 +164,29 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) {
|
||||
long pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
DEBUG_LOG("Page size: %x\n", (unsigned int)pageSize);
|
||||
|
||||
preferredImageBase = header32.imageBase;
|
||||
exportDirectoryRVA = header32.exportTable.virtualAddress;
|
||||
exportDirectorySize = header32.exportTable.size;
|
||||
relocationDirectoryRVA = header32.baseRelocationTable.virtualAddress;
|
||||
relocationDirectorySize = header32.baseRelocationTable.size;
|
||||
|
||||
// Build buffer
|
||||
imageSize = header32.sizeOfImage;
|
||||
if (exec) {
|
||||
imageBuffer = mmap((void *)header32.imageBase, header32.sizeOfImage, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0);
|
||||
} else {
|
||||
imageBuffer = mmap(nullptr, header32.sizeOfImage, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
int prot = PROT_READ | PROT_WRITE;
|
||||
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);
|
||||
}
|
||||
memset(imageBuffer, 0, header32.sizeOfImage);
|
||||
if (imageBuffer == MAP_FAILED) {
|
||||
perror("Image mapping failed!");
|
||||
imageBuffer = 0;
|
||||
imageBuffer = nullptr;
|
||||
return false;
|
||||
}
|
||||
relocationDelta = (intptr_t)((uintptr_t)imageBuffer - (uintptr_t)header32.imageBase);
|
||||
memset(imageBuffer, 0, header32.sizeOfImage);
|
||||
|
||||
// Read the sections
|
||||
fseek(file, offsetToPE + sizeof header + header.sizeOfOptionalHeader, SEEK_SET);
|
||||
@ -191,6 +214,48 @@ 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;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *relocCursor = fromRVA<uint8_t>(relocationDirectoryRVA);
|
||||
uint8_t *relocEnd = relocCursor + relocationDirectorySize;
|
||||
while (relocCursor < relocEnd) {
|
||||
auto *block = reinterpret_cast<PEBaseRelocationBlock *>(relocCursor);
|
||||
if (block->sizeOfBlock < sizeof(PEBaseRelocationBlock) || block->sizeOfBlock > static_cast<uint32_t>(relocEnd - relocCursor)) {
|
||||
break;
|
||||
}
|
||||
if (block->sizeOfBlock == sizeof(PEBaseRelocationBlock)) {
|
||||
break;
|
||||
}
|
||||
size_t entryCount = (block->sizeOfBlock - sizeof(PEBaseRelocationBlock)) / sizeof(uint16_t);
|
||||
auto *entries = reinterpret_cast<uint16_t *>(relocCursor + sizeof(PEBaseRelocationBlock));
|
||||
for (size_t i = 0; i < entryCount; ++i) {
|
||||
uint16_t entry = entries[i];
|
||||
uint16_t type = entry >> 12;
|
||||
uint16_t offset = entry & 0x0FFF;
|
||||
if (type == IMAGE_REL_BASED_ABSOLUTE)
|
||||
continue;
|
||||
uintptr_t target = reinterpret_cast<uintptr_t>(imageBuffer) + block->virtualAddress + offset;
|
||||
switch (type) {
|
||||
case IMAGE_REL_BASED_HIGHLOW: {
|
||||
auto *addr = reinterpret_cast<uint32_t *>(target);
|
||||
*addr += static_cast<uint32_t>(relocationDelta);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DEBUG_LOG("Unhandled relocation type %u at %08x\n", type, block->virtualAddress + offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
relocCursor += block->sizeOfBlock;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exec) {
|
||||
// No need to resolve imports
|
||||
return true;
|
||||
@ -222,8 +287,6 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) {
|
||||
++lookupTable;
|
||||
++addressTable;
|
||||
}
|
||||
freeModule(module);
|
||||
|
||||
++dir;
|
||||
}
|
||||
|
||||
|
156
main.cpp
156
main.cpp
@ -1,15 +1,15 @@
|
||||
#include "common.h"
|
||||
#include "files.h"
|
||||
#include <asm/ldt.h>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include "strutil.h"
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <stdarg.h>
|
||||
#include <vector>
|
||||
#include <asm/ldt.h>
|
||||
#include <charconv>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <stdarg.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <vector>
|
||||
|
||||
uint32_t wibo::lastError = 0;
|
||||
char** wibo::argv;
|
||||
@ -34,145 +34,6 @@ void wibo::debug_log(const char *fmt, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d))
|
||||
#define FOR_256_2(a, b) \
|
||||
FOR_256_3(a, b, 0, 0) FOR_256_3(a, b, 0, 1) FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) \
|
||||
FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) FOR_256_3(a, b, 1, 3) \
|
||||
FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \
|
||||
FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3)
|
||||
#define FOR_256 \
|
||||
FOR_256_2(0, 0) FOR_256_2(0, 1) FOR_256_2(0, 2) FOR_256_2(0, 3) \
|
||||
FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) \
|
||||
FOR_256_2(2, 0) FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) \
|
||||
FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) FOR_256_2(3, 3) \
|
||||
|
||||
static int stubIndex = 0;
|
||||
static char stubDlls[0x100][0x100];
|
||||
static char stubFuncNames[0x100][0x100];
|
||||
|
||||
static void stubBase(int index) {
|
||||
printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void (*stubFuncs[0x100])(void) = {
|
||||
#define FOR_ITER(i) []() { stubBase(i); },
|
||||
FOR_256
|
||||
#undef FOR_ITER
|
||||
};
|
||||
|
||||
#undef FOR_256_3
|
||||
#undef FOR_256_2
|
||||
#undef FOR_256
|
||||
|
||||
static void *resolveMissingFuncName(const char *dllName, const char *funcName) {
|
||||
DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName);
|
||||
assert(stubIndex < 0x100);
|
||||
assert(strlen(dllName) < 0x100);
|
||||
assert(strlen(funcName) < 0x100);
|
||||
strcpy(stubFuncNames[stubIndex], funcName);
|
||||
strcpy(stubDlls[stubIndex], dllName);
|
||||
return (void *)stubFuncs[stubIndex++];
|
||||
}
|
||||
|
||||
static void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) {
|
||||
char buf[16];
|
||||
sprintf(buf, "%d", ordinal);
|
||||
return resolveMissingFuncName(dllName, buf);
|
||||
}
|
||||
|
||||
extern const wibo::Module lib_advapi32;
|
||||
extern const wibo::Module lib_bcrypt;
|
||||
extern const wibo::Module lib_crt;
|
||||
extern const wibo::Module lib_kernel32;
|
||||
extern const wibo::Module lib_lmgr;
|
||||
extern const wibo::Module lib_mscoree;
|
||||
extern const wibo::Module lib_msvcrt;
|
||||
extern const wibo::Module lib_ntdll;
|
||||
extern const wibo::Module lib_ole32;
|
||||
extern const wibo::Module lib_user32;
|
||||
extern const wibo::Module lib_vcruntime;
|
||||
extern const wibo::Module lib_version;
|
||||
const wibo::Module * wibo::modules[] = {
|
||||
&lib_advapi32,
|
||||
&lib_bcrypt,
|
||||
&lib_crt,
|
||||
&lib_kernel32,
|
||||
&lib_lmgr,
|
||||
&lib_mscoree,
|
||||
&lib_msvcrt,
|
||||
&lib_ntdll,
|
||||
&lib_ole32,
|
||||
&lib_user32,
|
||||
&lib_vcruntime,
|
||||
&lib_version,
|
||||
nullptr,
|
||||
};
|
||||
|
||||
HMODULE wibo::loadModule(const char *dllName) {
|
||||
auto *result = new ModuleInfo;
|
||||
result->name = dllName;
|
||||
for (int i = 0; modules[i]; i++) {
|
||||
for (int j = 0; modules[i]->names[j]; j++) {
|
||||
if (strcasecmp(dllName, modules[i]->names[j]) == 0) {
|
||||
result->module = modules[i];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void wibo::freeModule(HMODULE module) { delete static_cast<ModuleInfo *>(module); }
|
||||
|
||||
void *wibo::resolveFuncByName(HMODULE module, const char *funcName) {
|
||||
auto *info = static_cast<ModuleInfo *>(module);
|
||||
assert(info);
|
||||
if (info->module && info->module->byName) {
|
||||
void *func = info->module->byName(funcName);
|
||||
if (func)
|
||||
return func;
|
||||
}
|
||||
return resolveMissingFuncName(info->name.c_str(), funcName);
|
||||
}
|
||||
|
||||
void *wibo::resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) {
|
||||
auto *info = static_cast<ModuleInfo *>(module);
|
||||
assert(info);
|
||||
if (info->module && info->module->byOrdinal) {
|
||||
void *func = info->module->byOrdinal(ordinal);
|
||||
if (func)
|
||||
return func;
|
||||
}
|
||||
return resolveMissingFuncOrdinal(info->name.c_str(), ordinal);
|
||||
}
|
||||
|
||||
wibo::Executable *wibo::executableFromModule(HMODULE module) {
|
||||
if (wibo::isMainModule(module)) {
|
||||
return wibo::mainModule;
|
||||
}
|
||||
|
||||
auto info = static_cast<wibo::ModuleInfo *>(module);
|
||||
if (!info->executable) {
|
||||
DEBUG_LOG("wibo::executableFromModule: loading %s\n", info->name.c_str());
|
||||
auto executable = std::make_unique<wibo::Executable>();
|
||||
const auto path = files::pathFromWindows(info->name.c_str());
|
||||
FILE *f = fopen(path.c_str(), "rb");
|
||||
if (!f) {
|
||||
perror("wibo::executableFromModule");
|
||||
return nullptr;
|
||||
}
|
||||
bool result = executable->loadPE(f, false);
|
||||
fclose(f);
|
||||
if (!result) {
|
||||
DEBUG_LOG("wibo::executableFromModule: failed to load %s\n", path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
info->executable = std::move(executable);
|
||||
}
|
||||
return info->executable.get();
|
||||
}
|
||||
|
||||
struct UNICODE_STRING {
|
||||
unsigned short Length;
|
||||
unsigned short MaximumLength;
|
||||
@ -410,6 +271,8 @@ int main(int argc, char **argv) {
|
||||
wibo::argv = argv + 1;
|
||||
wibo::argc = argc - 1;
|
||||
|
||||
wibo::initializeModuleRegistry();
|
||||
|
||||
wibo::Executable exec;
|
||||
wibo::mainModule = &exec;
|
||||
|
||||
@ -432,6 +295,7 @@ int main(int argc, char **argv) {
|
||||
: "r"(tibSegment), "r"(exec.entryPoint)
|
||||
);
|
||||
DEBUG_LOG("We came back\n");
|
||||
wibo::shutdownModuleRegistry();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
815
module_registry.cpp
Normal file
815
module_registry.cpp
Normal file
@ -0,0 +1,815 @@
|
||||
#include "common.h"
|
||||
#include "files.h"
|
||||
#include "strutil.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
extern const wibo::Module lib_advapi32;
|
||||
extern const wibo::Module lib_bcrypt;
|
||||
extern const wibo::Module lib_crt;
|
||||
extern const wibo::Module lib_kernel32;
|
||||
extern const wibo::Module lib_lmgr;
|
||||
extern const wibo::Module lib_mscoree;
|
||||
extern const wibo::Module lib_msvcrt;
|
||||
extern const wibo::Module lib_ntdll;
|
||||
extern const wibo::Module lib_ole32;
|
||||
extern const wibo::Module lib_user32;
|
||||
extern const wibo::Module lib_vcruntime;
|
||||
extern const wibo::Module lib_version;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr DWORD DLL_PROCESS_DETACH = 0;
|
||||
constexpr DWORD DLL_PROCESS_ATTACH = 1;
|
||||
|
||||
struct PEExportDirectory {
|
||||
uint32_t characteristics;
|
||||
uint32_t timeDateStamp;
|
||||
uint16_t majorVersion;
|
||||
uint16_t minorVersion;
|
||||
uint32_t name;
|
||||
uint32_t base;
|
||||
uint32_t numberOfFunctions;
|
||||
uint32_t numberOfNames;
|
||||
uint32_t addressOfFunctions;
|
||||
uint32_t addressOfNames;
|
||||
uint32_t addressOfNameOrdinals;
|
||||
};
|
||||
|
||||
#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d))
|
||||
#define FOR_256_2(a, b) \
|
||||
FOR_256_3(a, b, 0, 0) \
|
||||
FOR_256_3(a, b, 0, 1) \
|
||||
FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) \
|
||||
FOR_256_3(a, b, 1, 3) FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \
|
||||
FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3)
|
||||
#define FOR_256 \
|
||||
FOR_256_2(0, 0) \
|
||||
FOR_256_2(0, 1) \
|
||||
FOR_256_2(0, 2) FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) \
|
||||
FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) \
|
||||
FOR_256_2(3, 3)
|
||||
|
||||
static int stubIndex = 0;
|
||||
static char stubDlls[0x100][0x100];
|
||||
static char stubFuncNames[0x100][0x100];
|
||||
|
||||
static void stubBase(int index) {
|
||||
printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void (*stubFuncs[0x100])(void) = {
|
||||
#define FOR_ITER(i) []() { stubBase(i); },
|
||||
FOR_256
|
||||
#undef FOR_ITER
|
||||
};
|
||||
|
||||
#undef FOR_256_3
|
||||
#undef FOR_256_2
|
||||
#undef FOR_256
|
||||
|
||||
void *resolveMissingFuncName(const char *dllName, const char *funcName) {
|
||||
DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName);
|
||||
assert(stubIndex < 0x100);
|
||||
assert(strlen(dllName) < 0x100);
|
||||
assert(strlen(funcName) < 0x100);
|
||||
strcpy(stubFuncNames[stubIndex], funcName);
|
||||
strcpy(stubDlls[stubIndex], dllName);
|
||||
return (void *)stubFuncs[stubIndex++];
|
||||
}
|
||||
|
||||
void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) {
|
||||
char buf[16];
|
||||
sprintf(buf, "%d", ordinal);
|
||||
return resolveMissingFuncName(dllName, buf);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
using ModulePtr = std::unique_ptr<wibo::ModuleInfo>;
|
||||
|
||||
struct ModuleRegistry {
|
||||
std::recursive_mutex mutex;
|
||||
std::unordered_map<std::string, ModulePtr> modulesByKey;
|
||||
std::unordered_map<std::string, wibo::ModuleInfo *> modulesByAlias;
|
||||
std::optional<std::filesystem::path> dllDirectory;
|
||||
bool initialized = false;
|
||||
std::unordered_map<void *, wibo::ModuleInfo *> onExitTables;
|
||||
};
|
||||
|
||||
ModuleRegistry ®istry() {
|
||||
static ModuleRegistry reg;
|
||||
return reg;
|
||||
}
|
||||
|
||||
std::string toLowerCopy(const std::string &value) {
|
||||
std::string out = value;
|
||||
std::transform(out.begin(), out.end(), out.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string normalizeAlias(const std::string &value) {
|
||||
std::string out = value;
|
||||
std::replace(out.begin(), out.end(), '/', '\\');
|
||||
std::transform(out.begin(), out.end(), out.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
struct ParsedModuleName {
|
||||
std::string original;
|
||||
std::string directory; // Windows-style directory component (may be empty)
|
||||
std::string base;
|
||||
bool hasExtension = false;
|
||||
bool endsWithDot = false;
|
||||
};
|
||||
|
||||
ParsedModuleName parseModuleName(const std::string &name) {
|
||||
ParsedModuleName parsed;
|
||||
parsed.original = name;
|
||||
parsed.base = name;
|
||||
std::string sanitized = name;
|
||||
std::replace(sanitized.begin(), sanitized.end(), '/', '\\');
|
||||
auto sep = sanitized.find_last_of('\\');
|
||||
if (sep != std::string::npos) {
|
||||
parsed.directory = sanitized.substr(0, sep);
|
||||
parsed.base = sanitized.substr(sep + 1);
|
||||
} else {
|
||||
parsed.base = sanitized;
|
||||
}
|
||||
parsed.endsWithDot = !parsed.base.empty() && parsed.base.back() == '.';
|
||||
parsed.hasExtension = (!parsed.endsWithDot) && parsed.base.find('.') != std::string::npos;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
std::vector<std::string> candidateModuleNames(const ParsedModuleName &parsed) {
|
||||
std::vector<std::string> names;
|
||||
if (!parsed.base.empty()) {
|
||||
names.push_back(parsed.base);
|
||||
if (!parsed.hasExtension && !parsed.endsWithDot) {
|
||||
names.push_back(parsed.base + ".dll");
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
std::string normalizedBaseKey(const ParsedModuleName &parsed) {
|
||||
if (parsed.base.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
std::string base = parsed.base;
|
||||
if (!parsed.hasExtension && !parsed.endsWithDot) {
|
||||
base += ".dll";
|
||||
}
|
||||
return normalizeAlias(base);
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> findCaseInsensitiveFile(const std::filesystem::path &directory,
|
||||
const std::string &filename) {
|
||||
std::error_code ec;
|
||||
if (directory.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string lower = toLowerCopy(filename);
|
||||
for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) {
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
const std::string candidate = toLowerCopy(entry.path().filename().string());
|
||||
if (candidate == lower) {
|
||||
return std::filesystem::canonical(entry.path(), ec);
|
||||
}
|
||||
}
|
||||
auto direct = directory / filename;
|
||||
if (std::filesystem::exists(direct, ec)) {
|
||||
return std::filesystem::canonical(direct, ec);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> combineAndFind(const std::filesystem::path &directory,
|
||||
const std::string &filename) {
|
||||
if (filename.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (directory.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return findCaseInsensitiveFile(directory, filename);
|
||||
}
|
||||
|
||||
std::filesystem::path canonicalPath(const std::filesystem::path &path) {
|
||||
std::error_code ec;
|
||||
auto canonical = std::filesystem::weakly_canonical(path, ec);
|
||||
if (!ec) {
|
||||
return canonical;
|
||||
}
|
||||
return std::filesystem::absolute(path);
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> collectSearchDirectories(bool alteredSearchPath) {
|
||||
std::vector<std::filesystem::path> dirs;
|
||||
std::unordered_set<std::string> seen;
|
||||
auto addDirectory = [&](const std::filesystem::path &dir) {
|
||||
if (dir.empty())
|
||||
return;
|
||||
std::error_code ec;
|
||||
auto canonical = std::filesystem::weakly_canonical(dir, ec);
|
||||
if (ec) {
|
||||
canonical = std::filesystem::absolute(dir, ec);
|
||||
}
|
||||
if (ec)
|
||||
return;
|
||||
if (!std::filesystem::exists(canonical, ec) || ec)
|
||||
return;
|
||||
std::string key = toLowerCopy(canonical.string());
|
||||
if (seen.insert(key).second) {
|
||||
dirs.push_back(canonical);
|
||||
}
|
||||
};
|
||||
|
||||
auto ® = registry();
|
||||
|
||||
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 (reg.dllDirectory.has_value()) {
|
||||
addDirectory(*reg.dllDirectory);
|
||||
}
|
||||
|
||||
addDirectory(files::pathFromWindows("Z:/Windows/System32"));
|
||||
addDirectory(files::pathFromWindows("Z:/Windows"));
|
||||
|
||||
if (!alteredSearchPath) {
|
||||
addDirectory(std::filesystem::current_path());
|
||||
}
|
||||
|
||||
if (const char *envPath = std::getenv("PATH")) {
|
||||
std::string pathList = envPath;
|
||||
size_t start = 0;
|
||||
while (start <= pathList.size()) {
|
||||
size_t end = pathList.find_first_of(":;", start);
|
||||
if (end == std::string::npos) {
|
||||
end = pathList.size();
|
||||
}
|
||||
if (end > start) {
|
||||
auto piece = pathList.substr(start, end - start);
|
||||
if (!piece.empty()) {
|
||||
std::filesystem::path candidate(piece);
|
||||
if (piece.find(':') != std::string::npos || piece.find('\\') != std::string::npos) {
|
||||
auto converted = files::pathFromWindows(piece.c_str());
|
||||
if (!converted.empty()) {
|
||||
candidate = converted;
|
||||
}
|
||||
}
|
||||
addDirectory(candidate);
|
||||
}
|
||||
}
|
||||
if (end == pathList.size()) {
|
||||
break;
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return dirs;
|
||||
}
|
||||
std::optional<std::filesystem::path> resolveModuleOnDisk(const std::string &requestedName, bool alteredSearchPath) {
|
||||
ParsedModuleName parsed = parseModuleName(requestedName);
|
||||
auto names = candidateModuleNames(parsed);
|
||||
|
||||
if (!parsed.directory.empty()) {
|
||||
for (const auto &candidate : names) {
|
||||
auto combined = parsed.directory + "\\" + candidate;
|
||||
auto posixPath = files::pathFromWindows(combined.c_str());
|
||||
if (!posixPath.empty()) {
|
||||
auto resolved = findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(),
|
||||
std::filesystem::path(posixPath).filename().string());
|
||||
if (resolved) {
|
||||
return canonicalPath(*resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto dirs = collectSearchDirectories(alteredSearchPath);
|
||||
for (const auto &dir : dirs) {
|
||||
for (const auto &candidate : names) {
|
||||
auto resolved = combineAndFind(dir, candidate);
|
||||
if (resolved) {
|
||||
return canonicalPath(*resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string storageKeyForPath(const std::filesystem::path &path) {
|
||||
return normalizeAlias(files::pathToWindows(canonicalPath(path)));
|
||||
}
|
||||
|
||||
std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; }
|
||||
|
||||
wibo::ModuleInfo *findByAlias(const std::string &alias) {
|
||||
auto ® = registry();
|
||||
auto it = reg.modulesByAlias.find(alias);
|
||||
if (it != reg.modulesByAlias.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void registerAlias(const std::string &alias, wibo::ModuleInfo *info) {
|
||||
if (alias.empty() || !info) {
|
||||
return;
|
||||
}
|
||||
auto ® = registry();
|
||||
if (reg.modulesByAlias.find(alias) == reg.modulesByAlias.end()) {
|
||||
reg.modulesByAlias[alias] = info;
|
||||
}
|
||||
}
|
||||
|
||||
void registerBuiltinModule(const wibo::Module *module) {
|
||||
if (!module) {
|
||||
return;
|
||||
}
|
||||
ModulePtr entry = std::make_unique<wibo::ModuleInfo>();
|
||||
entry->module = module;
|
||||
entry->refCount = UINT_MAX;
|
||||
entry->originalName = module->names[0] ? module->names[0] : "";
|
||||
entry->normalizedName = normalizedBaseKey(parseModuleName(entry->originalName));
|
||||
entry->exportsInitialized = true;
|
||||
auto storageKey = storageKeyForBuiltin(entry->normalizedName);
|
||||
auto raw = entry.get();
|
||||
auto ® = registry();
|
||||
reg.modulesByKey[storageKey] = std::move(entry);
|
||||
|
||||
for (size_t i = 0; module->names[i]; ++i) {
|
||||
registerAlias(normalizeAlias(module->names[i]), raw);
|
||||
ParsedModuleName parsed = parseModuleName(module->names[i]);
|
||||
registerAlias(normalizedBaseKey(parsed), raw);
|
||||
}
|
||||
}
|
||||
|
||||
void callDllMain(wibo::ModuleInfo &info, DWORD reason) {
|
||||
if (!info.entryPoint || info.module) {
|
||||
return;
|
||||
}
|
||||
using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID);
|
||||
auto dllMain = reinterpret_cast<DllMainFunc>(info.entryPoint);
|
||||
if (!dllMain) {
|
||||
return;
|
||||
}
|
||||
if (reason == 1) {
|
||||
if (info.processAttachCalled) {
|
||||
return;
|
||||
}
|
||||
info.processAttachCalled = true;
|
||||
BOOL result = dllMain(reinterpret_cast<HMODULE>(info.imageBase), reason, nullptr);
|
||||
info.processAttachSucceeded = result != 0;
|
||||
} else if (reason == 0) {
|
||||
if (info.processAttachCalled && info.processAttachSucceeded) {
|
||||
dllMain(reinterpret_cast<HMODULE>(info.imageBase), reason, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ensureInitialized() {
|
||||
auto ® = registry();
|
||||
if (reg.initialized) {
|
||||
return;
|
||||
}
|
||||
reg.initialized = true;
|
||||
|
||||
const wibo::Module *builtins[] = {
|
||||
&lib_advapi32, &lib_bcrypt, &lib_crt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_msvcrt,
|
||||
&lib_ntdll, &lib_ole32, &lib_user32, &lib_vcruntime, &lib_version, nullptr,
|
||||
};
|
||||
|
||||
for (const wibo::Module **module = builtins; *module; ++module) {
|
||||
registerBuiltinModule(*module);
|
||||
}
|
||||
}
|
||||
|
||||
void registerExternalModuleAliases(const std::string &requestedName, const std::filesystem::path &resolvedPath,
|
||||
wibo::ModuleInfo *info) {
|
||||
ParsedModuleName parsed = parseModuleName(requestedName);
|
||||
registerAlias(normalizedBaseKey(parsed), info);
|
||||
registerAlias(normalizeAlias(requestedName), info);
|
||||
registerAlias(storageKeyForPath(resolvedPath), info);
|
||||
}
|
||||
|
||||
wibo::ModuleInfo *moduleFromAddress(void *addr) {
|
||||
if (!addr)
|
||||
return nullptr;
|
||||
auto ® = registry();
|
||||
for (auto &pair : reg.modulesByKey) {
|
||||
wibo::ModuleInfo *info = pair.second.get();
|
||||
if (!info)
|
||||
continue;
|
||||
uint8_t *base = nullptr;
|
||||
size_t size = 0;
|
||||
if (info->imageBase && info->imageSize) {
|
||||
base = static_cast<uint8_t *>(info->imageBase);
|
||||
size = info->imageSize;
|
||||
} else if (info->executable) {
|
||||
base = static_cast<uint8_t *>(info->executable->imageBuffer);
|
||||
size = info->executable->imageSize;
|
||||
}
|
||||
if (!base || size == 0)
|
||||
continue;
|
||||
uint8_t *ptr = static_cast<uint8_t *>(addr);
|
||||
if (ptr >= base && ptr < base + size) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ensureExportsInitialized(wibo::ModuleInfo &info) {
|
||||
if (info.module || info.exportsInitialized)
|
||||
return;
|
||||
if (!info.executable)
|
||||
return;
|
||||
auto *exe = info.executable.get();
|
||||
if (!exe->exportDirectoryRVA || !exe->exportDirectorySize) {
|
||||
info.exportsInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto *dir = exe->fromRVA<PEExportDirectory>(exe->exportDirectoryRVA);
|
||||
info.exportOrdinalBase = dir->base;
|
||||
uint32_t functionCount = dir->numberOfFunctions;
|
||||
info.exportsByOrdinal.assign(functionCount, nullptr);
|
||||
if (functionCount) {
|
||||
auto *functions = exe->fromRVA<uint32_t>(dir->addressOfFunctions);
|
||||
for (uint32_t i = 0; i < functionCount; ++i) {
|
||||
uint32_t rva = functions[i];
|
||||
if (!rva) {
|
||||
continue;
|
||||
}
|
||||
if (rva >= exe->exportDirectoryRVA && rva < exe->exportDirectoryRVA + exe->exportDirectorySize) {
|
||||
const char *forward = exe->fromRVA<const char>(rva);
|
||||
info.exportsByOrdinal[i] = resolveMissingFuncName(info.originalName.c_str(), forward);
|
||||
} else {
|
||||
info.exportsByOrdinal[i] = exe->fromRVA<void>(rva);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nameCount = dir->numberOfNames;
|
||||
if (nameCount) {
|
||||
auto *names = exe->fromRVA<uint32_t>(dir->addressOfNames);
|
||||
auto *ordinals = exe->fromRVA<uint16_t>(dir->addressOfNameOrdinals);
|
||||
for (uint32_t i = 0; i < nameCount; ++i) {
|
||||
uint16_t index = ordinals[i];
|
||||
uint16_t ordinal = static_cast<uint16_t>(dir->base + index);
|
||||
if (index < info.exportsByOrdinal.size()) {
|
||||
const char *namePtr = exe->fromRVA<const char>(names[i]);
|
||||
info.exportNameToOrdinal[std::string(namePtr)] = ordinal;
|
||||
}
|
||||
}
|
||||
}
|
||||
info.exportsInitialized = true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wibo {
|
||||
|
||||
void initializeModuleRegistry() {
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
ensureInitialized();
|
||||
}
|
||||
|
||||
void shutdownModuleRegistry() {
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
for (auto &pair : registry().modulesByKey) {
|
||||
ModuleInfo *info = pair.second.get();
|
||||
if (!info || info->module) {
|
||||
continue;
|
||||
}
|
||||
runPendingOnExit(*info);
|
||||
if (info->processAttachCalled && info->processAttachSucceeded) {
|
||||
callDllMain(*info, DLL_PROCESS_DETACH);
|
||||
}
|
||||
}
|
||||
registry().modulesByKey.clear();
|
||||
registry().modulesByAlias.clear();
|
||||
registry().dllDirectory.reset();
|
||||
registry().initialized = false;
|
||||
registry().onExitTables.clear();
|
||||
}
|
||||
|
||||
ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast<ModuleInfo *>(module); }
|
||||
|
||||
void setDllDirectoryOverride(const std::filesystem::path &path) {
|
||||
auto canonical = canonicalPath(path);
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
registry().dllDirectory = canonical;
|
||||
}
|
||||
|
||||
void clearDllDirectoryOverride() {
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
registry().dllDirectory.reset();
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> dllDirectoryOverride() {
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
return registry().dllDirectory;
|
||||
}
|
||||
|
||||
void registerOnExitTable(void *table) {
|
||||
if (!table)
|
||||
return;
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
ensureInitialized();
|
||||
auto ® = registry();
|
||||
if (reg.onExitTables.find(table) == reg.onExitTables.end()) {
|
||||
if (auto *info = moduleFromAddress(table)) {
|
||||
reg.onExitTables[table] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addOnExitFunction(void *table, void (*func)()) {
|
||||
if (!func)
|
||||
return;
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
auto ® = registry();
|
||||
ModuleInfo *info = nullptr;
|
||||
auto it = reg.onExitTables.find(table);
|
||||
if (it != reg.onExitTables.end()) {
|
||||
info = it->second;
|
||||
} else if (table) {
|
||||
info = moduleFromAddress(table);
|
||||
if (info)
|
||||
reg.onExitTables[table] = info;
|
||||
}
|
||||
if (info) {
|
||||
info->onExitFunctions.push_back(reinterpret_cast<void *>(func));
|
||||
}
|
||||
}
|
||||
|
||||
void runPendingOnExit(ModuleInfo &info) {
|
||||
for (auto it = info.onExitFunctions.rbegin(); it != info.onExitFunctions.rend(); ++it) {
|
||||
auto fn = reinterpret_cast<void (*)(void)>(*it);
|
||||
if (fn) {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
info.onExitFunctions.clear();
|
||||
}
|
||||
|
||||
void executeOnExitTable(void *table) {
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
auto ® = registry();
|
||||
ModuleInfo *info = nullptr;
|
||||
if (table) {
|
||||
auto it = reg.onExitTables.find(table);
|
||||
if (it != reg.onExitTables.end()) {
|
||||
info = it->second;
|
||||
reg.onExitTables.erase(it);
|
||||
} else {
|
||||
info = moduleFromAddress(table);
|
||||
}
|
||||
}
|
||||
if (info) {
|
||||
runPendingOnExit(*info);
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE findLoadedModule(const char *name) {
|
||||
if (!name) {
|
||||
return nullptr;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
ensureInitialized();
|
||||
ParsedModuleName parsed = parseModuleName(name);
|
||||
std::string alias = normalizedBaseKey(parsed);
|
||||
ModuleInfo *info = findByAlias(alias);
|
||||
if (!info) {
|
||||
info = findByAlias(normalizeAlias(name));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
HMODULE loadModule(const char *dllName) {
|
||||
if (!dllName) {
|
||||
lastError = ERROR_INVALID_PARAMETER;
|
||||
return nullptr;
|
||||
}
|
||||
std::string requested(dllName);
|
||||
DEBUG_LOG("loadModule(%s)\n", requested.c_str());
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
ensureInitialized();
|
||||
|
||||
ParsedModuleName parsed = parseModuleName(requested);
|
||||
std::string alias = normalizedBaseKey(parsed);
|
||||
ModuleInfo *existing = findByAlias(alias);
|
||||
if (!existing) {
|
||||
existing = findByAlias(normalizeAlias(requested));
|
||||
}
|
||||
if (existing) {
|
||||
DEBUG_LOG(" found existing module alias %s\n", alias.c_str());
|
||||
if (existing->refCount != UINT_MAX) {
|
||||
existing->refCount++;
|
||||
}
|
||||
lastError = ERROR_SUCCESS;
|
||||
return existing;
|
||||
}
|
||||
|
||||
auto resolvedPath = resolveModuleOnDisk(requested, false);
|
||||
if (!resolvedPath) {
|
||||
DEBUG_LOG(" module not found on disk\n");
|
||||
lastError = ERROR_MOD_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string key = storageKeyForPath(*resolvedPath);
|
||||
auto ® = registry();
|
||||
auto it = reg.modulesByKey.find(key);
|
||||
if (it != reg.modulesByKey.end()) {
|
||||
ModuleInfo *info = it->second.get();
|
||||
info->refCount++;
|
||||
registerExternalModuleAliases(requested, *resolvedPath, info);
|
||||
lastError = ERROR_SUCCESS;
|
||||
return info;
|
||||
}
|
||||
|
||||
FILE *file = fopen(resolvedPath->c_str(), "rb");
|
||||
if (!file) {
|
||||
perror("loadModule");
|
||||
lastError = ERROR_MOD_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto executable = std::make_unique<Executable>();
|
||||
if (!executable->loadPE(file, true)) {
|
||||
DEBUG_LOG(" loadPE failed for %s\n", resolvedPath->c_str());
|
||||
fclose(file);
|
||||
lastError = ERROR_BAD_EXE_FORMAT;
|
||||
return nullptr;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
ModulePtr info = std::make_unique<ModuleInfo>();
|
||||
info->module = nullptr;
|
||||
info->originalName = requested;
|
||||
info->normalizedName = alias;
|
||||
info->resolvedPath = *resolvedPath;
|
||||
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(requested, *resolvedPath, raw);
|
||||
ensureExportsInitialized(*raw);
|
||||
|
||||
callDllMain(*raw, DLL_PROCESS_ATTACH);
|
||||
lastError = ERROR_SUCCESS;
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
void freeModule(HMODULE module) {
|
||||
if (!module) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock(registry().mutex);
|
||||
ModuleInfo *info = moduleInfoFromHandle(module);
|
||||
if (!info || info->refCount == UINT_MAX) {
|
||||
return;
|
||||
}
|
||||
if (info->refCount == 0) {
|
||||
return;
|
||||
}
|
||||
info->refCount--;
|
||||
if (info->refCount == 0) {
|
||||
auto ® = registry();
|
||||
for (auto it = reg.onExitTables.begin(); it != reg.onExitTables.end();) {
|
||||
if (it->second == info) {
|
||||
it = reg.onExitTables.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
runPendingOnExit(*info);
|
||||
callDllMain(*info, DLL_PROCESS_DETACH);
|
||||
std::string key = info->resolvedPath.empty() ? storageKeyForBuiltin(info->normalizedName)
|
||||
: storageKeyForPath(info->resolvedPath);
|
||||
reg.modulesByKey.erase(key);
|
||||
for (auto it = reg.modulesByAlias.begin(); it != reg.modulesByAlias.end();) {
|
||||
if (it->second == info) {
|
||||
it = reg.modulesByAlias.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void *resolveFuncByName(HMODULE module, const char *funcName) {
|
||||
ModuleInfo *info = moduleInfoFromHandle(module);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
if (info->module && info->module->byName) {
|
||||
void *func = info->module->byName(funcName);
|
||||
if (func) {
|
||||
return func;
|
||||
}
|
||||
}
|
||||
ensureExportsInitialized(*info);
|
||||
if (!info->module) {
|
||||
auto it = info->exportNameToOrdinal.find(funcName);
|
||||
if (it != info->exportNameToOrdinal.end()) {
|
||||
return resolveFuncByOrdinal(module, it->second);
|
||||
}
|
||||
}
|
||||
return resolveMissingFuncName(info->originalName.c_str(), funcName);
|
||||
}
|
||||
|
||||
void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) {
|
||||
ModuleInfo *info = moduleInfoFromHandle(module);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
if (info->module && info->module->byOrdinal) {
|
||||
void *func = info->module->byOrdinal(ordinal);
|
||||
if (func) {
|
||||
return func;
|
||||
}
|
||||
}
|
||||
if (!info->module) {
|
||||
ensureExportsInitialized(*info);
|
||||
if (!info->exportsByOrdinal.empty() && ordinal >= info->exportOrdinalBase) {
|
||||
size_t index = static_cast<size_t>(ordinal - info->exportOrdinalBase);
|
||||
if (index < info->exportsByOrdinal.size()) {
|
||||
void *addr = info->exportsByOrdinal[index];
|
||||
if (addr) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolveMissingFuncOrdinal(info->originalName.c_str(), ordinal);
|
||||
}
|
||||
|
||||
Executable *executableFromModule(HMODULE module) {
|
||||
if (isMainModule(module)) {
|
||||
return mainModule;
|
||||
}
|
||||
ModuleInfo *info = moduleInfoFromHandle(module);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!info->executable && !info->resolvedPath.empty()) {
|
||||
FILE *file = fopen(info->resolvedPath.c_str(), "rb");
|
||||
if (!file) {
|
||||
perror("executableFromModule");
|
||||
return nullptr;
|
||||
}
|
||||
auto executable = std::make_unique<Executable>();
|
||||
if (!executable->loadPE(file, false)) {
|
||||
DEBUG_LOG("executableFromModule: failed to load %s\n", info->resolvedPath.c_str());
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
}
|
||||
fclose(file);
|
||||
info->executable = std::move(executable);
|
||||
}
|
||||
return info->executable.get();
|
||||
}
|
||||
|
||||
} // namespace wibo
|
20
test/Makefile
Normal file
20
test/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
CC = i686-w64-mingw32-gcc
|
||||
CFLAGS = -Wall -Wextra -O2
|
||||
|
||||
DLL_SRC = external_exports.c
|
||||
EXE_SRC = test_external_dll.c
|
||||
DLL = external_exports.dll
|
||||
EXE = test_external_dll.exe
|
||||
|
||||
all: $(DLL) $(EXE)
|
||||
|
||||
$(DLL): $(DLL_SRC)
|
||||
$(CC) $(CFLAGS) -shared -o $@ $<
|
||||
|
||||
$(EXE): $(EXE_SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(DLL) $(EXE)
|
||||
|
||||
.PHONY: all clean
|
22
test/external_exports.c
Normal file
22
test/external_exports.c
Normal file
@ -0,0 +1,22 @@
|
||||
#include <windows.h>
|
||||
|
||||
static int attached = 0;
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
|
||||
(void) hinstDLL;
|
||||
(void) lpReserved;
|
||||
if (fdwReason == DLL_PROCESS_ATTACH) {
|
||||
attached = 1;
|
||||
} else if (fdwReason == DLL_PROCESS_DETACH) {
|
||||
attached = 2;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall add_numbers(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall was_attached(void) {
|
||||
return attached;
|
||||
}
|
32
test/test_external_dll.c
Normal file
32
test/test_external_dll.c
Normal file
@ -0,0 +1,32 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
typedef int (__stdcall *add_numbers_fn)(int, int);
|
||||
typedef int (__stdcall *was_attached_fn)(void);
|
||||
|
||||
HMODULE mod = LoadLibraryA("external_exports.dll");
|
||||
if (!mod) {
|
||||
printf("LoadLibraryA failed: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
add_numbers_fn add_numbers = (add_numbers_fn)GetProcAddress(mod, "add_numbers@8");
|
||||
was_attached_fn was_attached = (was_attached_fn)GetProcAddress(mod, "was_attached@0");
|
||||
if (!add_numbers || !was_attached) {
|
||||
printf("GetProcAddress failed: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sum = add_numbers(2, 40);
|
||||
int attached = was_attached();
|
||||
|
||||
printf("sum=%d attached=%d\n", sum, attached);
|
||||
|
||||
if (!FreeLibrary(mod)) {
|
||||
printf("FreeLibrary failed: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (sum == 42 && attached == 1) ? 0 : 2;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user