Largely based on a patch by Nikolay Sivov for NtGetNextProcess().
Signed-off-by: Paul Gofman <pgofman(a)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 */
+@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 */
+@REPLY
+ obj_handle_t handle; /* next thread handle */
+@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