814 lines
26 KiB
C++
814 lines
26 KiB
C++
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
|
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
// ******************************************************************
|
|
// *
|
|
// * This file is part of the Cxbx project.
|
|
// *
|
|
// * Cxbx and Cxbe are free software; you can redistribute them
|
|
// * and/or modify them under the terms of the GNU General Public
|
|
// * License as published by the Free Software Foundation; either
|
|
// * version 2 of the license, or (at your option) any later version.
|
|
// *
|
|
// * This program is distributed in the hope that it will be useful,
|
|
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// * GNU General Public License for more details.
|
|
// *
|
|
// * You should have recieved a copy of the GNU General Public License
|
|
// * along with this program; see the file COPYING.
|
|
// * If not, write to the Free Software Foundation, Inc.,
|
|
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
|
|
// *
|
|
// * (c) 2002-2003 Aaron Robinson <caustik@caustik.com>
|
|
// *
|
|
// * All rights reserved
|
|
// *
|
|
// ******************************************************************
|
|
|
|
#define LOG_PREFIX CXBXR_MODULE::FS
|
|
|
|
|
|
#include <core\kernel\exports\xboxkrnl.h>
|
|
#include "core\kernel\exports\EmuKrnl.h" // For InitializeListHead(), etc.
|
|
#include "core\kernel\exports\EmuKrnlKe.h"
|
|
#include "core\kernel\support\EmuFS.h" // For fs_instruction_t
|
|
#include "core\kernel\init\CxbxKrnl.h"
|
|
#include "Logging.h"
|
|
|
|
#include <windows.h>
|
|
#include <cstdio>
|
|
#include <vector>
|
|
|
|
#ifdef RtlZeroMemory
|
|
#undef RtlZeroMemory
|
|
#endif
|
|
|
|
// NT_TIB (Thread Information Block) offsets - see https://www.microsoft.com/msj/archive/S2CE.aspx
|
|
#define TIB_ExceptionList offsetof(NT_TIB, ExceptionList) // = 0x00/0
|
|
#define TIB_StackBase offsetof(NT_TIB, StackBase) // = 0x04/4
|
|
#define TIB_StackLimit offsetof(NT_TIB, StackLimit) // = 0x08/8
|
|
#define TIB_SubSystemTib offsetof(NT_TIB, SubSystemTib) // = 0x0C/12
|
|
#define TIB_FiberData offsetof(NT_TIB, FiberData) // = 0x10/16
|
|
#define TIB_ArbitraryUserPointer offsetof(NT_TIB, ArbitraryUserPointer) // = 0x14/20
|
|
#define TIB_Self offsetof(NT_TIB, Self) // = 0x18/24
|
|
|
|
// KPCR (Kernel Processor Control Region) offsets
|
|
#define KPCR_NtTib offsetof(KPCR, NtTib) // = 0x00/0
|
|
#define KPCR_SelfPcr offsetof(KPCR, SelfPcr) // = 0x1C/28
|
|
#define KPCR_Prcb offsetof(KPCR, Prcb) // = 0x20/32
|
|
#define KPCR_Irql offsetof(KPCR, Irql) // = 0x24/36
|
|
#define KPCR_PrcbData offsetof(KPCR, PrcbData) // = 0x28/40
|
|
|
|
// KPRCB (Kernel PRocesor Control Block) offsets
|
|
#define KPRCB_CurrentThread offsetof(KPRCB, CurrentThread) // = 0x00, KPCR : 0x28/40
|
|
#define KPRCB_NextThread offsetof(KPRCB, NextThread) // = 0x04, KPCR : 0x2C/44
|
|
#define KPRCB_IdleThread offsetof(KPRCB, IdleThread) // = 0x08, KPCR : 0x30/48
|
|
#define KPRCB_DpcListHead offsetof(KPRCB, DpcListHead) // = 0x28, KPCR : 0x50/80
|
|
#define KPRCB_DpcRoutineActive offsetof(KPRCB, DpcRoutineActive) // = 0x30, KPCR : 0x58/88
|
|
|
|
// KTHREAD (Kernel Thread) offsets
|
|
#define KTHREAD_Header offsetof(KTHREAD, Header) // = 0x0/0
|
|
#define KTHREAD_MutantListHead offsetof(KTHREAD, MutantListHead) // = 0x10/16
|
|
#define KTHREAD_KernelTime offsetof(KTHREAD, KernelTime) // = 0x18/24
|
|
#define KTHREAD_StackBase offsetof(KTHREAD, StackBase) // = 0x1C/28
|
|
#define KTHREAD_StackLimit offsetof(KTHREAD, StackLimit) // = 0x20/32
|
|
#define KTHREAD_KernelStack offsetof(KTHREAD, KernelStack) // = 0x24/36
|
|
#define KTHREAD_TlsData offsetof(KTHREAD, TlsData) // = 0x28/40
|
|
#define KTHREAD_State offsetof(KTHREAD, State) // = 0x2C/44
|
|
#define KTHREAD_Alerted offsetof(KTHREAD, Alerted) // = 0x2D/45
|
|
#define KTHREAD_Alertable offsetof(KTHREAD, Alertable) // = 0x2F/47
|
|
#define KTHREAD_NpxState offsetof(KTHREAD, NpxState) // = 0x30/48
|
|
// = 0x31/49 */ CHAR Saturation;
|
|
// = 0x32/50 */ CHAR Priority;
|
|
// = 0x33/51 */ CHAR Padding;
|
|
// = 0x34/52 */ KAPC_STATE ApcState;
|
|
// = 0x4C/76 */ ULONG ContextSwitches;
|
|
// = 0x50/80 */ ULONG WaitStatus;
|
|
// = 0x54/84 */ CHAR WaitIrql;
|
|
// = 0x55/85 */ CHAR WaitMode;
|
|
// = 0x56/86 */ CHAR WaitNext;
|
|
// = 0x57/87 */ CHAR WaitReason;
|
|
// = 0x58/88 */ PVOID WaitBlockList;
|
|
// = 0x5C/92 */ LIST_ENTRY WaitListEntry;
|
|
// = 0x64/100 */ ULONG WaitTime;
|
|
// = 0x68/104 */ ULONG KernelApcDisable;
|
|
// = 0x6C/108 */ ULONG Quantum;
|
|
// = 0x70/112 */ CHAR BasePriority;
|
|
// = 0x71/113 */ CHAR DecrementCount;
|
|
// = 0x72/114 */ CHAR PriorityDecrement;
|
|
// = 0x73/115 */ CHAR DisableBoost;
|
|
// = 0x74/116 */ CHAR NpxIrql;
|
|
// = 0x75/117 */ CHAR SuspendCount;
|
|
// = 0x76/118 */ CHAR Preempted;
|
|
// = 0x77/119 */ CHAR HasTerminated;
|
|
// = 0x78/120 */ PVOID Queue;
|
|
// = 0x7C/124 */ LIST_ENTRY QueueListEntry;
|
|
// = 0x88/136 */ UCHAR rsvd1[4];
|
|
// = 0x88/136 */ KTIMER Timer;
|
|
// = 0xB0/176 */ KWAIT_BLOCK TimerWaitBlock;
|
|
// = 0xC8/200 */ KAPC SuspendApc;
|
|
// = 0xF0/240 */ KSEMAPHORE SuspendSemaphore;
|
|
// = 0x104/260 */ LIST_ENTRY ThreadListEntry;
|
|
// = 0x10C/268 */ UCHAR _padding[4];
|
|
|
|
NT_TIB *GetNtTib()
|
|
{
|
|
return (NT_TIB *)__readfsdword(TIB_LinearSelfAddress);
|
|
}
|
|
|
|
|
|
xbox::KPCR* WINAPI KeGetPcr();
|
|
|
|
uint32_t fs_lock = 0;
|
|
|
|
__declspec(naked) void LockFS()
|
|
{
|
|
__asm {
|
|
// Backup Registers
|
|
pushfd
|
|
pushad
|
|
jmp entry
|
|
|
|
// Spin until we can aquire the lock
|
|
spinlock :
|
|
call SwitchToThread // Give other threads a chance to run if we couldn't get the lock
|
|
entry:
|
|
mov eax, 1
|
|
xchg eax, fs_lock
|
|
test eax, eax
|
|
jnz spinlock
|
|
|
|
// Restore registers and return
|
|
popad
|
|
popfd
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void UnlockFS()
|
|
{
|
|
__asm {
|
|
pushfd
|
|
pushad
|
|
xor eax, eax
|
|
xchg eax, fs_lock
|
|
popad
|
|
popfd
|
|
ret
|
|
}
|
|
}
|
|
|
|
void EmuKeSetPcr(xbox::KPCR *Pcr)
|
|
{
|
|
// Store the Xbox KPCR pointer in FS (See KeGetPcr())
|
|
//
|
|
// Note : Cxbx currently doesn't do preemptive thread switching,
|
|
// which implies that thread-state management is done by Windows.
|
|
//
|
|
// Xbox executable code expects thread-specific state data to
|
|
// be available via the FS segment register. To emulate this,
|
|
// Cxbx uses the user data-slot feature of Windows threads.
|
|
//
|
|
// Cxbx puts a pointer to a thread-specific copy of an entire
|
|
// Kernel Processor Control Region (KPCR) into this data-slot.
|
|
//
|
|
// In the Xbox there's only be KPCR (as it's a per-processor-
|
|
// structure, and the Xbox has only one processor).
|
|
//
|
|
// Since Cxbx doesn't control thread-swiches (yet), each thread
|
|
// must have a thread-specific copy of the KPCR, to contain all
|
|
// thread-specific data that can be reached via this structure
|
|
// (like the NT_TIB structure and ETHREAD CurrentThread pointer).
|
|
//
|
|
// For this to work, Cxbx patches all executable code accessing
|
|
// the FS segment register, so that the KPCR is accessed via
|
|
// the user data-slot of each Windows thread Cxbx uses for an
|
|
// Xbox thread.
|
|
//
|
|
__writefsdword(TIB_ArbitraryDataSlot, (DWORD)Pcr);
|
|
}
|
|
|
|
void EmuKeFreePcr()
|
|
{
|
|
// NOTE: don't call KeGetPcr because that one creates a new pcr for the thread when __readfsdword returns nullptr, which we don't want
|
|
xbox::PKPCR Pcr = reinterpret_cast<xbox::PKPCR>(__readfsdword(TIB_ArbitraryDataSlot));
|
|
|
|
if (Pcr) {
|
|
// tls can be nullptr
|
|
xbox::PVOID Dummy;
|
|
xbox::ulong_xt Size;
|
|
xbox::ntstatus_xt Status;
|
|
if (Pcr->NtTib.StackBase) {
|
|
// NOTE: the tls pointer was increased by 12 bytes to enforce the 16 bytes alignment, so adjust it to reach the correct pointer
|
|
// that was allocated by xbox::NtAllocateVirtualMemory
|
|
Dummy = static_cast<xbox::PBYTE>(Pcr->NtTib.StackBase) - 12;
|
|
Size = xbox::zero;
|
|
Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free tls
|
|
assert(Status == X_STATUS_SUCCESS);
|
|
}
|
|
Dummy = Pcr->Prcb->CurrentThread;
|
|
Size = xbox::zero;
|
|
Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free ethread
|
|
assert(Status == X_STATUS_SUCCESS);
|
|
Dummy = Pcr;
|
|
Size = xbox::zero;
|
|
Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free pcr
|
|
assert(Status == X_STATUS_SUCCESS);
|
|
__writefsdword(TIB_ArbitraryDataSlot, NULL);
|
|
}
|
|
else {
|
|
EmuLog(LOG_LEVEL::WARNING, "__readfsdword in EmuKeFreePcr returned nullptr: was this called from a non-xbox thread?");
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_RefreshKPCR()
|
|
{
|
|
// Backup all registers, call KeGetPcr and then restore all registers
|
|
// KeGetPcr makes sure a valid KPCR exists for the current thread
|
|
// and creates it if missing, we backup and restore all registers
|
|
// to keep it safe to call in our patches
|
|
// This function can be later expanded to do nice things
|
|
// like setup the per-thread KPCR values for us too!
|
|
__asm {
|
|
pushfd
|
|
pushad
|
|
call KeGetPcr
|
|
popad
|
|
popfd
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_CmpEsiFs00()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
cmp esi, [eax]
|
|
pop eax
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEaxFs00()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov eax, [eax]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEaxFs04()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov eax, [eax + 04h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEaxFs20()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov eax, [eax + 20h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEaxFs28()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov eax, [eax + 28h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEaxFs58()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov eax, [eax + 58h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEbxFs00()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov ebx, fs : [TIB_ArbitraryDataSlot]
|
|
mov ebx, [ebx]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEbxFs04()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov ebx, fs : [TIB_ArbitraryDataSlot]
|
|
mov ebx, [ebx + 04h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEcxFs00()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov ecx, fs : [TIB_ArbitraryDataSlot]
|
|
mov ecx, [ecx]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEcxFs04()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov ecx, fs : [TIB_ArbitraryDataSlot]
|
|
mov ecx, [ecx + 04h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEdiFs00()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov edi, fs : [TIB_ArbitraryDataSlot]
|
|
mov edi, [edi]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEdiFs04()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov edi, fs : [TIB_ArbitraryDataSlot]
|
|
mov edi, [edi + 04h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEdxFs00()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov edx, fs : [TIB_ArbitraryDataSlot]
|
|
mov edx, [edx]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEdxFs04()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov edx, fs : [TIB_ArbitraryDataSlot]
|
|
mov edx, [edx + 04h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEsiFs00()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov esi, fs : [TIB_ArbitraryDataSlot]
|
|
mov esi, [esi]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovEsiFs04()
|
|
{
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov esi, fs : [TIB_ArbitraryDataSlot]
|
|
mov esi, [esi + 04h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovzxEaxBytePtrFs24()
|
|
{
|
|
// Note : Inlined KeGetCurrentIrql()
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
movzx eax, byte ptr[eax + 24h]
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Eax()
|
|
{
|
|
// Note : ebx must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push ebx
|
|
mov ebx, fs : [TIB_ArbitraryDataSlot]
|
|
mov [ebx], eax
|
|
pop ebx
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Ebx()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov [eax], ebx
|
|
pop eax
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Ecx()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov [eax], ecx
|
|
pop eax
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Edi()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov [eax], edi
|
|
pop eax
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Edx()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov [eax], edx
|
|
pop eax
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Esi()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov [eax], esi
|
|
pop eax
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_MovFs00Esp()
|
|
{
|
|
// Note : eax must be preserved here, hence the push/pop
|
|
__asm
|
|
{
|
|
pushfd
|
|
call EmuFS_RefreshKPCR
|
|
push eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
mov [eax], esp
|
|
add [eax], 12 // account for esp changes from pushed registers and return address
|
|
pop eax
|
|
popfd
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_PushDwordPtrFs00()
|
|
{
|
|
static uint32_t returnAddr;
|
|
static uint32_t temp;
|
|
|
|
__asm
|
|
{
|
|
call LockFS
|
|
call EmuFS_RefreshKPCR
|
|
pop returnAddr
|
|
mov temp, eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
push dword ptr[eax]
|
|
mov eax, temp
|
|
push returnAddr
|
|
call UnlockFS
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void EmuFS_PopDwordPtrFs00()
|
|
{
|
|
static uint32_t returnAddr;
|
|
static uint32_t temp;
|
|
|
|
__asm
|
|
{
|
|
call LockFS
|
|
call EmuFS_RefreshKPCR
|
|
pop returnAddr
|
|
mov temp, eax
|
|
mov eax, fs : [TIB_ArbitraryDataSlot]
|
|
pop dword ptr [eax]
|
|
mov eax, temp
|
|
push returnAddr
|
|
call UnlockFS
|
|
ret
|
|
}
|
|
}
|
|
|
|
// initialize fs segment selector emulation
|
|
void EmuInitFS()
|
|
{
|
|
/**
|
|
* Build the vector of FS instructions we need to intercept.
|
|
* The entries must be in order of size, to keep the chance of false positives to a minimum.
|
|
*/
|
|
std::vector<fs_instruction_t> fsInstructions;
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x0F, 0xB6, 0x05, 0x24, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovzxEaxBytePtrFs24 });// movzx eax, large byte ptr fs:24
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x3B, 0x35, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_CmpEsiFs00 }); // cmp esi, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x1D, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEbxFs00 }); // mov ebx, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x1D, 0x04, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEbxFs04 }); // mov ebx, large fs:4
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEcxFs00 }); // mov ecx, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x0D, 0x04, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEcxFs04 }); // mov ecx, large fs:4
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x3D, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEdiFs00 }); // mov edi, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x3D, 0x04, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEdiFs04 }); // mov edi, large fs:4
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x15, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEdxFs00 }); // mov edx, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x15, 0x04, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEdxFs04 }); // mov edx, large fs:4
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x35, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEsiFs00 }); // mov esi, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8B, 0x35, 0x04, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEsiFs04 }); // mov esi, large fs:4
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x89, 0x1D, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Ebx }); // mov large fs:0, ebx
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x89, 0x0D, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Ecx }); // mov large fs:0, ecx
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x89, 0x3D, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Edi }); // mov large fs:0, edi
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x89, 0x15, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Edx }); // mov large fs:0, edx
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x89, 0x35, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Esi }); // mov large fs:0, esi
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x89, 0x25, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Esp }); // mov large fs:0, esp
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0x8F, 0x05, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_PopDwordPtrFs00 }); // pop large dword ptr fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xFF, 0x35, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_PushDwordPtrFs00 }); // push large dword ptr fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xA1, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEaxFs00 }); // mov eax, large fs:0
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xA1, 0x04, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEaxFs04 }); // mov eax, large fs:4
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xA1, 0x20, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEaxFs20 }); // mov eax, large fs:20
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xA1, 0x28, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEaxFs28 }); // mov eax, large fs:28
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xA1, 0x58, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovEaxFs58 }); // mov eax, large fs:58
|
|
fsInstructions.push_back(fs_instruction_t { { 0x64, 0xA3, 0x00, 0x00, 0x00, 0x00 }, (void*)&EmuFS_MovFs00Eax }); // mov large fs:0, eax
|
|
EmuLogEx(CXBXR_MODULE::INIT, LOG_LEVEL::DEBUG, "Patching FS Register Accesses\n");
|
|
DWORD sizeOfImage = CxbxKrnl_XbeHeader->dwSizeofImage;
|
|
long numberOfInstructions = fsInstructions.size();
|
|
|
|
// Iterate through each CODE section
|
|
for (uint32_t sectionIndex = 0; sectionIndex < CxbxKrnl_Xbe->m_Header.dwSections; sectionIndex++) {
|
|
if (!CxbxKrnl_Xbe->m_SectionHeader[sectionIndex].dwFlags.bExecutable) {
|
|
continue;
|
|
}
|
|
|
|
EmuLogEx(CXBXR_MODULE::INIT, LOG_LEVEL::DEBUG, "Searching for FS Instruction in section %s\n", CxbxKrnl_Xbe->m_szSectionName[sectionIndex]);
|
|
xbox::addr_xt startAddr = CxbxKrnl_Xbe->m_SectionHeader[sectionIndex].dwVirtualAddr;
|
|
xbox::addr_xt endAddr = startAddr + CxbxKrnl_Xbe->m_SectionHeader[sectionIndex].dwSizeofRaw;
|
|
for (xbox::addr_xt addr = startAddr; addr < endAddr; addr++)
|
|
{
|
|
for (int i = 0; i < numberOfInstructions; i++)
|
|
{
|
|
// Loop through the data, checking if we get an exact match
|
|
long sizeOfData = fsInstructions[i].data.size();
|
|
|
|
if (addr + sizeOfData >= endAddr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (memcmp((void*)addr, &fsInstructions[i].data[0], sizeOfData) == 0)
|
|
{
|
|
EmuLogEx(CXBXR_MODULE::INIT, LOG_LEVEL::DEBUG, "Patching FS Instruction at 0x%.8X\n", addr);
|
|
|
|
// Write Call opcode
|
|
*(uint8_t*)addr = OPCODE_CALL_E8;
|
|
*(uint32_t*)(addr + 1) = (uint32_t)fsInstructions[i].functionPtr - addr - 5;
|
|
|
|
// Fill the remaining bytes with nop instructions
|
|
int remaining_bytes = fsInstructions[i].data.size() - 5;
|
|
memset((void*)(addr + 5), OPCODE_NOP_90, remaining_bytes);
|
|
addr += sizeOfData - 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EmuLogEx(CXBXR_MODULE::INIT, LOG_LEVEL::DEBUG, "Done patching FS Register Accesses\n");
|
|
}
|
|
|
|
// generate fs segment selector
|
|
void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData)
|
|
{
|
|
void *pNewTLS = nullptr;
|
|
xbox::PVOID base;
|
|
xbox::ulong_xt size;
|
|
|
|
// Be aware that TLS might be absent (for example in homebrew "Wolf3d-xbox")
|
|
if (pTLS != nullptr) {
|
|
// copy global TLS to the current thread
|
|
{
|
|
uint32_t dwCopySize = 0;
|
|
uint32_t dwZeroSize = pTLS->dwSizeofZeroFill;
|
|
|
|
if (pTLSData != NULL) {
|
|
// Make sure the TLS Start and End addresses are within Xbox virtual memory
|
|
if (pTLS->dwDataStartAddr >= XBE_MAX_VA || pTLS->dwDataEndAddr >= XBE_MAX_VA) {
|
|
// ignore
|
|
}
|
|
else {
|
|
dwCopySize = pTLS->dwDataEndAddr - pTLS->dwDataStartAddr;
|
|
}
|
|
}
|
|
|
|
/* + HACK: extra safety padding 0x100 */
|
|
base = xbox::zeroptr;
|
|
size = dwCopySize + dwZeroSize + 0x100 + 0xC;
|
|
xbox::NtAllocateVirtualMemory(&base, 0, &size, XBOX_MEM_RESERVE | XBOX_MEM_COMMIT, XBOX_PAGE_READWRITE);
|
|
pNewTLS = (void*)base;
|
|
xbox::RtlZeroMemory(pNewTLS, dwCopySize + dwZeroSize + 0x100 + 0xC);
|
|
/* Skip the first 12 bytes so that TLSData will be 16 byte aligned (addr returned by NtAllocateVirtualMemory is 4K aligned) */
|
|
pNewTLS = (uint8_t*)pNewTLS + 12;
|
|
|
|
if (dwCopySize > 0) {
|
|
memcpy((uint8_t*)pNewTLS + 4, pTLSData, dwCopySize);
|
|
}
|
|
|
|
#ifdef _DEBUG_TRACE
|
|
// dump raw TLS data
|
|
if (pNewTLS == nullptr) {
|
|
EmuLog(LOG_LEVEL::DEBUG, "TLS Non-Existant (OK)");
|
|
} else {
|
|
EmuLog(LOG_LEVEL::DEBUG, "TLS Data Dump...");
|
|
if (g_bPrintfOn) {
|
|
for (uint32_t v = 4; v < dwCopySize + 4; v++) {// Note : Don't dump dwZeroSize
|
|
|
|
uint8_t *bByte = (uint8_t*)pNewTLS + v;
|
|
|
|
if (v % 0x10 == 0) {
|
|
EmuLog(LOG_LEVEL::DEBUG, "0x%.8X:", (xbox::addr_xt)bByte);
|
|
}
|
|
|
|
// Note : Use printf instead of EmuLog here, which prefixes with GetCurrentThreadId() :
|
|
printf(" %.2X", *bByte);
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// prepare TLS
|
|
{
|
|
if (pTLS->dwTLSIndexAddr != 0) {
|
|
*(xbox::addr_xt*)pTLS->dwTLSIndexAddr = xbox::zero;
|
|
}
|
|
|
|
// dword @ pTLSData := pTLSData
|
|
if (pNewTLS != nullptr)
|
|
*(void**)pNewTLS = pNewTLS;
|
|
}
|
|
}
|
|
|
|
// Allocate the xbox KPCR structure
|
|
base = xbox::zeroptr;
|
|
size = sizeof(xbox::KPCR);
|
|
xbox::NtAllocateVirtualMemory(&base, 0, &size, XBOX_MEM_RESERVE | XBOX_MEM_COMMIT, XBOX_PAGE_READWRITE);
|
|
xbox::KPCR *NewPcr = (xbox::KPCR*)base;
|
|
xbox::RtlZeroMemory(NewPcr, sizeof(xbox::KPCR));
|
|
xbox::NT_TIB *XbTib = &(NewPcr->NtTib);
|
|
xbox::PKPRCB Prcb = &(NewPcr->PrcbData);
|
|
// Note : As explained above (at EmuKeSetPcr), Cxbx cannot allocate one NT_TIB and KPRCB
|
|
// structure per thread, since Cxbx currently doesn't do thread-switching.
|
|
// Thus, the only way to give each thread it's own PrcbData.CurrentThread, is to put the
|
|
// KPCR pointer in the TIB_ArbitraryDataSlot, which is read by the above EmuFS_* patches.
|
|
//
|
|
// Once we simulate thread switching ourselves, we can update PrcbData.CurrentThread
|
|
// and simplify this initialization, by using only one KPCR for the single Xbox processor.
|
|
//
|
|
// One way to do our own (preemprive) thread-switching would be to use this technique :
|
|
// http://www.eran.io/implementing-a-preemptive-kernel-within-a-single-windows-thread/
|
|
// See https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/issues/146 for more info.
|
|
|
|
// Copy the Nt TIB over to the emulated TIB :
|
|
{
|
|
memcpy(XbTib, GetNtTib(), sizeof(NT_TIB));
|
|
// Fixup the TIB self pointer :
|
|
NewPcr->NtTib.Self = XbTib;
|
|
// Set the stack base - TODO : Verify this, doesn't look right?
|
|
NewPcr->NtTib.StackBase = pNewTLS;
|
|
|
|
// Write the Xbox stack base to the Host, allows ConvertThreadToFiber to work correctly
|
|
// Test case: DOA3
|
|
// NOTE: This is disabled due to cause of corruption to host's TIB and
|
|
// silent crash for xbox threads creation.
|
|
// Test case:
|
|
// * Direct3DCreate9Ex call from inside xbox thread
|
|
// * PCSTProxy (used from PsCreateSystemThreadEx export function)
|
|
//__writefsdword(TIB_StackBase, (DWORD)NewPcr->NtTib.StackBase);
|
|
//__writefsdword(TIB_StackLimit, (DWORD)NewPcr->NtTib.StackLimit);
|
|
}
|
|
|
|
// Set flat address of this PCR
|
|
NewPcr->SelfPcr = NewPcr;
|
|
// Set pointer to Prcb
|
|
NewPcr->Prcb = Prcb;
|
|
|
|
// Initialize the prcb :
|
|
{
|
|
// TODO : Once we do our own thread-switching (as mentioned above),
|
|
// we can also start using Prcb->DpcListHead instead of DpcQueue :
|
|
InitializeListHead(&(Prcb->DpcListHead));
|
|
Prcb->DpcRoutineActive = FALSE;
|
|
|
|
NewPcr->Irql = PASSIVE_LEVEL; // See KeLowerIrql;
|
|
}
|
|
|
|
// Initialize a fake PrcbData.CurrentThread
|
|
{
|
|
base = xbox::zeroptr;
|
|
size = sizeof(xbox::ETHREAD);
|
|
xbox::NtAllocateVirtualMemory(&base, 0, &size, XBOX_MEM_RESERVE | XBOX_MEM_COMMIT, XBOX_PAGE_READWRITE);
|
|
xbox::ETHREAD *EThread = (xbox::ETHREAD*)base; // Clear, to prevent side-effects on random contents
|
|
xbox::RtlZeroMemory(EThread, sizeof(xbox::ETHREAD));
|
|
|
|
EThread->Tcb.TlsData = pNewTLS;
|
|
EThread->UniqueThread = GetCurrentThreadId();
|
|
// Set PrcbData.CurrentThread
|
|
Prcb->CurrentThread = (xbox::KTHREAD*)EThread;
|
|
// Initialize the thread header and its wait list
|
|
Prcb->CurrentThread->Header.Type = xbox::ThreadObject;
|
|
Prcb->CurrentThread->Header.Size = sizeof(xbox::KTHREAD) / sizeof(xbox::long_xt);
|
|
InitializeListHead(&Prcb->CurrentThread->Header.WaitListHead);
|
|
// Also initialize the timer associated with the thread
|
|
xbox::KeInitializeTimer(&Prcb->CurrentThread->Timer);
|
|
xbox::PKWAIT_BLOCK WaitBlock = &Prcb->CurrentThread->TimerWaitBlock;
|
|
WaitBlock->Object = &Prcb->CurrentThread->Timer;
|
|
WaitBlock->WaitKey = (xbox::cshort_xt)STATUS_TIMEOUT;
|
|
WaitBlock->WaitType = xbox::WaitAny;
|
|
WaitBlock->Thread = Prcb->CurrentThread;
|
|
WaitBlock->WaitListEntry.Flink = &Prcb->CurrentThread->Timer.Header.WaitListHead;
|
|
WaitBlock->WaitListEntry.Blink = &Prcb->CurrentThread->Timer.Header.WaitListHead;
|
|
}
|
|
|
|
// Make the KPCR struct available to KeGetPcr()
|
|
EmuKeSetPcr(NewPcr);
|
|
|
|
EmuLog(LOG_LEVEL::DEBUG, "Installed KPCR in TIB_ArbitraryDataSlot (with pTLS = 0x%.8X)", pTLS);
|
|
}
|