| 778 | } |
| 779 | |
| 780 | NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { |
| 781 | if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_SUSPEND_RESUME)) { |
| 782 | return STATUS_ACCESS_DENIED; |
| 783 | } |
| 784 | |
| 785 | auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); |
| 786 | THREAD_BASIC_INFORMATION Info; |
| 787 | if (NTSTATUS Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { |
| 788 | return Err; |
| 789 | } |
| 790 | |
| 791 | const auto ThreadTID = reinterpret_cast<uint64_t>(Info.ClientId.UniqueThread); |
| 792 | if (ThreadTID == GetCurrentThreadId()) { |
| 793 | LogMan::Msg::DFmt("Suspending self"); |
| 794 | // Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using |
| 795 | // SetThreadContext (which doesn't use the BTCpu API) |
| 796 | if (!(GetTLS().ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { |
| 797 | if (NTSTATUS Err = Context::FlushThreadStateContext(*ThreadDup); Err) { |
| 798 | return Err; |
| 799 | } |
| 800 | } |
| 801 | |
| 802 | return NtSuspendThread(*ThreadDup, Count); |
| 803 | } |
| 804 | |
| 805 | LogMan::Msg::DFmt("Suspending thread: {:X}", ThreadTID); |
| 806 | |
| 807 | auto [Err, TLS] = GetThreadTLS(*ThreadDup); |
| 808 | if (Err) { |
| 809 | return Err; |
| 810 | } |
| 811 | |
| 812 | std::scoped_lock Lock(ThreadCreationMutex); |
| 813 | |
| 814 | // If the thread hasn't yet been initialized, suspend it without special handling as it wont yet have entered the JIT |
| 815 | if (!Threads.contains(ThreadTID)) { |
| 816 | LogMan::Msg::DFmt("Thread suspended: {:X}", ThreadTID); |
| 817 | return NtSuspendThread(*ThreadDup, Count); |
| 818 | } |
| 819 | |
| 820 | // If CONTROL_IN_JIT is unset at this point, then it can never be set (and thus the JIT cannot be reentered) as |
| 821 | // CONTROL_PAUSED has been set, as such, while this may redundantly request interrupts in rare cases it will never |
| 822 | // miss them |
| 823 | if (TLS.ControlWord().fetch_or(ControlBits::PAUSED, std::memory_order::relaxed) & ControlBits::IN_JIT) { |
| 824 | LogMan::Msg::DFmt("Thread {:X} is in JIT, polling for interrupt", ThreadTID); |
| 825 | |
| 826 | ULONG TmpProt; |
| 827 | void* TmpAddress = &TLS.ThreadState()->InterruptFaultPage; |
| 828 | SIZE_T TmpSize = FEXCore::Utils::FEX_PAGE_SIZE; |
| 829 | NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_READONLY, &TmpProt); |
| 830 | } |
| 831 | |
| 832 | // Spin until the JIT is interrupted |
| 833 | while (TLS.ControlWord().load() & ControlBits::IN_JIT) |
| 834 | ; |
| 835 | |
| 836 | // The JIT has now been interrupted and the context stored in the thread's CPU area is up-to-date |
| 837 | if (Err = NtSuspendThread(*ThreadDup, Count); Err) { |
nothing calls this directly
no test coverage detected