猎捕异常(Exceptions)
猎捕异常(Exceptions)
准备:
这一节的内容需要前面讲过的一些的基本知识,请先阅读有关的内容再继续吧。有关异常(Exceptions)的知识,可以看我有关的发表的文章。
介绍:
我想是时候再写一点有关使用WinDbg 来解决问题的文章了。我的大部分时间是花费在大量的web应用程序中查找异常。所以我想,说说异常是一个很好的话题。前面说过OutOfMemory Exceptions,我现在想扩展这个系列,让它更普遍一点。我的工作中经常会出现下面两个场景:
1) 客户报告说他们第二次看到了“服务器页面错误”的异常被显示在页面上,黄页又出现了。
2) 性能很差,当我研究一下时,发现那里有好几吨的异常被抛出来了。
在这一章中我将讲述怎么样来研究由应用程序抛出的异常。
从哪里开始
好的,你有一个web的应用程序,你已经监视并相信有很多的异常产生。你已经有了该进程的dump文件,你已经准备好开始研究,你从哪里开始呢?
!dumpallexceptions (!dae)
如果你的应用程序是运行在.net 1.1 下面,你可以使用!dumpallexceptions(!dae)来列出所有仍然在堆上的异常。记住,这些异常也是托管对象,他们也会被垃圾收集的。那意味着我们看到的异常是仍然在内存里面的,并不是从程序开始运行就有的每一个异常。你可以去运行这个命令,在命令的输出中,如果可能,他会给出每个异常的调用堆栈,每一个异常的调用堆栈可能是不相同的,136个NullReferenceExceptions可能有20不同的调用堆栈。
不幸的,在.net 2.0 中,这个命令没有了,要得到相同的结果可以使用如下的方式:
0:000> !dumpheap -type Exception -stat ------------------------------ Heap 0 total 79 objects ------------------------------ Heap 1 total 76 objects ------------------------------ …… total 338 objects Statistics: MT Count TotalSize Class Name 790ff624 3 36 System.Text.DecoderExceptionFallback 790ff5d8 3 36 System.Text.EncoderExceptionFallback Total 338 objects |
应该过滤掉什么
当分析的时候,知道那些没有用的信息要过滤掉是非常有用的。
过去—现在 的异常
有三种类型的异常是在工作进程被创建的时候就有的。这就是说你总是会看见他们在堆上,即使他们没有被抛出来。
System.ExecutionEngineException
System.StackOverflowException
System.OutOfMemoryException
那么,为什么他们会被创建,即使没有抛出他们呢? 猜猜看。
答案非常简单,你会运行进入这样一种状况:当你想创建他们的时候,你已经不能创建他们了。比如,当你已经没有可用内存了,在也不能分配最小的一个字符串的时候,你怎么还能分配足够的内存来创建一个新的异常呢?
为了避免那种情况的发生,在堆上就有一个这样的异常先存在了。你可以忽略它们。所以,当你遇到ExecutionEngineExceptions 和OutOfMemoryExceptions,你肯定可以找到StackOverflowException,如果你运行 !clrstack ,发现有超过200多行,这或多或少就是你的问题了。
System.Threading.ThreadAbortException
通常当你看到这样一个ThreadAbortExceptions的异常,是因为你调用了Response.Redirect。
无论什么时候你调用了Response.Redirect,都会导致调用Response.End,进程会被提前结束。导致System.Threading.ThreadAbortException异常,看看下面的堆栈:
SP IP Function 1ED mscorlib_ni!System.Threading.Thread.Abort(System.Object)+0x 1ED System_Web_ni!System.Web.HttpResponse.End()+0x 1ED System_Web_ni!System.Web.HttpResponse.Redirect(System.String, Boolean)+0x 1ED System_Web_ni!System.Web.HttpResponse.Redirect(System.String)+0x7 1ED Company_Web!Company.Web.UI.Page.RedirectToPreviousPage()+0x125 |
我并没有说你可以忽略所有的System.Threading.ThreadAbortExceptions。即使你没有理由相信ThreadAbortExceptions是一个主要的原因,拿它来研究一下,有时候是一个好主意。花一两分钟来检查一下在Response.Redirect下面有调用Response.End。有足够的统计信息让你证明是重定向(Redirects)导致了ThreadAbortExceptions,你可以忽略,继续向下看。
检查异常
我们想看看System.Data.SqlClient.SqlException的调用堆栈,首先我们需要的是它的地址,你可能还记得,很容易的来获得:
0:000> !dumpheap -type System.Data.SqlClient.SqlException ………. ------------------------------ Heap 2 0b62cf38 total 1 objects ------------------------------ …….. total 1 objects Statistics: MT Count TotalSize Class Name Total 1 objects |
现在我们有了异常的地址,为了研究这个异常,我们可以使用 !dumpobject,但我先介绍另外一个命令
!printexception (!pe)
0:000> !pe 0b62cf38 Exception object: 0b62cf38 Exception type: System.Data.SqlClient.SqlException Message: Transaction (Process ID 96) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. InnerException: StackTrace (generated): SP IP Function 1DACECA8 1D Company_Database_1d 1DACED Company_Database_1d ………….. StackTraceString: HResult: 80131904 The current thread is unmanaged |
真是一个好伙计,它还把调用堆栈给了我们。(并不总是这样,因为调用堆栈可能已经超出了范围)。
!dumpobject (!do)仍然有用
我并没有说!printexception可以完全代替!dumpobject来检查异常。!Printexception会把异常格式为一个标准的模版,因为一些异常可能会包含更多的信息,我们还需要!dumpobject。那个SqlException 有一个属性叫_errors,是System.Data.SqlClient.SqlErrorCollection类型的,我们看那个可能会有用,但上面并没有列出来,所以我们要用!dumpobject来查看。
0:000> !do 0b62cf38 Name: System.Data.SqlClient.SqlException MethodTable: EEClass: Size: 76(0x (C:\WINDOWS\assembly\GAC_32\System.Data\ Fields: MT Field Offset 790fdb60 40000bf 34 System.Int32 1 instance 0 _remoteStackIndex 790fdb60 790fcfa4 790fdb60 |
我们有了,我们可以使用 !dumpobject来进行更深入的研究。
内部错误
如果我们找个HttpUnhandledExceptions来看看,会发现它有一个内存错误,它会告诉我们更多的信息。
0:000> !pe Exception object: Exception type: System.Web.HttpUnhandledException Message: InnerException: System.NullReferenceException, use !PrintException 10544df8 to see more StackTrace (generated): SP IP Function 1E3BE1D8 6614FDB2 System_Web_ni!System.Web.UI.Page.HandleError(System.Exception)+0x3e6 1E3BE220 System_Web_ni!System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)+0x1b |
那意味着System.NullReferenceException导致了我们正在研究的HttpUnhandledException异常的发生。如果我们想要找出根本的原因我们还需要看看这个内部错误。
其他信息
在我的“高级命令”一文中讲了 .foreach 命令。这个命令很好用,例如,你想看看System.ArgumentNullExceptions的调用堆栈,除了手动的每次列举所有的这些异常,你可以把他们一次性的列出来,然后看调用堆栈。
0:000> .foreach(myVariable {!dumpheap -type System.ArgumentNullException -short}){!pe myVariable;.echo *************} Exception object: Exception type: System.ArgumentNullException Message: Value cannot be null. InnerException: StackTrace (generated): SP IP Function mscorlib_ni!System.Guid..ctor(System.String)+0x Foo_1cfd0000!Pages.PersonHomePage.Page_Load(System.Object, System.EventArgs)+0x77 StackTraceString: HResult: 80004003 The current thread is unmanaged ************* Exception object: 08378e24 Exception type: System.ArgumentNullException Message: Value cannot be null. InnerException: StackTrace (generated): SP IP Function 1CE6EC60 795FC mscorlib_ni!System.Guid..ctor(System.String)+0x 1CE6ECBC 1D0AB Foo_1cfd0000!Pages.PersonHomePage.Page_Load(System.Object, System.EventArgs)+0x77 StackTraceString: HResult: 80004003 The current thread is unmanaged ************* Exception object: Exception type: System.ArgumentNullException Message: Value cannot be null. ………………… |