ExceptionHandler in .NET - c#

Good day to all!
I copy a method with use of Mono.Cecil, and have faced the following problem:
My function for copying is simple (it's for example):
public static bool Ping()
{
MessageBox.Show("Ping");
try
{
MessageBox.Show("status");
}
catch (Exception exception)
{
MessageBox.Show("Exception ");
return false;
}
return true;
}
It's work not problem. But if I do:
MessageBox.Show("Exception " + exception.Message);
I have the trouble: when execution reaches the function call, it's generate "Unhandled Exception" and clr finishes work. I can't even see MessageBox.Show("Ping")!
I copy try/catch blocks so:
foreach (ExceptionHandler exh in SourceMethod.Body.ExceptionHandlers)
{
var ex = new ExceptionHandler(exh.HandlerType)
{
TryStart = exh.TryStart,
TryEnd = exh.TryEnd,
HandlerStart = exh.TryEnd,
HandlerEnd = exh.HandlerEnd,
CatchType = Assembly.MainModule.Import(typeof(Exception)),
HandlerType = exh.HandlerType,
FilterStart = exh.FilterStart
};
target.Body.ExceptionHandlers.Add(ex);
}
And I think my problem is here:
CatchType = Assembly.MainModule.Import(typeof(Exception)),
But I do not know how to make this properly
I tryed:
CatchType = exh.CatchType
But unfortunate.
How to solve this situation? Have any ideas?

Most likely you are generating invalid IL and the runtime is detecting that and not even starting to run your program (whence you don't see the ping).
You can check that (on Windows at least) by running:
peverify path-to-your-assembly
That being said, you can see a full example here: https://cecilifier.me/?gistid=fd87051cdca0860916a171c419a30f80

Related

Exception in loaded plugin crashes parent application

I'm writing a .NET 6 application for which users can create plugins. In some situations however, when a plugin throws an unhandled exception, my own application crashes as well. That should not happen, no matter what. The plugin may stop working, it may unload, it may whatever, just leave the parent app alive.
Loading happens like this:
public static ServiceInfo? LoadService(string relativePath)
{
var loadContext = new ServiceLoadContext(relativePath);
_alcs.Add(loadContext);
try
{
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(relativePath)));
var shouldLoadDll = false;
foreach (var type in assembly.GetTypes())
{
if (typeof(IMonitorableService).IsAssignableFrom(type))
{
var directoryName = new FileInfo(relativePath).Directory!.FullName;
if (Activator.CreateInstance(type, new object[] { directoryName }) is IMonitorableService result)
{
shouldLoadDll = true;
return new ServiceInfo
{
Category = Directory.GetParent(relativePath)!.Name,
Name = Path.GetFileNameWithoutExtension(relativePath),
AssemblyPath = relativePath,
Service = result!
};
}
}
}
if (!shouldLoadDll)
{
loadContext.Unload();
}
}
catch (Exception)
{
// This is handled, but this won't catch the exception in the plugin
}
return null;
}
I have my share of try/catch phrases, and since these IMonitorableServices are BackgroundServices, they're started like
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var service in _options.Services)
{
try
{
await service.Service.StartAsync(cancellationToken);
}
catch (Exception ex)
{
// This is handled, but it won't catch the exception in the plugin
}
}
}
Now I doubt that it's really relevant to provide the specific error, but just in case: it's a
'System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute',
following an operation on event subscriptions. I know how to solve that in the plugin, but I could never trust my future plugin writers to always handle their exceptions (or prevent them from happening). I need some way to catch absolutely everything in my own application. I've been breaking my head over this and I can find many considerations on plugins loaded in AppDomains, but they're from the .NET Framework era...
Who has an idea how to solve this? I could hardly imagine this is something that has been overlooked in .NET Core/6 development.
Update: I find that other type of exceptions actually are caught within the StartAsync method. So it might have something to do with the exception being raised from an event in the plugin (don't want to put you on the wrong track though). I must add, the event is registered from within the StartAsync method, but it seems to bypass the regular catch.

C# Recreating an exception for testing

How would I go about recreating a "The specified network name is no longer available" exception for testing.
The following code below is trying to copy a file on the same network. If the connection to the network is lost, I would like to recall the CopyFile method and try running it after a couple seconds before throwing the exception. What would be the most easiest way to test this exception?
private void CopyFile()
{
int numberOfExecution = 0;
bool copying = true;
while (copying)
{
copying = false;
try
{
File.Copy(file1, file2);
}
catch (Exception ex)
{
if (ex.Message.Contains("The specified network name is no longer available"))
{
numberOfExecution += 1;
System.Threading.Thread.Sleep(5000);
if (numberOfExecution >= 5)
{
throw ex;
}
copying = true;
}
else
{
throw ex;
}
}
finally
{
}
}
}
The idea is to create a test where File.Copy resolves to a static method you've set up for testing purposes and not to System.IO.File.Copy. In other words, you mock File.Copy.
You can tailor this method to cover all cases; succeed on first try, fail at first and succeed later on, or fail on all tries.
The fact that it doesn't really copy anything and simply returns or throws is irrelevant to the method you are testing.
My advice, use an existing tool to do this; Alexei's comment points you in the right direction.

Gracefully handling corrupted state exceptions

Related to this question, I would like to force CLR to let my .NET 4.5.2 app catch Corrupted State Exceptions, for the sole purpose of logging them and then terminating the application. What's the correct way to do this, if I have catch (Exception ex) at several places around the app?
So, after I specify the <legacyCorruptedStateExceptionsPolicy> attribute, if I understood correctly, all the catch (Exception ex) handlers will catch exceptions like AccessViolationException and happily continue.
Yeah, I know catch (Exception ex) is a Bad Idea™, but if CLR would at least put the correct stack trace into the Event Log, I would be more than happy to explain to the customer that his server app failing fast at 1AM and being offline for the night is a good thing. But unfortunately, CLR logs an unrelated exception into the Event Log and then closes the process so that I cannot find out what actually happened.
The question is, how to make this happen, process wide:
if the exception thrown is a Corrupted State Exception:
- write the message to the log file
- end the process
(Update)
In other words, this would probably work for most exceptions in a simple app:
[HandleProcessCorruptedStateExceptions]
[SecurityCritical]
static void Main() // main entry point
{
try
{
}
catch (Exception ex)
{
// this will catch CSEs
}
}
But, it won't work for:
Unhandled app domain exceptions (i.e. thrown on non-foreground threads)
Windows Service apps (which don't have an actual Main entry point)
So it seems like <legacyCorruptedStateExceptionsPolicy> is the only way to make this work, in which case I don't know how to fail after logging the CSE?
Instead of using <legacyCorruptedStateExceptionsPolicy> it would be better to use [HandleProcessCorruptedStateExceptions] (and [SecurityCritical]) as stated here:
https://msdn.microsoft.com/en-us/magazine/dd419661.aspx
Following that, your Main method should look something like this:
[HandleProcessCorruptedStateExceptions, SecurityCritical]
static void Main(string[] args)
{
try
{
...
}
catch (Exception ex)
{
// Log the CSE.
}
}
But be aware that this doesn't catch the more serious exceptions like StackOverflowException and ExecutionEngineException.
Also finally of involved try blocks will not be executed:
https://csharp.2000things.com/2013/08/30/920-a-finally-block-is-not-executed-when-a-corrupted-state-exception-occurs/
For other unhandled appdomain exceptions you can use :
AppDomain.CurrentDomain.UnhandledException
Application.Current.DispatcherUnhandledException
TaskScheduler.UnobservedTaskException
(Please do a search for the details when a specific handler is appropriate for your situation. TaskScheduler.UnobservedTaskException for example is a bit tricky.)
If you don't have access to the Main method, you can also mark your AppDomain exception handler to catch the CSE:
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
...
[HandleProcessCorruptedStateExceptions, SecurityCritical]
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// AccessViolationExceptions will get caught here but you cannot stop
// the termination of the process if e.IsTerminating is true.
}
The last line of defense could be an unmanaged UnhandledExceptionFilter like this:
[DllImport("kernel32"), SuppressUnmanagedCodeSecurity]
private static extern int SetUnhandledExceptionFilter(Callback cb);
// This has to be an own non generic delegate because generic delegates cannot be marshalled to unmanaged code.
private delegate uint Callback(IntPtr ptrToExceptionInfo);
And then somewhere at the beginning of your process:
SetUnhandledExceptionFilter(ptrToExceptionInfo =>
{
var errorCode = "0x" + Marshal.GetExceptionCode().ToString("x2");
...
return 1;
});
You can find more information about the possible return codes here:
https://msdn.microsoft.com/en-us/library/ms680634(VS.85).aspx
A "specialty" of the UnhandledExceptionFilter is that it isn't called if a debugger is attached. (At least not in my case of having a WPF app.) So be aware of that.
If you set all the appropriate ExceptionHandlers from above, you should be logging all exceptions that can be logged. For the more serious exceptions (like StackOverflowException and ExecutionEngineException) you have to find another way because the whole process is unusable after they happened. A possible way could perhaps be another process that watches the main process and logs any fatal errors.
Additional hints:
In the AppDomain.CurrentDomain.UnhandledException you can safely cast the e.ExceptionObject to Exception without having to worry - at least if you don't have any IL code that throws other objects than Exception: Why is UnhandledExceptionEventArgs.ExceptionObject an object and not an Exception?
If you want to suppress the Windows Error Reporting dialog, you can take a look here: How to terminate a program when it crashes? (which should just fail a unit test instead of getting stuck forever)
If you have a WPF application with multiple dispatchers you can also use a Dispatcher.UnhandledException for the other dispatchers.
Thanks to #haindl for pointing out that you can also decorate handler methods with the [HandleProcessCorruptedStateExceptions]1 attribute, so I made a little test app just to confirm if things really work as they are supposed to.
1 Note: Most answers state that I should also include the [SecurityCritical] attribute, although in the tests below omitting it didn't change the behavior (the [HandleProcessCorruptedStateExceptions] alone seemed to work just fine). However, I will leave both attributes below since I am presuming all these folks knew what they were saying. That's a school example of "Copied from StackOverflow" pattern in action.
The idea is, obviously, to remove the <legacyCorruptedStateExceptionsPolicy> setting from app.config, i.e. only allow our outermost (entry-level) handler(s) to catch the exception, log it, and then fail. Adding the setting will allow your app to continue, if you catch the exception in some inner handler, and this is not what you want: the idea is just to get the accurate exception info and then die miserably.
I used the following method to throw the exception:
static void DoSomeAccessViolation()
{
// if you have any questions about why this throws,
// the answer is "42", of course
var ptr = new IntPtr(42);
Marshal.StructureToPtr(42, ptr, true);
}
1. Catching exceptions from Main:
[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
static void Main(string[] args)
{
try
{
DoSomeAccessViolation();
}
catch (Exception ex)
{
// this will catch all CSEs in the main thread
Log(ex);
}
}
2. Catching all exceptions, including background threads/tasks:
// no need to add attributes here
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += UnhandledException;
// throw on a background thread
var t = new Task(DoSomeAccessViolation);
t.Start();
t.Wait();
}
// but it's important that this method is marked
[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
private static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// this will catch all unhandled exceptions, including CSEs
Log(e.ExceptionObject as Exception);
}
I would recommend using just the latter approach, and removing the [HandleProcessCorruptedStateExceptions] from all other places to make sure the exception doesn't get caught at the wrong place. I.e. if you have a try/catch block somewhere and an AccessViolationException is thrown, you want CLR to skip the catch block and propagate to the UnhandledException before ending the app.
Is party over? not so fast
Microsoft: "Use application domains to isolate tasks that might bring down a process."
The program below will protect your main application/thread from unrecoverable failures without risks associated with use of HandleProcessCorruptedStateExceptions and <legacyCorruptedStateExceptionsPolicy>
public class BoundaryLessExecHelper : MarshalByRefObject
{
public void DoSomething(MethodParams parms, Action action)
{
if (action != null)
action();
parms.BeenThere = true; // example of return value
}
}
public struct MethodParams
{
public bool BeenThere { get; set; }
}
class Program
{
static void InvokeCse()
{
IntPtr ptr = new IntPtr(123);
System.Runtime.InteropServices.Marshal.StructureToPtr(123, ptr, true);
}
// This is a plain code that will prove that CSE is thrown and not handled
// this method is not a solution. Solution is below
private static void ExecInThisDomain()
{
try
{
var o = new BoundaryLessExecHelper();
var p = new MethodParams() { BeenThere = false };
Console.WriteLine("Before call");
o.DoSomething(p, CausesAccessViolation);
Console.WriteLine("After call. param been there? : " + p.BeenThere.ToString()); //never stops here
}
catch (Exception exc)
{
Console.WriteLine($"CSE: {exc.ToString()}");
}
Console.ReadLine();
}
// This is a solution for CSE not to break your app.
private static void ExecInAnotherDomain()
{
AppDomain dom = null;
try
{
dom = AppDomain.CreateDomain("newDomain");
var p = new MethodParams() { BeenThere = false };
var o = (BoundaryLessExecHelper)dom.CreateInstanceAndUnwrap(typeof(BoundaryLessExecHelper).Assembly.FullName, typeof(BoundaryLessExecHelper).FullName);
Console.WriteLine("Before call");
o.DoSomething(p, CausesAccessViolation);
Console.WriteLine("After call. param been there? : " + p.BeenThere.ToString()); // never gets to here
}
catch (Exception exc)
{
Console.WriteLine($"CSE: {exc.ToString()}");
}
finally
{
AppDomain.Unload(dom);
}
Console.ReadLine();
}
static void Main(string[] args)
{
ExecInAnotherDomain(); // this will not break app
ExecInThisDomain(); // this will
}
}

Receiving "...has already been registered..." from EventLog.CreateEventSource even though I'm checking !EventLog.SourceExists

My following code fails with "...has already been registered as a source on the local computer" even though I'm doing checks first:
lock ( eventLock )
{
string eventLog = Constants.EventLogPL;
string eventSrc = Constants.EventSrcPL;
if (!EventLog.Exists(eventLog))
{
if (!EventLog.SourceExists(eventSrc))
{
try
{
EventLog.CreateEventSource(eventSrc, eventLog);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
}
}
I'd have thought my call to !EventLog.SourceExists would have been enough to prevent my error!
I'm on 2010 .NET 4 and Windows 7 64 compiling to any CPU.
Edit: Updated code to get Constant's to locals to check they don't change, and use locking to make sure only one thread can test and create. Code still fails with the same error.
Found the problem after digging in to Sysinternals' Process Monitor a little more:
Calling EventLog.Exists("MyLog");
Logs Name not found, as expected in:
KLM\System\CurrentControlSet\services\eventlog\MyLog
Calling EventLog.SourceExists("MySource");
Checks several places, name not found in as expected:
HKLM\System\CurrentControlSet\services\eventlog\Application\MySource
HKLM\System\CurrentControlSet\services\eventlog\HardwareEvents\MySource
HKLM\System\CurrentControlSet\services\eventlog\Internet Explorer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Key Management Service\MySource
HKLM\System\CurrentControlSet\services\eventlog\Media Center\MySource
HKLM\System\CurrentControlSet\services\eventlog\ODiag\MySource
HKLM\System\CurrentControlSet\services\eventlog\OSession\MySource
HKLM\System\CurrentControlSet\services\eventlog\Security\MySource
HKLM\System\CurrentControlSet\services\eventlog\System\MySource
HKLM\System\CurrentControlSet\services\eventlog\VisualSVNServer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Windows PowerShell\MySource
HKLM\System\CurrentControlSet\services\eventlog\Application\MySource
HKLM\System\CurrentControlSet\services\eventlog\HardwareEvents\MySource
HKLM\System\CurrentControlSet\services\eventlog\Internet Explorer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Key Management Service\MySource
HKLM\System\CurrentControlSet\services\eventlog\Media Center\MySource
HKLM\System\CurrentControlSet\services\eventlog\ODiag\MySource
HKLM\System\CurrentControlSet\services\eventlog\OSession\MySource
HKLM\System\CurrentControlSet\services\eventlog\Security\MySource
HKLM\System\CurrentControlSet\services\eventlog\System\MySource
HKLM\System\CurrentControlSet\services\eventlog\VisualSVNServer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Windows PowerShell\MySource
HKLM\System\CurrentControlSet\services\eventlog\MyLog
However, calling EventLog.CreateEventSource("MySource", "MyLog");
Finds MyLog in the following registry location and errors:
HKLM\System\CurrentControlSet\services\eventlog\Application\MyLog
Removing "HKLM\System\CurrentControlSet\services\eventlog\Application\MyLog" and re-running fixed my problem!
Looks like the .Exists don't look in all the places .CreateEvent does!
//0 for false, 1 for true.
private static int usingResource = 0;
if (!EventLog.SourceExists(Constants.EventSrcPL))
{
//0 indicates that the method is not in use.
if (0 == Interlocked.Exchange(ref usingResource, 1))
{
if (!EventLog.SourceExists(Constants.EventSrcPL))
{
try
{
EventLog.CreateEventSource(Constants.EventSrcPL, Constants.EventLogPL);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
//Release the lock
Interlocked.Exchange(ref usingResource, 0);
}
}
}
}
else
{
usingResource = 0;
}
Does not solve the issue when the source is created by a different application in the exact time you are accessing the event log.
Edited: made modifications that account for the delayed creation of a EventSource.

The uncatchable exception, pt 2

Update: I've filed a bug report on Microsoft Connect: https://connect.microsoft.com/VisualStudio/feedback/details/568271/debugger-halting-on-exception-thrown-inside-methodinfo-invoke#details
If you can reproduce this problem on your machine, please upvote the bug so it can be fixed!
Ok I've done some testing and I've reduced the problem to something very simple:
i. Create a method in a new class that throws an exception:
public class Class1 {
public void CallMe() {
string blah = null;
blah.ToLower();
}
}
ii. Create a MethodInfo that points to this method somewhere else:
Type class1 = typeof( Class1 );
Class1 obj = new Class1();
MethodInfo method = class1.GetMethod( "CallMe" );
iii. Wrap a call to Invoke() in a try/catch block:
try {
method.Invoke( obj, null ); // exception is not being caught!
} catch {
}
iv. Run the program without the debugger (works fine).
v. Now run the program with the debugger. The debugger will halt the program when the exception occurs, even though it's wrapped in a catch handler that tries to ignore it. (Even if you put a breakpoint in the catch block it will halt before it reaches it!)
In fact, the exception is happening when you run it without the debugger too. In a simple test project it's getting ignored at some other level, but if your app has any kind of global exception handling, it will get triggered there as well. [see comments]
This is causing me a real headache because it keeps triggering my app's crash-handler, not to mention the pain it is to attempt to debug.
I can reproduce this on my .NET 4 box, and you're right -- it only happens on .NET 4.0.
This smells very much like a bug to me, and should go on MS Connect. Major bummer if this is tripping your crash handler. Sounds like a non-pleasing way to work around this is to wrap the invoked method inside its own handler. :-(
One thing I can not reproduce, though, is tripping the crash handler. Here's my program:
namespace trash {
public class Class1 {
public void CallMe() {
string blah = null;
blah.ToLower();
}
}
class Program {
static void Main(string[] args) {
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
var class1 = typeof(Class1);
var method = class1.GetMethod("CallMe");
try {
var obj = new Class1();
method.Invoke(obj, null); // exception is not being caught!
}
catch (System.Reflection.TargetInvocationException) {
Console.Write("what you would expect");
}
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
Console.Write("it would be horrible if this got tripped but it doesn't!");
}
}
}
You can't catch all exceptions. There's a few assumptions in your example. You are, for instance, assuming the exception was raised on the calling thread. Catching unhandled exceptions on other threads depends on which runtimes you're using (console, winforms, WPF, ASP.Net, etc).
Additionally, calls to System.Environment.FailFast() do not generate any handlable condition - the process is effectively terminated with no chance for intervention.

Categories

Resources