Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
这篇文章我们一起来分析一个从Windows Phone Dev Center上下载下来的dump file。首先按照我上一篇的步骤设置好我们的Windbg,并按住Ctrl +D打开dumpfile。可以看到下面的界面:
分析一个dump file可以分解为4个步骤,第一步是信息收集,第二步是定位异常上下文,第三步分析和推理出现问题的原因,第四步分析和定位我们的源代码并进行修复和验证。
信息收集
我们可以使用一些命令浏览一下这个dump file对应的系统版本和一些模块的信息辅助我们后面的分析。
1. version命令,查看系统版本号
2. lm命令,显示当前加载的模块。
可以使用lmv命令查看所有模块的详细信息,如果想看某一模块的详细信息,需要使用参数m,比如查看System_Data_Linq_ni.dll的详细信息,”lmv mSystem_Data_Linq_ni”
定位异常上下文
当异常发生的时候,寄存器的上下文会被异常分发器保存在栈上。我们可以通过一些方法找到并恢复发生异常时候的上下文,从上下文中找到我们需要的信息,这里使用”!analyze -v”命令。
1. 首先确认发生异常的线程。有时候发生异常的线程不止一个,我们在使用 “!analyze –v”之前需要确认发生异常的线程。使用”~* kvb”命令来查看所有线程的调用堆栈。”~*“ 命令是枚举所有的线程,”kvb“命令是列出线程的调用堆栈。
可以发现所有的线程都是等待状态,只有线程0不是,从线程0的callstack可以看出来线程0就是我们要找的发生异常的那个线程。
2. 切换到发生异常的线程,”~0 s”
3. 使用“!analyze –v”,这个扩展命令来帮助我们找到发生异常时候的上下文,并显示当时的调用堆栈,有些情况下给出的调用堆栈并不是发生异常的第一现场,遇到这种情况我们需要进一步分析。
分析和推测
在上面的显示中,我们发现一个很有意思的托管调用堆栈,里面反复出现了”System.Diagnostics.StackTrace..ctor()+0x12”。看起来StackTrack这个类型的对象在构造的时候调用了自己的成员函数GetStackFramesInternal,而这个函数又去构造了新的StackTrack的对象,如此反复以至于发生了循环调用而导致栈被耗尽,这里并没有给出与我们的代码相关的调用,看起来很像一个.net framework的bug。那么为什么会发生这样的调用呢?让我们继续进行分析,看看是哪里引发了这个调用。
为了找到更多的线索,我们可以进一步查看发生异常的线程栈里都保留了什么,我们可以通过”!teb”命令来查看当前线程的属性,并找到栈的基址和大小,有了栈的基址和大小,我们就可以查看里面的内容了。
1. 查看当前线程的属性,”!teb”命令
2. ”dps + 地址范围”命令可以让我们查看栈里面保留的信息。
跳过这些无效的内容,我们继续往后查看。
红线的模块和函数正是我们App中的代码,我们可以做一个大胆的推理在这里。我们的函数DecrementPendingAndFinishIfNecessary调用了Logger.Info函数,这个函数使用了系统的StackTrace.CaptureStackTrace来获取当前的调用堆栈。那么为什么这个函数StackTrace.CaptureStackTrace又会去构造它自己的对象呢?让我们打开我们程序的源代码进一步分析。
分析和定位我们的源代码
打开我们的代码并找到Logger.Info的实现,红色的代码正是验证了我们上面的推理。在一些极端的形况下,StackTrace会创建失败并扔出异常,这个异常恰好被后面的catch块捕获再次调用了Logger的函数,而这个函数会再次创建StackTrace类型的对象,继续触发异常导致了反复的调用。
private static void WriteLine(Level level, string message)
{
try
{
if (0 == message.Length)
{
return;
}
StackTrace st = new StackTrace(); // 1. 这里exception
string name = st.GetFrame(2).GetMethod().Name;
string prefix = string.Format("[{0}]@{1}", level, name);
message = prefix + "-" + message;
}
catch (Exception e)
{
Logger.Fatal("Faild in WriteLog,message:" + e.Message); // 2. 然后执行这里
}
}
public static void Fatal(string message)
{
WriteLine(Level.Fatal, message); //3. 这里继续执行1, 1 继续exception
}
知道了原因,我们就可以修改代码来修复了,最简单的方法就是先去掉catch里面的调用。
后续问题
那么为什么系统的StackTrack的GetStackFramesInternal会失败呢?感兴趣的同学可以尝试反编译命令来查看里面的细节。