From 25a0752e58dea47ce1a3c268e01a66cee8e0b250 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Fri, 15 Aug 2025 19:40:41 -0700 Subject: [PATCH] FEXCore: Move LDT/GDT management to the frontend This will allow the frontend to manage LDT and GDT so that Linux will be able to install 32-bit code segments. --- FEXCore/Source/Interface/Core/Core.cpp | 22 ------------ .../LinuxSyscalls/ThreadManager.cpp | 34 ++++++++++++++++++- .../LinuxSyscalls/ThreadManager.h | 4 +++ Source/Windows/ARM64EC/Module.cpp | 29 ++++++++++++++-- Source/Windows/WOW64/Module.cpp | 32 ++++++++++++++--- 5 files changed, 91 insertions(+), 30 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 55df06c93..baccd94ee 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -451,28 +451,6 @@ ContextImpl::CreateThread(uint64_t InitialRIP, uint64_t StackPointer, const FEXC memcpy(&Thread->CurrentFrame->State, NewThreadState, sizeof(FEXCore::Core::CPUState)); } - Thread->CurrentFrame->State.segment_arrays[0] = &Thread->CurrentFrame->State.private_gdt[0]; - // TODO: LDTs are currently unsupported, mirror them to GDT. - Thread->CurrentFrame->State.segment_arrays[1] = &Thread->CurrentFrame->State.private_gdt[0]; - - // Set up default code segment. - // Default code segment indexes match the numbers that the Linux kernel uses. - Thread->CurrentFrame->State.cs_idx = Core::CPUState::DEFAULT_USER_CS << 3; - - // TODO: Segment management should be moved to the frontend. - auto GDT = Thread->CurrentFrame->State.GetSegmentFromIndex(Thread->CurrentFrame->State, Thread->CurrentFrame->State.cs_idx); - Core::CPUState::SetGDTBase(GDT, 0); - Core::CPUState::SetGDTLimit(GDT, 0xF'FFFFU); - - if (Config.Is64BitMode) { - GDT->L = 1; // L = Long Mode = 64-bit - GDT->D = 0; // D = Default Operand SIze = Reserved - } - else { - GDT->L = 0; // L = Long Mode = 32-bit - GDT->D = 1; // D = Default Operand Size = 32-bit - } - // Set up the thread manager state Thread->CurrentFrame->Thread = Thread; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp index 704757766..b1d9b2e67 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp @@ -166,19 +166,51 @@ FEX::HLE::ThreadStateObject* ThreadManager::CreateThread(uint64_t InitialRIP, ui ThreadStateObject->ThreadInfo.TID = FHU::Syscalls::gettid(); ThreadStateObject->Thread = CTX->CreateThread(InitialRIP, StackPointer, NewThreadState); + auto Frame = ThreadStateObject->Thread->CurrentFrame; // Allocate the call-ret stack with guard pages on both sides auto AllocBase = reinterpret_cast(::mmap(nullptr, CALLRET_STACK_ALLOC_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); // Set the base used for invalidation to the start past the guard pages ThreadStateObject->Thread->CallRetStackBase = reinterpret_cast(AllocBase + FEXCore::Utils::FEX_PAGE_SIZE); ::mprotect(ThreadStateObject->Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE, PROT_READ | PROT_WRITE); - ThreadStateObject->Thread->CurrentFrame->State.callret_sp = ThreadStateObject->GetCallRetStackInfo().DefaultLocation; + Frame->State.callret_sp = ThreadStateObject->GetCallRetStackInfo().DefaultLocation; ThreadStateObject->Thread->FrontendPtr = ThreadStateObject; if (ProfileStats()) { ThreadStateObject->Thread->ThreadStats = Stat.AllocateSlot(ThreadStateObject->ThreadInfo.TID); } + // GDT and LDT are tracked per thread. + Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT] = &ThreadStateObject->gdt[0]; + // TODO: LDTs are currently unsupported, mirror them to GDT. + Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = &ThreadStateObject->gdt[0]; + + if (InheritThread) { + // If we are inheriting thread data then we inherit both the gdt and ldt arrays. + // They are then forked from the parent thread. + static_assert(sizeof(ThreadStateObject->gdt) == (8 * 32)); + memcpy(ThreadStateObject->gdt, InheritThread->gdt, sizeof(ThreadStateObject->gdt)); + // TODO: Inherit LDT once supported. + } + else { + // Without any thread data to inherit, setup the default gdt. + // Default code segment indexes match the numbers that the Linux kernel uses. + Frame->State.cs_idx = FEXCore::Core::CPUState::DEFAULT_USER_CS << 3; + auto GDT = FEXCore::Core::CPUState::GetSegmentFromIndex(Frame->State, Frame->State.cs_idx); + FEXCore::Core::CPUState::SetGDTBase(GDT, 0); + FEXCore::Core::CPUState::SetGDTLimit(GDT, 0xF'FFFFU); + Frame->State.cs_cached = FEXCore::Core::CPUState::CalculateGDTBase(*FEXCore::Core::CPUState::GetSegmentFromIndex(Frame->State, Frame->State.cs_idx)); + + if (Is64BitMode()) { + GDT->L = 1; // L = Long Mode = 64-bit + GDT->D = 0; // D = Default Operand SIze = Reserved + } + else { + GDT->L = 0; // L = Long Mode = 32-bit + GDT->D = 1; // D = Default Operand Size = 32-bit + } + } + if (InheritThread) { FEX::HLE::_SyscallHandler->SeccompEmulator.InheritSeccompFilters(InheritThread, ThreadStateObject); ThreadStateObject->persona = InheritThread->persona; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h index 8cc6ddf92..015e34935 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h @@ -109,6 +109,9 @@ struct ThreadStateObject : public FEXCore::Allocator::FEXAllocOperators { return {Base - FEXCore::Utils::FEX_PAGE_SIZE, Base + FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE + FEXCore::Utils::FEX_PAGE_SIZE, Base + FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE / 4}; } + + // GDT and LDT tracking + FEXCore::Core::CPUState::gdt_segment gdt[32] {}; }; class ThreadManager final { @@ -239,6 +242,7 @@ private: void HandleThreadDeletion(FEX::HLE::ThreadStateObject* Thread, bool NeedsTLSUninstall = false); void NotifyPause(); FEX_CONFIG_OPT(ProfileStats, PROFILESTATS); + FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); }; } // namespace FEX::HLE diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 03ab52ea5..73c39183f 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -644,6 +644,7 @@ NTSTATUS ProcessInit() { CTX->SetSignalDelegator(SignalDelegator.get()); CTX->SetSyscallHandler(SyscallHandler.get()); CTX->InitCore(); + InvalidationTracker.emplace(*CTX, Threads); HandleImageMap(NtDllBase); @@ -924,6 +925,24 @@ NTSTATUS ThreadInit() { auto* Thread = CTX->CreateThread(0, 0); + // Default segment setup. + auto Frame = Thread->CurrentFrame; + auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32]; + + // Setup initial code-segment GDT + auto &GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS]; + FEXCore::Core::CPUState::SetGDTBase(&GDT, 0); + FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU); + GDT.L = 1; // L = Long Mode = 64-bit + GDT.D = 0; // D = Default Operand SIze = Reserved + + Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT] = &NewSegments[0]; + // TODO: LDTs are currently unsupported, mirror them to GDT. + Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = &NewSegments[0]; + + Frame->State.cs_idx = FEXCore::Core::CPUState::DEFAULT_USER_CS << 3; + Frame->State.cs_cached = FEXCore::Core::CPUState::CalculateGDTBase(GDT); + FEX::Windows::CallRetStack::InitializeThread(Thread); Thread->CurrentFrame->Pointers.Common.ExitFunctionEC = reinterpret_cast(&ExitFunctionEC); CPUArea.StateFrame() = Thread->CurrentFrame; @@ -935,7 +954,7 @@ NTSTATUS ThreadInit() { CPUArea.DispatcherLoopTopEnterECFillSRA() = EnterECFillSRA; CPUArea.ContextAmd64() = {.ContextFlags = CONTEXT_CONTROL | CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, - .AMD64_SegCs = 0x33, + .AMD64_SegCs = (FEXCore::Core::CPUState::DEFAULT_USER_CS << 3) | 3, .AMD64_SegDs = 0x2b, .AMD64_SegEs = 0x2b, .AMD64_SegFs = 0x53, @@ -1000,9 +1019,13 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { StatAllocHandler->DeallocateSlot(CPUArea.ThreadState()->ThreadStats); } } + auto ThreadState = CPUArea.ThreadState(); - FEX::Windows::CallRetStack::DestroyThread(CPUArea.ThreadState()); - CTX->DestroyThread(CPUArea.ThreadState()); + // GDT and LDT are mirrored, only free one. + delete [] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; + + FEX::Windows::CallRetStack::DestroyThread(ThreadState); + CTX->DestroyThread(ThreadState); ::VirtualFree(reinterpret_cast(CPUArea.EmulatorStackLimit()), 0, MEM_RELEASE); if (ThreadTID == GetCurrentThreadId()) { FEX::Windows::DeinitCRTThread(); diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 88d8f87c3..44fabebff 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -508,6 +508,7 @@ void BTCpuProcessInit() { CTX->SetSignalDelegator(SignalDelegator.get()); CTX->SetSyscallHandler(SyscallHandler.get()); CTX->InitCore(); + InvalidationTracker.emplace(*CTX, Threads); auto NtDllX86 = reinterpret_cast(GetProcAddress(NtDll, "LdrSystemDllInitBlock"))->ntdll_handle; @@ -565,10 +566,29 @@ void BTCpuThreadInit() { FEX::Windows::InitCRTThread(); auto* Thread = CTX->CreateThread(0, 0); + // Default segment setup. + auto Frame = Thread->CurrentFrame; + auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32]; + + // Setup initial code-segment GDT + auto &GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS]; + FEXCore::Core::CPUState::SetGDTBase(&GDT, 0); + FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU); + GDT.L = 0; // L = Long Mode = 32-bit + GDT.D = 1; // D = Default Operand Size = 32-bit + + Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT] = &NewSegments[0]; + // TODO: LDTs are currently unsupported, mirror them to GDT. + Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = &NewSegments[0]; + + Frame->State.cs_idx = FEXCore::Core::CPUState::DEFAULT_USER_CS << 3; + Frame->State.cs_cached = FEXCore::Core::CPUState::CalculateGDTBase(GDT); + FEX::Windows::CallRetStack::InitializeThread(Thread); - GetTLS().ThreadState() = Thread; - GetTLS().ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed); + auto TLS = GetTLS(); + TLS.ThreadState() = Thread; + TLS.ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed); auto ThreadTID = GetCurrentThreadId(); Threads.emplace(ThreadTID, Thread); @@ -614,9 +634,13 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { StatAllocHandler->DeallocateSlot(TLS.ThreadState()->ThreadStats); } } + auto ThreadState = TLS.ThreadState(); - FEX::Windows::CallRetStack::DestroyThread(TLS.ThreadState()); - CTX->DestroyThread(TLS.ThreadState()); + // GDT and LDT are mirrored, only free one. + delete [] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; + + FEX::Windows::CallRetStack::DestroyThread(ThreadState); + CTX->DestroyThread(ThreadState); if (Self) { FEX::Windows::DeinitCRTThread(); }