mirror of
https://github.com/reactos/reactos.git
synced 2026-05-30 14:25:03 +08:00
[NTDLL_APITEST] Add a test for Nt[Query|Set]InformationThread(ThreadNameInformation) (#8484)
Thanks to 'artDev', 'HP-12C', and 'julenuri' Discord contributors for their help!
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
/*
|
||||
* PROJECT: ReactOS API tests
|
||||
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
||||
* PURPOSE: Tests for the NtQueryInformationThread API
|
||||
* COPYRIGHT: Copyright 2020 George Bișoc <george.bisoc@reactos.org>
|
||||
* PROJECT: ReactOS API tests
|
||||
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
||||
* PURPOSE: Tests for the NtQueryInformationThread API
|
||||
* COPYRIGHT: Copyright 2020 George Bișoc <george.bisoc@reactos.org>
|
||||
* Copyright 2025-2026 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
|
||||
*/
|
||||
|
||||
#include "precomp.h"
|
||||
#include <internal/ps_i.h>
|
||||
|
||||
#define ok_wchar(x, y) ok_hex((unsigned)(unsigned short)(x), (unsigned)(unsigned short)(y))
|
||||
|
||||
#define ok_nwstr(str1, str2, count) \
|
||||
ok(wcsncmp((PWCHAR)(str1), (PWCHAR)(str2), (count)) == 0, \
|
||||
"Wrong string. Expected '%.*S', got '%.*S'\n", (count), (str1), (count), (str2))
|
||||
|
||||
static
|
||||
void
|
||||
Test_ThreadBasicInformationClass(void)
|
||||
@@ -101,6 +108,14 @@ Test_ThreadBasicInformationClass(void)
|
||||
HeapFree(GetProcessHeap(), 0, ThreadInfoBasic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Dummy thread used by the Test_ThreadHideFromDebuggerClass()
|
||||
* and the Test_ThreadNameInformation() API tests.
|
||||
*
|
||||
* @param[in] Parameter
|
||||
* Handle to an event that tells the thread to terminate itself when signaled.
|
||||
**/
|
||||
static ULONG
|
||||
NTAPI
|
||||
DummyThread(
|
||||
@@ -295,9 +310,316 @@ Test_ThreadQueryAlignmentProbe(void)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief An NT-equivalent of kernel32!GetThreadDescription().
|
||||
**/
|
||||
static NTSTATUS
|
||||
ntGetThreadName(
|
||||
_In_ HANDLE hThread,
|
||||
_Outptr_ PTHREAD_NAME_INFORMATION* ppThreadNameInfo,
|
||||
_Out_ PULONG pLength,
|
||||
_In_ UCHAR Fill)
|
||||
{
|
||||
PTHREAD_NAME_INFORMATION NameInfo = NULL;
|
||||
PVOID ptr;
|
||||
NTSTATUS Status;
|
||||
ULONG Length;
|
||||
|
||||
*ppThreadNameInfo = NULL;
|
||||
*pLength = 0;
|
||||
|
||||
/* Since there may be concurrent SetThreadDescription() invocations being
|
||||
* executed, we need to loop over the buffer allocation size until we can
|
||||
* successfully capture the thread name. */
|
||||
Length = 0;
|
||||
Status = NtQueryInformationThread(hThread, ThreadNameInformation, NULL, 0, &Length);
|
||||
if (Status != STATUS_BUFFER_TOO_SMALL)
|
||||
return Status;
|
||||
|
||||
/* Loop again only if we get buffer-too-small types of errors;
|
||||
* otherwise, break the loop: any error will be handled below. */
|
||||
while (Status == STATUS_INFO_LENGTH_MISMATCH ||
|
||||
Status == STATUS_BUFFER_TOO_SMALL)
|
||||
{
|
||||
/* (Re-)allocate the information buffer */
|
||||
ptr = (NameInfo ? RtlReAllocateHeap(RtlGetProcessHeap(), 0, NameInfo, Length)
|
||||
: RtlAllocateHeap(RtlGetProcessHeap(), 0, Length));
|
||||
if (!ptr)
|
||||
{
|
||||
Status = STATUS_NO_MEMORY;
|
||||
break;
|
||||
}
|
||||
RtlFillMemory(ptr, Length, Fill);
|
||||
NameInfo = ptr;
|
||||
Status = NtQueryInformationThread(hThread, ThreadNameInformation,
|
||||
NameInfo, Length, &Length);
|
||||
// Status should be STATUS_SUCCESS, unless concurrent SetThreadDescription() happens.
|
||||
}
|
||||
if (NT_SUCCESS(Status))
|
||||
{
|
||||
*ppThreadNameInfo = NameInfo;
|
||||
*pLength = Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlFreeHeap(RtlGetProcessHeap(), 0, NameInfo);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
static void
|
||||
DoThreadNameTest(
|
||||
_In_ UINT i,
|
||||
_In_ HANDLE hThread,
|
||||
_In_opt_ PCWSTR TestName)
|
||||
{
|
||||
BOOL bEmptyName = ((TestName == (PCWSTR)(ULONG_PTR)-1) || !TestName || !*TestName);
|
||||
ULONG ExpectedNameLength = (ULONG)(bEmptyName ? 0 : wcslen(TestName) * sizeof(WCHAR));
|
||||
ULONG ExpectedLength = sizeof(UNICODE_STRING) + ExpectedNameLength;
|
||||
|
||||
NTSTATUS Status;
|
||||
ULONG Length;
|
||||
PTHREAD_NAME_INFORMATION pNameInfo;
|
||||
/* IMPORTANT REMARK: The fixed Buffer is made large enough
|
||||
* to hold all the TestName's being tested in this file. */
|
||||
struct { THREAD_NAME_INFORMATION Info; WCHAR Buffer[MAX_PATH]; } NameInfo;
|
||||
|
||||
ASSERT(sizeof(NameInfo.Buffer) >= ExpectedNameLength);
|
||||
|
||||
winetest_push_context("Test %lu", i);
|
||||
|
||||
/* Set a new thread name only if it's not '-1'; otherwise we
|
||||
* just query the existing name without setting it before. */
|
||||
if (TestName != (PCWSTR)(ULONG_PTR)-1)
|
||||
{
|
||||
/* Set a non-empty thread name, to reset it to something different
|
||||
* from the previous case and distinguish with the next one. */
|
||||
RtlFillMemory(&NameInfo, sizeof(NameInfo), 0xEF);
|
||||
NameInfo.Info.ThreadName.Length = NameInfo.Info.ThreadName.MaximumLength = sizeof(NameInfo.Buffer);
|
||||
NameInfo.Info.ThreadName.Buffer = NameInfo.Buffer;
|
||||
Status = NtSetInformationThread(hThread, ThreadNameInformation,
|
||||
&NameInfo.Info, sizeof(NameInfo.Info));
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
|
||||
/* Now set the thread name to be tested */
|
||||
RtlFillMemory(&NameInfo, sizeof(NameInfo), 0xAB);
|
||||
RtlInitUnicodeString(&NameInfo.Info.ThreadName, TestName);
|
||||
Status = NtSetInformationThread(hThread, ThreadNameInformation,
|
||||
&NameInfo.Info, sizeof(NameInfo.Info));
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
/* Verify that ThreadNameInformation call with NULL buffer returns the expected length */
|
||||
Length = 0;
|
||||
Status = NtQueryInformationThread(hThread, ThreadNameInformation, NULL, 0, &Length);
|
||||
ok_ntstatus(Status, STATUS_BUFFER_TOO_SMALL);
|
||||
ok_long(Length, ExpectedLength);
|
||||
|
||||
pNameInfo = &NameInfo.Info;
|
||||
|
||||
/* Test ThreadNameInformation call with minimally-sized buffer */
|
||||
RtlFillMemory(&NameInfo, sizeof(NameInfo), 0xAB);
|
||||
Length = 0;
|
||||
Status = NtQueryInformationThread(hThread, ThreadNameInformation,
|
||||
pNameInfo, sizeof(*pNameInfo), &Length);
|
||||
if (bEmptyName)
|
||||
{
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
ok_long(Length, ExpectedLength); // == sizeof(UNICODE_STRING);
|
||||
ok_long((ULONG)pNameInfo->ThreadName.Length, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ok_ntstatus(Status, STATUS_BUFFER_TOO_SMALL);
|
||||
ok_long(Length, ExpectedLength); // == sizeof(UNICODE_STRING) + ExpectedNameLength;
|
||||
ok_long((ULONG)pNameInfo->ThreadName.Length, 0xABAB);
|
||||
}
|
||||
ok_long((ULONG)pNameInfo->ThreadName.MaximumLength, (ULONG)pNameInfo->ThreadName.Length);
|
||||
if (bEmptyName)
|
||||
{
|
||||
ok(pNameInfo->ThreadName.Buffer == NULL || /* ReactOS will set the Buffer to NULL */
|
||||
/* All Win10+ versions erroneously point the Buffer past the end of the data */
|
||||
broken(pNameInfo->ThreadName.Buffer == (PVOID)(&pNameInfo->ThreadName + 1)),
|
||||
"Expected NULL pointer, got 0x%p\n", pNameInfo->ThreadName.Buffer);
|
||||
/* NOTE: pNameInfo->ThreadName.Buffer[0] is invalid */
|
||||
}
|
||||
else
|
||||
{
|
||||
ok_ptr(pNameInfo->ThreadName.Buffer, (PVOID)(ULONG_PTR)0xABABABABABABABABULL);
|
||||
}
|
||||
|
||||
RtlFillMemory(&NameInfo, sizeof(NameInfo), 0xAB);
|
||||
Length = 0;
|
||||
if (bEmptyName)
|
||||
{
|
||||
/* Retest the same with a slightly-larger buffer */
|
||||
Status = NtQueryInformationThread(hThread, ThreadNameInformation,
|
||||
&NameInfo,
|
||||
sizeof(UNICODE_STRING) + sizeof(UNICODE_NULL),
|
||||
&Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Test direct ThreadNameInformation call with correctly-sized buffer */
|
||||
Status = NtQueryInformationThread(hThread, ThreadNameInformation,
|
||||
&NameInfo,
|
||||
ExpectedLength,
|
||||
&Length);
|
||||
}
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
ok_long(Length, ExpectedLength);
|
||||
ok_long((ULONG)pNameInfo->ThreadName.Length, ExpectedNameLength);
|
||||
ok_long((ULONG)pNameInfo->ThreadName.MaximumLength, (ULONG)pNameInfo->ThreadName.Length);
|
||||
if (bEmptyName)
|
||||
{
|
||||
ok(pNameInfo->ThreadName.Buffer == NULL || /* ReactOS will set the Buffer to NULL */
|
||||
/* All Win10+ versions erroneously point the Buffer past the end of the data */
|
||||
broken(pNameInfo->ThreadName.Buffer == (PVOID)(&pNameInfo->ThreadName + 1)),
|
||||
"Expected NULL pointer, got 0x%p\n", pNameInfo->ThreadName.Buffer);
|
||||
|
||||
/* NOTE: pNameInfo->ThreadName.Buffer[0] should be invalid, but let's
|
||||
* check past it to see what data there is (in the "broken" Windows case). */
|
||||
if (pNameInfo->ThreadName.Buffer == (PVOID)(&pNameInfo->ThreadName + 1))
|
||||
ok_wchar(pNameInfo->ThreadName.Buffer[0], 0xABAB);
|
||||
}
|
||||
else
|
||||
{
|
||||
ok_ptr(pNameInfo->ThreadName.Buffer, (PVOID)(&pNameInfo->ThreadName + 1));
|
||||
if (pNameInfo->ThreadName.Buffer == (PVOID)(&pNameInfo->ThreadName + 1))
|
||||
ok_nwstr(pNameInfo->ThreadName.Buffer, TestName, ExpectedNameLength / sizeof(WCHAR));
|
||||
else
|
||||
skip("pNameInfo->ThreadName.Buffer is invalid (0x%p)\n", pNameInfo->ThreadName.Buffer);
|
||||
}
|
||||
|
||||
/* Test ThreadNameInformation with dynamically allocated buffer */
|
||||
pNameInfo = NULL;
|
||||
Length = 0;
|
||||
Status = ntGetThreadName(hThread, &pNameInfo, &Length, 0xCD);
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
ok_long(Length, ExpectedLength);
|
||||
ok(pNameInfo != NULL, "Expected not NULL, got NULL\n");
|
||||
if (pNameInfo)
|
||||
{
|
||||
ok_long((ULONG)pNameInfo->ThreadName.Length, ExpectedNameLength);
|
||||
ok_long((ULONG)pNameInfo->ThreadName.MaximumLength, (ULONG)pNameInfo->ThreadName.Length);
|
||||
if (bEmptyName)
|
||||
{
|
||||
ok(pNameInfo->ThreadName.Buffer == NULL || /* ReactOS will set the Buffer to NULL */
|
||||
/* All Win10+ versions erroneously point the Buffer past the end of the data */
|
||||
broken(pNameInfo->ThreadName.Buffer == (PVOID)(&pNameInfo->ThreadName + 1)),
|
||||
"Expected NULL pointer, got 0x%p\n", pNameInfo->ThreadName.Buffer);
|
||||
/* NOTE: pNameInfo->ThreadName.Buffer[0] is invalid */
|
||||
}
|
||||
else
|
||||
{
|
||||
ok_ptr(pNameInfo->ThreadName.Buffer, (PVOID)(&pNameInfo->ThreadName + 1));
|
||||
if (pNameInfo->ThreadName.Buffer == (PVOID)(&pNameInfo->ThreadName + 1))
|
||||
ok_nwstr(pNameInfo->ThreadName.Buffer, TestName, ExpectedNameLength / sizeof(WCHAR));
|
||||
else
|
||||
skip("pNameInfo->ThreadName.Buffer is invalid (0x%p)\n", pNameInfo->ThreadName.Buffer);
|
||||
}
|
||||
RtlFreeHeap(RtlGetProcessHeap(), 0, pNameInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
skip("pNameInfo is NULL\n");
|
||||
}
|
||||
|
||||
winetest_pop_context();
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
Test_ThreadNameInformation(void)
|
||||
{
|
||||
ULONG NTDDIVersion;
|
||||
NTSTATUS Status;
|
||||
HANDLE hWaitEvent = NULL, hThread = NULL;
|
||||
|
||||
OSVERSIONINFOEXW osVerInfo;
|
||||
osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
|
||||
GetVersionExW((LPOSVERSIONINFOW)&osVerInfo);
|
||||
trace("osVerInfo: %lu.%lu.%lu ('%S')\n",
|
||||
osVerInfo.dwMajorVersion,
|
||||
osVerInfo.dwMinorVersion,
|
||||
osVerInfo.dwBuildNumber,
|
||||
osVerInfo.szCSDVersion);
|
||||
|
||||
NTDDIVersion = GetNTDDIVersion();
|
||||
trace("NTDDIVersion: 0x%08lx\n", NTDDIVersion);
|
||||
if (NTDDIVersion < NTDDI_WIN10_RS1)
|
||||
{
|
||||
trace("Running %s on NT %hu.%hu(.%hu), it may not work!\n",
|
||||
__FUNCTION__,
|
||||
(NTDDIVersion & 0xFF000000) >> 24,
|
||||
(NTDDIVersion & 0x00FF0000) >> 16,
|
||||
(NTDDIVersion & 0x000000FF));
|
||||
}
|
||||
|
||||
Status = NtCreateEvent(&hWaitEvent,
|
||||
EVENT_ALL_ACCESS,
|
||||
NULL,
|
||||
NotificationEvent,
|
||||
FALSE);
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
ok(hWaitEvent != NULL, "Expected not NULL, got NULL\n");
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
skip("Could not create the stop event! (Status 0x%08lx)\n", Status);
|
||||
goto Quit;
|
||||
}
|
||||
|
||||
/* Create the dummy thread and wait for it to start up */
|
||||
Status = RtlCreateUserThread(NtCurrentProcess(),
|
||||
NULL,
|
||||
FALSE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
DummyThread,
|
||||
(PVOID)hWaitEvent,
|
||||
&hThread,
|
||||
NULL);
|
||||
ok_ntstatus(Status, STATUS_SUCCESS);
|
||||
ok(hThread != NULL, "Expected not NULL, got NULL\n");
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
skip("Failed to create the dummy thread (Status 0x%08lx)\n", Status);
|
||||
goto Quit;
|
||||
}
|
||||
|
||||
/* EXPECTATION: Initial thread name is empty. */
|
||||
DoThreadNameTest(1, hThread, (PCWSTR)(ULONG_PTR)-1);
|
||||
|
||||
/* Set a non-empty thread name.
|
||||
* EXPECTATION: Thread name is what we have set. */
|
||||
DoThreadNameTest(2, hThread, L"Hello World!");
|
||||
|
||||
/* Set an empty-string thread name.
|
||||
* EXPECTATION: Thread name is empty. */
|
||||
DoThreadNameTest(3, hThread, L"");
|
||||
|
||||
/* Set a NULL-pointer empty-string thread name.
|
||||
* EXPECTATION: Thread name is empty. */
|
||||
DoThreadNameTest(4, hThread, NULL);
|
||||
|
||||
/* Send the kill signal and wait for the thread to terminate */
|
||||
NtSetEvent(hWaitEvent, NULL);
|
||||
NtWaitForSingleObject(hThread, FALSE, NULL);
|
||||
/* Close the thread */
|
||||
NtTerminateThread(hThread, STATUS_SUCCESS);
|
||||
NtClose(hThread);
|
||||
Quit:
|
||||
/* Close the event */
|
||||
if (hWaitEvent)
|
||||
NtClose(hWaitEvent);
|
||||
}
|
||||
|
||||
START_TEST(NtQueryInformationThread)
|
||||
{
|
||||
Test_ThreadBasicInformationClass();
|
||||
Test_ThreadHideFromDebuggerClass();
|
||||
Test_ThreadQueryAlignmentProbe();
|
||||
Test_ThreadNameInformation();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user