.NET Internals Cookbook Part 1 — Exceptions, filters and corrupted processes

This is the first part of the .NET Internals Cookbook series. For your convenience you can find other parts in the table of contents in Part 0 – Table of contents

1. What happens when you throw something which does not inherit from System.Exception? Was it different in .NET 1?

First, you should ask „how to do that”? Beside C# there are other languages running on CLR. One of them is C++/CLI which is a managed version of C++ language. In C++ you can throw anything — integer, string, byte etc. If you try doing that in C++/CLI you are effectively throwing something which does not derive from System.Exception.

The object you throw is wrapped in System.Runtime.CompilerServices.RuntimeWrappedException so you can still catch it with normal catch(Exception e) {} block.

However, before .NET 2 it was different. The thrown object was not wrapped so you couldn’t catch it in this way. You had to use untyped version of catch block in the form of catch {}. Because of that, you could see code like this:

After migrating to .NET 2 this code doesn’t compile anymore because the last catch clause will never be executed. You can either modify the code or restore old behavior using RuntimeCompatibilityAttribute.

2. How to swallow ThreadAbortException?

You can catch it easily with an exception block but if you don’t reset it it gets rethrown automatically. To reset it, you need to call Thread.ResetAbort() method.

This means that calling Thread.Abort() guarantees nothing – the target thread can catch and swallow the exception, effectively stopping the thread from terminating.

But that’s not all. ThreadAbortException cannot be thrown in finally block which means that you have no guarantee that you even stop the thread from doing its current task. This pattern is pretty common with CER where the try block is empty and everything happens in finally.

So how does the Thread.Abort works under the hood? There are few steps:

  1. Suspend OS thread.
  2. Set metadata bit indicating that abort was requested.
  3. Add APC to the queue.
  4. Resume the thread.

At this point the thread works again and when it gets to alertable state it executes the APC and in turn checks the flag. But what happens if the thread doesn’t get to the alertable state? In that case .NET can hijack the thread and modify its IP register directly. See this post for some more details.

Not to mention that Thread.Abort is not available in .NET Core.

3. How to catch AccessViolationException and similar? How was it working in .NET 1?

You can use HandleProcessCorruptedStateExceptionsAttribute attribute to mark methods where you want to handle those exceptions. This is not a simple thing so you probably should avoid it but it is possible. See this article for more details.

Interesting thing is that this exception was not available before .NET 2. What’s more, in .NET 2 you could just catch it with catch block. You can still restore old behavior by using legacyNullReferenceExceptionPolicy and legacyCorruptedStateExceptionPolicy attributes. Also, behavior depends on the compiling environment, not execution one so if you run older binary using latest CLR you still get older behavior.

4. Is it possible that finally block is not executed? Is it possible that only some of them are not executed?

For both parts the answer is YES.

First, if someone kills the application then finally is not executed at all, but that is obvious. What is not so obvious is that it also happens when you terminate application by calling Environment.FailFast() method. But there are other cases when it is not executed — StackOverflowException, unhandled exceptions etc.

The answer for the second part is even more tricky. Let’s grab this code:

Is it possible that we see Method 1 but not Method 2 in the console? The answer is yes. In case of access violation exceptions the normal blocks are not executed. If you mark Method1 with HandleProcessCorruptedStateExceptionsAttribute you can get the described effect. See this:

The output is:

5. What is fault?

There are 3 exception handling blocks in C#: try, catch and finally. IL supports additional one: fault. This block is executed always if there was an exception. You cannot use it directly from C#, however, there are tricks to get it emitted by the compiler.

Compile this code with ILasm to see the effect:

6. What is an exception filter? Is it just a syntax sugar?

Instead of catching the exception and then checking it contents to see if we should handle it (and then rethrowing), we can check the exception before catching it. The difference is that exception filter is executed in the first pass of the two pass exception mechanism. See Part 2 — Handling and rethrowing exceptions in C# to see the differences when it comes to the debugging scenarios.

Take this code:

If you decompile it you will get this:

You can clearly see that it uses filter under the hood, not some logic inside the catch handler.