无论你是一个单纯的电脑用户还是一名高级软件工程师,都一定对程序崩溃不陌生。做为一名Windows CE应用程序开发者,你也一定遇到过下图这种场景:
这个对话框告诉你,有一个叫installer.exe的程序在地址00019320处崩溃了。如果这个程序归你负责,那么你的问题就来了:怎么找出这个BUG?这篇文章我想谈谈我在这方面的一些经验。
Windows CE的崩溃界面给出的信息十分的少,其中最有用的无疑是崩溃地址,如果你能从崩溃地址定位到源代码去,这个问题可以说就解决了一半。
从地址定位到源代码的方法有几种。一种是利用MAP文件:你可以在BUILD程序时生成MAP文件。MAP文件是一个文本文件,其中主要记录了各个函数入口对应的地址信息。比如这个例子中,崩溃地址对应的入口是:
view plaincopy to clipboardprint?
0001:000082f4 ??1?$CComPtr@UIImage@@@ATL@@QAA@XZ 000192f4 f i ImageViewer.obj
MAP文件的好处是它是文本文件,可以人工阅读,缺点是信息不够多,只能定位到函数级别,而且要看懂MAP文件你需要有足够的经验,比如其中那串长长的貌似乱码的字符串是C++函数经过name mangling处理后的结果,没有一定的经验你根本没法还原出实际的函数。
另 一种方法是利用PDB文件,PDB文件收集了应用程序的调试符号。PDB文件提供的信息很全,不过你得需要一定的工具才能解读它。如果你是一个经验丰富的 Windows桌面平台的应用程序开发者,你可能听说过MSJ(Microsoft System Journal)这本杂志。如果你曾经看过这本杂志,应该对Bugslayer不陌生。在这篇文章中,Bugslayer 介绍了他做的一个工具:CrashFinder。CrashFinder可以从崩溃地址通过查询相应的PDB文件直接定位到导致崩溃的源代码行。幸运的 是,由于Windows CE可执行程序及其PDB文件的格式和Windows桌面系统上的是一样的,因此CrashFinder也可以用来定位Windows CE程序的崩溃地址。下面是CrashFinder显示的结果:
CrashFinder提供的信息十分有用,但是不够直观。为此我在Remote Process Explorer提供了一个更方便的界面,它可以直接显示源代码,并把导致崩溃的那一行highlight出来:
PDB 文件里包含着大量的调试信息可以帮助你诊断应用程序错误,因此一般来说即使是正式release的版本,你也应该生成并维护好这些PDB文件。使用PDB 文件的关键是崩溃的应用程序和PDB文件一定要匹配,否则它不但不能帮到你,反而会误导你。下图是VS2005中设置生成PDB文件和MAP文件的地方:
Data Abort: Thread=8d661000 Proc=81a477c0 'installer.exe'
AKY=00000401 PC=00019320(installer.exe+0x00009320) RA=00019094(installer.exe+0x0 0009094) BVA=16080100 FSR=00000007
我 相信Windows CE开发者一定也对这几行信息很熟悉。怎么利用这些信息诊断程序的问题?这里面最关键的信息是PC和RA给出的地址信息。PC就是上面提到的崩溃地址,根 据这个地址用CrashFinder或者我的Remote Process Explorer里的Crack Address界面可以定位到导致崩溃的源代码行;RA是PC的返回地址(Return Address),根据这个地址可以找到导致崩溃的上一级函数,这个信息也很重要,因为很多时候崩溃的原因往往是上层函数往底层函数传递了非法参数导致 的,比如你的应用程序用一个非法的窗口类调用MFC函数,崩溃地址在MFC函数里面,但是出问题的地方在你的调用代码里。下图是RA地址对应的源代码:
除了PC和RA,其他信息也可以提供一些参考作用:BVA是ARM中的Fault Address Register(FAR),是引起Data Abort的虚拟地址,比如说你的程序试图访问一个非法地址里的内容,那么Data Abort时BVA就是这个非法地址;FSR是Fault Status Register,指明导致异常的原因,FSR的解释可以看这里。 要注意的是Thread和Proc给出的不是Thread Id/Thread Handle或者Process Id/Process Handle,它们给出的是该Thread或Proc对应的内核对象的指针,类似于Window NT平台的TEB和PEB的概念。由于你看到崩溃信息时线程已经退出了,因此根据这个信息在事后你无法知道是哪个线程出的错。以后我将介绍一种系统级的 logging机制,这种机制可以把每条log的Thread Id、TEB等信息同时记录下来,这样在崩溃时就可以根据Data Abort的TEB信息和先前log中出现的TEB,找到出错的线程。这样,你不但可以定位到错误的源代码,还能找到运行错误代码的线程,将大大提高解决 问题的效率。
-----------------------------------------
首先要确保你的PC上有dbghelp.dll。微软没有公开PDB文件格式,不过有解析PDB文件的API,在dbghelp.dll中提供,最新版本可在微软网站上下载。在微软推出.NET Framework之前,基于COM组件编程很流行,因此微软也以COM组件的形式提供了另外一组接口,叫DIA(Debug Interface Access)。Remote Proecess Explorer用dbghelp.dll解析WinCE调试符号,由于WinCE的可执行程序模块使用PE格式,调试符号文件使用PDB格式,和Windows桌面平台一样。因此它也可以分析Windows桌面应用程序地址。dbghelp.dll可以放到系统目录(c:windowssystem32),或者放到remote process explorer所在目录。其次应该把EXE/DLL和PDB放在一个目录。如果放在不同目录,dbghelp可能会找不到PDB。具体用这个工具有几步。
举个例子来说明,比如要定位下面的崩溃地址
PC=03f7e034:RaiseException: Thread=8d5108b0 Proc=81a37040 'device.exe' AKY=00000205 PC=03f7e034(coredll.dll+0x0001e034) RA=80120ef4(NK.EXE+0x00020ef4) BVA=00000001 FSR=00000001
从PC=03f7e034(coredll.dll+0x0001e034) 这句我们知道三个重要信息:崩溃模块coredll.dll,崩溃地址0x03f7e034,模块加载基地址0x03f7e034-0x0001e034=0x03f60000。CE5以前的版本不会显示(coredll.dll+0x0001e034),基地址不能直接算出来。不过一般来说这些DLL如果你的系统没有做过很大改变,每次加载的基地址是一样的,所以你可以把系统运行起来查看它的加载基地址。
根据这三条信息Remote Process Explorer就可以工作了:
1,Cracking Address填崩溃地址:0x03f7e034
2,base address填基地址:0x03f60000
3,模块路径栏填coredll.dll的全路径,可以用右面的按钮定位。
现在可以试着按第三行的寻找按钮(有一个放大镜的图标)。顺利的话应该就能提示源文件路径、函数名和代码行。在设当设置了调试符号搜索路径和源文件搜索路径后,下面的文本框还能直接显示源代码,崩溃行用红色标出。搜索路径在"File --> File Path..."对话框里设置(多个路径用分号;分隔):
由于生成PDB时的源文件路径和我们安装Platform Builder的WINCE路径肯定不一样,因此最下面的"Source Path Mapping"还要做一些路径映射,方便Remote Process Explorer搜索源代码。
来源:博客园 作者:sting feng
Mark Russinovich的博客
remote process explorer 源程序:cepro.rar
Bugslayer:http://www.microsoft.com/msj/0498/bugslayer0498.aspx