65.9K
CodeProject 正在变化。 阅读更多。
Home

入门:使用 WinDBG 和 SOS 调试 .Net 应用程序中的内存相关问题

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (35投票s)

2008 年 2 月 12 日

CPOL

17分钟阅读

viewsIcon

188802

使用 WinDBG 和 SOS 入门

目录

引言

Visual Studio™ 内置调试器是一个广为人知的调试器,它提供了托管和混合调试选项。但是,当我们需要调试与内存相关的问,例如优化应用程序性能、解决程序崩溃或内存不足异常时,我们就需要一个更强大的调试器,该调试器能够深入了解堆上的对象以及垃圾回收 (GC)。

市面上也有许多性能分析工具可用于分析挂起或崩溃,例如 .NET Memory Profiler 和 Ants Profiler,它们似乎是最常见的。在应用程序形状不佳而无法进行性能分析的情况下,这些工具可能会失效且无用。

什么是“糟糕的应用程序”?

假设一个应用程序包含太多包含自引用的对象,导致其运行时“.NET CLR Memory->% Time spent in GC”的值高达 80%。这个应用程序可能会崩溃,甚至导致性能分析工具本身挂起。几天前,我在尝试分析一个 WinForms 应用程序中内存不足 (OOM) 异常的原因时遇到了类似的情况。因此,我选择使用 WinDBG + SOS 作为我的 .NET 性能分析工具。为什么?这个问题将在本文结束之前得到解答。

为什么选择 WinDBG + SOS?

乍一看,WinDBG + SOS 似乎是正确的选择,因为:

  1. 它是微软免费提供的。:)
  2. 它甚至可以治疗癌症晚期患者;我的意思是,它可以轻松诊断一个“糟糕的应用程序”。
  3. 它完美地融合了原生和托管代码的同步调试。

我曾多次听说过 WinDBG,但直到我的应用程序在尝试诊断 OOM 异常时无法使用常规性能分析工具启动时,我才开始使用它。我在互联网上找到了许多关于使用 WinDBG 的零散信息,但很难将它们整合起来并理解如何真正使用此工具进行应用程序调试。因此,我认为将所有细节记录在一个地方会很有用,以便使那些决定开始使用 WinDBG 但不熟悉其用法和潜力的用户能够更轻松。

在使用 WinDBG 和 SOS 之前需要了解的内容

什么是 WinDBG?

WinDBG 是微软 Windows 的一款多用途调试器,由微软在网上分发。它可以用于调试用户模式应用程序以及内核应用程序,如驱动程序,甚至操作系统。本文档仅涉及用户模式应用程序的调试。

请参阅此图快速了解 WinDBG。

WinDBG 可以做什么?

  1. WinDBG 用于调试崩溃转储文件或小型转储文件。转储文件是在应用程序崩溃/挂起或内存耗尽时生成的,用于事后调试。 调试基础部分将更详细地解释转储文件。
  2. WinDBG 也可用于调试实时目标,通常是在开发人员自己的机器上。
  3. WinDBG 调试引擎是 Windows 操作系统的一部分;它支持调试原生代码,但其扩展也允许托管代码调试。
  4. WinDBG 是高度可扩展的,因为它支持加载外部库来调试公共语言运行时。在本文中,我们将使用 SOS.dll
  5. WinDBG 支持在远程机器上共享调试会话,这意味着两个人可以同时分析问题。

什么是 SOS.dll (Son of Strike)?

SOS 是一个 NTSD(NT 系统调试器,一种低级调试器)的扩展 DLL。当与 WinDBG(用于原生调试)一起使用时,它支持托管代码调试。SOS 扩展 DLL 也可以直接在 Visual Studio IDE 中使用,并允许查看 GC 和堆内部发生的情况。

注意: SOSEx 是 SOS 的升级版。请在关注点部分阅读相关信息。

调试基础

在深入了解 WinDBG 的初始调试步骤之前,我们需要理解以下术语:

  1. 调试符号
  2. 程序数据库
  3. 托管与原生调试
  4. 第一次和第二次机会异常
  5. 崩溃转储文件与小型转储文件
  6. 崩溃转储与挂起转储(小型转储)
  7. 内核模式与用户模式调试符号

调试符号有助于调试器将可执行文件中的原始地址映射到源代码行。这些符号就是 .pdb 文件,每个文件都存储在单独的目录中,可以从 Microsoft 网站下载:http://msdl.microsoft.com/download/symbols[^]

有关如何在 Visual Studio 中设置调试符号的信息,请参阅此图

MSDN 指出,在使用各种 Microsoft 工具调试应用程序时,必须具有符号信息。符号文件提供了可执行文件和动态链接库中包含的函数的足迹。此外,符号文件可以展示导致失败点的函数调用路线图。例如,当您在调试器中转储调用堆栈时,必须拥有符号。

程序数据库

扩展名为 .pdb 的程序数据库文件独立于可执行文件存储。程序数据库 (PDB) 文件包含调试和项目状态信息,允许对程序的 Debug 配置进行增量链接。当调试机器上没有源代码时,它们是必需的。

调试器如何查找 pdb?

我们先来看看 Visual Studio 调试器,因为我们大多数人都经常使用它。它直接使用链接器创建的项目 PDB 文件,并将 PDB 的绝对路径嵌入到 EXE 或 DLL 文件中。如果调试器在指定位置找不到 PDB 文件,或者路径无效(例如,项目被移到另一台计算机上),调试器会在包含 EXE 的路径、解决方案属性页(通用属性文件夹、调试符号文件页)中指定的符号路径中进行搜索。有关如何在 Visual Studio 中设置调试符号的信息,请参阅此图

WinDBG 在符号服务器定义的路径(即“Symbol File Path”)中搜索所需的 PDB。另请参阅此图

托管与原生调试

假设我们有一个使用非托管 COM 对象的 C# 或 VB 项目。以 Visual Studio 调试器为例,如果我们只想调试我们编写的 C# 或 VB 代码,那就是托管调试。但是,有时我们可能想调试 COM 对象。那就是原生调试。

我们需要先启用原生调试,步骤如下:

  1. 在解决方案资源管理器中右键单击项目
  2. 转到属性 ->“配置属性 | 调试 | 启用非托管代码”,然后选中以启用。

图 1:在 Visual Studio 中启用非托管调试
EnableUnmanagedDebugging.PNG

现在您可以调试托管和原生代码,这实际上是混合调试。

注意:WinDBG 是一个高级原生代码调试器,与 SOS 结合使用时,也支持托管代码调试。

第一次和第二次机会异常

调试器会收到两次异常通知。它在应用程序有机会处理异常之前(“第一次机会异常”)收到通知。如果应用程序不处理异常,调试器将有机会处理异常(“第二次机会异常”)。如果调试器不处理第二次机会异常,应用程序将退出。

崩溃转储文件与小型转储文件

有两种使用调试器的方法:要么将其附加到正在运行的进程,要么使用它来分析崩溃转储文件。第一种选项允许您查看应用程序运行时的情况,但仅适用于开发环境中的调试,而不适用于生产环境(在这里我们需要内存转储)。两种环境之间存在重要区别,但本文档不讨论这些区别,因为这超出了本文的范围。因此,转储文件提供了一种将实际调试阶段与数据收集过程分开的选项。

崩溃转储文件

第一代崩溃转储文件,通常称为“完整用户转储”,包含整个进程空间的每一个字节,因此像文本编辑器这样的简单应用程序的崩溃转储可能大小为几兆字节。虽然毫无疑问对事后调试很有用,但这些转储文件通常变得非常大,以至于通过电子方式将其传输给软件开发人员变得不可能,或者至少不方便。这些不再使用。

小型转储文件

小型转储文件是高度可定制的。在生成小型转储文件时,不保存整个进程空间,而是只保存某些部分。但是,如果需要,小型转储文件可以包含比旧式崩溃转储文件更多信息。例如,小型转储文件可以包含有关进程使用的内核对象的信息。

小型转储文件可定制的特性允许我们在保持小型转储文件尽可能小的同时,选择我们需要调试的应用程序状态信息。小型转储文件需要使用外部工具生成;我们将使用 adplus.vbs 脚本,该脚本是 WinDBG 安装的一部分。我们将在 捕获转储文件部分介绍如何生成定制转储文件。

不使用外部工具编写小型转储文件

可再发行组件 DbgHelp.dll 公开了创建小型转储文件的公共 API,我们不再需要依赖外部工具。这在敏感的生产环境中非常有帮助,因为某些客户可能不希望安装工具来生成转储文件。Andy Pennell 的文章很好地解释了如何使用 MiniDumpWriteDump() 编写小型转储文件。

崩溃转储与挂起转储(小型转储)

当我们无法确定问题何时发生时,需要崩溃转储。顾名思义,它通常由程序崩溃引起,但这并非唯一情况。在这种情况下,我们可以提前配置调试器来监视目标进程,并在进程终止时捕获转储,或者在我们需要捕获特定异常的转储时。我们将在后面的 捕获转储文件部分介绍捕获转储文件。

挂起转储可以在问题发生但进程仍在内存中时捕获,例如在内存泄漏场景中,也可以在进程占用 CPU 过高时捕获。

注意:从这里开始,当我提到转储时,指的是小型转储,因为在整个讨论的剩余部分我们将使用小型转储。

如何配置 WinDBG?

启动 WinDBG

首先,请从WinDbg 安装安装 Debugging Tools for Windows。

安装完成后,您应该可以在安装文件夹中找到 WinDBG.exe,并启动该可执行文件。

图 2:WinDBG 选项概览

WinDBG.PNG

通过以下步骤将符号路径设置为 Microsoft 符号服务器,使用 WinDBG:

  1. 启动 Windows Debugger。
  2. 在“文件”菜单中,单击“符号文件路径”(Ctrl+S)。
  3. 在“Symbol Path”框中,键入以下内容:
    SRV*your local folder for symbols*http://msdl.microsoft.com/download/symbols
    其中 your local folder for symbols 是您复制本地符号缓存的文件夹。调试符号将下载到此位置。

图 3:设置符号路径
SymbolSearchPath.PNG

按以下方式设置源路径:

  1. 启动 Windows Debugger。
  2. 在“文件”菜单中,单击“源文件路径”(Ctrl+P)。
  3. 在“Source Search Path”框中,键入以下内容:
    C:\SourceDir\
    其中,SourceDir 包含要调试的目标应用程序的源文件。

图 4:设置源路径
SourceSearchPath.PNG

WinDBG 和 SOS 命令

以下是说明您开始时所需的大部分命令的备忘单。
CommandsCheatSheet.PNG

您可以从 Kent Boogaart 的博客下载“备忘单”:Kent Boogaart's blog。下载提供多种格式。

SOS 帮助:在 WinDBG 命令窗口中键入 !SOS.help 也会列出所有 SOS 命令。
SOSHelp.PNG

以下是 SOS 命令的分类列表,摘自 Johan Straarup 的博客:Johan Straarup's blog。函数按类别列出,然后大致按重要性排序。常用函数的快捷名称显示在括号中。

对象检查
-----------------------------
DumpObj (do)
DumpArray (da)
DumpStackObjects (dso)
DumpHeap
DumpVC
GCRoot
ObjSize
FinalizeQueue
PrintException (pe)
TraverseHeap
检查 CLR 数据结构
-----------------------------
DumpDomain
EEHeap
Name2EE
SyncBlk
DumpMT
DumpClass
DumpMD
Token2EE
EEVersion
DumpModule
线程池
DumpAssembly
DumpMethodSig
DumpRuntimeTypes
DumpSig
RCWCleanupList
DumpIL
检查代码和堆栈
----------------------------
线程
CLRStack
IP2MD
U
DumpStack
EEStack
GCInfo
EHInfo
COMState
BPMD
诊断工具
-----------------------------
VerifyHeap
DumpLog
FindAppDomain
SaveModule
GCHandles
GCHandleLeaks
VMMap
VMStat
ProcInfo
StopOnException (soe)
MinidumpMode

特定命令的帮助

如果您需要特定命令的帮助,可以使用 !< SOS >.help <commandname>,例如:

0:000> !sos.help dumpobj

另一个有用的命令是:

!DumpObj [-v] [-short] [-r 2] <object address>

此命令允许您检查对象的字段,并了解对象的重要属性,例如 EEClass、MethodTable 和大小。

注意:WinDBG 命令的完整列表也可以在此处找到:here

执行批处理命令

有两种方法可以批处理执行上述命令:

  1. 在调试器窗口中将所有命令输入为单个字符串,用分号分隔。
  2. 将所有命令存储在一个脚本文件中,并使用以下命令运行它们:
    $$>< (Script file)

CPU 寄存器

在分析堆栈和堆时,我们会看到以下寄存器名称:

  • EAX - 通用 32 位(8 字节)寄存器
  • EBX - 通用 32 位(8 字节)寄存器
  • ECX - 通用 32 位(8 字节)寄存器
  • EDX - 通用 32 位(8 字节)寄存器
  • EDI - 扩展目标指针
  • EBP - 扩展基址指针
  • ESI - 扩展源索引
  • ESP - 堆栈指针
  • EIP - 当前 CPU 地址(指令指针)

捕获转储文件

可以使用 Adplus 脚本 adplus.vbs 来生成转储文件,该脚本是 WinDBG 安装的一部分。Adplus 脚本允许使用各种可配置选项生成定制转储文件。以下链接逐步介绍了如何生成定制转储文件。

注释
通常,在异常发生后立即生成转储文件即可,除非进程在异常发生后很快退出。在这种情况下,请启动 WinDBG 并直接从 WinDBG 运行应用程序。它将加载 .exe 并立即中断。键入 g(go)并按 Enter。

当进程终止时,WinDBG 将自动再次中断。然后您可以使用 .dump 命令保存转储文件。

.dump /ma <output file>

使用 WinDBG 和 SOS 进行调试

调试实时目标

这种调试通常在开发机器上进行,这些机器也提供应用程序源代码。

  • 一、使用 WinDBG
    1. 启动 WinDBG。
    2. 使用“文件”|“打开可执行文件”(Ctrl+E)直接从 WinDBG 运行应用程序。
      或者
      使用“文件”|“附加到进程”(F6)附加到正在运行的进程。

      使用进程列表对话框中的“非侵入式”选项,可以(在 Windows XP 或 2003 上)在不终止调试进程的情况下分离(detach)它,但这会限制您可用的命令,因此请将其保持未选中状态。

      图 5:附加到正在运行的可执行文件
      AttachToProcess.PNG
    3. WinDBG 将加载可执行文件并立即中断执行。
    4. 在命令窗口中键入 .load SOS 来加载 SOS 扩展并启用托管代码调试。

      图 6:加载 SOS 扩展
      WinDBGCommandWindow.PNG

    5. 使用以下命令设置函数断点:
      !bpmd TestApp.exe TestClass.MyMethod
      如果需要在特定点中断程序执行。
    6. 键入 g(go 命令)并按 Enter 切换到调试目标(目标应用程序),以便您可以对其进行操作。
    7. 当进程异常终止或命中断点时,WinDBG 将自动再次中断。然后,您可以使用 .dump 命令检查进程并保存转储文件以进行事后调试。

      注释:

      1. 当调试目标运行时,您可以通过向 WinDBG 窗口发出 CTRL+BREAK 来随时回到调试器。它会尝试停止调试目标,并将您带回到命令提示符;有些人称之为即时窗口。
      2. 更多示例:这些示例足以让您熟悉分析问题的命令。
      3. 二、不使用 WinDBG [在 Visual Studio 中使用 SOS]

        SOS 也可以直接在 VS2003 及更高版本的 Visual Studio 中使用,无需 WinDBG。请按照以下步骤使用 SOS:

        1. 以调试模式打开目标 Visual Studio 项目,我们称之为调试目标。
        2. 启用原生调试。为了加载任何扩展,如 SOS.dll,您必须在原生模式下进行调试,因此,在启动调试器之前,请在项目上右键单击,选择“项目”|“属性”|“调试”,然后勾选“启用非托管代码调试”框。
        3. 设置符号服务器路径。
          • a) 设置 Visual Studio 2003 使用 Microsoft 符号服务器。在“项目属性”对话框中,将“调试”页上的“符号路径”设置为:
            SRV*c:/symbols*http://msdl.microsoft.com/download/symbols
            此字符串告诉调试器使用符号服务器获取符号,并在本地符号服务器中创建一个符号服务器,其中将复制这些符号。

            图 7:在 Visual Studio 中设置调试符号
            SetVSDebugSymbols.PNG

          • b) 设置 Visual Studio 2005 使用 Microsoft 符号服务器。有关说明,请参阅此链接:http://geekswithblogs.net/mskoolaid/archive/2005/12/17/63418.aspx
        4. F5 开始调试。
        5. 加载 SOS 扩展。

          查看输出窗口,它将显示所有已加载的符号。在 Visual Studio 加载完符号后,在即时窗口(Alt+Ctrl+I)中键入以下命令来加载 SOS:

          .load C:\WINDOWS\Microsoft.NET\Framework\vx.x.xxxx\SOS
          其中 vx.x.xxxx 是调试目标使用的 .NET 框架版本。
          或者
          .loadby SOS mscorwks
        6. 使用 SOS 命令分析堆。关于在 Visual Studio 2005 中使用 SOS 的简短教程,请访问以下网址:http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2006/10/25/8930.aspx
        7. 三、同时使用 WinDBG 和 Visual Studio 调试器

          以下链接展示了如何轻松地同时使用 Visual Studio 和 WinDBG 的强大功能:http://www.wintellect.com/cs/blogs/jrobbins/archive/2007/12/08/use-two-debuggers-at-once.aspx

调试转储文件(事后调试)

  • 一、使用 WinDBG
    1. 使用 WinDBG 的“文件”|“打开崩溃转储”(Ctrl+D)菜单打开转储文件。
    2. 在命令窗口中键入 .load SOS 来加载 SOS,或者:
      .load C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\SOS
    3. 3. 分析转储文件。一个示例位于:http://blogs.msdn.com/johan/archive/2007/01/11/i-am-getting-outofmemoryexceptions-how-can-i-troubleshoot-this.aspx
  • 二、不使用 WinDBG [在 Visual Studio 中使用 SOS]
    有趣的是,VS 2003 实际上也支持托管应用程序的事后调试以及使用 SOS。但是,它只支持托管小型转储文件,而升级的 VS 版本则支持托管和原生转储文件。
    1. 使用“文件”|“打开”|“项目”在 Visual Studio 中打开 .dmp 文件。
    2. 按 F5 开始调试。
    3. 加载 SOS。单击提示并加载符号后(您可以在输出窗口中看到所有已加载的符号),打开即时窗口并键入 .load SOS

      扩展加载成功后,您可以键入 !help 来列出所有 SOS 命令。

    4. 分析转储中捕获的托管应用程序的状态。

示例

有关更多示例,请参阅以下链接:

  1. 排除 OutOfMemoryExceptions 故障
  2. .NET 挂起调试
  3. .NET 内存泄漏调试 第一部分:定义“何处?”
  4. .NET 内存泄漏调试 第二部分:案例研究

关注点

  1. 博客包含以下有用阅读材料:
    • 一、如何自动启动 WinDBG;只需在 .dmp 文件的右键单击菜单中选择一个选项。
    • 二、如何连接到远程调试会话。
  2. SOSEx 是 SOS 的升级版。它功能更强大,使用更方便,并且解决了使用 SOS 时的一些常见限制。您可以在此处此处查看 SOSEx 命令的实际应用。
  3. 在终端会话中生成转储
    如果您需要在 Windows NT 或 Windows 2000 计算机上生成进程转储,则无法通过终端服务器会话进行,因为调试器无法附加到运行在不同窗口站中的进程,并且如果您尝试这样做,您可能会收到类似 Win32 error 5 Access is denied. 的消息。

    这是设计使然,但如果您需要,也有一些解决方案。请查看您无法通过终端服务器会话进行调试,或查看 Windbg 帮助中的“远程运行崩溃模式”主题。

    DebugDiag 通过其属性对话框将其配置为在服务模式下运行,从而更轻松地克服此限制。在此处阅读更多信息

历史记录

  • 2008 年 2 月 12 日 - 初始版本
© . All rights reserved.