VOID PspQueueApcSpecialApc( IN PKAPC Apc, IN PKNORMAL_ROUTINE *NormalRoutine, IN PVOID *NormalContext, IN PVOID *SystemArgument1, IN PVOID *SystemArgument2 ) { PAGED_CODE();
// ........ // // If APC queuing is disabled or the APC is already inserted, then set // inserted to FALSE. Otherwise, set the system parameter values in the // APC object, insert the APC in the thread APC queue, and set inserted to // true. //
;++ ; ; Macro Description: ; ; This macro is called before returning to user mode. It dispatches ; any pending user mode APCs. ; ; Arguments: ; ; TFrame - TrapFrame ; interrupts disabled ; ; Return Value: ; ;--
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> 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
VOID KiDeliverApc( IN KPROCESSOR_MODE PreviousMode, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame ) /*++ Routine Description: This function is called from the APC interrupt code and when one or more of the APC pending flags are set at system exit and the previous IRQL is zero. All special kernel APC's are delivered first, followed by normal kernel APC's if one is not already in progress, and finally if the user APC queue is not empty, the user APC pending flag is set, and the previous mode is user, then a user APC is delivered. On entry to this routine IRQL is set to APC_LEVEL. N.B. The exception frame and trap frame addresses are only guaranteed to be valid if, and only if, the previous mode is user.
// // If the thread was interrupted in the middle of the SLIST pop code, // then back up the PC to the start of the SLIST pop. //
if (TrapFrame != NULL) { KiCheckForSListAddress(TrapFrame); }
// // Save the current thread trap frame address and set the thread trap // frame address to the new trap frame. This will prevent a user mode // exception from being raised within an APC routine. //
KeMemoryBarrier(); while (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {
// // Raise IRQL to dispatcher level, lock the APC queue, and check // if any kernel mode APC's can be delivered. // // If the kernel APC queue is now empty because of the removal of // one or more entries, then release the APC lock, and attempt to // deliver a user APC. //
// // 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) { // // First entry in the kernel APC queue is a special kernel APC. // Remove the entry from the APC queue, set its inserted state // to FALSE, release dispatcher database lock, and call the kernel // routine. On return raise IRQL to dispatcher level and lock // dispatcher database lock. // RemoveEntryList(NextEntry); Apc->Inserted = FALSE; KeReleaseInStackQueuedSpinLock(&LockHandle); (KernelRoutine)(Apc, &NormalRoutine, &NormalContext, &SystemArgument1, &SystemArgument2); #if DBG if (KeGetCurrentIrql() != LockHandle.OldIrql) { KeBugCheckEx(IRQL_UNEXPECTED_VALUE, KeGetCurrentIrql() << 16 | LockHandle.OldIrql << 8, (ULONG_PTR)KernelRoutine, (ULONG_PTR)Apc, (ULONG_PTR)NormalRoutine); } #endif
} } else { // // First entry in the kernel APC queue is a normal kernel APC. // If there is not a normal kernel APC in progress and kernel // APC's are not disabled, then remove the entry from the APC // queue, set its inserted state to FALSE, release the APC queue // lock, call the specified kernel routine, set kernel APC in // progress, lower the IRQL to zero, and call the normal kernel // APC routine. On return raise IRQL to dispatcher level, lock // the APC queue, and clear kernel APC in progress. // if ((Thread->ApcState.KernelApcInProgress == FALSE) && (Thread->KernelApcDisable == 0)) {
// // Kernel APC queue is empty. If the previous mode is user, user APC // pending is set, and the user APC queue is not empty, then remove // the first entry from the user APC queue, set its inserted state to // FALSE, clear user APC pending, release the dispatcher database lock, // and call the specified kernel routine. If the normal routine address // is not NULL on return from the kernel routine, then initialize the // user mode APC context and return. Otherwise, check to determine if // another user mode APC can be processed. // // N.B. There is no race condition associated with checking the APC // queue outside the APC lock. User APCs are always delivered at // system exit and never interrupt the execution of the thread // in the kernel. // if ((PreviousMode == UserMode) && (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) && (Thread->ApcState.UserApcPending != FALSE)) {
// // Raise IRQL to dispatcher level, lock the APC queue, and deliver // a user mode APC. //
VOID KiInitializeUserApc( IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame, IN PKNORMAL_ROUTINE NormalRoutine, IN PVOID NormalContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) {
// // Transfer the context information to the user stack, initialize the // APC routine parameters, and modify the trap frame so execution will // continue in user mode at the user mode APC dispatch routine. //
try {
// // If the exception frame address is NULL, then the context copy // has been bypassed and the context is already on the user stack. //
if (ExceptionFrame == NULL) {
// // The address of the already copied context record and the real // exception frame address are passed via unused fields in the // trap frame. // // N.B. The context record has been probed for write access. //
// // Compute address of aligned machine frame, compute address of // context record, and probe user stack for writeability. // // 这个结构存放了自陷框架中的临时存放的rip/rsp MachineFrame = (PMACHINE_FRAME)((TrapFrame->Rsp - sizeof(MACHINE_FRAME)) & ~STACK_ROUND); ContextRecord = (PCONTEXT)((ULONG64)MachineFrame - sizeof(CONTEXT)); ProbeForWriteSmallStructure(ContextRecord, sizeof(MACHINE_FRAME) + CONTEXT_LENGTH, STACK_ALIGN); // // Move machine state from trap and exception frames to the context // record on the user stack. // ContextRecord->ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; KeContextFromKframes(TrapFrame, ExceptionFrame, ContextRecord); // 此处将当前的Frame存放在了Context位置上
// // Fill in machine frame information. // 此时的MachineFrame存放的是用户空间的上下文 MachineFrame->Rsp = ContextRecord->Rsp; MachineFrame->Rip = ContextRecord->Rip; }
// // Set the address new stack pointer in the current trap frame and // the continuation address so control will be transferred to the user // APC dispatcher. //
// // Lower the IRQL to PASSIVE_LEVEL, set the exception address to // the current program address, and raise an exception by calling // the exception dispatcher. // // N.B. The IRQL is lowered to PASSIVE_LEVEL to allow APC interrupts // during the dispatching of the exception. The current thread // will be terminated during the dispatching of the exception, // but lowering of the IRQL is required to enable the debugger // to obtain the context of the current thread. //
USHORT SegDs; USHORT SegEs; USHORT SegFs; USHORT SegGs; // // Previous trap frame address. // ULONG64 TrapFrame; // // Saved nonvolatile registers RBX, RDI and RSI. These registers are only // saved in system service trap frames. // ULONG64 Rbx; ULONG64 Rdi; ULONG64 Rsi; // // Saved nonvolatile register RBP. This register is used as a frame // pointer during trap processing and is saved in all trap frames. // ULONG64 Rbp; // // Information pushed by hardware. // // N.B. The error code is not always pushed by hardware. For those cases // where it is not pushed by hardware a dummy error code is allocated // on the stack. // union { ULONG64 ErrorCode; ULONG64 ExceptionFrame; };
// // Copy of the global patch cycle at the time of the fault. Filled in by the // invalid opcode and general protection fault routines. // LONG CodePatchCycle; } KTRAP_FRAME, *PKTRAP_FRAME;
;++ ; ; 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. ; ;--
; ; 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 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 NTSTATUS KiContinue( IN PCONTEXT ContextRecord, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame ) /*++ Routine Description: This function is called to copy the specified context frame to the specified exception and trap frames for the continue system service. Arguments: ContextRecord - Supplies a pointer to a context record. ExceptionFrame - Supplies a pointer to an exception frame. TrapFrame - Supplies a pointer to a trap frame. Return Value: STATUS_ACCESS_VIOLATION is returned if the context record is not readable from user mode. STATUS_DATATYPE_MISALIGNMENT is returned if the context record is not properly aligned. STATUS_SUCCESS is returned if the context frame is copied successfully to the specified exception and trap frames. --*/ {
KIRQL OldIrql; NTSTATUS Status; // // Synchronize with other context operations. // // If the current IRQL is less than APC_LEVEL, then raise IRQL to APC level. // OldIrql = KeGetCurrentIrql(); if (OldIrql < APC_LEVEL) { KfRaiseIrql(APC_LEVEL); } // // If the previous mode was not kernel mode, then use wrapper function // to copy context to kernel frames. Otherwise, copy context to kernel // frames directly. // Status = STATUS_SUCCESS; if (KeGetPreviousMode() != KernelMode) {// 说明当前的系统调用是从用户态发生的 try { KiContinuePreviousModeUser(ContextRecord, ExceptionFrame, TrapFrame);
} except(EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); }
} else { KeContextToKframes(TrapFrame, ExceptionFrame, ContextRecord, ContextRecord->ContextFlags, KernelMode); } // // If the old IRQL was less than APC level, then lower the IRQL to its // previous value. // if (OldIrql < APC_LEVEL) { KeLowerIrql(OldIrql); } return Status; }