当你的代码发布为产品后,无论是在其他人的电脑中运行,还是部署到服务器中,你通常不再能访问到它的程序文件,也不能观察到代码的当前运行情况和运行环境。当你的代码在新的环境运行时,有很多因素会影响到程序的运行情况,如服务器系统打了更新补丁,网络策略改变,防火墙规则限制,磁盘权限配置等等。当代码运行不正常时,你可能只能靠代码中各处输出的日志来判断运行情况。但只靠输出日志,你有时还是不能判断故障出现的原因。 在不浪费客户时间的同时做故障排除对你是个很大的挑战,因为不会有客户喜欢被一个搞技术的家伙不断问是点了那个按钮操作了哪些步骤导致程序出错的。boss也不会给你几天甚至几周的时间让你慢慢排除故障,你必须现在就知道到底发生了什么。 理想情况下,你应该能看到堆栈跟踪,能查看到当前的变量值,能调试代码。事实证明你可以做到这点。。。而且不需要附加到客户环境中!
下载WinDbg
下载Debugging Tools for Windows到你的本地开发机中,windbg是其中的一部分,如果你只需要windbg,在可以在“Common Utilities”中选择“Debugging Tools for Windows"进入安装。安装程序会根据你电脑的cpu类型安装对应的windbg,x86的电脑会安装x86版本,x64的cpu电脑就会安装x64版。如果你选择的是“Redistributable Packages“,就会为你下载全部三个版本(x86,x64,Itanium)。
WinDbg默认会安装到c盘的“Program Files\Debugging Tools for Windows”文件夹下,建议你把安装目录复制到“d:\\debug”,这样方便后面增加其他扩展组件。 安装好后,目录下的windbg.exe就是windbg程序
安装PssCor2
下一步是加载托管代码扩展组件PssCor2。默认时,WinDbg只能用于调试非托管代码程序 ,而加载.net使用的SOS.dll扩展组件后,WinDbg就能调试托管程序了。WinDbg调试.net程序的另一个选择是PssCor2,它是SOS.dll的超集,并提供了一些面对托管代码的额外功能,如查看托管线程,托管堆,CLR堆栈等等。下载PssCor2并解压到“d:\\debug”,以方便后面的调试使用。
设定符号文件路径(Symbol Path)
当你使用Visual Studio编译程序时,是否有留意到在bin/Debug文件夹下会有.pdb后缀的文件?这些文件包含有dll程序集的调试符号,pdb文件并不包含有执行代码,只是使调试工具能把代码执行指令翻译为正确的可识别字符。微软提供了包含大量pdb文件的公共服务器,地址如下: http://msdl.microsoft.com/download/symbols 在windbg中设定符号文件路径后,相关的pdb文件会自动从服务器下载下来并保存到本地。你首先需要指定一个pdb文件的保存路径,如“d:\\debug\symbols”。
打开windbg程序,选择“File->Symbol File Path…“,把下面的内容复制进去保存。
srv*d:\debug\symbols*http://msdl.microsoft.com/download/symbols
创建测试程序
我们先创建一个简单的命令行程序用于测试:
using System;
namespace Microsoft.PFE.Samples
{
public class Program
{
static void Main()
{
Console.WriteLine("Enter a message:");
string input = Console.ReadLine();
Data d = new Data
{
ID = 5,
Message = input,
CurrentDateTime = System.DateTime.Now
};
Console.WriteLine("You entered: " + d);
}
}
public class Data
{
public int ID {get; set;}
public string Message {get; set;}
public DateTime CurrentDateTime {get; set;}
public override string ToString()
{
Console.ReadLine();
return string.Format("ID:{0} {1} at {2}", ID, Message,
CurrentDateTime.ToLongTimeString());
}
}
}
因为PssCor2只能处理.Net 3.5以下的程序,所以在编译前需要先把程序的环境改为.Net 3.5。假如是调试.NET 4.0的程序,可以下载PssCor4。编译运行程序,输入一个字符串,看程序是否运行正常。 客户抱怨说不知道为什么程序需要按两次enter键。程序并不按我们的预期工作,我们必须找到具体的原因。作为一个简单的例子,我们能一眼看出代码中的ToString()方法中多了一次ReadLine()导致的,但我们这次试下用windbg找出问题所在。 运行程序,输入一个字符串,按一次enter,当出现第二次输入提示时,不要动!我们处于捕捉问题的关键点,我们需要做一个dump文件。
创建dump文件
在windows7和windows2008中,可以在任务管理器中直接创建dump文件。只需打开任务管理器,右键进程名并选择“Create Dump File”。
dump文件创建成功后,我们会看到提示:
dump文件是当前进程的内存快照,dump文件的大小会和进程使用的内存大小一样,为了减少体积,你可以使用压缩软件进行压缩。 还有另外的工具可以创建dump文件,如Process Explorer from SysInternals,也只需要在任务管理中右键选择“Full Dump”。
ADPlus和DebugDiag也可以创建dump文件。ADPlus是windbg安装目录下的一个命令行程序,你可以用下面的命令创建一个dump文件:
Adplus -quiet -hang -p 4332 -o d:\debug
4332是进程id,任务管理器默认是不显示进程id的,要显示出来,需要在windows任务管理器选择“查看->选择列”,勾选“PID(进程标识符)”。
开始使用WinDbg
现在我们有了程序dump文件,打开windbg程序,选择菜单“File->Open Crash Dump”,并选择刚创建的dump文件,你会看到一些信息:
Loading Dump File [D:\debug\program6.dmp]
User Mini Dump File: Only registers, stack and portions of memory are available
Symbol search path is: srv*d:\debug\symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
Windows 7 Version 7600 MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
Machine Name:
Debug session time: Sun Feb 6 10:43:57.000 2011 (GMT-6)
System Uptime: not available
Process Uptime: 0 days 1:05:48.000
.........................
ntdll!NtRequestWaitReplyPort+0xa:
00000000`76d2ff7a c3 ret
在上面的文字中,你可以看到dump文件的路径,符号文件的查找路径等信息。而程序最下方有个输入框,你可以在上面输入命令。
显示模块
让我们试下显示程序已加载了哪些模块。在窗口最下方的输入框中,输入“lm”命令。
0:000> lm
start end module name
00000000`00120000 00000000`00128000 program (deferred)
00000000`742b0000 00000000`74379000 msvcr80 (deferred)
00000000`76ac0000 00000000`76bba000 user32 (deferred)
00000000`76bc0000 00000000`76cdf000 kernel32 (pdb symbols) d:\debug\symbols\kernel32.pdb\D5E268B5DD1048A1BFB011C744DD3DFA2\kernel32.pdb
00000000`76ce0000 00000000`76e8b000 ntdll (pdb symbols) d:\debug\symbols\ntdll.pdb\0F7FCF88442F4B0E9FB51DC4A754D9DE2\ntdll.pdb
000007fe`f3fb0000 000007fe`f4134000 mscorjit (deferred)
000007fe`f5030000 000007fe`f5f0b000 mscorlib_ni (deferred)
000007fe`f7650000 000007fe`f7ffe000 mscorwks (deferred)
000007fe`f8010000 000007fe`f80a0000 mscoreei (deferred)
000007fe`f80a0000 000007fe`f810f000 mscoree (deferred)
000007fe`fcb70000 000007fe`fcb7f000 CRYPTBASE (deferred)
000007fe`fcc40000 000007fe`fcc4f000 profapi (deferred)
000007fe`fcf20000 000007fe`fcf8b000 KERNELBASE (deferred)
000007fe`fd0e0000 000007fe`fd2e2000 ole32 (deferred)
000007fe`fd4d0000 000007fe`fd59a000 usp10 (deferred)
000007fe`fd6f0000 000007fe`fe476000 shell32 (deferred)
000007fe`fe480000 000007fe`fe4ae000 imm32 (deferred)
000007fe`fe840000 000007fe`fe84e000 lpk (deferred)
000007fe`fe9d0000 000007fe`feaab000 advapi32 (deferred)
000007fe`feb50000 000007fe`fec7e000 rpcrt4 (deferred)
000007fe`fec80000 000007fe`fecf1000 shlwapi (deferred)
000007fe`fed00000 000007fe`fed67000 gdi32 (deferred)
000007fe`fee10000 000007fe`fef19000 msctf (deferred)
000007fe`fef20000 000007fe`fefbf000 msvcrt (deferred)
000007fe`fefd0000 000007fe`fefef000 sechost (deferred)
在上面的模块列表中,你需要关注的是mscorwks是否存在,PssCor2只能用于.NET 3.5的程序,假如是.NET 4.0程序,就看不到mscorwks模块。 对于SharePoint开发人员,假如你正在调试程序特性如 receivers 和 event 处理为什么不触发,lm将是很好用的命令。通过上面的列表,你能知道有哪些模块没加载进来,可能是配置不正确导致的,这样能大大地减少你查找问题的范围。对于ASP.NET开发人员,会有助于查找HttpModule不触发的原因,可能是web.config配置不正确。
加载PssCor2
要把PssCor2扩展组件加载入windbg,需使用下面的命令:
.load d:\debug\psscor2\amd64\psscor2.dll
我的电脑是64位的,所以我加载了AMD64 版本的PssCor2.dll。加载的PssCor2版本必须和dump文件进程所在服务器的架构相一致,假如你是调试x86程序的dump文件,你就必须加载x86版本的PssCor2.dll。 输入下面的命令,以确认PssCor2是否加载成功:
!help
正确会输入下面的内容:
0:000> .load d:\debug\psscor2\amd64\psscor2.dll
0:000> !help
-------------------------------------------------------------------------------
PSSCOR is a debugger extension DLL designed to aid in the debugging of managed
programs. Functions are listed by category, then roughly in order of
importance. Shortcut names for popular functions are listed in parenthesis.
Type "!help " for detailed info on that function.
Object Inspection Examining code and stacks
----------------------------- -----------------------------
DumpObj (do) Threads
DumpArray (da) CLRStack
DumpStackObjects (dso) IP2MD
DumpAllExceptions (dae) BPMD
DumpHeap U
DumpVC DumpStack
GCRoot EEStack
ObjSize GCInfo
FinalizeQueue EHInfo
PrintException (pe) COMState
TraverseHeap
DumpField (df)
DumpDynamicAssemblies (dda)
GCRef
DumpColumnNames (dcn)
DumpRequestQueues
DumpUMService
Examining CLR data structures Diagnostic Utilities
----------------------------- -----------------------------
DumpDomain VerifyHeap
EEHeap DumpLog
Name2EE FindAppDomain
SyncBlk SaveModule
DumpThreadConfig (dtc) SaveAllModules (sam)
DumpMT GCHandles
DumpClass GCHandleLeaks
DumpMD VMMap
Token2EE VMStat
EEVersion ProcInfo
DumpModule StopOnException (soe)
ThreadPool MinidumpMode
DumpHttpRuntime FindDebugTrue
DumpIL FindDebugModules
PrintDateTime Analysis
DumpDataTables CLRUsage
DumpAssembly CheckCurrentException (cce)
RCWCleanupList CurrentExceptionName (cen)
PrintIPAddress VerifyObj
DumpHttpContext HeapStat
ASPXPages GCWhere
DumpASPNETCache (dac) ListNearObj (lno)
DumpSig
DumpMethodSig Other
DumpRuntimeTypes -----------------------------
ConvertVTDateToDate (cvtdd) FAQ
ConvertTicksToDate (ctd)
DumpRequestTable
DumpHistoryTable
DumpBuckets
GetWorkItems
DumpXmlDocument (dxd)
DumpCollection (dc)
Examining the GC history
-----------------------------
HistInit
HistStats
HistRoot
HistObj
HistObjFind
HistClear
mscordacwks.dll
我喜欢在服务器中创建程序的dump文件,然后把dump文件转移到自己的windows7开发机上进行调试。假如服务器是Windows Server 2008 R2的操作系统,当我在本地开发机使用psscor2时,很容易遇到下面的错误:
CLRDLL: CLR DLL load disabled
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
2) the file mscordacwks.dll that matches your version of mscorwks.dll is
in the version directory
3) or, if you are debugging a dump file, verify that the file
mscordacwks___.dll is on your symbol path.
4) you are debugging on the same architecture as the dump file.
For example, an IA64 dump file must be debugged on an IA64
machine.
You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload.
If that succeeds, the PSSCOR command should work on retry.
If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.
通过bing搜索发现一篇相关的博客文章how to work around the mscordacwks issue,文章指出需要把服务器的mscordacwks文件拷贝到windbg程序目录下。mscordacwks在我的Windows Server 2008 R2服务器上的版本是4952,所以我把服务器上的mscordacwks拷贝到windbg目录,并重命名为“mscordacwks_AMD64_AMD64_2.0.50727.4952.dll“。mscordacwks在服务器的路径是“”C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll”,假如你不知道正确的重命名规则,你可以输入下面的命令,输出会提示需要加载的mscordacwks命名。
0:000> .cordll -ve -u -l
CLR DLL status: No load attempts
0:000> !threads
CLRDLL: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.3053 f:0
doesn't match desired version 2.0.50727.3625 f:0
CLRDLL: Unable to find mscordacwks_AMD64_AMD64_2.0.50727.4952.dll by mscorwks search
CLRDLL: Unable to find 'mscordacwks_AMD64_AMD64_2.0.50727.4952.dll' on the path
CLRDLL: Unable to get version info for 'd:\debug\symbols\mscorwks.dll\4E154C985a9000\mscordacwks_AMD64_AMD64_2.0.50727.4952.dll', Win32 error 0n87
CLRDLL: ERROR: Unable to load DLL mscordacwks_AMD64_AMD64_2.0.50727.4952.dll, Win32 error 0n87
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
2) the file mscordacwks.dll that matches your version of mscorwks.dll is
in the version directory
3) or, if you are debugging a dump file, verify that the file
mscordacwks___.dll is on your symbol path.
4) you are debugging on the same architecture as the dump file.
For example, an IA64 dump file must be debugged on an IA64
machine.
You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload.
If that succeeds, the PSSCOR command should work on retry.
If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.
重命名后,再次输入上面的命令,会显示加载成功提示。
0:000> .cordll -ve -u -l
CLR DLL status: Loaded DLL mscordacwks_AMD64_AMD64_2.0.50727.4952.dll
检查CRL堆栈
要查看CLR堆栈内容,输入下面命令:
!clrstack
输出如下:
0:000> !clrstack
OS Thread Id: 0xa48 (0)
*** WARNING: Unable to verify checksum for mscorlib.ni.dll
Child-SP RetAddr Call Site
000000000012e910 000007fef5a910e9 DomainNeutralILStubClass.IL_STUB(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
000000000012ea30 000007fef5a91202 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Int32, Int32 ByRef)
000000000012ea90 000007fef538065a System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
000000000012eaf0 000007fef53a28ca System.IO.StreamReader.ReadBuffer()
000000000012eb40 000007fef5a9435f System.IO.StreamReader.ReadLine()
000000000012eb90 000007ff0017015b System.IO.TextReader+SyncTextReader.ReadLine()
000000000012ebf0 000007fef791d502 Program.Main()
真棒!你现在可以看到堆栈的调用情况了。我们可以立即看到程序进入了Program.Main函数,调用Console.ReadLine并等待用户的输入。
结论
本篇文章只是简单介绍windbg的使用,假如你想全局的了解windbg,并如何使用windbg做故障排除,可以看下Tess Ferrandez的视频教程“Debugging .NET Applications with WinDbg“。