实验室 1.3 排查崩溃问题 - 使用 createdump 工具捕获核心故障转储

适用于: .NET Core 2.1、.NET Core 3.1、.NET 5

本文讨论如何使用 createdump 工具在 Linux 中捕获 .NET Core 故障转储文件,然后使用 lldb 诊断崩溃问题。

先决条件

遵循这些故障排除实验室的最低要求是有一个 ASP.NET Core应用程序来演示低 CPU 和高 CPU 性能问题。

可以在 Internet 上找到几个示例应用程序来实现此目标。 例如,可以下载并设置 Microsoft 的简单 webapi 示例 来演示不需要的行为。 或者,可以使用 BuggyAmb ASP.NET Core 应用程序作为示例项目。

如果已遵循本系列的上一部分,则应准备好以下设置:

  • Nginx 配置为托管两个网站:
    • 第一个使用 myfirstwebsite 主机标头 () http://myfirstwebsite ,并将请求路由到侦听端口 5000 的演示 ASP.NET Core应用程序。
    • 第二个服务器使用 buggyamb 主机标头 (http://buggyamb) ,并将请求路由到侦听端口 5001 的第二个 ASP.NET Core示例 buggy 应用程序。
  • 这两个 ASP.NET Core应用程序都应作为服务运行,这些服务会在服务器重启或应用程序停止响应时自动重启。
  • Linux 本地防火墙已启用并配置为允许 SSH 和 HTTP 流量。

注意

如果设置未准备就绪,请转到“第 2 部分创建并运行 ASP.NET Core应用”。

若要继续本实验室,必须至少有一个在 Nginx 后面运行的有问题的 ASP.NET Core Web 应用程序。

本实验室的目标

自动生成的核心转储文件没有用,因为它们不包含所有托管状态信息。 建议创建用于捕获 .NET Core 核心故障转储文件的工具。

在本部分中,你将了解如何使用 createdump 捕获故障转储文件,并在 lldb 中打开该文件以诊断崩溃问题。

将 createdump 配置为在进程终止时运行

Createdump 与每个 .NET Core 运行时一起自动安装。

createdump 配置策略 文档中所述,可以设置具有环境变量的配置选项。 这些作为参数传递给 createdump 命令。 下面是支持的环境变量:

  • COMPlus_DbgEnableMiniDump:如果设置为 1,则启用终止时自动核心转储生成。 默认值为 0
  • COMPlus_DbgMiniDumpType:这是要创建的小型转储文件的类型。 默认值为 2 (,或枚举类型 MiniDumpWithPrivateReadWriteMemory 为) 。 这意味着生成的转储文件将包括 GC 堆以及捕获进程中所有现有线程的堆栈跟踪所需的信息。
  • COMPlus_DbgMiniDumpName:如果已设置,请使用 作为模板创建转储文件路径和文件名。 可以使用 参数将 PID 放入名称 %d 中。 默认模板为 /tmp/coredump.%d。 使用此环境变量,可以配置输出目录。
  • COMPlus_CreateDumpDiagnostics:如果设置为 1,则启用 createdump 工具诊断消息 (TRACE 宏) 。 如果 createdump 未按预期工作并且未生成内存转储文件,则此设置可能很有用。

可以在 createdump 配置策略中找到有关这些变量的详细信息。

此处的重要变量是 COMPlus_DbgEnableMiniDump。 必须将此环境变量设置为 1。 可通过多种方法设置此环境:

  • 在应用程序的配置文件中设置它。
  • export COMPlus_DbgEnableMiniDump=1使用 命令设置它。 此设置在操作系统重启后不会保留。 因此,如果要在重启后使设置保持启用状态,则必须将其设置为持久性。
  • 在 ASP.NET Core服务单元文件中设置它。

在 ASP.NET Core服务单元文件中设置此变量是最简单的方法。 缺点是应重启服务。 在本故障排除部分中,这是演示的选项。

打开 bug 应用程序的服务文件,并添加 COMPlus_DbgEnableMiniDump=1 环境变量。 这与你在本培训前几章中多次操作相同。

错误命令的屏幕截图。

必须重新加载服务配置,因为配置已更改。 然后,重启 BuggyAmb 服务。

sudo 命令的屏幕截图。

进行这些更改后,请重现崩溃问题。 如果 createdump 正常工作,则应将转储文件写入 /tmp/ 目录下作为 coredump。<PID>。 按照相同的步骤重现问题:

  1. 选择 “崩溃 3”。 页面加载正确,但返回错误消息,指示进程应已崩溃。
  2. 选择“慢”。 这将生成“HTTP 502”响应代码, (错误的网关错误) 而不是产品表。
  3. 出现问题后,不会呈现任何页面,并且 10-15 秒内将收到相同的错误消息。
  4. 10-15 秒后,应用程序开始正常工作。

现在,在 /tmp 目录中应有一个核心转储文件。

ll 命令的屏幕截图。

如果没有核心转储文件,请确保正确配置了 buggyamb.service 该文件。 还必须重新加载服务配置并重启服务。

在 lldb 中打开核心转储文件

建议将转储文件移动到文件夹 ~/dumps/ ,以便执行示例分析。 若要打开转储文件,请运行 lldb --core ~/dumps/coredump.<10354>。 在此命令中,将 10354 占位符替换为进程的 PID。

注意

如果之前已打开转储文件并使用 lldb,则表示已设置符号并安装了 SOS。 可以打开相同的 .NET Core 版本转储文件,而无需再次下载符号。 但是,如果打开符号尚未下载的其他 .NET Core 版本转储文件,则必须先下载该版本的符号,然后才能开始分析。

运行 SOS clrstack 命令以显示托管调用堆栈。 请记住,使用系统生成的核心转储文件运行同一命令时,会看到错误。 这一次,应会看到正确的托管调用堆栈。

lldb 命令的屏幕截图。

这是一个良好的开端。 但是,显示的调用堆栈属于调试进程的main线程。 它不是引发异常的线程。

注意

如果我们在 Windows 上的 WinDbg 中打开故障转储文件,WinDbg 将直接选择导致崩溃的线程。 但是,lldb 中情况并非如此。 在 lldb 中,WinDbg 不会自动选择触发调试器生成内存转储的线程。

虽然此 WinDbg 行为在调试时很有用,但 lldb 中缺少此功能并不是世界末日。 相反,可以检查所有线程,以尝试确定可以引发异常的位置。 首先,使用 thread list 命令检查本机线程。

最好先对所有线程调用堆栈运行快速检查,以便了解生成转储文件时正在运行的内容。 首先查看具有 thread list 命令的本机线程列表。

注意

列表中的第一个线程附近的星号 (*) (thread #1) 指示它是活动线程。

list 命令的屏幕截图。

本机线程检查不会透露太多。 由于这是一个 .NET Core 应用程序,因此请运行 SOS clrthreads 命令来检查 CLR 线程的列表。 此命令列出应用程序中运行的托管线程。

clrthreads 命令的屏幕截图。

此屏幕截图未显示所有托管线程。 但是,屏幕截图中列出了应关注的详细信息。 Thread #15 存在我们在系统日志中看到的异常。

thread15 信息的屏幕截图。

检查该线程的调用堆栈。 为此,必须首先选择有问题的线程。 在将要运行的内存转储分析中,线程数很可能不同。 若要选择另一个线程作为活动线程,请使用 thread select 命令,并传递 lldb dbg 线程 ID。 例如,运行 thread select 15 以切换到线程 15。 然后,运行的每个连续命令都将位于该线程的上下文中。 若要查看本机调用堆栈,请运行 bt (back trace) 命令。

选择命令的屏幕截图。

如此屏幕截图所示,此线程肯定是触发崩溃的线程。

  • PROCEndProcessPROCAbort() 是在未经处理的异常之后调用的。
  • POCCreateCrashDump 告诉我们故障转储由 .NET Core 编写。

可以通过运行 clrstack 命令来检查托管调用堆栈。 但是,这不会透露太多。 运行 pe 命令以获取异常详细信息。

pe 命令的屏幕截图。

此信息指示:在 System.Net.HttpWebRequest 方法的 Crash3 页中 LogTheRequest() 触发了 。 这是一条有助于查找问题的重要信息。 但是,如果要查找 HTTP 请求的 URL,该怎么办? 若要继续,请尝试检查堆栈上引用的对象,以查看是否可以从此列表中收集更多信息。 若要显示当前堆栈边界内找到的所有托管对象,请运行 dso

dso 命令的屏幕截图。

这无济于事。 不应看到任何 System.Net.HttpWebRequest 实例。 存在异常实例,你已检查过它。 因此,此命令不会生成与原因相关的新信息。

所有托管对象都存储在托管堆中,我们可以通过运行 dumpheap来查看托管堆。 不要在没有任何参数的情况下运行 dumpheap ,因为这样命令将列出托管堆中的所有对象 (大型列表) 。 相反,可以使用 命令获取堆 dumpheap -stat 的统计信息。

可以通过运行以下格式的 命令,再使用一种策略来缩小统计信息范围:

dumpheap -stat -type System.Net.HttpWebRequest

以下屏幕截图显示了托管对象(其名称中包含字符串 System.Net.HttpWebRequest )的统计信息。

请求信息的屏幕截图。

在示例应用程序中,托管堆上只有一个 System.Net.HttpWebRequest 对象。 在上一个列表中,条目旁边 HttpWebRequest 看到的地址不是内存中该对象的地址。 相反,它是与 类型 System.Net.HttpWebRequest对象的“方法表”相对应的地址。 若要获取对象的实际列表,可以通过以下方式将该方法表 (MT) 地址传递给 dumpheap 命令:

dumpheap -mt <address>

例如,运行 dumpheap -mt 00007f53623cb640 以查找对象的地址。

dumpheap 命令的屏幕截图。

现在,你可以识别有问题的对象的地址。 在此示例中,它是 00007f51300c0868。 可以通过将该地址传递给 dumpobj 命令来调查对象的属性。 这将列出该对象的属性。 在此示例中,运行 dumpobj 00007f51300c0868 以检查对象的属性。

dumpobj 命令的屏幕截图。

注意

你正在调查对象 System.Net.HttpWebRequest ,其属性之一是 _requestUri。 这是 类型的对象 System.Uri 。 需要确定 URI。 因此,将 属性的 _requestUri 地址传递给 dumpobj 命令

复制对象的地址 System.Uri ,然后再次使用 dumpobj 对其进行调查。 运行 dumpobj 00007f51300bfbb8。 生成的内存转储文件中对象的地址肯定不同。 列表将显示 _stringSystem.Uri属性。

dumpobj2 命令的屏幕截图。

复制 的 _string地址,然后再次对其运行 dumpobj 命令。 运行 dumpobj 00007f51300bfb40。 以下屏幕截图中列出了结果。

dumpobj3 命令的屏幕截图。

最后,你可以找到 HttpWebRequest 的 URL: http://buggyamb/Problem/Api/NotExistingLoggingApi。 顾名思义,这可能不是应用程序中的现有页。

最后,关于崩溃如何发生的理论如下:

  • HttpWebRequest 是在 Crash3 网页的 方法中LogTheRequest()对不存在的 URL 进行的。
  • 在实际应用程序中,解决此问题的解决方案是在出现 错误时 HttpWebRequest 进行处理。 但是,在这种情况下,解决方案要简单得多:不要向不存在的页面发出 HttpWebRequest 请求。

此时,你可能对导致崩溃的原因有更多疑问。 例如,为什么在选择 “慢速 ”链接后触发了崩溃?

请自行继续调查。 建议执行的下一步是使用HttpWebRequest对象地址运行 gcroot 命令,以找出其根目录。 这可能有助于你了解崩溃的发生方式。

实验室到此结束。 按 Ctrl+C 或使用 q 命令退出 lldb 调试器。

后续步骤

实验室 2.1 在 Linux 中使用 createdump 排查性能问题