mirror of
https://github.com/reactos/reactos.git
synced 2026-06-03 09:51:03 +08:00
[NTOS:PS][NTDLL_APITESTS] Implement ThreadNameInformation for NtQuery/SetInformationThread() (#8484)
The `ThreadNameInformation` (#38) class is the Windows 10.1607+ way of assigning a human-readable name (i.e. description) to a given thread object, that is visible to debuggers and diagnostic tools (e.g. WinDbg `!thread` command, Process Explorer ...), which is useful for debugging scenarios.[^1] Before this, the only way to assign a name to a thread for debugging purposes was to raise a specific exception, that had to be caught and interpreted by a supported debugger.[^2][^3] When the thread object is being deleted (`kill.c!PspDeleteThread()`), free the thread name if set (courtesy of Ahmed Arif, PR #8796). References: [^1]: https://learn.microsoft.com/en-us/visualstudio/debugger/tips-for-debugging-threads [^2]: https://learn.microsoft.com/en-us/archive/blogs/stevejs/naming-threads-in-win32-and-net [^3]: https://ofekshilon.com/2009/04/10/naming-threads/
This commit is contained in:
@@ -56,7 +56,7 @@ QuerySetProcessValidator(
|
||||
break;
|
||||
}
|
||||
|
||||
/* This one works different from the others */
|
||||
/* This one works differently from the others */
|
||||
case ProcessUserModeIOPL:
|
||||
{
|
||||
if (ExpectedStatus == STATUS_INFO_LENGTH_MISMATCH)
|
||||
@@ -159,7 +159,7 @@ QuerySetProcessValidator(
|
||||
break;
|
||||
}
|
||||
|
||||
/* This one works different from the others */
|
||||
/* This one works differently from the others */
|
||||
case ProcessUserModeIOPL:
|
||||
{
|
||||
if (ExpectedStatus == STATUS_INFO_LENGTH_MISMATCH)
|
||||
@@ -295,6 +295,21 @@ QuerySetThreadValidator(
|
||||
break;
|
||||
}
|
||||
|
||||
/* ThreadNameInformation is Windows 10+, but
|
||||
* ReactOS supports this class, so don't exclude it */
|
||||
case ThreadNameInformation:
|
||||
{
|
||||
#ifndef __REACTOS__
|
||||
if (GetNTVersion() < _WIN32_WINNT_WIN10)
|
||||
SpecialStatus = STATUS_INVALID_INFO_CLASS;
|
||||
#else
|
||||
/* This one works differently from the others */
|
||||
if (ExpectedStatus == STATUS_INFO_LENGTH_MISMATCH)
|
||||
ExpectedStatus = STATUS_BUFFER_TOO_SMALL;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
/* All of these classes only exist on Windows 7 and above */
|
||||
@@ -381,6 +396,17 @@ QuerySetThreadValidator(
|
||||
break;
|
||||
}
|
||||
|
||||
/* ThreadNameInformation is Windows 10+, but
|
||||
* ReactOS supports this class, so don't exclude it */
|
||||
case ThreadNameInformation:
|
||||
{
|
||||
#ifndef __REACTOS__
|
||||
if (GetNTVersion() < _WIN32_WINNT_WIN10)
|
||||
SpecialStatus = STATUS_INVALID_INFO_CLASS;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
/* All of these classes only exist on Windows 7 and above */
|
||||
|
||||
@@ -593,8 +593,15 @@ static const INFORMATION_CLASS_INFO PsThreadInfoClass[] =
|
||||
IQS_NONE,
|
||||
/* ThreadContainerId */
|
||||
IQS_NONE,
|
||||
|
||||
/* ThreadNameInformation */
|
||||
IQS_NONE,
|
||||
IQS_SAME
|
||||
(
|
||||
UNICODE_STRING,
|
||||
ULONG_PTR,
|
||||
ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE
|
||||
),
|
||||
|
||||
/* ThreadSelectedCpuSets */
|
||||
IQS_NONE,
|
||||
/* ThreadSystemThreadInformation */
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
#define TAG_PS_APC 'pasP' /* Psap - Ps APC */
|
||||
#define TAG_SHIM 'MIHS'
|
||||
#define TAG_QUOTA_BLOCK 'bQsP'
|
||||
#define TAG_THREAD_NAME 'mNhT'
|
||||
|
||||
/* Run-Time Library Tags */
|
||||
#define TAG_HDTB 'BTDH'
|
||||
|
||||
@@ -416,11 +416,19 @@ PspDeleteThread(IN PVOID ObjectBody)
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup impersionation information */
|
||||
/* Cleanup impersonation information */
|
||||
PspDeleteThreadSecurity(Thread);
|
||||
|
||||
/* Free the thread name if set */
|
||||
if (Thread->ThreadName)
|
||||
{
|
||||
ExFreePoolWithTag(Thread->ThreadName, TAG_THREAD_NAME);
|
||||
Thread->ThreadName = NULL;
|
||||
}
|
||||
|
||||
/* Make sure the thread was inserted, before continuing */
|
||||
if (!Process) return;
|
||||
if (!Process)
|
||||
return;
|
||||
|
||||
/* Check if the thread list is valid */
|
||||
if (Thread->ThreadListEntry.Flink)
|
||||
|
||||
@@ -2879,6 +2879,92 @@ NtSetInformationThread(
|
||||
break;
|
||||
}
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1) || defined(__REACTOS__)
|
||||
case ThreadNameInformation:
|
||||
{
|
||||
UNICODE_STRING CapturedThreadName;
|
||||
PUNICODE_STRING NewThreadName;
|
||||
|
||||
/* Check buffer length */
|
||||
if (ThreadInformationLength != sizeof(UNICODE_STRING))
|
||||
{
|
||||
Status = STATUS_INFO_LENGTH_MISMATCH;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Reference the thread.
|
||||
* NOTE: Win10+ uses THREAD_SET_LIMITED_INFORMATION instead;
|
||||
* however some tools misuse thread names to perform suspicious
|
||||
* operations; therefore we try to mess with these by requiring
|
||||
* a bit more of access rights. */
|
||||
Status = ObReferenceObjectByHandle(ThreadHandle,
|
||||
THREAD_SET_INFORMATION,
|
||||
PsThreadType,
|
||||
PreviousMode,
|
||||
(PVOID*)&Thread,
|
||||
NULL);
|
||||
if (!NT_SUCCESS(Status))
|
||||
break;
|
||||
|
||||
/* Probe and capture the thread name */
|
||||
Status = ProbeAndCaptureUnicodeString(&CapturedThreadName,
|
||||
PreviousMode,
|
||||
(PUNICODE_STRING)ThreadInformation);
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
ObDereferenceObject(Thread);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Allocate a new buffer only if the thread name isn't empty
|
||||
* (REMARK: We only consider Length instead of MaximumLength).
|
||||
* If empty, just reset the thread name pointer to NULL instead
|
||||
* of allocating an empty UNICODE_STRING. */
|
||||
NewThreadName = NULL;
|
||||
if (CapturedThreadName.Length > 0)
|
||||
{
|
||||
ULONG Length = sizeof(UNICODE_STRING) + CapturedThreadName.Length;
|
||||
NewThreadName = ExAllocatePoolWithTag(NonPagedPool, // FIXME: NonPagedPoolNx
|
||||
Length, TAG_THREAD_NAME);
|
||||
if (!NewThreadName)
|
||||
{
|
||||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Copy the new thread name */
|
||||
NewThreadName->Length =
|
||||
NewThreadName->MaximumLength = CapturedThreadName.Length;
|
||||
NewThreadName->Buffer = (PWCH)(NewThreadName + 1);
|
||||
RtlCopyMemory(NewThreadName->Buffer,
|
||||
CapturedThreadName.Buffer,
|
||||
CapturedThreadName.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the captured string */
|
||||
ReleaseCapturedUnicodeString(&CapturedThreadName, PreviousMode);
|
||||
|
||||
/* Replace the original thread name with the new one */
|
||||
if (NT_SUCCESS(Status))
|
||||
{
|
||||
PUNICODE_STRING OldThreadName;
|
||||
PspLockThreadSecurityExclusive(Thread);
|
||||
OldThreadName = Thread->ThreadName;
|
||||
Thread->ThreadName = NewThreadName;
|
||||
PspUnlockThreadSecurityExclusive(Thread);
|
||||
|
||||
/* Free the old thread name */
|
||||
if (OldThreadName)
|
||||
ExFreePoolWithTag(OldThreadName, TAG_THREAD_NAME);
|
||||
}
|
||||
|
||||
/* Dereference the thread */
|
||||
ObDereferenceObject(Thread);
|
||||
break;
|
||||
}
|
||||
#endif /* (NTDDI_VERSION >= NTDDI_WIN10_RS1) || defined(__REACTOS__) */
|
||||
|
||||
/* Anything else */
|
||||
default:
|
||||
/* Not yet implemented */
|
||||
@@ -3388,6 +3474,73 @@ NtQueryInformationThread(
|
||||
break;
|
||||
}
|
||||
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1) || defined(__REACTOS__)
|
||||
case ThreadNameInformation:
|
||||
{
|
||||
PUNICODE_STRING ThreadName;
|
||||
|
||||
/* Reference the thread */
|
||||
Status = ObReferenceObjectByHandle(ThreadHandle,
|
||||
// FIXME: Use THREAD_QUERY_LIMITED_INFORMATION when implemented
|
||||
THREAD_QUERY_INFORMATION,
|
||||
PsThreadType,
|
||||
PreviousMode,
|
||||
(PVOID*)&Thread,
|
||||
NULL);
|
||||
if (!NT_SUCCESS(Status))
|
||||
break;
|
||||
|
||||
PspLockThreadSecurityShared(Thread);
|
||||
|
||||
ThreadName = Thread->ThreadName;
|
||||
|
||||
/* Set the return length (REMARK: We only
|
||||
* consider Length instead of MaximumLength) */
|
||||
Length = sizeof(UNICODE_STRING);
|
||||
Length += (ThreadName ? ThreadName->Length : 0);
|
||||
if (ThreadInformationLength < Length)
|
||||
{
|
||||
PspUnlockThreadSecurityShared(Thread);
|
||||
ObDereferenceObject(Thread);
|
||||
Status = STATUS_BUFFER_TOO_SMALL;
|
||||
/* As on Windows, and *not* STATUS_INFO_LENGTH_MISMATCH */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Protect writes with SEH */
|
||||
_SEH2_TRY
|
||||
{
|
||||
PTHREAD_NAME_INFORMATION NameInfo =
|
||||
(PTHREAD_NAME_INFORMATION)ThreadInformation;
|
||||
if (ThreadName && (ThreadName->Length > 0))
|
||||
{
|
||||
NameInfo->ThreadName.Length =
|
||||
NameInfo->ThreadName.MaximumLength = ThreadName->Length;
|
||||
NameInfo->ThreadName.Buffer = (PWCH)(&NameInfo->ThreadName + 1);
|
||||
RtlCopyMemory(NameInfo->ThreadName.Buffer,
|
||||
ThreadName->Buffer,
|
||||
ThreadName->Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlInitEmptyUnicodeString(&NameInfo->ThreadName, NULL, 0);
|
||||
}
|
||||
}
|
||||
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
/* Get exception code */
|
||||
Status = _SEH2_GetExceptionCode();
|
||||
}
|
||||
_SEH2_END;
|
||||
|
||||
PspUnlockThreadSecurityShared(Thread);
|
||||
|
||||
/* Dereference the thread */
|
||||
ObDereferenceObject(Thread);
|
||||
break;
|
||||
}
|
||||
#endif /* (NTDDI_VERSION >= NTDDI_WIN10_RS1) || defined(__REACTOS__) */
|
||||
|
||||
/* Anything else */
|
||||
default:
|
||||
/* Not yet implemented */
|
||||
|
||||
@@ -1340,6 +1340,11 @@ typedef struct _ETHREAD
|
||||
LIST_ENTRY AlpcWaitListEntry;
|
||||
KSEMAPHORE AlpcWaitSemaphore;
|
||||
ULONG CacheManagerCount;
|
||||
#endif
|
||||
// TODO: Missing Vista+ members
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1) || defined(__REACTOS__)
|
||||
PUNICODE_STRING ThreadName;
|
||||
// TODO: Missing Win10+ members
|
||||
#endif
|
||||
} ETHREAD;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user