0

我最近正在调试我们的一个应用程序的“挂起”问题。我是该工具的新手,但对我在 DebugDiag 分析中看到的内容有一个特殊的问题。其他问题我会单独发帖。它有一个标题为“以前的 .NET 异常(所有 .NET 堆中的异常)”的部分,其中列出了 5 个异常:

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>      ; 

有人可以帮我理解一些事情:

  • 该部分的标题是什么意思?“以前的 .NET 异常”?
  • 如果抛出异常,为什么它不会出现在日志记录中,为什么应用程序不会中止而不是挂起?我检查了我们的代码是否有任何可能的异常被“吃掉”。
  • 为什么没有相关的消息或堆栈跟踪?我获得了 .dmp 文件中所有其他内容的调试信息,包括所有线程的 CLR 堆栈跟踪。有没有更好的方法/工具来确定这些来自哪里?

提前致谢!

4

1 回答 1

2

该部分的标题是什么意思?“以前的 .NET 异常”?

当发生异常时,它可能由使用try{} catch{}构造的代码处理。在许多情况下(至少我在代码中看到的情况),异常正在被转换为其他异常。原因通常是某种 API 只能抛出“自己的”异常,即由库定义的类型的异常:

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

同样,人们通常不会设置InnerException属性,尽管这样做并不难:

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

无论如何,如果没有正确完成,原始异常就消失了,调试这种情况变得更加困难。

但是,异常不会花费很长时间才能向上冒泡并导致崩溃,从而导致崩溃转储。在许多情况下,垃圾收集器没有足够的时间来收集不再引用的异常。

DebugDiag 尝试使丢失的异常对您可用。它所做的基本上是列出所有名称中包含Exception的对象,假设它们是异常。这相当于 SOS 命令!dumpheap -type Exception

DebugDiag 发现不是异常的异常

在某些情况下,您可能会在此处找到感兴趣的异常,因此它可能会有所帮助。但在许多情况下,它只会列出 6 个默认异常,这更令人困惑而不是有用。

如果抛出异常,为什么它不会出现在日志记录中,为什么应用程序不会中止而不是挂起?

您看到的 6 个异常是“默认”异常,它存在于每个 .NET 项目中,即使在简单的 Hello World 项目中也是如此。这些例外为每个AppDomain创建一次,并且您始终拥有一个默认 AppDomain。

它们是为特殊场合而设计的。其中一些可以很容易地解释:

  • OutOfMemoryException:当内存已满时,可能无法做new一个对象。创建 OutOfMemoryException 将再次导致 OutOfMemoryException。
  • StackOverflowException:如果堆栈已满,则无法调用其他方法。对于 .NET,可能需要调用方法才能调用构造函数。这样做会再次导致 StackOverflowException。
  • ExecutionEngineException:如果 .NET 运行时损坏,它可能无法再创建该异常。(此异常在 .NET Core 中已过时,因此在 .NET Core 项目中可能不存在)

ThreadAbortExceptions 可能有类似的原因。我真的不知道普通的异常有什么好处。

为什么没有相关的消息或堆栈跟踪?

那是因为它们还没有被扔掉。其中一些甚至可能在被抛出时没有得到调用堆栈,例如,在 OutOfMemoryException 的情况下没有用于构建堆栈跟踪的内存。对于 StackOverflowException 也是如此。

有没有更好的方法/工具来确定这些来自哪里?

不,这只是知识问题。现在你已经获得了这些知识:-)

赏金与

从有信誉的来源寻找答案

我不完全知道什么是有信誉的来源。我可以提供Tess Ferrandez 的存档博客文章。Tess Ferrandez 是 Microsoft ASP.NET 升级工程师。您会发现,在 2009 年,默认异常并不像现在那么多。

如果您想要一本书,那么很可能在Mario Hewardt 的“Advanced .NET Debugging”一书中。我有这本书,但目前没有,否则我会查的。

.NET CLR 源代码中,您可以找到appdomain.cpp,其中有一个名为void SystemDomain::CreatePreallocatedExceptions(). 甚至我都不知道有一个正常的 ThreadAbortException 和一个“粗鲁”的 ThreadAbortException。哇!

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

自己测试一些语句的源代码:

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
        }
    }
}
于 2020-06-26T11:41:02.430 回答