14

I hope this post is not a duplicate one. Let me explain:

I have considered the similar post How to pause / resume any external process under Windows? but with C++/Python preference and yet without an accepted answer as of the time of posting.


My Question:

I'm interested in a possible implementation in Delphi of the functionality provided by PsSuspend by Mark Russinovich of Windows Sysinternals.

Quotes:

PsSuspend lets you suspend processes on the local or a remote system, which is desirable in cases where a process is consuming a resource (e.g. network, CPU or disk) that you want to allow different processes to use. Rather than kill the process that's consuming the resource, suspending permits you to let it continue operation at some later point in time.

Thank you.


Edit:

A partial implementation will do. Remote capability can be dropped.

Community
  • 1
  • 1
menjaraz
  • 7,551
  • 4
  • 41
  • 81

4 Answers4

13

You can try to use the following code. It uses the undocumented functions NtSuspendProcess and NtResumeProcess. I've tried it on Windows 7 64-bit from the 32-bit application built in Delphi 2009 and it works for me. Note that these functions are undocumented thus can be removed from future versions of Windows.

Update

The SuspendProcess and ResumeProcess wrappers from the following code are now functions and returns True if succeed, False otherwise.

type
  NTSTATUS = LongInt;
  TProcFunction = function(ProcHandle: THandle): NTSTATUS; stdcall;

const
  STATUS_SUCCESS = $00000000;
  PROCESS_SUSPEND_RESUME = $0800;

function SuspendProcess(const PID: DWORD): Boolean;
var
  LibHandle: THandle;
  ProcHandle: THandle;
  NtSuspendProcess: TProcFunction;
begin
  Result := False;
  LibHandle := SafeLoadLibrary('ntdll.dll');
  if LibHandle <> 0 then
  try
    @NtSuspendProcess := GetProcAddress(LibHandle, 'NtSuspendProcess');
    if @NtSuspendProcess <> nil then
    begin
      ProcHandle := OpenProcess(PROCESS_SUSPEND_RESUME, False, PID);
      if ProcHandle <> 0 then
      try
        Result := NtSuspendProcess(ProcHandle) = STATUS_SUCCESS;
      finally
        CloseHandle(ProcHandle);
      end;
    end;
  finally
    FreeLibrary(LibHandle);
  end;
end;

function ResumeProcess(const PID: DWORD): Boolean;
var
  LibHandle: THandle;
  ProcHandle: THandle;
  NtResumeProcess: TProcFunction;
begin
  Result := False;
  LibHandle := SafeLoadLibrary('ntdll.dll');
  if LibHandle <> 0 then
  try
    @NtResumeProcess := GetProcAddress(LibHandle, 'NtResumeProcess');
    if @NtResumeProcess <> nil then
    begin
      ProcHandle := OpenProcess(PROCESS_SUSPEND_RESUME, False, PID);
      if ProcHandle <> 0 then
      try
        Result := NtResumeProcess(ProcHandle) = STATUS_SUCCESS;
      finally
        CloseHandle(ProcHandle);
      end;
    end;
  finally
    FreeLibrary(LibHandle);
  end;
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
  • I'm starting to think they are not functions but procedures from Delphi's point of view since they returns always False even if the process is frozen. – TLama Apr 14 '12 at 13:22
  • 4
    That's the joy of undocumented functions. Anyway, `OpenProcess` returns a process handle and not a module handle, FWIW. – David Heffernan Apr 14 '12 at 13:28
  • @David, good catch, thanks! Menjaraz, interesting on the `NtSuspendProcess` function is its behavior. It freeze the process, but the windows are still receiving windows messages. Once you release the process they are processed. – TLama Apr 14 '12 at 13:38
  • 1
    That's exactly as exected. Messages get placed in a queue. Threads don't need to be running for that to happen. – David Heffernan Apr 14 '12 at 14:08
  • 2
    Here is some [code sample](http://programmersforum.ru/showthread.php?t=5343) (also shows how to suspend each Thread). from pocking inside `PsSuspend` program, I believe it also uses these APIs. Just one more thing, It would be nice to somehow get the current status, the same as in Process Explorer. – kobik Apr 14 '12 at 14:31
  • Maybe, but I don't get why most of the examples uses the `PROCESS_ALL_ACCESS` privilege. It's enough to have `PROCESS_SUSPEND_RESUME`, moreover it's exactly for this purpose, see [`the reference`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880%28v=vs.85%29.aspx). Maybe was the `PROCESS_SUSPEND_RESUME` added in recent version of Windows, don't know. – TLama Apr 14 '12 at 14:35
  • 2
    I don't trust a code that uses `PROCESS_ALL_ACCESS`. It is always a good idea to specify the minimum set of access rights required for the operation. Maybe you need to set the calling process with `SeDebugPrivilege`, this should be tested with standard user privileges. – kobik Apr 14 '12 at 14:39
  • 3
    Here is [how to check if thread/process is suspended](http://vtopan.wordpress.com/2009/04/15/how-to-find-out-if-a-threadprocess-is-suspended-get-thread-state/) – kobik Apr 14 '12 at 15:01
  • 2
    @TLama:Found this site [The Undocumented Functions](http://undocumented.ntinternals.net/). Seems to be interesting. – menjaraz Apr 16 '12 at 03:56
5

There is no SuspendProcess API call in Windows. So what you need to do is:

  1. Enumerate all the threads in the process. See RRUZ's answer for sample code.
  2. Call SuspendThread for each of these threads.
  3. In order to implement the resume part of the program, call ResumeThread for each thread.
Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • This seems reasonable to emulate the functionality. I suspect undocumented API based on the usage of PsSuspend: `pssuspend [- ] [-r] [\\computer [-u username] [-p password]] `. Is there a way to ascertain that it doesn't any use such api given that it was written by an expert within MS itself? – menjaraz Apr 14 '12 at 12:52
  • 2
    Mark joined MS some time after writing PsSuspend. All the same it may very well use undocumented APIs. He also wrote Windows Internals before joining MS so inside or outside makes little difference. I think dependency walker in profile mode would tell you what APIs are used. – David Heffernan Apr 14 '12 at 12:58
  • @Chibueze Opata: The trouble is that for some reasons (at purpose/by negligence) MS doesn't disclose them, so the only option left is to find other public/undergroung source. – menjaraz Apr 15 '12 at 06:00
  • What I actually meant is that it is a common topic, so even if it was secret, it would be an open secret... – Chibueze Opata Apr 15 '12 at 07:28
  • @Chibueze Opata: You name it! Nothing can be kept secret in the long run. – menjaraz Apr 16 '12 at 06:16
  • 1
    What you have to watch out for is that undocumented APIs are subject to change. Which means that if you rely on them then your program could stop working when run on future versions of the OS – David Heffernan Apr 16 '12 at 06:23
5

There is a race condition for the "suspend all threads" implementation - what happens if the program you are trying to suspend creates one or more threads between the time that you create the snapshot and the time that you complete suspending?

You could loop, getting another snapshot and suspending any unsuspending threads, exiting only when you found none.

The undocumented function avoids this issue.

Wombat
  • 51
  • 1
1

I just found the following snippets here (Author: steve10120).

I think they are valuables and I can't help posting them also as an alternative answer to my own question.


Resume Process:

function ResumeProcess(ProcessID: DWORD): Boolean;
 var
   Snapshot,cThr: DWORD;
   ThrHandle: THandle;
   Thread:TThreadEntry32;
 begin
   Result := False;
   cThr := GetCurrentThreadId;
   Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
   if Snapshot <> INVALID_HANDLE_VALUE then
    begin
     Thread.dwSize := SizeOf(TThreadEntry32);
     if Thread32First(Snapshot, Thread) then
      repeat
       if (Thread.th32ThreadID <> cThr) and (Thread.th32OwnerProcessID = ProcessID) then
        begin
         ThrHandle := OpenThread(THREAD_ALL_ACCESS, false, Thread.th32ThreadID);
         if ThrHandle = 0 then Exit;
         ResumeThread(ThrHandle);
         CloseHandle(ThrHandle);
        end;
      until not Thread32Next(Snapshot, Thread);
      Result := CloseHandle(Snapshot);
     end;
 end;

Suspend Process:

function SuspendProcess(PID:DWORD):Boolean;
 var
 hSnap:  THandle;
 THR32:  THREADENTRY32;
 hOpen:  THandle;
 begin
   Result := FALSE;
   hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
   if hSnap <> INVALID_HANDLE_VALUE then
   begin
     THR32.dwSize := SizeOf(THR32);
     Thread32First(hSnap, THR32);
     repeat
       if THR32.th32OwnerProcessID = PID then
       begin
         hOpen := OpenThread($0002, FALSE, THR32.th32ThreadID);
         if hOpen <> INVALID_HANDLE_VALUE then
         begin
           Result := TRUE;
           SuspendThread(hOpen);
           CloseHandle(hOpen);
         end;
       end;
     until Thread32Next(hSnap, THR32) = FALSE;
     CloseHandle(hSnap);
   end;
 end;

Disclaimer:

I didn't test them at all. Please enjoy and don't forget to feedback.

menjaraz
  • 7,551
  • 4
  • 41
  • 81