.NET异常——跟踪代码中发生异常的地方

我曾在以前写过一篇文章,介绍如何使用 adplus 的配置脚本跟踪异常。

通常,方法都写得很短,以至于只要知道哪个函数里有异常,就肯定能跟踪到原因。但是大家都明白,我们并没有生活在那个完美世界里——只需要编写模块化的应用程序,然后一切都漂漂亮亮地排好了。:)

假设你在堆中发现了这么一个异常:

从堆栈中我们得知,NullReference 异常发生在 DisplayUserInfo.Page_Load 函数里,但是如何确切地知道在函数的哪个地方呢?还有,你怎么得知是什么引起了异常?

我常做的第一件事,就是如果有代码就去查看代码。如果没有代码,那么我就用 sos.dll 中的 !savemodule 和 !saveallmodules(!sam)从 dump 中抽取出 dll。在 dump 被获取时,sos.dll 将给我一个加载到内存中的 dll 原样拷贝。(一个小警告,!sam 的功能在2.0版的 sos.dll 中并不存在,所以在 2.0 中你得使用 savemodule)

因此对于这个异常,我就知道是在 IP(指令指针)0x029C3269 处出错。然后我可以用此地址先得到这个方法的描述符(使用 !ip2md——Instruction Pointer to Method Descriptor)。

然后 dump 出方法表,找到由这段代码编译成的 dll。

一旦我们知道是哪个 dll,就能通过运行 lmv m 来找到其载入地址。

得到这个以后,我们现在就能从内存 dump 中抽取出汇编代码(App_Web_dmjhn1yn.dll):

好了,现在我们就有了这个 dll,但如何得到它的实际代码呢?

嗯,可以用 ildasm.exe 打开它,找到 DisplayUserInfo.Page_Load 函数,就会看到一些相对可读的中间语言代码。

我喜欢用的工具是 Lutz Roeder 的反射器,从 http://www.aisto.com/roeder/dotnet/ 处可以得到。当我浏览到 DisplayUserInfo.Page_Load 函数的时候,这个反射器就输出了以下内容:

很酷吧!:)恰恰是原始代码的一个准确复制品……但是……光有代码并不能真的告诉我们到底异常出现在哪里。因此,让我们回到指令指针处,并且用 !u 反汇编 dump 中的函数,然后就能搜索距离目前指令指针最近的那条指令。来看看我们在哪儿……

下面这行:

……正是我们当前IP的前一行,这也就意味着它是引起NullReferenceException的代码行。

我把相邻的几行标记成灰色,以便帮助我们比较反汇编代码和反编译代码。

反汇编代码:

反射器得到的代码:

我们可以看到对 this.tblBlogRoll.Rows.Add(System_Web_ni!System.Web.UI.WebControls.TableRowCollection.Add) 和 this.Session["BlogRoll"](System_Web_ni!System.Web.SessionState.HttpSessionState.get_Item) 的调用,后面跟着的是 ArrayList 的类型转换(mscorwks!JIT_ChkCastClassSpecial)。

在加粗的代码行之后,我们可以看到 new TableCell(System_Web_ni!System.Web.UI.WebControls.TableCell.ctor) 的调用,这意味着那行加粗代码一定是在 for 行中的某条指令:

for (int num1 = 0; num1 < list1.Count; num1++)

更具体地说,是 list1.Count 引起了这个空引用异常。换句话说,list1 是一个空引用,因为 this.Session["BlogRoll"] 是空值,当试图使用它的 Count 属性时,就进行了一次空引用。所以,要避免这个问题,我们需要在把 Session["BlogRoll"] 赋给 ArrayList 变量前,对它进行一次空引用检查。

顺带提一下,这个方法不仅仅可以应用在异常方面。你还能用它判断出具体要在哪里加锁,还有其他类似的内容,但最可能用到的地方还是异常。

下回见……

你可能感兴趣的:(.NET异常——跟踪代码中发生异常的地方)