写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

  看此教程之前,问几个问题,


🔒 华丽的分割线 🔒


临界区的实现

  实现临界区的方式有很多,只要保证在多线程多核的情况只有一个线程进入在临界区即可。如下是我实现临界区的效果图:

同步篇——事件等待与唤醒-LMLPHP

  如下是我的实现代码:

#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>

int flag=0;
int content=0;

void __declspec(naked) MyCriticalEnter()
{
    _asm
    {
startproc:
        mov eax, 1;
        lock xchg [flag],eax;
        test eax,eax;
        jz endproc;
    }
    Sleep(100);
    _asm
    {
        jmp startproc;
endproc:
        ret;
    }
}


void __declspec(naked) MyCriticalExit()
{
    _asm
    {
        mov eax,0;
        lock xchg [flag],eax;
        ret;
    }
}

DWORD WINAPI ThreadProc(LPWORD param)
{
    MyCriticalEnter();
    content++;
    printf("content : %d\n",content);
    MyCriticalExit();
    return 0;
}

int main(int argc, char* argv[])
{
    for (int i = 0 ;i<100;i++)
    {
        CloseHandle(CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,NULL,NULL));
    }

    system("pause");
    return 0;
}

高并发的 Hook

  首先我们需要一个实验的小白鼠,之前我们学过线程切换,正好这个函数就是非常高并发的函数:SwapContext。我们实现的功能就是Hook它实现统计计数,当然你可以给其他的高并发函数挂钩,实现其他的功能。
  我们给这样的函数进行挂钩的时候,会有一个并发的问题,稍有不慎就会导致蓝屏。周所周知,每一行汇编代码都是由一些规则编制出的硬编码,它们有长有短。我们给出如下情况:

0x463891 | 6A 00  | push 0
0x463893 | 6A 00  | push 0
0x463895 | 6A 00  | push 0
0x463897 | 6A 00  | push 0
0x463899 | 6A 00  | push 0
0x46389B | 6A 00  | push 0
0x46389D | 6A 00  | push 0

  这是某一块函数反汇编的参数结果,如果我必须Hook这里,否则我就无法实现我的功能,我们常见的Hook无非是一个跳转,但是如果是长跳转会带来问题,因为它的硬编码是5个字节,它已修改会影响3个2字节的硬编码汇编,这是十分不好的结果。不过我们的短跳是2个字节,我们可以通过几个短跳加长跳的方式解决。
  如果我们使用长跳进行Hook,这就会有个问题,因为对于32位的CPU一般的指令一条执行最多改4个字节,也就是说,我们正好执行到我们修改的区域当中时,然后线程切换,你把它给改了,然后线程回去后的EIP还是指向那里,导致出错,所以直接Hook是不可以的。
  还有种情况,比如你修改跳转的地址,你两个字节的改也会出现问题。比如我执行到你改Jmp跳转地址的Hook,然而你只改了两个字节,还有两个字节没改,而我已经执行完毕了,这就会导致Hook地址错误,也是不可以的。
  可以看出Hook是不能乱Hook的,有很多我们需要考虑的情况,虽然正好这么巧的概率挺低的,但时间一长,线程一多,迟早会出问题的。
  下面我们来实现一下这个功能:

#include <ntifs.h>
#include <ntddk.h>

int HookAddr = 0;
unsigned int OldThread = 0;
char shellcode[8] = { 0xE9,0,0,0,0 ,0x9c,0x8b,0x0b };
char yshellcode[8] = { 0x26, 0xC6, 0x46, 0x2D,0x02,0x9c,0x8b,0x0b };

KDPC dpc = { 0 };
KTIMER timer = { 0 };
LARGE_INTEGER duringtime = { 0 };

VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
 _In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
    if (OldThread)
        DbgPrint("Report Per 2s : Calls Old Thread %x \n", OldThread);
    OldThread = 0;
    KeSetTimer(&timer, duringtime, &dpc);
}

void __declspec(naked) HookSwapContext()
{
    __asm
    {
        mov byte ptr es : [esi + 2Dh] , 2;
        mov [OldThread], edi;
        mov eax, [HookAddr];
        add eax, 5;
        push eax;
        ret;
    }
}

unsigned int  __declspec(naked) GetKernelBase()
{
    __asm
    {
        mov eax, fs: [34h] ;
        mov eax, [eax + 18h];
        mov eax, [eax];
        mov eax, [eax + 18h];
        ret;
    }
}

void __declspec(naked) HookProc()
{
    _asm
    {
        pushad;
        pushfd;
        xor edx, edx;
        lea esi, shellcode;
        mov ebx, [esi];
        mov ecx, [esi + 4];

        mov edi, [HookAddr];
        mov eax, [edi];
        mov edx, [edi + 4];
        lock cmpxchg8b qword ptr[edi];
        popfd;
        popad;
        ret;
    }
}

void __declspec(naked) UnHookProc()
{
    _asm
    {
        pushad;
        pushfd;
        xor edx, edx;
        lea esi, yshellcode;
        mov ebx, [esi];
        mov ecx, [esi + 4];

        mov edi, [HookAddr];
        mov eax, [edi];
        mov edx, [edi + 4];
        lock cmpxchg8b qword ptr[edi];
        popfd;
        popad;
        ret;

}

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    UnHookProc();
    while (1)
    {
        KeDelayExecutionThread(KernelMode, TRUE, &  duringtime);
        if (!OldThread)
        {
            break;
        }
    }

    KeCancelTimer(&timer);
    DbgPrint("Unloaded Successfully!");
    return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DriverObject->DriverUnload = UnloadDriver;

    unsigned int base = GetKernelBase();

    HookAddr = base + 0x6A8E2;

    //初始化 shellcode
    unsigned int jmpdes = (int)HookSwapContext -    HookAddr - 5;
    RtlCopyMemory(&shellcode[1], &jmpdes, 4);


    KeInitializeTimer(&timer);
    KeInitializeDpc(&dpc, DPCRoutine, NULL);
    duringtime.QuadPart = -20 * 1000 * 1000;

    HookProc();

    KeSetTimer(&timer, duringtime, &dpc);

    DbgPrint("Loaded Successfully!");

    return STATUS_SUCCESS;
}

  效果图如下:

同步篇——事件等待与唤醒-LMLPHP

  当然,这个代码并不完美,主要是在退出卸载驱动上。因为我要卸载Hook的话,必须让所有的线程必须都从我的Hook流程处理中出来,否则我硬撤掉的话,就会导致蓝屏。目前没有很好的办法来处理,就算使用引用计数的方式也不太行。

等待与唤醒概要

  我们之前讲解了如何自己实现临界区以及什么是Windows自旋锁,这两种同步方案在线程无法进入临界区时都会让当前线程进入等待状态:一种是通过Sleep函数实现的,一种是通过让当前的CPU空转实现的,但这两种等待方式都有局限性。
  如果通过临界区进行,我们的线程通过Sleep函数进行等待,等待时间是不确定的。我们可以举个例子,一个线程已经进入临界区还未出来,另一个线程发现不行开始睡大觉,睡一会发现还不行,继续睡,即使这次期间那个线程走了,必须等到设置的时间到了才能醒来进入临界区,如果线程一多更没有头了。通过自旋锁的方式,只有等待时间很短的情况下才有意义,否则对CPU资源是种浪费,而且自旋锁只能在多核的环境下才有意义。所以有没有更加合理的等待方式呢?只有在条件成熟的时候才将当前线程唤醒?
  在Windows中,一个线程可以通过等待一个或者多个可等待对象,从而进入等待状态,另一个线程可以在某些时刻唤醒等待这些对象的其他线程。我们在3环编程的时候经常会用到WaitForSingleObject来等待内核对象,比如互斥体、事件、进程或者线程等。为什么那些内核对象可以等待呢?我们之前提到过它们头部都有一个结构体:

kd> dt _DISPATCHER_HEADER
ntdll!_DISPATCHER_HEADER
   +0x000 Type             : UChar
   +0x001 Absolute         : UChar
   +0x002 Size             : UChar
   +0x003 Inserted         : UChar
   +0x004 SignalState      : Int4B
   +0x008 WaitListHead     : _LIST_ENTRY

  上面的结构体的研究将是我们以后介绍的重点。可以被等待的内核对象有:KPROCESSKTHREADKTIMERKSEMAPHORE(信号量)、KEVENTKMUTANT(互斥体)、FILE_OBJECT。但是最后一个比较特殊,它没有直接包含上面所述的结构体,我们来看看它的结构:

kd> dt _FILE_OBJECT
ntdll!_FILE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Vpb              : Ptr32 _VPB
   +0x00c FsContext        : Ptr32 Void
   +0x010 FsContext2       : Ptr32 Void
   +0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS
   +0x018 PrivateCacheMap  : Ptr32 Void
   +0x01c FinalStatus      : Int4B
   +0x020 RelatedFileObject : Ptr32 _FILE_OBJECT
   +0x024 LockOperation    : UChar
   +0x025 DeletePending    : UChar
   +0x026 ReadAccess       : UChar
   +0x027 WriteAccess      : UChar
   +0x028 DeleteAccess     : UChar
   +0x029 SharedRead       : UChar
   +0x02a SharedWrite      : UChar
   +0x02b SharedDelete     : UChar
   +0x02c Flags            : Uint4B
   +0x030 FileName         : _UNICODE_STRING
   +0x038 CurrentByteOffset : _LARGE_INTEGER
   +0x040 Waiters          : Uint4B
   +0x044 Busy             : Uint4B
   +0x048 LastLock         : Ptr32 Void
   +0x04c Lock             : _KEVENT
   +0x05c Event            : _KEVENT
   +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT

  为什么文件内核对象可以被等待,是因为它有LockEvent成员,这两个都是事件,都是可被等待的对象,所以它可以被等待。

等待链与等待网

  学习相关知识之前,我们得看一张图:

同步篇——事件等待与唤醒-LMLPHP

  如上就是一个线程等待一个内核对象的示意图,为了更好的理解这东西,我们做一个实验加以讲解。如下是实验代码:

DWORD WINAPI ThreadProc(LPVOID param)
{
    WaitForSingleObject((HANDLE)param,INFINITE);
    puts("等待线程执行完毕!");
    return 0;
}

int main(int argc, char* argv[])
{
    HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    HANDLE hthread = CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadProc,hEvent,NULL,NULL);

    system("pause");
    CloseHandle(hthread);
    CloseHandle(hEvent);
    return 0;
}

  编译运行之后,然后就是仅有显示“按任意键继续”的控制台,这就是想要的效果,由于是无尽等待,所以会一直停在这里。我们在WinDbg看看等待链结构也就是上面的结构:

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
……

Failed to get VadRoot
PROCESS 89b4f020  SessionId: 0  Cid: 03fc    Peb: 7ffdf000  ParentCid: 06a0
    DirBase: 12dc0220  ObjectTable: e10641a0  HandleCount:  23.
    Image: WaitTest.exe
……

kd> !process 89b4f020
Failed to get VadRoot
PROCESS 89b4f020  SessionId: 0  Cid: 03fc    Peb: 7ffdf000  ParentCid: 06a0
    ……
        THREAD 89d9a8a8  Cid 03fc.04dc  Teb: 7ffde000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable
            89aeb488  ProcessObject
        Not impersonating
        DeviceMap                 e18d1c80
        Owning Process            00000000       Image:         <Invalid process>
        Attached Process          89b4f020       Image:         WaitTest.exe
        Wait Start TickCount      75638          Ticks: 9940 (0:00:01:39.543)
        Context Switch Count      59             IdealProcessor: 0
        UserTime                  00:00:00.000
        KernelTime                00:00:00.000
        Win32 Start Address 0x00401380
        Stack Init b83a6000 Current b83a5ca0 Base b83a6000 Limit b83a3000 Call 00000000
        Priority 12 BasePriority 8 PriorityDecrement 2 IoPriority 0 PagePriority 0
        Kernel stack not resident.
        ChildEBP RetAddr
        b83a5cb8 80501cd6     nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        b83a5cc4 804fad62     nt!KiSwapThread+0x46 (FPO: [0,0,0])
        b83a5cec 805b7126     nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        b83a5d50 8053e638     nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])
    <Intermediate frames may have been skipped due to lack of complete unwind>
        b83a5d50 7c92e4f4 (T) nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ b83a5d64)
    <Intermediate frames may have been skipped due to lack of complete unwind>
        0012fdd8 00000000 (T) ntdll!KiFastSystemCallRet (FPO: [0,0,0])

        THREAD 89d14da8  Cid 03fc.068c  Teb: 7ffdd000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable
            89af1cb0  NotificationEvent
        Not impersonating
        DeviceMap                 e18d1c80
        Owning Process            00000000       Image:         <Invalid process>
        Attached Process          89b4f020       Image:         WaitTest.exe
        Wait Start TickCount      75638          Ticks: 9940 (0:00:01:39.543)
        Context Switch Count      10             IdealProcessor: 0
        UserTime                  00:00:00.000
        KernelTime                00:00:00.000
        Win32 Start Address 0x00401005
        Stack Init b8226000 Current b8225ca0 Base b8226000 Limit b8223000 Call 00000000
        Priority 10 BasePriority 8 PriorityDecrement 2 IoPriority 0 PagePriority 0
        Kernel stack not resident.
        ChildEBP RetAddr
        b8225cb8 80501cd6     nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        b8225cc4 804fad62     nt!KiSwapThread+0x46 (FPO: [0,0,0])
        b8225cec 805b7126     nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        b8225d50 8053e638     nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])
    <Intermediate frames may have been skipped due to lack of complete unwind>
        b8225d50 7c92e4f4 (T) nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ b8225d64)
    <Intermediate frames may have been skipped due to lack of complete unwind>
        0052ff44 00000000 (T) ntdll!KiFastSystemCallRet (FPO: [0,0,0])

  由于篇幅限制,我只展示了部分,一般来说,最后一个就是我们想要找的线程,我们查看一下:

kd> dt _KTHREAD 89d14da8
ntdll!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   ……
   +0x05c WaitBlockList    : 0x89d14e18 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY [ 0x0 - 0x80553d88 ]
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   ……
kd> dx -id 0,0,805539a0 -r1 ((ntdll!_KWAIT_BLOCK *)0x89d14e18)
((ntdll!_KWAIT_BLOCK *)0x89d14e18)                 : 0x89d14e18 [Type: _KWAIT_BLOCK *]
    [+0x000] WaitListEntry    [Type: _LIST_ENTRY]
    [+0x008] Thread           : 0x89d14da8 [Type: _KTHREAD *]
    [+0x00c] Object           : 0x89af1cb0 [Type: void *]
    [+0x010] NextWaitBlock    : 0x89d14e18 [Type: _KWAIT_BLOCK *]
    [+0x014] WaitKey          : 0x0 [Type: unsigned short]
    [+0x016] WaitType         : 0x1 [Type: unsigned short]

  这就是我们要找的等待块。由于线程只等待一个事件对象,所以只有一个可用的等待块,这里为什么说是可用的。因为虽然有4个等待块,最后一个也就是第四个等待块已经被占坑了,也就是我们调用WaitForSingleObject函数后的等待超时时间,如果是某一个值,第四个等待块就会启用,也就是计时器。我们来看看第四个等待块:

kd> dt _KWAIT_BLOCK 0x89d14e18 + 18 * 3
ntdll!_KWAIT_BLOCK
   +0x000 WaitListEntry    : _LIST_ENTRY [ 0x89d14ea0 - 0x89d14ea0 ]
   +0x008 Thread           : 0x89d14da8 _KTHREAD
   +0x00c Object           : 0x89d14e98 Void
   +0x010 NextWaitBlock    : (null)
   +0x014 WaitKey          : 0x102
   +0x016 WaitType         : 1

  我们再会过来看看第一个等待块的内容。WaitKey是指等待块的索引,但是对于第四个等待块,它是特殊的,被赋予了比较大的值0x102Thread是指向的是当前线程,在这里也就是被等待的线程。Object指的就是被等待的对象的地址,我们可以看一下我们等待的到底是不是事件:

kd> dt _OBJECT_HEADER 0x89af1cb0-18
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n2
   +0x004 HandleCount      : 0n1
   +0x004 NextToFree       : 0x00000001 Void
   +0x008 Type             : 0x89fabbf8 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0 ''
   +0x010 ObjectCreateInfo : 0x89b32660 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x89b32660 Void
   +0x014 SecurityDescriptor : (null)
   +0x018 Body             : _QUAD
kd> dx -id 0,0,805539a0 -r1 ((ntkrnlpa!_OBJECT_TYPE *)0x89fabbf8)
((ntkrnlpa!_OBJECT_TYPE *)0x89fabbf8)                 : 0x89fabbf8 [Type: _OBJECT_TYPE *]
    [+0x000] Mutex            : Unowned Resource [Type: _ERESOURCE]
    [+0x038] TypeList         [Type: _LIST_ENTRY]
    [+0x040] Name             : "Event" [Type: _UNICODE_STRING]
    [+0x048] DefaultObject    : 0x0 [Type: void *]
    [+0x04c] Index            : 0x9 [Type: unsigned long]
    [+0x050] TotalNumberOfObjects : 0x631 [Type: unsigned long]
    [+0x054] TotalNumberOfHandles : 0x677 [Type: unsigned long]
    [+0x058] HighWaterNumberOfObjects : 0x684 [Type: unsigned long]
    [+0x05c] HighWaterNumberOfHandles : 0x6cd [Type: unsigned long]
    [+0x060] TypeInfo         [Type: _OBJECT_TYPE_INITIALIZER]
    [+0x0ac] Key              : 0x6e657645 [Type: unsigned long]
    [+0x0b0] ObjectLocks      [Type: _ERESOURCE [4]]

  通过我们的Name属性我们就能判断出就是事件内核对象了。
  继续下面的讲NextWaitBlock指向的就是下一个等待块的地址,由于我们只等待第一个,所以就是指向自己的地址。WaitType指的是等待类型,如果线程等待多个内核对象,只要有一个等待对象符合条件就被激活,那么这个值就是1;如果必须全部的等待对象符合条件才激活,该值就是0。
  对于WaitListEntry这个成员,我们需要看一下被等待对象的Head成员:

kd> dt _KEVENT 0x89af1cb0
ntdll!_KEVENT
   +0x000 Header           : _DISPATCHER_HEADER
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_DISPATCHER_HEADER *)0x89af1cb0))
(*((ntdll!_DISPATCHER_HEADER *)0x89af1cb0))                 [Type: _DISPATCHER_HEADER]
    [+0x000] Type             : 0x0 [Type: unsigned char]
    [+0x001] Absolute         : 0x1c [Type: unsigned char]
    [+0x002] Size             : 0x4 [Type: unsigned char]
    [+0x003] Inserted         : 0x89 [Type: unsigned char]
    [+0x004] SignalState      : 0 [Type: long]
    [+0x008] WaitListHead     [Type: _LIST_ENTRY]
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_LIST_ENTRY *)0x89af1cb8))
(*((ntdll!_LIST_ENTRY *)0x89af1cb8))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x89d14e18 [Type: _LIST_ENTRY *]
    [+0x004] Blink            : 0x89d14e18 [Type: _LIST_ENTRY *]

  被等待对象的Header成员中的WaitListHead会把用来等待它的等待块通过WaitListEntry串起来,这个就是该成员的作用。对于线程拥有的等待块会通过KThread结构体的WaitListEntry成员串起来,这个图的结构也就是这样了。
  如果是一个线程等待两个内核对象,它的结构示意图如下:

同步篇——事件等待与唤醒-LMLPHP

  实验代码如下:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

HANDLE hEvent[2];

DWORD WINAPI ThreadProc(LPVOID param)
{
    WaitForMultipleObjects(2,hEvent,TRUE,INFINITE);
    puts("等待线程执行完毕!");
    return 0;
}

int main(int argc, char* argv[])
{
    hEvent[0] = CreateEvent(NULL,TRUE,FALSE,NULL);
    hEvent[1]=CreateEvent(NULL,TRUE,FALSE,NULL);
    HANDLE hthread = CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,NULL,NULL);

    system("pause");
    CloseHandle(hthread);
    CloseHandle(hEvent[0]);
    CloseHandle(hEvent[1]);
    return 0;
}

  具体步骤我就不详细展示了,我就给一下结果:

kd> dt _KTHREAD 89d14da8
ntdll!_KTHREAD
   ……
   +0x05c WaitBlockList    : 0x89d14e18 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY [ 0x89b15e08 - 0x89d99908 ]
   ……
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_KWAIT_BLOCK (*)[4])0x89d14e18))
(*((ntdll!_KWAIT_BLOCK (*)[4])0x89d14e18))                 [Type: _KWAIT_BLOCK [4]]
    [0]              [Type: _KWAIT_BLOCK]
    [1]              [Type: _KWAIT_BLOCK]
    [2]              [Type: _KWAIT_BLOCK]
    [3]              [Type: _KWAIT_BLOCK]
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_KWAIT_BLOCK *)0x89d14e18))
(*((ntdll!_KWAIT_BLOCK *)0x89d14e18))                 [Type: _KWAIT_BLOCK]
    [+0x000] WaitListEntry    [Type: _LIST_ENTRY]
    [+0x008] Thread           : 0x89d14da8 [Type: _KTHREAD *]
    [+0x00c] Object           : 0x89a6ee58 [Type: void *]
    [+0x010] NextWaitBlock    : 0x89d14e30 [Type: _KWAIT_BLOCK *]
    [+0x014] WaitKey          : 0x0 [Type: unsigned short]
    [+0x016] WaitType         : 0x0 [Type: unsigned short]
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_KWAIT_BLOCK *)0x89d14e30))
(*((ntdll!_KWAIT_BLOCK *)0x89d14e30))                 [Type: _KWAIT_BLOCK]
    [+0x000] WaitListEntry    [Type: _LIST_ENTRY]
    [+0x008] Thread           : 0x89d14da8 [Type: _KTHREAD *]
    [+0x00c] Object           : 0x89d93598 [Type: void *]
    [+0x010] NextWaitBlock    : 0x89d14e18 [Type: _KWAIT_BLOCK *]
    [+0x014] WaitKey          : 0x1 [Type: unsigned short]
    [+0x016] WaitType         : 0x0 [Type: unsigned short]
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_KWAIT_BLOCK *)0x89d14e48))
(*((ntdll!_KWAIT_BLOCK *)0x89d14e48))                 [Type: _KWAIT_BLOCK]
    [+0x000] WaitListEntry    [Type: _LIST_ENTRY]
    [+0x008] Thread           : 0x89d14da8 [Type: _KTHREAD *]
    [+0x00c] Object           : 0x0 [Type: void *]
    [+0x010] NextWaitBlock    : 0x0 [Type: _KWAIT_BLOCK *]
    [+0x014] WaitKey          : 0x0 [Type: unsigned short]
    [+0x016] WaitType         : 0x0 [Type: unsigned short]
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_KWAIT_BLOCK *)0x89d14e60))
(*((ntdll!_KWAIT_BLOCK *)0x89d14e60))                 [Type: _KWAIT_BLOCK]
    [+0x000] WaitListEntry    [Type: _LIST_ENTRY]
    [+0x008] Thread           : 0x89d14da8 [Type: _KTHREAD *]
    [+0x00c] Object           : 0x89d14e98 [Type: void *]
    [+0x010] NextWaitBlock    : 0x0 [Type: _KWAIT_BLOCK *]
    [+0x014] WaitKey          : 0x102 [Type: unsigned short]
    [+0x016] WaitType         : 0x1 [Type: unsigned short]

  上面介绍的都是一个线程等待一个或者多个内核对象。如果一个一个线程交错起来,有的被等待对象同时被多个线程等待,就会形成错综复杂的网络,也就是所谓的等待网:

同步篇——事件等待与唤醒-LMLPHP

WaitForSingleObject 分析概要

  对于不同的可等待的内核对象,WaitForSingleObject的处理方式都是不同的。我们把大概的流程说一下,学完可以被等待的内核对象结构体之后,我们将详细分析一下它的具体流程,将会在本篇章的总结与提升中进行讲解。
  WaitForSingleObject经过分析调用流程如下:

graph TD WaitForSingleObject --> WaitForSingleObjectEx -.进入内核.-> NtWaitForSingleObject --> KeWaitForSingleObject
02-12 01:56