[PATCH 3/3] ntoskrnl.exe: Implement KeInsertQueueApc.
Derek Lesho
dlesho at codeweavers.com
Wed Sep 9 13:32:29 CDT 2020
Signed-off-by: Derek Lesho <dlesho at codeweavers.com>
---
dlls/ntoskrnl.exe/ntoskrnl.c | 2 +
dlls/ntoskrnl.exe/ntoskrnl_private.h | 4 +
dlls/ntoskrnl.exe/sync.c | 160 ++++++++++++++++++++++++++-
dlls/ntoskrnl.exe/tests/driver.c | 9 --
dlls/ntoskrnl.exe/tests/ntoskrnl.c | 1 -
server/protocol.def | 14 ++-
server/thread.c | 136 +++++++++++++++++++----
server/thread.h | 1 +
8 files changed, 290 insertions(+), 37 deletions(-)
diff --git a/dlls/ntoskrnl.exe/ntoskrnl.c b/dlls/ntoskrnl.exe/ntoskrnl.c
index a0f45b4135c..43f203faa10 100644
--- a/dlls/ntoskrnl.exe/ntoskrnl.c
+++ b/dlls/ntoskrnl.exe/ntoskrnl.c
@@ -2318,6 +2318,8 @@ static void *create_thread_object( HANDLE handle )
}
}
+ InitializeListHead(&thread->ApcListHead[KernelMode]);
+ InitializeListHead(&thread->ApcListHead[UserMode]);
return thread;
}
diff --git a/dlls/ntoskrnl.exe/ntoskrnl_private.h b/dlls/ntoskrnl.exe/ntoskrnl_private.h
index a1e1b892e8c..12e67f75e0d 100644
--- a/dlls/ntoskrnl.exe/ntoskrnl_private.h
+++ b/dlls/ntoskrnl.exe/ntoskrnl_private.h
@@ -50,6 +50,10 @@ struct _KTHREAD
CLIENT_ID id;
unsigned int critical_region;
KAFFINITY user_affinity;
+ LIST_ENTRY ApcListHead[2];
+ CRITICAL_SECTION apc_cs;
+ HANDLE apc_event;
+ HANDLE imposter_thread;
};
struct _ETHREAD
diff --git a/dlls/ntoskrnl.exe/sync.c b/dlls/ntoskrnl.exe/sync.c
index c96a490ff04..b9ca7d71957 100644
--- a/dlls/ntoskrnl.exe/sync.c
+++ b/dlls/ntoskrnl.exe/sync.c
@@ -696,14 +696,170 @@ void WINAPI KeInitializeApc(PRKAPC apc, PRKTHREAD thread, KAPC_ENVIRONMENT env,
}
}
+static DWORD WINAPI thread_impersonate_loop(PVOID context)
+{
+ PKTHREAD thread = (PKTHREAD) context;
+ PKAPC apc;
+ HANDLE apc_handle;
+ PKKERNEL_ROUTINE krnl_routine;
+ PKNORMAL_ROUTINE nrml_routine;
+ PVOID nrml_ctx;
+ PVOID sysarg1;
+ PVOID sysarg2;
+ KPROCESSOR_MODE apc_mode;
+ BOOL special_apc;
+ PKAPC wait_apcs[2];
+ HANDLE wait_handles[3];
+ DWORD wait_count;
+ NTSTATUS stat;
+
+ NtCurrentTeb()->SystemReserved1[15] = thread;
+
+ for(;;)
+ {
+ wait_handles[0] = thread->apc_event;
+ wait_count = 1;
+ EnterCriticalSection(&thread->apc_cs);
+ if (!IsListEmpty(&thread->ApcListHead[KernelMode]))
+ {
+ wait_apcs[0] = CONTAINING_RECORD(thread->ApcListHead[KernelMode].Flink, KAPC, ApcListEntry);
+ wait_handles[1] = wine_server_ptr_handle(wait_apcs[0]->Spare0);
+ wait_count++;
+ }
+ if (!IsListEmpty(&thread->ApcListHead[UserMode]))
+ {
+ wait_apcs[wait_count - 1] = CONTAINING_RECORD(thread->ApcListHead[UserMode].Flink, KAPC, ApcListEntry);
+ wait_handles[wait_count] = wine_server_ptr_handle(wait_apcs[wait_count - 1]->Spare0);
+ wait_count++;
+ }
+ LeaveCriticalSection(&thread->apc_cs);
+
+ TRACE("%u %p %p %p\n", wait_count, wait_handles[0], wait_handles[1], wait_handles[2]);
+ stat = NtWaitForMultipleObjects(wait_count, wait_handles, WaitAny, FALSE, NULL);
+ if (stat < 0)
+ {
+ ERR("Failed to wait for APC err=%x\n", stat);
+ return 1;
+ }
+ if (stat == WAIT_OBJECT_0)
+ continue;
+
+ apc = wait_apcs[stat - 1];
+
+ EnterCriticalSection(&thread->apc_cs);
+ RemoveEntryList(&apc->ApcListEntry);
+ LeaveCriticalSection(&thread->apc_cs);
+
+ apc->Inserted = FALSE;
+
+ /* copy the APC, as KernelRoutine can free it */
+ apc_handle = wine_server_ptr_handle(apc->Spare0);
+ krnl_routine = apc->KernelRoutine;
+ nrml_routine = apc->NormalRoutine;
+ nrml_ctx = apc->NormalContext;
+ sysarg1 = apc->SystemArgument1;
+ sysarg2 = apc->SystemArgument2;
+ apc_mode = apc->ApcMode;
+
+ special_apc = apc_mode == KernelMode && !nrml_routine;
+
+ TRACE("\1%04x:%04x:Call %s APC %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, special_apc ? "Special" : "Normal", krnl_routine);
+ krnl_routine(apc, &nrml_routine, &nrml_ctx, &sysarg1, &sysarg2);
+ TRACE("\1%04x:%04x:Ret %s APC %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, special_apc ? "Special" : "Normal", krnl_routine);
+
+ if (nrml_routine && apc_mode == KernelMode && !special_apc)
+ {
+ TRACE("\1%04x:%04x:Call kernel APC NormalRoutine %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, nrml_routine);
+ nrml_routine(nrml_ctx, sysarg1, sysarg2);
+ TRACE("\1%04x:%04x:Ret kernel APC NormalRoutine %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, nrml_routine);
+ }
+
+ SERVER_START_REQ(finalize_apc)
+ {
+ req->handle = wine_server_obj_handle(apc_handle);
+ if (apc_mode == UserMode && nrml_routine)
+ {
+ TRACE("finalizing APC as %p\n", nrml_routine);
+ req->call.type = APC_USER;
+ req->call.user.user.func = wine_server_client_ptr(nrml_routine);
+ req->call.user.user.args[0] = (ULONG_PTR) nrml_ctx;
+ req->call.user.user.args[1] = (ULONG_PTR) sysarg1;
+ req->call.user.user.args[2] = (ULONG_PTR) sysarg2;
+ }
+ else
+ req->call.type = APC_NONE;
+ if ((stat = wine_server_call( req )))
+ {
+ ERR("Failed to finalize apc! err=%x\n", stat);
+ }
+ }
+ SERVER_END_REQ;
+
+ CloseHandle(apc_handle);
+ }
+
+ return 0;
+}
+
/***********************************************************************
* KeInsertQueueApc (NTOSKRNL.EXE.@)
*/
BOOLEAN WINAPI KeInsertQueueApc(PRKAPC apc, PVOID sysarg1, PVOID sysarg2, KPRIORITY increment)
{
- FIXME("apc %p arg1 %p arg2 %p inc %u\n", apc, sysarg1, sysarg2, increment);
+ NTSTATUS stat;
+ HANDLE thread_handle;
+ obj_handle_t apc_handle;
+
+ TRACE("apc %p arg1 %p arg2 %p inc %u\n", apc, sysarg1, sysarg2, increment);
- return FALSE;
+ if(!apc->Thread->imposter_thread)
+ {
+ if (!(apc->Thread->apc_event = CreateEventA(NULL, FALSE, FALSE, NULL)))
+ return FALSE;
+ InitializeCriticalSection(&apc->Thread->apc_cs);
+ if (!(apc->Thread->imposter_thread = CreateThread(NULL, 0, thread_impersonate_loop, apc->Thread, 0, NULL)))
+ {
+ DeleteCriticalSection(&apc->Thread->apc_cs);
+ if (apc->Thread->apc_event) CloseHandle(apc->Thread->apc_event);
+ return FALSE;
+ }
+ }
+
+ if ((stat = ObOpenObjectByPointer(apc->Thread, OBJ_KERNEL_HANDLE, NULL, THREAD_SET_CONTEXT, PsThreadType, KernelMode, &thread_handle)))
+ {
+ ERR("Failed to open APC thread; err=%x\n", stat);
+ return FALSE;
+ }
+
+ SERVER_START_REQ( queue_apc )
+ {
+ req->handle = wine_server_obj_handle(thread_handle);
+ req->call.type = apc->ApcMode ? APC_REAL_USER : APC_REAL_KERNEL;
+ req->call.real_apc.special_apc = apc->ApcMode == APC_REAL_KERNEL && !apc->NormalRoutine;
+ stat = wine_server_call( req );
+ apc_handle = reply->handle;
+ }
+ SERVER_END_REQ;
+
+ CloseHandle(thread_handle);
+
+ if (stat)
+ {
+ ERR("Failed to queue real APC, err=%x\n", stat);
+ return FALSE;
+ }
+
+ apc->SystemArgument1 = sysarg1;
+ apc->SystemArgument2 = sysarg2;
+ apc->Inserted = TRUE;
+ apc->Spare0 = apc_handle;
+
+ EnterCriticalSection(&apc->Thread->apc_cs);
+ InsertTailList(&apc->Thread->ApcListHead[(DWORD)apc->ApcMode], &apc->ApcListEntry);
+ if (!(SetEvent(apc->Thread->apc_event)))
+ ERR("Failed to set apc event!\n");
+ LeaveCriticalSection(&apc->Thread->apc_cs);
+ return TRUE;
}
/***********************************************************************
diff --git a/dlls/ntoskrnl.exe/tests/driver.c b/dlls/ntoskrnl.exe/tests/driver.c
index 32095476abf..a5a9bd67658 100644
--- a/dlls/ntoskrnl.exe/tests/driver.c
+++ b/dlls/ntoskrnl.exe/tests/driver.c
@@ -2075,16 +2075,7 @@ static void test_apc(const struct test_input *test_input)
pKeInitializeApc(&special_apc, current_apc_thread, OriginalApcEnvironment, kernel_routine, rundown_routine,
NULL, KernelMode, (PVOID)(ULONG_PTR)0xdeadbeef);
res = pKeInsertQueueApc(&special_apc, normal_routine, done_event, 0);
-todo_wine
ok(res, "KeInsertQueueApc failed.\n");
- if (!res)
- {
- KeSetEvent(&terminate_event, 0, FALSE);
- ObDereferenceObject(current_apc_thread);
- ObDereferenceObject(done_event);
- ZwClose(done_event_system_handle);
- return;
- }
stat = wait_single(done_event, 5 * -10000000);
ok(stat == STATUS_WAIT_0, "Waiting on special kernel APC to complete failed: %#x\n", stat);
diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c
index 83766a7783a..d8d9204d782 100644
--- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c
+++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c
@@ -161,7 +161,6 @@ DWORD WINAPI apc_host_thread_func(PVOID param)
HANDLE done_event = param;
WaitForSingleObjectEx(done_event, INFINITE, TRUE);
-todo_wine
ok (apc_ran == TRUE, "Driver failed to queue user mode APC\n");
CloseHandle(done_event);
return 0;
diff --git a/server/protocol.def b/server/protocol.def
index 92290af701c..8c5cb253ac5 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -462,7 +462,9 @@ enum apc_type
APC_MAP_VIEW,
APC_UNMAP_VIEW,
APC_CREATE_THREAD,
- APC_BREAK_PROCESS
+ APC_BREAK_PROCESS,
+ APC_REAL_USER,
+ APC_REAL_KERNEL,
};
typedef union
@@ -572,6 +574,11 @@ typedef union
mem_size_t reserve; /* reserve size for thread stack */
mem_size_t commit; /* commit size for thread stack */
} create_thread;
+ struct
+ {
+ enum apc_type type; /* APC_REAL_KERNEL && APC_REAL_USER */
+ int special_apc;
+ } real_apc;
} apc_call_t;
typedef union
@@ -1037,6 +1044,11 @@ struct rawinput_device
int self; /* run APC in caller itself? */
@END
+ at REQ(finalize_apc)
+ obj_handle_t handle; /* handle to the apc */
+ apc_call_t call; /* real call */
+ at END
+
/* Get the result of an APC call */
@REQ(get_apc_result)
diff --git a/server/thread.c b/server/thread.c
index 9b14174578e..69b2886462b 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -75,6 +75,7 @@ struct thread_wait
struct thread *thread; /* owner thread */
int count; /* count of objects */
int flags;
+ int in_kernel; /* we can't timeout when winedevice is working on the thread's behalf */
int abandoned;
enum select_op select;
client_ptr_t key; /* wait key for keyed events */
@@ -90,6 +91,7 @@ struct thread_apc
{
struct object obj; /* object header */
struct list entry; /* queue linked list */
+ struct thread *callee; /* thread this apc is queued on */
struct thread *caller; /* thread that queued this apc */
struct object *owner; /* object that queued this apc */
int executed; /* has it been executed by the client? */
@@ -245,6 +247,7 @@ static inline void init_thread_structure( struct thread *thread )
list_init( &thread->mutex_list );
list_init( &thread->system_apc );
+ list_init( &thread->kernel_apc);
list_init( &thread->user_apc );
list_init( &thread->kernel_object );
@@ -391,6 +394,7 @@ static void cleanup_thread( struct thread *thread )
thread->context = NULL;
}
clear_apc_queue( &thread->system_apc );
+ clear_apc_queue( &thread->kernel_apc );
clear_apc_queue( &thread->user_apc );
free( thread->req_data );
free( thread->reply_data );
@@ -488,6 +492,7 @@ static int thread_apc_signaled( struct object *obj, struct wait_queue_entry *ent
static void thread_apc_destroy( struct object *obj )
{
struct thread_apc *apc = (struct thread_apc *)obj;
+ if (apc->callee) release_object( apc->callee);
if (apc->caller) release_object( apc->caller );
if (apc->owner) release_object( apc->owner );
}
@@ -501,6 +506,7 @@ static struct thread_apc *create_apc( struct object *owner, const apc_call_t *ca
{
apc->call = *call_data;
apc->caller = NULL;
+ apc->callee = NULL;
apc->owner = owner;
apc->executed = 0;
apc->result.type = APC_NONE;
@@ -769,6 +775,7 @@ static int wait_on( const select_op_t *select_op, unsigned int count, struct obj
wait->user = NULL;
wait->when = when;
wait->abandoned = 0;
+ wait->in_kernel = 0;
current->wait = wait;
for (i = 0, entry = wait->queues; i < count; i++, entry++)
@@ -816,6 +823,16 @@ static int check_wait( struct thread *thread )
if ((wait->flags & SELECT_INTERRUPTIBLE) && !list_empty( &thread->system_apc ))
return STATUS_KERNEL_APC;
+ if ((wait->flags & SELECT_INTERRUPTIBLE && !list_empty( &thread->kernel_apc )))
+ {
+ struct thread_apc *apc = LIST_ENTRY( list_head(&thread->kernel_apc), struct thread_apc, entry );
+ assert(apc->call.type == APC_REAL_KERNEL);
+ thread->wait->in_kernel = 1;
+ apc->executed = 1;
+ wake_up( &apc->obj, 0 );
+ return -1;
+ }
+
/* Suspended threads may not acquire locks, but they can run system APCs */
if (thread->process->suspend + thread->suspend > 0) return -1;
@@ -834,7 +851,20 @@ static int check_wait( struct thread *thread )
if (entry->obj->ops->signaled( entry->obj, entry )) return i;
}
- if ((wait->flags & SELECT_ALERTABLE) && !list_empty(&thread->user_apc)) return STATUS_USER_APC;
+ if ((wait->flags & SELECT_ALERTABLE) && !list_empty(&thread->user_apc))
+ {
+ struct thread_apc *apc = LIST_ENTRY( list_head(&thread->user_apc), struct thread_apc, entry );
+ if (apc->call.type != APC_REAL_USER)
+ return STATUS_USER_APC;
+ else
+ {
+ thread->wait->in_kernel = 1;
+ apc->executed = 1;
+ wake_up( &apc->obj, 0 );
+ return -1;
+ }
+ }
+
if (wait->when >= 0 && wait->when <= current_time) return STATUS_TIMEOUT;
if (wait->when < 0 && -wait->when <= monotonic_time) return STATUS_TIMEOUT;
return -1;
@@ -1018,7 +1048,7 @@ static void select_on( const select_op_t *select_op, data_size_t op_size, client
}
/* now we need to wait */
- if (current->wait->when != TIMEOUT_INFINITE)
+ if (current->wait->when != TIMEOUT_INFINITE && !current->wait->in_kernel)
{
if (!(current->wait->user = add_timeout_user( abstime_to_timeout(current->wait->when),
thread_timeout, current->wait )))
@@ -1056,7 +1086,10 @@ static inline struct list *get_apc_queue( struct thread *thread, enum apc_type t
case APC_NONE:
case APC_USER:
case APC_TIMER:
+ case APC_REAL_USER:
return &thread->user_apc;
+ case APC_REAL_KERNEL:
+ return &thread->kernel_apc;
default:
return &thread->system_apc;
}
@@ -1111,13 +1144,14 @@ static int queue_apc( struct process *process, struct thread *thread, struct thr
if (thread->state == TERMINATED) return 0;
queue = get_apc_queue( thread, apc->call.type );
/* send signal for system APCs if needed */
- if (queue == &thread->system_apc && list_empty( queue ) && !is_in_apc_wait( thread ))
+ if ((queue == &thread->system_apc || queue == &thread->kernel_apc) && list_empty( queue ) && !is_in_apc_wait( thread ))
{
if (!send_thread_signal( thread, SIGUSR1 )) return 0;
}
/* cancel a possible previous APC with the same owner */
if (apc->owner) thread_cancel_apc( thread, apc->owner, apc->call.type );
}
+ apc->callee = (struct thread *) grab_object( thread );
grab_object( apc );
list_add_tail( queue, &apc->entry );
@@ -1699,6 +1733,7 @@ DECL_HANDLER(queue_apc)
struct thread *thread = NULL;
struct process *process = NULL;
struct thread_apc *apc;
+ obj_handle_t apc_handle = 0;
if (!(apc = create_apc( NULL, &req->call ))) return;
@@ -1706,6 +1741,8 @@ DECL_HANDLER(queue_apc)
{
case APC_NONE:
case APC_USER:
+ case APC_REAL_USER:
+ case APC_REAL_KERNEL:
thread = get_thread_from_handle( req->handle, THREAD_SET_CONTEXT );
break;
case APC_VIRTUAL_ALLOC:
@@ -1744,34 +1781,85 @@ DECL_HANDLER(queue_apc)
break;
}
- if (thread)
+ if (!thread && !process)
{
- if (!queue_apc( NULL, thread, apc )) set_error( STATUS_THREAD_IS_TERMINATING );
- release_object( thread );
+ release_object(apc);
+ return;
}
- else if (process)
+
+ apc_handle = alloc_handle( current->process, apc, SYNCHRONIZE, 0 );
+
+ if (apc_handle)
{
- reply->self = (process == current->process);
- if (!reply->self)
+ if (process)
+ reply->self = (process == current->process);
+ if (queue_apc( process, thread, apc ))
{
- obj_handle_t handle = alloc_handle( current->process, apc, SYNCHRONIZE, 0 );
- if (handle)
- {
- if (queue_apc( process, NULL, apc ))
- {
- apc->caller = (struct thread *)grab_object( current );
- reply->handle = handle;
- }
- else
- {
- close_handle( current->process, handle );
- set_error( STATUS_PROCESS_IS_TERMINATING );
- }
- }
+ apc->caller = (struct thread *)grab_object( current );
+ reply->handle = apc_handle;
+ }
+ else
+ {
+ close_handle( current->process, apc_handle );
+ set_error( thread ? STATUS_THREAD_IS_TERMINATING : STATUS_PROCESS_IS_TERMINATING );
}
- release_object( process );
+ release_object( thread ? (void*) thread : (void*) process );
}
+ /* Optimization: APC_USER and APC_NONE don't use the handle */
+ if (apc->call.type == APC_USER || apc->call.type == APC_NONE)
+ close_handle(current->process, apc_handle);
+
+ release_object( apc );
+}
+
+/* transform the APC object into the type for usermode */
+DECL_HANDLER(finalize_apc)
+{
+ struct thread_apc *apc;
+ struct list *queue;
+
+ if (!(apc = (struct thread_apc *)get_handle_obj(current->process, req->handle,
+ 0, &thread_apc_ops ))) return;
+
+ if (apc->executed != 1)
+ {
+ set_error(STATUS_INVALID_PARAMETER);
+ goto done;
+ }
+
+ if (apc->call.type != APC_REAL_USER && apc->call.type != APC_REAL_KERNEL)
+ {
+ set_error(STATUS_INVALID_PARAMETER);
+ goto done;
+ }
+
+ queue = get_apc_queue(apc->callee, apc->call.type);
+ if (list_head(queue) != &apc->entry)
+ {
+ set_error(STATUS_INVALID_PARAMETER);
+ goto done;
+ }
+
+ apc->executed = 0;
+
+ if (apc->call.type == APC_REAL_KERNEL)
+ {
+ /* dequeue */
+ list_remove(&apc->entry);
+ release_object(apc);
+
+ if (req->call.type != APC_NONE)
+ set_error(STATUS_INVALID_PARAMETER);
+ }
+ else
+ apc->call = req->call;
+
+ if (apc->callee->wait)
+ apc->callee->wait->in_kernel = 0;
+ wake_thread( apc->callee );
+
+ done:
release_object( apc );
}
diff --git a/server/thread.h b/server/thread.h
index 650bc44628d..feb1916a81a 100644
--- a/server/thread.h
+++ b/server/thread.h
@@ -59,6 +59,7 @@ struct thread
struct msg_queue *queue; /* message queue */
struct thread_wait *wait; /* current wait condition if sleeping */
struct list system_apc; /* queue of system async procedure calls */
+ struct list kernel_apc; /* queue of kernel async procedure calls */
struct list user_apc; /* queue of user async procedure calls */
struct inflight_fd inflight[MAX_INFLIGHT_FDS]; /* fds currently in flight */
unsigned int error; /* current error code */
--
2.28.0
More information about the wine-devel
mailing list