Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarchive

archiveJan 3, 2021

Windows SEH (Structured Exception Handling)

How Windows SEH chains work, how debuggers interact with exceptions, common NTSTATUS codes, and the TEB ExceptionList entry point.

SEH (Structured Exception Handler)

SEH is the Windows exception-handling system. You wire it with __try, __except, and __finally. It is a Microsoft C extension for recovering from hardware faults and other exception codes so resources (memory blocks, files) still get released when execution would otherwise die unexpectedly.

SEH mechanisms

  • Exception handlers (__except): blocks that can respond to or dismiss an exception
  • Termination handlers (__finally): blocks that always run, whether an exception forced the exit or not

Normal execution

When an exception fires, the OS first gives the process a chance to handle it. If the process installed SEH for that case, it can recover and continue. If not, the default handler terminates the process.

Under a debugger

If the debuggee raises an exception, the OS notifies the debugger first. The debugger owns almost everything about the debuggee: run/stop, virtual memory, and registers. It is responsible for every fault inside that process. The debuggee's own SEH sits behind the debugger in priority.

What the debugger can do when stopped

  1. Fix it directly — edit code, registers, or memory at the faulting address until the exception is gone.
  2. Pass it back — if the debuggee already has SEH, forward the exception notification and let the process handle it like a normal run.
  3. Fall through to the default handler — if neither debugger nor debuggee handles it (or deliberately declines), the OS default handler ends the debuggee and stops the session.

Exception status codes

#ifndef UMDF_USING_NTSTATUS 
#ifndef WIN32_NO_STATUS 
/*lint -save -e767 */  
#define STATUS_WAIT_0                           ((DWORD   )0x00000000L) 
#define STATUS_ABANDONED_WAIT_0          ((DWORD   )0x00000080L)    
#define STATUS_USER_APC                  ((DWORD   )0x000000C0L)    
#define STATUS_TIMEOUT                   ((DWORD   )0x00000102L)    
#define STATUS_PENDING                   ((DWORD   )0x00000103L)    
#define DBG_EXCEPTION_HANDLED            ((DWORD   )0x00010001L)    
#define DBG_CONTINUE                     ((DWORD   )0x00010002L)    
#define STATUS_SEGMENT_NOTIFICATION      ((DWORD   )0x40000005L)    
#define STATUS_FATAL_APP_EXIT            ((DWORD   )0x40000015L)    
#define DBG_REPLY_LATER                  ((DWORD   )0x40010001L)    
#define DBG_TERMINATE_THREAD             ((DWORD   )0x40010003L)    
#define DBG_TERMINATE_PROCESS            ((DWORD   )0x40010004L)    
#define DBG_CONTROL_C                    ((DWORD   )0x40010005L)    
#define DBG_PRINTEXCEPTION_C             ((DWORD   )0x40010006L)    
#define DBG_RIPEXCEPTION                 ((DWORD   )0x40010007L)    
#define DBG_CONTROL_BREAK                ((DWORD   )0x40010008L)    
#define DBG_COMMAND_EXCEPTION            ((DWORD   )0x40010009L)    
#define DBG_PRINTEXCEPTION_WIDE_C        ((DWORD   )0x4001000AL)    
#define STATUS_GUARD_PAGE_VIOLATION      ((DWORD   )0x80000001L)    
#define STATUS_DATATYPE_MISALIGNMENT     ((DWORD   )0x80000002L)    
#define STATUS_BREAKPOINT                ((DWORD   )0x80000003L)    
#define STATUS_SINGLE_STEP               ((DWORD   )0x80000004L)    
#define STATUS_LONGJUMP                  ((DWORD   )0x80000026L)    
#define STATUS_UNWIND_CONSOLIDATE        ((DWORD   )0x80000029L)    
#define DBG_EXCEPTION_NOT_HANDLED        ((DWORD   )0x80010001L)    
#define STATUS_ACCESS_VIOLATION          ((DWORD   )0xC0000005L)    
#define STATUS_IN_PAGE_ERROR             ((DWORD   )0xC0000006L)    
#define STATUS_INVALID_HANDLE            ((DWORD   )0xC0000008L)    
#define STATUS_INVALID_PARAMETER         ((DWORD   )0xC000000DL)    
#define STATUS_NO_MEMORY                 ((DWORD   )0xC0000017L)    
#define STATUS_ILLEGAL_INSTRUCTION       ((DWORD   )0xC000001DL)    
#define STATUS_NONCONTINUABLE_EXCEPTION  ((DWORD   )0xC0000025L)    
#define STATUS_INVALID_DISPOSITION       ((DWORD   )0xC0000026L)    
#define STATUS_ARRAY_BOUNDS_EXCEEDED     ((DWORD   )0xC000008CL)    
#define STATUS_FLOAT_DENORMAL_OPERAND    ((DWORD   )0xC000008DL)    
#define STATUS_FLOAT_DIVIDE_BY_ZERO      ((DWORD   )0xC000008EL)    
#define STATUS_FLOAT_INEXACT_RESULT      ((DWORD   )0xC000008FL)    
#define STATUS_FLOAT_INVALID_OPERATION   ((DWORD   )0xC0000090L)    
#define STATUS_FLOAT_OVERFLOW            ((DWORD   )0xC0000091L)    
#define STATUS_FLOAT_STACK_CHECK         ((DWORD   )0xC0000092L)    
#define STATUS_FLOAT_UNDERFLOW           ((DWORD   )0xC0000093L)    
#define STATUS_INTEGER_DIVIDE_BY_ZERO    ((DWORD   )0xC0000094L)    
#define STATUS_INTEGER_OVERFLOW          ((DWORD   )0xC0000095L)    
#define STATUS_PRIVILEGED_INSTRUCTION    ((DWORD   )0xC0000096L)    
#define STATUS_STACK_OVERFLOW            ((DWORD   )0xC00000FDL)    
#define STATUS_DLL_NOT_FOUND             ((DWORD   )0xC0000135L)    
#define STATUS_ORDINAL_NOT_FOUND         ((DWORD   )0xC0000138L)    
#define STATUS_ENTRYPOINT_NOT_FOUND      ((DWORD   )0xC0000139L)    
#define STATUS_CONTROL_C_EXIT            ((DWORD   )0xC000013AL)    
#define STATUS_DLL_INIT_FAILED           ((DWORD   )0xC0000142L)    
#define STATUS_FLOAT_MULTIPLE_FAULTS     ((DWORD   )0xC00002B4L)    
#define STATUS_FLOAT_MULTIPLE_TRAPS      ((DWORD   )0xC00002B5L)    
#define STATUS_REG_NAT_CONSUMPTION       ((DWORD   )0xC00002C9L)    
#define STATUS_HEAP_CORRUPTION           ((DWORD   )0xC0000374L)    
#define STATUS_STACK_BUFFER_OVERRUN      ((DWORD   )0xC0000409L)    
#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD   )0xC0000417L)    
#define STATUS_ASSERTION_FAILURE         ((DWORD   )0xC0000420L)    
#define STATUS_ENCLAVE_VIOLATION         ((DWORD   )0xC00004A2L)    
#define STATUS_INTERRUPTED               ((DWORD   )0xC0000515L)    
#define STATUS_THREAD_NOT_RUNNING        ((DWORD   )0xC0000516L)    
#define STATUS_ALREADY_REGISTERED        ((DWORD   )0xC0000718L)    
#if defined(STATUS_SUCCESS) || (_WIN32_WINNT > 0x0500) || (_WIN32_FUSION >= 0x0100) 
#define STATUS_SXS_EARLY_DEACTIVATION    ((DWORD   )0xC015000FL)    
#define STATUS_SXS_INVALID_DEACTIVATION  ((DWORD   )0xC0150010L)    
#endif

STATUS_ACCESS_VIOLATION (DWORD)0xC0000005L

Raised when code touches memory that does not exist or that it has no rights to.

MOV DWORD PTR DS:[0], 1
=> 메모리 주소 0은 할당된 영역이 아니다.
 
ADD DWORD PTR DS:[0x401000], 1
=> .text 섹션의 시작 주소 0x401000은 READ 속성만을 가지고 있다. (WRITE 속성 없음)
 
XOR DWORD PTR DS:[80000000], 1234
=> 메모리 주소 0x80000000 은 Kernel 영역이라 user 모드에선 접근 불가

STATUS_BREAKPOINT (DWORD)0x80000003L

When a breakpoint sits in executable code, the CPU raises EXCEPTION_BREAKPOINT as it reaches that address. Debuggers build their BP feature on that exception.

How software breakpoints work

INT 3

The breakpoint instruction is INT 3. Its IA-32 encoding is 0xCC. When the CPU hits INT3, it raises EXCEPTION_BREAKPOINT. Debuggers often treat a temporary user BP as invisible in the UI; confirming the 0xCC patch usually means dumping the process and inspecting that address.

STATUS_ILLEGAL_INSTRUCTION ((DWORD)0xC000001DL)

Raised when the CPU cannot decode an instruction.

0FFF

That encoding is not a defined x86 instruction, so you get EXCEPTION_ILLEGAL_INSTRUCTION.

STATUS_INTEGER_DIVIDE_BY_ZERO (DWORD)0xC0000094L

Integer division with a zero divisor. Easy to hit in application code when a denominator variable becomes zero.

SEH chain

Handlers form a chain. If the first handler declines, control moves to the next until someone handles it. Mechanically, SEH is a linked list of EXCEPTION_REGISTRATION_RECORD structures.

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
     PEXCEPTION_REGISTRATION_RECORD Next;
     PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

Next points at the following record; Handler is the exception filter/function. Next == 0xFFFFFFFF marks the end of the list.

./SEH_CHAIN.png
./SEH_CHAIN.png

With three registrations, a raised exception walks handlers (A) → (B) → (C) until one resolves it.

SEH handler signature

EXCEPTION_DISPOSITION

EXCPETION_DISPOSITION _except_handler (
	EXCEPTION_RECORD               *pRecord,
	EXCEPTION_REGISTRATION_RECORD  *pFrame
	CONTEXT                        *pContext,
	PVOID                           pValue
);

The handler takes four parameters and returns an EXCEPTION_DISPOSITION enum value. The system calls it as a callback; the parameters carry the exception context.

EXCEPTION_RECORD

typedef struct _EXCEPTION_RECORD {
  DWORD                    ExceptionCode;                // 예외 코드
  DWORD                    ExceptionFlags;
  struct _EXCEPTION_RECORD *ExceptionRecord;
  PVOID                    ExceptionAddress;             // 예외 발생 주소
  DWORD                    NumberParameters;
  ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
  • ExceptionCode: which exception fired
  • ExceptionAddress: faulting code address

CONTEXT

typedef struct _CONTEXT {
  DWORD64 P1Home;
  DWORD64 P2Home;
  DWORD64 P3Home;
  DWORD64 P4Home;
  DWORD64 P5Home;
  DWORD64 P6Home;
  DWORD   ContextFlags;
  DWORD   MxCsr;
  WORD    SegCs;
  WORD    SegDs;
  WORD    SegEs;
  WORD    SegFs;
  WORD    SegGs;
  WORD    SegSs;
  DWORD   EFlags;
  DWORD64 Dr0;
  DWORD64 Dr1;
  DWORD64 Dr2;
  DWORD64 Dr3;
  DWORD64 Dr6;
  DWORD64 Dr7;
  DWORD64 Rax;
  DWORD64 Rcx;
  DWORD64 Rdx;
  DWORD64 Rbx;
  DWORD64 Rsp;
  DWORD64 Rbp;
  DWORD64 Rsi;
  DWORD64 Rdi;
  DWORD64 R8;
  DWORD64 R9;
  DWORD64 R10;
  DWORD64 R11;
  DWORD64 R12;
  DWORD64 R13;
  DWORD64 R14;
  DWORD64 R15;
  DWORD64 Rip;
  union {
    XMM_SAVE_AREA32 FltSave;
    NEON128         Q[16];
    ULONGLONG       D[32];
    struct {
      M128A Header[2];
      M128A Legacy[8];
      M128A Xmm0;
      M128A Xmm1;
      M128A Xmm2;
      M128A Xmm3;
      M128A Xmm4;
      M128A Xmm5;
      M128A Xmm6;
      M128A Xmm7;
      M128A Xmm8;
      M128A Xmm9;
      M128A Xmm10;
      M128A Xmm11;
      M128A Xmm12;
      M128A Xmm13;
      M128A Xmm14;
      M128A Xmm15;
    } DUMMYSTRUCTNAME;
    DWORD           S[32];
  } DUMMYUNIONNAME;
  M128A   VectorRegister[26];
  DWORD64 VectorControl;
  DWORD64 DebugControl;
  DWORD64 LastBranchToRip;
  DWORD64 LastBranchFromRip;
  DWORD64 LastExceptionToRip;
  DWORD64 LastExceptionFromRip;
} CONTEXT, *PCONTEXT;

CONTEXT snapshots CPU registers. That matters under multi-threading: each thread owns a CONTEXT. When the scheduler leaves a thread, it saves registers into that structure; when it returns, it restores them and resumes at the saved instruction pointer.

Multi-threading

The CPU time-slices across threads. Intervals are short enough that several threads appear concurrent. Priority changes how often a thread gets the core.

On an exception, the faulting thread stops and SEH runs. The OS passes a pointer to that thread's CONTEXT into the handler. On 32-bit layouts, Eip lives at a fixed offset in the structure (the notes mark offset 0xB8). If the handler rewrites CONTEXT.Eip (or the 64-bit Rip) and returns appropriately, the paused thread resumes at the new address.

EXCEPTION_DISPOSITION

typedef enum _EXCEPTION_DISPOSITION
{
         ExceptionContinueExecution = 0,
         ExceptionContinueSearch = 1,
         ExceptionNestedException = 2,
         ExceptionCollidedUnwind = 3
} EXCEPTION_DISPOSITION;

Return ExceptionContinueExecution (0) after handling to resume at the faulting code. Return ExceptionContinueSearch (1) to hand the exception to the next chain entry.

TEB.NtTib.ExceptionList

Reach the process SEH chain through the Thread Environment Block's NtTib member.

0:003> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

ExceptionList is the first TEB member. On x86, the TEB base is what FS points at:

TEB.NtTib.ExceptionList = FS:[0]

related

  1. Jan 3, 2021/archiveWindows SEH chain and TEB ExceptionList
  2. Jan 3, 2021/archiveTEB (Thread Environment Block)
  3. Apr 1, 2021/archiveHyper-V Ubuntu 20.04 Full-Screen Fix

graphfeed