/ Windows / 430浏览

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);
}
Windows是如何区分互联网下载文件和本地文件的
Windows是如何区分互联网下载文件和本地文件的
如何低成本的获取到应用卡顿情况
如何低成本的获取到应用卡顿情况
【译】ETW 堆跟踪 – 每个分配都被记录
【译】ETW 堆跟踪 – 每个分配都被记录
【译】Wait Analysis – 寻找空闲时间
【译】Wait Analysis – 寻找空闲时间
如何通过 ETW Provider 来记录应用日志
如何通过 ETW Provider 来记录应用日志
如何通过 C++ 实时监听 ETW 事件
如何通过 C++ 实时监听 ETW 事件

0

  1. This post has no comment yet

发表回复

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