Vlad Ioan Topan

My playground

How to check if a thread/process is suspended (get thread state)

with 4 comments

The basic steps to get to a thread’s status information is the following (knowing of course both the process ID (hence forth PID) and the thread ID (TID)):

  1. call NtQuerySystemInformation with SystemInformation set to SystemProcessInformation (5)
  2. iterate over the array of SYSTEM_PROCESS_INFORMATION structures (the structure contents is (wrongfully) explained here; correct version here) to find your PID (ProcessId member) of interest
  3. iterate over the array of SYSTEM_THREAD structures (detailed below) to find the desired TID (UniqueThread member) and check the State and WaitReason members; both must be set to 5 if the thread is suspended, any other values otherwise

As it’s probably obvious to most people keen on system-level programming, a process is suspended when all it’s threads are suspended, so all of them must be checked for the suspended status.

Step one: calling NtQuerySystemInformation

The required structures are defined here (for Delphi). The function isn’t defined in any headers, so we must declare it’s prototype ourselves:

function NtQuerySystemInformation(SystemInformationClass:DWORD; SystemInformation:pointer; SystemInformationLength:DWORD; ReturnLength:PDWORD):cardinal; stdcall; external 'ntdll';

Example usage:

var 
   spi:PSYSTEM_PROCESS_INFORMATION;
   size:DWORD;
begin
if (NtQuerySystemInformation(5, nil, 0, @size) = STATUS_INFO_LENGTH_MISMATCH) // SystemProcessInformation
   and (size > 0)
   then begin
        GetMem(spi, size);
        if NtQuerySystemInformation(5, spi, size, @size) = 0
           then begin
                [...] // do something with spi
                end
           else HandleError; // failed listing processes!
        FreeMem(spi);
        end
    else HandleError; // failed listing processes!
end;

HandleError is a fictional function (which you’ll most likely skip, ’cause you’re in a hurry to get things done, right? πŸ™‚ ).

Step two: iterating the process list

The structure only looks like a linked list item; the NextEntryOffset member is an actual offset from the beginning of the current structure to the beginning of the next one. This is needed because of the variable size of the structure (given by the variable number of threads for each process). We need an extra crt:PSYSTEM_PROCESS_INFORMATION variable to walk the pseudo-linked list because we must keep the original psi pointer to free it’s memory.
The outline of the code which iterates the processes looking for a PID (given the spi:PSYSTEM_PROCESS_INFORMATION pointer from above) would look like this:

var 
    crt:PSYSTEM_PROCESS_INFORMATION; 
[...]
    crt := spi;
    repeat
        if crt^.ProcessID = PID
           then begin
                [...] // do something with crt^
                break;
                end;
        crt := Pointer(DWORD(crt) + crt^.NextEntryOffset);
    until crt^.NextEntryOffset = 0;

Step three: find the appropriate thread

Given the ThreadInfo array in the structure located at the previous step, we iterate through it and test the State and WaitReason members for the item matching our TID:

var
    j:integer;
[...]
    for j := 0 to crt^.NumberOfThreads-1 do
        begin
        if crt^.ThreadInfo[j].UniqueThread = TID
           then begin
                if crt^.ThreadInfo[j].WaitReason = 5
                   then [...] // the thread is suspended
                   else [...]; // the thread is not suspended
                break; 
                end;
        end;

The State member must also be set to 5 (“waiting”), but if the WaitReason is non-null, the State must be 5 (and vice-versa), so there’s little point in checking it explicitly.

Additional info: thread starting address, priority etc.

If you’ve paid any attention while reading the structures, you might have noticed additional interesting information about threads and processes, such as the creation time, image path, priority, handle count and memory and I/O usage/history for processes (this is how Process Explorer gets, for example, the WorkingSet/PeakWorkingSet and ReadBytes/WriteBytes/OtherBytes information) and starting address, priority/base priority and various timing information for threads. The starting address is particularly interesting, because the NtQueryInformationThread API with ThreadInformationClass set to ThreadQuerySetWin32StartAddress (9) only works (on Windows pre-Vista) “before the thread starts running” (quoted from MSDN), which seems to me rather pointless in the first place.

The NtQuerySystemInformation API is also a useful replacement for the CreateToolhelp32Snapshot suite, yielding more information about processes and threads.

Advertisements

Written by vtopan

April 15, 2009 at 1:07 AM

Posted in Delphi, Snippets

4 Responses

Subscribe to comments with RSS.

  1. Hi, thank you very much for you code. The trouble is that it does not work. There are multiple bugs there (ThreadInfo has length of 1 element, and you address it as if it had length of crt^.NumberOfThreads, also while doing repeat..until cycle in step three you miss the very last process). So, here is the source of the function that really works.

    type
    PSYSTEM_THREAD = ^SYSTEM_THREAD

    function IsThreadSuspended:Boolean;
    var
    spi:PSYSTEM_PROCESS_INFORMATION;
    crt:PSYSTEM_PROCESS_INFORMATION;
    PThreadInfo:PSYSTEM_THREAD;
    Size:DWORD;
    j:Integer;
    LastProcess:Boolean;
    begin
    Result:=False; // Default result, will be also returned if any error arises.
    // If process ID is 0 then we use current process ID
    If AProcessID=0 then AProcessID:=GetCurrentProcessId;
    // —————————————————————————–
    if (NtQuerySystemInformation(5,nil,0,@Size)=STATUS_INFO_LENGTH_MISMATCH) and (Size>0) then
    begin
    GetMem(spi,Size);
    try
    if NtQuerySystemInformation(5,spi,Size,@Size)=0 then
    begin
    // ————————————————-
    crt:=spi;
    LastProcess:=False;
    While not LastProcess do
    begin
    LastProcess:=crt^.NextEntryOffset = 0;
    if crt^.ProcessID=AProcessID then
    begin
    // ————————————————–
    for j := 0 to crt^.NumberOfThreads-1 do
    begin
    PThreadInfo:=PSYSTEM_THREAD(@crt^.ThreadInfo[j]);
    if PThreadInfo^.UniqueThread = AThreadID then
    begin
    if PThreadInfo^.WaitReason = 5
    then Exit(True) // the thread is suspended
    else Exit(False); // the thread is not suspended
    Break;
    end;
    end;
    // ————————————————–
    Break;
    end;
    crt := Pointer(DWORD(crt) + crt^.NextEntryOffset);
    end;
    // ————————————————-
    end else Exit; // failed listing processes!
    finally
    FreeMem(spi);
    end;
    end else Exit; // failed listing processes!
    end;

    Ace

    August 6, 2015 at 8:25 PM

  2. Wonderful! My comment about bugs in your code was deleted without any notice/reply. Thank you very much, very professional.

    Ace

    August 12, 2015 at 11:37 AM

    • No it wasn’t, it was simply ignored as this is a 6 year old post and this blog is no longer actively maintained. But since you seem inclined to take offense, there you go, both your comments are approved. πŸ™‚

      ThreadInfo does not actually have a size (it’s an open array), but you’re right about missing the last element. It’s also just a “template” of sorts, not working code.

      Thank you for the comments! πŸ™‚

      vtopan

      August 12, 2015 at 7:30 PM

      • Hi, no offence taken πŸ™‚ Actually I was trying to solve the problem that your solutions solves. And I was glad that I have found your blog. I didn’t care the article was 6 years old. I knew it should work, but when I copy-pasted your code, it didn’t. So I corrected bugs and posted a working solution hoping that maybe in next 6 years some poor devil will need it and will be as hopeless as I was. I somehow thought that you were offended by the fact that I corrected your bugs. I was wrong, sorry. Peace!

        Ace

        August 12, 2015 at 8:53 PM


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: