转载请注明出处:http://blog.csdn.net/ecjtu_luowei/article/details/43974727
一、前言
WinDBG作为Microsoft的御用工具,其强大之处使我这等小辈难以望其项背,它设计了极其丰富的功能来支持各种调试任务,包括用户态调试、内核态调试、dump文件调试、远程调试等。其灵活性和可扩展性能极大满足调试要求。所以对于WinDBG,读者非常有必要花费一些业余时间来学习研究。
本文主要是通过调试C#编写的NET程序来对WinDBG作一个片面的介绍,由于笔者真正开始使用Windbg的时间并不长,所以文中难免有许多谬误之处,如在阅读本文过程中遇到问题或错误,可直接联系笔者(sbcd3760@gmail.com)。
二、概述
尽管Windbg是个GUI程序,但是大部分工作都是通过命令的方式。Windbg共支持三种命令:标准命令、元命令、和扩展命令。在接下来的文章中,这三种命令都会有所涉及。
标准命令往往都非常简单,通常一两个字符或符号,用来提供使用各种调试目标的最基本调试功能。如:g、t、p、q等。
元命令用来提供标准命令没有提供的调试功能,元命令和标准命令都是内建在调试器或Windbg程序文件中。
扩展命令用于扩展某一方面的调试功能。扩展命令是通过加载第三方的DLL来实现调试任务的。调试net程序大部分使用的都是扩展命令,主要的扩展dll就是SOS.dll和SOSEX.dll。
这三种命令有个明显的区分方式就是元命令都是以点(.)开始,扩展命令通常以感叹号(!)开始,如果一个有效的命令既不是扩展命令也不是元命令那么它就是标准命令了。
三、开始调试
3.1 调试符号(Symbol)
看大部分Windbg的基础教程和Windbg的官网首页上都首先提到Symbol路径的设置,Symbol其实主要起一个辅助作用,在调试分析的过程中对内存中的机器代码翻译成对应的源代码的信息或是Win32 API的信息,如函数名,数据结构名,变量名、机器指令对应的源文件名和行号等。一般我们在组建编译文件的时候生成exe或dll文件的同时也会生成一个对应的pdb文件,这个文件就包含了exe或dll的辅助信息。
是否加载Symbol的区别如下:
没有加载Symbol |
0:000> u D20108 *** ERROR: Module load completed but symbols could not be loaded for notepad.exe notepad+0x108: 00d20108 8936 mov dword ptr [esi],esi 00d2010a 0000 add byte ptr [eax],al 00d2010c 0010 add byte ptr [eax],dl 00d2010e 0000 add byte ptr [eax],al 00d20110 00c0 add al,al 00d20112 0000 add byte ptr [eax],al 00d20114 0000 add byte ptr [eax],al 00d20116 d200 rol byte ptr [eax],cl |
加载Symbol |
0:000> u D20108 notepad!_imp__RegSetValueExW <PERF> (notepad+0x108): 00d20108 8936 mov dword ptr [esi],esi notepad!_imp__RegSetValueExW <PERF> (notepad+0x10a): 00d2010a 0000 add byte ptr [eax],al notepad!_imp__RegSetValueExW <PERF> (notepad+0x10c): 00d2010c 0010 add byte ptr [eax],dl notepad!_imp__RegSetValueExW <PERF> (notepad+0x10e): 00d2010e 0000 add byte ptr [eax],al notepad!_imp__RegSetValueExW <PERF> (notepad+0x110): 00d20110 00c0 add al,al notepad!_imp__RegSetValueExW <PERF> (notepad+0x112): 00d20112 0000 add byte ptr [eax],al notepad!_imp__RegSetValueExW <PERF> (notepad+0x114): 00d20114 0000 add byte ptr [eax],al notepad!_imp__RegSetValueExW <PERF> (notepad+0x116): 00d20116 d200 rol byte ptr [eax],cl |
如果Symbol文件和对应的dll或exe在同一目录下,那么在调试的过程中,当dll或exe文件被加载的时候,对应的Symbol文件也会自动加载,所以一般情形下我们不需要设置Symbol路径。如果pdb文件和dll文件不再同一目录下,我们也可以手动的设置Symbol的路径。一般设置Symbol的方式有两种:
1. 通过菜单设置
2. 通过命令设置:
0:004> .sympath d:\symbol Symbol search path is: d:\symbol Expanded Symbol search path is: d:\symbol
************* Symbol Path validation summary ************** Response Time (ms) Location OK d:\symbol 0:004> .reload Reloading current modules ........................ |
注意:设置了Symbol路径需要重新加载,否则设置的Symbol路径无效。
Symbol路径的设置同样可以指定多个路径,多个路径通过分号(;)隔开, 例如:
0:004> .sympath+ d:\symbol2 Symbol search path is: d:\symbol;d:\symbol2 Expanded Symbol search path is: d:\symbol;d:\symbol2
************* Symbol Path validation summary ************** Response Time (ms) Location OK d:\symbol OK d:\symbol2 |
Symbol路径还可以设置连到server路径,需要的时候从server上下载(前提是server有你需要的Symbol)。最有名的Symbol server就是Microsoft的http://msdl.microsoft.com/download/symbols,但是Server上含有的Symbol大都是一些Common的调试符号,Microsoft的这个Symbol Server 使用于驱动开发程序的Debug或者对Win API的分析,于NET程序的调试帮助有限,所以这里不多讲,详细配置可参考Windbg的官网。
3.2 调试目标
不同的调试环境决定了对目标进行调试的局限性,根据调试目标和调试环境的不同,一般建立调试会话分为4种:
1. 启动调试目标程序
2. 附加(attach)已经启动的程序
3. 调试Dump文件
4. 远程调试
1. 启动调试目标程序
第一种是非常常见的,就像我们用vs写完了程序直接F5一样,建立这样的调试环境也很简单。打开Windbg后通过快捷键Ctrl + E或者通过菜单:
启动一个NET程序后能看到:
Microsoft (R) Windows Debugger Version 6.3.9600.16384 X86 Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: D:\Code\Ext1\Ext1\bin\Debug\Ext1.exe Symbol search path is: *** Invalid *** **************************************************************************** * Symbol loading may be unreliable without a symbol search path. * * Use .symfix to have the debugger choose a symbol path. * * After setting your symbol path, use .reload to refresh symbol locations. * **************************************************************************** Executable search path is: ModLoad: 00360000 00368000 Ext1.exe ModLoad: 77b60000 77ce0000 ntdll.dll ModLoad: 73570000 735ba000 C:\Windows\SysWOW64\MSCOREE.DLL ModLoad: 75720000 75830000 C:\Windows\syswow64\KERNEL32.dll ModLoad: 768f0000 76936000 C:\Windows\syswow64\KERNELBASE.dll (4e8.1060): Break instruction exception - code 80000003 (first chance) *** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll - eax=00000000 ebx=00000000 ecx=693b0000 edx=0011e0f8 esi=fffffffe edi=00000000 eip=77c00f3b esp=002af8a4 ebp=002af8d0 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!LdrVerifyImageMatchesChecksum+0x96c: 77c00f3b cc int 3 |
最后一行执行的是INT 3,这是个软中断指令,机器码是CCh,所以也被称为CC指令。当调试目标程序执行INT 3时会进入异常处理,这个时候调试器就会捕获这个异常,从而停在这个地方。这就是下断点的方式,当然这次中断并不是用户所下的断点,而是当Windbg调试器在启动一个被调试进程的时候,会自动注入一个中断指令,在下面要讲到的Attach一个进程的后也会自动注入一个中断指令,这样做的目的是便于用户通过调试器分析调试目标。当目标程序在运行的时候,我们可以通过快捷键Ctrl + Break的方式注入一个中断指令或者通过菜单进行设置:
(4e8.c90): Break instruction exception - code 80000003 (first chance) eax=7ef9c000 ebx=00000000 ecx=00000000 edx=77bff7ea esi=00000000 edi=00000000 eip=77b7000c esp=04bfff0c ebp=04bfff38 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!DbgBreakPoint: 77b7000c cc int 3 |
需要注意的是,这种方式建立的调试会话都是非入侵式的,这意味着当你关掉Windbg的时候,被调试目标也会随着关掉。为了避免调试目标被关掉可以通过Menu -> Debug -> Detach Debuggee或者输入命令qd
2. 附加(attach)已经启动的程序
如果一个程序已经在运行,那么这时候可以使用附加进程的方式建立调试会话。可以通过快捷键F6或者通过Menu -> File -> Attach to a Process:
注意上面的弹出窗口,在底下有个Noninvasive,这个checkbox就决定了当前的调试会话是否是入侵式的,如果不check,那么就是入侵式的,这个时候关掉调试器,调试目标继续运行。对于非入侵式的调试会话怎么避免调试目标的关闭,可以参考本文的上一小节。
3. 调试Dump文件
很多情况,bug的发现都是在客户的环境上,这个时候我们不可能让客户等着我们去帮他们去调试当时的环境,这个时候我们可以要求客户在出问题的时候创建一个dump文件给我们分析,然后我们拿到dump文件,就可以通过分析这个dump找到对应的问题。
Dump文件主要是将内存中的内容储存起来的物理文件,根据储存的不同内存段,可以将dump文件分为内核模式dump(Kernel-mode dump)和用户模式dump(User-mode dump)。其中这两大模式的Dump也有不同类型,其中用户模式dump又可以分为完全dump(Full User-Mode Dump)和迷你dump(Minidump),通常我们用到的都是用户模式的minidump,因为minidump远远小于full dump,便于客户传给我们分析,同时分析minidump足以让我们分析出问题。
对于内核模式和用户模式的dump,看名字就能知道这两种dump的区别,在这里不多说。Full dump和minidump的区别在于full dump包含了所有用户空间的内存,同时还包括了虚拟内存,minidump只包含目标进程相关的内存。所以在这里只介绍创建minidump的命令:
0:007> .dump /ma d:\1.dmp Creating d:\1.dmp - mini user dump GenInvokeEnumStackProviders(C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll) failed, 0x8007007f Dump successfully written |
创建dump的命令是“.dump”, /ma是选项,表示创建minidump,d:\1.dump是参数,表示创建dump的文件名。
当我们拿到dump之后可以直接在Windbg中打开这个dump文件,通过Menu -> File -> Open Crash Dump 。值得一提的是,dump文件是创建dump时的内存镜像,所以我们不能指望还可以执行调试目标,来看怎么重现的问题。
4. 远程调试
远程调试的方法有很多,常用的有四种:
a. 通过调试器构建远程调试会话
b. 通过Remote.exe程序
c. 激活一个进程服务器(dbgsrv.exe)
d. 激活Repeater(dbengprx.exe)
(a)是最常用的,也是笔者下面要讲的。(b)只适用于控制台的调试器, 如KD、CDB和NTSD,不能被Windbg利用,这种方式支持批处理命令操作,非常方便。(c)有一定的局限性,就是只能调试live的程序,不能调试dump文件。(d)这种方式是构建一个轻量级的协议服务器,这种方式用的人也不多,我也不是很了解,只是让大家知道有这么种方式。接下来我主要讲(a)方式,其他方式读者若有兴趣可以自行google了。
既然是远程调试,那么一定有个Server(调试目标所在机器),一个Client(调试器所在机器)。通过调试器构建远程调试会话的前提条件是两台机器都装有Windbg,而且是同一版本的。两台机器的联系可以通过不同的协议连接,如tcp/pipe/串口或并口等,至于具体使用什么样的协议取决两台机器的网络环境和安全性的要求。
通过调试器构建远程调试会话一般需要两个步骤:
a. 激活调试服务器
b. 激活调试客户端
在这里我以tcp协议为例:
步骤a:首先在程序要运行的机器或dump文件所在的机器(即调试服务器)上通过Windbg构建调试会话。然后输入命令:“.server tcp:port=1024”,如果端口号被占了,可以使用49152-65535的端口号,输入命令会看到如下,点击允许访问。
0:004> .server tcp:port=1024 Server started. Client can connect with any of these command lines 0: <debugger> -remote tcp:Port=1024,Server=WIN-K584J5H3VLQ |
步骤b:在步骤a完成并没问题之后,在Client上启动Windbg,点击Menu -> File -> Connect to Remote Session,这时候有个Connect to Remote Debug Session弹出框:
在Connection string里面输入命令“tcp:server=192.168.244.128,port=1024”,点击OK。
Server started. Client can connect with any of these command lines 0: <debugger> -remote tcp:Port=1024,Server=WIN-K584J5H3VLQ ECJTU_LUOWEI-PC\ECJTU_LUOWEI (tcp 192.168.244.1:53682) connected at Sat Feb 21 10:11:09 2015 |
到此已经完整的建立了一个远程调试会话环境,构建远程调试会话环境的方式有很多,读者可以根据不同的需求灵活运用,在这里只起个抛砖引玉的作用。
3.3 认识界面
Windbg是个典型的Windows 窗口程序,一般Windbg有10个常用的窗口:
名称 |
快捷键 |
用途 |
Command |
Alt+1 |
输入命令、显示命令结果和调试信息输出 |
Watch |
Alt+2 |
监控窗口 |
Locals |
Alt+3 |
局部变量 |
Registers |
Alt+4 |
观察和修改寄存器的值 |
Memory |
Alt+5 |
观察和修改内存数据 |
Call Stack |
Alt+6 |
Trace 用的 |
Disassembly |
Alt+7 |
反汇编 |
Scratch Pad |
Alt+8 |
做笔记用的 |
Processes and Threads |
Alt+9 |
显示所有调试目标的列表,进程和线程 |
Command Browser |
Ctrl+N |
执行和浏览历史命令 |
这些窗口最常用的就是命令窗口,我们所有的调试工作几乎都是和这个窗口打交道。这个窗口分三个部分:
第一部分是Title部分,点击箭头所指的部分,会有个context menu弹出来,主要是显示的设置作用;第二部分是中间的白板部分,是显示命令和输出结果用的;最后一个部分主要是用来显示状态信息或者等待命令输入。
有时候第三部分是显示Busy的状态,如:
如果不是Busy的状态,左边的灰色部分显示的是X:Y的格式信息,其中X表示进程ID,Y表示线程ID,具体哪个进程是哪个ID,可以查看Processes and Threads窗口。同时需要注意的是这里的线程ID不等于C#中线程的ManagedThreadId,这个线程ID是Windbg自己维护的一个ID。根据笔者的经验,创建越早的线程,ID值越小,其中随着进程一起创建的线程的ID值为0,大部分情况下UI线程就是随着进程一起创建的,所以一般可以将ID为0的线程看做UI线程。
3.4 调试托管程序
要想调试托管程序,调试器需要加载SOS或者SOSEX扩展程序。如果是分析Dump文件,还需要加载Dump文件mscordacwks。
SOS和SOSEX都是用来调试NET程序的扩展DLL,其中SOS包含在framework的安装目录下:
%windir%\microsoft.net\<architecture>\<version>\sos.dll
SOSEX是Steve Johnson写的,这个dll的最新版本下载可以在该作者的网址上下载:
http://www.stevestechspot.com/
加载扩展dll的命令是:
0:004> .load c:\Windows\Microsoft.NET\Framework\v2.0.50727\SOS.dll |
加载了SOS之后还需要一个加载同文件夹下的CLR.dll,才能使用扩展命令。
在CLR 1.1和2.0的版本还不是使用CLR.dll,这个时候用的是mscorwks.dll。根据framework版本的不同,还有一个简单的加载方式:
Framework 1.0 & 2.0 & 3.0 & 3.5 |
Framework 4.0 & 4.5 |
.loadby sos mscorwks |
.loadby sos clr |
对于SOSEX的加载只能使用.load的方式,所以建议读者可以将SOSEX.dll下载到Windbg的安装目录下,这样便于加载SOSEX。
当调试从其他机器打出的dump文件的时候,SOS和mscordacwks版本的问题一直让人纠结,一般可以有三种解决方式:
a. 设置调试符号路径,并连接到Microsoft的公共Symbol server
b. 装一个与创建dump文件一样版本的framework
c. 从创建dump文件的机器上获取对应的SOS和mscordacwks
一般都是采用第三种方式,如果是客户创建的dump,在客户给我们dump的时候可以顺便要一下SOS和mscordacwks;第二种办法往往都不在正常人的考虑范围;至于第一种办法是在获取不到对应的SOS和mscordacwks的时候采取的办法,这个办法唯一的缺点就是从Microsoft Symbol Server上下载对应的Symbol太慢。
值得注意的是,通过启动程序建立的调试会话在第一次中断的时候还没有加载CLR或MSCORWKS,这个时候是无法通过loadby找到对应的CLR或MSCORWKS模块,通过命令g继续执行等加载了CLR或MSCORWKS模块就可以使用loadby:
CommandLine: D:\Code\Ext1\Ext1\bin\Debug\Ext1.exe Symbol search path is: *** Invalid *** **************************************************************************** * Symbol loading may be unreliable without a symbol search path. * * Use .symfix to have the debugger choose a symbol path. * * After setting your symbol path, use .reload to refresh symbol locations. * **************************************************************************** Executable search path is: ModLoad: 01180000 01188000 Ext1.exe ModLoad: 77180000 77300000 ntdll.dll ModLoad: 70000000 7004a000 C:\Windows\SysWOW64\MSCOREE.DLL ModLoad: 76640000 76750000 C:\Windows\syswow64\KERNEL32.dll ModLoad: 765f0000 76636000 C:\Windows\syswow64\KERNELBASE.dll (c7c.b58): Break instruction exception - code 80000003 (first chance) *** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll - eax=00000000 ebx=00000000 ecx=78910000 edx=0015dd28 esi=fffffffe edi=00000000 eip=77220f3b esp=002af7e8 ebp=002af814 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!LdrVerifyImageMatchesChecksum+0x96c: 77220f3b cc int 3 0:000> .loadby sos clr Unable to find module 'clr' 0:000> g ModLoad: 762c0000 76360000 C:\Windows\syswow64\ADVAPI32.dll ModLoad: 74d40000 74dec000 C:\Windows\syswow64\msvcrt.dll ModLoad: 763f0000 76409000 C:\Windows\SysWOW64\sechost.dll ModLoad: 753f0000 754e0000 C:\Windows\syswow64\RPCRT4.dll ModLoad: 74ce0000 74d40000 C:\Windows\syswow64\SspiCli.dll ModLoad: 74cd0000 74cdc000 C:\Windows\syswow64\CRYPTBASE.dll ModLoad: 6ff80000 6fffa000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll ModLoad: 74ee0000 74f37000 C:\Windows\syswow64\SHLWAPI.dll ModLoad: 76360000 763f0000 C:\Windows\syswow64\GDI32.dll ModLoad: 751a0000 752a0000 C:\Windows\syswow64\USER32.dll ModLoad: 74fc0000 74fca000 C:\Windows\syswow64\LPK.dll ModLoad: 750f0000 7518d000 C:\Windows\syswow64\USP10.dll ModLoad: 74e80000 74ee0000 C:\Windows\SysWOW64\IMM32.DLL ModLoad: 754e0000 755ac000 C:\Windows\syswow64\MSCTF.dll ModLoad: 6eec0000 6f552000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll ModLoad: 6ede0000 6eeb3000 C:\Windows\SysWOW64\MSVCR110_CLR0400.dll (c7c.b58): Unknown exception - code 04242420 (first chance) ModLoad: 6de20000 6eddf000 C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\51e2934144ba15628ba5a31be2dae7dc\mscorlib.ni.dll ModLoad: 76be0000 76d3c000 C:\Windows\syswow64\ole32.dll ModLoad: 6d430000 6d49e000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll (c7c.508): Break instruction exception - code 80000003 (first chance) *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\syswow64\KERNEL32.dll - eax=7ef9c000 ebx=00000000 ecx=00000000 edx=7721f7ea esi=00000000 edi=00000000 eip=7719000c esp=04a5ff0c ebp=04a5ff38 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!DbgBreakPoint: 7719000c cc int 3 0:004> .loadby sos clr |
加载SOS或者SOSEX之后,有个常用的命令是!help,!help命令后面可以带参数,参数是命令名,然后命令窗口会输出该命令的详细用法,这个命令的地位好比linux中的man。由于SOS和SOSEX的帮助命令都是!help,所以如果同时加载了两个扩展模块,输入命令的时候可以将模块名作为标识,如:!sos.help。以下是help命令的简略信息:
0:004> !sos.help ------------------------------------------------------------------------------- SOS 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 <functionname>" for detailed info on that function.
Object Inspection Examining code and stacks ----------------------------- ----------------------------- DumpObj (do) Threads DumpArray (da) ThreadState ... 0:004> !sosex.help SOSEX - Copyright 2007-2014 by Steve Johnson - http://www.stevestechspot.com/ To report bugs or offer feedback about SOSEX, please email sjjohnson@pobox.com Quick Ref: -------------------------------------------------- bhi [filename] BuildHeapIndex - Builds an index file for heap objects. ... Use !help <command> or !sosexhelp <command> for more details about each command. You can also use the /? (or -?) option on any command to get help for that command. 0:004> !sos.help threads ------------------------------------------------------------------------------- !Threads [-live] [-special] !Threads lists all the mananaged threads in the process. -live: optional. Only print threads associated with a live thread. -special: optional. With this switch, the command will display all the special threads created by CLR. Those threads might not be managed threads so they might not be shown in the first part of the command's output. Example of special threads include: GC threads (in concurrent GC and server GC), Debugger helper threads, Finalizer threads, AppDomain Unload threads, and Threadpool timer threads. ... |