Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions inc/usersim/ke.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ _IRQL_requires_min_(PASSIVE_LEVEL) _When_((timeout == NULL || timeout->QuadPart
_In_ BOOLEAN alertable,
_In_opt_ PLARGE_INTEGER timeout);

USERSIM_API
_IRQL_requires_min_(PASSIVE_LEVEL) _IRQL_requires_max_(APC_LEVEL) NTKERNELAPI NTSTATUS
KeDelayExecutionThread(
_In_ __drv_strictType(KPROCESSOR_MODE / enum _MODE, __drv_typeConst) KPROCESSOR_MODE wait_mode,
_In_ BOOLEAN alertable,
_In_ PLARGE_INTEGER interval);

USERSIM_API
_IRQL_requires_same_ ULONG64
KeQueryUnbiasedInterruptTimePrecise(_Out_ PULONG64 qpc_time_stamp);
Expand Down
23 changes: 23 additions & 0 deletions src/ke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,29 @@ KeReadStateSemaphore(_In_ PRKSEMAPHORE semaphore)

#pragma endregion semaphores

_IRQL_requires_min_(PASSIVE_LEVEL) _IRQL_requires_max_(APC_LEVEL) NTKERNELAPI NTSTATUS
KeDelayExecutionThread(
_In_ __drv_strictType(KPROCESSOR_MODE / enum _MODE, __drv_typeConst) KPROCESSOR_MODE wait_mode,
_In_ BOOLEAN alertable,
_In_ PLARGE_INTEGER interval)
{
UNREFERENCED_PARAMETER(wait_mode);
UNREFERENCED_PARAMETER(alertable);

// Interval is expressed in 100-ns units. A negative value is a delay relative to the current time; a
// non-negative value is an absolute expiration time, which the user-mode mock does not track and therefore
// treats as no delay.
DWORD delay_ms = 0;
if (interval->QuadPart < 0) {
// Convert the relative 100-ns interval to milliseconds, rounding up so a sub-millisecond request still
// yields a non-zero wait.
delay_ms = (DWORD)((-interval->QuadPart + 9999) / 10000);
}

Sleep(delay_ms);
return STATUS_SUCCESS;
}

_IRQL_requires_max_(APC_LEVEL) NTKERNELAPI VOID
KeStackAttachProcess(_Inout_ PRKPROCESS process, _Out_ PRKAPC_STATE apc_state)
{
Expand Down
21 changes: 21 additions & 0 deletions tests/ke_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,24 @@ TEST_CASE("event", "[ke]")
REQUIRE(wait_status == STATUS_TIMEOUT);
REQUIRE(end_time - start_time >= 1000);
}

TEST_CASE("KeDelayExecutionThread", "[ke]")
{
// A negative interval is a relative delay; the call should block for at least the requested duration.
LARGE_INTEGER interval = {0};
interval.QuadPart = -10 * 1000 * 10ll; // 10 ms, expressed in 100-ns units.

uint64_t qpc_time;
uint64_t start_time = KeQueryUnbiasedInterruptTimePrecise(&qpc_time);

REQUIRE(KeDelayExecutionThread(KernelMode, FALSE, &interval) == STATUS_SUCCESS);

uint64_t end_time = KeQueryUnbiasedInterruptTimePrecise(&qpc_time);

// Returned time is in 100-ns units. Assert a conservative lower bound (1 ms) to avoid timer-granularity flakiness.
REQUIRE(end_time - start_time >= 10000);

// A zero interval must return immediately without blocking.
interval.QuadPart = 0;
REQUIRE(KeDelayExecutionThread(KernelMode, FALSE, &interval) == STATUS_SUCCESS);
}