Largely based on a patch by Nikolay Sivov for NtGetNextProcess().
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- v2: - stop searching for thread even if handle allocation failed; - do not zero reply handle in server; - set output handle to NULL in case of error (also add tests supporting that); - move return handle assignment outside of SERVER_START_REQ() scope.
dlls/ntdll/ntdll.spec | 1 + dlls/ntdll/tests/om.c | 93 ++++++++++++++++++++++++++++++++++++++++ dlls/ntdll/unix/thread.c | 29 +++++++++++++ server/protocol.def | 12 ++++++ server/thread.c | 50 +++++++++++++++++++++ 5 files changed, 185 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..7d9ca47be12 100644 --- a/dlls/ntdll/tests/om.c +++ b/dlls/ntdll/tests/om.c @@ -78,6 +78,8 @@ 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 +2566,95 @@ 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; + handle = (HANDLE)0xdeadbeef; + } + pNtClose(prev); + ok(!handle, "Got unexpected handle %p.\n", handle); + ok(status == STATUS_NO_MORE_ENTRIES, "Unexpected status %#x.\n", status); + ok(found, "Thread not found.\n"); + + handle = (HANDLE)0xdeadbeef; + status = pNtGetNextThread((void *)0xdeadbeef, 0, PROCESS_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, &handle); + ok(status == STATUS_INVALID_HANDLE, "Unexpected status %#x.\n", status); + ok(!handle, "Got unexpected handle %p.\n", handle); + handle = (HANDLE)0xdeadbeef; + status = pNtGetNextThread(hprocess, (void *)0xdeadbeef, PROCESS_QUERY_LIMITED_INFORMATION, OBJ_INHERIT, 0, &handle); + ok(status == STATUS_INVALID_HANDLE, "Unexpected status %#x.\n", status); + ok(!handle, "Got unexpected handle %p.\n", handle); + + /* Reversed search is only supported on 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 +2706,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 +2724,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 274fae24873..36f90372611 100644 --- a/dlls/ntdll/unix/thread.c +++ b/dlls/ntdll/unix/thread.c @@ -1382,3 +1382,32 @@ 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) +{ + HANDLE ret_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 ); + ret_handle = ret ? NULL : wine_server_ptr_handle( reply->handle ); + } + SERVER_END_REQ; + + *handle = ret_handle; + return ret; +} diff --git a/server/protocol.def b/server/protocol.def index d061fca7073..9361933d47c 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3687,3 +3687,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 48f793b6a74..fc8362e2ca0 100644 --- a/server/thread.c +++ b/server/thread.c @@ -1925,3 +1925,53 @@ 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; + + 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 ); +}