jd314159
jd314159

Reputation: 395

debugging hang with DebugDiag - mystery exceptions

I'm recently working on debugging a "hang" issue with one of our applications. I'm new to the tool but have a particular question about what I'm seeing in the DebugDiag analysis. I'll post separately for other questions. It has a section titled "Previous .NET Exceptions (Exceptions in all .NET Heaps)" it lists 5 exceptions as such:

Exception Type                          Count    Message     Stack Trace
System.Exception                        1        <none>      ;
System.OutOfMemoryException             1        <none>      ;
System.StackOverflowException           1        <none>      ;
System.ExecutionEngineException         1        <none>      ;
System.Threading.ThreadAbortException   2        <none>      ; 

Can someone help me understand a few things:

Thanks in advance!

Upvotes: 1

Views: 916

Answers (1)

Thomas Weller
Thomas Weller

Reputation: 59238

what does the title of the section mean? "Previous .NET Exceptions"?

When an exception occurs, it might be handled by code using a try{} catch{} construct. In many cases (at least what I see in code), Exceptions are being converted into other Exceptions. The reason often is some sort of API which shall only throw "own" exceptions, i.e. Exceptions of a type that is defined by the library:

try 
{
    DoSomething();
}
catch (ArgumentException)
{
    throw new MyWhateverLibraryException();
}

Also often, people do no set the InnerException property, although that would not be very hard to do:

try 
{
    DoSomething();
}
catch (ArgumentException argex)
{
    var myex = new MyWhateverLibraryException();
    myex.InnerException = argex;
    throw myex;
}

Anyway, when not done properly, the original exception is gone and debugging that situation becomes harder.

However, an exception will not take very long to bubble upwards and cause a crash resulting in a crash dump. In many cases, there will not be enough time for the garbage collector to collect the Exception which is no longer referenced.

DebugDiag attempts to make that lost Exception available to you. What it does is basically list all objects that have Exception in their name, assuming that they are exceptions. This is equivalent to the SOS command !dumpheap -type Exception.

DebugDiag finding an Exception that isn't an exception

In some cases, you may find an exception of interest there, so it may be helpful. But in many cases it will just list the 6 default Exceptions, which is more confusing than helpful.

if there was an exception thrown, why wouldn't it show up in logging and why wouldn't the app abort instead of hang?

The 6 exceptions you see are "default" exceptions, which exists in every .NET project, even in a simple Hello World project. These exceptions are created once per AppDomain and you always have a default AppDomain.

They are created for special occasions. Some of them can be explained easily:

  • OutOfMemoryException: when the memory is full, it might be impossible do new up an object. Creating the OutOfMemoryException would cause an OutOfMemoryException again.
  • StackOverflowException: if the stack is full, you can't call another method. For .NET, it might be required to call a method in order to invoke the Constructor. Doing so would result in a StackOverflowException again.
  • ExecutionEngineException: if the .NET runtime is corrupted, it may not be able to create that Exception any more. (This exception is obsolete in .NET Core, so it might not be there in .NET Core projects)

The ThreadAbortExceptions probably have similar reasons. I can't really tell what the ordinary Exception is good for.

Why is there no message or stack trace associated?

That's because they have not been thrown yet. Some of them might even not get a call stack when they are thrown, e.g. there's no memory left for building a stack trace in case of an OutOfMemoryException. Likewise for StackOverflowException.

Is there a better way/tool to determine where these are coming from?

No. It's just a matter of knowledge. Now you have gained that knowledge :-)

The bounty was associated with

looking for an answer from a reputable source

I don't exactly know what qualifies as a reputable source. I can offer an archived blog post by Tess Ferrandez. Tess Ferrandez was a Microsoft ASP.NET escalation engineer. You'll find that in 2009 there were not as many default exceptions as there are nowadays.

If you want a book, then it's likely in Mario Hewardt's "Advanced .NET Debugging" book. I have the book, but not currently available, otherwise I would have looked it up.

In the .NET CLR source code you can find appdomain.cpp, which has a method called void SystemDomain::CreatePreallocatedExceptions(). Even I didn't know that there's one normal ThreadAbortException and one "rude" ThreadAbortException. Wow!

EXCEPTIONREF pRudeAbortException = (EXCEPTIONREF)AllocateObject(g_pThreadAbortExceptionClass);
...
EXCEPTIONREF pAbortException = (EXCEPTIONREF)AllocateObject(g_pThreadAbortExceptionClass);

Source code for testing some of the statements yourself:

using System;

namespace DefaultExceptions
{
    class ThisDoesNotDeriveFromException
    { }
    class Program
    {
        static void Main()
        {

            var noexception = new ThisDoesNotDeriveFromException();
            Console.WriteLine("This is just an example to demonstrate the presence of some default exceptions.");
            Console.WriteLine("Create a crash dump now, so you can analyze it with WinDbg and SOS or DebugDiag.");
            Console.WriteLine("Commands:");
            Console.WriteLine(".loadby sos clr");
            Console.WriteLine("!dumpheap -type Exception");
            Console.ReadLine();
            Console.WriteLine(noexception.ToString()); // avoid noexception being GC'd
        }
    }
}

Upvotes: 4

Related Questions