挂起/恢复进程,就像PsSuspend一样

14

我希望这篇文章不是重复的。让我解释一下:

我考虑了类似的帖子如何暂停/恢复Windows下的任何外部进程?但更喜欢C++/Python,而且在发布时还没有被接受的答案。


我的问题:

我对实现由Windows SysinternalsMark Russinovich提供的PsSuspend的功能的Delphi可能实现感兴趣。

PsSuspend可以让您暂停本地或远程系统上的进程,这在某些情况下是可取的,例如某个进程正在消耗您希望允许不同进程使用的资源(例如网络、CPU或磁盘)。而不是终止正在消耗资源的进程,暂停允许您在稍后的某个时间点让它继续操作。

谢谢。


编辑:

部分实现将会做到。远程功能可以删除。


你想要模拟整个功能吗?包括远程进程? - David Heffernan
1
现在这是一个非常好的问题。 - user497849
@David Heffernan:不,让我们放弃远程功能。 - menjaraz
4个回答

13
您可以尝试使用以下代码。它使用未公开的函数 NtSuspendProcessNtResumeProcess。我已经在 Windows 7 64 位上通过 Delphi 2009 构建的 32 位应用程序中尝试了它,并且对我有效。请注意,这些函数是未公开的,因此可能会从 Windows 的未来版本中删除。 更新: 下面代码的 SuspendProcessResumeProcess 封装现在是函数,并且如果成功则返回 True,否则返回 False。
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;

4
这就是未记录的函数的乐趣。不管怎样,“OpenProcess”返回一个进程句柄而不是模块句柄,供参考。 - David Heffernan
2
这里有一些代码示例(还展示了如何暂停每个线程),从PsSuspend程序内部探究,我相信它也使用了这些API。只有一个问题,希望能以某种方式获取当前状态,就像在Process Explorer中一样。 - kobik
2
我不信任使用 PROCESS_ALL_ACCESS 的代码。指定操作所需的最小访问权限是一个好习惯。也许你需要使用 SeDebugPrivilege 来设置调用进程,这应该在标准用户权限下进行测试。 - kobik
3
以下是“如何检查线程/进程是否已挂起”的翻译内容(原文链接:http://vtopan.wordpress.com/2009/04/15/how-to-find-out-if-a-threadprocess-is-suspended-get-thread-state/):这里是如何检查线程/进程是否已挂起的方法。 - kobik
2
@TLama:发现了这个网站 The Undocumented Functions。看起来很有趣。 - menjaraz
显示剩余4条评论

5
在Windows中没有SuspendProcess API调用,所以您需要执行以下操作:
  1. 枚举进程中的所有线程。请参见RRUZ's答案获取示例代码。
  2. 对这些线程中的每一个调用SuspendThread
  3. 为了实现程序的恢复部分,请为每个线程调用ResumeThread

这似乎是模拟功能的合理方式。我怀疑基于PsSuspend使用的未记录API:pssuspend [-] [-r] [\\computer [-u username] [-p password]] <process name | process id>。有没有办法确定它不使用这样的API,考虑到它是由MS内部的专家编写的? - menjaraz
2
马克在编写 PsSuspend 之后不久加入了微软。尽管如此,它很可能使用未记录的 API。加入微软之前,他还编写了《Windows 内部》一书,因此内部或外部并没有太大的区别。我认为,在配置文件模式下使用依赖项浏览器将告诉您使用了哪些 API。 - David Heffernan
@Chibueze Opata:问题在于由于某些原因(故意或疏忽),微软不会公开披露它们,所以唯一的选择是寻找其他公共/地下来源。 - menjaraz
我实际上的意思是这是一个常见的话题,所以即使它是秘密,也会成为公开的秘密... - Chibueze Opata
@Chibueze Opata:你说得对,长期以往,没有什么能保持秘密。 - menjaraz
1
你需要注意的是,未记录的API可能会发生变化。这意味着如果你依赖它们,那么当在未来版本的操作系统上运行时,你的程序可能会停止工作。 - David Heffernan

5

"暂停所有线程" 实现中存在竞态条件 - 如果您尝试暂停的程序在创建快照和完成暂停之间创建了一个或多个线程,会发生什么?

你可以循环获取另一个快照并挂起任何未挂起的线程,只有当找不到时才退出。

未记录的函数避免了这个问题。


1
我刚刚在这里找到了以下代码片段here(作者:steve10120)。
我认为它们非常有价值,所以我忍不住将它们发布出来,作为对我的问题的另一种答案。

恢复进程:

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;

暂停进程:

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;

免责声明:

我并没有对它们进行任何测试。请享受使用,并不要忘记反馈。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接