Background and motivation
I am using Windows-specific terminology and APIs in this issue, but the broader idea applies to other platforms as well.
To my knowledge it is not possible today to write a "safe" vectored exception handler in C#, as the runtime will crash when an exception occurs in managed code (due to no GC transition occurring):
static void Main()
{
unsafe
{
AddVectoredExceptionHandler(1, &ExceptionHandler);
*ReadOnlyOrGuardedPage = 10;
}
}
[UnmanagedCallersOnly]
static unsafe int ExceptionHandler(EXCEPTION_POINTERS* ex)
{
Console.WriteLine("ExceptionHandler");
return EXCEPTION_CONTINUE_SEARCH;
}
This will exit with:
Fatal error.
Invalid Program: attempted to call a UnmanagedCallersOnly method from managed code.
It is possible to bypass this by not using UnmanagedCallersOnly and passing a managed function pointer to AddVectoredExceptionHandler, but I don't think I need to explain why this is a bad idea (and it also now has the opposite issue: it is dangerous if an exception occurs in unmanaged code now).
Additionally, if you define your vectored exception handler in native code, you still have the same problems if you end up calling back into managed code (i.e. through a function pointer).
My use case for this is high-performance dirty page tracking in a C# port of an emulator (this technique is known colloquially among emulator developers as "fastmem", and is also seen in software like VirtualBox). In C++ I allocate pages with PAGE_READONLY protection and catch write exceptions in a vectored exception handler. This handler deals with notifying page listeners and changing the protection to PAGE_READWRITE to suppress exceptions until the page needs to be tracked again. Write watching through GetWriteWatch and MEM_WRITE_WATCH is insufficient as it is a "query" API rather than a "notification" API, so the extremely chatty usage patterns that would be required to use it lead to worse performance by several orders of magnitude.
This issue is similar in spirit to #12077, though that one is limited to catching access violation exceptions. I have use cases for catching other types as well (such as invalid instruction exceptions), though they are less relevant.
API Proposal
I propose adding a new ExtendedGCTransition attribute that can be applied to UnmanagedCallersOnly methods, which causes them to support being invoked regardless of the current GC mode:
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class ExtendedGCTransitionAttribute : Attribute
{
public ExtendedGCTransitionAttribute()
{
}
}
API Usage
static void Main()
{
unsafe
{
AddVectoredExceptionHandler(1, &ExceptionHandler);
*ReadOnlyOrGuardedPage = 2;
}
}
[ExtendedGCTransition]
[UnmanagedCallersOnly]
static unsafe int ExceptionHandler(EXCEPTION_POINTERS* ex)
{
Console.WriteLine("ExceptionHandler");
return EXCEPTION_CONTINUE_SEARCH;
}
Alternative Designs
This could be exposed as a CallConv type, but I don't believe this would make sense as it is not related to how the method is invoked. It could also be possible for the runtime itself to expose a low-level exception handling callback, as it already registers its own VEH that it can dispatch from.
Risks
This is not a common scenario, and may be challenging to support in the runtime (I am not familiar with what would be required to support this kind of method).
Background and motivation
I am using Windows-specific terminology and APIs in this issue, but the broader idea applies to other platforms as well.
To my knowledge it is not possible today to write a "safe" vectored exception handler in C#, as the runtime will crash when an exception occurs in managed code (due to no GC transition occurring):
This will exit with:
It is possible to bypass this by not using
UnmanagedCallersOnlyand passing a managed function pointer toAddVectoredExceptionHandler, but I don't think I need to explain why this is a bad idea (and it also now has the opposite issue: it is dangerous if an exception occurs in unmanaged code now).Additionally, if you define your vectored exception handler in native code, you still have the same problems if you end up calling back into managed code (i.e. through a function pointer).
My use case for this is high-performance dirty page tracking in a C# port of an emulator (this technique is known colloquially among emulator developers as "fastmem", and is also seen in software like VirtualBox). In C++ I allocate pages with
PAGE_READONLYprotection and catch write exceptions in a vectored exception handler. This handler deals with notifying page listeners and changing the protection toPAGE_READWRITEto suppress exceptions until the page needs to be tracked again. Write watching throughGetWriteWatchandMEM_WRITE_WATCHis insufficient as it is a "query" API rather than a "notification" API, so the extremely chatty usage patterns that would be required to use it lead to worse performance by several orders of magnitude.This issue is similar in spirit to #12077, though that one is limited to catching access violation exceptions. I have use cases for catching other types as well (such as invalid instruction exceptions), though they are less relevant.
API Proposal
I propose adding a new
ExtendedGCTransitionattribute that can be applied toUnmanagedCallersOnlymethods, which causes them to support being invoked regardless of the current GC mode:API Usage
Alternative Designs
This could be exposed as a
CallConvtype, but I don't believe this would make sense as it is not related to how the method is invoked. It could also be possible for the runtime itself to expose a low-level exception handling callback, as it already registers its own VEH that it can dispatch from.Risks
This is not a common scenario, and may be challenging to support in the runtime (I am not familiar with what would be required to support this kind of method).