Skip to content

Latest commit

 

History

History
105 lines (92 loc) · 3.48 KB

APC.md

File metadata and controls

105 lines (92 loc) · 3.48 KB
2: kd> !idt

Dumping IDT: ffff9781a924e000
...
1f:	fffff80032bee810 nt!KiApcInterrupt
...

KiApcInterrupt is used to "inject" APC processing in the running thread by invoking interrupt through LAPIC, this is done from

VOID
FASTCALL
KiInsertQueueApc (
    IN PKAPC Apc,
    IN KPRIORITY Increment
    )
    {
    [...]
    
    Thread = Apc->Thread;
    
    [...]
            ThreadState = Thread->State;
            if (ThreadState == Running) {
                RequestInterrupt = TRUE;
            }
     [...]
        if (RequestInterrupt == TRUE) {
            KiRequestApcInterrupt(Thread->NextProcessor);
        }
     }

Prior to calling KiApcInterrupt a CPU and the kernel creates a frame on a stack that contains registers state for an interrupted thread. As you can see in the example below there is no call to KiApcInterrupt from the interrupted function ( ironicaly KiDeliverApc here but can be any ) so this is a genuine interrupt.

1: kd> kn
 # Child-SP          RetAddr           Call Site
00 ffff9781`aa9fd588 fffff800`32ab44e9 nt!KiApcInterrupt
01 ffff9781`aa9fd5b0 fffff800`32ab2c67 nt!KiDeliverApc+0x229
02 ffff9781`aa9fd640 fffff800`32ab2461 nt!KiSwapThread+0x337
03 ffff9781`aa9fd6f0 fffff800`32ab06b7 nt!KiCommitThreadWait+0x101

fffff800`32ab44e3 33c0            xor     eax,eax
fffff800`32ab44e5 440f22c0        mov     cr8,rax <-- no call to KiApcInterrupt, the code was interrupted after this instruction
fffff800`32ab44e9 4c8b45e8        mov     r8,qword ptr [rbp-18h] <-- return address
fffff800`32ab44ed 488b55f0        mov     rdx,qword ptr [rbp-10h]
fffff800`32ab44f1 488b4df8        mov     rcx,qword ptr [rbp-8]
fffff800`32ab44f5 488b45e0        mov     rax,qword ptr [rbp-20h]

Some notes on APC damaging a stack if a caller failes to wait for IRP completion in case of STATUS_PENDING returned by IoCallDriver. Completion APC can change a registers frame pushed on a thread stack during interrupt processing, e.g. to deliver APC by invoking KiApcInterrupt through LAPC. The other scenario that was met in practice when the RDI register had its lower part zeroed is outlined below ( details here http://www.osronline.com/cf.cfm?PageURL=showThread.CFM?link=285110 )

KiDeliverApc()
{

    //
    // RDI == &Thread->ApcState.ApcListHead[KernelMode]
    //
    // IsListEmpty is translated to cmp and je machine instructions
    //
    //  cmp     qword ptr [rdi],rdi
    //  je        nt!KiDeliverApc+Offset_Outside_While
    //
    while( ! IsListEmpty( &Thread->ApcState.ApcListHead[KernelMode] ) 
    {
         //
         // RDI is an invariant in the cycle and points to  
&Thread->ApcState.ApcListHead[KernelMode]
         //
         ApcEntry = RemoveHeadList( &Thread->ApcState.ApcListHead[KernelMode] );
         Apc = CONTAINING_RECORD(ApcEntry, KAPC, ApcListEntry);
         CompletionRoutine = Apc->KernelRoutine;
         ....
         call CompletionRoutine
         {
             //
             // inside CompletionRoutine
             //
             ....
             push ...
             push RDI   // after push RSP == Irp->UserIosb
             push ...
             push ...
             ....
             Irp->UserIosb.Status = STATUS_SUCCESS // this changes the saved RDI
             ....
             pop ...
             pop ...
             pop RDI // the changed RDI is restored from the stack
             pop ...
             ret
         }
         //
         // go to while with RDI having its lower half being zeroed
         //
    }

}