插入APC

一、插入APC:分析KeInsertQueueApc函数

下面是KeInsertQueueApc函数的反汇编代码

bash 复制代码
.text:004B6816
.text:004B6816 ; __stdcall KeInsertQueueApc(x, x, x, x)
.text:004B6816                 public _KeInsertQueueApc@16
.text:004B6816 _KeInsertQueueApc@16 proc near          ; CODE XREF: ExpTimerDpcRoutine(x,x,x,x)+6A↑p
.text:004B6816                                         ; IopfCompleteRequest(x,x)+3F1↑p ...
.text:004B6816
.text:004B6816 var_D           = byte ptr -0Dh
.text:004B6816 spinCount       = dword ptr -0Ch
.text:004B6816 OldIrql         = byte ptr -8
.text:004B6816 PKPRCB          = dword ptr -4
.text:004B6816 Apc             = dword ptr  8
.text:004B6816 SystemArgument1 = dword ptr  0Ch
.text:004B6816 SystemArgument2 = dword ptr  10h
.text:004B6816 Increment       = dword ptr  14h
.text:004B6816
.text:004B6816                 mov     edi, edi
.text:004B6818                 push    ebp
.text:004B6819                 mov     ebp, esp
.text:004B681B                 and     esp, 0FFFFFFF8h
.text:004B681E                 sub     esp, 14h
.text:004B6821                 push    ebx
.text:004B6822                 push    esi
.text:004B6823                 mov     esi, [ebp+Apc]  ; esi = PKAPC
.text:004B6826                 push    edi
.text:004B6827                 mov     edi, [esi+_KAPC.Thread] ; edi = Apc->Thread
.text:004B682A                 call    ds:__imp__KeRaiseIrqlToDpcLevel@0 ; IRQL提升到DPC
.text:004B6830                 and     [esp+20h+spinCount], 0 ; 初始化自旋锁计数
.text:004B6835                 mov     [esp+20h+OldIrql], al ; 保存老的IRQL
.text:004B6839                 mov     eax, large fs:_KPCR.Prcb ; eax=当前逻辑cpu的KPRCB
.text:004B683F                 mov     [esp+20h+PKPRCB], eax
.text:004B6843                 lea     ebx, [edi+_KTHREAD.ApcQueueLock] ; 定位线程 APC 队列锁
.text:004B6843                                         ; ebx = &Thread->ApcQueueLock
.text:004B6846                 jmp     short loc_4B6871 ; 从 0x004B6846 到 0x004B687A
.text:004B6846                                         ; 整一段逻辑只做了一件事:
.text:004B6846                                         ; 获取 KTHREAD.ApcQueueLock 的自旋锁。
.text:004B6846                                         ; 没有任何 APC 语义、状态判断或插入逻辑。
.text:004B6848 ; ---------------------------------------------------------------------------
.text:004B6848
.text:004B6848 loc_4B6848:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+59↓j
.text:004B6848                                         ; KeInsertQueueApc(x,x,x,x)+64↓j
.text:004B6848                 inc     [esp+20h+spinCount] ; 自旋次数++
.text:004B684C                 mov     eax, [esp+20h+spinCount]
.text:004B6850                 test    ds:_HvlLongSpinCountMask, eax
.text:004B6856                 jnz     short loc_4B6869
.text:004B6858                 test    byte ptr ds:_HvlEnlightenments, 40h
.text:004B685F                 jz      short loc_4B6869
.text:004B6861                 push    eax
.text:004B6862                 call    _HvlNotifyLongSpinWait@4 ; HvlNotifyLongSpinWait(x)
.text:004B6867                 jmp     short loc_4B686B
.text:004B6869 ; ---------------------------------------------------------------------------
.text:004B6869
.text:004B6869 loc_4B6869:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+40↑j
.text:004B6869                                         ; KeInsertQueueApc(x,x,x,x)+49↑j
.text:004B6869                 pause
.text:004B686B
.text:004B686B loc_4B686B:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+51↑j
.text:004B686B                 mov     eax, [ebx]
.text:004B686D                 test    eax, eax        ; 检查锁是否仍被占用
.text:004B686D                                         ; 如果锁仍为非 0 → 继续自旋
.text:004B686F                 jnz     short loc_4B6848
.text:004B6871
.text:004B6871 loc_4B6871:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+30↑j
.text:004B6871                 xor     eax, eax
.text:004B6873                 mov     ecx, ebx
.text:004B6875                 inc     eax
.text:004B6876                 xchg    eax, [ecx]
.text:004B6878                 test    eax, eax
.text:004B687A                 jnz     short loc_4B6848
.text:004B687C                 mov     eax, [edi+0B8h] ; eax = Thread->ThreadFlags
.text:004B6882                 test    al, 20h         ; 检查线程是否允许 APC 入队(ApcQueueable)
.text:004B6882                                         ; 测试 bit5(ApcQueueable)
.text:004B6884                 jz      short loc_4B68B2 ; 如果ApcQueueable为0
.text:004B6884                                         ; 返回插入失败
.text:004B6886                 cmp     [esi+_KAPC.Inserted], 1 ; _KAPC.Inserted=1说明已经插入过了
.text:004B6886                                         ; 返回插入失败
.text:004B688A                 jz      short loc_4B68B2
.text:004B688C                 mov     eax, [ebp+SystemArgument1]
.text:004B688F                 push    dword ptr [esp+20h+OldIrql] ; 作为参数 传给 KiInsertQueueApc
.text:004B6893                 mov     ecx, [esp+24h+PKPRCB] ; ecx = PKPRCB,也是参数
.text:004B6897                 mov     [esi+_KAPC.SystemArgument1], eax ; 填充KAPC结构
.text:004B6897                                         ; _KAPC.SystemArgument1=SystemArgument1
.text:004B689A                 mov     eax, [ebp+SystemArgument2]
.text:004B689D                 mov     edx, esi        ; edx = PKAPC
.text:004B689F                 mov     byte ptr [esi+2Eh], 1 ; 把插入标记位置1
.text:004B689F                                         ; Apc->Inserted = TRUE
.text:004B68A3                 mov     [esi+_KAPC.SystemArgument2], eax ; 填充KAPC结构
.text:004B68A3                                         ; _KAPC.SystemArgument2=SystemArgument2
.text:004B68A6                 call    @KiInsertQueueApc@12 ; KiInsertQueueApc(x,x,x)
.text:004B68AB                 mov     [esp+20h+var_D], 1
.text:004B68B0                 jmp     short loc_4B68B7
.text:004B68B2 ; ---------------------------------------------------------------------------
.text:004B68B2
.text:004B68B2 loc_4B68B2:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+6E↑j
.text:004B68B2                                         ; KeInsertQueueApc(x,x,x,x)+74↑j
.text:004B68B2                 mov     [esp+20h+var_D], 0 ; 插入失败路径
.text:004B68B2                                         ; 返回值=0
.text:004B68B7
.text:004B68B7 loc_4B68B7:                             ; CODE XREF: KeInsertQueueApc(x,x,x,x)+9A↑j
.text:004B68B7                 xor     eax, eax        ;  ApcQueueLock = 0
.text:004B68B9                 lock and [ebx], eax     ; 释放 ApcQueueLock
.text:004B68BC                 push    dword ptr [esp+20h+OldIrql]
.text:004B68C0                 push    [ebp+Increment]
.text:004B68C3                 push    1
.text:004B68C5                 push    eax
.text:004B68C6                 push    [esp+30h+PKPRCB]
.text:004B68CA                 call    _KiExitDispatcher@20 ; KiExitDispatcher(x,x,x,x,x)
.text:004B68CF                 mov     al, [esp+20h+var_D] ; 返回 TRUE / FALSE
.text:004B68D3                 pop     edi
.text:004B68D4                 pop     esi
.text:004B68D5                 pop     ebx
.text:004B68D6                 mov     esp, ebp
.text:004B68D8                 pop     ebp
.text:004B68D9                 retn    10h
.text:004B68D9 _KeInsertQueueApc@16 endp

下面是分析反汇编代码后的c语言伪代码:

bash 复制代码
//
// Win7 x86:BOOLEAN __stdcall KeInsertQueueApc(PKAPC Apc, PVOID SysArg1, PVOID SysArg2, KPRIORITY Increment)
// 返回:TRUE 表示插入成功;FALSE 表示失败(未插入)
//
BOOLEAN __stdcall KeInsertQueueApc(
    PKAPC     Apc,              // [ebp+8]  -> esi
    PVOID     SystemArgument1,   // [ebp+0Ch]
    PVOID     SystemArgument2,   // [ebp+10h]
    KPRIORITY Increment          // [ebp+14h]
)
{
    //
    // 对应栈上的局部变量
    //
    UCHAR  insertedResult;       // var_D  (最终 mov al, var_D)
    ULONG  spinCount = 0;        //自旋锁计数
    KIRQL  oldIrql;              // OldIrql (KeRaiseIrqlToDpcLevel 返回值 al)
    PKPRCB prcb;                 // PKPRCB  (fs:[KPCR.Prcb])

    //
    // ========== 1) 取目标线程 ==========
    //
    // 汇编:mov edi, [esi+_KAPC.Thread]
    //
    // 说明:APC 永远属于某个线程,插入队列必然以该线程为中心。
    //
    PKTHREAD thread = Apc->Thread;

    //
    // ========== 2) 提升 IRQL 到 DPC_LEVEL ==========
    //
    // 汇编:call KeRaiseIrqlToDpcLevel
    //       mov [OldIrql], al
    //
    // 含义:进入调度器/队列操作的安全 IRQL,避免并发与抢占干扰。
    //
    oldIrql = KeRaiseIrqlToDpcLevel();

    //
    // 汇编:spinCount 清零
    //  and [esp+20h+spinCount]
    //
    spinCount = 0;

    //
    // ========== 3) 获取当前 CPU 的 PRCB ==========
    //
    // 汇编:mov eax, fs:_KPCR.Prcb
    //       mov [PKPRCB], eax
    //
    // 含义:后续调用 KiInsertQueueApc / KiExitDispatcher 都需要当前 CPU 上下文。
    //
    prcb = (PKPRCB)__readfsdword(offsetof(KPCR, Prcb));   // Win7 x86: fs:[KPCR.Prcb]

    //
    // ========== 4) 定位线程 APC 队列锁 ==========
    //
    // 汇编:lea ebx, [edi+_KTHREAD.ApcQueueLock]  (0x60)
    //
    // _KTHREAD 定义里:
    //   ULONG ApcQueueLock; // 0x60
    //
    volatile LONG* apcQueueLock = (volatile LONG*)&thread->ApcQueueLock;

    //
    // ========== 5) 获取 ApcQueueLock(自旋锁) ==========
    //
    // 汇编锁逻辑分两层:
    //   (a) 先看 lock 是否非 0 -> 走 pause/HV 优化自旋
    //   (b) 用 xchg(lock,1) 原子抢锁,失败就回到 (a)
    //
    for (;;)
    {
        //
        // (a) 先观察锁当前值,非 0 则自旋等待
        // 汇编:mov eax, [ebx] ; test eax,eax ; jnz spin
        //
        while (*apcQueueLock != 0)
        {
            //
            // 汇编:inc spinCount
            //
            spinCount++;

            //
            // 汇编:
            // test _HvlLongSpinCountMask, spinCount
            // jnz pause
            // test _HvlEnlightenments, 0x40
            // jz pause
            // call HvlNotifyLongSpinWait(spinCount)
            //
            // 含义:在 Hyper-V 环境下,长自旋会通知 Hypervisor(纯性能优化)。
            //
            if ( ((_HvlLongSpinCountMask & spinCount) == 0) &&
                 ((_HvlEnlightenments & 0x40) != 0) )
            {
                HvlNotifyLongSpinWait(spinCount);
            }
            else
            {
                _mm_pause(); // 汇编:pause
            }
        }

        //
        // (b) 原子抢锁:xchg(lock,1)
        // 汇编:
        //   eax=1
        //   xchg eax,[lock]
        //   test eax,eax
        //   jnz spin
        //
        if (InterlockedExchange(apcQueueLock, 1) == 0)
        {
            // 成功获得锁
            break;
        }

        // 否则继续循环(回到 while(*lock!=0) 的等待逻辑)
    }

    //
    // ========== 6) 拿到锁后:合法性检查 ==========
    //

    //
    // (1) 检查线程是否允许 APC 入队(ApcQueueable)
    //
    // 汇编:
    //   mov eax, [thread+0xB8]
    //   test al, 0x20
    //   jz fail
    //
    // _KTHREAD 定义中:
    //   union { ... volatile ULONG ApcQueueable:1; ... } ThreadFlags; // 0xB8
    // ApcQueueable 位正好是 bit5 -> 0x20
    //
    if ((thread->ThreadFlags & 0x20) == 0)   // !ApcQueueable
    {
        insertedResult = 0;
        goto Exit;
    }

    //
    // (2) 检查 APC 是否已插入(防止重复插入)
    //
    // 汇编:
    //   cmp byte ptr [Apc+0x2E], 1
    //   jz fail
    //
    // 对应 _KAPC.Inserted
    //
    if (Apc->Inserted == 1)
    {
        insertedResult = 0;
        goto Exit;
    }

    //
    // ========== 7) 填充 SystemArgument1/2,标记 Inserted,插入队列 ==========
    //
    // 汇编:
    //   Apc->SystemArgument1 = SystemArgument1
    //   Apc->SystemArgument2 = SystemArgument2
    //   Apc->Inserted = 1
    //   KiInsertQueueApc(prcb, Apc, oldIrql)
    //
    Apc->SystemArgument1 = SystemArgument1;
    Apc->SystemArgument2 = SystemArgument2;
    Apc->Inserted        = 1;

    //
    // 汇编传参形式:
    //   push oldIrql
    //   ecx = prcb
    //   edx = Apc
    //   call KiInsertQueueApc
    //
    // 语义:真正把 APC 的 ApcListEntry 挂到 thread 对应 APC_STATE 的 ApcListHead 上,
    // 并设置 KernelApcPending/UserApcPending 等标志。
    //
    KiInsertQueueApc(prcb, Apc, oldIrql);

    insertedResult = 1;

Exit:
    //
    // ========== 8) 释放 ApcQueueLock ==========
    //
    // 汇编:xor eax,eax ; lock and [lock], eax
    //
    // 语义:把锁清零(并保证对其他 CPU 可见)
    //
    InterlockedAnd(apcQueueLock, 0);

    //
    // ========== 9) KiExitDispatcher:恢复 IRQL + 触发调度/APC 递送检查 ==========
    //
    // 汇编 push 顺序:
    //   push oldIrql
    //   push Increment
    //   push 1
    //   push 0
    //   push prcb
    // call KiExitDispatcher
    //
    // 说明:
    // - 这一步不等价于"执行 APC"
    // - 它是离开调度器临界区的统一出口:恢复 IRQL,并检查是否需要调度/递送 APC
    //
    KiExitDispatcher(prcb, /*arg2*/0, /*arg3*/1, Increment, oldIrql);

    //
    // ========== 10) 返回:是否插入成功 ==========
    //
    return (BOOLEAN)insertedResult;
}

二、分析KeInsertQueueApc后可以得出以下结论

KeInsertQueueApc 的作用只有一个:

把一个已经初始化好的 APC,安全地插入到目标线程的 APC 队列中。

它本身 不执行 APC,也 不决定 APC 什么时候执行。

2.1 KeInsertQueueApc 的执行流程

结合反汇编代码,可以将 KeInsertQueueApc 的逻辑概括为以下几个步骤:

  1. 进入安全环境
  • 将 IRQL 提升到 DISPATCH_LEVEL

  • 获取目标线程的 ApcQueueLock

  1. 获取自旋锁(从 0x004B68460x004B687A
  • 通过自旋锁确保同一时间只有一个 CPU 修改该线程的 APC 队列

  • 这一阶段不涉及任何 APC 语义,仅用于同步

  1. 检查插入合法性
  • 检查线程是否允许插入 APC(ApcQueueable)

  • 检查该 APC 是否已经被插入过(Apc->Inserted)

  1. 准备 APC 参数
  • 填充 SystemArgument1 和 SystemArgument2

  • 将 Apc->Inserted 标记为 TRUE

  1. 真正插入 APC
  • 调用内部函数 KiInsertQueueApc

  • 由该函数将 APC 挂入线程对应的 APC 队列

  1. 退出并返回结果
  • 释放自旋锁,恢复 IRQL

  • 返回是否插入成功

KeInsertQueueApc 只负责"安全地入队 APC",

真正的执行时机和执行过程,完全不在这个函数中。

2.2 KeInsertQueueApc 的返回值

从反汇编可以非常清楚地看到:

bash 复制代码
call    KiInsertQueueApc
mov     [var_D], 1     ; 返回值直接置 1
jmp     loc_...
  • 是否返回 1(插入成功),完全取决于是否"调用到了 KiInsertQueueApc"

  • KiInsertQueueApc 没有返回值参与判断

也就是说:一旦执行路径进入 KiInsertQueueApc,

KeInsertQueueApc 的返回值就已经确定为成功。

三、分析KiInsertQueueApc函数

下面是KiInsertQueueApc函数的反汇编代码:

bash 复制代码
.text:004B1375
.text:004B1375 ; =============== S U B R O U T I N E =======================================
.text:004B1375
.text:004B1375 ; Attributes: bp-based frame fuzzy-sp
.text:004B1375
.text:004B1375 ; char __fastcall KiInsertQueueApc(__int32 a1, _KAPC *apc, char a3)
.text:004B1375 @KiInsertQueueApc@12 proc near          ; CODE XREF: KeSuspendThread(x)+FC↑p
.text:004B1375                                         ; KeInsertQueueApc(x,x,x,x)+90↓p ...
.text:004B1375
.text:004B1375 IsNormalApc     = byte ptr -19h
.text:004B1375 var_18          = dword ptr -18h
.text:004B1375 _KPRCB          = dword ptr -14h
.text:004B1375 var_10          = dword ptr -10h
.text:004B1375 var_C           = word ptr -0Ch
.text:004B1375 var_A           = word ptr -0Ah
.text:004B1375 var_4           = dword ptr -4
.text:004B1375 OldIrq          = byte ptr  8
.text:004B1375
.text:004B1375                 mov     edi, edi
.text:004B1377                 push    ebp
.text:004B1378                 mov     ebp, esp
.text:004B137A                 and     esp, 0FFFFFFF8h
.text:004B137D                 sub     esp, 1Ch
.text:004B1380                 push    ebx
.text:004B1381                 push    esi
.text:004B1382                 push    edi
.text:004B1383                 mov     edi, edx        ; edi =APC
.text:004B1385                 cmp     [edi+_KAPC.ApcStateIndex], 3 ; 0 OriginalApcEnvironment,//原始 APC 环境(线程最初所在的进程)
.text:004B1385                                         ; 1 AttachedApcEnvironment,//挂靠环境(KeStackAttachProcess 后的 APC 环境)
.text:004B1385                                         ; 2 CurrentApcEnvironment,//初始化时环境决定
.text:004B1385                                         ; 3 InsertApcEnvironment //插入时环境决定
.text:004B1389                 mov     esi, [edi+_KAPC.Thread]
.text:004B138C                 mov     [esp+28h+_KPRCB], ecx ; ecx = _KPRCB
.text:004B1390                 jnz     short loc_4B139B ; ; 如果 ApcStateIndex != 3,则跳过环境修正逻辑
.text:004B1392                 mov     al, [esi+_KTHREAD.ApcStateIndex]
.text:004B1398                 mov     [edi+_KAPC.ApcStateIndex], al ; 当_KAPC.ApcStateIndex==3时
.text:004B1398                                         ; 根据_KTHREAD.ApcStateIndex(插入时环境)来决定
.text:004B1398                                         ; _KTHREAD.ApcStateIndex代表当前线程是正常状态还是挂靠状态
.text:004B1398                                         ;
.text:004B1398                                         ; 之前分析KeInitializeApc中,也有过这种判断当_KAPC.ApcStateIndex==2时
.text:004B1398                                         ; 也是根据_KTHREAD.ApcStateIndex来决定
.text:004B1398                                         ; 使_KAPC.ApcStateIndex=_KTHREAD.ApcStateIndex
.text:004B1398                                         ;
.text:004B1398                                         ; 所以虽然_KAPC.ApcStateIndex有4种状态,
.text:004B1398                                         ; 但从这句执行之后开始,后续所有用 _KAPC.ApcStateIndex 做索引的代码,只可能是 0 或 1。
.text:004B139B
.text:004B139B loc_4B139B:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+1B↑j
.text:004B139B                 cmp     [edi+_KAPC.NormalRoutine], 0 ; 判断 Apc->NormalRoutine 是否为空
.text:004B139F                 movsx   eax, [edi+_KAPC.ApcStateIndex]
.text:004B13A3                 mov     ecx, [esi+eax*4+_KTHREAD.ApcStatePointer] ;  ecx = Thread->ApcStatePointer[ApcStateIndex]
.text:004B13A3                                         ;  取得目标 _KAPC_STATE 结构地址
.text:004B13A3                                         ; ecx指向要插入的_KAPC_STATE
.text:004B13AA                 mov     bl, [edi+_KAPC.ApcMode] ; bl = Apc->ApcMode(KernelMode / UserMode)
.text:004B13AD                 jz      short loc_4B13F4 ; ; 如果 NormalRoutine == NULL,跳转
.text:004B13AF                 mov     [esp+28h+IsNormalApc], 1
.text:004B13B4                 test    bl, bl          ; 判断 ApcMode 是否为 KernelMode(bl == 0)
.text:004B13B6                 jz      short loc_4B13DC ; 如果是 KernelMode Normal APC,跳转
.text:004B13B8                 cmp     [edi+_KAPC.KernelRoutine], offset _PsExitSpecialApc@20 ; 判断 KernelRoutine 是否为 PsExitSpecialApc(特殊用户 APC)
.text:004B13BF                 jnz     short loc_4B13DC ; 如果不是 PsExitSpecialApc,跳转
.text:004B13C1                 movsx   edx, bl         ; edx = ApcMode(此处 bl == 1,即 UserMode)
.text:004B13C4                 mov     [esi+_KTHREAD.___u12.ApcState.UserApcPending], 1 ; 设置线程 UserApcPending标志
.text:004B13C4                                         ; 表示线程存在待处理的用户态 APC
.text:004B13C8                 lea     ecx, [ecx+edx*8] ; ecx = &_KAPC_STATE.ApcListHead[ApcMode]
.text:004B13C8                                         ; 因edx=1,所以此是User APC 队列头地址(LIST_ENTRY)
.text:004B13CB                 mov     edx, [ecx]      ; edx =_KAPC_STATE.ApcListHead[1].Flink
.text:004B13CB                                         ; 取得当前 User APC 链表的第一个节点
.text:004B13CD                 lea     eax, [edi+_KAPC.ApcListEntry] ; eax = &Apc->ApcListEntry
.text:004B13CD                                         ; 待插入的链表节点
.text:004B13D0                 mov     [eax], edx      ;  apc.ApcListEntry.Flink = 原链表头的 Flink
.text:004B13D2                 mov     [eax+4], ecx    ; apc.ApcListEntry.Blink = ApcListHead
.text:004B13D5                 mov     [edx+4], eax    ; 原第一个节点的 Blink 指向新插入的 ApcListEntry
.text:004B13D8                 mov     [ecx], eax      ;  ApcListHead.Flink 指向新插入的 ApcListEntry
.text:004B13D8                                         ; 完成 头插操作
.text:004B13DA                 jmp     short loc_4B1420
.text:004B13DC ; ---------------------------------------------------------------------------
.text:004B13DC
.text:004B13DC loc_4B13DC:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+41↑j
.text:004B13DC                                         ; KiInsertQueueApc(x,x,x)+4A↑j
.text:004B13DC                 movsx   edx, bl
.text:004B13DF                 lea     ecx, [ecx+edx*8] ; ecx =&_KAPC_STATE.ApcListHead[ApcMode]
.text:004B13E2                 mov     edx, [ecx+4]    ; edx=_KAPC_STATE.ApcListHead[ApcMode].Blink
.text:004B13E2                                         ; 取链表尾节点
.text:004B13E5                 lea     eax, [edi+_KAPC.ApcListEntry] ;  &Apc->ApcListEntry
.text:004B13E5                                         ;  取当前要插入的链表节点地址
.text:004B13E8                 mov     [eax], ecx      ;  NewEntry->Flink = ListHead
.text:004B13E8                                         ; (尾插:新节点的 Flink 指向表头)
.text:004B13EA                 mov     [eax+4], edx    ; NewEntry->Blink = OldTail
.text:004B13EA                                         ; (尾插:新节点的 Blink 指向原尾节点)
.text:004B13ED                 mov     [edx], eax      ; OldTail->Flink = NewEntry
.text:004B13ED                                         ; 原尾节点的 Flink 指向新节点
.text:004B13EF                 mov     [ecx+4], eax    ; ListHead->Blink = NewEntry
.text:004B13EF                                         ; 表头 Blink 更新为新节点(新节点成为新的尾节点)
.text:004B13F2                 jmp     short loc_4B1420
.text:004B13F4 ; ---------------------------------------------------------------------------
.text:004B13F4
.text:004B13F4 loc_4B13F4:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+38↑j
.text:004B13F4                 movsx   eax, bl
.text:004B13F7                 lea     ecx, [ecx+eax*8] ;  ecx = &_KAPC_STATE.ApcListHead[ApcMode]
.text:004B13FA                 mov     eax, [ecx+4]    ; eax=ApcListHead.Blink 尾节点
.text:004B13FD                 mov     [esp+28h+IsNormalApc], 0
.text:004B1402                 jmp     short loc_4B140D ; 先跳到比较点(典型的 do/while 结构)
.text:004B1404 ; ---------------------------------------------------------------------------
.text:004B1404
.text:004B1404 loc_4B1404:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+9A↓j
.text:004B1404                 cmp     dword ptr [eax+10h], 0 ;  比较 [eax+0x10] 是否为 0
.text:004B1404                                         ;  此时的eax=ApcListHead.Blink,类型为_KAPC
.text:004B1404                                         ;  ApcListEntry 偏移是 0x0C:
.text:004B1404                                         ;  LIST_ENTRY(0x0C) + 0x10 = 0x1C  ==> 这对应 KAPC 里的 NormalRoutine 指针
.text:004B1404                                         ; 所以这里等价于:检查"该 APC 的 NormalRoutine 是否为 NULL"
.text:004B1408                 jz      short loc_4B1411 ;  如果NormalRoutine == NUL,找到目标节点,跳去插入点
.text:004B140A                 mov     eax, [eax+4]    ; eax = eax->Blink
.text:004B140A                                         ; 否则沿着 Blink 指针向前走(从尾往头遍历)
.text:004B140D
.text:004B140D loc_4B140D:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+8D↑j
.text:004B140D                 cmp     eax, ecx        ; 判断是否走回表头
.text:004B140D                                         ; ecx 是 ListHead,如果 eax == ecx,说明遍历到头了
.text:004B140F                 jnz     short loc_4B1404 ; 没到表头就继续循环
.text:004B1411
.text:004B1411 loc_4B1411:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+93↑j
.text:004B1411                 mov     edx, [eax]      ; eax是从尾部循环找到NormalRoutine==NULL的节点
.text:004B1411                                         ; edx = eax->Flink
.text:004B1411                                         ; 取当前节点的下一个节点地址
.text:004B1413                 lea     ecx, [edi+0Ch]  ; ecx = &Apc->ApcListEntry
.text:004B1413                                         ; 当前要插入的新节点
.text:004B1416                 mov     [ecx], edx      ;  NewEntry->Flink = 旧节点的Flink
.text:004B1416                                         ; 新插入的Flink等于
.text:004B1418                 mov     [ecx+4], eax    ; NewEntry->Blink=旧节点
.text:004B141B                 mov     [edx+4], ecx    ; 原旧节点的后一个节点的Blink= NewEntry
.text:004B141E                 mov     [eax], ecx      ; OldEntry->Flink=NewEntry
.text:004B1420
.text:004B1420 loc_4B1420:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+65↑j
.text:004B1420                                         ; KiInsertQueueApc(x,x,x)+7D↑j
.text:004B1420                 movzx   eax, [esi+_KTHREAD.ApcStateIndex]
.text:004B1427                 movsx   ecx, byte ptr [edi+2Ch]
.text:004B142B                 cmp     ecx, eax
.text:004B142D                 jnz     loc_4B1627
.text:004B1433                 mov     eax, [esp+28h+_KPRCB]
.text:004B1437                 cmp     esi, [eax+4]
.text:004B143A                 jnz     short loc_4B147F
.text:004B143C                 test    bl, bl
.text:004B143E                 jnz     loc_4B1627
.text:004B1444                 cmp     dword ptr [esi+84h], 0
.text:004B144B                 jz      short loc_4B1465
.text:004B144D                 cmp     [esp+28h+IsNormalApc], bl
.text:004B1451                 jnz     loc_4B1627
.text:004B1457                 cmp     word ptr [esi+86h], 0
.text:004B145F                 jnz     loc_4B1627
.text:004B1465
.text:004B1465 loc_4B1465:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+D6↑j
.text:004B1465                 cmp     [ebp+OldIrq], 1
.text:004B1469                 mov     byte ptr [esi+55h], 1
.text:004B146D                 jnb     loc_4B15C2
.text:004B1473                 or      dword ptr [esi+3Ch], 100h
.text:004B147A                 jmp     loc_4B1627
.text:004B147F ; ---------------------------------------------------------------------------
.text:004B147F
.text:004B147F loc_4B147F:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+C5↑j
.text:004B147F                 test    bl, bl
.text:004B1481                 jnz     loc_4B152E
.text:004B1487                 mov     byte ptr [esi+55h], 1
.text:004B148B                 xchg    eax, [esp+28h+var_10]
.text:004B148F                 mov     al, [esi+68h]
.text:004B1492                 movzx   eax, al
.text:004B1495                 cmp     eax, 2
.text:004B1498                 jz      loc_4B15B4
.text:004B149E                 cmp     eax, 5
.text:004B14A1                 jnz     loc_4B1627
.text:004B14A7                 and     [esp+28h+var_18], 0
.text:004B14AC                 lea     ebx, [esi+34h]
.text:004B14AF                 jmp     short loc_4B14DA
.text:004B14B1 ; ---------------------------------------------------------------------------
.text:004B14B1
.text:004B14B1 loc_4B14B1:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+163↓j
.text:004B14B1                                         ; KiInsertQueueApc(x,x,x)+16E↓j
.text:004B14B1                 inc     [esp+28h+var_18]
.text:004B14B5                 mov     eax, [esp+28h+var_18]
.text:004B14B9                 test    ds:_HvlLongSpinCountMask, eax
.text:004B14BF                 jnz     short loc_4B14D2
.text:004B14C1                 test    byte ptr ds:_HvlEnlightenments, 40h
.text:004B14C8                 jz      short loc_4B14D2
.text:004B14CA                 push    eax
.text:004B14CB                 call    _HvlNotifyLongSpinWait@4 ; HvlNotifyLongSpinWait(x)
.text:004B14D0                 jmp     short loc_4B14D4
.text:004B14D2 ; ---------------------------------------------------------------------------
.text:004B14D2
.text:004B14D2 loc_4B14D2:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+14A↑j
.text:004B14D2                                         ; KiInsertQueueApc(x,x,x)+153↑j
.text:004B14D2                 pause
.text:004B14D4
.text:004B14D4 loc_4B14D4:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+15B↑j
.text:004B14D4                 mov     eax, [ebx]
.text:004B14D6                 test    eax, eax
.text:004B14D8                 jnz     short loc_4B14B1
.text:004B14DA
.text:004B14DA loc_4B14DA:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+13A↑j
.text:004B14DA                 xor     eax, eax
.text:004B14DC                 mov     ecx, ebx
.text:004B14DE                 inc     eax
.text:004B14DF                 xchg    eax, [ecx]
.text:004B14E1                 test    eax, eax
.text:004B14E3                 jnz     short loc_4B14B1
.text:004B14E5                 mov     al, [esi+68h]
.text:004B14E8                 cmp     al, 5
.text:004B14EA                 jnz     short loc_4B1524
.text:004B14EC                 cmp     byte ptr [esi+6Ah], 0
.text:004B14F0                 jnz     short loc_4B1524
.text:004B14F2                 xor     eax, eax
.text:004B14F4                 cmp     [esi+86h], ax
.text:004B14FB                 jnz     short loc_4B1524
.text:004B14FD                 cmp     [edi+1Ch], eax
.text:004B1500                 jz      short loc_4B1510
.text:004B1502                 cmp     [esi+84h], ax
.text:004B1509                 jnz     short loc_4B1524
.text:004B150B                 cmp     [esi+54h], al
.text:004B150E                 jnz     short loc_4B1524
.text:004B1510
.text:004B1510 loc_4B1510:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+18B↑j
.text:004B1510                 push    100h
.text:004B1515                 push    [esp+2Ch+_KPRCB]
.text:004B1519                 xor     eax, eax
.text:004B151B                 call    @KiSignalThread@16 ; KiSignalThread(x,x,x,x)
.text:004B1520                 or      byte ptr [esi+38h], 10h
.text:004B1524
.text:004B1524 loc_4B1524:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+175↑j
.text:004B1524                                         ; KiInsertQueueApc(x,x,x)+17B↑j ...
.text:004B1524                 xor     eax, eax
.text:004B1526                 lock and [ebx], eax
.text:004B1529                 jmp     loc_4B1627
.text:004B152E ; ---------------------------------------------------------------------------
.text:004B152E
.text:004B152E loc_4B152E:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+10C↑j
.text:004B152E                 mov     al, [esi+68h]
.text:004B1531                 cmp     al, 5
.text:004B1533                 jnz     loc_4B1627
.text:004B1539                 mov     [esp+28h+IsNormalApc], 0
.text:004B153E                 lea     edi, [esi+34h]
.text:004B1541                 xor     ebx, ebx
.text:004B1543                 jmp     short loc_4B1567
.text:004B1545 ; ---------------------------------------------------------------------------
.text:004B1545
.text:004B1545 loc_4B1545:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+1F0↓j
.text:004B1545                                         ; KiInsertQueueApc(x,x,x)+1FB↓j
.text:004B1545                 inc     ebx
.text:004B1546                 test    ds:_HvlLongSpinCountMask, ebx
.text:004B154C                 jnz     short loc_4B155F
.text:004B154E                 test    byte ptr ds:_HvlEnlightenments, 40h
.text:004B1555                 jz      short loc_4B155F
.text:004B1557                 push    ebx
.text:004B1558                 call    _HvlNotifyLongSpinWait@4 ; HvlNotifyLongSpinWait(x)
.text:004B155D                 jmp     short loc_4B1561
.text:004B155F ; ---------------------------------------------------------------------------
.text:004B155F
.text:004B155F loc_4B155F:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+1D7↑j
.text:004B155F                                         ; KiInsertQueueApc(x,x,x)+1E0↑j
.text:004B155F                 pause
.text:004B1561
.text:004B1561 loc_4B1561:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+1E8↑j
.text:004B1561                 mov     eax, [edi]
.text:004B1563                 test    eax, eax
.text:004B1565                 jnz     short loc_4B1545
.text:004B1567
.text:004B1567 loc_4B1567:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+1CE↑j
.text:004B1567                 xor     eax, eax
.text:004B1569                 mov     ecx, edi
.text:004B156B                 inc     eax
.text:004B156C                 xchg    eax, [ecx]
.text:004B156E                 test    eax, eax
.text:004B1570                 jnz     short loc_4B1545
.text:004B1572                 mov     al, [esi+68h]
.text:004B1575                 cmp     al, 5
.text:004B1577                 jnz     short loc_4B15A3
.text:004B1579                 cmp     byte ptr [esi+6Bh], 1
.text:004B157D                 jnz     short loc_4B15A3
.text:004B157F                 test    byte ptr [esi+3Ch], 20h
.text:004B1583                 jnz     short loc_4B158B
.text:004B1585                 cmp     byte ptr [esi+56h], 0
.text:004B1589                 jz      short loc_4B15A3
.text:004B158B
.text:004B158B loc_4B158B:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+20E↑j
.text:004B158B                 push    0C0h
.text:004B1590                 push    [esp+2Ch+_KPRCB]
.text:004B1594                 xor     eax, eax
.text:004B1596                 call    @KiSignalThread@16 ; KiSignalThread(x,x,x,x)
.text:004B159B                 or      byte ptr [esi+38h], 20h
.text:004B159F                 mov     [esp+28h+IsNormalApc], al
.text:004B15A3
.text:004B15A3 loc_4B15A3:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+202↑j
.text:004B15A3                                         ; KiInsertQueueApc(x,x,x)+208↑j ...
.text:004B15A3                 xor     eax, eax
.text:004B15A5                 lock and [edi], eax
.text:004B15A8                 cmp     [esp+28h+IsNormalApc], al
.text:004B15AC                 jz      short loc_4B1627
.text:004B15AE                 mov     byte ptr [esi+56h], 1
.text:004B15B2                 jmp     short loc_4B1627
.text:004B15B4 ; ---------------------------------------------------------------------------
.text:004B15B4
.text:004B15B4 loc_4B15B4:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+123↑j
.text:004B15B4                 push    0               ; ProcNumber
.text:004B15B6                 call    _KeGetCurrentProcessorNumberEx@4 ; KeGetCurrentProcessorNumberEx(x)
.text:004B15BB                 mov     ecx, [esi+58h]
.text:004B15BE                 cmp     eax, ecx
.text:004B15C0                 jnz     short loc_4B15CC
.text:004B15C2
.text:004B15C2 loc_4B15C2:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+F8↑j
.text:004B15C2                 mov     cl, 1
.text:004B15C4                 call    ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)
.text:004B15CA                 jmp     short loc_4B1627
.text:004B15CC ; ---------------------------------------------------------------------------
.text:004B15CC
.text:004B15CC loc_4B15CC:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+24B↑j
.text:004B15CC                 mov     esi, [esi+58h]
.text:004B15CF                 xor     edx, edx
.text:004B15D1                 inc     edx
.text:004B15D2                 mov     eax, edx
.text:004B15D4                 mov     [esp+28h+var_C], ax
.text:004B15D9                 mov     [esp+28h+var_A], ax
.text:004B15DE                 xor     eax, eax
.text:004B15E0                 lea     edi, [esp+28h+var_4]
.text:004B15E4                 stosd
.text:004B15E5                 mov     eax, ds:_KiProcessorIndexToNumberMappingTable[esi*4]
.text:004B15EC                 movzx   esi, [esp+28h+var_C]
.text:004B15F1                 mov     ecx, eax
.text:004B15F3                 shr     ecx, 6
.text:004B15F6                 and     eax, 3Fh
.text:004B15F9                 cmp     esi, ecx
.text:004B15FB                 ja      short loc_4B1605
.text:004B15FD                 lea     esi, [ecx+1]
.text:004B1600                 mov     [esp+28h+var_C], si
.text:004B1605
.text:004B1605 loc_4B1605:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+286↑j
.text:004B1605                 mov     eax, ds:_KiMask32Array[eax*4]
.text:004B160C                 lea     ecx, [esp+ecx*4+28h+var_4]
.text:004B1610                 or      [ecx], eax
.text:004B1612                 mov     eax, large fs:20h
.text:004B1618                 inc     dword ptr [eax+3574h]
.text:004B161E                 lea     ecx, [esp+28h+var_C]
.text:004B1622                 call    @KiIpiSend@8    ; KiIpiSend(x,x)
.text:004B1627
.text:004B1627 loc_4B1627:                             ; CODE XREF: KiInsertQueueApc(x,x,x)+B8↑j
.text:004B1627                                         ; KiInsertQueueApc(x,x,x)+C9↑j ...
.text:004B1627                 pop     edi
.text:004B1628                 pop     esi
.text:004B1629                 pop     ebx
.text:004B162A                 mov     esp, ebp
.text:004B162C                 pop     ebp
.text:004B162D                 retn    4
.text:004B162D @KiInsertQueueApc@12 endp

下面是分析反汇编代码后的c语言伪代码:

cpp 复制代码
// ----------------- KiInsertQueueApc 的 C 版本 -----------------
//
// 功能总览:
// 1) 决定 APC 要进入哪个 KAPC_STATE(Original/Attached)
// 2) 决定 APC 插入 ApcListHead[Kernel/User] 的位置(头插/尾插/插在分界点后)
// 3) 插完以后,根据:
//    - 目标线程是不是当前CPU正在跑的线程
//    - APC 是 Kernel 还是 User
//    - 线程状态(Running/Waiting等)
//    - 各种 Disable / InProgress 标志
//    去触发:软件中断 / KiSignalThread / 发送 IPI
//
UCHAR __fastcall KiInsertQueueApc(PKPRCB Prcb, PKAPC Apc, UCHAR OldIrq)
{
    // [栈变量] 汇编里是 -19h
    // 语义:这次插入的 APC 是否属于"Normal APC"(NormalRoutine != NULL)
    // 后面用于"某些情况下是否允许触发 APC delivery"。
    UCHAR IsNormalApc = 0;

    // [004B1389]
    // APC 绑定的目标线程
    PKTHREAD Thread = Apc->Thread;

    // =========================================================
    // 1) 修正 ApcStateIndex(InsertApcEnvironment -> 0/1)
    // =========================================================
    //
    // [004B1385~004B1398]
    // Apc->ApcStateIndex 可能为 3(InsertApcEnvironment)
    // 这表示:"插入时由内核决定到底插进 Original 还是 Attached 环境"
    // Win7 x86 这里的策略:直接取 Thread->ApcStateIndex(当前线程处于原进程还是Attach进程)
    //
    if (Apc->ApcStateIndex == 3) {
        // [004B1392~004B1398]
        // 修正后 ApcStateIndex 只可能变成 0 或 1
        Apc->ApcStateIndex = Thread->ApcStateIndex;
    }

    // =========================================================
    // 2) 取目标 KAPC_STATE
    // =========================================================
    //
    // [004B139F~004B13A3]
    // Thread->ApcStatePointer[2] 指向两个 APC 环境对应的 KAPC_STATE:
    //   [0] = OriginalApcEnvironment 的 KAPC_STATE
    //   [1] = AttachedApcEnvironment 的 KAPC_STATE
    //
    PKAPC_STATE ApcState =
        Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];

    // [004B13AA]
    // ApcMode:0=KernelMode,1=UserMode
    // 注意:它同时决定往 ApcListHead[0] 还是 ApcListHead[1] 插入
    UCHAR ApcMode = (UCHAR)Apc->ApcMode;

    // =========================================================
    // 3) APC 插入阶段(决定插入策略)
    // =========================================================
    //
    // 核心点:KiInsertQueueApc 在 Win7 里对队列顺序是有"分层"的:
    //
    // - Normal APC(NormalRoutine != NULL)
    //   通常尾插(保证 FIFO/到尾),但有一个特殊类型会头插(更紧急)
    //
    // - Special APC(NormalRoutine == NULL)
    //   不是简单尾插,而是要插到 "Special APC 分区" 的尾部,
    //   也就是插在 "某个 Special APC" 后面,保证 Special APC 优先于 Normal APC
    //
    //

    // ---------------------------------------------------------
    // 3A) Normal APC(NormalRoutine != NULL)
    // ---------------------------------------------------------
    // [004B139B cmp NormalRoutine,0 / jz loc_4B13F4]
    //
    if (Apc->NormalRoutine != NULL) {

        // [004B13AF]
        // 标记这是 Normal APC
        IsNormalApc = 1;

        // [004B13B4 test bl,bl / jz loc_4B13DC]
        // 仅当是 UserMode 且 KernelRoutine==PsExitSpecialApc 才走"头插路径"
        //
        // PsExitSpecialApc 通常是进程/线程退出相关的用户 APC,属于"必须尽快投递"的特殊 Normal APC
        //
        if (ApcMode != KernelMode &&
            Apc->KernelRoutine == PsExitSpecialApc)
        {
            // [004B13C4]
            // 设置线程 UserApcPending:表示线程存在待投递的用户 APC
            Thread->ApcState.UserApcPending = 1;

            // [004B13C8~004B13D8]
            // 头插到 ApcState->ApcListHead[UserMode]
            // 汇编里写指针顺序严格是:
            //   Entry->Flink = Head->Flink
            //   Entry->Blink = Head
            //   First->Blink = Entry
            //   Head->Flink  = Entry
            //
            // 注意:这里 ListHead 取的是 &ApcListHead[ApcMode],
            // 但此分支里 ApcMode 必然是 UserMode(1),所以就是 ApcListHead[1]
            //
            {
                PLIST_ENTRY ListHead =
                    &ApcState->ApcListHead[ApcMode];
                PLIST_ENTRY Entry =
                    &Apc->ApcListEntry;
                PLIST_ENTRY First =
                    ListHead->Flink;

                Entry->Flink = First;
                Entry->Blink = ListHead;
                First->Blink = Entry;
                ListHead->Flink = Entry;
            }

            // [004B13DA jmp loc_4B1420]
            goto AfterInsert;
        }

        // -----------------------------------------------------
        // [loc_4B13DC] 普通 Normal APC → 尾插
        // -----------------------------------------------------
        // [004B13DC~004B13F2]
        // 写指针顺序:
        //   OldTail = Head->Blink
        //   Entry->Flink = Head
        //   Entry->Blink = OldTail
        //   OldTail->Flink = Entry
        //   Head->Blink = Entry
        //
        {
            PLIST_ENTRY ListHead =
                &ApcState->ApcListHead[ApcMode];
            PLIST_ENTRY Entry =
                &Apc->ApcListEntry;
            PLIST_ENTRY OldTail =
                ListHead->Blink;

            Entry->Flink = ListHead;
            Entry->Blink = OldTail;
            OldTail->Flink = Entry;
            ListHead->Blink = Entry;
        }

        goto AfterInsert;
    }

    // ---------------------------------------------------------
    // 3B) Special APC(NormalRoutine == NULL)
    // ---------------------------------------------------------
    // [loc_4B13F4] 004B13F4~004B141E
    //
    // 核心语义:
    //   从队尾开始向前找,直到找到 "某个 Special APC(NormalRoutine==NULL)"
    //   然后把新的 Special APC 插在它后面
    //
    // 这样可以保持队列布局:
    //   [Head] <-> ... <-> Special APC ... <-> Special APC ... <-> Normal APC ... <-> [Tail]
    //
    // 新来的 Special APC 不会被插到 Normal APC 后面去(否则会被"延后执行")
    //
    {
        PLIST_ENTRY ListHead =
            &ApcState->ApcListHead[ApcMode];

        // [004B13FA] 从尾部节点开始
        PLIST_ENTRY Pos =
            ListHead->Blink;

        // [004B13FD]
        IsNormalApc = 0;

        // [004B1402 jmp 004B140D + 004B1404/140A/140F]
        // do/while 形态:从尾向头遍历(沿 Blink 走)
        while (Pos != ListHead) {

            // Pos 是某个 APC 的 ApcListEntry,反推出 KAPC*
            PKAPC CurApc =
                CONTAINING_RECORD(Pos, KAPC, ApcListEntry);

            // [004B1404 cmp [eax+10h],0]
            // 等价于 CurApc->NormalRoutine == NULL ?
            if (CurApc->NormalRoutine == NULL) {
                // 找到"Special 分区"的锚点:在这个节点后面插入
                break;
            }

            // [004B140A] Pos = Pos->Blink(继续从尾往头)
            Pos = Pos->Blink;
        }

        // [004B1411~004B141E]
        // 插在 Pos 之后(Pos 可以是:某个 Special APC,或 ListHead)
        // 如果一直没找到 Special APC,Pos 最终会走到 ListHead,
        // 那就等价于 "插在表头之后"(也就是插到最前面)
        //
        {
            PLIST_ENTRY Entry =
                &Apc->ApcListEntry;
            PLIST_ENTRY Next =
                Pos->Flink;

            Entry->Flink = Next;
            Entry->Blink = Pos;
            Next->Blink  = Entry;
            Pos->Flink   = Entry;
        }
    }

AfterInsert:

    // =========================================================
    // 4) 插入完成后的触发 / 唤醒 / 中断逻辑
    // =========================================================
    //
    // 这一大段做的事:
    //  - 如果插入到的环境已经不是线程当前环境 -> 不做触发(return 0)
    //  - 如果目标线程是当前 CPU 正在跑的线程 -> 可能请求软件中断/置位 flag
    //  - 如果目标线程不在当前 CPU 跑:
    //      * Kernel APC:尝试 signal/wake 或跨核 IPI
    //      * User APC:设置 UserApcPending,并在必要时 signal
    //

    // [004B1420~004B142D]
    // 如果 ApcStateIndex 不等于 Thread 当前的 ApcStateIndex,直接放弃触发
    // (因为插入到了"另一个环境",当前不适合/不应投递)
    if ((UCHAR)Apc->ApcStateIndex !=
        (UCHAR)Thread->ApcStateIndex)
    {
        return 0;
    }

    // ---------------------------------------------------------
    // 4A) 目标线程 == 当前 CPU 的 CurrentThread
    // ---------------------------------------------------------
    // [004B1433~004B147A]
    //
    // 这里的 *(Prcb+4) 之前也说了:等价于 Prcb->CurrentThread
    //
    if (Thread == *(PKTHREAD *)((char*)Prcb + 4)) {

        // [004B143C test bl,bl / jnz exit]
        // 当前线程路径只处理 KernelMode APC
        if (ApcMode != KernelMode) {
            return 0;
        }

        // [004B1444~004B145F]
        // CombinedApcDisable != 0:说明 APC 投递被禁止(KernelApcDisable/SpecialApcDisable)
        // 但这里允许一种情况继续:Special APC 且 SpecialApcDisable==0
        if (Thread->CombinedApcDisable != 0) {

            // Normal APC:直接不触发
            if (IsNormalApc != 0) {
                return 0;
            }

            // Special APC 但 SpecialApcDisable!=0:也不触发
            if (Thread->SpecialApcDisable != 0) {
                return 0;
            }
        }

        // [004B1469] 设置 KernelApcPending = 1
        Thread->ApcState.KernelApcPending = 1;

        // [004B1465 cmp OldIrq,1 / jnb loc_4B15C2]
        // OldIrq 表示当时 IRQL 情况(上文里有 OldIrq 变量)
        // 若 OldIrq >= 1:可以直接请求软件中断,让 APC 尽快投递
        if (OldIrq >= 1) {
            HalRequestSoftwareInterrupt(1);
            return 1;
        } else {
            // 否则:只置一个标志位(MiscFlags |= 0x100)
            // 等后续合适时机再触发
            Thread->MiscFlags |= 0x100;
            return 0;
        }
    }

    // ---------------------------------------------------------
    // 4B) 目标线程不是当前线程:分 Kernel APC / User APC
    // ---------------------------------------------------------

    // =========================
    // 4B-1) Kernel APC
    // =========================
    // [004B147F~004B1529] 主体
    //
    if (ApcMode == KernelMode) {

        // [004B1487] KernelApcPending = 1
        Thread->ApcState.KernelApcPending = 1;

        // [004B1495] state==2 -> 特殊路径:请求中断或 IPI
        if (Thread->State == 2) {
            goto RequestInterruptOrIpi;
        }

        // [004B149E] state!=5 直接退出
        // 5 在这里对应"Waiting/可处理/调度器等待状态"
        if (Thread->State != 5) {
            return 0;
        }

        // [004B14AC] ThreadLock = &Thread->ThreadLock (0x34)
        volatile ULONG* ThreadLock =
            (volatile ULONG*)((char*)Thread + 0x34);

        // [004B14AF~004B14E3] 自旋获取 ThreadLock
        // 含 Hyper-V long spin notify(HvlNotifyLongSpinWait)
        {
            ULONG Spin = 0;
            for (;;) {
                ULONG prev =
                    __atomic_exchange_n(ThreadLock, 1, __ATOMIC_ACQ_REL);
                if (prev == 0) break;

                ++Spin;
                if ((HvlLongSpinCountMask & Spin) == 0 &&
                    (HvlEnlightenments & 0x40))
                {
                    HvlNotifyLongSpinWait(Spin);
                } else {
                    __asm { pause }
                }
            }
        }

        // [004B14E5~004B1520] 锁内检查
        // 条件满足时:KiSignalThread(Prcb,0x100,...)
        //
        // waitIrql==0 && SpecialApcDisable==0:允许投递
        if (Thread->State == 5 &&
            Thread->WaitIrql == 0 &&
            Thread->SpecialApcDisable == 0)
        {
            // Special APC:允许 signal
            // Normal APC:还要求 KernelApcDisable==0 且 KernelApcInProgress==0
            if (Apc->NormalRoutine == NULL ||
                (Thread->KernelApcDisable == 0 &&
                 Thread->ApcState.KernelApcInProgress == 0))
            {
                KiSignalThread(Prcb, 0x100, 0, 0);

                // [004B1520] WaitRegister/Alerted 相关位 OR 0x10
                *(UCHAR*)((char*)Thread + 0x38) |= 0x10;
            }
        }

        // [004B1524~004B1526] 释放锁(汇编是 lock and [ebx],0)
        __atomic_store_n(ThreadLock, 0, __ATOMIC_RELEASE);
        return 0;
    }

    // =========================
    // 4B-2) User APC
    // =========================
    // [004B152E~004B15B2]
    //
    // User APC 只能在 certain waiting 状态下投递(WaitMode==1 等)
    //
    if (Thread->State != 5) {
        return 0;
    }

    volatile ULONG* ThreadLock =
        (volatile ULONG*)((char*)Thread + 0x34);

    // [004B1543~004B1570] 自旋获取 ThreadLock
    {
        ULONG Spin = 0;
        for (;;) {
            ULONG prev =
                __atomic_exchange_n(ThreadLock, 1, __ATOMIC_ACQ_REL);
            if (prev == 0) break;

            ++Spin;
            if ((HvlLongSpinCountMask & Spin) == 0 &&
                (HvlEnlightenments & 0x40))
            {
                HvlNotifyLongSpinWait(Spin);
            } else {
                __asm { pause }
            }
        }
    }

    // [004B1572~004B159F]
    // 条件:
    //   Thread->State==5 && WaitMode==1(用户态等待)
    //   且(MiscFlags&0x20 或 UserApcPending 已经为真)
    // 则 KiSignalThread(Prcb,0xC0) 并置位 0x20
    //
    if (Thread->State == 5 &&
        Thread->WaitMode == 1)
    {
        if ((Thread->MiscFlags & 0x20) ||
            Thread->ApcState.UserApcPending)
        {
            KiSignalThread(Prcb, 0xC0, 0, 0);
            *(UCHAR*)((char*)Thread + 0x38) |= 0x20;
        }
    }

    // [004B15A3~004B15A5] 释放锁
    __atomic_store_n(ThreadLock, 0, __ATOMIC_RELEASE);

    // [004B15AE] 最终设置 UserApcPending=1
    // 语义:告诉调度器/线程:有用户 APC 待投递
    Thread->ApcState.UserApcPending = 1;
    return 0;

RequestInterruptOrIpi:

    // =========================================================
    // 5) RequestInterruptOrIpi:当前核/跨核触发
    // =========================================================
    //
    // [004B15B4~004B1622]
    // 目标线程可能在另一个 CPU 上运行(或即将运行)
    // - 如果目标 CPU 就是当前 CPU:请求软件中断即可
    // - 否则:构造 IPI mask,调用 KiIpiSend 通知目标 CPU
    //
    {
        ULONG Current = KeGetCurrentProcessorNumberEx(0);
        ULONG Target  = Thread->NextProcessor;

        // 当前核就是目标核:直接请求软件中断(1)
        if (Current == Target) {
            HalRequestSoftwareInterrupt(1);
            return 1;
        }

        // 构造 IPI packet(这段严格对应汇编里:
        // - KiProcessorIndexToNumberMappingTable
        // - 拆 group/number
        // - KiMask32Array[number]
        // - or 到 mask word
        //
        USHORT Count = 1;
        USHORT Size  = 1;
        ULONG  Mask[1] = {0};

        ULONG Enc =
            KiProcessorIndexToNumberMappingTable[Target];

        ULONG Group  = (Enc >> 6);
        ULONG Number = (Enc & 0x3F);

        if (Count <= Group) {
            Count = (USHORT)(Group + 1);
        }

        Mask[Group] |= KiMask32Array[Number];

        struct {
            USHORT Count;
            USHORT Size;
            ULONG  Mask[1];
        } Packet = { Count, Size, { Mask[0] } };

        KiIpiSend(&Packet);
        return 1;
    }
}

3.1 总结KiInsertQueueApc 做了什么

KiInsertQueueApc 的作用是:

在保证 APC 队列有序性的前提下,把一个 APC 安全地插入目标线程的 APC 队列中,并在必要时唤醒线程或触发中断,使 APC 得以尽快执行。

这个函数分 4 个层次在做事

第一层:决定 APC 要插到「哪个 APC 队列」

关键点

  • 每个线程 有两个 APC 队列:

    • ApcListHead[0] → Kernel APC 队列

    • ApcListHead[1] → User APC 队列

  • 同一个线程 可能处在两种 APC 环境:

    • Original / Attached(正常 / 挂靠进程)

本函数做的事:

  1. 如果 Apc->ApcStateIndex == InsertApcEnvironment (3)

    Thread->ApcStateIndex 纠正它

  2. Thread->ApcStatePointer[ApcStateIndex]

    找到真正要插入的 _KAPC_STATE

总结:这一层只是在"选桶",不插链表

第二层:决定 APC 在队列中的「相对顺序」

APC 在链表中不是随便插的,而是分三类

1. User Normal APC + PsExitSpecialApc(.text:004B13C1 ~ 004B13DA)
  • 条件:

    • NormalRoutine != NULL

    • ApcMode == UserMode

    • KernelRoutine == PsExitSpecialApc

  • 行为:

    • Thread->UserApcPending = 1

    • 插到 User APC 队列头部(头插)

含义:PsExitSpecialApcs是进程退出相关 APC,优先级最高,必须尽快执行

2. 普通 Normal APC(Kernel / User)
  • 条件:

    • NormalRoutine != NULL

    • 但不满足上面的 PsExitSpecialApc

  • 行为:

    • 尾插 到对应的 APC 队列

含义: 普通 APC 按"先来先服务"排队

3. Special APC(NormalRoutine == NULL)
  • 条件
    • NormalRoutine == NULL

行为逻辑:

  • 从队列尾部开始向前遍历

  • 找到第一个 NormalRoutine == NULL 的 APC

  • 把当前 APC 插在它后面

含义:Special APC 会被插入到"Special APC 区段"的末尾,
但始终排在所有 Normal APC之前。

第三层:插完链表后,判断「要不要立刻触发执行」

需要先知道这两个位的作用:

字段 作用范围 精确语义
Thread->KernelApcDisable Kernel APC 的 NormalRoutine 阶段 阶段闸门:禁止在内核态执行 NormalRoutine(不影响 KernelRoutine,本次 APC 仍然完成交付)
Thread->SpecialApcDisable 所有 APC(Kernel + User) 全局闸门:禁止 APC delivery(延迟投递,不执行、不摘队)

插队 ≠ 一定会立刻跑。

这里开始涉及 线程状态 + IRQL + 当前 CPU。

如果目标线程就是当前 CPU 正在跑的线程

  • 只允许 Kernel APC

  • 必须满足:

    • KernelApcDisable == 0

    • SpecialApcDisable == 0

  • 行为:

    • KernelApcPending置1

    • 根据 OldIrq:

      • 触发 软件中断

      • 或打一个延迟执行标志

含义: 正在跑的线程,不能随便被 APC 打断,只在安全窗口触发

如果目标线程在"别的状态 / 别的 CPU"
  • Kernel APC

    • 如果线程处于:

    • Ready / Waiting 等可调度状态

  • 行为:

    • 自旋锁住 ThreadLock

    • 再次确认状态

    • 调用 KiSignalThread

User APC

  • 只在:

    • Thread->State == Waiting

    • WaitMode == UserMode

  • 才允许触发

  • UserApcPending置1

  • 唤醒线程

含义:User APC 永远不会强行打断内核态线程

第四层:如何真正"让 CPU 知道有 APC 要处理"

这是最底层的调度细节:

  • 如果目标线程在 当前 CPU

    • HalRequestSoftwareInterrupt
  • 如果在线程在 其他 CPU

    • 构造 IPI mask
    • KiIpiSend

含义:APC 本质是"调度事件",最终一定要落实到 CPU 中断或 IPI 上

四:总结

KiInsertQueueApc 并不是简单地"把 APC 放进链表",

而是一个 融合了 APC 语义、线程状态、CPU 调度与中断机制的核心函数:

  • 它精确维护 Normal APC 与 Special APC 的执行顺序

  • 它确保 User APC 永远不会在内核态非法执行

  • 它根据线程运行位置,选择 软件中断或跨核 IPI

  • 它是 APC 从"数据结构"走向"真实执行"的关键桥梁

相关推荐
非凡ghost5 小时前
ImageConverter(图像转换编辑工具)
图像处理·人工智能·windows·学习·计算机视觉·软件需求
洛克大航海5 小时前
Python 在系统 Windows 和 Ubuntu 中创建虚拟环境
windows·python·ubuntu·虚拟环境
ZEERO~5 小时前
@dataclass的作用
开发语言·windows·python
雪域迷影6 小时前
Windows11上安装Redis服务和Redis可视化客户端
windows·redis
广然6 小时前
Windows 11 关闭 VBS 的几种方法
windows·ensp·vbs
不染尘.6 小时前
Linux基本概述
linux·windows·centos·ssh
郑泰科技6 小时前
快速地图匹配(FMM)的开源工具与代码示例
c++·windows·python·交通物流
张世争17 小时前
windows clion MingW cmake 编译运行 FreeRTOS
windows·freertos·mingw·cmake·clion
AI 智能服务17 小时前
第6课__本地工具调用(文件操作)
服务器·人工智能·windows·php