[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