Thread naming
Multi-threading is becoming quite common these days. It’s a useful way to provide a responsive user interface while performing work at the same time. Our tools report data per thread where that is warranted (per-thread code coverage doesn’t seem to be a thing – no one has requested it in 17 years). In this article I’m going to discuss thread naming, OS support for thread naming, and additional support for thread naming that our tools automatically provide.
The Default Thread Display
Threads are represented by a thread handle and a thread id. Typically the thread id is what will be used to represent the thread in a user interface reporting thread related data. Thread ids are numeric. For example: 341. For trivial programs you can often infer which thread is which by looking at the data allocated on each thread. However that doesn’t scale very well to more complex applications. You can end up with thread displays like this:
Microsoft Thread Naming Exception
The WIN32 API does not provide any functions to allow you to name a thread. To handle this oversight, Microsoft use a convention that allows a program to communicate a thread name with it’s debugger. This is done via means of an exception that the program throws (and then catches to prevent it’s propagation terminating the application). The debugger also catches this exception and with the help of ReadProcessMemory() can retrieve the exception name from the program. Here’s how that works.
In your program
The exception code that identifies a thread naming exception is 0x406D1388. To pass the thread name to the debugger you need to create a struct of type THREADNAME_INFO (definition shown int the code sample), populate it with the appropriate data then raise an exception with the specified exception code. You’ll need to use SEH __try/__except to surround the RaiseException() call to prevent crashing the program.
typedef struct tagTHREADNAME_INFO { DWORD dwType; // must be 0x1000 LPCSTR szName; // pointer to name (in user addr space) buffer must be 8 chars + 1 terminator DWORD dwThreadID; // thread ID (-1 == caller thread) DWORD dwFlags; // reserved for future use, must be zero } THREADNAME_INFO; #define MS_VC_EXCEPTION 0x406D1388 void nameThread(const DWORD threadId, const char *name) { // You can name your threads by using the following code. // Thread Validator will intercept the exception and pass it along (so if you are also running // under a debugger the debugger will also see the exception and read the thread name // NOTE: this is for 'unmanaged' C++ ONLY! #define BUFFER_LEN 16 THREADNAME_INFO ThreadInfo; char szSafeThreadName[BUFFER_LEN]; // buffer can be any size, just make sure it is large enough! memset(szSafeThreadName, 0, sizeof(szSafeThreadName)); // ensure all characters are NULL before strncpy(szSafeThreadName, name, BUFFER_LEN - 1); // copying name //szSafeThreadName[BUFFER_LEN - 1] = '\0'; ThreadInfo.dwType = 0x1000; ThreadInfo.szName = szSafeThreadName; ThreadInfo.dwThreadID = threadId; ThreadInfo.dwFlags = 0; __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(ThreadInfo) / sizeof(DWORD_PTR), (DWORD_PTR *)&ThreadInfo); } __except(EXCEPTION_EXECUTE_HANDLER) { // do nothing, just catch the exception so that you don't terminate the application } }
In the debugger
The THREADNAME_INFO struct address is communicated to the debugger in event.u.Exception.ExceptionRecord.ExceptionInformation[0]. Cast this pointer to a THREADNAME_INFO *, check the number of parameters are correct (4 on x86, 3 on x64), check the dwType and dwFlags are correct, then use the pointer in the szName field. This is a pointer inside the other process, which means you can’t read it directly you need to use ReadProcessMemory(). Once the debugger continues and this event goes out of scope, the ability to read the thread name no longer exists. If you want to read the thread name you must read it immediately, storing it for future use.
if ((de->dwDebugEventCode == EXCEPTION_DEBUG_EVENT) && (de->u.Exception.ExceptionRecord.ExceptionCode == SVL_THREAD_NAMING_EXCEPTION)) { #ifdef _WIN64 if (de->u.Exception.ExceptionRecord.NumberParameters == 3) #else //#ifdef _WIN64 if (de->u.Exception.ExceptionRecord.NumberParameters == 4) #endif //#ifdef _WIN64 { THREADNAME_INFO *tni; tni = (THREADNAME_INFO *)&de->u.Exception.ExceptionRecord.ExceptionInformation[0]; if (tni->dwType == 4096 && tni->dwFlags == 0) { void *ptr = (void *)tni->szName; DWORD threadId = tni->dwThreadID; if (ptr != NULL) { char buffer[1000]; int bRet; SIZE_T dwRead = 0; memset(buffer, 0, 1000); bRet = ReadProcessMemory(hProcess, ptr, buffer, 1000 - 1, // remove one so there is always a terminating zero &dwRead); if (buffer[0] != '\0') setThreadName(threadId, buffer); } } } }
Our tools support intercepting this mechanism so that we can name your threads if you use this mechanism.
The problem with this mechanism is that it puts the onus for naming the threads on the creator of the application. If that involves 3rd party components that spawn their own threads, and OS threads then almost certainly not all threads will be named, which results in some threads having a name and other threads being represented by a thread id.
CreateThread() And Symbols
In an attempt to automatically name threads to provide useful naming for threads without having to rely on the author of a program using thread naming exceptions we started monitoring CreateThread() to get the thread start address, then resolve that address into a symbol name (assuming debugging symbols are available) and use that symbol name to represent the thread. After some tests (done with our own tools – which are heavily multithreaded – dog-fooding) we concluded this was a useful way to name threads.
However this doesn’t solve the problem as many threads are now named _threadstartex().
_beginthread(), _beginthreadex() And Symbols
The problem with the CreateThread() approach is that any threads created by calls to the C runtime functions _beginthread() or _beginthreadex() will result in the thread being called _threadstart() or _threadstartex(). This is because these functions pass their own thread function to CreateThread(). The way to solve this problem is to also monitor _beginthread() and _beginthreadex() functions to get the thread start address, then resolve that address into a symbol name.
SetThreadDescription
Starting with Windows 10, a name can be associated with a thread handle via the API SetThreadDescription(). The name associated with a thread handle can be retrieved using GetThreadDescription(). We use these functions to provide names for your threads if you have used these functions.
Un-named Threads
Some threads still end up without names – typically these are transient threads created by the OS to do a short amount of work. If the call to create the thread was internal to Kernel32.dll then CreateThread() will not be called by an IAT call and will not be monitored, resulting in the thread not getting named automatically. This isn’t ideal, but ultimately you don’t control these threads, which means you can’t affect the data reported by these threads, so it’s not that important.
Thread Naming Priority
We’ve made these changes to all our Validator tools and updated the user interfaces to represent threads by both thread id and thread name.
If a thread is named by a thread naming exception, SetThreadDescription(), or via a Software Verify API call that name will take precedence over any automatically named thread. This is useful in cases where the same function is used by many threads – you can if you wish give each thread a unique name to prevent multiple threads having the same name.
Here are some of the displays that have benefited from named threads.
Bug Validator
Memory Validator
Performance Validator
Thread Validator