Avoid using std::async in WaitApc

This commit is contained in:
ergo720 2022-02-17 02:33:44 +01:00
parent a769e896c6
commit 7589f0a94c
9 changed files with 117 additions and 88 deletions

View File

@ -38,6 +38,7 @@
#include "Logging.h"
#include "core\kernel\support\Emu.h"
#include "core\kernel\exports\EmuKrnl.h" // For DefaultLaunchDataPage
#include "core\kernel\exports\EmuKrnlKi.h"
#include "core\kernel\support\EmuFile.h"
#include "core\kernel\support\NativeHandle.h"
#include "EmuShared.h"
@ -997,16 +998,19 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait)
LOG_FUNC_ARG(bAlertable)
LOG_FUNC_END;
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
bool Exit = false;
auto async_bool = WaitApc(bAlertable, UserMode, &Exit);
LARGE_INTEGER ExpireTime, DueTime, NewTime;
ExpireTime.QuadPart = DueTime.QuadPart = (dwMilliseconds == INFINITE) ? ~0ull : -dwMilliseconds;
KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime);
dword_xt dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, dwMilliseconds, bAlertable);
xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional<DWORD> {
DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable);
if (dwRet == WAIT_TIMEOUT) {
return std::nullopt;
}
return std::make_optional<DWORD>(dwRet);
}, &NewTime, bAlertable, UserMode);
Exit = true;
bool result = async_bool.get();
RETURN(result ? X_STATUS_USER_APC : dwRet);
RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret);
}
// ******************************************************************

View File

@ -105,6 +105,7 @@ typedef void* LPSECURITY_ATTRIBUTES;
#define X_STATUS_NOT_COMMITTED 0xC000002DL
#define X_STATUS_UNRECOGNIZED_VOLUME 0xC000014FL
#define X_STATUS_OBJECT_PATH_NOT_FOUND 0xC000003AL
#define X_STATUS_TIMEOUT 0x00000102L
// ******************************************************************
// * Registry value types

View File

@ -210,50 +210,6 @@ const DWORD IrqlMasks[] = {
0x00000000, // IRQL 31 (HIGH_LEVEL)
};
// This helper function is used to signal WinApi waiting functions that the wait has been satisfied by an xbox user APC
static void WINAPI EndWait(ULONG_PTR Parameter)
{
// Do nothing
}
std::future<bool> WaitApc(xbox::boolean_xt Alertable, xbox::char_xt WaitMode, bool *Exit)
{
// NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should
// also interrupt the wait
xbox::PKPCR Kpcr = EmuKeGetPcr();
// This new thread must execute APCs in the context of the calling thread
return std::async(std::launch::async, [Kpcr, Alertable, WaitMode, Exit]() {
EmuKeSetPcr(Kpcr);
xbox::PETHREAD eThread = reinterpret_cast<xbox::PETHREAD>(Kpcr->Prcb->CurrentThread);
while (true) {
xbox::KiApcListMtx.lock();
bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]);
bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]);
xbox::KiApcListMtx.unlock();
if (EmptyKernel == false) {
xbox::KiExecuteKernelApc();
}
if ((EmptyUser == false) &&
(Alertable == TRUE) &&
(WaitMode == xbox::UserMode)) {
xbox::KiExecuteUserApc();
// Queue a native APC to the calling thread to forcefully terminate the wait of the WinApi functions,
// in the case it didn't terminate already
[[maybe_unused]] BOOL ret = QueueUserAPC(EndWait, *GetNativeHandle(eThread->UniqueThread), 0);
assert(ret);
EmuKeSetPcr(nullptr);
return true;
}
SwitchToThread();
if (*Exit) {
EmuKeSetPcr(nullptr);
return false;
}
}
});
}
// ******************************************************************
// * 0x0033 - InterlockedCompareExchange()

View File

@ -27,6 +27,7 @@
#include "core\kernel\init\CxbxKrnl.h"
#include "core\kernel\support\Emu.h"
#include "core\kernel\support\EmuFS.h"
#include <future>
// CONTAINING_RECORD macro
@ -104,6 +105,40 @@ extern HalSystemInterrupt HalSystemInterrupts[MAX_BUS_INTERRUPT_LEVEL + 1];
bool DisableInterrupts();
void RestoreInterruptMode(bool value);
void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql);
std::future<bool> WaitApc(xbox::boolean_xt Alertable, xbox::char_xt WaitMode, bool *Exit);
template<typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER AbsoluteExpireTime, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
{
// NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should
// also interrupt the wait
xbox::ulonglong_xt now = xbox::KeQueryInterruptTime();
xbox::PETHREAD eThread = reinterpret_cast<xbox::PETHREAD>(EmuKeGetPcr()->Prcb->CurrentThread);
while (now <= static_cast<xbox::ulonglong_xt>(AbsoluteExpireTime->QuadPart)) {
if (const auto ret = Lambda()) {
return *ret;
}
xbox::KiApcListMtx.lock();
bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]);
bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]);
xbox::KiApcListMtx.unlock();
if (EmptyKernel == false) {
xbox::KiExecuteKernelApc();
}
if ((EmptyUser == false) &&
(Alertable == TRUE) &&
(WaitMode == xbox::UserMode)) {
xbox::KiExecuteUserApc();
return X_STATUS_USER_APC;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
now = xbox::KeQueryInterruptTime();
}
return X_STATUS_TIMEOUT;
}
#endif

View File

@ -566,14 +566,27 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
// We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well
// Test case: Metal Slug 3
bool Exit = false;
auto async_bool = WaitApc(Alertable, WaitMode, &Exit);
LARGE_INTEGER NewTime;
if (Interval) {
LARGE_INTEGER ExpireTime, DueTime;
ExpireTime.QuadPart = DueTime.QuadPart = Interval->QuadPart;
KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime);
}
else {
NewTime.QuadPart = ~0ull;
}
NTSTATUS ret = NtDll::NtDelayExecution(Alertable, (NtDll::LARGE_INTEGER *)Interval);
xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime);
if (Status == 0) { // STATUS_SUCCESS
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, &NewTime, Alertable, WaitMode);
Exit = true;
bool result = async_bool.get();
RETURN(result ? X_STATUS_USER_APC : ret);
RETURN(ret);
}
// ******************************************************************
@ -2000,24 +2013,6 @@ XBSYSAPI EXPORTNUM(156) xbox::dword_xt VOLATILE xbox::KeTickCount = 0;
// ******************************************************************
XBSYSAPI EXPORTNUM(157) xbox::ulong_xt xbox::KeTimeIncrement = CLOCK_TIME_INCREMENT;
xbox::PLARGE_INTEGER FASTCALL KiComputeWaitInterval(
IN xbox::PLARGE_INTEGER OriginalTime,
IN xbox::PLARGE_INTEGER DueTime,
IN OUT xbox::PLARGE_INTEGER NewTime
)
{
if (OriginalTime->QuadPart >= 0) {
return OriginalTime;
}
else {
NewTime->QuadPart = xbox::KeQueryInterruptTime();
NewTime->QuadPart -= DueTime->QuadPart;
return NewTime;
}
}
// ******************************************************************
// * 0x009E - KeWaitForMultipleObjects()
// ******************************************************************

View File

@ -928,3 +928,20 @@ xbox::void_xt xbox::KiExecuteUserApc()
{
KiExecuteApc<UserMode>();
}
xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval
(
IN xbox::PLARGE_INTEGER OriginalTime,
IN xbox::PLARGE_INTEGER DueTime,
IN OUT xbox::PLARGE_INTEGER NewTime
)
{
if (OriginalTime->QuadPart >= 0) {
return OriginalTime;
}
else {
NewTime->QuadPart = xbox::KeQueryInterruptTime();
NewTime->QuadPart -= DueTime->QuadPart;
return NewTime;
}
}

View File

@ -139,6 +139,13 @@ namespace xbox
void_xt KiExecuteKernelApc();
void_xt KiExecuteUserApc();
PLARGE_INTEGER FASTCALL KiComputeWaitInterval
(
IN PLARGE_INTEGER OriginalTime,
IN PLARGE_INTEGER DueTime,
IN OUT PLARGE_INTEGER NewTime
);
};
extern xbox::KPROCESS KiUniqueProcess;

View File

@ -2200,19 +2200,32 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
}
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
bool Exit = false;
auto async_bool = WaitApc(Alertable, WaitMode, &Exit);
LARGE_INTEGER NewTime;
if (Timeout) {
LARGE_INTEGER ExpireTime, DueTime;
ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart;
KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime);
}
else {
NewTime.QuadPart = ~0ull;
}
NTSTATUS ret = NtDll::NtWaitForMultipleObjects(
Count,
Handles,
(NtDll::OBJECT_WAIT_TYPE)WaitType,
Alertable,
(NtDll::PLARGE_INTEGER)Timeout);
xbox::ntstatus_xt ret = WaitApc([Count, Handles, WaitType, Alertable]() -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtWaitForMultipleObjects(
Count,
Handles,
(NtDll::OBJECT_WAIT_TYPE)WaitType,
Alertable,
&ExpireTime);
if (Status == STATUS_TIMEOUT) {
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, &NewTime, Alertable, WaitMode);
Exit = true;
bool result = async_bool.get();
RETURN(result ? X_STATUS_USER_APC : ret);
RETURN(ret);
}
// ******************************************************************

View File

@ -56,6 +56,7 @@ void RegisterXboxHandle(xbox::HANDLE xhandle, HANDLE nhandle)
if (ret.second) {
return;
}
now += std::chrono::milliseconds(5);
}
// If we reach here, it means that we could not insert the handle after more than two seconds of trying. This probably means