如何用WinDbg帮你诊断应用程序或系统故障

如何用WinDbg帮你诊断应用程序或系统故障

 

 

1 关于WinDbg.. 2

1.1 简介... 2

1.2 安装... 2

2使用基础.... 3

2.1 配置Symbols路径... 3

2.2 基础命令... 3

2.3 Attach方式——在线调试... 6

2.4 Dump 方式——离线分析... 7

2.5 双机联调... 9

3 典型案例.... 9

3.1 访问冲突... 9

3.2 系统蓝屏... 13

3.3 内存泄露... 18

3.4 线程错误... 18

4 更多问题.... 18

5 参考文献.... 18

 

 

 

 

关键字: Debug  WinDbg Dump  Crash  ShutDown

 

 

摘要:本文首先介绍了WinDbg的使用基础,包括安装配置、基本命令和网络支持;然后介绍其主要工作过程:Attach、Dump两种工作方式和Kernel-mode、User-mode两种调试模式;最后给出几个典型案例,演示如何用WinDbg解决问题。

 

 

 

 

 

1 关于WinDbg

1.1 简介

WinDbgis a multipurposed debugger for MicrosoftWindows, distributed on the web by Microsoft. It can be used todebuguser modeapplications, drivers, and theoperatingsystem itself inkernel mode. It is aGUI application, but haslittle in common with the more well-known, but less powerful,Visual Studio Debugger.

——from http://en.wikipedia.org

 

WinDbg与KD,CDB,andNTSD属于DebuggingTools for Windows工具集合,许多命令写法一致,但它们的适用场合不同,详见MSDN。

 

WinDbg可以进行内核态和用户态调试,可以在线调试(Attach方式)、离线分析(Dump方式)。即使是用户态也很多调试功能是VS没有的,因而WiKi才有“It is aGUIapplication, but has little in common with the more well-known, but lesspowerful,Visual Studio Debugger.”。

 

个人认为WinDbg主要优势在于解决下列问题:

(1)       Kernel模式:系统问题,无论大小,都要找WinDbg;

(2)       User模式:偶发性故障、隐藏较深的问题、多线程、缺乏源代码等;

 

 

更多介绍:

Wiki

http://en.wikipedia.org/wiki/WinDbg

 Debugging Tools and Symbols: GettingStarted(建议直接从本地Help文档进入)
http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx
A word for WinDbg(写得深入浅出,作者谦虚了)
http://mtaulty.com/communityserver/blogs/mike_taultys_blog/archive/2004/08/03/4656.aspx

1.2 安装

选择正确的版本,新版的WinDbg都归DebuggingTools for Windows旗下: 

Debugging Tools for Windows 32-bit Version
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
Debugging Tools for Windows 64-bit Versions
http://www.microsoft.com/whdc/devtools/debugging/install64bit.mspx

 

安装很简单,一路Next即可。

 

2使用基础

2.1 配置Symbols路径

 

我们来看看为什么要Symbols?怎样加载Symbols?

Symbols是你调试程序、分析Dump的重要基础,否则你无法查看Stack Trace、变量内容、设置断点,所以要为你分析的模块加载正确的Symbol文件。对于个人模块,若采用VC++、VS编译,就是你项目的.pdb文件;而系统的一些模块,需要从微软的符号服务器上下载。

Symbols分为几种类型:

Symbolsare typically either private symbols (include variable information),retailsymbols (function information but not variable information) orexportsymbols(generally not so useful for debugging purposes). If you’re debugging with“export” symbols it’s not usually enough to actually work out what’s going on.

——from A word for WinDbg

 

配置方法有多种,这里介绍最简便的:

在File->Symbol FilePath里设置

MyCodeSymbolDir; SRV* MyLocalSymbolDir*http://msdl.microsoft.com/download/symbols

当然不是直接复制,要根据你自己的情况稍作修改,MyCodeSymbolDir配置成你自有模块的.pdb文件路径,也可以加多个目录,用分号分开即可;MyLocalSymbolDir是你下载系统符号文件目录,建议通过官网一次性下载到你指定的目录;至于分号后面长的怪怪的一串也自有用处SRV* MyLocalSymbolDir*http://msdl.microsoft.com/download/symbols,告诉WinDbg到哪里下载缺失的Symbol文件。如果你是调试自己的应用程序的话,建议你将自己应用程序的*.pdb文件的路径放在前面这样对Windbg来说查找起来比较快。

 

你可能会遇到点小困难,常常无法加载符号表。运气好,按下面的方法操作即可,运气差些,你可能需要重启机器才能解决问题,运气更差的话请求助网络支持(参考http://www.189works.com/article-14826-1.html)。

 

另外,若有源文件,还可以配置一下源文件路径:

File->Symbol File Path,在弹出的窗口里指定你的源代码文件的路径,路径格式只要符合Windows操作系统的格式即可,可以指定多个,中间以分号间隔。

 

2.2 基础命令

(1)断点指令:BP,BM,BA,BL,BC,BD,BE

BP 在指定的地址设置断点。bpnotepad!WinMain,在Notepad的WinMain函数处下断点。断点的位置可以用符号表示,也以直接使用地址及Windbg的Pseudo-Register(虚拟寄存器),如$exentry表示进程的入口点,可以使用bp@$exentry在进程的入口点设置断点,对于Notepad当前入口点为01006420,也可以直接 bp 01006420,等效于bp notepad!WinMainCRTStartup.

BM 使用模式匹配设置断点。需要符号表支持。bm值一提,在符号表合法的情况下(符号表中包含私有符号的时候),bm可能通过模式一次下多个断点,bmmydriver!FastIo*指定可以将所有与FastIo*模式相匹配的函数下断点,如FastIoRead,FastIoWrite等。但是bm需要full or export symbols支持,Microsoft的提供的符号表不是都支持的,通常我们自己编译的程序的符号表(Windbg显示为private pdb symbols)默认是支持的。

BA (Break on Access)。顾名思义,对内存访问下断点。对于在多核或多处理器调试的时候很有用,对于调试多线程也很有用,应该说用处很多,比如对一个全局变量设置断点,ba mydriver!gMonitoredDevices,如果你认为这个变量的值被莫名的修改了,相信通过BA设置的断点,你很快就能找到是谁修改的。

BL(List),BC(Clear),BE(Enable),BD(Disable)

这四个指令是分别用于列表,清除,开启和禁用断点,也是使用非常频繁的指令。

条件断点(BP + J)

以上所提到的断点指令通过与J指令很容易形成条件断点。比如:

bpUSER32!GetMessageW "r $t1=poi(esp+4);r $t2=poi(@$t1+4); j(@$t2 = 0x102 )'du @$t1+8 L2;gc';'gc'"

这个条件断点,截取WM_CHAR消息,并将字符(包括中文)显示出来。条件断点的最简形式:

bp Address"j (Condition) 'OptionalCommands'; 'gc' "

Address是指令的地址,Condition是一个条件表达式,如果@eax=1,'OptionalCommands'是在断点被击中并且表达式成立时要执行的指令;gc指定是从一个条件断点返回,是不可少的一部分。

(2)数据查看指令 d{a|b|c|d|D|f|p|q|u|w|W}

这些指令区分大小

d{b|c|d|D|f|p|q}分别是显示以下数据:

db:                                         byte&ASCII,

dc:                                         double-word&ASCII,

dd:                                         double-word,

dD:                                        double-precision,

df:                                          float,

dp:                                         pointer-sized,

dq:                                         quad-word;

 

DA:                                    用于显示ASCII,

DU:                                    用于显示UNICODE;

BYB:                                  显示binary和Byte

BYD:                                  显示binary和DWORD

另外,DV,用于查看本地变量用。

(3)栈指令k[b|p|P|v|d]

这些指令区分大小(仅第二个字母区分)

这四条指令显示的内容类似,但是每个指令都有特色。kb显示三个参数,kp显示所有的参数,但需要Full Symbols或Private PDB Symbols支持。kP与kp相似,只是kP将参数换行显示了。kv用于显示FPO和调用约定。kd用于显示Stack的Dump,在跟踪栈时比较有用。

 

(4)数据修改指令e{b|d|D|f|p|q|w} 

详见WinDbg帮助文档。

(5)反汇编指令u,uf

u Address L10 从Address处反汇编十条指令

0:001> u@$exentry L10

uf Function 汇编整个Function函数

0:000> ufGetLanguageCount

(6)跟踪指令T,TA,TB,TC,WT,P,PA,PC

T指令单步执行,在源码调试状态下,可指源码的一行,根据不同的选项也可以为一行ASM指令;

TA单步跟踪到指定地址,如果没有参数将运行到断点处。

TB执行到分支指令,分支指令包括calls, returns, jumps, counted loops, and while loops

TC执行到Call指令

WT Trace and Watch Data,一条强大指令,对执行流程做Profile,执行一下看看结果吧

P,PA,PC相信不用多做解释,大家也都明白了

(7)源代码操作指令 .open,lsf,lsc,ls,l,lsp

.open指令打一个源文件。可以打开一个全路径的文件,也可以通过函数地址来打开并定位到源文件中函数的位置,如.open –a myapp!main,.open j:\mydriver\mydriver.c

lsf指定一个源文件为当前源文件。Lsf可以使用全路径,如果源路径已经设置,也可以直接指定源文件名称。如lsf mydriver.c,lsfj:\mydriver\mydriver.c

lsc显示当前源文件。

ls显示当前源文件的代码。如ls 200显示第200行

l 用于设置源文件选项。

lsp 设置源文件行在调试时显示范围比如,显示当前行的前50,后50,lsp 100

但通常使用Windbg时,可以直接用Ctrl+O来打开并查看源文件。

(8)寄存器指令 r

相信大家对这个指令都很熟悉,在Windbg中r指令除了可以显示修改CPU寄存器之外,Pseudo-Register可使用这个命令来修改。对eax 操作,r eax 显示其值,r eax=2,修改其值;r $t2=10,修改Pseudo-Register的值,r @$t2显示其值。

(9)Search 指令 s,#

s指令对内存区别进行查找,可用于查找数字,字符串,但不支持模式查找。s -d @esp L100 8187bc40,从esp指向的内存地址0x100个字节内查找DWORD  8187bc40。查找字符串s -a 0012ff40 L20 "Hello" 。s -sa 和 s -su 显示内存可打印的ASCII和Unicode字符串。

#指令可以查询汇编指令模式, #"call[ ]+esp" kernel32 L1000查找call esp 指令。

(10)其他常用指令lm,!peb,x,dt

lm 查看当前载入的模块

!peb 查看当前进程环境块(PEB)

x 查看模块的符号,如xmydriver!*FastIo*,显示所有与*FastIo*匹配的符号列表

dt 查看类型数据,还可用于查看模块类型的符号列表,如

dtdgguarder!_IMAGE_DOS_HEADER 00400000

从00400000处查看_IMAGE_DOS_HEADER类型的数据

!address AD 查看AD地址的内存页属性,非常有用,如诊断Access Violation问题时很有用。

!analyze –v 查看Dump文件的详细分析报告

(11)上下文的概念

Windbg下上下文的概念很重要,根据文档中说明有多种上下文概念,

Session Context,

Process Context

Register Context(其实也就是线程上下文)

Local Context(这个关系到本地如何解析本地变量的问题)

调试Win32应用程序,Session Context和Process Context是确定的,主要是RegisterContext,也即Thread Context。

~指令来查看,改变当前的ThreadContext 。

~*显示所有线程列表,

~xs用于切换上下文(x是数字),如:~1s,将上下文切换到1号线程。

.frame用来设置Local Context。

 

2.3 Attach方式——在线调试

WinDbg提供了Attach to Process的工作方式,允许对一个正在运行的进程进行在线调试。这种方式适用于你需要调试程序,但又不想或无法(比如很重要的线上服务器)长时间中断它。

Step 1:File->Attach to Process


Step 2:选择进程,如记事本程序Notepad.

Step 3:选择OK进入后,WinDbg首先触发一个中断,暂停进程的运行,以便设置断点、查看进程内部状态等。

***wait with pending attach提示后,依次显示符号路径、模块列表、中断提示、寄存器状态、及中断位置。

Symbolsearch path is:                                                    当前设置的符号路径

Executablesearch path is:                                            模块列表

Breakinstruction exception - code 80000003 (first chance)这是WinDbg主动中断进程的提示

ntdll!DbgBreakPoint:                                                  中断位置

进程处于Wait状态,现在可以对进程进行各种查看和设置,用WinDbg强大的命令系统进行调试。输入g使进程继续执行,Ctrl+Break可以暂停进程。

2.4 Dump 方式——离线分析

 

Dump即尸体

Dump其实或可翻译为转储,实质为进程或系统运行中的内部印象(不仅仅是内存、还有CPU寄存器)。只要系统没挂,我们总能得到进程的Dump,系统挂了,我们可以得到系统的Dump,然后我们等着解剖这些尸体(Dump在此情况下或许更适合翻译为尸体)。这么看来WinDbg简直是一个完美的尸体解剖器,通过解剖挂掉的进程或系统,告诉你它们是怎么死得,以及以后避免重蹈覆辙。但其实并不完美,还有一种情况,WinDbg恐怕无能为力:系统遇到比Vital Error更严重的问题,来不及自我牺牲(蓝屏),于是无法触发Dump,这时候为系统收尸都难(更别想知道它是怎么死的)。这时候,解决方法就是避免。

2.4.1User-mode转储分析

(1)如何配置崩溃调试器及自动生成Dump?

这里采用编辑注册表的方法(也可使用一些修改注册表的工具)。

配置调试器:

\\HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\AeDebug

Auto:  设定有进程崩溃时是否自动进入调试环境,1=自动进入调试,0手动选择是否调试。

Debugger:配置调试工具,及一些特定配置。

自动生成Dump:

Debugger="YourDbgDir\windbg.exe" -p %ld –c ".dump /ma /u YourDmpDir\YourDmpName.dmp" -e  %ld –g

YourDbgDir是你的调试的运行目录,YourDmpDir可以指定dmp文件的指定目录,YourDmpName为指定文件名(实际名字系统会帮你加上时间戳和时间戳两端共8位的编码)。

(2)User-mode转储分析

参见下文案例部分。

2.4.2Kernel-mode转储分析

(1)核心调试配置生成Dump

系统崩溃即ShutDown。当系统发生错误是,最常见的就是蓝屏(Blue screen),这时就会在系统目录下产生一个Dump文件,如MEMORY.DMP 。这个文件的主要意义在于分析系统错误发生的原因,以作出解决的方法。 

三种Dump类型:
   a.完全内存转储。这个文件比较大,和物理内存相当,包含了程序崩溃前系统及用户模式下的所有信息。
   b.核心内存转储。这个文件大小约物理内存的三分之一,主要包含崩溃前系统内核的运行情况。一般为了分析内核错误,就选用这种文件。
   c.小内存转储。这个文件小,只有64k,刚好一个页面文件大小。它包含了相对比较少的信息,主要可用于微软的在线分析。

 

 设置方法:以上三种形式的文件可以在我的电脑——〉鼠标右键——〉属性——〉高级——〉故障及恢复中设置。如下图:

 

 

(2)Kernel-mode转储分析

系统的尸体解剖起来比较难,这不仅是因为系统的Dump更复杂,Windows更复杂,Drivers五花八门,关键困难还是与Symbols有关:系统和一些驱动的Symbol文件可能并不全面。当然,没有Source也会加重调试的难度,但这并不关键。

 

参见下文案例部分及http://blogs.itecn.net/blogs/shehuayu/archive/2009/07/15/101749.aspx

 

2.5 双机联调

系统崩溃时,有可能由于某种原因无法生成内存转储文件(http://www.cnblogs.com/mvperic/archive/2010/06/25/1764833.html)。同时系统挂起而不崩溃的情况也是有的。在这样各种得不到转储文件的情形下,如何才能才能诊断和调试系统错误呢?这时需要双机调试来解决问题。

 

3 典型案例

3.1访问冲突

这里举一个访问冲突的例子。访问冲突会导致进程Crash,而且问题很容易重现,所以可以通过Open Executable方式进行调试。

测试解决方案名为CrashTest.sln,这是一个进程崩溃的测试框架。通过动态绑定CrashBase指针到CrashMemoryFault子类对象,然后运行其run方法,而run方中包含一个内错错误,引起进程崩溃。

1) 程序主要代码如下:

//CrashTest.cpp


//CrashBase.h



2)启动WinDbg,配置Symbol File Path为:(分号前为CrashTest.pdb的路径)

D:\TestProgramme\CrashTest\Debug;SRV*C:\WINDOWS\symbols*http://msdl.microsoft.com/download/symbols

配置Source File Path为:(程序源代码路径)

D:\TestProgramme\CrashTest\CrashTest

3)通过WinDbg打开CrashTestD.exe,弹出Command、Source File窗口,如下:


可以看到程序执行在ntdll!DbgBreakPoint处停住了, Command窗口给出一些提示信息,分别显示:命令行、Symbol路径、加载模块及路径。然后可以看到:

(284.13fc):Break instruction exception - code 80000003 (first chance)

这是一个断点信息,并不是程序的错误,而是WinDbg主动在程序进入main之前进行中断,以方便用户设置断点、查看一些初始信息等操作。

4)用x命令查看符号信息,bp设置断点:

00411670是getString的地址,所以也可以这样使用:

bpCrashTestD!CrashMemoryFault::getString

输入g,继续运行,遇到断点中止:

这时可检查各种状态信息,如变量、内存、寄存器还有调用栈。

还可以用kb指令,能看出传参值;或者用~.num指定num号线程观察其调用栈。因为本程序只有一个线程,这里不演示多线程的内容。

5)下面我们使用g命令继续程序,看看会否发生崩溃以及崩溃产生的原因是什么。输入g:

进程立刻中断,

Command显示:

表明132c.b60号线程遇到一个Access Violation错误,错误码为c0000005,第一次尝试遇到这个错误引发exception,从而中止进程执行;给出寄存器信息;给出中断的位置,用符号给出,及汇编代码。在进程崩溃的情况下,因为被WinDbg截获在中断状态,我们可以查看所有线程、堆栈、全局变量、内存、寄存器、句柄等信息,以诊断问题原因。报告是Access Violation错误,我们直接来看发生错误的汇编行:

在CrashTestD!CrashMemoryFault::run+0x4c处执行的mov操作,目的地址采用寄存器直接寻址。

 

6)通过!address  eax来看看【eax】处得内存页属性:

该内存也为CrashTestD.exe进程空间内存,有访问权限;但Protect方式为PAGE_READONLY,只读!对只读的内存进行写操作,显然是访问冲突。这已经找到了根本原因,但我们在来看看源代码级别的错误在哪里,即哪行代码出了问题。

 

 

 

中断时,Source窗口已自动定位到一行蓝色源代码:

很容易发现,p[i]指向常量字符串”I a const string!”,我们知道C++中后者是存在只读区的,因而这里的赋值操作是错误的。同时可根据dc eax证实这点:

eax指向的内存确实存着”I a const string!”,我们尝试对00416784处得字节I进行修改。

 

这是一个采用在线调试(程序在运行,跟Attach方式类似)、User-mode的简单例子,出现的问题是AccessViolation。虽然简单,但可以发现像!address等指令可以提供很多很深入的调试信息,这比VS强大得多。对于实际的复杂程序,产生内存错误(或AccessViolation)可以采用类似的方式调试,可能难点在于如何定位源码行及错误的源头,WinDbg提供了丰富指令集、加上对程序逻辑的理解,可以帮我们最终找出问题原因。所以,一般的内存错误,采用WinDbg调试应该是比较容易发现原因的。

3.2系统蓝屏

当windows无法应对复杂的危机时,会产生蓝屏来自我牺牲,以保护应用程序的资源(数据、设备等)免受更大的破坏。这也就是“系统崩溃”,或BSOD。

解决蓝屏问题需要在Kernel-mode下进行,这里采用驱动错误主动制造蓝屏,然后通过配置注册表使系统在崩溃时给出Dump文件。

下面演示蓝屏的产生、配置生成Dump以及事后如何分析系统Dump以找出系统崩溃的真凶。

 

配置系统生成Dump

我的电脑——〉鼠标右键——〉属性——〉高级——〉故障及恢复中设置。如下图:

完成设置,点击确定即可。

 

产生蓝屏

(以下斜体为转载)

由于驱动深入到了内核,它的要求非常苛刻,一个简单的除零操作就可引发蓝屏。但是驱动的编写与普通win32 api是有很大不同的,为了减轻负担,我直接运用一个现成的程序,是《Microsoft Windows Internals》作者写的Notmyfault(见附件)。它由Notmyfault.exe和Myfault.sys两部分组成。正如名字一样,引发蓝屏的不是 Notmyfault.exe而是由他加载到内核中的Myfault.sys。如图:

 

 

点击Do Bug,出现蓝屏(注意系统属性中不要勾选“自动重启系统”,否则蓝屏一闪而过):

从蓝屏提示信息可以初步了解问题原因。

“1”给出Stop Code:查询微软网站可以了解引发0x000000D1的多种原因和案例;“2”给出错误类型DREVER_IRQL_NOT_LESS_OR_EQUAL;“3”给出较具体的错误源为myfault.sys。

选择重启系统,用WinDbg来加载产生的dump,以分析产生蓝屏的原因:

BugCheck 就是我们熟悉的“系统崩溃(system crash)”或者“内核错误(kernel error)”抑或“停止错误(Stop error)”。这里提示指出BugCheck是D1,并给了四个参数,这里的D1可以在windbg文档的 Bug Check Code Reference中查出其具体含义。

用!analyze –show D1命令查出D1具体含义

提示指出,错误在于:在一个过高级别的中断请求(IRQL)处分页内存(或者完全非法的内存)被非法访问。

用!analyze –v 命令查看详细的dump报告:

(//后部分为笔者加的注释)

0: kd> !analyze -v

*******************************************************************************

*                                                                            *

*                       Bugcheck Analysis                                    *

*                                                                             *

*******************************************************************************

 

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)                              //指出停止错误为D1

An attempt was made to access a pageable (or completely invalid)address at an

interrupt request level (IRQL) that is too high.  This is usually

caused by drivers using improper addresses.

If kernel debugger is available get stack backtrace.                      //以上4行解释了错误原因

Arguments:

Arg1: e754e008, memory referenced

Arg2: 0000001c, IRQL

Arg3: 00000000, value 0 = read operation, 1 = write operation

Arg4: ba6e8403, address which referenced memory                    //给出停止错误四个参数

 

Debugging Details:                   //顾名思义

------------------

 

Page 31a66 not present in the dump file. Type ".hhdbgerr004" for details //.hh可以产看更多

READ_ADDRESS:  e754e008Paged pool                  //Arg1:发生错误的分页池

CURRENT_IRQL:  1c                                  //Arg2:当前中断级别

FAULTING_IP:                                        //停止错误的模块及汇编指令

myfault+403

ba6e8403 8b06           mov     eax,dword ptr [esi]

 

DEFAULT_BUCKET_ID: DRIVER_FAULT                      //错误类型为驱动错误

BUGCHECK_STR:  0xD1                                 //意思是错误的关键词为D1

PROCESS_NAME: NotMyfault.exe                       //所述进程

TRAP_FRAME:  a616eb8c --(.trap 0xffffffffa616eb8c)        

ErrCode = 00000000                                  //Arg3

//以下为发生错误时寄存器内容

eax=e5f699d0 ebx=88b15188 ecx=000000d1 edx=0000001c esi=e754e008edi=00000000

eip=ba6e8403 esp=a616ec00 ebp=a616ec64 iopl=0        nv up ei ng nz na po nc

cs=0008  ss=0010  ds=0023 es=0023  fs=0030  gs=0000             efl=00010282

myfault+0x403:                                  //在此给出错误汇编指令及Arg4

ba6e8403 8b06           mov     eax,dword ptr [esi]  ds:0023:e754e008=????????

Resetting default scope

 

LAST_CONTROL_TRANSFER: from ba6e8403 to 80545758

 

STACK_TEXT:                          //调用栈,显示myfault+0x403处发横错误

a616eb8c ba6e8403 badb0d00 0000001c a616ebe4 nt!KiTrap0E+0x238

WARNING: Stack unwind information not available. Followingframes may be wrong.

a616ec64 8058181d 892333b0 88b15170 88b0ba78 myfault+0x403

a616ed00 8057a298 00000118 00000000 00000000nt!IopXxxControlFile+0x5c5

a616ed34 8054267c 00000118 00000000 00000000nt!NtDeviceIoControlFile+0x2a

a616ed34 7c92e514 00000118 00000000 00000000 nt!KiFastCallEntry+0xfc

0012fa54 00000000 00000000 00000000 00000000 0x7c92e514

 

STACK_COMMAND:  kb                                       //这个不用解释了

FOLLOWUP_IP:                                                              

myfault+403

ba6e8403 8b06           mov     eax,dword ptr [esi]

 

SYMBOL_STACK_INDEX:  1                                         //不知何意,应该用处不大

FOLLOWUP_NAME:  MachineOwner          //指出谁崩溃了

MODULE_NAME: myfault                  

IMAGE_NAME:  myfault.sys

DEBUG_FLR_IMAGE_TIMESTAMP: 43774e1d

SYMBOL_NAME:  myfault+403

FAILURE_BUCKET_ID: 0xD1_myfault+403

BUCKET_ID: 0xD1_myfault+403

Followup: MachineOwner

---------

//以上大多为前面的重复,或者顾名思义。

小结:通过以上的分析,可知蓝屏原因是Bugcheck D1引起的,驱动程序读操作了过高的IRQL。引发蓝屏的驱动程序是 myfault.sys,属于notmyfaulf.exe的进程。还知道了蓝屏前bug程序myfault.sys的调用栈等有用信息,下一步即可对myfault.sys进行Bug修改,或者联系驱动开发商寻求支持。

3.3内存泄露

对应用程序来说,偶发性的内存泄露往往不会严重错误,但持续性的内存泄露则会使得系统可用内存越来越少,系统性能下降、甚至程序崩溃。内存泄露可以分为进程空间内的内存泄露和核心内存泄露,前者主要来源于动态申请(malloc/new)引起的堆内存泄露,而后者往往是由于内核对象引起的。

 

本案例探讨如何使用WinDbg诊断各种内存泄露问题。

(1)       堆内存泄露

以下为含有堆内存泄露的代码片段,可执行程序为MemoryLeak.exe

这是一个简单的程序,下面演示怎样用WinDbg发现及定位这样内存泄露。

在cmd中输入gflags.exe /i +ust,使能跟踪堆内存分配的标志;

WinDbg中启动MemoryLeak.exe

0:000> !heap

NtGlobalFlag enables following debugging aids for new heaps:   validate parameters

    stack back traces

Index   Address  Name     Debugging options enabled

  1:   00150000                 tail checking free checkingvalidate parameters

  2:   00250000                 tail checking free checkingvalidate parameters

  3:   00260000                 tail checking free checkingvalidate parameters

  4:   00390000                 tail checking free checkingvalidate parameters

进程共有四个堆,!heap–stat获取所有堆块及其句柄:

0:000> !heap -stat

_HEAP 00390000

     Segments            00000001

         Reserved  bytes 00010000

         Committed bytes 0000a000

     VirtAllocBlocks     00000000

         VirtAlloc bytes 00000000

_HEAP 00250000

     Segments            00000001

         Reserved  bytes 00010000

         Committed bytes 00006000

     VirtAllocBlocks     00000000

         VirtAlloc bytes 00000000

_HEAP 00150000

     Segments            00000001

         Reserved  bytes 00100000

         Committed bytes 00004000

     VirtAllocBlocks     00000000

         VirtAlloc bytes 00000000

_HEAP 00260000

     Segments            00000001

         Reserved  bytes 00010000

         Committed bytes 00003000

     VirtAllocBlocks     00000000

         VirtAlloc bytes 00000000

0:000> !heap-stat -h 0为每一个AllocSize 列出指定分配统计句柄:

0:000> !heap -stat -h 0

 

Allocations statistics for

 heap @ 00150000

group-by: TOTSIZE max-display: 20

    size     #blocks    total     ( %) (percent of totalbusy bytes)

    314 2 - 628  (45.33)

    2d8 1 - 2d8  (20.94)

    214 1 - 214  (15.30)

    c4 1 - c4  (5.64)

    40 2 - 80 (3.68)

    3d 2 - 7a  (3.51)

    44 1 - 44  (1.96)

    24 1 - 24  (1.04)

    22 1 - 22  (0.98)

    10 2 - 20  (0.92)

    18 1 - 18  (0.69)

    1 1 - 1  (0.03)

 

 

Allocations statistics for

 heap @ 00250000

group-by: TOTSIZE max-display: 20

    size     #blocks    total     ( %) (percent of totalbusy bytes)

    cc 2 - 198  (44.64)

    50 5 - 190  (43.76)

    42 1 - 42  (7.22)

    28 1 - 28  (4.38)

 

 

Allocations statistics for

 heap @ 00260000

group-by: TOTSIZE max-display: 20

    size     #blocks    total     ( %) (percent of totalbusy bytes)

 

 

Allocations statistics for

 heap @ 00390000

group-by: TOTSIZE max-display: 20

    size     #blocks    total     ( %) (percent of totalbusy bytes)

    9896a4 14 - bebc4d0  (99.99)

    1800 1 - 1800  (0.00)

    824 2 - 1048  (0.00)

    6d0 1 - 6d0  (0.00)

    224 3 - 66c  (0.00)

    3c 11 - 3fc  (0.00)

    28 19 - 3e8  (0.00)

    37a 1 - 37a  (0.00)

    29a 1 - 29a  (0.00)

    c4 3 - 24c  (0.00)

    244 1 - 244  (0.00)

    238 1 - 238  (0.00)

    15f 1 - 15f  (0.00)

    a6 2 - 14c  (0.00)

    a2 2 - 144  (0.00)

    136 1 - 136  (0.00)

    134 1 - 134  (0.00)

    124 1 - 124  (0.00)

    fc 1 - fc  (0.00)

54 3 - fc  (0.00)

这时可以发现大小为9896a4的块可能存在泄露。执行!heap -flt s  9896a4,列出特定大小所有程序块:

0:000> !heap -flt s  9896a4

    _HEAP @ 150000

    _HEAP @ 250000

    _HEAP @ 260000

    _HEAP @ 390000

      HEAP_ENTRY Size PrevFlags    UserPtr UserSize - state

invalid allocation size, possible heap corruption

        01420018 1312d4 0000  [0b]  01420020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        01db0018 1312d4 12d4  [0b]  01db0020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        02740018 1312d4 12d4  [0b]  02740020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        030d0018 1312d4 12d4  [0b]  030d0020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        03a60018 1312d4 12d4  [0b]  03a60020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        043f0018 1312d4 12d4  [0b]  043f0020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        04d80018 1312d4 12d4  [0b]  04d80020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        05710018 1312d4 12d4  [0b]  05710020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        060a0018 1312d4 12d4  [0b]  060a0020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        06a30018 1312d4 12d4  [0b]  06a30020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        073c0018 1312d4 12d4  [0b]  073c0020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        07d50018 1312d4 12d4  [0b]  07d50020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        086e0018 1312d4 12d4  [0b]  086e0020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        09070018 1312d4 12d4  [0b]  09070020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        09a00018 1312d4 12d4  [0b]  09a00020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        0a390018 1312d4 12d4  [0b]  0a390020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        0ad20018 1312d4 12d4  [0b]  0ad20020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        0b6b0018 1312d4 12d4  [0b]  0b6b0020    9896a4 - (busyVirtualAlloc)

invalid allocation size, possible heap corruption

        0c040018 1312d4 12d4  [0b]  0c040020    9896a4 - (busy VirtualAlloc)

invalid allocation size, possible heap corruption

        0c9d0018 1312d4 12d4  [0b]  0c9d0020    9896a4 - (busyVirtualAlloc)

很明显,这里有有用信息invalid allocation size, possible heap corruption表明可能存在堆破坏或者泄露。通过!heap –p –a02740020 追踪调用栈:

0:000> !heap -p -a 02740020

    address 02740020 found in

    _HEAP @ 390000

      HEAP_ENTRY Size PrevFlags    UserPtr UserSize - state

invalid allocation size, possible heap corruption

        02740018 1312d4 0000  [0b]  02740020    9896a4 - (busyVirtualAlloc)

        Trace: 00b4

        7c98fbcantdll!RtlDebugAllocateHeap+0x000000e1

        7c96b244ntdll!RtlAllocateHeapSlowly+0x00000044

        7c939c0cntdll!RtlAllocateHeap+0x00000e64

        102c103eMSVCR90D!_heap_alloc_base+0x0000005e

        102cfd76 MSVCR90D!_heap_alloc_dbg_impl+0x000001f6

        102cfb2fMSVCR90D!_nh_malloc_dbg_impl+0x0000001f

        102cfadcMSVCR90D!_nh_malloc_dbg+0x0000002c

        102db25bMSVCR90D!malloc+0x0000001b

        102bd691 MSVCR90D!operatornew+0x00000011

        41264e MemoryLeak!operatornew[]+0x0000000e

        41199eMemoryLeak!LeakHeap::run+0x0000004e

        4117edMemoryLeak!wmain+0x0000025d

        413678MemoryLeak!__tmainCRTStartup+0x000001a8

        4134bfMemoryLeak!wmainCRTStartup+0x0000000f

        7c817077 kernel32!BaseProcessStart+0x00000023

已定位到函数调用,进一步还可以定位到代码行,bp  41199e然后g:

成功定位到产生泄露的代码行。接下来可以根据这个信息修改程序Bug。

 

(2) 核心内存泄露

以下为含有堆内存泄露的代码片段,可执行程序为MemoryLeak.exe.

 

内核内存泄露,很多时候来自于应用程序没有及时释放内核对象,造成其占用内核空间的泄露,内核内存分为Paged 和Non-Page,内核对象大多使用Paged memory.

现在来看这个案例:

首先设置Gflags,打开gflags.exe工具,设置如下:

运行windbg,打开MemoryLeak.exe,并使其跑起来;执行!handle

0:000> !handle

Handle c4

  Type         Section

Handle d0

  Type         Section

Handle d4

  Type         Section

Handle d8

  Type         Section

Handle dc

  Type         Section

Handle e0

  Type         Section

Handle e4

  Type         Section

Handle e8

  Type         Section

Handle ec

  Type         Section

Handle f4

  Type         Mutant

Handle 104

  Type         Section

Handle 10c

  Type         Section

Handle 110

  Type         Section

Handle 138

  Type         Section

Handle 3dc

  Type         Mutant

Handle 3e8

  Type         Mutant

Handle 3f0

  Type         Mutant

Handle 3f8

  Type         Mutant

Handle 408

  Type         Mutant

Handle 418

  Type         Section

Handle 438

  Type         Mutant

Handle 48c

  Type         File

Handle 4a4

  Type         Section

Handle 7dc

  Type         Process

Handle 7e0

  Type         WindowStation

Handle 7e4

  Type         Event

Handle 7e8

  Type          File

Handle 7ec

  Type         Port

Handle 7f0

  Type         Directory

Handle 7f4

  Type         Process

Handle 7f8

  Type         Directory

Handle 7fc

  Type         KeyedEvent

32 Handles

Type           Count

Event          1

Section        15

File           2

Port           1

Directory      2

Mutant         7

WindowStation  1

Process        2

KeyedEvent     1

用!htrace -enable命令开启句柄检测:

0:000> !htrace -enable

Handle tracing enabled.

Handle tracing information snapshot successfully taken.

按F5继续执行,中断进程,使用!htrace-snapshot命令,获得此时进程句柄的镜像:

0:000> !htrace -snapshot

Handle tracing information snapshot successfully taken.

再次F5运行然后中断,使用!htrace -diff命令获得当前句柄状态与第4步 snapshot镜像句柄的差异:

0:000> !htrace -snapshot

Handle tracing information snapshot successfully taken.

0:000> !htrace -diff

Handle tracing information snapshot successfully taken.

0x0 new stack traces since the previous snapshot.

--------------------------------------

No outstanding handles opened since the previous snapshot were detected.

既然No outstanding说明泄露不够严重,果断F5:

0:000>!htrace -diff

Handletracing information snapshot successfully taken.

0x1bnew stack traces since the previous snapshot.

Ignoringhandles that were already closed...

Outstandinghandles opened since the previous snapshot:

--------------------------------------

Handle= 0x008eb22c - OPEN

ThreadID = 0x00001570, Process ID = 0x000014d4

 

0x00411c02:MemoryLeak!LeakProcess::run+0x00000062

0x004117ed:MemoryLeak!wmain+0x0000025d

0x00413678:MemoryLeak!__tmainCRTStartup+0x000001a8

0x004134bf:MemoryLeak!wmainCRTStartup+0x0000000f

0x7c817077:kernel32!BaseProcessStart+0x00000023

--------------------------------------

Handle= 0x008eb228 - OPEN

ThreadID = 0x00001570, Process ID = 0x000014d4

 

0x00411c02:MemoryLeak!LeakProcess::run+0x00000062

0x004117ed:MemoryLeak!wmain+0x0000025d

0x00413678:MemoryLeak!__tmainCRTStartup+0x000001a8

0x004134bf:MemoryLeak!wmainCRTStartup+0x0000000f

0x7c817077:kernel32!BaseProcessStart+0x00000023

-------------------------------------

//此处省略100

--------------------------------------

Handle= 0x008eb1c8 - OPEN

ThreadID = 0x00001570, Process ID = 0x000014d4

 

0x00411c02:MemoryLeak!LeakProcess::run+0x00000062

0x004117ed:MemoryLeak!wmain+0x0000025d

0x00413678:MemoryLeak!__tmainCRTStartup+0x000001a8

0x004134bf:MemoryLeak!wmainCRTStartup+0x0000000f

0x7c817077:kernel32!BaseProcessStart+0x00000023

--------------------------------------

Handle= 0x008eb1c4 - OPEN

ThreadID = 0x00001570, Process ID = 0x000014d4

 

0x00411c02:MemoryLeak!LeakProcess::run+0x00000062

0x004117ed:MemoryLeak!wmain+0x0000025d

0x00413678:MemoryLeak!__tmainCRTStartup+0x000001a8

0x004134bf:MemoryLeak!wmainCRTStartup+0x0000000f

0x7c817077:kernel32!BaseProcessStart+0x00000023

--------------------------------------

Displayed0x1b stack traces for outstanding handles opened since the previous snapshot.

 

显然这个一看必然是句柄泄露,我们可以继续寻找泄露代码行:

0:000>lsa MemoryLeak!LeakProcess::run+0x00000062

    76:           while(1)

    77:           {

    78:                     Sleep(0); 

    79:                     intnProcessID = (int)getpid();

>   80:                    hProcess =OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,nProcessID);

    81:                     if(hProcess== 0)

    82:                              printf("OpenProcessfailed!\n");                       

    83:

    84:                     //CloseHandle(hProcess);

85:                     hProcess = NULL;

 

至此已经成功核心内存泄露的具体原因。

3.4线程错误

(待续)

 

4 更多问题

(待续)

 

5 参考文献

 

1 WinDbg from Wik

ihttp://en.wikipedia.org/wiki/WinDbg

2 Debugging Tools and Symbols: GettingStarted

http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx
3 A word for WinDbg(写得深入浅出,作者谦虚了)
http://mtaulty.com/communityserver/blogs/mike_taultys_blog/archive/2004/08/03/4656.aspx

4双机联调

http://www.cnblogs.com/mvperic/archive/2010/07/25/1784790.html

5配置系统转储

http://www.cnblogs.com/mvperic/archive/2010/06/25/1764833.html

6调试蓝屏

http://blogs.itecn.net/blogs/shehuayu/archive/2009/07/15/101749.aspx

 

 


你可能感兴趣的:(Windows,C/C++,allocation,debugging,statistics,parameters,汇编,windows)