Interrupts and exceptions explained (Part 1)
So far we have seen various bugchecks depicted. What I left there is the explanation of how exceptions happen in the first place and how the execution flow reaches KiDispatchException. When some abnormal condition happens such as breakpoint, division by zero or memory protection violation then normal CPU execution flow (code stream) is interrupted (therefore I use the terms “interrupt” and “exception” interchangeably here). The type of interrupt is specified by a number called interrupt vector number. Obviously CPU has to transfer execution to some procedure in memory to handle that interrupt. CPU has to find that procedure, theoretically either having one procedure for all interrupts and specifying interrupt vector number as a parameter or having a table containing pointers to various procedures that correspond to different interrupt vectors. Intel x86 and x64 CPUs use the latter approach which is depicted on the following diagram:
When an exception happens (divide by zero, for example) CPU gets the address of the procedure table from IDTR (Interrupt Descriptor Table Register). This IDT (Interrupt Descriptor Table) is a zero-based array of 8-byte descriptors (x86) or 16-byte descriptors (x64). CPU calculates the location of the necessary procedure to call and does some necessary steps like saving appropriate registers before calling it.
The same happens when some external I/O device interrupts. We will talk about external interrupts later. For I/O devices the term “interrupt” is more appropriate. On the picture above I/O hardware interrupt vector numbers were taken from some dump. These are OS and user-defined numbers. The first 32 vectors are reserved by Intel.
Before Windows switches CPU to protected mode during boot process it creates IDT table in memory and sets IDTR to point to it by using SIDT instruction.
Let me now illustrate this by using a UML class diagram annotated by pseudo-code that shows what CPU does before calling the appropriate procedure. The pseudo-code on the diagram below is valid for interrupts and exceptions happening when the current CPU execution mode is kernel. For interrupts and exceptions generated when CPU executes code in user mode the picture is a little more complicated because the processor has to switch the current user-mode stack to kernel mode stack.
The following diagram is for 32-bit x86 processor (x64 will be illustrated later):

Let’s see all this in some dump. The address of IDT can be found by using !pcr [processor number] command. Every processor on a multiprocessor machine has its own IDT:
0: kd> !pcr 0
KPCR for Processor 0 at ffdff000:
Major 1 Minor 1
NtTib.ExceptionList: f2178b8c
NtTib.StackBase: 00000000
NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 80042000
NtTib.Version: 0005c645
NtTib.UserPointer: 00000001
NtTib.SelfTib: 7ffdf000
SelfPcr: ffdff000
Prcb: ffdff120
Irql: 0000001f
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 8003f400
GDT: 8003f000
TSS: 80042000
CurrentThread: 88c1d3c0
NextThread: 00000000
IdleThread: 808a68c0
DpcQueue:
Every entry in IDT has the type _KIDTENTRY and we can get the first entry for divide by zero exception which has vector number 0:
0: kd> dt _KIDTENTRY 8003f400
+0x000 Offset : 0x47ca
+0x002 Selector : 8
+0x004 Access : 0x8e00
+0x006 ExtendedOffset : 0x8083
By gluing together ExtendedOffset and Offset fields we get the address of the interrupt handling procedure (0×808347ca) which is KiTrap00:
0: kd> ln 0x808347ca
(808347ca) nt!KiTrap00 | (808348a5) nt!Dr_kit1_a
Exact matches:
nt!KiTrap00
We can also see the interrupt trace in raw stack. For example, we can open the kernel dump and see the following stack trace and registers in the output of !analyze -v command:
ErrCode = 00000000
eax=00001b00 ebx=00001b00 ecx=00000000 edx=00000000 esi=f2178cb4 edi=bc15a838
eip=bf972586 esp=f2178c1c ebp=f2178c90 iopl=0 nv up ei ng nz ac po cy
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010293
driver!foo+0xf9:
bf972586 f77d10 idiv eax,dword ptr [ebp+10h] ss:0010:f2178ca0=00000000
STACK_TEXT:
f2178b44 809989be nt!KeBugCheck+0×14
f2178b9c 8083484f nt!Ki386CheckDivideByZeroTrap+0×41
f2178b9c bf972586 nt!KiTrap00+0×88
f2178c90 bf94c23c driver!foo+0xf9
f2178d54 80833bdf driver!bar+0×11c
The dumping memory around ESP value (f2178c1c) shows the values processor pushes when divide by zero exception happens:
0: kd> dds f2178c1c-100 f2178c1c+100
...
...
...
f2178b80 00000000
f2178b84 f2178b50
f2178b88 00000000
f2178b8c f2178d44
f2178b90 8083a8cc nt!_except_handler3
f2178b94 80870828 nt!`string'+0xa4
f2178b98 ffffffff
f2178b9c f2178ba8
f2178ba0 8083484f nt!KiTrap00+0x88
f2178ba4 f2178ba8
f2178ba8 f2178c90
f2178bac bf972586 driver!foo+0xf9
f2178bb0 badb0d00
f2178bb4 00000000
f2178bb8 0000006d
f2178bbc bf842315
f2178bc0 f2178c6c
f2178bc4 f2178bec
f2178bc8 bf844154
f2178bcc f2178c88
f2178bd0 f2178c88
f2178bd4 00000000
f2178bd8 bc150000
f2178bdc f2170023
f2178be0 f2170023
f2178be4 00000000
f2178be8 00000000
f2178bec 00001b00
f2178bf0 f2178c0c
f2178bf4 f2178d44
f2178bf8 f2170030
f2178bfc bc15a838
f2178c00 f2178cb4
f2178c04 00001b00
f2178c08 f2178c90
f2178c0c 00000000 ; ErrorCode
f2178c10 bf972586 driver!foo+0xf9 ; EIP
f2178c14 00000008 ; CS
f2178c18 00010293 ; EFLAGS
f2178c1c 00000000 ; <- ESP before interrupt
f2178c20 0013c220
f2178c24 00000000
f2178c28 60000000
f2178c2c 00000001
f2178c30 00000000
f2178c34 00000000
…
…
…
ErrorCode is not the same as interrupt vector number although it is the same number here (0). I won’t cover interrupt error codes here. If you are interested please consult Intel Architecture Software Developer’s Manual.
If we try to execute !idt extension command it will show you only user-defined hardware interrupt vectors:
0: kd> !idt
Dumping IDT:
37: 80a7d1ac hal!PicSpuriousService37
50: 80a7d284 hal!HalpApicRebootService
51: 89495044 serial!SerialCIsrSw (KINTERRUPT 89495008)
52: 89496044 i8042prt!I8042MouseInterruptService (KINTERRUPT 89496008)
53: 894ea044 USBPORT!USBPORT_InterruptService (KINTERRUPT 894ea008)
63: 894f2044 USBPORT!USBPORT_InterruptService (KINTERRUPT 894f2008)
72: 89f59044 atapi!IdePortInterrupt (KINTERRUPT 89f59008)
73: 89580044 NDIS!ndisMIsr (KINTERRUPT 89580008)
83: 899e7824 NDIS!ndisMIsr (KINTERRUPT 899e77e8)
92: 89f56044 atapi!IdePortInterrupt (KINTERRUPT 89f56008)
93: 89f1e044 SCSIPORT!ScsiPortInterrupt (KINTERRUPT 89f1e008)
a3: 894fa044 USBPORT!USBPORT_InterruptService (KINTERRUPT 894fa008)
a4: 894a3044 cpqcidrv+0×22AE (KINTERRUPT 894a3008)
b1: 89f697dc ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 89f697a0)
b3: 89498824 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 894987e8)
b4: 894a2044 cpqasm2+0×5B99 (KINTERRUPT 894a2008)
c1: 80a7d410 hal!HalpBroadcastCallService
d1: 80a7c754 hal!HalpClockInterrupt
e1: 80a7d830 hal!HalpIpiHandler
e3: 80a7d654 hal!HalpLocalApicErrorService
fd: 80a7e11c hal!HalpProfileInterrupt
In the next part I’m going to cover x64 specifics.
- Dmitry Vostokov -
July 18th, 2008 at 4:22 pm
Interesting the initial interrupt handler code runs in dynamically created code in a nonpagedd pool Ioin block
before setting the irql’s etc and passing control to the real device driver interrupt routine
July 18th, 2008 at 6:44 pm
Interesting, I haven’t yet looked at hardware device intrupt processing only at CPU interrupts