/ Windows / 251浏览

Windows 应用暂停技术

背景

在特定场景下,一些进程运行单纯的浪费资源,但又不能杀掉进程,所以需要通过挂起的方式,暂停进程运行。以释放资源给关键进程运行。

方法对比

方案方案描述优势不足
NtSuspendProcess在内核模式下遍历线程,并通过挂起线程来完成进程的挂起1. 内核态遍历,性能高 2. 新创建的线程也能挂起1. 不能中断内核代码 2. 中断影响不可恢复 3. 存在部分兼容问题
SuspendThread在用户模式下遍历线程,再通过挂起线程来完成进程的挂起-1. 不能中断内核代码 2. 中断影响不可恢复 3. 用户态遍历,性能较低 4. 可能存在部分新线程未挂起 5. 存在部分兼容问题
NtDebugActiveProcess利用调试的机制,创建一个调试端口附加到特定进程,以实现挂起应用(实际也是在内核模式下遍历线程,并挂起)1. 内核态遍历,性能高 2. 可恢复性好 3. 兼容性好1. 不能中断内核代码
JobObjectFreezeInfomation利用 Job 从进程级别冻结1. 进程级别冻结,不存在线程之间竞争1. 可恢复性差
NtChangeProcessState通过修改进程状态来冻结进程可恢复性好不能中断内核代码

方法详解

1. NtSuspendProcess

通过直接调用 NtSuspendProcess 来对进程进行挂起,通过 NtResumeProcess 来恢复进程。

此 API 是 ntdll.dll 导出但未文档化的接口。也是最常用到的接口。其中 ProcessHackerSystemInfomations Sandboxie 等都使用的这种方式。

但也存在一定的缺陷,一些情况下还是会存在无法挂起的情况。

例如:

[Plus v1.11.4] Process Suspend/Resume issues · Issue #3375 · sandboxie-plus/Sandboxie

devenv, msedge, discord, skype and other processes cannot be suspended · Issue #856 · winsiderss/sys

下面我们来看看它是怎么做的:

1.1 原理

通过 https://github.com/reactos/reactos 进去(这里也可以通过 windbg 来看,是一样的),可以看到其调用链:

NtSuspendProcess -> PsSuspendProcess -> PsGetNextProcessThread + PsSuspendThread -> KeSuspendThread -> KiSuspendThread -> KiInsertQueueApc

那这个我们就突然熟悉了,这就很明显了,就是通过在内核模式下,通过遍历线程,然后依次塞入 APC 来进行挂起。具体可以看 线程挂起和恢复 里面有详细的分析。

1.2 问题

  • 不能暂停内核代码。由于是塞了个 APC 到线程中去,所以是需要等内核代码执行完成后才会执行到 APC 中,才有可能被暂停(加之 APC 队列中可能还存在其他 APC)
  • 中断的影响不可恢复。如果在遍历线程的过程中进程意外退出,可能导致部分线程被挂起的状态,可能导致进程部分功能异常。且无法恢复。

1.3 示例

void ByNtSuspendProcess(DWORD dwProcessId)
{
    HANDLE hProcess = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    if (NtSuspendProcess(hProcess) != STATUS_SUCCESS)
    {
        std::cout << "NtSuspendProcess failed" << std::endl;
        CloseHandle(hProcess);
        return;
    }

    std::cout << "Process suspended" << std::endl;
    CloseHandle(hProcess);
}

void ByNtResumeProcess(DWORD dwProcessId) {
    HANDLE hProcess = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    if (NtResumeProcess(hProcess) != STATUS_SUCCESS)
    {
        std::cout << "NtResumeProcess failed" << std::endl;
        CloseHandle(hProcess);
        return;
    }

    std::cout << "Process resumed" << std::endl;
    CloseHandle(hProcess);
}

2. SuspendThread

通过遍历当前进程的所有线程,然后依次在用户模式下调用 SuspendThread 来实现进程的挂起,通过 ResumeThread 来恢复进程。

特别地:对于 WOW64 线程,需要调用 Wow64SuspendThreadResumeThread 来进行挂起和恢复。

API 参考:

这里不同的遍历线程方式,可能会产生不同的效果。

主要分为两种不同的遍历方式:

  • CreateToolhelp32Snapshot function (tlhelp32.h) - Win32 apps:通过创建快照的方式,依次进行挂起
    • 只记录调用时刻的线程列表,调用后创建的进程没法获取
    • 进程快照会带来较大的开销
    • 快照中记录的是线程 ID,如果该线程退出后,快速被分配其他线程,则有可能挂起错误的线程(可能性很少,但存在可能)
  • NtGetNextThread(undocument):依次枚举线程,再挂起。由于其是挂起一个线程,获取下一个线程,所以不存在上述问题。

2.1 原理

此方式的原理在 线程挂起和恢复 有较详细的分析,这里不再赘述。与 NtSuspendProcess 几乎一致。

2.2 问题

  • 额外的开销。由于在用户模式下遍历线程,并调用 SuspendThread,这里需要每次在获取目标线程的句柄时,都有额外的检查。
  • 不能暂停内核代码。由于是塞了个 APC 到线程中去,所以是需要等内核代码执行完成后才会执行到 APC 中,才有可能被暂停(加之 APC 队列中可能还存在其他 APC)
  • 中断的影响不可恢复。如果在遍历线程的过程中进程意外退出,可能导致部分线程被挂起的状态,可能导致进程部分功能异常。且无法恢复。

2.3 示例

  1. Snapshot & SuspendThread
void BySnapshotAndSuspendThread(DWORD dwProcessId)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessId);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        std::cout << "CreateToolhelp32Snapshot failed" << std::endl;
        CloseHandle(hProcess);
        return;
    }

    THREADENTRY32 te32;
    te32.dwSize = sizeof(THREADENTRY32);
    if (!Thread32First(hSnapshot, &te32))
    {
        std::cout << "Thread32First failed" << std::endl;
        CloseHandle(hSnapshot);
        CloseHandle(hProcess);
        return;
    }

    do
    {
        if (te32.th32OwnerProcessID == dwProcessId)
        {
            HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
            if (hThread == NULL)
            {
                std::cout << "OpenThread failed" << std::endl;
                CloseHandle(hSnapshot);
                CloseHandle(hProcess);
                return;
            }

            if (SuspendThread(hThread) == -1)
            {
                std::cout << "SuspendThread failed" << std::endl;
                CloseHandle(hThread);
                CloseHandle(hSnapshot);
                CloseHandle(hProcess);
                return;
            }

            std::cout << "Thread " << te32.th32ThreadID << " suspended" << std::endl;
            CloseHandle(hThread);
        }
    } while (Thread32Next(hSnapshot, &te32));

    CloseHandle(hSnapshot);
    CloseHandle(hProcess);
}

void BySnapshotAndResumeThread(DWORD dwProcessId)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessId);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        std::cout << "CreateToolhelp32Snapshot failed" << std::endl;
        CloseHandle(hProcess);
        return;
    }

    THREADENTRY32 te32;
    te32.dwSize = sizeof(THREADENTRY32);
    if (!Thread32First(hSnapshot, &te32))
    {
        std::cout << "Thread32First failed" << std::endl;
        CloseHandle(hSnapshot);
        CloseHandle(hProcess);
        return;
    }

    do
    {
        if (te32.th32OwnerProcessID == dwProcessId)
        {
            HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
            if (hThread == NULL)
            {
                std::cout << "OpenThread failed" << std::endl;
                CloseHandle(hSnapshot);
                CloseHandle(hProcess);
                return;
            }

            if (ResumeThread(hThread) == -1)
            {
                std::cout << "ResumeThread failed" << std::endl;
                CloseHandle(hThread);
                CloseHandle(hSnapshot);
                CloseHandle(hProcess);
                return;
            }

            std::cout << "Thread " << te32.th32ThreadID << " resumed" << std::endl;
            CloseHandle(hThread);
        }
    } while (Thread32Next(hSnapshot, &te32));

    CloseHandle(hSnapshot);
    CloseHandle(hProcess);
}

  1. NtGetNextThread & SuspendThread
void ByNtGetNextThreadAndSuspendThread(DWORD dwProcessId)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    HANDLE hThread = NULL;
    THREAD_BASIC_INFORMATION tbi;
    NTSTATUS status = STATUS_SUCCESS;
    while (NtGetNextThread(hProcess, hThread, THREAD_SUSPEND_RESUME, 0, 0, &hThread) == STATUS_SUCCESS)
    {
        if (SuspendThread(hThread) == -1)
        {
            std::cout << "NtSuspendThread failed, error code: " << GetLastError() << std::endl;
        }
    }

    CloseHandle(hProcess);
}

void ByNtGetNextThreadAndResumeThread(DWORD dwProcessId) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    HANDLE hThread = nullptr;
    THREAD_BASIC_INFORMATION tbi;
    NTSTATUS status = STATUS_SUCCESS;
    while (NtGetNextThread(hProcess, hThread, THREAD_SUSPEND_RESUME, 0, 0, &hThread) == STATUS_SUCCESS)
    {
        if (ResumeThread(hThread) == -1)
        {
            std::cout << "NtResumeThread failed, error code: " << GetLastError() << std::endl;
            break;
        }
    }

    CloseHandle(hProcess);
}

3. NtDebugActiveProcess

通过调用 NtCreateDebugObject 来创建 Debug Object,然后通过 NtDebugActiveProcess 将 Debug Object 附加到进程上,以此实现进程的暂停。

通过 NtRemoveProcessDebug 移除对应的 Debug Object 即可恢复执行。

3.1 原理

这种方式主要依赖 Windows 中的调试机制,其可以兼容几乎所有的应用。

首先,通过 NtCreateDebugObject 来创建一个调试对象(也称:调试端口),然后将其连接到目标进程(NtDebugActiveProcess )。然后等待目标进程发生感兴趣的事件(NtWaitForDebugEvent ),然后再进行对应处理(NtDebugContinue )。

这里讨论的是用户模式下的调试,内核模式机制不太一样。这里不详述

这里如果关闭了调试对象的 handle(或者进程崩溃)会自动取消暂停

这里我们不是真的调试,所以不需要后续的操作,只需要连接到目标进程即可。

那问题来了,NtDebugActiveProcess 连接进程之后怎么就停下来了呢?是怎么做到的呢?

  1. ReactOS

我们看看 ReactOS 里面是怎么实现 NtDebugActiveProcess 的:

reactos/ntoskrnl/dbgk/dbgkobj.c at 1a162375f9946088d503d586aff73e40ff2c143a · reactos/reactos

总结一下调用链:

NtDebugActiveProcess -> DbgkpPostFakeProcessCreateMessages -> DbgkpPostFakeThreadMessages -> PsGetNextProcessThread + PsSuspendThread

于是我们就熟悉了,在连接到目标进程的时候,在内核模式下,通过发送进程/线程的创建事件,遍历线程并通过 PsSuspendThread 来将线程挂起。

  1. Windbg 来看

在 Win10 22H2 上内核调试,追踪 NtDebugActiveProcess 可以看到与 ReactOS 基本一致:

堆栈如下:

nt!PsSuspendThread
nt!DbgkpPostFakeThreadMessages+0x16a
nt!DbgkpPostFakeProcessCreateMessages+0x59
nt!NtDebugActiveProcess+0x174
nt!KiSystemServiceCopyEnd+0x25
ntdll!NtDebugActiveProcess+0x14

结合 NtDebugActiveProcess 的反汇编代码(中间神略部分):

3.2 问题

  • 不能暂停内核代码。由于是塞了个 APC 到线程中去,所以是需要等内核代码执行完成后才会执行到 APC 中,才有可能被暂停(加之 APC 队列中可能还存在其他 APC)

3.3 示例

HANDLE ByNtCreateDebugObjectSuspendProcess(DWORD dwProcessId)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return nullptr;
    }

    HANDLE hDebugObject = nullptr;
    OBJECT_ATTRIBUTES objAttr;
    InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
    if (NtCreateDebugObject(&hDebugObject, DEBUG_ALL_ACCESS, &objAttr, 0) != STATUS_SUCCESS)
    {
        std::cout << "NtCreateDebugObject failed" << std::endl;
        CloseHandle(hProcess);
        hProcess = nullptr;
        return nullptr;
    }

    if (NtDebugActiveProcess(hProcess, hDebugObject) != STATUS_SUCCESS)
    {
        std::cout << "NtDebugActiveProcess failed" << std::endl;
        CloseHandle(hDebugObject);
        CloseHandle(hProcess);
        return nullptr;
    }

    std::cout << "Process suspended" << std::endl;
    CloseHandle(hProcess);
    return hDebugObject;
}

void ByNtRemoveProcessDebugResumeProcess(DWORD dwProcessId, HANDLE hDebugObject)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    if (NtRemoveProcessDebug(hProcess, hDebugObject) != STATUS_SUCCESS)
    {
        std::cout << "NtRemoveProcessDebug failed" << std::endl;
        CloseHandle(hProcess);
        return;
    }

    std::cout << "Process resumed" << std::endl;
    CloseHandle(hProcess);
}

4. JobObjectFreezeInformation

通过 CreateJobObject 新建一个 job 对象,通过 NtSetInformationJobObject 修改 job 对象的属性 JobObjectFreezeInformation 中的 Freeze 为 true,再通过 AssignProcessToJobObject 将目标进程指定到此 job 中,即可实现对进程的冻结。

通过 NtSetInformationJobObject 修改 job 对象属性 Freeze 为 false,即可取消对进程的冻结。

此方案,被用于现代应用(UWP 应用)的挂起操作中。

4.1 原理

Windows 提供对的一种对进程的冻结机制。

可以在内核进程对象 KPROCESS 中找到对应的属性 DeepFreeze:

2: kd> dt nt!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER
   +0x018 ProfileListHead  : _LIST_ENTRY
   +0x028 DirectoryTableBase : Uint8B
   +0x030 ThreadListHead   : _LIST_ENTRY
   +0x040 ProcessLock      : Uint4B
   +0x044 ProcessTimerDelay : Uint4B
   +0x048 DeepFreezeStartTime : Uint8B
   +0x050 Affinity         : _KAFFINITY_EX
   +0x0f8 AffinityPadding  : [12] Uint8B
   +0x158 ReadyListHead    : _LIST_ENTRY
   +0x168 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x170 ActiveProcessors : _KAFFINITY_EX
   +0x218 ActiveProcessorsPadding : [12] Uint8B
   +0x278 AutoAlignment    : Pos 0, 1 Bit
   +0x278 DisableBoost     : Pos 1, 1 Bit
   +0x278 DisableQuantum   : Pos 2, 1 Bit
   +0x278 DeepFreeze       : Pos 3, 1 Bit
   +0x278 TimerVirtualization : Pos 4, 1 Bit
   +0x278 CheckStackExtents : Pos 5, 1 Bit
   +0x278 CacheIsolationEnabled : Pos 6, 1 Bit
   +0x278 PpmPolicy        : Pos 7, 3 Bits
   +0x278 VaSpaceDeleted   : Pos 10, 1 Bit
   +0x278 ReservedFlags    : Pos 11, 21 Bits
   +0x278 ProcessFlags     : Int4B
   +0x27c ActiveGroupsMask : Uint4B
   +0x280 BasePriority     : Char
   +0x281 QuantumReset     : Char
   +0x282 Visited          : Char
   +0x283 Flags            : _KEXECUTE_OPTIONS
   +0x284 ThreadSeed       : [20] Uint2B
   +0x2ac ThreadSeedPadding : [12] Uint2B
   +0x2c4 IdealProcessor   : [20] Uint2B
   +0x2ec IdealProcessorPadding : [12] Uint2B
   +0x304 IdealNode        : [20] Uint2B
   +0x32c IdealNodePadding : [12] Uint2B
   +0x344 IdealGlobalNode  : Uint2B
   +0x346 Spare1           : Uint2B
   +0x348 StackCount       : _KSTACK_COUNT
   +0x350 ProcessListEntry : _LIST_ENTRY
   +0x360 CycleTime        : Uint8B
   +0x368 ContextSwitches  : Uint8B
   +0x370 SchedulingGroup  : Ptr64 _KSCHEDULING_GROUP
   +0x378 FreezeCount      : Uint4B
   +0x37c KernelTime       : Uint4B
   +0x380 UserTime         : Uint4B
   +0x384 ReadyTime        : Uint4B
   +0x388 UserDirectoryTableBase : Uint8B
   +0x390 AddressPolicy    : UChar
   +0x391 Spare2           : [71] UChar
   +0x3d8 InstrumentationCallback : Ptr64 Void
   +0x3e0 SecureState      : <anonymous-tag>
   +0x3e8 KernelWaitTime   : Uint8B
   +0x3f0 UserWaitTime     : Uint8B
   +0x3f8 EndPadding       : [8] Uint8B

4.2 问题

  • 如果在冻结后,丢失了 job 的句柄,则无法恢复

4.3 示例

#define JOB_OBJECT_OPERATION_FREEZE 1

HANDLE ByJobObjectFreeze(DWORD dwProcessId)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return nullptr;
    }

    HANDLE hJob = CreateJobObject(NULL, NULL);
    if (hJob == NULL)
    {
        std::cout << "CreateJobObject failed" << std::endl;
        CloseHandle(hProcess);
        return nullptr;
    }

    JOBOBJECT_FREEZE_INFORMATION jfi;
    jfi.Flags = JOB_OBJECT_OPERATION_FREEZE;
    jfi.Freeze = true;

    // (JOBOBJECTINFOCLASS)18 <-> JobObjectFreezeInformation
    if (!NT_SUCCESS(NtSetInformationJobObject(hJob, (JOBOBJECTINFOCLASS)18, &jfi, sizeof(jfi))))
    {
        std::cout << "SetInformationJobObject failed" << std::endl;
        CloseHandle(hJob);
        CloseHandle(hProcess);
        return nullptr;
    }

    if (!AssignProcessToJobObject(hJob, hProcess))
    {
        std::cout << "AssignProcessToJobObject failed" << std::endl;
        CloseHandle(hJob);
        CloseHandle(hProcess);
        return nullptr;
    }

    std::cout << "Process freeze" << std::endl;
    CloseHandle(hProcess);

    return hJob;
}

void ByJobObjectUnFreeze(DWORD dwProcessId, HANDLE hObj) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    JOBOBJECT_FREEZE_INFORMATION jfi;
    jfi.Flags = JOB_OBJECT_OPERATION_FREEZE;
    jfi.Freeze = false;

    // (JOBOBJECTINFOCLASS)18 <-> JobObjectFreezeInformation
    if (!NT_SUCCESS(NtSetInformationJobObject(hObj, (JOBOBJECTINFOCLASS)18, &jfi, sizeof(jfi))))
    {
        std::cout << "SetInformationJobObject failed" << std::endl;
        CloseHandle(hObj);
        CloseHandle(hProcess);
        return;
    }

    std::cout << "Process unfreeze" << std::endl;
    CloseHandle(hProcess);
}

5. NtChangeProcessState

此 API 只能在 Win11 以上使用

首先通过 NtCreateProcessStateChange 创建一个句柄,然后通过 NtChangeProcessState 修改进程状态

如果释放了 NtCreateProcessStateChange 创建的句柄,则会自动恢复

5.1 原理

这里直接看看调用堆栈:

Call Site
nt!KiInsertQueueApc
nt!KiSuspendThread+0x6f
nt!KiFreezeSingleThread+0x71
nt!KeFreezeProcess+0x9f
nt!PsFreezeProcess+0x2c
nt!NtChangeProcessState+0x179
nt!KiSystemServiceCopyEnd+0x25
ntdll!NtChangeProcessState+0x14

基本上可以看出,其本质上都是通过插入 apc 来实现进程挂起。

5.2 问题

  • 不能暂停内核代码。由于是塞了个 APC 到线程中去,所以是需要等内核代码执行完成后才会执行到 APC 中,才有可能被暂停(加之 APC 队列中可能还存在其他 APC)

5.3 示例


#define PROCESS_STATE_CHANGE_STATE (0x0001)
#define PROCESS_STATE_ALL_ACCESS STANDARD_RIGHTS_ALL | PROCESS_STATE_CHANGE_STATE

// 此方法只在 win11 以后的版本才有
HANDLE ByProcessStateChangeFreeze(DWORD processId) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return nullptr;
    }

    HANDLE hProcessStateChange = nullptr;
    NTSTATUS status = NtCreateProcessStateChange(&hProcessStateChange, PROCESS_SET_INFORMATION | PROCESS_STATE_ALL_ACCESS, nullptr, hProcess, NULL);
    if (!NT_SUCCESS(status)) {
        std::cout << "NtCreateProcessStateChange failed, status is : " << status << std::endl;
        CloseHandle(hProcess);
        return nullptr;
    }

    status = NtChangeProcessState(hProcessStateChange, hProcess, PROCESS_STATE_CHANGE_TYPE::ProcessStateChangeSuspend, NULL, 0, 0);
    if (!NT_SUCCESS(status)) {
        std::cout << "NtChangeProcessState failed, status is : " << status << std::endl;
        CloseHandle(hProcessStateChange);
        CloseHandle(hProcess);
        return nullptr;
    }

    std::cout << "Process freeze" << std::endl;
    CloseHandle(hProcess);
}

void ByProcessStateChangeResume(DWORD processId, HANDLE hProcessStateChange) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
    if (hProcess == NULL)
    {
        std::cout << "OpenProcess failed" << std::endl;
        return;
    }

    NTSTATUS status = NtChangeProcessState(hProcessStateChange, hProcess, PROCESS_STATE_CHANGE_TYPE::ProcessStateChangeResume, NULL, 0, 0);
    if (!NT_SUCCESS(status)) {
        std::cout << "NtChangeProcessState failed, status is: " << status << std::endl;
        CloseHandle(hProcessStateChange);
        CloseHandle(hProcess);
        return;
    }

    std::cout << "Process unfreeze" << std::endl;
    CloseHandle(hProcessStateChange);
    CloseHandle(hProcess);
}
如何通过 C++ 实时监听 ETW 事件
如何通过 C++ 实时监听 ETW 事件
【译】调查并确定 Windows 运行速度变慢问题
【译】调查并确定 Windows 运行速度变慢问题
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 采样
【译】丢失的 WPA 文档 —— CPU 采样
如何通过 PDH(Performance Data Helper) 获取性能计数器的值

0

  1. This post has no comment yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注