SEHException not caught by Try/Catch

Andreas picture Andreas · May 8, 2013 · Viewed 9k times · Source

In a background thread, my application regularly examines a network folder (UNC Path) for application updates. It reads out the assembly version of the file, like so:

Try
    newVers = System.Reflection.AssemblyName.GetAssemblyName("\\server\app.exe").Version
Catch ex As Exception
    ' ignore
End Try

This snippet is executed quite often, in total I'd guess way more than 100.000 times on multiple customer sites so far without a problem.

Sometimes, GetAssemblyName raises a FileNotFoundException, for instance in case the network folder is not reachable (which can happen and must be dealt with). This exception is caught by the Catch block just below, and everything works just fine.

In three reported cases, however, the GetAssemblyName call raised an SEHException. The strange thing is that this exception was not caught by the Catch block just below, but by my global unhandled exception handler (System.AppDomain.CurrentDomain.UnhandledException). As a result, the application crashes.

Here is the exception detail (unfortunately, the ErrorCode and CanResume fields of the exception are not logged by my error handling routine):

Caught exception: System.Runtime.InteropServices.SEHException
   Message: External component has thrown an exception.
   Source: mscorlib
   TargetSite: System.Reflection.AssemblyName nGetFileInformation(System.String)
   StackTrace: 
      at System.Reflection.AssemblyName.nGetFileInformation(String s)
      at System.Reflection.AssemblyName.GetAssemblyName(String assemblyFile)
      at SyncThread.Run() 
      at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
      at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
      at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
      at System.Threading.ThreadHelper.ThreadStart()

Why is is that the exception is not caught by the Catch block just below?

(Maybe this is relevant: this has only happened on customer sites, where the UNC path pointed to a server that was not part of the local network, but a remote server on a VPN.)

Answer

porges picture porges · May 8, 2013

Since .NET 4, some SEHExceptions indicate corrupted process states, and are termed "corrupted state exceptions". These are things like segfaults/access violations, where the exception is thrown after memory corruption has been detected.

While these errors are still mapped back to managed .NET SEHExceptions, they are not catchable by default, so try { ... } catch (Exception ex) { ... } won't handle them.

You can opt-in to handling these exceptions (either via an attribute or a policy change in your app's config file), but it is not recommended, as your program could now be processing invalid data:

The CLR has always delivered SEH exceptions to managed code using the same mechanisms as exceptions raised by the program itself. This isn't a problem as long as code doesn't attempt to handle exceptional conditions that it cannot reasonably handle. Most programs cannot safely continue execution after an access violation. Unfortunately, the CLR's exception handling model has always encouraged users to catch these serious errors by allowing programs to catch any exception at the top of the System.Exception hierarchy. But this is rarely the right thing to do.

Writing catch (Exception e) is a common programming error because unhandled exceptions have serious consequences. But you might argue that if you don't know what errors will be raised by a function, you should protect against all possible errors when your program calls that function. This seems like a reasonable course of action until you think about what it means to continue execution when your process is possibly in a corrupted state. Sometimes aborting and trying again is the best option: nobody likes to see a Watson dialog, but it's better to restart your program than to have your data corrupted.

Programs catching exceptions arising from contexts they don't understand is a serious problem. But you can't solve the problem by using exceptions specifications or some other contract mechanism. And it's important that managed programs be able to receive notification of SEH exceptions because the CLR is a platform for many kinds of applications and hosts. Some hosts, such as SQL Server, need to have total control of their application's process. Managed code that interoperates with native code sometimes must deal with native C++ exceptions or SEH exceptions.

But most programmers who write catch (Exception e) don't really want to catch access violations. They'd prefer that execution of their program stops when a catastrophic error occurs rather than letting the program limp along in an unknown state. This is especially true for programs that host managed add-ins such as Visual Studio or Microsoft Office. If an add-in causes an access violation and then swallows the exception, the host could be doing damage to its own state (or user files) without ever realizing something went wrong.

The article this quote is from (in MSDN magazine) goes into much more detail.

If you do decide to handle these exceptions, there is a lot to consider - as the article states "It's very difficult to write correct code that handles a CSE and continues running the process safely."

In particular, any finally blocks that the exception has passed over have not been executed (so, e.g. any file handles are dangling until collected), and even constrained execution regions may be skipped!


In addition, you should probably report this as a bug to Microsoft, as GetAssemblyName shouldn't be throwing this kind of exception.