Win32 Structured Exception Handling

(or, How to handle a GPF programmatically)


Introduction

First things first: Stop reading right now, because unless you've read Chapter 9 ("Crash Handler's") of "Debugging Applications" by John Robbins, and / or Matt Pietrek's January 1997 MSJ column then the rest of this article may not make much sense. However, if you want a quick overview of what I'm talking about, here it is: 

There are Win32 API calls you can make to install a callback procedure that gets called when your application GPF's, and in your callback procedure you can decide how/if you want to handle the GPF.

Well, it's not quite like that, but close enough that you get the idea. Basically, every process in a Win32 application has a operating-system supplied structured exception handler (SEH).  It's the SEH that is responsible for displaying the error box to the user saying that the application has terminated, and if you have a system debugger installed then it's the SEH that gives you the option to debug your application.

The SEH functionality that is provided by many C++ compiler's (including VC++) is a wrapper around the SEH provided by the OS.  By using the C++ keywords __try, __except and __finally you are inserting a SEH procedure before the OS supplied one. By using another Win32 API call, you can insert yet another procedure at the front of the list.  

However, if you are using C++ - which most of the readers of this article won't - then you need to be aware of the problems in inherent in using OS-supplied SEH with compiler-provided SEH. The big problem is that when your own SEH procedure unwinds out of an exception it won't call any class destructors, so if a constructor allocated memory or other resources then skipping the destructor will result in memory leaks, and possibly much worse.

Be aware that MSDN assumes you are using C++ and uses  __try, __except and __finally in quite a few of it's examples.  Also, some of the functions listed in MSDN (AbnormalTermination, GetExceptionCode and GetExceptionInformation) aren't actual functions that are exported by the OS that a CW program can call - they're simply #defines inside of EXCPT.H 

Installing the SEH procedure

To install your SEH procedure you need to use SetUnhandledExceptionFilter.  It takes 1 parameter, the address of the new SEH procedure that will be executed, and returns the address of the next SEH procedure in the chain.

  Program
  Map
    New_SEH_Proc(ULong),Long,Pascal
    Module('Win32API')
      SetUnhandledExceptionFilter(Ulong),Ulong,Pascal
    End  
  End
Previous_SEH_Proc   ULong
  Code
  Previous_SEH_Proc = SetUnhandledExceptionFilter(Address(New_SEH_Proc))
  

The new SEH procedure, New_SEH_Proc(), takes one parameter (a pointer to a group of 2 pointers) and returns a value indicating what happens next (continue with the program or continue with the next SEH in the chain).

Inside the SEH procedure

Because Clarion is a pointer-deficient language you have to use MemCpy inside of your filter procedure to dereference the pointer that was passed in.  Once it's dereferenced you have 2 more pointers to deal with, again using MemCpy.  The first is a pointer to a ExceptionRecord, which contains information on where the exception occurred, what type of exception it was, etc etc.  The second parameter is a pointer to a Context structure, which holds the current values of all the CPU register's, including the floating-point processor.  

However, there is another thing to be aware of - if the exception is EXCEPTION_STACK_OVERFLOW the only register known to be accurate is the instruction pointer, EIP.  If the stack is damaged it's going to limit the ability of your SEH procedure to handle the exception, so you need to check the type of exception as soon as possible and degrade your SEH procedure gracefully if a stack overflow occurred.

New_SEH_Proc Procedure(Prm:Ptr)
Loc:EP                  Like(EXCEPTION_POINTER)
Loc:ER                  Like(EXCEPTION_RECORD)
Loc:CR                  Like(CONTEXT)
  
  Code
  If Prm:Ptr <> Null
    MemCpy(Address(Loc:EP),Prm:Ptr,Size(Loc:EP))
    If Loc:EP:PtrToExceptionRecord <> Null
      MemCpy(Address(Loc:ER),Loc:EP:PtrToExceptionRecord,Size(Loc:ER))
      If Loc:EP:PtrToContextRecord <> Null
        MemCpy(Address(Loc:CR),Loc:EP:PtrToContextRecord,Size(Loc:CR))
        !
        ! Loc:ER contains a exception record, which details what type of
        ! exception this is and where in memory it occurred.
        !
        ! Loc:CR contains a context record, which contains values for
        ! all of the CPU registers.
      End
    End
  End      
  Return(EXCEPTION_EXECUTE_HANDLER)

User Exceptions

GPF's are not the only times a SEH procedure will be called.  It appears to be a little-known fact that your own application can raise it's own exceptions via the RaiseException procedure. By calling RaiseException your procedure can specify it's own exception code, whether it's continuable (fatal) and it's own parameters (up to 15) to the exception.

The following code generates a continuable user exception numbered 123 with no parameters:

  Program
  Map
    Module('Win32API')
      RaiseException(DWord,DWord,DWord,ULong),Pascal
    End  
  End
  Code
  RaiseException(123,0,Null,Null)

Debugging your SEH procedure

You can't :)

Because of the way that the Win32 debug API works, the debugger will get passed the exception before your SEH procedure.  That includes OS exceptions, such as GPF's, and user exceptions caused by the RaiseException procedure. It's even documented in MSKB as article Q173652 - Bug: Unhandled Exception Filter not called inside Debugger

SEH & the Clarion Run-Time Library

You may think that you can use most Clarion RTL functions inside of a SEH procedure.  Think again.

What RTL functions you can call inside a SEH procedure depends on what the individual exception is, and what state the RTL is in when the SEH procedure is called.  Unfortunately there is no way of knowing which exceptions are safe to allow RTL calls, or to determine what the RTL state is. There's some basic functionality you have to use in order to get the exception information in the first place (IE use MemCpy to dereference the pointers) but even the demo SEH procedure I showed above could avoid using memory by using global variables instead of local's.

I have it on good authority that the RTL uses SEH internally, by manipulating the FS register (as described in the January 1997 MSJ column I told you to read earlier). Although I can't find any proof of this (the debugger doesn't show segment registers), it shouldn't matter - because your SEH procedure is installed last, once your program has begun execution, it takes first place in the SEH chain and gets called first when an exception occurs.  The RTL SEH procedure will only kick in if your SEH procedure fails to handle the exception (IE you only handle user defined exceptions created with RaiseException and pass all other exceptions on down the chain)

Download

CW_SEH.ZIP (219K)


Back to the home page