ASP.NET Debug系列之三:Crash调试
原文作者:Tess
原文链接:http://blogs.msdn.com/tess/archive/2008/02/11/net-debugging-demos-lab-2-crash-review.aspx
译者:Wencui http://www.cnblogs.com/David-Qian
前两天翻译了Tess Debug系列的Hang调试,今天就继续和大家看看Tess的第三篇文章,Crash调试。同样,由于机器的差异,可能你的调试结果和Tess有所差异,不过这个不要紧,只要你知道其中的原理就可以了。文章的图片可能会不太清楚,如果需要,大家可以去Tess的原文上看。
1. 问题重现
1) 浏览到http://localhost/BuggyBits/Reviews.aspx页面。
2) 点击Refresh按钮,它将使得w3wp.exe程序crash(IIS 5下是aspnet_wp.exe)。注意,如果你装有Visual Studio的话,Just-In-Time的消息将会弹出,你可以点击no按钮,此时我们不需要来实时调试。
2. 检查系统日志
打开日志(在命令行中可以输入eventvwr.exe)。日志对于不同版本的系统和IIS可能会有所不同。在系统日志中,我们可以看到这条信息:
问题1:你的日志是什么样子的?
结果1:在Tess的机器中(Windows 2003,.NET 2.0 SP1),系统日志如上所示。同时,在程序日志中, 有如下两条信息:
从第一条信息看,它像是一个nullreference的异常所致
第二条信息显示,Review的Finalize方法导致了nullreference的异常。但也有可能你看不到第二条信息,这个因不同的系统,IIS版本,.NET版本等而异。
问题2:exit code 0xe0434f4d代表着什么呢?
结果2:这个code是.NET异常的一个等价物。不管是SQL exception,nullreference exception还是FileNotFoundException,它们都将会用0xe0434f4d code来表示。
问题3:从这些日志中,你能判断是什么导致了程序的crash吗?
结果3:从程序日志的第二条信息中,我们可以很容易的看出,程序的crash是因为Finalize Review这个对象时产生的异常引起的。
3. 获取一个dump文件
1) 打开http://localhost/BuggyBits/Reviews.aspx页面,但是不要点击"Refresh"按钮。
2) 打开命令行窗口并切换到debugger tools所在的目录,然后输入:adplus -crash -pn w3wp.exe,并立即敲回车。
问题1:一个新的窗口将出现在工具栏上,这个程序是什么?
结果1:这个程序的图标是:
其实这就是debugger,它正在运行着cdb,是windbg没有UI状态下的等价物。
问题2:dubugger在等待着什么呢?
结果2:下面的一张表将显示什么样的events/exceptions adplus会监视,以及当其中的之一发生时,adplus将会做什么。
一些缩写:L=在debuglog中写日志,T=记录时间,S=记录stack,M=最小的dump,F=全的dump,E=在eventlog中写日志。如果你想adplus捕捉你想要的events,你可以自定义一个cfg文件供adplus使用。
3) 在reviews页面上点击"Refresh"按钮以重现问题。
问题1:在debugger tools目录,什么文件被创建了?
结果1:在Tess的机器上,如下文件被创建在crash文件夹(名字中含有crash_mode)下:
ADPlus_report.txt
<DIR> CDBScripts
PID-4628__W3WP.EXE__Date_02-11-2008__Time_13-14-0808.log
Process_List.txt
PID-4628__W3WP.EXE__1st_chance_AccessViolation__mini_17a4_2008-02-11_13-14-19-358_1214.dmp
PID-4628__W3WP.EXE__1st_chance_Process_Shut_Down__full_17a4_2008-02-11_13-14-31-889_1214.dmp
PID-4628__W3WP.EXE__2nd_chance_NET_CLR__full_17a4_2008-02-11_13-14-20-093_1214.dmp
从上面的文件中,我们可以看出,adplus产生了三个dump文件。第一次的access violation(NullReferenceException)将产生一个最小的dump文件。由于此exception并未被处理,于是引起了第二次的CLR exception。最终,导致了process的shutdown。其实,从三个dump文件的时间戳可以很容易的得知它们产生的顺序。
4. 使用windbg调试
1) 打开CLR exception的dump文件(xx_2nd_chance_NET_CLR_xx)。
这里有些要说明的地方。.NET中,exception是比较常见的。当我们的程序中抛出异常时,这个叫做1st chance。此时我们可以使用try/catch语句来捕获这个异常。但是,如果我们并没有及时捕获这个异常,并且这个异常不能被.NET的全局异常处理掉,它就变成了2nd异常。任何2nd的异常都将导致process的crash。
2) 设置symbols的path,并且load sos。这些都是基本步骤,前面都提到过,不再多说。
在crash mode下,由于dump是在异常发生时产生的,因而active的thread就是发生异常的thread。
问题1:你的dump中,哪个thread是active的?
结果1:active的thread是#20。如果我们用!threads命令来查看thread的信息的话,我们可以发现#20 thread是Finalizer thread。
5. 检查callstack以及exception
1) 检查native以及managed callstack。
问题1:这个thread是什么类型的thread?
结果1:从kb 2000命令中,我们可以看出它是Finalizer thread,但是由于它是发生异常的thread,我们无法使用!clrstack来查看更多的managed callstack的信息。
问题2:这个thread正在做什么?
结果2:它正在回收某些objects,在回收的过程中抛出了异常,并最终成为2nd异常。
2) 检查exception
!pe
!pe/!PrintException 命令将打印出当前堆栈上正在被抛出的exception。
问题1:这是个什么类型的exception?
结果1:这个是空引用的异常 NullReferenceException。
注意,在大多数的情况下,我们很难得到2nd exception发生前的stack trace。这样的话,我们需要使用别的命令来找出更多的关于1st exception的信息。
3) 在堆栈上查看更多关于1st exception的信息
问题1:1st exception的地址是什么?
结果1:在上图中,我们可以看到2nd exception的地址是0x2f0cb3c,因而,我们比较下图可知,1st exception的地址是 0x6f1e5e4。
4) 查看1st exception的信息以及callstack。
问题1:exception是在哪个方法中被抛出的?
结果1:在Review class的Finalizer方法中。
问题2:什么object正在被回收?
结果2:是Review object。
问题3:通常情况下,当异常被抛出时,我们如果没有使用try/catch块的话,它将会被ASP.NET全局异常来处理,同时显示给用户一个错误提示页面,为什么这个异常并不能被处理,而致使process的crash呢?
结果3:ASP.NET的全局异常处理仅仅用来捕获处理requests时的exception。在我们的例子中,异常是发生在Finalizer thread中的,它并不属于某一个特定用户,因而也无法将错误信息显示给某一特定用户。这样就导致了process的crash。
6. 验证我们的代码
1) 打开Review.cs,查看它的Finalizer方法。
问题1:哪一行代码会抛出exception?
结果1:从代码中,我们可以很容易的看出,当string非空时,它被赋值null,这将导致NullReferenceException。如果我们并不能查看源代码,我们可以通过!u命令来查看JIT生成的汇编代码。这里暂不做讨论,在以后的文章中再做解释。
同样,这篇文章非常的长,也翻译了很长时间,希望能够对大家有所帮助 :)
Have a nice day!