一、插入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 的逻辑概括为以下几个步骤:
- 进入安全环境
-
将 IRQL 提升到 DISPATCH_LEVEL
-
获取目标线程的 ApcQueueLock
- 获取自旋锁(从
0x004B6846到0x004B687A)
-
通过自旋锁确保同一时间只有一个 CPU 修改该线程的 APC 队列
-
这一阶段不涉及任何 APC 语义,仅用于同步
- 检查插入合法性
-
检查线程是否允许插入 APC(ApcQueueable)
-
检查该 APC 是否已经被插入过(Apc->Inserted)
- 准备 APC 参数
-
填充 SystemArgument1 和 SystemArgument2
-
将 Apc->Inserted 标记为 TRUE
- 真正插入 APC
-
调用内部函数 KiInsertQueueApc
-
由该函数将 APC 挂入线程对应的 APC 队列
- 退出并返回结果
-
释放自旋锁,恢复 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(正常 / 挂靠进程)
本函数做的事:
-
如果
Apc->ApcStateIndex == InsertApcEnvironment (3)用
Thread->ApcStateIndex纠正它 -
从
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 从"数据结构"走向"真实执行"的关键桥梁