14

I have created an additional thread in some small testing app and want to suspend the main thread from this additional thread. The additional thread is created via CreateRemoteThread from an external process.

Since SuspendThread needs a HANDLE to the thread which should be suspended, I want to know how to get this HANDLE from code running in my additional thread.

Etan
  • 17,014
  • 17
  • 89
  • 148
  • It's a 32-bit process under windows 7. I'm using Visual Studio 2008 and therefore Visual C++. – Etan Dec 28 '09 at 14:09
  • Do you want to suspend only the "main" thread or all ? What exactly are you trying to achieve ? It might have another way to do it... – cedrou Dec 28 '09 at 14:14
  • The main thread would be sufficient. However, a solution which suspends all threads would also work in my case. I want to call some non-threadsafe function from my additional thread, which is normally called by the processes main thread. – Etan Dec 28 '09 at 14:17
  • 1
    Suspending a thread doesn't make your other thread's actions thread-safe. For all you know, the main thread could be performing that non-thread-safe action at the very time you call SuspendThread. – Rob Kennedy Jul 14 '10 at 14:01

5 Answers5

18

I don't think there is anything that differentiates the main thread from other threads once the process has started. However, you can enumerate all threads in the process, and use GetThreadTimes to find the thread with the earliest creation time. Call OpenThread to get a HANDLE from a thread ID.

interjay
  • 107,303
  • 21
  • 270
  • 254
9

Get the thread id with this function:

/* CAUTION: ONLY x86 TESTED
 * get the thread id of the main thread of a target process
 *
 * params:
 *     DWORD dwPid  process id of the target process
 *
 * return:
 *     Success      thread id
 *     Error        NULL
 */
DWORD GetMainThreadId(DWORD dwPid)
{
    LPVOID lpTid;

    _asm
    {
        mov eax, fs:[18h]
        add eax, 36
        mov [lpTid], eax
    }

    HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, dwPid);
    if(hProcess == NULL)
        return NULL;

    DWORD dwTid;
    if(ReadProcessMemory(hProcess, lpTid, &dwTid, sizeof(dwTid), NULL) == FALSE)
    {
        CloseHandle(hProcess);
        return NULL;
    }

    CloseHandle(hProcess);

    return dwTid;
}

Simple open the thread to get the handle:

/*
 * get a handle to the main thread of a target process
 * if successfull, the returned handle must be closed with CloseHandle()
 *
 * params:
 *     DWORD dwPid              process id of the target process
 *     DWORD dwDesiredAccess    desired access rights to the thread
 *
 * return:
 *     Success      thread handle with desired access rights
 *     Error        NULL
 */
HANDLE GetMainThreadHandle(DWORD dwPid, DWORD dwDesiredAccess)
{
    DWORD dwTid = GetMainThreadId(dwPid);
    if(dwTid == FALSE)
        return NULL;

    return OpenThread(dwDesiredAccess, FALSE, dwTid);
}
mtx
  • 99
  • 1
  • 2
  • 4
    I don't believe this will work perfectly on various environments. Specifically, you are assuming Tib addresses are all equal in each processes on all machines and even the caller of GetMainThreadId() is the main thread, right? You should explain how it works as it has too many suspicious things to ask. – Laie Jun 26 '15 at 01:31
  • This is awesome, but I'm trying to make a function compatible with 32 and 64 bit – Ari Seyhun Sep 19 '17 at 06:29
8
DWORD GetMainThreadId () {
    const std::tr1::shared_ptr<void> hThreadSnapshot(
        CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0), CloseHandle);
    if (hThreadSnapshot.get() == INVALID_HANDLE_VALUE) {
        throw std::runtime_error("GetMainThreadId failed");
    }
    THREADENTRY32 tEntry;
    tEntry.dwSize = sizeof(THREADENTRY32);
    DWORD result = 0;
    DWORD currentPID = GetCurrentProcessId();
    for (BOOL success = Thread32First(hThreadSnapshot.get(), &tEntry);
        !result && success && GetLastError() != ERROR_NO_MORE_FILES;
        success = Thread32Next(hThreadSnapshot.get(), &tEntry))
    {
        if (tEntry.th32OwnerProcessID == currentPID) {
            result = tEntry.th32ThreadID;
        }
    }
    return result;
}
Etan
  • 17,014
  • 17
  • 89
  • 148
  • Is it guaranteed that a process's "main" thread will always be the first one in a snapshot? Also, the question wasn't about how to identify the main thread; it was about how to get a handle, which this answer ignores. – Rob Kennedy Jul 14 '10 at 14:00
  • 7
    There's not even a guarantee that a process's "main" thread still exists! The process's main thread may have done an `ExitThread`. – Raymond Chen Nov 09 '11 at 00:20
  • You can sanity check this by getting the main thread's start address from the module's PE header. If the start address matches the thread's start address (obtained by calling NtQueryInformationThread), you've got your main thread. Empirically this always gives the right thread, or none as @RaymondChen mentions, but at least no false positives – SonarJetLens Sep 24 '18 at 14:51
  • Thanks for this solution – Haseeb Mir Sep 04 '21 at 17:35
4

Why don't you just create a program-wide global (use extern if you have to)

HANDLE mainThread ;
DWORD mainThreadId ;

On the first line of main, (before any threads are created) do

mainThread = GetCurrentThread() ;
mainThreadId = GetCurrentThreadId() ;

You can use any form of IPC to share either the id or the HANDLE with the remote process (haven't verified sharing the HANDLE will work but it should!)

bobobobo
  • 64,917
  • 62
  • 258
  • 363
  • Why in main and not simply `HANDLE mainThread = GetCurrentThread()`? – Liviu Jun 17 '14 at 14:01
  • Also asked here: http://stackoverflow.com/questions/13287963/id-of-main-thread-in-c – Liviu Jun 17 '14 at 14:08
  • 4
    According to the docs of `GetCurrentThread()`, it returns a pseudo-handle, not a handle, so using it for comparison would not work I assume. – Pol Jul 06 '16 at 03:01
2

A number of useful API functions of this type are under the (of course!) Tool Help suite. The CreateToolhelp32Snapshot() API will take a snapshot of the running threads for a specified process.

// Take a snapshot of all running threads  
hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); 
if( hThreadSnap == INVALID_HANDLE_VALUE ) 
  return( FALSE );

Full example code here.

The struct returned does not differentiate the primary thread from the others. I do not know a mechanism to do so; while some versions of the C runtime will all ExitProcess() at the end of the primary thread, in all recent versions the process continues to run until the last thread exits.

Interjay's recommendation to use GetThreadTimes may be the best bet. If you can CreateProcess() the target process, the hThread member of the PROCESS_INFORMATION block contains the tid for the primary thread. Welcome any ideas from others.

J.J.
  • 5,019
  • 2
  • 28
  • 26
  • I wonder how software like OllyDbg finds out what the "main thread" is. You can also attach to already running processes with it. – Etan Dec 28 '09 at 16:38
  • DebugActiveProcess() -- http://msdn.microsoft.com/en-us/library/ms679295%28VS.85%29.aspx GetThreadContext() returns the registers for the "current thread context," but no distinction of primary thread I'm aware of. – J.J. Dec 28 '09 at 21:20