[PATCH] ntdll: Implement NtGetNextThread().

Paul Gofman pgofman at codeweavers.com
Tue Apr 20 10:29:06 CDT 2021


Largely based on a patch by Nikolay Sivov for NtGetNextProcess().

Signed-off-by: Paul Gofman <pgofman at codeweavers.com>
---
    Used by Forza Horizon 4.

 dlls/ntdll/ntdll.spec    |  1 +
 dlls/ntdll/tests/om.c    | 86 ++++++++++++++++++++++++++++++++++++++++
 dlls/ntdll/unix/thread.c | 26 ++++++++++++
 server/protocol.def      | 12 ++++++
 server/thread.c          | 52 ++++++++++++++++++++++++
 5 files changed, 177 insertions(+)

diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec
index a93fa08c406..cade83da4b1 100644
--- a/dlls/ntdll/ntdll.spec
+++ b/dlls/ntdll/ntdll.spec
@@ -223,6 +223,7 @@
 @ stdcall -norelay -syscall NtGetContextThread(long ptr)
 @ stdcall -syscall NtGetCurrentProcessorNumber()
 # @ stub NtGetDevicePowerState
+@ stdcall -syscall NtGetNextThread(ptr ptr long long long ptr)
 @ stdcall -syscall NtGetNlsSectionPtr(long long long ptr ptr)
 @ stub NtGetPlugPlayEvent
 @ stdcall NtGetTickCount()
diff --git a/dlls/ntdll/tests/om.c b/dlls/ntdll/tests/om.c
index e0988769c9c..eb1ebe8dd80 100644
--- a/dlls/ntdll/tests/om.c
+++ b/dlls/ntdll/tests/om.c
@@ -78,6 +78,7 @@ static void     (WINAPI *pRtlWakeAddressAll)( const void * );
 static void     (WINAPI *pRtlWakeAddressSingle)( const void * );
 static NTSTATUS (WINAPI *pNtOpenProcess)( HANDLE *, ACCESS_MASK, const OBJECT_ATTRIBUTES *, const CLIENT_ID * );
 static NTSTATUS (WINAPI *pNtCreateDebugObject)( HANDLE *, ACCESS_MASK, OBJECT_ATTRIBUTES *, ULONG );
+static NTSTATUS (WINAPI *pNtGetNextThread)(HANDLE process, HANDLE thread, ACCESS_MASK access, ULONG attributes, ULONG flags, HANDLE *handle);
 
 #define KEYEDEVENT_WAIT       0x0001
 #define KEYEDEVENT_WAKE       0x0002
@@ -2564,6 +2565,89 @@ static void test_object_types(void)
     }
 }
 
+static DWORD WINAPI test_get_next_thread_proc( void *arg )
+{
+    HANDLE event = (HANDLE)arg;
+
+    WaitForSingleObject(event, INFINITE);
+    return 0;
+}
+
+static void test_get_next_thread(void)
+{
+    HANDLE hprocess = GetCurrentProcess();
+    HANDLE handle, thread, event, prev;
+    NTSTATUS status;
+    DWORD thread_id;
+    BOOL found;
+
+    if (!pNtGetNextThread)
+    {
+        win_skip("NtGetNextThread is not available.\n");
+        return;
+    }
+
+    event = CreateEventA(NULL, FALSE, FALSE, NULL);
+
+    thread = CreateThread( NULL, 0, test_get_next_thread_proc, event, 0, &thread_id );
+
+    status = pNtGetNextThread(hprocess, NULL, THREAD_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, NULL);
+    ok(status == STATUS_ACCESS_VIOLATION, "Got unexected status %#x.\n", status);
+
+    found = FALSE;
+    prev = NULL;
+    while (!(status = pNtGetNextThread(hprocess, prev, THREAD_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, &handle)))
+    {
+        if (prev)
+        {
+            if (GetThreadId(handle) == thread_id)
+                found = TRUE;
+            pNtClose(prev);
+        }
+        else
+        {
+            ok(GetThreadId(handle) == GetCurrentThreadId(), "Got unexpected thread id %04x, current %04x.\n",
+                    GetThreadId(handle), GetCurrentThreadId());
+        }
+        prev = handle;
+    }
+    pNtClose(prev);
+    ok(status == STATUS_NO_MORE_ENTRIES, "Unexpected status %#x.\n", status);
+    ok(found, "Thread not found.\n");
+
+    status = pNtGetNextThread((void *)0xdeadbeef, 0, PROCESS_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, &handle);
+    ok(status == STATUS_INVALID_HANDLE, "Unexpected status %#x.\n", status);
+    status = pNtGetNextThread(hprocess, (void *)0xdeadbeef, PROCESS_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, &handle);
+    ok(status == STATUS_INVALID_HANDLE, "Unexpected status %#x.\n", status);
+
+    /* Reversed search only supported in recent enough Win10. */
+    status = pNtGetNextThread(hprocess, 0, PROCESS_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 1, &handle);
+    ok(!status || broken(status == STATUS_INVALID_PARAMETER), "Unexpected status %#x.\n", status);
+    if (!status)
+        pNtClose(handle);
+
+    status = pNtGetNextThread(hprocess, 0, PROCESS_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 2, &handle);
+    ok(status == STATUS_INVALID_PARAMETER, "Unexpected status %#x.\n", status);
+
+    SetEvent(event);
+    WaitForSingleObject(thread, INFINITE);
+
+    found = FALSE;
+    prev = NULL;
+    while (!(status = pNtGetNextThread(hprocess, prev, THREAD_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, &handle)))
+    {
+        if (prev)
+            pNtClose(prev);
+        if (GetThreadId(handle) == thread_id)
+            found = TRUE;
+        prev = handle;
+    }
+    pNtClose(prev);
+    ok(found, "Thread not found.\n");
+
+    CloseHandle(thread);
+}
+
 START_TEST(om)
 {
     HMODULE hntdll = GetModuleHandleA("ntdll.dll");
@@ -2615,6 +2699,7 @@ START_TEST(om)
     pRtlWakeAddressSingle   =  (void *)GetProcAddress(hntdll, "RtlWakeAddressSingle");
     pNtOpenProcess          =  (void *)GetProcAddress(hntdll, "NtOpenProcess");
     pNtCreateDebugObject    =  (void *)GetProcAddress(hntdll, "NtCreateDebugObject");
+    pNtGetNextThread        =  (void *)GetProcAddress(hntdll, "NtGetNextThread");
 
     test_case_sensitive();
     test_namespace_pipe();
@@ -2632,4 +2717,5 @@ START_TEST(om)
     test_wait_on_address();
     test_process();
     test_object_types();
+    test_get_next_thread();
 }
diff --git a/dlls/ntdll/unix/thread.c b/dlls/ntdll/unix/thread.c
index b1c64f6f7a8..62cee1d848b 100644
--- a/dlls/ntdll/unix/thread.c
+++ b/dlls/ntdll/unix/thread.c
@@ -1382,3 +1382,29 @@ ULONG WINAPI NtGetCurrentProcessorNumber(void)
     /* fallback to the first processor */
     return 0;
 }
+
+
+/******************************************************************************
+ *              NtGetNextThread  (NTDLL.@)
+ */
+NTSTATUS WINAPI NtGetNextThread(HANDLE hprocess, HANDLE hthread, ACCESS_MASK access, ULONG attributes,
+        ULONG flags, HANDLE *handle)
+{
+    NTSTATUS ret;
+
+    TRACE( "hprocess %p, hthread %p, access %#x, attributes %#x, flags %#x, handle %p.\n",
+            hprocess, hthread, access, attributes, flags, handle );
+
+    SERVER_START_REQ( get_next_thread )
+    {
+        req->process = wine_server_obj_handle( hprocess );
+        req->last = wine_server_obj_handle( hthread );
+        req->access = access;
+        req->attributes = attributes;
+        req->flags = flags;
+        ret = wine_server_call( req );
+        if (!ret) *handle = wine_server_ptr_handle( reply->handle );
+    }
+    SERVER_END_REQ;
+    return ret;
+}
diff --git a/server/protocol.def b/server/protocol.def
index 9ea6967acdd..a5ba8a4ca35 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -3699,3 +3699,15 @@ struct handle_info
 @REQ(resume_process)
     obj_handle_t handle;       /* process handle */
 @END
+
+
+/* Iterate thread list for process */
+ at REQ(get_next_thread)
+    obj_handle_t process;      /* process handle */
+    obj_handle_t last;         /* thread handle to start with */
+    unsigned int access;       /* desired access for returned handle */
+    unsigned int attributes;   /* returned handle attributes */
+    unsigned int flags;        /* controls iteration direction */
+ at REPLY
+    obj_handle_t handle;       /* next thread handle */
+ at END
diff --git a/server/thread.c b/server/thread.c
index fe9f9bdec37..bdfafeec25a 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -1950,3 +1950,55 @@ DECL_HANDLER(get_selector_entry)
         release_object( thread );
     }
 }
+
+/* Iterate thread list for process. Use global thread list to also
+ * return terminated but not yet destroyed threads. */
+DECL_HANDLER(get_next_thread)
+{
+    struct thread *thread, *next;
+    struct process *process;
+    struct list *ptr;
+
+    reply->handle = 0;
+
+    if ( req->flags > 1 )
+    {
+        set_error( STATUS_INVALID_PARAMETER );
+        return;
+    }
+
+    if (!(process = get_process_from_handle( req->process, PROCESS_QUERY_INFORMATION )))
+        return;
+
+    if (!req->last)
+    {
+        ptr = req->flags ? list_tail( &thread_list ) : list_head( &thread_list );
+    }
+    else if ((thread = get_thread_from_handle( req->last, 0 )))
+    {
+        ptr = req->flags ? list_prev( &thread_list, &thread->entry ) :
+                list_next( &thread_list, &thread->entry );
+        release_object( thread );
+    }
+    else
+    {
+        release_object( process );
+        return;
+    }
+
+    while (ptr)
+    {
+        next = LIST_ENTRY( ptr, struct thread, entry );
+        if (next->process == process
+                && (reply->handle = alloc_handle( current->process, next, req->access, req->attributes )))
+        {
+            release_object( process );
+            return;
+        }
+        ptr = req->flags ? list_prev( &thread_list, &next->entry )
+                : list_next( &thread_list, &next->entry );
+    }
+
+    set_error( STATUS_NO_MORE_ENTRIES );
+    release_object( process );
+}
-- 
2.30.2




More information about the wine-devel mailing list