7.6 实现进程挂起与恢复

挂起与恢复进程是指暂停或恢复进程的工作状态,以达到一定的控制和管理效果。在 Windows 操作系统中,可以使用系统提供的函数实现进程的挂起和恢复,以达到对进程的控制和调度。需要注意,过度使用进程挂起/恢复操作可能会造成系统性能的降低,导致死锁等问题,因此在使用时应该谨慎而慎重。同时,通过和其他进程之间协同工作,也可以通过更加灵活的方式,实现进程的协调、交互等相应的功能,从而实现更加高效和可靠的进程管理。

要实现挂起进程,首先我们需要实现挂起线程,因为挂起进程的实现原理是通过调用SuspendThread函数循环将进程内的所有线程全部挂起后实现的,而要实现挂起线程则我们需要先确定指定进程内的线程信息,要实现枚举进程内的线程信息则可以通过以下几个步骤实现。

首先通过CreateToolhelp32Snapshot得到当前系统下所有的进程快照,并通过遍历进程的方式寻找是否符合我们所需要枚举的进程名,如果是则调用CreateToolhelp32Snapshot并通过传入TH32CS_SNAPTHREAD代表枚举线程,通过循环的方式遍历进程内的线程,每次通过调用OpenThread打开线程,并调用ZwQueryInformationThread查询该线程的入口信息以及线程所在的模块信息,最后以此输出即可得到当前进程内的所有线程信息。

c 复制代码
#include <iostream>
#include <Windows.h>  
#include <TlHelp32.h>
#include <Psapi.h>

using namespace std;

typedef enum _THREADINFOCLASS
{
  ThreadBasicInformation,
  ThreadTimes,
  ThreadPriority,
  ThreadBasePriority,
  ThreadAffinityMask,
  ThreadImpersonationToken,
  ThreadDescriptorTableEntry,
  ThreadEnableAlignmentFaultFixup,
  ThreadEventPair_Reusable,
  ThreadQuerySetWin32StartAddress,
  ThreadZeroTlsCell,
  ThreadPerformanceCount,
  ThreadAmILastThread,
  ThreadIdealProcessor,
  ThreadPriorityBoost,
  ThreadSetTlsArrayAddress,
  ThreadIsIoPending,
  ThreadHideFromDebugger,
  ThreadBreakOnTermination,
  MaxThreadInfoClass
}THREADINFOCLASS;

typedef struct _CLIENT_ID
{
  HANDLE UniqueProcess;
  HANDLE UniqueThread;
}CLIENT_ID;

typedef struct _THREAD_BASIC_INFORMATION
{
  LONG ExitStatus;
  PVOID TebBaseAddress;
  CLIENT_ID ClientId;
  LONG AffinityMask;
  LONG Priority;
  LONG BasePriority;
}THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

extern "C" LONG(__stdcall * ZwQueryInformationThread)
(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
) = NULL;

// 枚举进程内的线程
BOOL EnumThread(char *ProcessName)
{
  // 进程快照句柄
  HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) };

  // 遍历进程
  while (Process32Next(hProcessSnap, &process))
  {
    // char* 转 string
    string s_szExeFile = process.szExeFile;
    if (s_szExeFile == ProcessName)
    {
      HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
      THREADENTRY32 te32;

      // 创建线程快照
      hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
      if (hThreadSnap == INVALID_HANDLE_VALUE)
      {
        return FALSE;
      }

      // 为快照分配内存空间
      te32.dwSize = sizeof(THREADENTRY32);

      // 获取第一个线程的信息
      if (!Thread32First(hThreadSnap, &te32))
      {
        return FALSE;
      }

      // 遍历线程
      while (Thread32Next(hThreadSnap, &te32))
      {
        // 判断线程是否属于本进程
        if (te32.th32OwnerProcessID == process.th32ProcessID)
        {
          // 打开线程
          HANDLE hThread = OpenThread(
            THREAD_ALL_ACCESS,        // 访问权限
            FALSE,                    // 由此线程创建的进程不继承线程的句柄
            te32.th32ThreadID         // 线程 ID
            );
          if (hThread == NULL)
          {
            return FALSE;
          }

          // 将区域设置设置为从操作系统获取的ANSI代码页
          setlocale(LC_ALL, ".ACP");

          // 获取 ntdll.dll 的模块句柄
          HINSTANCE hNTDLL = ::GetModuleHandle("ntdll");

          // 从 ntdll.dll 中取出 ZwQueryInformationThread
          (FARPROC&)ZwQueryInformationThread = GetProcAddress(hNTDLL, "ZwQueryInformationThread");

          // 获取线程入口地址
          PVOID startaddr;                          // 用来接收线程入口地址
          ZwQueryInformationThread(
            hThread,                              // 线程句柄
            ThreadQuerySetWin32StartAddress,      // 线程信息类型 ThreadQuerySetWin32StartAddress 线程入口地址
            &startaddr,                           // 指向缓冲区的指针
            sizeof(startaddr),                    // 缓冲区的大小
            NULL
            );

          // 获取线程所在模块
          THREAD_BASIC_INFORMATION tbi;            // _THREAD_BASIC_INFORMATION 结构体对象
          TCHAR modname[MAX_PATH];                 // 用来接收模块全路径
          ZwQueryInformationThread(
            hThread,                             // 线程句柄
            ThreadBasicInformation,              // 线程信息类型,ThreadBasicInformation :线程基本信息
            &tbi,                                // 指向缓冲区的指针
            sizeof(tbi),                         // 缓冲区的大小
            NULL
            );

          // 检查入口地址是否位于某模块中
          GetMappedFileName(
            OpenProcess(                                        // 进程句柄
            PROCESS_ALL_ACCESS,                                 // 访问权限
            FALSE,                                              // 由此线程创建的进程不继承线程的句柄
            (DWORD)tbi.ClientId.UniqueProcess                   // 唯一进程 ID
            ),
            startaddr,                            // 要检查的地址
            modname,                              // 用来接收模块名的指针
            MAX_PATH                              // 缓冲区大小
            );
          std::cout << "线程ID: " << te32.th32ThreadID << " 线程入口: " << startaddr << " 所在模块: " << modname << std::endl;
        }
      }
    }
  }
}

int main(int argc, char* argv[])
{
  EnumThread("lyshark.exe");

  system("pause");
  return 0;
}

读者可自行运行上述代码片段,即可枚举出当前运行进程lyshark.exe中所有的后动线程信息,如下图所示;

当我们能够得到当前进程内的线程信息后,接下来就是实现如何挂起或恢复进程内的特定线程,挂起线程可以使用SuspendThread 其函数声明如下:

c 复制代码
DWORD SuspendThread(
  HANDLE hThread
);

其中,hThread 是一个指向线程句柄的指针,指向要挂起的线程的句柄,该函数返回挂起前线程的线程计数器值,表示被挂起线程在挂起前还未执行的指令数目。

可以多次调用 SuspendThread 函数将同一个线程进行多次挂起,每次返回被挂起前线程的线程计数器值,每调用一次则会阻塞该线程,其状态会变为挂起状态。当该线程被 ResumeThread 恢复时,它将继续从上次挂起时的位置开始执行。

ResumeThread 函数声明如下:

c 复制代码
DWORD ResumeThread(
  HANDLE hThread
);

其中,hThread 是线程句柄,指向要恢复的线程的句柄。

调用 ResumeThread 函数可以让一个被挂起的线程从上次挂起的位置开始继续执行,函数返回值是被恢复的线程的先前挂起次数。当被恢复的线程的挂起计数器归零时,其状态将自动变为非挂起状态,并开始继续执行。

当有了上述两个函数的支持那么挂起线程将变得很容易实现了,首先后去所有进程快照,接着就是直接打开OpenThread()符合要求的线程,此时只需要调用SuspendThread(hThread)即可挂起一个线程,调用ResumeThread(hThread)则可以恢复一个线程,具体实现代码如下所示;

c 复制代码
#include <windows.h>
#include <stdio.h>
#include <TlHelp32.h>

int Start_Stop_Thread(DWORD Pid, DWORD ThreadID, BOOL flag)
{
    THREADENTRY32 te32 = { 0 };
    te32.dwSize = sizeof(THREADENTRY32);

    // 获取全部线程快照
    HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (INVALID_HANDLE_VALUE != hThreadSnap)
    {
        // 获取快照中第一条信息
        BOOL bRet = Thread32First(hThreadSnap, &te32);
        while (bRet)
        {
            // 只过滤出 pid 里面的线程
            if (Pid == te32.th32OwnerProcessID)
            {
                // 判断是否为ThreadID,暂停指定的TID
                if (ThreadID == te32.th32ThreadID)
                {
                    // 打开线程
                    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);

                    if (flag == TRUE)
                    {
                        ResumeThread(hThread);     // 恢复线程
                    }
                    else
                    {
                        SuspendThread(hThread);     // 暂停线程
                    }
                    CloseHandle(hThreadSnap);
                }
            }
            // 获取快照中下一条信息
            bRet = Thread32Next(hThreadSnap, &te32);
        }
        return 0;
    }
    return -1;
}

int main(int argc, char* argv[])
{
    // 暂停或恢复进程ID = 4204 里面的线程ID = 10056
    int ret = Start_Stop_Thread(4204, 10056, TRUE);    // TRUE = 恢复线程 FALSE = 挂起线程
    printf("状态: %d \n", ret);

    system("pause");
    return 0;
}

当有了上述功能的支持以后,那么实现挂起进程将变得很容易,读者只需要在特定一个进程内枚举出所有的活动线程,并通过循环的方式逐个挂起即可实现挂起整个进程的效果,这段完整代码如下所示;

c 复制代码
#include <windows.h>
#include <iostream>
#include <tlhelp32.h>
#include <Psapi.h>

#pragma comment(lib,"psapi.lib")

#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define SystemProcessesAndThreadsInformation    5       // 功能号
typedef DWORD(WINAPI* PQUERYSYSTEM)(UINT, PVOID, DWORD, PDWORD);

// 线程状态的枚举常量
typedef enum _THREAD_STATE
{
  StateInitialized,    // 初始化状态
  StateReady,          // 准备状态
  StateRunning,        // 运行状态
  StateStandby,
  StateTerminated,     // 关闭
  StateWait,           // 等待
  StateTransition,     // 切换
  StateUnknown
}THREAD_STATE;

// 线程处于等待的原因的枚举常量
typedef 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
}KWAIT_REASON;

typedef LONG   NTSTATUS;
typedef LONG   KPRIORITY;

typedef struct _CLIENT_ID
{
  DWORD        UniqueProcess;
  DWORD        UniqueThread;
} CLIENT_ID, * PCLIENT_ID;

typedef struct _VM_COUNTERS
{
  SIZE_T        PeakVirtualSize;
  SIZE_T        VirtualSize;
  ULONG         PageFaultCount;
  SIZE_T        PeakWorkingSetSize;
  SIZE_T        WorkingSetSize;
  SIZE_T        QuotaPeakPagedPoolUsage;
  SIZE_T        QuotaPagedPoolUsage;
  SIZE_T        QuotaPeakNonPagedPoolUsage;
  SIZE_T        QuotaNonPagedPoolUsage;
  SIZE_T        PagefileUsage;
  SIZE_T        PeakPagefileUsage;
} VM_COUNTERS;

// 线程信息结构体
typedef struct _SYSTEM_THREAD_INFORMATION
{
  LARGE_INTEGER   KernelTime;
  LARGE_INTEGER   UserTime;
  LARGE_INTEGER   CreateTime;
  ULONG           WaitTime;
  PVOID           StartAddress;
  CLIENT_ID       ClientId;
  KPRIORITY       Priority;
  KPRIORITY       BasePriority;
  ULONG           ContextSwitchCount;
  LONG            State;// 状态,是THREAD_STATE枚举类型中的一个值
  LONG            WaitReason;//等待原因, KWAIT_REASON中的一个值
} SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION;


typedef struct _UNICODE_STRING
{
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

// 进程信息结构体
typedef struct _SYSTEM_PROCESS_INFORMATION
{
  ULONG            NextEntryDelta;    // 指向下一个结构体的指针
  ULONG            ThreadCount;       // 本进程的总线程数
  ULONG            Reserved1[6];      // 保留
  LARGE_INTEGER    CreateTime;        // 进程的创建时间
  LARGE_INTEGER    UserTime;          // 在用户层的使用时间
  LARGE_INTEGER    KernelTime;        // 在内核层的使用时间
  UNICODE_STRING   ProcessName;       // 进程名
  KPRIORITY        BasePriority;
  ULONG            ProcessId;         // 进程ID
  ULONG            InheritedFromProcessId;
  ULONG            HandleCount;       // 进程的句柄总数
  ULONG            Reserved2[2];      // 保留
  VM_COUNTERS      VmCounters;
  IO_COUNTERS      IoCounters;
  SYSTEM_THREAD_INFORMATION Threads[5];    // 子线程信息数组
}SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;

// 获取线程是被是否被挂起 1=表示线程被挂起  0=表示线程正常 -1=未知状态
int IsThreadSuspend(DWORD dwProcessID, DWORD dwThreadID)
{
  int nRet = 0;
  NTSTATUS Status = 0;

  PQUERYSYSTEM NtQuerySystemInformation = NULL;
  PSYSTEM_PROCESS_INFORMATION pInfo = { 0 };

  // 获取函数地址
  NtQuerySystemInformation = (PQUERYSYSTEM) GetProcAddress(LoadLibrary("ntdll.dll"), "NtQuerySystemInformation");

  DWORD   dwSize = 0;

  // 获取信息所需的缓冲区大小
  Status = NtQuerySystemInformation(SystemProcessesAndThreadsInformation,// 要获取的信息的类型
    NULL, // 用于接收信息的缓冲区
    0,  // 缓冲区大小
    &dwSize
  );

  // 申请缓冲区
  char* pBuff = new char[dwSize];
  pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuff;
  if (pInfo == NULL)
    return -1;

  // 再次调用函数, 获取信息
  Status = NtQuerySystemInformation(SystemProcessesAndThreadsInformation, // 要获取的信息的类型
    pInfo, // 用于接收信息的缓冲区
    dwSize,  // 缓冲区大小
    &dwSize
  );
  if (!NT_SUCCESS(Status))
  {
    /*如果函数执行失败*/
    delete[] pInfo;
    return -1;
  }

  // 遍历结构体,找到对应的进程
  while (1)
  {
    // 判断是否还有下一个进程
    if (pInfo->NextEntryDelta == 0)
      break;

    // 判断是否找到了ID
    if (pInfo->ProcessId == dwProcessID)
    {

      // 找到该进程下的对应的线程,也就是遍历所有线程
      for (DWORD i = 0; i < pInfo->ThreadCount; i++)
      {
        if (pInfo->Threads[i].ClientId.UniqueThread == dwThreadID)
        {
          // 找到线程 
          // 如果线程被挂起
          if (pInfo->Threads[i].State == StateWait&& pInfo->Threads[i].WaitReason == Suspended)
          {
            nRet = 1;
            break;
          }
        }
      }
      break;
    }
    // 迭代到下一个节点
    pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);
  }

  delete[] pBuff;
  return nRet;
}

// 设置进程状态 挂起/非挂起
int SuspendProcess(DWORD dwProcessID, BOOL fSuspend)
{
  HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessID);

  if (hSnapshot != INVALID_HANDLE_VALUE)
  {
    THREADENTRY32 te = { sizeof(te) };
    BOOL fOk = Thread32First(hSnapshot, &te);
    for (; fOk; fOk = Thread32Next(hSnapshot, &te))
    {
      if (te.th32OwnerProcessID == dwProcessID)
      {
        HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,FALSE, te.th32ThreadID);
        if (hThread != NULL)
        {
          if (fSuspend)
          {
            if (IsThreadSuspend(dwProcessID, te.th32ThreadID) == 0)
            {
              SuspendThread(hThread);
            }
            else
            {
              return 0;
            }
          }
          else
          {
            if (IsThreadSuspend(dwProcessID, te.th32ThreadID) == 1)
            {
              ResumeThread(hThread);
            }
            else
            {
              return 0;
            }
          }
        }
        CloseHandle(hThread);
      }
    }

  }
  CloseHandle(hSnapshot);
  return 1;
}

int main(int argc, char *argv[])
{
  // 挂起进程
  SuspendProcess(20308, TRUE);

  // 恢复进程
  SuspendProcess(20308, FALSE);

  return 0;
}

读者可自行编译并运行上述代码,通过调用SuspendProcess函数并以此传入需要挂起的进程PID以及一个状态,当该状态为TRUE时则代表挂起进程,而当状态值为FALSE时则代表为恢复一个进程,当一个进程被挂起后其会出现卡死的现象,当恢复后一切都会变得正常。

相关推荐
cjy0001112 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本3 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji34163 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan3 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer4 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor3564 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor3564 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer5 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP6 小时前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪
人间打气筒(Ada)6 小时前
如何基于 Go-kit 开发 Web 应用:从接口层到业务层再到数据层
开发语言·后端·golang