同步对象的总结分析

1. 事件(KEV ENT)

内核结构体如下:

nt!_KEVENT
   +0x000 Header           : _DISPATCHER_HEADER  

事件类型:

typdef enum _EVENT_TYPE
{
    NotificationEvent     // 通知事件
    SynchronizationEvent  // 同步事件
} EVENT_TYPE

首先用户在UserMode下通过 CreateEvent()创建一个事件同步对象

其参数如下

HANDLE WINAPI CreateEventW(
    __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性, 其确定返回的句柄是否可被子进程继承
    __in     BOOL bManualReset,         // 指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态
    __in     BOOL bInitialState,        // 指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态
    __in_opt LPCWSTR lpName             // 指定事件的对象的名称
    );

在UserMode下的CreateEventW值得注意的一处判断, 它决定了传到KernelMode的事件类型

我们查看IDA,最终调用的是NtCreateEvent()

其内部实现也比较简单

先判断UserMode传来的参数 EventHandle 是否可写(参照ROS源码)

    /* Check if we were called from user-mode */
    if (PreviousMode != KernelMode)
    {
        /* Enter SEH Block */
        _SEH2_TRY
        {
            /* Check handle pointer */
            ProbeForWriteHandle(EventHandle);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }

随后又以ExEventObjectType为模板, 创建了一个KEVENT对象

    /* Create the Object */
    Status = ObCreateObject(PreviousMode,
                            ExEventObjectType,
                            ObjectAttributes,
                            PreviousMode,
                            NULL,
                            sizeof(KEVENT),
                            0,
                            0,
                            (PVOID*)&Event);

随后初始化该KEVENT结构体

        /* Initialize the Event */
        KeInitializeEvent(Event,
                          EventType,
                          InitialState);
VOID NTAPI KeInitializeEvent(OUT PKEVENT Event,
                  IN EVENT_TYPE Type,
                  IN BOOLEAN State)
{
    /* Initialize the Dispatcher Header */
    Event->Header.Type = Type; // 初始化为事件类型
    Event->Header.Size = sizeof(KEVENT) / sizeof(ULONG); 
    Event->Header.SignalState = State; // 用户传来的参数 bInitialState
    InitializeListHead(&(Event->Header.WaitListHead)); // 初始化事件的WaitListHead
}

最后将事件插入到进程的句柄表中, 并返回事件句柄

...    
    /* Insert it */
    Status = ObInsertObject((PVOID)Event,
                             NULL,
                             DesiredAccess,
                             0,
                             NULL,
                             &hEvent);
...
    *EventHandle = hEvent; // 返回句柄至UserMode

从这里来看该函数只是创建了一个事件对象, 接下来我们来分析手动设置Event的函数ResetEvent()对应 KeSetEvent()

(2)IDA中分析 KeSetEvent()

.text:0040C996 ; LONG __stdcall KeSetEvent(PRKEVENT Event, KPRIORITY Increment, BOOLEAN Wait)
.text:0040C996                 public _KeSetEvent@12
.text:0040C996 _KeSetEvent@12  proc near               ; CODE XREF: ExQueueWorkItem(x,x)+185p
.text:0040C996                                         ; MiInsertPageInFreeList(x)+104p ...
.text:0040C996
.text:0040C996 Event           = dword ptr  8
.text:0040C996 Increment       = dword ptr  0Ch
.text:0040C996 Wait            = byte ptr  10h
.text:0040C996
.text:0040C996 ; FUNCTION CHUNK AT .text:0040D69A SIZE 00000020 BYTES
.text:0040C996 ; FUNCTION CHUNK AT .text:0044034B SIZE 00000011 BYTES
.text:0040C996
.text:0040C996                 mov     edi, edi
.text:0040C998                 push    ebp
.text:0040C999                 mov     ebp, esp
.text:0040C99B                 push    ebx
.text:0040C99C                 push    esi
.text:0040C99D                 push    edi
.text:0040C99E                 call    ds:__imp__KeRaiseIrqlToDpcLevel@0 ; KeRaiseIrqlToDpcLevel()
.text:0040C9A4                 mov     ecx, [ebp+Event]
.text:0040C9A7                 mov     edi, [ecx+_KEVENT.Header.SignalState]
.text:0040C9AA                 mov     bl, al
.text:0040C9AC                 lea     eax, [ecx+_KEVENT.Header.WaitListHead]
.text:0040C9AF                 mov     esi, [eax]      ; ESI = _KEVENT.Header.WaitListHead.Flink
.text:0040C9B1                 cmp     esi, eax        ; 判断列表是否指向了自己
.text:0040C9B3                 jnz     short loc_40C9D7 ; 不是跳转
.text:0040C9B5                 mov     [ecx+_KEVENT.Header.SignalState], 1
.text:0040C9BC
.text:0040C9BC loc_40C9BC:                             ; CODE XREF: KeSetEvent(x,x,x)+4Fj
.text:0040C9BC                                         ; KeSetEvent(x,x,x)+5Cj ...
.text:0040C9BC                 mov     cl, [ebp+Wait]
.text:0040C9BF                 test    cl, cl          ; 判断是否有下一个等待线程
.text:0040C9C1                 jnz     loc_44034B
.text:0040C9C7                 mov     cl, bl
.text:0040C9C9                 call    @KiUnlockDispatcherDatabase@4 ; KiUnlockDispatcherDatabase(x)
.text:0040C9CE
.text:0040C9CE loc_40C9CE:                             ; CODE XREF: KeSetEvent(x,x,x)+339C1j
.text:0040C9CE                 mov     eax, edi
.text:0040C9D0                 pop     edi
.text:0040C9D1                 pop     esi
.text:0040C9D2                 pop     ebx
.text:0040C9D3                 pop     ebp
.text:0040C9D4                 retn    0Ch
.text:0040C9D7 ; ---------------------------------------------------------------------------
.text:0040C9D7
.text:0040C9D7 loc_40C9D7:                             ; CODE XREF: KeSetEvent(x,x,x)+1Dj
.text:0040C9D7                 xor     eax, eax
.text:0040C9D9                 inc     eax             ; EAX = 0x1
.text:0040C9DA                 cmp     byte ptr [ecx], _NotificationEvent ; _DISPATCHER_HEADER.Type与0x0比较, 判断是否为通知事件
.text:0040C9DD                 jnz     loc_40D69A      ; 不是则为同步事件, 跳转
.text:0040C9E3
.text:0040C9E3 loc_40C9E3:                             ; CODE XREF: KeSetEvent(x,x,x)+D08j
.text:0040C9E3                 test    edi, edi        ; _KEVENT.Header.SignalState 是否为 0x1
.text:0040C9E5                 jnz     short loc_40C9BC ; 是, 循环
.text:0040C9E7                 mov     edx, [ebp+Increment]
.text:0040C9EA                 mov     [ecx+_KEVENT.Header.SignalState], eax
.text:0040C9ED                 call    @KiWaitTest@8   ; 唤醒在一个对象上等待的所有线程
.text:0040C9F2                 jmp     short loc_40C9BC
.text:0040C9F2 _KeSetEvent@12  endp

.text:0044034B loc_44034B:                             ; CODE XREF: KeSetEvent(x,x,x)+2Bj
.text:0044034B                 mov     eax, large fs:124h ; EAX = CurrentThead
.text:00440351                 mov     [eax+5Ah], cl   ; WaitNext = CL
.text:00440354                 mov     [eax+58h], bl   ; WaitIrql = BL
.text:00440357                 jmp     loc_40C9CE

.text:0040D69A loc_40D69A:                             ; CODE XREF: KeSetEvent(x,x,x)+47j
.text:0040D69A                 cmp     [esi+_KWAIT_BLOCK.WaitType], ax ; 判断等待类型是否为 WaitAny
.text:0040D69E                 jnz     loc_40C9E3      ; 不是跳转
.text:0040D6A4                 movzx   edx, [esi+_KWAIT_BLOCK.WaitKey]
.text:0040D6A8                 mov     ecx, [esi+_KWAIT_BLOCK.Thread]
.text:0040D6AB                 push    0
.text:0040D6AD                 push    [ebp+Increment]
.text:0040D6B0                 call    @KiUnwaitThread@16 ; // 这个函数使目标线程的等待块脱离各自(对象)的队列, 然后将线程挂入就绪队列
.text:0040D6B5                 jmp     loc_40C9BC
总结:
    从上面可以知道如果是同步事件(SynchronizationEvent), 调用 KeSetEvent() 则只会摘除一个线程的所有等待块(也就是唤醒一个线程)
    如果是通知事件(NotificationEvent), 调用 KeSetEvent() 则会摘除目标对象所有线程的所有等待块(唤醒所有线程, 并将SignalState = 0x1)

再结合ROS源代码分析不难理解

我们通过汇编发现如果是同步事件KeSetEvent()仅做的是摘除一个线程的所有等待块, 并没有将SignalState = 0x1, 这一点值得注意(但是线程却不再等待这代表在某处SignalState = 0x1)

如果为 通知事件(NotificationEvent) , KeSetEvent() 则会摘除目标对象所有线程的所有等待块(唤醒所有线程, 并将SignalState = 0x1), 这个时候所有在等待该事件的线程并不一定会立刻从睡眠中苏醒

我们查看 KeWaitForSingleObject() , 在其函数死循环中

...
    } else if (Objectx->Header.SignalState > 0) {
        // 目标对象为一般可等待对象, 满足了结束等待的条件
        KiWaitSatisfyOther(Objectx);
        WaitStatus = (NTSTATUS)(0);
        goto NoWait; // 结束等待
    }
...
#define KiWaitSatisfyOther(_Object_) {                                       \
    if (((_Object_)->Header.Type & DISPATCHER_OBJECT_TYPE_MASK) ==  EventSynchronizationObject) { \
        (_Object_)->Header.SignalState = 0;  // 如果是同步事件将 SignalState 清0x0    \
                                                                             \
    } else if ((_Object_)->Header.Type == SemaphoreObject) {                 \
        (_Object_)->Header.SignalState -= 1; // 如果是信号对象, 递减           \
                                                                             \
    }                                                                        \
}
//  对于"通知型"事件不做任何处理, 因为通知型事件非 0x1 即 0x0
// 而 SemaphoreObject 可以是正整数、0x0、负整数
// SignalState 为0x1表示所等待的事件已经发生了, 反之为0x0则表示所要等待的条件尚未满足, 所以需要睡眠等待

可以知道一旦事件有了信号线程直接退出了死循环

 

2.信号量(Semaphore)

内核结构体

nt!_KSEMAPHORE
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 Limit            : Int4B // 信号量的最大上限

用户模式下是通过 CreateSemaphoreW()创建信号量的

HANDLE  WINAPI  CreateSemaphoreW( 
 __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  // 安全描述符
 __in LONG lInitialCount,     // 设置信号量的初始计数(也就是有多少个等待信号量线程可以苏醒)
 __in LONG lMaximumCount,     // 设置信号量的最大计数
 __in_opt LPCWSTR lpName      // 信号量的名称
 );

最终调用的是NtCreateSemaphore()

KernelMode下信号量的创建与事件步骤大体一致, 值得注意的是 KeInitializeSemaphore()

VOID NTAPI KeInitializeSemaphore(IN PKSEMAPHORE Semaphore,
                      IN LONG Count,
                      IN LONG Limit)
{
    /* Simply Initialize the Header */
    初始化 _KSEMAPHORE 结构体
    Semaphore->Header.Type = SemaphoreObject;
    Semaphore->Header.Size = sizeof(KSEMAPHORE) / sizeof(ULONG);
    Semaphore->Header.SignalState = Count; // 初始化为User空间传来的参数 InitialCount
    InitializeListHead(&(Semaphore->Header.WaitListHead));

    /* Set the Limit */
    Semaphore->Limit = Limit; // 初始化为User空间传来的参数 MaximumCount
}

随后我们来看信号量的唤醒函数 NtReleaseSemaphore() 

ROS源代码

NtReleaseSemaphore() 只是进行了参数效验和返回本次操作前的信号量数,最终调用了 KeReleaseSemaphore()

...
    /* Release the semaphore */
    // 释放所占有的信号量
    LONG PrevCount = KeReleaseSemaphore(Semaphore,
                                        IO_NO_INCREMENT, (0x0) // 表示暂时不要提升唤醒线程的调度优先级
                                        ReleaseCount, // 从UserMode下传来的信号量的增值
                                        FALSE);
...

其首先保存之前的信号量的值, 随后算出增加后的信号量的值并检验值是否正确

LONG NTAPI KeReleaseSemaphore(IN PKSEMAPHORE Semaphore,
                   IN KPRIORITY Increment,
                   IN LONG Adjustment,
                   IN BOOLEAN Wait)
{
...
    /* Lock the Dispatcher Database */
    OldIrql = KiAcquireDispatcherLock(); // 提升IRQL

    /* Save the Old State and get new one */
    InitialState = Semaphore->Header.SignalState; // 本次操作前的信号量数值
    State = InitialState + Adjustment;            // 加上本次的增量
    
        /* Check if the Limit was exceeded */
    if ((Semaphore->Limit < State) || (InitialState > State))
    {
        /* Raise an error if it was exceeded */
        // 参数不合理
        KiReleaseDispatcherLock(OldIrql);
        ExRaiseStatus(STATUS_SEMAPHORE_LIMIT_EXCEEDED);
    }

之后修改信号量

...
    /* Now set the new state */
    Semaphore->Header.SignalState = State; // 修正信号量
...

随后判断之前信号量的值是否为0x0

...
    /* Check if we should wake it */
    if (!(InitialState) && !(IsListEmpty(&Semaphore->Header.WaitListHead)))
    {
        /* Wake the Semaphore */
        // 本次操作前的信号数值为 0x0, 且有线程在本信号量上睡眠
        KiWaitTest(&Semaphore->Header, Increment); // 唤醒所有等待该信号量的线程
    }
...

最后函数结束并返回之前信号量的值

综上可知信号量的SignalState是决定有多少线程能脱离睡眠的关键, 从上面可以看出 SignalState 初始化是由 InitialCount 决定的

Semaphore->Header.SignalState = Count; // 初始化为User空间传来的参数 InitialCount

在 KeWaitForSingleObject()中

#define KiWaitSatisfyOther(_Object_) {                                       \
    if (((_Object_)->Header.Type & DISPATCHER_OBJECT_TYPE_MASK) ==  EventSynchronizationObject) { \
        (_Object_)->Header.SignalState = 0;  // 如果是同步事件将 SignalState 清0x0    \
                                                                             \
    } else if ((_Object_)->Header.Type == SemaphoreObject) {                 \
        (_Object_)->Header.SignalState -= 1; // 如果是信号对象, 递减           \
                                                                             \
    }                                                                        \
}

每有一次等待信号对象的苏醒, SignalState都会递减, 直到为0x0

而在KeReleaseSemaphore()中如果 SignalState 为 0x0, 则将唤醒所有在等待信号量的对象; 然而并没,并没什么作用直到 SignalState 不为0x0, 这些等待线程才有退出去的活路

    // 本次操作前的信号数值为 0x0, 且有线程在本信号量上睡眠
    KiWaitTest(&Semaphore->Header, Increment); // 唤醒所有等待该信号量的线程

 

3.互斥体(Mutant/Mutex)

互斥体Mutant 向User开发, 而Mutex 只对Kernel开发
互斥体的原理就是把信号量的最大值和初始值都设置为0x1, 那么只能有一个线程进入临界区
内核结构体:
nt!_KMUTANT
   +0x000 Header           : _DISPATCHER_HEADER // 同步对象头
   +0x010 MutantListEntry  : _LIST_ENTRY        // 互斥体队列
   +0x018 OwnerThread      : Ptr32 _KTHREAD     // 当前拥互斥体拥有者线程
   +0x01c Abandoned        : UChar              // 该互斥体是否被丢弃
   +0x01d ApcDisable       : UChar              // 通过该互斥体进入临界区是否需要关闭 APC
UserMode下的互斥体创建函数为 CreateMutex()
HANDLE CreateMutex(
    LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针
    BOOL bInitialOwner, // 初始化互斥对象的所有者, 为TRUE则当前线程即为互斥体的初始拥有者
    LPCTSTR lpName // 指向互斥对象名的指针
);
KernelMode下是NtCreateMutant(),该函数初始化步骤与事件一致,不过初始化函数 KeInitializeMutant()  值得注意, ROS代码如下:
VOID NTAPI KeInitializeMutant(IN PKMUTANT Mutant/*创建的互斥体对象*/,
                   IN BOOLEAN InitialOwner/*User参数 bInitialOwner*/)
{
    PKTHREAD CurrentThread;
    KIRQL OldIrql;

    /* Check if we have an initial owner */
    if (InitialOwner) // InitialOwner表示当前线程是否要成为互斥体的拥有者
    {
        /* We also need to associate a thread */
        CurrentThread = KeGetCurrentThread();
        Mutant->OwnerThread = CurrentThread;

        /* We're about to touch the Thread, so lock the Dispatcher */
        OldIrql = KiAcquireDispatcherLock();

        /* And insert it into its list */
        InsertTailList(&CurrentThread->MutantListHead,
                       &Mutant->MutantListEntry); // 将当前互斥体插入到线程互斥体队列中

        /* Release Dispatcher Lock */
        KiReleaseDispatcherLock(OldIrql);
    }
    else
    {
        /* In this case, we don't have an owner yet */
        Mutant->OwnerThread = NULL;
    }

    /* Now we set up the Dispatcher Header */
    Mutant->Header.Type = MutantObject;
    Mutant->Header.Size = sizeof(KMUTANT) / sizeof(ULONG);
    Mutant->Header.SignalState = InitialOwner ? 0 : 1; // InitialOwner为TRUE, 则SignalState为FALSE, 表示别的线程无法进入该互斥体
    InitializeListHead(&(Mutant->Header.WaitListHead));

    /* Initialize the default data */
    Mutant->Abandoned = FALSE;
    Mutant->ApcDisable = 0; // 对于Mutant为0x0, 对于Mutex为0x1
}
可以看的处User参数bInitialOwner对互斥体的初始化影响很大
之后我们查看互斥体的唤醒函数 NtReleaseMutant()
该函数只是对参数进行了校验和根据句柄找到并引用互斥体对象, 最终调用的是 KeReleaseMutant()
...
    /* Release the mutant */
    LONG Prev = KeReleaseMutant(Mutant,
                                MUTANT_INCREMENT, // 为0x1
                                FALSE,
                                FALSE);

    /* Return the previous count if requested */
    if (PreviousCount) *PreviousCount = Prev;
...

KeReleaseMutant()先是记录了之前的SignalState 

LONG NTAPI KeReleaseMutant(IN PKMUTANT Mutant,
                IN KPRIORITY Increment,
                IN BOOLEAN Abandon,
                IN BOOLEAN Wait)
{
    KIRQL OldIrql;
    LONG PreviousState;
    PKTHREAD CurrentThread = KeGetCurrentThread();
    BOOLEAN EnableApc = FALSE;
    ASSERT_MUTANT(Mutant);
    ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);

    /* Lock the Dispatcher Database */
    OldIrql = KiAcquireDispatcherLock();

    /* Save the Previous State */
    PreviousState = Mutant->Header.SignalState; // 本次操作前的SignalState状态

 随后判断进入当前函数的线程是不是当前互斥体的拥有者

    /* Check if it is to be abandonned */
    if (Abandon == FALSE) // 该互斥体是否被丢弃
    {
        /* Make sure that the Owner Thread is the current Thread */
        if (Mutant->OwnerThread != CurrentThread)
        {
            // 当前线程并非互斥体的拥有者, 报错
            /* Release the lock */
            KiReleaseDispatcherLock(OldIrql);
            /* Raise an exception */
            ExRaiseStatus(Mutant->Abandoned ? STATUS_ABANDONED :
                                              STATUS_MUTANT_NOT_OWNED);
        }
        /* If the thread owns it, then increase the signal state */
        // 如果为互斥体拥有者线程, 则增加信号状态, 但线程不一定退出睡眠状态
        Mutant->Header.SignalState++;
    }
    else
    {
        /* It's going to be abandonned */
        // 本互斥体将被废弃
        Mutant->Header.SignalState = 1;
        Mutant->Abandoned = TRUE;
    }

随后判断互斥体是否有信号

   /* Check if the signal state is only single */
   if (Mutant->Header.SignalState == 1)
    {
        // 拥有者线程已经退出了这个互斥体
        /* Check if it's below 0 now */
        if (PreviousState <= 0)
        {
            /* Remove the mutant from the list */
            // 互斥体与当前拥有者线程脱钩
            RemoveEntryList(&Mutant->MutantListEntry);

            /* Save if we need to re-enable APCs */
            EnableApc = Mutant->ApcDisable;
        }
        /* Remove the Owning Thread and wake it */
        Mutant->OwnerThread = NULL; // 清空互斥体拥有者线程
        /* Check if the Wait List isn't empty */
        if (!IsListEmpty(&Mutant->Header.WaitListHead))
        {
            // 如果有别的线程在等待互斥体
            /* Wake the Mutant */
            KiWaitTest(&Mutant->Header, Increment); // 唤醒等待中的线程
        }
    }

从上面我们可以看出一旦互斥体拥有者线程退出互斥体, 其立马会判断有没有别的线程在等待互斥体, 有则唤醒等待中的线程

再来到我们的 KeWaitForSingleObject()函数

...    
    if (Objectx->Header.Type == MutantObject) {
        // 目标为互斥门特殊处理
        if ((Objectx->Header.SignalState > 0) ||
            (Thread == Objectx->OwnerThread)) { // 互斥体有信号或者是互斥体拥有者线程
            if (Objectx->Header.SignalState != MINLONG) { // MINLONG == 0x80000000
                KiWaitSatisfyMutant(Objectx, Thread);
                WaitStatus = (NTSTATUS)(Thread->WaitStatus);
                goto NoWait; // 结束等待
            } else {
                KiUnlockDispatcherDatabase(Thread->WaitIrql);
                ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
            }
        }
...
#define KiWaitSatisfyMutant(_Object_, _Thread_) {                            \
    (_Object_)->Header.SignalState -= 1; // 互斥体信号减一                                    \
    if ((_Object_)->Header.SignalState == 0) {                               \
        (_Thread_)->KernelApcDisable = (_Thread_)->KernelApcDisable -  (_Object_)->ApcDisable; // 修改线程的APC关闭开关 \
        (_Object_)->OwnerThread = (_Thread_); // 修改互斥体拥有者线程                               \
        if ((_Object_)->Abandoned == TRUE) {  // 如果当前互斥体是被废弃的                               \
            (_Object_)->Abandoned = FALSE;    // 启用互斥体                               \
            (_Thread_)->WaitStatus = STATUS_ABANDONED;                       \
        }                                                                    \
                                                                             \
        InsertHeadList((_Thread_)->MutantListHead.Blink,  // 将线程加入到互斥体队列中                   \
                       &(_Object_)->MutantListEntry);                        \
    }                                                                        \
}

可以看到一旦KeReleaseMutant()导致互斥体变为有信号状态

    // 如果为互斥体拥有者线程, 则增加信号状态, 但线程不一定退出睡眠状态
    Mutant->Header.SignalState++;

则互斥体拥有者线程退出死循环, 且互斥体会给下一个等待互斥体的线程

    // 如果有别的线程在等待互斥体
    /* Wake the Mutant */
    KiWaitTest(&Mutant->Header, Increment); // 唤醒等待中的线程

那么当一个互斥体通用者线程意外死亡怎么办?别的等待互斥体线程总不可能一直等待吧?

关于这一点我们可以查看线程终止函数 PspTerminateThreadByPointer(), 其最终调用的函数是

    /* Directly terminate the thread */
    PspExitThread(ExitStatus);

在 PspExitThread()函数中我们通过分析发现, 这个函数在进行一系列的收尾处理中, 在这其中调用了一个 KeRundownThread(), 来处理互斥体相关的信息

    /* Rundown Mutexes */
    KeRundownThread();

ROS源代码如下:

VOID NTAPI KeRundownThread(VOID)
{
    KIRQL OldIrql;
    PKTHREAD Thread = KeGetCurrentThread();
    PLIST_ENTRY NextEntry, ListHead;
    PKMUTANT Mutant;
    ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);

    /* Optimized path if nothing is on the list at the moment */
    if (IsListEmpty(&Thread->MutantListHead)) return;

    /* Lock the Dispatcher Database */
    OldIrql = KiAcquireDispatcherLock();

    /* Get the List Pointers */
    ListHead = &Thread->MutantListHead;
    NextEntry = ListHead->Flink;
    while (NextEntry != ListHead)
    {
        /* Get the Mutant */
        Mutant = CONTAINING_RECORD(NextEntry, KMUTANT, MutantListEntry); // 从线程的中互斥体队列中获取互斥体

        /* Make sure it's not terminating with APCs off */
        if (Mutant->ApcDisable)
        {
            // 如果该互斥体已经是被废弃的直接蓝屏
            /* Bugcheck the system */
            KeBugCheckEx(THREAD_TERMINATE_HELD_MUTEX,
                         (ULONG_PTR)Thread,
                         (ULONG_PTR)Mutant,
                         0,
                         0);
        }

        /* Now we can remove it */
        RemoveEntryList(&Mutant->MutantListEntry); // 将当前互斥体移除互斥体链表

        /* Unconditionally abandon it */
        Mutant->Header.SignalState = 1; // 互斥体变为有信号
        Mutant->Abandoned = TRUE;       // 当前互斥体标志位废弃
        Mutant->OwnerThread = NULL;     // 清空当前拥有者线程

        /* Check if the Wait List isn't empty */
        if (!IsListEmpty(&Mutant->Header.WaitListHead)) // 判断是否还有等待互斥体的线程
        {
            /* Wake the Mutant */
            KiWaitTest(&Mutant->Header, MUTANT_INCREMENT); // 有线程则全部唤醒
        }

        /* Move on */
        NextEntry = NextEntry->Flink; // 下一个互斥体
    }

    /* Release the Lock */
    KiReleaseDispatcherLock(OldIrql);
}

从上面的代码可以看出,其将互斥体设置为废弃状态和互斥体变为有信号以及清空当前拥有者线程, 并唤醒了说有在等待互斥体的线程

我们看 KeWaitForSingleObject()的代码, 一旦互斥体变为有信号, 将导致其中一条等待互斥体的线程退出休眠运行结束,随后蝴蝶效应导致第二条等待互斥体线程也退出休眠运行结束, 直至等待此互斥体的线程为0x0

所以最后我们得出结论在线程退出时会对互斥体进行处理,使得下一个等待互斥体的线程退出睡眠状态

 

我们测试以下结论, 上UserMode的程序代码

#include <iostream>
#include <windows.h>

HANDLE hMutant;
HANDLE hSemaphore;
HANDLE hEvent;
HANDLE hThread0;
HANDLE hThread1;
HANDLE hThread2;

DWORD WINAPI ThreadRoutine0(LPVOID lpThreadParameter)
{
	hMutant = CreateMutexW(NULL, TRUE , L"11LG");
	Sleep(3000);
	printf("ThreadRoutine0_End\n");
	return 0x0;
}

DWORD WINAPI ThreadRoutine1(LPVOID lpThreadParameter)
{
	WaitForSingleObject(hMutant, -1);
	printf("ThreadRoutine1_End\n");
	while (1);
	return 0x0;
}

DWORD WINAPI ThreadRoutine2(LPVOID lpThreadParameter)
{
	WaitForSingleObject(hMutant, -1);
	printf("ThreadRoutine2_End\n");
	while (1);
	return 0x0;
}

int main()
{
	hThread0 = CreateThread(NULL, 0, ThreadRoutine0, NULL, NULL, 0);
	Sleep(1000);
	hThread1 = CreateThread(NULL, 0, ThreadRoutine1, NULL, NULL, 0);
	hThread2 = CreateThread(NULL, 0, ThreadRoutine2, NULL, NULL, 0);
	
	WaitForSingleObject(hThread0, -1);
	WaitForSingleObject(hThread1, -1);
	WaitForSingleObject(hThread2, -1);

	printf("main_End\n");

	system("pause");
}

原创文章,转载请注明: 转载自Windows内核安全驱动编程

本文链接地址: 同步对象的总结分析

发表评论

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