一个经典的投放apc结束线程是这样的
KeInitializeApc(exitAPC,ethread,OriginalApcEnvironment,KernelKillThreadRoutine,0,0,KernelMode,0);
KeInsertQueueApc(exitAPC,exitAPC,NULL,2);
1.APC结构
typedef struct _KAPC {
UCHAR Type;
UCHAR SpareByte0;
UCHAR Size;
UCHAR SpareByte1;
ULONG SpareLong0;
struct _KTHREAD *Thread;
LIST_ENTRY ApcListEntry; // 插入线程APC链表
PKKERNEL_ROUTINE KernelRoutine; 内核模式中执行
PKRUNDOWN_ROUTINE RundownRoutine; //线程终止时还有APC没执行会调用这个函数?
PKNORMAL_ROUTINE NormalRoutine; // 这个为0 表示是一个特殊内核APC,否则是一个普通的(又分为内核态的和用户态的)。特殊的位于链表前部,普通的位于后部。 普通的APC,normal和kernel例程都将被调用
PVOID NormalContext;
PVOID SystemArgument1;
PVOID SystemArgument2;
CCHAR ApcStateIndex; // APC环境状态
KPROCESSOR_MODE ApcMode; //内核态or用户态
BOOLEAN Inserted;
} KAPC, *PKAPC, *PRKAPC;
2.关于APC的环境
这个枚举指的是指示线程attach之后 再回来的情形
typedef enum _KAPC_ENVIRONMENT {
OriginalApcEnvironment, //原始的进程环境
AttachedApcEnvironment, //挂靠后的进程环境
CurrentApcEnvironment, // 当前环境
InsertApcEnvironment // 被插入时的环境
} KAPC_ENVIRONMENT;
typedef struct _KAPC_STATE {
LIST_ENTRY ApcListHead[MaximumMode];//线程的apc链表 只有两个 内核态和用户态
struct _KPROCESS *Process; // 线程 挂靠的进程
BOOLEAN KernelApcInProgress; // 线程正在处理APC对象
BOOLEAN KernelApcPending; // 线程有内核apc等待交付
BOOLEAN UserApcPending; // 有用户态的等待
} KAPC_STATE, *PKAPC_STATE, *PRKAPC_STATE;
KTHREAD 中的KAPC_STATE 中有两个 ApcState ,
ApcState和SaveApcState,
ApcStatePointer是个数组,ApcStateIndex是下标
KiAttachProcess中//额 忘说了 已经attach的进程再次attach会BSOD...
KiMoveApcState(&Thread->ApcState, SavedApcState); //当前的APC状态移到Save里,然后初始化apc状态
InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]); //ApcState被初始化
InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]);
Thread->ApcState.KernelApcInProgress = FALSE;
Thread->ApcState.KernelApcPending = FALSE;
Thread->ApcState.UserApcPending = FALSE;
if (SavedApcState == &Thread->SavedApcState) {
Thread->ApcStatePointer[0] = &Thread->SavedApcState; //第一个指向保存的apc状态 原始apc环境
Thread->ApcStatePointer[1] = &Thread->ApcState; //第二个是当前的 挂靠apc环境
Thread->ApcStateIndex = 1; //表示现在的状态指向 指向挂靠状态
}
Dettach时,先派发APC state里面的APC,然后再恢复 //也就是在挂靠过程中线程被插apc 现在要集中解决
while (Thread->ApcState.KernelApcPending &&
(Thread->SpecialApcDisable == 0) &&
(LockHandle.OldIrql < APC_LEVEL)) {
//
// Unlock the thread APC lock and lower IRQL to its previous
// value. An APC interrupt will immediately occur which will
// result in the delivery of the kernel APC if possible.
//释放这个锁将导致 请求APC级别的中断,这样apc将得到释放
KeReleaseInStackQueuedSpinLock(&LockHandle);
KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock,
&LockHandle);
}
省略无关代码,到这里进行恢复
KiMoveApcState(&Thread->SavedApcState, &Thread->ApcState); //恢复了
Thread->SavedApcState.Process = (PKPROCESS)NULL;
Thread->ApcStatePointer[0] = &Thread->ApcState;
Thread->ApcStatePointer[1] = &Thread->SavedApcState;
Thread->ApcStateIndex = 0;
ApcStatePointer这样设计是巧妙的
在KiInsertQueueApc中直接用下标来找当前的环境值,(那是否ApcStateIndex = 0是代表线程在自己的环境中,1在别人的环境中??)
ApcState = Thread->ApcStatePointer[Apc->ApcStateIndex];
所以如果对一个插入别人的线程插入APC,具体插入那个环境,是由APC的环境变量决定的。
0是原本的环境,1是挂靠环境。 如果目标线程挂靠了,线程原本环境是SavedApcState,挂靠环境是ApcState;没有挂靠 原本环境是ApcState,挂靠环境还是ApcState
3.APC的实现过程
VOID
KeInitializeApc
if (Environment == CurrentApcEnvironment) { //当前环境,那Index就是线程的
Apc->ApcStateIndex = Thread->ApcStateIndex;
} else {
ASSERT((Environment <= Thread->ApcStateIndex) || (Environment == InsertApcEnvironment));
Apc->ApcStateIndex = (CCHAR)Environment;
}
KeInsertQueueApc
Thread = Apc->Thread;
KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle);
//升到synch_level获取apc锁
if ((Thread->ApcQueueable == FALSE) || //线程退出时不接受APC
(Apc->Inserted == TRUE)) {
Inserted = FALSE;
} else {
Apc->Inserted = TRUE;
Apc->SystemArgument1 = SystemArgument1;
Apc->SystemArgument2 = SystemArgument2;
KiInsertQueueApc(Apc, Increment);
Inserted = TRUE;
}
//
// Unlock the thread APC queue lock, exit the scheduler, and return
// whether the APC was inserted.
//
KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);
KiExitDispatcher(LockHandle.OldIrql);
KiInsertQueueApc
{
………….
Thread = Apc->Thread;
if (Apc->ApcStateIndex == InsertApcEnvironment) { //被插入线程的环境,这里面赋值
Apc->ApcStateIndex = Thread->ApcStateIndex;
}
ApcState = Thread->ApcStatePointer[Apc->ApcStateIndex];
ApcMode = Apc->ApcMode;
ASSERT (Apc->Inserted == TRUE);
if (Apc->NormalRoutine != NULL) {
if ((ApcMode != KernelMode) && (Apc->KernelRoutine == PsExitSpecialApc)) {//用户模式
Thread->ApcState.UserApcPending = TRUE;
InsertHeadList(&ApcState->ApcListHead[ApcMode],
&Apc->ApcListEntry);
} else {//普通内核模式 插入尾部
InsertTailList(&ApcState->ApcListHead[ApcMode],
&Apc->ApcListEntry);
}
} else {//特殊内核模式 找到最后一个特殊APC 插入 始终保持特殊APC在普通的前面,又要保证插入是按照时间顺序的。
ListEntry = ApcState->ApcListHead[ApcMode].Blink;
while (ListEntry != &ApcState->ApcListHead[ApcMode]) {
ApcEntry = CONTAINING_RECORD(ListEntry, KAPC, ApcListEntry);
if (ApcEntry->NormalRoutine == NULL) {
break;
}
ListEntry = ListEntry->Blink;
}
InsertHeadList(ListEntry, &Apc->ApcListEntry);
}
if (Apc->ApcStateIndex == Thread->ApcStateIndex) {
//是线程有的状态环境 0 or 1 原始或者挂靠的环境,另外两种状态已经在之前解决掉了 现在只有这两种。并且当现在状态相同,可以尝试让apc立即执行起来
//
// If the target thread is the current thread, then the thread state
// is running and cannot change.
//
if (Thread == KeGetCurrentThread()) {//插入的就是当前线程
ASSERT(Thread->State == Running);
//
// If the APC mode is kernel, then set kernel APC pending and
// request an APC interrupt if special APC's are not disabled.
//
if (ApcMode == KernelMode) {//内核态apc 直接请求apc中断
Thread->ApcState.KernelApcPending = TRUE;
if (Thread->SpecialApcDisable == 0) {
KiRequestSoftwareInterrupt(APC_LEVEL);
}
}
return;
}
RequestInterrupt = FALSE;
KiLockDispatcherDatabaseAtSynchLevel();
if (ApcMode == KernelMode) {
Thread->ApcState.KernelApcPending = TRUE;
KeMemoryBarrier();
ThreadState = Thread->State;
if (ThreadState == Running) {//线程正在运行 请求中断
RequestInterrupt = TRUE;
} else if ((ThreadState == Waiting) && //线程处于等待状态,唤醒这个线程 在KiUnwaitThread调用 KiReadyThread 这里面会交付APC
(Thread->WaitIrql == 0) &&
(Thread->SpecialApcDisable == 0) &&
((Apc->NormalRoutine == NULL) ||
((Thread->KernelApcDisable == 0) &&
(Thread->ApcState.KernelApcInProgress == FALSE)))) {
KiUnwaitThread(Thread, STATUS_KERNEL_APC, Increment);
} else if (Thread->State == GateWait) { //门等待 从门等待中拆出来 直接插入备用链表
KiAcquireThreadLock(Thread);
if ((Thread->State == GateWait) &&
(Thread->WaitIrql == 0) &&
(Thread->SpecialApcDisable == 0) &&
((Apc->NormalRoutine == NULL) ||
((Thread->KernelApcDisable == 0) &&
(Thread->ApcState.KernelApcInProgress == FALSE)))) {
GateObject = Thread->GateObject;
KiAcquireKobjectLock(GateObject);
RemoveEntryList(&Thread->WaitBlock[0].WaitListEntry);
KiReleaseKobjectLock(GateObject);
if ((Queue = Thread->Queue) != NULL) {
Queue->CurrentCount += 1;
}
Thread->WaitStatus = STATUS_KERNEL_APC;
KiInsertDeferredReadyList(Thread);
}
KiReleaseThreadLock(Thread);
}
} else if ((Thread->State == Waiting) &&
(Thread->WaitMode == UserMode) &&
(Thread->Alertable || Thread->ApcState.UserApcPending)) {//用户模式 正在等待 并且可以唤醒 调用 KiUnwaitThread
Thread->ApcState.UserApcPending = TRUE;
KiUnwaitThread(Thread, STATUS_USER_APC, Increment);
}
//其他的情况只能等待其他机会执行APC了
//
// Unlock the dispatcher database and request an APC interrupt if
// required.
//
//如果有请求中断 这里执行一个
KiUnlockDispatcherDatabaseFromSynchLevel();
if (RequestInterrupt == TRUE) {
KiRequestApcInterrupt(Thread->NextProcessor);
}
}
return;
}
4.APC的执行过程
APC_LEVEL的中断或者中断从更高级别降下来时,apc得到机会交付。KiDeliverApc分三种不同情况,做处理,逻辑比较简单
VOID
KiDeliverApc (
IN KPROCESSOR_MODE PreviousMode,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame
)
{
if (TrapFrame != NULL) {
KiCheckForSListAddress(TrapFrame);
}
Thread = KeGetCurrentThread();//更换陷阱帧
OldTrapFrame = Thread->TrapFrame;
Thread->TrapFrame = TrapFrame;
Process = Thread->ApcState.Process;
Thread->ApcState.KernelApcPending = FALSE;
if (Thread->SpecialApcDisable == 0) {
KeMemoryBarrier();
while (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {
//只处理当前线程当前状态环境下的dpc
KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);
NextEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
if (NextEntry == &Thread->ApcState.ApcListHead[KernelMode]) {//到这已经空了释放并退出
KeReleaseInStackQueuedSpinLock(&LockHandle);
break;
}
//
// Clear kernel APC pending, get the address of the APC object,
// and determine the type of APC.
//
// N.B. Kernel APC pending must be cleared each time the kernel
// APC queue is found to be non-empty.
//
Thread->ApcState.KernelApcPending = FALSE;
Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
ReadForWriteAccess(Apc);
KernelRoutine = Apc->KernelRoutine;
NormalRoutine = Apc->NormalRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {//特殊的内核历程
//调用apc
RemoveEntryList(NextEntry);
Apc->Inserted = FALSE;
KeReleaseInStackQueuedSpinLock(&LockHandle);
(KernelRoutine)(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
} else {//普通的内核apc
if ((Thread->ApcState.KernelApcInProgress == FALSE) &&
(Thread->KernelApcDisable == 0)) {
RemoveEntryList(NextEntry);
Apc->Inserted = FALSE;
KeReleaseInStackQueuedSpinLock(&LockHandle);
(KernelRoutine)(Apc, //普通的apc 比如nt!KiSuspendThread
&NormalRoutine, //NormalRoutine可能会在这里被改成0,如果没改成0继续执行
&NormalContext,
&SystemArgument1,
&SystemArgument2);
//还要调用Normal
if (NormalRoutine != (PKNORMAL_ROUTINE)NULL) { //比如这可能是是nt!KiSuspendNop 啥也不干直接返回
Thread->ApcState.KernelApcInProgress = TRUE; //在这个apc执行的时候 其他的普通apc不会被交付
KeLowerIrql(0); //这里normal和kernel的irql也不一样,normal在passive运行
(NormalRoutine)(NormalContext,
SystemArgument1,
SystemArgument2);
KeRaiseIrql(APC_LEVEL, &LockHandle.OldIrql);
}
Thread->ApcState.KernelApcInProgress = FALSE;
} else {
KeReleaseInStackQueuedSpinLock(&LockHandle);
goto CheckProcess;
}
}
}
if ((PreviousMode == UserMode) &&
(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) &&
(Thread->ApcState.UserApcPending != FALSE)) {
KeAcquireInStackQueuedSpinLock(&Thread->ApcQueueLock, &LockHandle);
Thread->ApcState.UserApcPending = FALSE;
NextEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
if (NextEntry == &Thread->ApcState.ApcListHead[UserMode]) {
KeReleaseInStackQueuedSpinLock(&LockHandle);
goto CheckProcess;
}
Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
ReadForWriteAccess(Apc);
KernelRoutine = Apc->KernelRoutine;
NormalRoutine = Apc->NormalRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
RemoveEntryList(NextEntry);
Apc->Inserted = FALSE;
KeReleaseInStackQueuedSpinLock(&LockHandle);
(KernelRoutine)(Apc, //用户态时这里很常见的是nt!IopDeallocateApc
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {
KeTestAlertThread(UserMode);
} else {//初始化一个用户态APC,这个函数把一个KAPC_RECORD放入栈中,TrapFrame->Eip = (ULONG)KeUserApcDispatcher; 这是一个ntdll中的函数,
//陷阱帧的东西还不太明白,不能说太细。。以后补充~~
KiInitializeUserApc(ExceptionFrame,
TrapFrame,
NormalRoutine,
NormalContext,
SystemArgument1,
SystemArgument2);
}
}
}
5.APC的调用时机
★.内核代码离开临界区或守护区,调用KiCheckForKernelApcDelivery或者请求APC级别中断
★.KiSwapThread返回以前调用一次KiDeliverApc
★.从系统服务或者异常返回时 调用KiDeliverApc 设置用户态apc的交付
★.APC_LEVEL的中断和irql降到passive时KiDeliverApc都会被调用