1) APCs允许用户E序和系l元件在一个进E的地址I间内某个线E的上下文中执行代码?br>2) I/O理器用APCs来完成一个线E发L异步的I/O操作。例如:当一个设备驱动调用IoCompleteRequest来通知I/O理器,它已l结束处理一个异步I/OhӞI/O理器排队一个apc到发赯求的U程。然后线E在一个较低IRQLU别Q来执行APC. APC的作用是从系l空间拷贝I/O操作l果和状态信息到U程虚拟内存I间的一个缓冲中?br>3) 使用APC可以得到或者设|一个线E的上下文和挂vU程的执行?/p>
上面是网上找来的Q下面是MSDN上的说明Q?/p>
An asynchronous procedure call (APC) is a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread, the system issues a software interrupt. The next time the thread is scheduled, it will run the APC function. An APC generated by the system is called a kernel-mode APC. An APC generated by an application is called a user-mode APC. A thread must be in an alertable state to run a user-mode APC.
Each thread has its own APC queue. An application queues an APC to a thread by calling the QueueUserAPC function. The calling thread specifies the address of an APC function in the call to QueueUserAPC. The queuing of an APC is a request for the thread to call the APC function.
q先看一下那些重要结构:
kd> dt KTHREAD
nt!KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 KernelStack : Ptr32 Void
+0x024 ThreadLock : Uint4B
+0x028 ApcState : _KAPC_STATE
+0x028 ApcStateFill : [23] UChar
+0x03f ApcQueueable : UChar
+0x040 NextProcessor : UChar
+0x041 DeferredProcessor : UChar
+0x042 AdjustReason : UChar
+0x043 AdjustIncrement : Char
+0x044 ApcQueueLock : Uint4B
+0x048 ContextSwitches : Uint4B
+0x04c State : UChar
+0x04d NpxState : UChar
+0x04e WaitIrql : UChar
+0x04f WaitMode : Char
+0x050 WaitStatus : Int4B
+0x054 WaitBlockList : Ptr32 _KWAIT_BLOCK
+0x054 GateObject : Ptr32 _KGATE
+0x058 Alertable : UChar
+0x059 WaitNext : UChar
+0x05a WaitReason : UChar
+0x05b Priority : Char
+0x05c EnableStackSwap : UChar
+0x05d SwapBusy : UChar
+0x05e Alerted : [2] UChar
+0x060 WaitListEntry : _LIST_ENTRY
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 Queue : Ptr32 _KQUEUE
+0x06c WaitTime : Uint4B
+0x070 KernelApcDisable : Int2B
+0x072 SpecialApcDisable : Int2B
+0x070 CombinedApcDisable : Uint4B
+0x074 Teb : Ptr32 Void
+0x078 Timer : _KTIMER
+0x078 TimerFill : [40] UChar
+0x0a0 AutoAlignment : Pos 0, 1 Bit
+0x0a0 DisableBoost : Pos 1, 1 Bit
+0x0a0 ReservedFlags : Pos 2, 30 Bits
+0x0a0 ThreadFlags : Int4B
+0x0a8 WaitBlock : [4] _KWAIT_BLOCK
+0x0a8 WaitBlockFill0 : [23] UChar
+0x0bf SystemAffinityActive : UChar
+0x0a8 WaitBlockFill1 : [47] UChar
+0x0d7 PreviousMode : Char
+0x0a8 WaitBlockFill2 : [71] UChar
+0x0ef ResourceIndex : UChar
+0x0a8 WaitBlockFill3 : [95] UChar
+0x107 LargeStack : UChar
+0x108 QueueListEntry : _LIST_ENTRY
+0x110 TrapFrame : Ptr32 _KTRAP_FRAME
+0x114 CallbackStack : Ptr32 Void
+0x118 ServiceTable : Ptr32 Void
+0x11c ApcStateIndex : UChar
+0x11d IdealProcessor : UChar
+0x11e Preempted : UChar
+0x11f ProcessReadyQueue : UChar
+0x120 KernelStackResident : UChar
+0x121 BasePriority : Char
+0x122 PriorityDecrement : Char
+0x123 Saturation : Char
+0x124 UserAffinity : Uint4B
+0x128 Process : Ptr32 _KPROCESS
+0x12c Affinity : Uint4B
+0x130 ApcStatePointer : [2] Ptr32 _KAPC_STATE
+0x138 SavedApcState : _KAPC_STATE
+0x138 SavedApcStateFill : [23] UChar
+0x14f FreezeCount : Char
+0x150 SuspendCount : Char
+0x151 UserIdealProcessor : UChar
+0x152 CalloutActive : UChar
+0x153 Iopl : UChar
+0x154 Win32Thread : Ptr32 Void
+0x158 StackBase : Ptr32 Void
+0x15c SuspendApc : _KAPC
+0x15c SuspendApcFill0 : [1] UChar
…………
…………
上面U色部分是APC机制用到的几个字D!Q?/p>
kd> dt _KAPC_STATE
nt!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRYQ每个指针指向_KAPCl构
+0x010 Process : Ptr32 _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
昄Q这里的 ApcListHead 是 APC 队列头。不q这是个大小?2 的数l,说明实际
?每个U程)有两?APC 队列。这是因?APC 函数分ؓ用户 APC 和内?APC 两种Q各?br>各的队列。所谓用?APCQ是指相应的 APC 函数位于用户I间、在用户I间执行Q而内?br>APCQ则相应?APC 函数为内核函数?/p>
SavedApcState也是个_KAPC_STATEl构Q当当前E暂?#8220;挂靠(Attach)”到另一个进E的地址I间的时侯,ApcState拷贝到SavedApcState暂时存放Q?/p>
当然Q还要有状态信息说明本U程当前是处?#8220;原始环境”q是“挂靠环境”Q这是 ApcStateIndex 的作用,代码中ؓ ApcStateIndex的值定义了一U枚丄型:
typedef enum _KAPC_ENVIRONMENT {
OriginalApcEnvironment,
AttachedApcEnvironment,
CurrentApcEnvironment,
InsertApcEnvironment
} KAPC_ENVIRONMENT;
实际可用?ApcStateIndex 的只?OriginalApcEnvironment?AttachedApcEnvironment?/p>
KAPC_STATE 指针数组 ApcStatePointer[2]Q就用ApcStateIndex 的当前gZ标,而数l中的指针则Ҏ情况可以分别指向两个APC_STATE 数据l构中的一个?/p>
kd> dt _KAPC ;APC对象
nt!_KAPC
+0x000 Type : UChar
+0x001 SpareByte0 : UChar
+0x002 Size : UChar
+0x003 SpareByte1 : UChar
+0x004 SpareLong0 : Uint4B
+0x008 Thread : Ptr32 _KTHREAD
+0x00c ApcListEntry : _LIST_ENTRY
+0x014 KernelRoutine : Ptr32 void
+0x018 RundownRoutine : Ptr32 void
+0x01c NormalRoutine : Ptr32 void
+0x020 NormalContext : Ptr32 Void
+0x024 SystemArgument1 : Ptr32 Void
+0x028 SystemArgument2 : Ptr32 Void
+0x02c ApcStateIndex : Char
+0x02d ApcMode : Char
+0x02e Inserted : UChar
KernelRoutine、RundownRoutine、NormalRoutine。其中只?NormalRoutine才指?执行)APC 函数的请求者所提供的函敎ͼ其余两个都是辅助性的Q?/p>
NTKERNELAPI
VOID
KeInitializeApc (
__out PRKAPC Apc,
__in PRKTHREAD Thread,
__in KAPC_ENVIRONMENT Environment,
__in PKKERNEL_ROUTINE KernelRoutine,
__in_opt PKRUNDOWN_ROUTINE RundownRoutine,
__in_opt PKNORMAL_ROUTINE NormalRoutine,
__in_opt KPROCESSOR_MODE ProcessorMode,
__in_opt PVOID NormalContext
);
q个函数主要用来初始化ApcQ_KAPCQ这个结构的Q如果Environment==CurrentApcEnvironment,Apc->ApcStateIndexqKTHREAD中的ApcStateIndex军_Q但Environment不能大于KTHREAD中的ApcStateIndexQ?/p>
最后,APC h的模式ProcessorModeQ但是有个例外,那就是:如果指针NormalRoutine ?0Q那么实际的模式变成?KernelMode。这是因为在q种情况下没有用L间APC函数可以执行Q?唯一得到执行的是KernelRoutineQ?/p>
最后,KeInitializeApc 讄Inserted域ؓFALSE。然而初始化APC对象Qƈ没有把它存放到相应的APC队列中?/p>
NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
__inout PRKAPC Apc,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2,
__in KPRIORITY Increment
);
据APCh的具体情况,有时候要插在队列的前_一般则挂在队列的尾部?/p>
_KiServiceExit:
cli ; disable interrupts
DISPATCH_USER_APC ebp, ReturnCurrentEax
;
; Exit from SystemService
;
EXIT_ALL NoRestoreSegs, NoRestoreVolatile ;q个宏以后再?/p>
DISPATCH_USER_APC macro TFrame, ReturnCurrentEax
local a, b, c
c:
.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)
test byte ptr [TFrame]+TsEflags+2, EFLAGS_V86_MASK/010000h ; is previous mode v86?
jnz short b ; if nz, yes, go check for APC
test byte ptr [TFrame]+TsSegCs,MODE_MASK ; is previous mode user mode?
jz a ; No, previousmode=Kernel, jump out
b: mov ebx, PCR[PcPrcbData+PbCurrentThread]; get addr of current thread
mov byte ptr [ebx]+ThAlerted, 0 ; clear kernel mode alerted
cmp byte ptr [ebx]+ThApcState.AsUserApcPending, 0
je a ; if eq, no user APC pending
mov ebx, TFrame
ifnb <ReturnCurrentEax>;条g宏汇~,如果ReturnCurrentEax参数不ؓI,则编译!
;DISPATCH_USER_APC ebp, ReturnCurrentEaxQ显然这里是~译的!
mov [ebx].TsEax, eax ; Store return code in trap frame
mov dword ptr [ebx]+TsSegFs, KGDT_R3_TEB OR RPL_MASK
mov dword ptr [ebx]+TsSegDs, KGDT_R3_DATA OR RPL_MASK
mov dword ptr [ebx]+TsSegEs, KGDT_R3_DATA OR RPL_MASK
mov dword ptr [ebx]+TsSegGs, 0
endif
;
; Save previous IRQL and set new priority level
;
RaiseIrql APC_LEVEL
push eax ; Save OldIrql
sti ; Allow higher priority ints
;
; call the APC delivery routine.
;
; ebx - Trap frame
; 0 - Null exception frame
; 1 - Previous mode
;
; call APC deliver routine
;
stdCall _KiDeliverApc, <1, 0, ebx> ;1是UserMode
pop ecx ; (ecx) = OldIrql
LowerIrql ecx
ifnb <ReturnCurrentEax> ;同上分析
mov eax, [ebx].TsEax ; Restore eax, just in case
endif
cli
jmp b ; 注意q个循环Q!
ALIGN 4
a:
endm
q段代码主要查:
卛_q回的是否用L间?br>是否有用户APCh正在{待执行
条gW合才用KiDeliverApc真正投递APC。注意代码jmp bQ好像在q回用户I间前KiDeliverApc会被循环调用直到没有user APCQ其实不是的QKiDeliverApc每处理完一个User APC把UserApcPending清零Q所以User APCs在返回用L间时q是只能投递一ơ!KiDeliverApc中用while处理完所有Kernel Mode APCsQ但User Mode APC却只处理一个!事实上User APC的投递是很特D的Q以后会讲到Q而且每次只能投递一ơ!
前面讲过QKTHREAD 中有两个 KAPC_STATE 数据l构Q一个是 ApcStateQ另一个是SavedApcStateQ二者都有APC 队列Q但是要投递的只是ApcState 中的队列?/p>
KiDeliverApc (
IN KPROCESSOR_MODE PreviousMode,//写成DeliverMode不是更好
IN PKEXCEPTION_FRAME ExceptionFrame,//q个参数几乎是0
IN PKTRAP_FRAME TrapFrame
)
q个函数里面q比较复杂,代码不帖了?/p>
参数PreviousMode表示需?#8220;投?#8221;哪一U?APCQ可以是UserModeQ也可以是KernelMode。不q,KernelMode 实表示只要求执行内?APCQ而UserMode 却表C在执行内核 APC 之外再执行用户APC?/p>
The Windows operating system uses three kinds of APCs:
User APCs run strictly in user mode and only when the current thread is in an alertable wait state. The operating system uses user APCs to implement mechanisms such as overlapped I/O and the QueueUserApc Win32 routine. Qrun IRQL = PASSIVE_LEVELQ?br>Normal kernel APCs run in kernel mode at IRQL = PASSIVE_LEVEL. A normal kernel APC preempts all user-mode code, including user APCs. Normal kernel APCs are generally used by file systems and file-system filter drivers.
Special kernel APCs run in kernel mode at IRQL = APC_LEVEL. A special kernel APC preempts user-mode code and kernel-mode code that executes at IRQL = PASSIVE_LEVEL, including both user APCs and normal kernel APCs. The operating system uses special kernel APCs to handle operations such as I/O request completion.
从代码的角度看是q样的:
User APCs
_KAPC.ApcMode==UserMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine!=NULL
Normal kernel APCs
_KAPC.ApcMode==KernelMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine!=NULL
Special kernel APCs
_KAPC.ApcMode==KernelMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine==NULL
有一点它们的_KAPC.KernelRoutine肯定不ؓI。ƈ且,如果NormolRoutine也不为空Q那么KernelRoutine都在NormolRoutine被调用前被调用!Q?/p>
上文中讲到投递User Mode APCs是很Ҏ的,道理很简单,因ؓUser Mode APC是ring3下的回调函数Q显然ring0中的KiDeliverAPCQ)不能像Kernel Mode APC那样直接callQ必要先回到ring3环境下。当然不能像普通情况那栯回(否则回到ring3pȝ调用的地方了Q,只有一个办法,那就是修改TrapFrame Q欺骗系l返?#8220;APC回调函数”QKiInitializeUserApc是q么做的Q?/p>
该函数首先把TrapFrame转化为ContextFrameQ然后移动用hESP指针Q分配大UsizeofQCONTEXTQ?sizeofQKAPC_RECORDQ个字节:
UserStack-> ……
KAPC_RECORD
……
……
CONTEXT
TopOfStack-> ……
KAPC_RECORD是KiInitializeUserApc的最后四个参?/p>
nt!_KAPC_RECORD
+0x000 NormalRoutine : Ptr32 void
+0x004 NormalContext : Ptr32 Void
+0x008 SystemArgument1 : Ptr32 Void
+0x00c SystemArgument2 : Ptr32 Void
CONTEXTl构主要来存放被修改前的TrapFrameQ之所以用CONTEXTl构是跟APC函数q回有关Q?/p>
TrapFrame->HardwareEsp = UserStack;
TrapFrame->Eip = (ULONG)KeUserApcDispatcher;
TrapFrame->ErrCode = 0;
从上面的代码看到实修改了TrapFrameQƈ且返回到的是ring3下的KeUserApcDispatcherQ刚才说的_KAPC_RECORD其实也是它的参数Q真正我们的User APC回调函数是由KeUserApcDispatcher调用的!
.func KiUserApcDispatcher@16
.globl _KiUserApcDispatcher@16
/* Setup SEH stack */
lea eax, [esp+CONTEXT_ALIGNED_SIZE+16]
mov ecx, fs:[TEB_EXCEPTION_LIST]
mov edx, offset _KiUserApcExceptionHandler
mov [eax], ecx
mov [eax+4], edx
/* Enable SEH */
mov fs:[TEB_EXCEPTION_LIST], eax
/* Put the Context in EDI */
pop eax
lea edi, [esp+12]
/* Call the APC Routine */
call eax
/* Restore exception list */
mov ecx, [edi+CONTEXT_ALIGNED_SIZE]
mov fs:[TEB_EXCEPTION_LIST], ecx
/* Switch back to the context */
push 1 ; TestAlert
push edi ;edi->CONTEXTl构
call _ZwContinue@8
;;不会q回到这里的
上面的代码ƈ不难理解Q我们的User APC回调函数q回后,立即调用了ZwContinueQ这是个ntdll中的导出函数Q这个函数又通过pȝ调用q入kernel中的NtContinueQ?/p>
NTSTATUS
; NtContinue (
; IN PCONTEXT ContextRecord,
; IN BOOLEAN TestAlert
; )
;
; Routine Description:
;
; This routine is called as a system service to continue execution after
; an exception has occurred. Its function is to transfer information from
; the specified context record into the trap frame that was built when the
; system service was executed, and then exit the system as if an exception
; had occurred.
;
; WARNING - Do not call this routine directly, always call it as
; ZwContinue!!! This is required because it needs the
; trapframe built by KiSystemService.
;
; Arguments:
;
; KTrapFrame (ebp+0: after setup) -> base of KTrapFrame
;
; ContextRecord (ebp+8: after setup) = Supplies a pointer to a context rec.
;
; TestAlert (esp+12: after setup) = Supplies a boolean value that specifies
; whether alert should be tested for the previous processor mode.
;
; Return Value:
;
; Normally there is no return from this routine. However, if the specified
; context record is misaligned or is not accessible, then the appropriate
; status code is returned.
;
;--
NcTrapFrame equ [ebp + 0]
NcContextRecord equ [ebp + 8]
NcTestAlert equ [ebp + 12]
align dword
cPublicProc _NtContinue ,2
push ebp ;ebp->TrapFrame
;
; Restore old trap frame address since this service exits directly rather
; than returning.
;
mov ebx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address
mov edx, [ebp].TsEdx ; restore old trap frame address
mov [ebx].ThTrapFrame, edx ;
;
; Call KiContinue to load ContextRecord into TrapFrame. On x86 TrapFrame
; is an atomic entity, so we don't need to allocate any other space here.
;
; KiContinue(NcContextRecord, 0, NcTrapFrame)
;
mov ebp,esp
mov eax, NcTrapFrame
mov ecx, NcContextRecord
stdCall _KiContinue, <ecx, 0, eax>
or eax,eax ; return value 0?
jnz short Nc20 ; KiContinue failed, go report error
;
; Check to determine if alert should be tested for the previous processor mode.
;
cmp byte ptr NcTestAlert,0 ; Check test alert flag
je short Nc10 ; if z, don't test alert, go Nc10
mov al,byte ptr [ebx]+ThPreviousMode ; No need to xor eax, eax.
stdCall _KeTestAlertThread, <eax> ; test alert for current thread
;如果User APCs不ؓI,它会讄UserApcPending,
;跟Alertable无关
Nc10: pop ebp ; (ebp) -> TrapFrame
mov esp,ebp ; (esp) = (ebp) -> trapframe
jmp _KiServiceExit2 ; common exit
Nc20: pop ebp ; (ebp) -> TrapFrame
mov esp,ebp ; (esp) = (ebp) -> trapframe
jmp _KiServiceExit ; common exit
stdENDP _NtContinue
NtContinue把CONTEXTl构转化成TrapFrameQ回复原来的陷阱帧)Q然后就从KiServiceExit2处退出系l调用!
;++
;
; _KiServiceExit2 - same as _KiServiceExit BUT the full trap_frame
; context is restored
;
;--
public _KiServiceExit2
_KiServiceExit2:
cli ; disable interrupts
DISPATCH_USER_APC ebp
;
; Exit from SystemService
;
EXIT_ALL ; RestoreAll
KiServiceExit2跟KiServiceExit差不多,只是宏参数的不同!同样如果q有User APC又会q入上文描述的情形,直到没有User APCQ至此才会返回真正发起原始系l调用的地方Q?/p>
本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/better0332/archive/2009/06/29/4306683.aspx