Managed exception handling is built on Windows OS’s Structured Exception Handling, commonly referred to as SEH (to learn more about SEH, please read Matt Pietrek’s excellent article first). This implies that CLR understands how to interoperate between SEH and managed exception systems, which is a very key point since SEH is based upon the notion of exception codes, while managed exception handling represents an exception using a managed type. Depending upon how, and by whom, the SEH exception is raised, the CLR accordingly maps it to a managed exception.
Note: The following discussion is focused on the Desktop CLR, which runs on Windows OS. While the idea of the discussion is to help understand the concept, it is illustrated using some implementation details that could change in future.
Synchronous exceptions in managed code
When managed code uses the throw keyword to throw an exception, it has already instantiated a managed exception object that will represent the thrown exception. This is passed to the CLR, which sets up some exception related state on the thread and invokes the Kernel32’s RaiseException API to raise the managed exception. The first argument to this API is the SEH exception code for the exception being raised and the CLR passes 0xE0434F4D, the managed exception SEH code.
At this point, the OS comes into the picture and begins to look for an SEH exception handler on the stack of the thread which raised the exception. The CLR registers one of its functions as an exception handler with the OS, to process exceptions arising out of managed code. When it sees the CLR SEH exception code, it knows that a managed exception is being raised and proceeds to look up the thread state to retrieve exception related details (e.g. identify the managed exception object representing the exception being raised).
Thus, in the case of synchronous managed throw, it is easy to map the SEH exception to managed exception type.
Asynchronous exceptions in managed code
Asynchronous exceptions, simply put, are the ones which get raised without an explicit throw. This can happen in managed code if you perform arithmetic operations (e.g. divide by zero exception) or use unsafe managed code that could result in exceptions like Access Violation (AV). The interesting thing about asynchronous exceptions is that they are represented using their unique SEH exception code. For example, AV is represented by 0xC0000005, divide by zero (integer) is represented by 0xC0000094, while divide by zero (floating) is represented by 0xC000008E. Common exceptions are listed here and exception code values can be found in WinNT.h.
When such an exception is raised in managed code, once again the OS begins looking for an exception handler on the stack of the thread where the exception was raised. When CLR’s exception handler is invoked, it understands that the exception in question was not synchronously thrown by managed code since the exception code is not the CLR SEH exception code. Thus, instead of looking up the thread state for exception related details, it proceeds to map the SEH exception to one of the managed exception types. For example, divide by zero exception (both integer and floating point) are represented using System.DivideByZeroException.
On a similar note, when a real AV is generated in managed code, it has the exception code of 0xC0000005. Since managed code has the notion of a null reference, the runtime does few more checks to determine if the AV represents an attempt to use a null reference or not. If it is, then it gets mapped to System.NullReferenceException. Otherwise, it gets mapped to System.AccessViolationException type (in v2.0 and later runtimes).
Exceptions thrown in native code
Another way the CLR gets to see SEH exceptions is when managed code has called into native code using a mechanism like Platform Invocation Services (PInvoke for short). In the example below, the stack grows from top to bottom and blue frames represent managed code, while the reddish frame represents native code:
Assuming Zoo throws a native exception that is not handled in it, the exception will propagate up the stack, as the OS walks up the stack looking for a potential handler for the exception. In such a case, since the next frame on the stack is Bar, which is managed, the OS ends up calling the CLR’s exception handler. Since the SEH exception comes from native code, it could have any exception code. For instance, it could be a C++ exception (which has the SEH exception code of 0xE06D7363) or it could be a SEH exception code specific to the application. The point is that CLR does not know about all the SEH exception codes that exist (and will be created in the future). And only a subset of the existing ones is of interest from the perspective of mapping the code to a specific managed exception type.
Thus, when the native exception is seen by CLR’s exception handler, it will attempt to map the most common SEH exception codes to their corresponding managed exception type (as explained earlier with some examples). For any other SEH exception code that the runtime does not identify, it gets mapped to the System.Runtime.InteropServices.SEHException type.
Subtleties and what happens next?
1) This mapping is done only once when CLR’s exception handler is invoked by the OS (assuming there are no context transitions on the stack) for the first managed frame on the stack from the point of throw of the specific SEH exception.
2) Since this mapping is done based upon SEH exception codes, the CLR knows if a given managed exception instance represents a real native SEH exception or not. For example, CLR knows if a System.AccessViolationException instance represents a real AV or is a regular managed exception thrown by managed code. Why does this matter? Well, it does when the CLR has determine the corruption severity of an exception!
Once the mapping is completed, the CLR begins regular exception handling process of finding a handler for the exception.
– Gaurav Khanna,