1.背景介绍随着应用程序的复杂度不断上升,要想将好的设计思想稳定的落实到线上,我们需要具备解决问题的能力。需要具备对运行时的错误进行定位且快速的解决它的能力。本篇文章我将分享一下我对.NET应用程序调试方面的学习和使用总结。 其实对调试程序的使用是不难的,关键是知道它的调试原理才行,因为调试一个程序或者dump文件,都需要了解一定的.NET调试的原理才行,比如你在附加到进程调试时在执行某个SOS扩展命令是需要切换到指定线程上的,而调试dump文件就不需要,但是对Dump文件的分析有些SOS扩展命令是不能用的,类似这样的问题,一旦出现你就一头雾水,所以花点时间学习一下原理是有必要的。 2.基本原理(Windows调试工具箱、.NET调试扩展SOS.DLL、SOSEX.DLL)在Windows平台上调试应用程序首选Windows调试工具箱,该工具箱包含了一套专门用来针对Windows进行很多复杂场景调试所需要的工具和组件。需要注意的是此工具箱是针对于非托管.NET平台用的,意思就是说此工具箱的所有工具和组件默认是不能够进行.NET应用程序调试的,只能用来对原生Windows程序进行调试。 那么.NET平台也并不是有自己一套专用的调试工具箱,毕竟.NET还是属于Windows平台的,所以很大部分的运行时原理还是基于Windows的,要想在原生的调试器中对.NET这个具有虚拟运行时程序进行调试就需要专门的翻译器才能够执行。SOS.DLL、SOSEX.DLL这两个就是用来对.NET程序在Windows调试工具中起到翻译作用的调试器扩展。简单讲就是,这两个组件是.NET项目组专门开发出来用来对.NET应用程序进行方便调试用的,当然不用这两个扩展也能调试.NET程序,只不过就会很困难,会被很多细节束缚住。有了这个调试扩展之后,我们就可以让原生Windows调试器正确的翻译出.NET相关概念。 图1:(Windows调试工具执行流程) 所有对.NET程序发起的调试会话都要经过.NET调试扩展组件进行翻译才行,也就是要使用.NET调试扩展的调试命令来调试.NET程序。上图中,我们如果要想调试.NET程序就需要将.NET调试扩展组件加载到Windows调试工具中去,然后才能方便在Windows调试工具中使用。 2.1.Windows调试工具箱Windows调试工具箱中包含了很多调试工具,都是用来辅助于我们进行方便调试用的。Windows调试工具箱分为两个执行版本,X86、X64这两个版本是专门用来分析不同的运行时环境的,如果你的分析环境是32位的你就需要使用X86的版本,同理,如果是用64位的环境就需要使用X64的版本。 下载地址为:http://www.microsoft.com/whdc/devtools/debugging/default.aspx 记住选择你需要的版本,建议你两个版本都下载,因为你随时需要针对Dump文件进行分析,而Dump文件是随时都有可能是两个版本。 Windows工具箱中的默认使用WinDbg.exe作为调试首选,它是一个GUI程序。 图2:(默认的Windows调试工具,WinDbg) 安装过后的菜单中就只有WinDbg作为调试选择。 这里需要注意的是,当你启动了WinDbg之后要留意程序的名字和标题,因为当你存在两个版本的WinDbg时会容易搞错,在调试时会有各种奇怪的问题出现,当你找了半天之后结果发现是因为用错了版本,那就正的无语了。 图3:(注意运行WinDbg的环境版本) WinDbg是默认的调试工具,但是在工具箱中还有几个控制台调试工具,他们行必之下比较轻量简单,有些任务比较好执行,在配合cmd使用会很方便,比如工具箱中的tlist.exe用来查看进程信息的小工具就非常方便。 图4:(方便查看进程ID) 这样我们就可以很方便的attach到一个指定的进程进行调试。 Windows调试工具箱中有很多其他的工具,需要用的话可以使用cmd切换到当前安装的目录下:C:Program FilesDebugging Tools for Windows (x86),或者你直接到工具的安装目录运行也行,这就看此工具是不是支持手动无参数启动了。 2.2..NET调试扩展包,SOS.DLL、SOSEX.DLL.NET调试扩展包分为两个,一个是SOS.DLL,该扩展包是.NET平台的一部分,属于官方版本。而SOSEX.DLL是微软的一名叫“Steve Johnson”软件工程师开发,属于个人维护的,用来增强SOS.DLL功能的,在SOSEX.DLL有很多功能比较强大的扩展命令。 下载地址为: 32位:http://www./downloads/sosex_32.zip 64位:http://www./downloads/sosex_64.zip 具体的帮助文档可以查看该工程师的博客来了解详情。这两个版本用来调试不同环境的程序的,如果你的程序是运行在32位环境下,就用32位的SOSEX,同理,用在64位下就用64位SOSEX。 而SOS.DLL扩展包是跟着.NETFramework一起安装的,地址位于:C:WindowsMicrosoft.NETFrameworkv4.0.30319。如果你是64位系统的话地址就是: C:WindowsMicrosoft.NETFramework64v4.0.30319。在这两个地址下面都可以找到SOS.dll文件,不同的目录下对应于调试不同机器类型的.NET程序。 有了这两个扩展包之后就可以在WinDbg中对.NET程序进行分析了,具体使用我们后面会介绍。 2.3.调试系统的基本流程及架构(.NETDAC概念、mscordacwks.dll)有一个很重要的原理我觉得很有必要讲一下,就是.NETDAC概念。 其实.NETDAC也就是.NET Data Access .NET数据访问层,这个是专门用来提供给SOS.DLLSOSEXDLL或者其他调试扩展包使用的,所有的调试扩展组件必须通过这个DAC才能访问到.NET运行时的数据,所以在初次使用SOS的时候会经常碰见加载错误的mscordacwks.dll文件,此文件就是DAC的物理文件。 这个文件和SOS扩展文件一样,都有这不同的版本,当加载不同类型的.NET程序时会使用到不同版本的mscordacwks.dll文件,当然大部分情况下此文件时自动加载的,只有出现你分析的文件与生成调试文件的环境不一致时才会出现头疼的问题。 图5:(mscordacwks.dll位置) 当你知道这个组件是工作于此位置时,当出现跟它相关的错误提示时你就不需要担心了,无非就是文件加载的位置或者版本不匹配而已。 调试器会话、调试器注入线程 还有一点我觉得也很有必要介绍的就是有关调试器如何调试.NET程序的,当我们在使用调试器启动被调试程序或者将调试器附加到被调试进程时,其实调试器会注入一些线程到.NET程序中,让调试线程与.NET程序原本的线程在一个.NET执行环境中,这样的目的是能够起到最.NET程序在执行时的控制,比如中断执行,设置断点。当我们需要执行某些跟线程上下文相关的扩展命令时就需要切换到正确的线程上去。 图6:(调试器注入线程) 此时,调试器使用一个注入线程将.NET程序在执行时中断,原理就是通过发送线程中断命令来达到控制目标线程,那么首先要能够与原线程通讯才行,所以需要注入托管线程。(注意:注入的线程不一定就是托管.NET线程,严重它最好的方法就是查看所有所有的进程内线程和所有托管线程,对比一下就知道了。),其实这个ID为3的线程是调试器会话线程。 图7:(切换到原托管线程) 我们通过~0s命令切换到我们需要调试的原托管线程中,比如,在执行!ClrStack命令时,就需要切换到当前线程上执行。 我们需要验证它是否是注入了托管线程还是非托管线程。 图8:(托管线程列表) 使用!Threads命令可以查看进程内所有的托管线程,仅仅是托管线程,此命令是无法查看非托管线程的,接下来我们使用另外一个命令来查看所有的线程。 图9:(所有的执行时线程) 这样我们就可以判断出,调试器使用了ID位7的作为目前的调试会话线程。知道这些背后的原理很重要,当你在执行某个调试命令时你就会发现此命令是否需要在.NET线程中执行,还是说可以在调试器会话线程中执行,一般dump类的命令都是可以远程执行的,也就是说在调试器会话中执行,当需要跟踪.NET线程内部过程时就需要切换到.NET线程上去执行。 2.4.VisualStudio中集成扩展调试(更加细粒度的调试程序)SOS扩展也是可以和VisualStudio进行集成的,这样真的方便了我们调试一些性能要求比较高的程序,当程序运行一段时间后我们用VS附加到进程,然后查看一些重要的对象数据,但是此时我们看不到.NET运行时的一些数据,比如:对象的代龄,托管堆的大小,线程池的任务等。通过集成SOS扩展会让我们对程序的运行时有了一个更加方便的跟踪。 图10:(打开本地代码调试) 设置断点,然后在”即时窗口“(调试->窗口->即时)中加载扩展SOS.DLL。 图11:(在VisualStudio2012中加载SOS.dll扩展) 这样的便利性大大提高我们在调试程序内存方面、线程方面的好处,我们可以适当的做压力测试,然后Attach process,执行SOS扩展命名来查看内存问题,当需要调试程序逻辑时在单步调式C#代码,一举两得。 3.调试程序类型(客户端程序、服务端程序).NET程序主要分为两类,一类是客户端程序,另一类是服务端程序。对于这两类程序来说前者调试时基本上可以通过附加进程的方式进行调试,而对于服务端程序则不行,因为服务程序通常是运行在一个复杂的线上环境中,我们没有任何权限或机会去接触,此时是通过获取进程的dump文件来进行分析。 客户端程序也大概分为控制台、Winform两种,服务端程序都是基于ASP.NET框架,宿主与IIS进程中。 4.调试方式及场景针对不同类型的程序及场景需要使用不同的方式进行调试,客户端程序中的控制台程序基本上可以通过在调试器中启动的方式进行调试。如果是GUI程序则需要附加进程方式。服务端程序如果在条件允许下也是可以使用附加进程的方式进行调试的,但是这一般不太可能,因为一旦附加进程将block住所有的线程活动。 4.1.本机调试(Attach Process,调试器启动)本机调试可以直接在调试器中启动程序,WinDbg打开后,在文件中有一个Open Executable,可以打开一个可执行文件。如果是使用NTSD控制台调试器,则需要在NTSD后面跟上程序的执行路径。 图12:(ntsd.exe打开调试程序) 同样,在WinDbg中也有一个附加进程的选项,NTSD也是一样,操作起来都比较简单,需要注意的是当你对进程进行附加时要清楚此进程是多少位的,然后你需要选择正确的调试器进行调试。 4.2.不中断调试或者称事后调试(对Dump文件进行调试)在不能够对被调试程序直接调试时我们就需要此程序的进程镜像文件,此镜像文件就是进程在某一个时刻的快照,通过分析这个快照,我们也是可以定位出问题的。首先我们需要使用适当的工具来获取进程的dump文件,操作系统本身的任务管理器就有这个功能,dump文件的存放位置默认在用户信息临时文件下面,比如:XXXUsersAdministratorAppDataLocalTemp,获取完dump文件后任务管理器会有提示路径的。 图13:(使用任务管理器获取dump文件) 图14: 使用任务管理器获取dump文件固然很方便,但是有一个问题就是如果当前机器是64位的,并且你的进程是以32位方式运行的,那么此时你获取出来的dump文件是64位的,当你通过32位的调试器无法进行分析,甚至会有各种其他的问题,这些问题就是因为获取dump文件的机器环境和你预想的不一致。这个时候我们希望能够通过很明了的方式来获取dump文件,就是通过调试器来获取dump文件。 通过调试器来获取dump文件有很多好处,可以设置很多选项,包括只获取进程的哪部分镜像数据等。 先通过tlist.exe查看所有进程列表,会有一个进程ID号,有了ID号才能进行获取。 图15:(tlist、ntsd 进入到指定进程中) 进入到ntsd调试器中,然后使用.dump/mf d:order.dmp 命令获取dump文件到D盘。 图16:(使用NTSD.exe获取dump文件) 此时我们就成功的获取到了dump文件。 通过调试器获取dump文件比较稳定可靠,因为机器运行环境的不同,通过任务管理器获取的dump文件会存在一些无法预知的问题,你并不清楚,当前任务管理器是使用哪个版本的环境输出调试信息的。 有了dump文件之后就是通过调试工具打开就行了,WinDbg就有一个菜单专门打开dump文件的,Open Crash Dump。使用ntsd需要使用命令ntsd -z d:order.dmp。 5.一般调试步骤知道了调试的一些原理和工具之后我们来看一下调试的基本步骤,这些步骤都具体是指的什么意思,有哪些好处。 5.1.设置符号文件(公有符号、私有符号)设置符号文件的目的是为了能够在调试器中正确的对应到源代码的位置和一些元数据信息。符号文件都是*.pdb文件名。符号文件分为公有和私有两种,公有的都是公司公开出去用于帮助调试用的,而私有的是公司内部使用的,为什么要区分公有和私有,是为了防止逆向工程。 图17:(设置符号文件路径) 首先通过.sympath d:,设置了符号路径为D盘,然后又使用.symfix+ d:,是设置私有符号路径,并且使用d盘为缓存路径。在最后一个红线中我们能看出来。 为什么使用.symfix 时要带上一个+号,其实是告诉调试器我们是多加一个符号位置,而不是覆盖原有符号位置。 设置好了两个符号位置后需要使用.reload命令来重新加载模块,这样调试器才会去符号位置去加载这些符号。 图18:(加载的符号文件) 调试器会自动的将公有符号下载到你刚才设置的缓存目录中。 5.2.加载.NET程序扩展调试包(SOS.DLL、SOSEX.DLL)对.NET程序分析当然是需要加载SOS扩展了。加载SOS扩展有两个命令可以使用,第一个是.load C:WindowsMicrosoft.NETFrameworkv4.0.30319SOS.dll,.load命令是要给出sos.dll绝对路径的。第二个是.loadby sos modulename,.loadby 命令是可以根据已经加载的模块名称来加载SOS.dll扩展。使用第一个命令有一个问题就是,我们需要人工的判断当前环境到底是需要什么版本的SOS扩展,而使用.loadby是可以根据已经加载的模块来自动的查找对应的SOS扩展。 0:000> .load C:WindowsMicrosoft.NETFrameworkv4.0.30319SOS.dll 0:000> .loadby sos.dll clrjit 使用.loadby 命令很容易的就可以加载SOS扩展,而不需要自己去判断当前程序是.NET什么版本的。 5.3.调试的三种命令类型(标准命令、元命令、扩展命令)在使用调试器调试程序时,所要使用的命令主要分为三类。 第一类是标准命令,就是不带任何符号开始的命令,比如:pb、lmvm。这一类命令是所有Windows调试工具箱中的调试工具通用的,不管你是使用ntsd还是winDbg都可以。 第二类命令是元命令,就是使用”.”号开始的命令,这一类命令并不是在所有调试工具中通用的。第三类是扩展命令,扩展命令就是各个调试器扩展出来的命令,也就是以”!”开始的命令,如:!dumpheap -stat,!dumpstatcobjects。 6.调试扩展的几个比较常用的命令(SOS.DLL、SOSEX.DLL)当然这个纯粹是我的个人感觉,排名不分先后。
可以一眼看出哪些对象过大,这里我是为了演示而用,一般在项目开发中,我们都大概知道哪些对象可能会有内存问题,比如:同步数据时的缓存对象。
当然还有很多其他很不错的命令,这里我个人觉得这几个比较常用,要想了解所有的命令可是在调试器中使用扩展命令!help来查看所有的命令帮助。
|
|
来自: weijianian > 《asp.net》