(75)内核APC执行过程,分析 KiDeliverApc 函数

一、内核APC执行过程

通过分析 SwapContext ,KiSwapContexgt , KiSwapThread ,我们得出一个结论:切换线程后会执行内核APC,调用的函数是 KiDeliverApc 。

内核APC和用户APC都要由 KiDeliverApc 函数调用,KiDeliverApc 函数首先处理内核APC,然后根据 PreviousMode 参数,用户APC队列是否有数据来判断是否需要处理用户APC。

内核APC的执行比较简单(相对用户APC而言),它是直接在 KiDeliverApc 函数内调用内核APC函数的。

// 调用内核APC函数
(NormalRoutine)(NormalContext,
                SystemArgument1,
                SystemArgument2);

二、KiDeliverApc 源码(apcsup.c)

内核APC执行的细节都能在这个函数内分析出来,中文注释是我写的,但即使是看源码,也有不少细节我没分析清楚,也难免会有错误,欢迎读者留言指正。

用户APC的执行我会另写一篇博客介绍。

VOID
KiDeliverApc (
    IN KPROCESSOR_MODE PreviousMode,
    IN PKEXCEPTION_FRAME ExceptionFrame,
    IN PKTRAP_FRAME TrapFrame
    )

/*++

Routine Description:

    This function is called from the APC interrupt code and when one or
    more of the APC pending flags are set at system exit and the previous
    IRQL is zero. All special kernel APC's are delivered first, followed
    by normal kernel APC's if one is not already in progress, and finally
    if the user APC queue is not empty, the user APC pending flag is set,
    and the previous mode is user, then a user APC is delivered. On entry
    to this routine IRQL is set to APC_LEVEL.

    N.B. The exception frame and trap frame addresses are only guaranteed
         to be valid if, and only if, the previous mode is user.

Arguments:

    PreviousMode - Supplies the previous processor mode.

    ExceptionFrame - Supplies a pointer to an exception frame.

    TrapFrame - Supplies a pointer to a trap frame.

Return Value:

    None.

--*/

{

    PKAPC Apc;
    PKKERNEL_ROUTINE KernelRoutine;
    KLOCK_QUEUE_HANDLE LockHandle;
    PLIST_ENTRY NextEntry;
    ULONG64 NewPC;
    PVOID NormalContext;
    PKNORMAL_ROUTINE NormalRoutine;
    ULONG64 PC; 
    PKPROCESS Process;
    PVOID SystemArgument1;
    PVOID SystemArgument2;
    PKTHREAD Thread;
    PKTRAP_FRAME OldTrapFrame;

    //
    // If the thread was interrupted in the middle of the SLIST pop code,
    // then back up the PC to the start of the SLIST pop. 
    //

    if (TrapFrame != NULL) {

#if defined(_AMD64_)

        if ((TrapFrame->Rip >= (ULONG64)&ExpInterlockedPopEntrySListResume) &&
            (TrapFrame->Rip <= (ULONG64)&ExpInterlockedPopEntrySListEnd)) {

            TrapFrame->Rip = (ULONG64)&ExpInterlockedPopEntrySListResume;
        }

#elif defined(_IA64_)

        //
        // Add the slot number so we do the right thing for the instruction
        // group containing the interlocked compare exchange.
        //

        PC = TrapFrame->StIIP + ((TrapFrame->StIPSR & IPSR_RI_MASK) >> PSR_RI);
        NewPC = (ULONG64)((PPLABEL_DESCRIPTOR)ExpInterlockedPopEntrySListResume)->EntryPoint;
        if ((PC >= NewPC) &&
            (PC <= (ULONG64)((PPLABEL_DESCRIPTOR)ExpInterlockedPopEntrySListEnd)->EntryPoint)) {

            TrapFrame->StIIP = NewPC;
            TrapFrame->StIPSR &= ~IPSR_RI_MASK;
        }

#elif defined(_X86_)

        if ((TrapFrame->Eip >= (ULONG)&ExpInterlockedPopEntrySListResume) &&
            (TrapFrame->Eip <= (ULONG)&ExpInterlockedPopEntrySListEnd)) {

            TrapFrame->Eip = (ULONG)&ExpInterlockedPopEntrySListResume;
        }

#else
#error "No Target Architecture"
#endif

    }

    //
    // Raise IRQL to dispatcher level and lock the APC queue.
    //

	// 获取当前线程
    Thread = KeGetCurrentThread();

    OldTrapFrame = Thread->TrapFrame;

    Thread->TrapFrame = TrapFrame;

	// 获取当前进程(提供CR3的进程)
    Process = Thread->ApcState.Process;

    KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);

    //
    // Get address of current thread object, clear kernel APC pending, and
    // check if any kernel mode APC's can be delivered.
    // 

	// 接下来要执行内核APC,这里提前声明处理完毕
    Thread->ApcState.KernelApcPending = FALSE;

	// 遍历内核APC队列
    while (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {
		// 获取 APC,获取 APC 的成员
        NextEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
        Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
        KernelRoutine = Apc->KernelRoutine;
        NormalRoutine = Apc->NormalRoutine;
        NormalContext = Apc->NormalContext;
        SystemArgument1 = Apc->SystemArgument1;
        SystemArgument2 = Apc->SystemArgument2;

        if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) 
		{
			// NormalRoutine 等于 NULL 的情况属于特殊内核APC,我不知道什么时候会插入这样的APC
			// 所以这里就不分析了,假如您读到这里,又知道相关的信息,不妨留言提示我一下^_^
			// 2020年11月29日21:04:21
            //
            // First entry in the kernel APC queue is a special kernel APC.
            // Remove the entry from the APC queue, set its inserted state
            // to FALSE, release dispatcher database lock, and call the kernel
            // routine. On return raise IRQL to dispatcher level and lock
            // dispatcher database lock.
            // 
            RemoveEntryList(NextEntry);

            Apc->Inserted = FALSE;

            KeReleaseInStackQueuedSpinLock(&LockHandle);

            (KernelRoutine)(Apc,
                            &NormalRoutine,
                            &NormalContext,
                            &SystemArgument1,
                            &SystemArgument2);

#if DBG
			// 蓝屏警告
            if (KeGetCurrentIrql() != LockHandle.OldIrql) {
                KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
                             KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8,
                             (ULONG_PTR)KernelRoutine,
                             (ULONG_PTR)Apc,
                             (ULONG_PTR)NormalRoutine);
            }

#endif

            KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);

        }
		else
		{
			// 走这个分支说明 NormalRoutine 非空,是普通的内核APC,PspTerminateThreadByPointer 和 NtQueueApcThread 都走这里
            //
            // First entry in the kernel APC queue is a normal kernel APC.
            // If there is not a normal kernel APC in progress and kernel
            // APC's are not disabled, then remove the entry from the APC
            // queue, set its inserted state to FALSE, release the APC queue
            // lock, call the specified kernel routine, set kernel APC in
            // progress, lower the IRQL to zero, and call the normal kernel
            // APC routine. On return raise IRQL to dispatcher level, lock
            // the APC queue, and clear kernel APC in progress.
            //

            if ((Thread->ApcState.KernelApcInProgress == FALSE) &&	// 没有内核APC正在执行 并且
               (Thread->KernelApcDisable == 0)) 					// 没有禁用内核APC
			{
			   // 从内核 APC 队列中移除这个 APC
                RemoveEntryList(NextEntry);

				// APC Inserted 标志清零
                Apc->Inserted = FALSE;

                KeReleaseInStackQueuedSpinLock(&LockHandle);

				// 调用 KernelRoutine,举两个例子说明
				// 如果 APC 通过PspTerminateThreadByPointer 构造, KernelRoutine 是 PsExitSpecialApc ,那么执行的操作就是释放APC内存,并终止当前线程
				// 如果 APC 通过 NtQueueApcThread 构造,KernelRoutine 是 PspQueueApcSpecialApc ,执行的操作仅仅是释放APC内存
				// 不过 NtQueueApcThread 插入的属于用户APC,不走这里,而是等内核APC执行完后再执行
				// 
				// KernelRoutine 的工作是释放APC内存,也可能包括一些额外的工作,如退出、挂起、恢复线程
				// KernelRoutine 是调用 KeInitializeApc 时决定的,是不确定的,各种函数对参数的使用情况都不一样
				// 例如 PspTerminateThreadByPointer 初始化 KernelRoutine 传的函数是 PsExitSpecialApc ,就只使用了第一个参数 Apc
                (KernelRoutine)(Apc,
                                &NormalRoutine,
                                &NormalContext,
                                &SystemArgument1,
                                &SystemArgument2);

#if DBG

                if (KeGetCurrentIrql() != LockHandle.OldIrql) {
                    KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
                                 KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8 | 1,
                                 (ULONG_PTR)KernelRoutine,
                                 (ULONG_PTR)Apc,
                                 (ULONG_PTR)NormalRoutine);
                }

#endif

				// NormalRoutine 是内核APC函数,经分析,我觉得能执行到这里,NormalRoutine 应该不是 NULL 的
				// 唯一可能修改 NormalRoutine 的就是上面调用的 KernelRoutine 函数
                if (NormalRoutine != (PKNORMAL_ROUTINE)NULL) {

					// 内核APC正在执行
                    Thread->ApcState.KernelApcInProgress = TRUE;

					// 降低IRQL到0
                    KeLowerIrql(0);

					// 调用内核APC函数
                    (NormalRoutine)(NormalContext,
                                    SystemArgument1,
                                    SystemArgument2);

					// 恢复IRQL到APC_LEVEL(1)
                    KeRaiseIrql(APC_LEVEL, &LockHandle.OldIrql);
                }

                KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);

				// 没有内核APC正在执行
                Thread->ApcState.KernelApcInProgress = FALSE;

            } else {
                KeReleaseInStackQueuedSpinLock(&LockHandle);
                goto CheckProcess;
            }
        }
    }

    //
    // Kernel APC queue is empty. If the previous mode is user, user APC
    // pending is set, and the user APC queue is not empty, then remove
    // the first entry from the user APC queue, set its inserted state to
    // FALSE, clear user APC pending, release the dispatcher database lock,
    // and call the specified kernel routine. If the normal routine address
    // is not NULL on return from the kernel routine, then initialize the
    // user mode APC context and return. Otherwise, check to determine if
    // another user mode APC can be processed.
    //
	// 内核APC执行完毕
	// 如果 PreviousMode 是用户模式(1),并且有用户APC,并且用户APC队列非空

    if ((IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) &&
       (PreviousMode == UserMode) && 
	   (Thread->ApcState.UserApcPending != FALSE)) 
	{
		// 提前声明用户APC队列已清空
        Thread->ApcState.UserApcPending = FALSE;

		// 获取APC和其属性
        NextEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
        Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
        KernelRoutine = Apc->KernelRoutine;
        NormalRoutine = Apc->NormalRoutine;
        NormalContext = Apc->NormalContext;
        SystemArgument1 = Apc->SystemArgument1;
        SystemArgument2 = Apc->SystemArgument2;

		// 从用户APC队列中取出
        RemoveEntryList(NextEntry);

		// 标记插入状态为FALSE
        Apc->Inserted = FALSE;

        KeReleaseInStackQueuedSpinLock(&LockHandle);

		// KernelRoutine 应该就是 PspQueueApcSpecialApc 
		// 因为用户APC是 NtQueueApcThread 函数构造和插入的,它就是这样初始化APC的
		// PspQueueApcSpecialApc 的唯一作用是释放APC内存
        (KernelRoutine)(Apc,
                        &NormalRoutine,
                        &NormalContext,
                        &SystemArgument1,
                        &SystemArgument2);

        if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {
			// 此函数定义在 thredobj.c			
            KeTestAlertThread(UserMode);

        } else {
			// 准备回3环调用 NormalContext
            KiInitializeUserApc(ExceptionFrame,
                                TrapFrame,
                                NormalRoutine,		// 用户APC总入口 BaseDispatchAPC(3环函数)
                                NormalContext,		// 3环APC函数
                                SystemArgument1,	// 3环APC函数的参数
                                SystemArgument2);	// 作用不明,BaseDispatchAPC 里用到了
        }

    } else {
        KeReleaseInStackQueuedSpinLock(&LockHandle);
    }

    //
    // Check if process was attached during the APC routine.
    // 检查当前进程是否发生变化(执行 APC 函数时发生了 attach)

CheckProcess:
    if (Thread->ApcState.Process != Process) {
		// 蓝屏警告
        KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
                     (ULONG_PTR)Process,
                     (ULONG_PTR)Thread->ApcState.Process,
                     (ULONG)Thread->ApcStateIndex,
                     (ULONG)KeIsExecutingDpc());
    }

    Thread->TrapFrame = OldTrapFrame;
    return;
}

三、总结

  1. 内核APC在线程切换的时候就会执行,这也就意味着,只要插入内核APC
    很快就会执行。

  2. 在执行用户APC之前会先执行内核APC。

  3. 内核APC在内核空间执行,不需要换栈,一个循环全部执行完毕。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页