Every few months I heard people asking the same question:
Given a process ID (or handle), how can I get its main thread ID (or handle)?
Normally that would raise another question:
What is the definition of a main thread?
While the Windows operating system doesn’t have a concept called main thread, and threads donnot have parent-child relationship at all.
Let’s reuse the sample code from Pop Quiz – Debug Event Loop and Timeslice Quota:
- The WinMain function would return right after it creates two worker threads.
- The two worker threads run into an endless loop.
If we compiled the code using cl.exe test.cpp, the generated test.exe would return immediately after we run it. If we take a quick debug, the call stack would look like this:
test!ILT+10(WinMain) test!__tmainCRTStartup+0x154 kernel32!BaseThreadInitThunk+0xd ntdll!RtlUserThreadStart+0x1d
That’s because the compiler has made the decision that we need to use CRT initialization, although actually we are not using it either explicitly or implicitly. It is the CRT exit code which called ntdll!RtlExitUserProcess and terminated our worker threads, and this is by design.
Now let’s switch to the following command:
cl test.cpp /link /NODEFAULTLIB /ENTRY:WinMain /SUBSYSTEM:CONSOLE kernel32.lib
As you can see, test.exe would enter an endless loop.
Now let’s try to give some possible definitions of main thread:
- The thread in which the CRT startup and exit code runs. (what if we are not using CRT at all? what if we start without CRT, then load a DLL that triggered the CRT initialization?)
- The thread which pumps window message for the main window. (what if we are not a GUI application? what in turn is the definition of a main window, and can we have two main windows?)
- The thread which runs through the OEP (Original Entry Point, we mentioned that in Data Breakpoints). (what if OEP has been covered several times?)
- The thread in which DllMain function get called with DLL_PROCESS_ATTACH and lpReserved is not NULL.
- The oldest thread.
It looks like option 4 and 5 have the most clean definition. If we use option 4 then we should stay with the facts that a process may not have a main thread, and that’s why we would normally end up with option 5.
Which one do you prefer and what is your own definition? Which option do you think the Visual Studio Debugger would use?