Fix `TlsGetValue` & more (#48)

`TlsGetValue` disambiguates 0 and an error by relying on `GetLastError`. Depending on the program state, `GetLastError` could be non-0, even though `TlsGetValue` succeeded. Resolve this by always setting `wibo::lastError`. This matches the behavior described by the documentation.

Additionally, when reading resources, later versions of mwcc and mwld call `GetModuleHandleA` with the program path, and then call `LoadStringA` on that handle. Support this behavior by _actually_ loading the PE at the path passed in to `GetModuleHandleA`, instead of assuming it's the current program.

(This is especially useful because sjiswrap relies on overriding `GetModuleFileNameA`, so the wrapped program reads its own resources, rather than sjiswrap's.)

Other small changes:
- Add ms-win-crt `exit` & run atexit funcs
- Implements vcruntime `memmove`
- Implements kernel32 `GetModuleFileNameA`
This commit is contained in:
Luke Street 2023-10-01 23:56:35 -04:00 committed by GitHub
parent 8a6aacb82d
commit c4de05946d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 145 additions and 31 deletions

View File

@ -7,6 +7,7 @@
#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.
@ -53,6 +54,7 @@ typedef unsigned char BYTE;
#define ERROR_HANDLE_EOF 38
#define ERROR_NOT_SUPPORTED 50
#define ERROR_INVALID_PARAMETER 87
#define ERROR_INSUFFICIENT_BUFFER 122
#define ERROR_NEGATIVE_SEEK 131
#define ERROR_ALREADY_EXISTS 183
@ -98,7 +100,7 @@ namespace wibo {
struct Executable {
Executable();
~Executable();
bool loadPE(FILE *file);
bool loadPE(FILE *file, bool exec);
void *imageBuffer;
size_t imageSize;
@ -115,6 +117,20 @@ namespace wibo {
return fromRVA<T>((uint32_t) rva);
}
};
struct ModuleInfo {
std::string name;
const wibo::Module* module = nullptr;
std::unique_ptr<wibo::Executable> executable;
};
extern Executable *mainModule;
}
Executable *executableFromModule(HMODULE module);
/**
* HMODULE will be `nullptr` or `mainModule->imageBuffer` 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;
}
} // namespace wibo

View File

@ -1,5 +1,7 @@
#include "common.h"
#include <vector>
typedef void (*_PVFV)();
typedef int (*_PIFV)();
@ -19,6 +21,8 @@ namespace crt {
int _commode = 0;
std::vector<_PVFV> atexitFuncs;
void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) {
do {
if (_PVFV pfn = *++ppfn) {
@ -48,7 +52,8 @@ int WIN_ENTRY _set_fmode(int mode) {
int *WIN_ENTRY __p__commode() { return &_commode; }
int WIN_ENTRY _crt_atexit(void (*func)()) {
DEBUG_LOG("STUB: _crt_atexit(%p)\n", func);
DEBUG_LOG("_crt_atexit(%p)\n", func);
atexitFuncs.push_back(func);
return 0;
}
@ -85,6 +90,15 @@ int *WIN_ENTRY __p___argc() { return &wibo::argc; }
size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); }
void WIN_ENTRY exit(int status) {
DEBUG_LOG("exit(%i)\n", status);
for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) {
DEBUG_LOG("Calling atexit function %p\n", *it);
(*it)();
}
::exit(status);
}
} // namespace crt
static void *resolveByName(const char *name) {
@ -118,6 +132,8 @@ static void *resolveByName(const char *name) {
return (void *)crt::__p___argc;
if (strcmp(name, "strlen") == 0)
return (void *)crt::strlen;
if (strcmp(name, "exit") == 0)
return (void *)crt::exit;
return nullptr;
}

View File

@ -250,14 +250,14 @@ namespace kernel32 {
unsigned int WIN_FUNC WaitForSingleObject(void *hHandle, unsigned int dwMilliseconds) {
DEBUG_LOG("WaitForSingleObject (%u)\n", dwMilliseconds);
// TODO - wait on other objects?
// TODO: wait for less than forever
assert(dwMilliseconds == 0xffffffff);
assert(dwMilliseconds == 0xffffffff);
processes::Process* process = processes::processFromHandle(hHandle, false);
int status;
waitpid(process->pid, &status, 0);
@ -268,7 +268,7 @@ namespace kernel32 {
// Specific exit codes don't really map onto any of these situations - we just know it's bad.
// Specify a non-zero exit code to alert our parent process something's gone wrong.
DEBUG_LOG("WaitForSingleObject: Child process exited abnormally - returning exit code 1.");
process->exitCode = 1;
process->exitCode = 1;
}
return 0;
@ -366,30 +366,42 @@ namespace kernel32 {
}
}
DEBUG_LOG("...returning nothing\n");
return 0xFFFFFFFF;
wibo::lastError = 1;
return 0xFFFFFFFF; // TLS_OUT_OF_INDEXES
}
unsigned int WIN_FUNC TlsFree(unsigned int dwTlsIndex) {
DEBUG_LOG("TlsFree(%u)\n", dwTlsIndex);
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
tlsValuesUsed[dwTlsIndex] = false;
return 1;
} else {
wibo::lastError = 1;
return 0;
}
}
void *WIN_FUNC TlsGetValue(unsigned int dwTlsIndex) {
// DEBUG_LOG("TlsGetValue(%u)\n", dwTlsIndex);
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex])
return tlsValues[dwTlsIndex];
else
return 0;
// DEBUG_LOG("TlsGetValue(%u)", dwTlsIndex);
void *result = nullptr;
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
result = tlsValues[dwTlsIndex];
// See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-TlsGetValue#return-value
wibo::lastError = ERROR_SUCCESS;
} else {
wibo::lastError = 1;
}
// DEBUG_LOG(" -> %p\n", result);
return result;
}
unsigned int WIN_FUNC TlsSetValue(unsigned int dwTlsIndex, void *lpTlsValue) {
// DEBUG_LOG("TlsSetValue(%u, %p)\n", dwTlsIndex, lpTlsValue);
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
tlsValues[dwTlsIndex] = lpTlsValue;
return 1;
} else {
wibo::lastError = 1;
return 0;
}
}
@ -1303,16 +1315,42 @@ namespace kernel32 {
}
}
unsigned int WIN_FUNC GetModuleFileNameA(void* hModule, char* lpFilename, unsigned int nSize) {
DWORD WIN_FUNC GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) {
DEBUG_LOG("GetModuleFileNameA (hModule=%p, nSize=%i)\n", hModule, nSize);
if (lpFilename == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return 0;
}
*lpFilename = 0; // just NUL terminate
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);
} else {
path = static_cast<wibo::ModuleInfo *>(hModule)->name;
}
const size_t len = path.size();
if (nSize == 0) {
wibo::lastError = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
wibo::lastError = 0;
return 0;
const size_t copyLen = std::min(len, nSize - 1);
memcpy(lpFilename, path.c_str(), copyLen);
if (copyLen < nSize) {
lpFilename[copyLen] = 0;
}
if (copyLen < len) {
wibo::lastError = ERROR_INSUFFICIENT_BUFFER;
return nSize;
}
wibo::lastError = ERROR_SUCCESS;
return copyLen;
}
unsigned int WIN_FUNC GetModuleFileNameW(void* hModule, uint16_t* lpFilename, unsigned int nSize) {
DWORD WIN_FUNC GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) {
DEBUG_LOG("GetModuleFileNameW (hModule=%p, nSize=%i)\n", hModule, nSize);
*lpFilename = 0; // just NUL terminate

View File

@ -57,8 +57,7 @@ namespace user32 {
return (unsigned int*)(rsrcBase + langEntry);
}
static const char *getStringFromTable(unsigned int uID) {
wibo::Executable *mod = wibo::mainModule;
static const char *getStringFromTable(wibo::Executable *mod, unsigned int uID) {
unsigned int tableID = (uID >> 4) + 1;
unsigned int entryID = uID & 15;
unsigned int* stringTable = getResourceByID(mod, 6, tableID, 1033);
@ -82,7 +81,11 @@ namespace user32 {
int WIN_FUNC LoadStringA(void* hInstance, unsigned int uID, char* lpBuffer, int cchBufferMax) {
DEBUG_LOG("LoadStringA %p %d %d\n", hInstance, uID, cchBufferMax);
const char* s = getStringFromTable(uID);
wibo::Executable *mod = wibo::executableFromModule(hInstance);
if (!mod) {
return 0;
}
const char* s = getStringFromTable(mod, uID);
if (!s) {
return 0;
}

View File

@ -8,6 +8,8 @@ void *WIN_ENTRY memset(void *dest, int ch, size_t count) { return ::memset(dest,
int WIN_ENTRY memcmp(const void *buf1, const void *buf2, size_t count) { return ::memcmp(buf1, buf2, count); }
void *WIN_ENTRY memmove(void *dest, const void *src, size_t count) { return ::memmove(dest, src, count); }
} // namespace vcruntime
static void *resolveByName(const char *name) {
@ -17,6 +19,8 @@ static void *resolveByName(const char *name) {
return (void *)vcruntime::memset;
if (strcmp(name, "memcmp") == 0)
return (void *)vcruntime::memcmp;
if (strcmp(name, "memmove") == 0)
return (void *)vcruntime::memmove;
return nullptr;
}

View File

@ -116,7 +116,13 @@ wibo::Executable::~Executable() {
}
}
bool wibo::Executable::loadPE(FILE *file) {
/**
* Load a PE file into memory.
*
* @param file The file to load.
* @param exec Whether to make the loaded image executable.
*/
bool wibo::Executable::loadPE(FILE *file, bool exec) {
// Skip to PE header
fseek(file, 0x3C, SEEK_SET);
uint32_t offsetToPE = read32(file);
@ -145,7 +151,12 @@ bool wibo::Executable::loadPE(FILE *file) {
// Build buffer
imageSize = header32.sizeOfImage;
imageBuffer = mmap((void *) header32.imageBase, header32.sizeOfImage, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0);
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);
}
memset(imageBuffer, 0, header32.sizeOfImage);
if (imageBuffer == MAP_FAILED) {
perror("Image mapping failed!");
@ -165,7 +176,7 @@ bool wibo::Executable::loadPE(FILE *file) {
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);
void *sectionBase = (void *) (header32.imageBase + section.virtualAddress);
void *sectionBase = (void *) ((uintptr_t) imageBuffer + section.virtualAddress);
if (section.pointerToRawData > 0 && section.sizeOfRawData > 0) {
// Grab this data
long savePos = ftell(file);
@ -179,6 +190,11 @@ bool wibo::Executable::loadPE(FILE *file) {
}
}
if (!exec) {
// No need to resolve imports
return true;
}
// Handle imports
PEImportDirectoryEntry *dir = fromRVA<PEImportDirectoryEntry>(header32.importTable.virtualAddress);

View File

@ -31,7 +31,7 @@ void wibo::debug_log(const char *fmt, ...) {
vfprintf(stderr, fmt, args);
}
va_end(args);
}
@ -110,11 +110,6 @@ const wibo::Module * wibo::modules[] = {
nullptr,
};
struct ModuleInfo {
std::string name;
const wibo::Module* module = nullptr;
};
HMODULE wibo::loadModule(const char *dllName) {
auto *result = new ModuleInfo;
result->name = dllName;
@ -151,6 +146,32 @@ void *wibo::resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) {
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;
@ -295,7 +316,7 @@ int main(int argc, char **argv) {
return 1;
}
exec.loadPE(f);
exec.loadPE(f, true);
fclose(f);
// 32-bit windows only reserves the lowest 2GB of memory for use by a process (https://www.tenouk.com/WinVirtualAddressSpace.html)