How to get thread state (e.g. suspended), memory + CPU usage, start time, priority, etc
Asked Answered
S

1

8

How do I get the information if a thread has been suspended with SuspendThread(). There is no API that gives this information. The toolhelp snapshot API is very limited. There is a lot of misleading information in internet and also on StackOverflow. Some people on StackOverflow even say that this is not possible.

Others post a solution that requires Windows 7. But I need the code to work on XP.

Scauper answered 8/4, 2014 at 22:49 Comment(0)
S
15

I found the answer myself. I wrote a class cProcInfo that obtains a lot of information about processes and threads like:

  1. Process and Thread identifiers
  2. Process Parent Identifier
  3. Process Name
  4. Priority
  5. Context Switches
  6. Address
  7. State (running, waiting, suspended, etc..)
  8. Date and Time when process and thread were started
  9. Time spent in Kernel mode
  10. Time spent in User mode
  11. Memory usage
  12. Handle count
  13. Page Faults

My class works on Windows 2000, XP, Vista, 7, 8...

The following code shows how to determine if the thread 640 in the process 1948 is supended:

Main()
{
    cProcInfo i_Proc;
    DWORD u32_Error = i_Proc.Capture();
    if (u32_Error)
    {
        printf("Error 0x%X capturing processes.\n", u32_Error);
        return 0;
    }

    SYSTEM_PROCESS* pk_Proc = i_Proc.FindProcessByPid(1948);
    if (!pk_Proc)
    {
        printf("The process does not exist.\n");
        return 0;
    }

    SYSTEM_THREAD* pk_Thread = i_Proc.FindThreadByTid(pk_Proc, 640);
    if (!pk_Thread)
    {
        printf("The thread does not exist.\n");
        return 0;
    }

    BOOL b_Suspend;
    i_Proc.IsThreadSuspended(pk_Thread, &b_Suspend);

    if (b_Suspend) printf("The thread is suspended.\n");
    else           printf("The thread is not suspended.\n");
    return 0;
}

My class first captures all currently running processes and threads with NtQuerySystemInformation() and then searches the information of the requested thread.

You can easily add a function that searches a process by name. (pk_Proc->usName)

Here comes my class. It is only one header file:

#pragma once

#include <winternl.h>
#include <winnt.h>

typedef LONG NTSTATUS;

#define STATUS_SUCCESS              ((NTSTATUS) 0x00000000)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS) 0xC0000004)

enum KWAIT_REASON
{
    Executive,
    FreePage,
    PageIn,
    PoolAllocation,
    DelayExecution,
    Suspended,
    UserRequest,
    WrExecutive,
    WrFreePage,
    WrPageIn,
    WrPoolAllocation,
    WrDelayExecution,
    WrSuspended,
    WrUserRequest,
    WrEventPair,
    WrQueue,
    WrLpcReceive,
    WrLpcReply,
    WrVirtualMemory,
    WrPageOut,
    WrRendezvous,
    Spare2,
    Spare3,
    Spare4,
    Spare5,
    Spare6,
    WrKernel,
    MaximumWaitReason
};

enum THREAD_STATE
{
    Running = 2,
    Waiting = 5,
};

#pragma pack(push,8)

struct CLIENT_ID
{
    HANDLE UniqueProcess; // Process ID
    HANDLE UniqueThread;  // Thread ID
};

// http://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/thread.htm
// Size = 0x40 for Win32
// Size = 0x50 for Win64
struct SYSTEM_THREAD
{
    LARGE_INTEGER KernelTime;
    LARGE_INTEGER UserTime;  
    LARGE_INTEGER CreateTime;
    ULONG         WaitTime;
    PVOID         StartAddress;
    CLIENT_ID     ClientID;           // process/thread ids
    LONG          Priority;
    LONG          BasePriority;
    ULONG         ContextSwitches;
    THREAD_STATE  ThreadState;
    KWAIT_REASON  WaitReason;
};

struct VM_COUNTERS // virtual memory of process
{
    ULONG_PTR PeakVirtualSize;
    ULONG_PTR VirtualSize;
    ULONG     PageFaultCount;
    ULONG_PTR PeakWorkingSetSize;
    ULONG_PTR WorkingSetSize;
    ULONG_PTR QuotaPeakPagedPoolUsage;
    ULONG_PTR QuotaPagedPoolUsage;
    ULONG_PTR QuotaPeakNonPagedPoolUsage;
    ULONG_PTR QuotaNonPagedPoolUsage;
    ULONG_PTR PagefileUsage;
    ULONG_PTR PeakPagefileUsage;
};

// http://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm
// See also SYSTEM_PROCESS_INROMATION in Winternl.h
// Size = 0x00B8 for Win32
// Size = 0x0100 for Win64
struct SYSTEM_PROCESS
{
    ULONG          NextEntryOffset; // relative offset
    ULONG          ThreadCount;
    LARGE_INTEGER  WorkingSetPrivateSize;
    ULONG          HardFaultCount;
    ULONG          NumberOfThreadsHighWatermark;
    ULONGLONG      CycleTime;
    LARGE_INTEGER  CreateTime;
    LARGE_INTEGER  UserTime;  
    LARGE_INTEGER  KernelTime;
    UNICODE_STRING ImageName;
    LONG           BasePriority;
    PVOID          UniqueProcessId;
    PVOID          InheritedFromUniqueProcessId;
    ULONG          HandleCount;
    ULONG          SessionId;
    ULONG_PTR      UniqueProcessKey;
    VM_COUNTERS    VmCounters;
    ULONG_PTR      PrivatePageCount;
    IO_COUNTERS    IoCounters;   // defined in winnt.h
};

#pragma pack(pop)

typedef NTSTATUS (WINAPI* t_NtQueryInfo)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);

class cProcInfo
{
public:
    cProcInfo()
    {
        #ifdef WIN64
            assert(sizeof(SYSTEM_THREAD) == 0x50 && sizeof(SYSTEM_PROCESS) == 0x100);
        #else
            assert(sizeof(SYSTEM_THREAD) == 0x40 && sizeof(SYSTEM_PROCESS) == 0xB8);
        #endif

        mu32_DataSize  = 1000;
        mp_Data        = NULL;
        mf_NtQueryInfo = NULL;
    }
    virtual ~cProcInfo()
    {
        if (mp_Data) LocalFree(mp_Data);
    }

    // Capture all running processes and all their threads.
    // returns an API or NTSTATUS Error code or zero if successfull
    DWORD Capture()
    {
        if (!mf_NtQueryInfo)
        {
            mf_NtQueryInfo = (t_NtQueryInfo)GetProcAddress(GetModuleHandleA("NtDll.dll"), "NtQuerySystemInformation");
            if (!mf_NtQueryInfo)
                return GetLastError();
        }

        // This must run in a loop because in the mean time a new process may have started 
        // and we need more buffer than u32_Needed !!
        while (true)
        {
            if (!mp_Data) 
            {
                mp_Data = (BYTE*)LocalAlloc(LMEM_FIXED, mu32_DataSize);
                if (!mp_Data)
                    return GetLastError();
            }

            ULONG u32_Needed = 0;
            NTSTATUS s32_Status = mf_NtQueryInfo(SystemProcessInformation, mp_Data, mu32_DataSize, &u32_Needed);

            if (s32_Status == STATUS_INFO_LENGTH_MISMATCH) // The buffer was too small
            {
                mu32_DataSize = u32_Needed + 4000;
                LocalFree(mp_Data);
                mp_Data = NULL;
                continue;
            }
            return s32_Status;
        }
    }

    // Searches a process by a given Process Identifier
    // Capture() must have been called before!
    SYSTEM_PROCESS* FindProcessByPid(DWORD u32_PID)
    {
        if (!mp_Data)
        {
            assert(mp_Data);
            return NULL;
        }

        SYSTEM_PROCESS* pk_Proc = (SYSTEM_PROCESS*)mp_Data;
        while (TRUE)
        {
            if ((DWORD)(DWORD_PTR)pk_Proc->UniqueProcessId == u32_PID)
                return pk_Proc;

            if (!pk_Proc->NextEntryOffset)
                return NULL;

            pk_Proc = (SYSTEM_PROCESS*)((BYTE*)pk_Proc + pk_Proc->NextEntryOffset);
        }
    }

    SYSTEM_THREAD* FindThreadByTid(SYSTEM_PROCESS* pk_Proc, DWORD u32_TID)
    {
        if (!pk_Proc)
        {
            assert(pk_Proc);
            return NULL;
        }

        // The first SYSTEM_THREAD structure comes immediately after the SYSTEM_PROCESS structure
        SYSTEM_THREAD* pk_Thread = (SYSTEM_THREAD*)((BYTE*)pk_Proc + sizeof(SYSTEM_PROCESS));

        for (DWORD i=0; i<pk_Proc->ThreadCount; i++)
        {
            if (pk_Thread->ClientID.UniqueThread == (HANDLE)(DWORD_PTR)u32_TID)
                return pk_Thread;

            pk_Thread++;
        }
        return NULL;
    }

    DWORD IsThreadSuspended(SYSTEM_THREAD* pk_Thread, BOOL* pb_Suspended)
    {
        if (!pk_Thread)
            return ERROR_INVALID_PARAMETER;

        *pb_Suspended = (pk_Thread->ThreadState == Waiting &&
                         pk_Thread->WaitReason  == Suspended);
        return 0;
    }

private:
    BYTE*         mp_Data;
    DWORD       mu32_DataSize;
    t_NtQueryInfo mf_NtQueryInfo;
};

// Based on the 32 bit code of Sven B. Schreiber on:
// http://www.informit.com/articles/article.aspx?p=22442&seqNum=5
Scauper answered 8/4, 2014 at 22:50 Comment(7)
Calling SYSTEM_PROCESS* pk_Proc = i_Proc.FindProcessByPid(1948); with a known-good PID fails (returns 0) on my Win 8.1 x64 system. Capture() succeeded.Alexandretta
May be you have to put a #pragma pack(8) around the struct definitions for 64 bit ? You should investigate more details! Single step through the code!Scauper
Want to point out that this undocumented code & struct definitions are for x86 only. Which is a bad design. It must be updated with the correct 64-bit definitions. Here and here is more info.Nannana
@ahmd0: Thank you for the links. When I wrote the code this information was not available in internet. I updated the code. Now it also runs on 64 bit Windows.Scauper
Or if you're targeting specifically for Windows 10, then you can use ThreadSystemThreadInformation (40) as info class to NtQueryInformationThread()Idyll
very nice answer. I have a question. Is that possible to get pk_Proc without Capture() and all time enumerate mp_Data ?Tude
You did not understand the code. Capture() obtains the list of all running processes in mp_Data. This has to be requested from the operating system. If you don't call Capture() where do you want to ge the list of running processes from ???Scauper

© 2022 - 2024 — McMap. All rights reserved.