box64/wine/wow64/wowbox64.c
2025-08-27 16:17:17 +02:00

478 lines
14 KiB
C

/*
* Copyright 2022-2025 André Zwing
* Copyright 2023 Alexandre Julliard
*/
#include <stddef.h>
#include <stdio.h>
#include <windows.h>
#include <ntstatus.h>
#include <winternl.h>
#include <winnt.h>
#include "debug.h"
#include "os.h"
#include "custommem.h"
#include "dynablock.h"
#include "env.h"
#include "emu/x64emu_private.h"
#include "emu/x87emu_private.h"
#include "x64trace.h"
#include "box64context.h"
#include "box64cpu.h"
#include "box64cpu_util.h"
#include "build_info.h"
#include "rbtree.h"
#include "wine/compiler.h"
#include "wine/debug.h"
#include "hostext.h"
uintptr_t box64_pagesize = 4096;
uint32_t default_gs = 0x2b;
uint32_t default_fs = 0;
int box64_rdtsc = 0;
uint8_t box64_rdtsc_shift = 0;
int box64_is32bits = 0;
int box64_wine = 0; // this is for the emulated x86 Wine.
static uint32_t x86emu_parity_tab[8] = {
0x96696996,
0x69969669,
0x69969669,
0x96696996,
0x69969669,
0x96696996,
0x96696996,
0x69969669,
};
static UINT16 DECLSPEC_ALIGN(4096) bopcode[4096 / sizeof(UINT16)];
static UINT16 DECLSPEC_ALIGN(4096) unxcode[4096 / sizeof(UINT16)];
typedef UINT64 unixlib_handle_t;
NTSTATUS(WINAPI* __wine_unix_call_dispatcher)(unixlib_handle_t, unsigned int, void*);
#define ROUND_ADDR(addr, mask) ((void*)((UINT_PTR)(addr) & ~(UINT_PTR)(mask)))
#define ROUND_SIZE(addr, size) (((SIZE_T)(size) + ((UINT_PTR)(addr) & page_mask) + page_mask) & ~page_mask)
static const UINT_PTR page_mask = 0xfff;
/* reserved TEB64 TLS slots for Wow64
#define WOW64_TLS_CPURESERVED 1
#define WOW64_TLS_TEMPLIST 3
#define WOW64_TLS_USERCALLBACKDATA 5
#define WOW64_TLS_APCLIST 7
#define WOW64_TLS_FILESYSREDIR 8
#define WOW64_TLS_WOW64INFO 10
#define WOW64_TLS_MAX_NUMBER 19
*/
#define WOW64_TLS_ENTRY_CONTEXT (WOW64_TLS_MAX_NUMBER - 1)
#define WOW64_TLS_EMU (WOW64_TLS_MAX_NUMBER - 2)
int is_addr_unaligned(uintptr_t addr)
{
// FIXME
return 0;
}
#ifdef DYNAREC
int nUnalignedRange(uintptr_t start, size_t size)
{
// FIXME
return 0;
}
void getUnalignedRange(uintptr_t start, size_t size, uintptr_t addrs[])
{
//FIXME
}
#endif
typedef void (*wrapper_t)(x64emu_t* emu, uintptr_t fnc);
int isSimpleWrapper(wrapper_t fun)
{
return 0;
}
int isRetX87Wrapper(wrapper_t fun)
{
return 0;
}
cpu_ext_t cpuext = {0};
static box64context_t box64_context;
box64context_t* my_context = &box64_context;
void fpu_to_box(WOW64_CONTEXT* ctx, x64emu_t* emu)
{
XMM_SAVE_AREA32* fpu = (XMM_SAVE_AREA32*)ctx->ExtendedRegisters;
emu->mxcsr.x32 = fpu->MxCsr;
emu->cw.x16 = fpu->ControlWord;
emu->sw.x16 = fpu->StatusWord;
LD2D(&ctx->FloatSave.RegisterArea[0], &emu->x87[0]);
LD2D(&ctx->FloatSave.RegisterArea[10], &emu->x87[1]);
LD2D(&ctx->FloatSave.RegisterArea[20], &emu->x87[2]);
LD2D(&ctx->FloatSave.RegisterArea[30], &emu->x87[3]);
LD2D(&ctx->FloatSave.RegisterArea[40], &emu->x87[4]);
LD2D(&ctx->FloatSave.RegisterArea[50], &emu->x87[5]);
LD2D(&ctx->FloatSave.RegisterArea[60], &emu->x87[6]);
LD2D(&ctx->FloatSave.RegisterArea[70], &emu->x87[7]);
memcpy(emu->xmm, fpu->XmmRegisters, sizeof(emu->xmm));
}
void box_to_fpu(WOW64_CONTEXT* ctx, x64emu_t* emu)
{
XMM_SAVE_AREA32* fpu = (XMM_SAVE_AREA32*)ctx->ExtendedRegisters;
fpu->MxCsr = emu->mxcsr.x32;
fpu->ControlWord = emu->cw.x16;
fpu->StatusWord = emu->sw.x16;
D2LD(&emu->x87[0], &ctx->FloatSave.RegisterArea[0]);
D2LD(&emu->x87[1], &ctx->FloatSave.RegisterArea[10]);
D2LD(&emu->x87[2], &ctx->FloatSave.RegisterArea[20]);
D2LD(&emu->x87[3], &ctx->FloatSave.RegisterArea[30]);
D2LD(&emu->x87[4], &ctx->FloatSave.RegisterArea[40]);
D2LD(&emu->x87[5], &ctx->FloatSave.RegisterArea[50]);
D2LD(&emu->x87[6], &ctx->FloatSave.RegisterArea[60]);
D2LD(&emu->x87[7], &ctx->FloatSave.RegisterArea[70]);
memcpy(fpu->XmmRegisters, emu->xmm, sizeof(emu->xmm));
}
static NTSTATUS invalidate_mapped_section(PVOID addr)
{
MEMORY_BASIC_INFORMATION mem_info;
SIZE_T size;
void* base;
NTSTATUS ret = NtQueryVirtualMemory(NtCurrentProcess(), addr, MemoryBasicInformation, &mem_info, sizeof(mem_info), NULL);
if (!NT_SUCCESS(ret))
return ret;
base = mem_info.AllocationBase;
size = (char*)mem_info.BaseAddress + mem_info.RegionSize - (char*)base;
while (!NtQueryVirtualMemory(NtCurrentProcess(), (char*)base + size, MemoryBasicInformation, &mem_info, sizeof(mem_info), NULL) && mem_info.AllocationBase == base) {
size += mem_info.RegionSize;
}
unprotectDB((uintptr_t)base, (DWORD64)size, 1);
return STATUS_SUCCESS;
}
void WINAPI BTCpuFlushInstructionCache2(LPCVOID addr, SIZE_T size)
{
printf_log(LOG_DEBUG, "BTCpuFlushInstructionCache2(%p, %zu)\n", addr, size);
unprotectDB((uintptr_t)addr, (size_t)size, 1);
}
void WINAPI BTCpuFlushInstructionCacheHeavy(LPCVOID addr, SIZE_T size)
{
printf_log(LOG_DEBUG, "BTCpuFlushInstructionCacheHeavy(%p, %zu)\n", addr, size);
unprotectDB((uintptr_t)addr, (size_t)size, 1);
}
void* WINAPI BTCpuGetBopCode(void)
{
printf_log(LOG_DEBUG, "BTCpuGetBopCode()\n");
return (UINT32*)&bopcode;
}
void* WINAPI __wine_get_unix_opcode(void)
{
printf_log(LOG_DEBUG, "__wine_get_unix_opcode()\n");
return (UINT32*)&unxcode;
}
NTSTATUS WINAPI BTCpuGetContext(HANDLE thread, HANDLE process, void* unknown, WOW64_CONTEXT* ctx)
{
printf_log(LOG_DEBUG, "BTCpuGetContext(%p, %p, %p, %p)\n", thread, process, unknown, ctx);
return NtQueryInformationThread(thread, ThreadWow64Context, ctx, sizeof(*ctx), NULL);
}
void WINAPI BTCpuNotifyMemoryDirty(PVOID addr, SIZE_T size)
{
printf_log(LOG_DEBUG, "BTCpuNotifyMemoryDirty(%p, %zu)\n", addr, size);
unprotectDB((uintptr_t)addr, (size_t)size, 1);
}
void WINAPI BTCpuNotifyMemoryFree(PVOID addr, SIZE_T size, ULONG free_type)
{
printf_log(LOG_DEBUG, "BTCpuNotifyMemoryFree(%p, %zu, %u)\n", addr, size, free_type);
if (!size)
invalidate_mapped_section(addr);
else if (free_type & MEM_DECOMMIT)
unprotectDB((uintptr_t)ROUND_ADDR(addr, page_mask), (DWORD64)ROUND_SIZE(addr, size), 1);
}
void WINAPI BTCpuNotifyMemoryProtect(PVOID addr, SIZE_T size, DWORD new_protect)
{
printf_log(LOG_DEBUG, "BTCpuNotifyMemoryProtect(%p, %zu, %08x)\n", addr, size, new_protect);
if (!(new_protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)))
return;
unprotectDB((uintptr_t)addr, size, 1);
}
void WINAPI BTCpuNotifyUnmapViewOfSection(PVOID addr, ULONG flags)
{
printf_log(LOG_DEBUG, "BTCpuNotifyUnmapViewOfSection(%p, %u)\n", addr, flags);
invalidate_mapped_section(addr);
}
NTSTATUS WINAPI BTCpuProcessInit(void)
{
printf_log(LOG_DEBUG, "BTCpuProcessInit()\n");
HMODULE module;
UNICODE_STRING str;
void** p__wine_unix_call_dispatcher;
#define STATIC_ASSERT(COND, MSG) typedef char static_assertion_##MSG[(!!(COND)) * 2 - 1]
/* otherwise adjust arm64_epilog.S and arm64_next.S */
STATIC_ASSERT(offsetof(x64emu_t, win64_teb) == 3120, offset_of_b_must_be_4);
#undef STATIC_ASSERT
LoadEnvVariables();
InitializeEnvFiles();
if (!BOX64ENV(nobanner)) PrintBox64Version(1);
if (DetectHostCpuFeatures())
PrintHostCpuFeatures();
else {
printf_log(LOG_INFO, "Minimum CPU requirements not met, disabling DynaRec\n");
SET_BOX64ENV(dynarec, 0);
}
TCHAR filename[MAX_PATH];
if (GetModuleFileNameA(NULL, filename, MAX_PATH)) {
char* shortname = strrchr(filename, '\\');
if (shortname) {
shortname++;
ApplyEnvFileEntry(shortname);
}
}
PrintEnvVariables(&box64env, LOG_INFO);
memset(bopcode, 0xc3, sizeof(bopcode));
memset(unxcode, 0xc3, sizeof(unxcode));
bopcode[0] = 0x2ecd;
unxcode[0] = 0x2ecd;
init_custommem_helper(&box64_context);
box64_context.db_sizes = rbtree_init("db_sizes");
if ((ULONG_PTR)bopcode >> 32 || (ULONG_PTR)unxcode >> 32) {
printf_log(LOG_NONE, "box64cpu loaded above 4G, disabling\n");
return STATUS_INVALID_ADDRESS;
}
RtlInitUnicodeString(&str, L"ntdll.dll");
LdrGetDllHandle(NULL, 0, &str, &module);
p__wine_unix_call_dispatcher = RtlFindExportedRoutineByName(module, "__wine_unix_call_dispatcher");
__wine_unix_call_dispatcher = *p__wine_unix_call_dispatcher;
RtlInitializeCriticalSection(&box64_context.mutex_dyndump);
RtlInitializeCriticalSection(&box64_context.mutex_trace);
RtlInitializeCriticalSection(&box64_context.mutex_tls);
RtlInitializeCriticalSection(&box64_context.mutex_thread);
RtlInitializeCriticalSection(&box64_context.mutex_bridge);
RtlInitializeCriticalSection(&box64_context.mutex_lock);
InitX64Trace(&box64_context);
return STATUS_SUCCESS;
}
static uint8_t box64_is_addr_in_jit(void* addr)
{
if (!addr)
return FALSE;
return !!FindDynablockFromNativeAddress(addr);
}
NTSTATUS WINAPI BTCpuResetToConsistentState(EXCEPTION_POINTERS* ptrs)
{
printf_log(LOG_DEBUG, "BTCpuResetToConsistentState(%p)\n", ptrs);
x64emu_t* emu = NtCurrentTeb()->TlsSlots[WOW64_TLS_EMU];
EXCEPTION_RECORD* rec = ptrs->ExceptionRecord;
CONTEXT* ctx = ptrs->ContextRecord;
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
dynablock_t* db = NULL;
void* addr = NULL;
uint32_t prot;
if (rec->NumberParameters == 2 && rec->ExceptionInformation[0] == 1)
addr = ULongToPtr(rec->ExceptionInformation[1]);
if (addr) {
unprotectDB((uintptr_t)addr, 1, 1); // unprotect 1 byte... But then, the whole page will be unprotected
NtContinue(ctx, FALSE);
}
}
if (!box64_is_addr_in_jit(ULongToPtr(ctx->Pc)))
return STATUS_SUCCESS;
/* Replace the host context with one captured before JIT entry so host code can unwind */
memcpy(ctx, NtCurrentTeb()->TlsSlots[WOW64_TLS_ENTRY_CONTEXT], sizeof(*ctx));
return STATUS_SUCCESS;
}
NTSTATUS WINAPI BTCpuSetContext(HANDLE thread, HANDLE process, void* unknown, WOW64_CONTEXT* ctx)
{
printf_log(LOG_DEBUG, "BTCpuSetContext(%p, %p, %p, %p)\n", thread, process, unknown, ctx);
return NtSetInformationThread(thread, ThreadWow64Context, ctx, sizeof(*ctx));
}
void WINAPI BTCpuSimulate(void)
{
printf_log(LOG_DEBUG, "BTCpuSimulate()\n");
WOW64_CPURESERVED* cpu = NtCurrentTeb()->TlsSlots[WOW64_TLS_CPURESERVED];
x64emu_t* emu = NtCurrentTeb()->TlsSlots[WOW64_TLS_EMU];
WOW64_CONTEXT* ctx = (WOW64_CONTEXT*)(cpu + 1);
CONTEXT entry_context;
RtlCaptureContext(&entry_context);
NtCurrentTeb()->TlsSlots[WOW64_TLS_ENTRY_CONTEXT] = &entry_context;
R_EAX = ctx->Eax;
R_EBX = ctx->Ebx;
R_ECX = ctx->Ecx;
R_EDX = ctx->Edx;
R_ESI = ctx->Esi;
R_EDI = ctx->Edi;
R_EBP = ctx->Ebp;
R_RIP = ctx->Eip;
R_ESP = ctx->Esp;
R_CS = ctx->SegCs & 0xffff;
R_DS = ctx->SegDs & 0xffff;
R_ES = ctx->SegEs & 0xffff;
R_FS = ctx->SegFs & 0xffff;
R_GS = ctx->SegGs & 0xffff;
R_SS = ctx->SegSs & 0xffff;
emu->eflags.x64 = ctx->EFlags;
emu->segs_offs[_FS] = calculate_fs();
emu->win64_teb = (uint64_t)NtCurrentTeb();
fpu_to_box(ctx, emu);
if (box64env.dynarec)
DynaRun(emu);
else
Run(emu, 0, 0);
}
NTSTATUS WINAPI BTCpuThreadInit(void)
{
printf_log(LOG_DEBUG, "BTCpuThreadInit()\n");
WOW64_CONTEXT* ctx;
x64emu_t* emu = RtlAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*emu));
RtlWow64GetCurrentCpuArea(NULL, (void**)&ctx, NULL);
emu->context = &box64_context;
// setup cpu helpers
for (int i = 0; i < 16; ++i)
emu->sbiidx[i] = &emu->regs[i];
emu->sbiidx[4] = &emu->zero;
emu->x64emu_parity_tab = x86emu_parity_tab;
reset_fpu(emu);
NtCurrentTeb()->TlsSlots[WOW64_TLS_EMU] = emu;
return STATUS_SUCCESS;
}
void EmitInterruptionImpl(x64emu_t* emu, int code)
{
if (code == 0x2e /* NT syscall */) {
WOW64_CPURESERVED* cpu = NtCurrentTeb()->TlsSlots[WOW64_TLS_CPURESERVED];
WOW64_CONTEXT* ctx = (WOW64_CONTEXT*)(cpu + 1);
int id = R_EAX;
BOOL is_unix_call = FALSE;
if (ULongToPtr(R_RIP) == &unxcode)
is_unix_call = TRUE;
else if (ULongToPtr(R_RIP) != &bopcode)
return;
R_RIP = Pop32(emu);
ctx->Eip = R_RIP;
ctx->Esp = R_ESP;
ctx->Ebx = R_EBX;
ctx->Esi = R_ESI;
ctx->Edi = R_EDI;
ctx->Ebp = R_EBP;
ctx->EFlags = emu->eflags.x64;
cpu->Flags = 0;
box_to_fpu(ctx, emu);
if (is_unix_call) {
uintptr_t handle_low = Pop32(emu);
uintptr_t handle_high = Pop32(emu);
unsigned int code = Pop32(emu);
uintptr_t args = Pop32(emu);
ctx->Esp = R_ESP;
R_EAX = __wine_unix_call_dispatcher(handle_low | (handle_high << 32), code, (void*)args);
} else {
R_EAX = Wow64SystemServiceEx(id, ULongToPtr(ctx->Esp + 4));
}
fpu_to_box(ctx, emu);
R_EBX = ctx->Ebx;
R_ESI = ctx->Esi;
R_EDI = ctx->Edi;
R_EBP = ctx->Ebp;
R_ESP = ctx->Esp;
R_RIP = ctx->Eip;
if (cpu->Flags & WOW64_CPURESERVED_FLAG_RESET_STATE) {
cpu->Flags &= ~WOW64_CPURESERVED_FLAG_RESET_STATE;
R_EAX = ctx->Eax;
R_ECX = ctx->Ecx;
R_EDX = ctx->Edx;
R_FS = ctx->SegFs & 0xffff;
emu->segs_offs[_FS] = calculate_fs();
emu->eflags.x64 = ctx->EFlags;
}
} else {
RtlRaiseStatus(STATUS_ACCESS_VIOLATION);
}
}
/* Calls a 2-argument function `Func` setting the parent unwind frame information to the given SP and PC */
static void __attribute__((naked)) SEHFrameTrampoline2Args(void* Arg0, int Arg1, void* Func, uint64_t Sp, uint64_t Pc)
{
asm(".seh_proc SEHFrameTrampoline2Args\n\t"
"stp x3, x4, [sp, #-0x10]!\n\t"
".seh_pushframe\n\t"
"stp x29, x30, [sp, #-0x10]!\n\t"
".seh_save_fplr_x 16\n\t"
".seh_endprologue\n\t"
"blr x2\n\t"
"ldp x29, x30, [sp], 0x20\n\t"
"ret\n\t"
".seh_endproc");
}
void EmitInterruption(x64emu_t* emu, int num, void* addr)
{
CONTEXT* entry_context = NtCurrentTeb()->TlsSlots[WOW64_TLS_ENTRY_CONTEXT];
SEHFrameTrampoline2Args(emu, num, (void*)EmitInterruptionImpl, entry_context->Sp, entry_context->Pc);
NtCurrentTeb()->TlsSlots[WOW64_TLS_ENTRY_CONTEXT] = entry_context;
}
NTSTATUS WINAPI LdrDisableThreadCalloutsForDll(HMODULE);
BOOL WINAPI DllMainCRTStartup(HINSTANCE inst, DWORD reason, void* reserved)
{
if (reason == DLL_PROCESS_ATTACH) LdrDisableThreadCalloutsForDll(inst);
return TRUE;
}