The Trap of Operating System: Exception Handling (Part 2)

The exception hierarchy of the operating system allows the developers to handle both hardware and software exceptions in a unified way and also improves the modularity of the code being developed. In the previous post we observed how the disassembly of a guarded code (__try, __except) block is different. In this post and explore how the system takes control when the exception happens. It is expected that the reader has some knowledge of C and WinDbg or any other related debugging tool.

Catching Exception In Debugger

Using WinDbg, load the application symbols during loader break  and set a breakpoint on ‘main’ routine 

.reload /f ex1.exe
bp ex1!main

Let’s put a breakpoint on ntdll exception dispatch routine ‘RtlDispatchException’ as well so that we catch the exception flow when OS gives the control back to the application and continue (g command in WinDbg). Since we are intentionally dividing by zero, debugger breaks in. Notice what it says on top – “First Chance”.

First Chance Exception: When the application is being debugged and an exception occurs, debugger get notified. At this point application is suspended and debugger (WinDbg) decides how to handle the exception.

0:000> bp ntdll!RtlDispatchException
0:000> g
(1b0c.2258): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000014 ebx=7f3e5000 ecx=99f63d15 edx=00000000 esi=002bdd3c edi=002bdd40
eip=00266a9e esp=00bdf850 ebp=00bdf87c iopl=0         nv up ei ng nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b             efl=00010286
00266a9e f77d0c         idiv   eax,dword ptr [ebp+0Ch] ss:002b:00bdf888=00000000

BOOL SafeDiv(int dividend, int divisor, int *pResult)
    *pResult = dividend / divisor;

00266a86 50             push   eax
00266a87 8d45f0         lea     eax,[ebp-10h]
00266a8a 64a300000000   mov     dword ptr fs:[00000000h],eax
00266a90 8965e8         mov     dword ptr [ebp-18h],esp
00266a93 c745fc00000000 mov     dword ptr [ebp-4],0
00266a9a 8b4508         mov     eax,dword ptr [ebp+8] >>>>first parameter (dividend) goes in eax (hex 14)
00266a9d 99             cdq
00266a9e f77d0c         idiv   eax,dword ptr [ebp+0Ch] >>>>second parameter (divisor) in 'ebp+0c' location (NULL or Zero)

0:000> ?@ebp+0c
Evaluate expression: 12449928 = 00bdf888

0:000> dd 00bdf888 l1
00bdf888  00000000

0:000> r eax
Let’s say we decide not to handle the exception and to continue while the breakpoint is set on ‘RtlDispatchException’. Debugger breaks-in again when it hits the breakpoint:
0:000> kn
 # ChildEBP RetAddr  
00 00bdf384 77b005bf ntdll!RtlDispatchException
01 00bdf384 00266a9e ntdll!KiUserExceptionDispatcher+0xf
02 00bdf87c 00266b28 Ex1!SafeDiv+0x3e  >>>>>>>>>>>>>>>>>>
03 00bdf894 00266e24 Ex1!main+0x18
04 (Inline) -------- Ex1!invoke_main+0x1d
05 00bdf8e0 75777c04 Ex1!__scrt_common_main_seh+0xff
06 00bdf8f4 77b1ab8f KERNEL32!BaseThreadInitThunk+0x24
07 00bdf93c 77b1ab5a ntdll!__RtlUserThreadStart+0x2f
08 00bdf94c 00000000 ntdll!_RtlUserThreadStart+0x1b
If you observer closely, frame 2 has ‘Ex1!SafeDiv+0x3e’ (00266a9e ) which is the address of instruction where the exception happened. instead of executing the next instruction, we magically end up in ntdll routines.
    • What happened in between?
    • What happened before first chance notification?
0:000> u Ex1!SafeDiv+0x3e l2
00266a9e f77d0c          idiv    eax,dword ptr [ebp+0Ch] >>>>>Exception occurred here
00266aa1 8b4d10          mov     ecx,dword ptr [ebp+10h]>>>>>next instruction in sequence

Exception Handling By Processor 

Every processor has an associated IDT (Interrupt Dispatch Table) to handle exception or interrupts. The IDT is an array of function pointers and each entry in the array corresponds to an event (exception or interrupt). During the boot time OS sets this table. In WinDbg, you can fetch the entries of IDT with ‘!idt’ command as shown below. Figure below shows how the table is laid out.
Exception are also a type of Interrupt. On x86 and x64 machines, all exceptions have predefined interrupt numbers that directly corresponds to the entry in IDT (Interrupt dispatch table) as mentioned above. All entries in IDT are trap handlers and first entry corresponds to the divide by zero exception trap handler which gets called when divide by zero exception happens. Since, I used Win7 x86 where the handler symbols is ‘KiTrap00’. Win 8 and above it is ‘KiDivideErrorFault’.
0: kd> !idt -a

Dumping IDT: 80b95400

d850b41900000000: 82649200 nt!_KiTrap00  >>>divide by zero trap handler
d850b41900000001: 82649390 nt!_KiTrap01
d850b41900000002: Task Selector = 0x0058
d850b41900000003: 82649800 nt!_KiTrap03
d850b41900000004: 82649988 nt!_KiTrap04
d850b41900000005: 82649ae8 nt!_KiTrap05
d850b41900000006: 82649c5c nt!_KiTrap06
d850b41900000007: 8264a258 nt!_KiTrap07
d850b41900000008: Task Selector = 0x0050
d850b41900000009: 8264a6b8 nt!_KiTrap09
d850b4190000000a: 8264a7dc nt!_KiTrap0A


To dig further, we need to start the application in a virtual machine. You can use the steps here to hook a debugger to VM and follow the steps.
Let’s set a breakpoint on trap handler for divide by zero exception (KiTrap00) and continue. When processor encounters the exception it calls the trap handler which generates a trap frame ‘_KTRAP_FRAME’ (differs in x86 and x64). As the result of the user mode exception, we are switching from user mode to kernel mode to handle it.
ba e1 nt!kitrap00
0: kd> kn
 # ChildEBP RetAddr  
00 93d0dd98 01236a9e nt!_KiTrap00  >>>>we hit the trap handler on exception
01 93d0ddc8 00000000 Ex1!SafeDiv+0x3e
Trap Handler is a function specific to a particular exception or interrupt.

In order to get the trap frame we can either look for ‘KiDispatchException’ or just run the command ‘pc’ from our breakpoint on ‘KiTrap00’. You will find the trap frame in ‘ebp’ register as shown below.
1: kd> r
eax=00000023 ebx=0028fde0 ecx=851f0128 edx=00000000 esi=010cdd3c edi=01076a9e
eip=8264d294 esp=9322fd30 ebp=9322fd34 iopl=0         nv up ei pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000246
8264d294 e86fea2400      call    nt!Ki386CheckDivideByZeroTrap (8289bd08)

1: kd> ub 8264d294 
8264d280 6a00            push    0
8264d282 6a00            push    0
8264d284 6a7f            push    7Fh
8264d286 e848900900      call    nt!KeBugCheck2 (826e62d3)
8264d28b 66837d6c1b      cmp     word ptr [ebp+6Ch],1Bh
8264d290 751d            jne     nt!KiTrap00+0xaf (8264d2af)
8264d292 fb              sti
8264d293 55              push    ebp  >>>>parameter to Ki386CheckDivideByZeroTrap 

1: kd> dt 9322fd34 nt!_ktrap_frame
   +0x000 DbgEbp           : 0x28fde0
   +0x004 DbgEip           : 0x1076a9e  
   +0x008 DbgArgMark       : 0xbadb0d00
   +0x00c DbgArgPointer    : 0
   +0x010 TempSegCs        : 0xdd40
   +0x012 Logging          : 0xc ''
   +0x013 Reserved         : 0x1 ''
   +0x014 TempEsp          : 0x10cdd3c
   +0x018 Dr0              : 0xffffffff
   +0x01c Dr1              : 0x28fde0
   +0x020 Dr2              : 0
   +0x024 Dr3              : 0x8264d22d
   +0x028 Dr6              : 8
   +0x02c Dr7              : 0
   +0x030 SegGs            : 0
   +0x034 SegEs            : 0x23
   +0x038 SegDs            : 0x23
   +0x03c Edx              : 0
   +0x040 Ecx              : 0x278b5988
   +0x044 Eax              : 0x14
   +0x048 PreviousPreviousMode : 0x102
   +0x04c ExceptionList    : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : 0x30
   +0x054 Edi              : 0x10cdd40
   +0x058 Esi              : 0x10cdd3c
   +0x05c Ebx              : 0x7ffdf000
   +0x060 Ebp              : 0x28fde0
   +0x064 ErrCode          : 0
   +0x068 Eip              : 0x1076a9e>>>>>>>>>>>>>address of exception
   +0x06c SegCs            : 0x1b
   +0x070 EFlags           : 0x10202
   +0x074 HardwareEsp      : 0x28fdb4
   +0x078 HardwareSegSs    : 0x23
   +0x07c V86Es            : 0
   +0x080 V86Ds            : 0
   +0x084 V86Fs            : 0
   +0x088 V86Gs            : 0

1: kd> u 0x1076a9e   l1
01076a9e f77d0c          idiv    eax,dword ptr [ebp+0Ch]

In the next part of the series will explore creation of the exception record, exception context and how we travel back to user mode.

Leave a Reply

Your email address will not be published. Required fields are marked *