适用于: .NET Core 2.1、.NET Core 3.1、.NET 5
本文介绍如何使用 createdump 工具捕获 Linux 中的 .NET Core 故障转储文件,然后使用 lldb 诊断崩溃问题。
先决条件
遵循这些故障排除实验室的最低要求是有一个 ASP.NET 核心应用程序来演示低 CPU 和高 CPU 性能问题。
可以在 Internet 上找到多个示例应用程序来实现此目标。 例如,可以下载并设置 Microsoft的简单 Webapi 示例 ,以演示不良行为。 或者,可以将 BuggyAmb ASP.NET Core 应用程序用作示例项目。
如果已遵循本系列的上一部分,则应准备好进行以下设置:
- Nginx 配置为托管两个网站:
- 第一个使用 myfirstwebsite 主机标头 (
http://myfirstwebsite
) 侦听请求,并将请求路由到在端口 5000 上侦听的演示 ASP.NET Core 应用程序。 - 第二个使用 buggyamb 主机标头 (
http://buggyamb
) 侦听请求,并将请求路由到侦听端口 5001 的第二个 ASP.NET 核心示例 buggy 应用程序。
- 第一个使用 myfirstwebsite 主机标头 (
- ASP.NET Core 应用程序应作为服务运行,这些服务会在服务器重启或应用程序停止响应时自动重启。
- Linux 本地防火墙已启用并配置为允许 SSH 和 HTTP 流量。
注意
如果设置尚未准备就绪,请转到“第 2 部分创建并运行 ASP.NET Core 应用”。
若要继续此实验室,必须至少有一个问题 ASP.NET 核心 Web 应用程序在 Nginx 后面运行。
本实验室的目标
自动生成的核心转储文件不有用,因为它们不包含所有托管状态信息。 创建用于捕获 .NET Core 核心故障转储文件的建议工具。
在本部分中,你将了解如何使用 createdump 捕获故障转储文件,并在 lldb 中打开该文件来诊断崩溃问题。
配置 createdump 以在进程终止时运行
Createdump 会自动与每个 .NET Core 运行时一起安装。
如创建的ump 配置策略文档中所述,可以设置具有环境变量的配置选项。 这些作为参数传递到 createdump 命令。 下面是支持的环境变量:
COMPlus_DbgEnableMiniDump
:如果设置为 1,则终止后启用自动核心转储生成。 默认值为 0。COMPlus_DbgMiniDumpType
:这是将创建的小型转储文件的类型。 默认值为 2 (或枚举类型MiniDumpWithPrivateReadWriteMemory
)。 这意味着生成的转储文件将包括 GC 堆以及捕获进程中所有现有线程的堆栈跟踪所需的信息。COMPlus_DbgMiniDumpName
:如果已设置,则用作模板来创建转储文件路径和文件名。 可以使用参数将 PID 放入名称%d
中。 默认模板为 /tmp/coredump.%d. 使用此环境变量,可以配置输出目录。COMPlus_CreateDumpDiagnostics
:如果设置为 1,则启用创建的ump 工具诊断消息(TRACE 宏)。 如果 createdump 无法按预期工作,并且不生成内存转储文件,则此设置可能很有用。
可以在 createdump 配置策略中找到有关这些变量的详细信息。
此处的重要变量 COMPlus_DbgEnableMiniDump
。 必须将此环境变量设置为 1。 可通过多种方法设置此环境:
- 在应用程序的配置文件中设置它。
- 使用
export COMPlus_DbgEnableMiniDump=1
命令设置它。 在操作系统重启后,此设置不会保留。 因此,如果要在重启后保持启用设置,则必须将其设置为持久性。 - 在 ASP.NET 核心服务单元文件中设置它。
在 ASP.NET Core 服务单元文件中设置此变量是最简单的方法。 缺点是应重启服务。 在此故障排除部分中,这是演示的选项。
打开 buggy 应用程序的服务文件,并添加 COMPlus_DbgEnableMiniDump=1
环境变量。 这与在本培训的上一章中做了几次相同。
必须重新加载服务配置,因为配置已更改。 然后,重启 BuggyAmb 服务。
进行这些更改后,重现崩溃问题。 如果 createdump 有效,则应将转储文件写入 /tmp/ 目录作为 coredump。<PID>。 按照相同的步骤重现问题:
- 选择 “崩溃 3”。 页面正确加载,但返回一条误导性消息,表明进程应该崩溃。
- 选择“ 慢”。 这将生成“HTTP 502”响应代码(网关错误错误),而不是产品表。
- 出现问题后,任何页面都不会呈现,并且你将在 10-15 秒内收到相同的错误消息。
- 10-15 秒后,应用程序开始正常工作。
现在,应该在 /tmp 目录中有一个核心转储文件。
如果没有核心转储文件,请确保正确配置 buggyamb.service 了该文件。 还必须重新加载服务配置并重启服务。
在 lldb 中打开核心转储文件
建议将转储文件移动到文件夹 ~/dumps/ ,以遵循示例分析。 若要打开转储文件,请运行 lldb --core ~/dumps/coredump.<10354>
。 在此命令中,将 10354 占位符替换为进程的 PID。
注意
如果之前已打开转储文件并处理 lldb,则已设置符号并安装了 SOS。 可以打开相同的 .NET Core 版本转储文件,而无需再次下载符号。 但是,如果打开尚未下载符号的其他 .NET Core 版本转储文件,则必须先下载该版本的符号,然后才能开始分析。
运行 SOS clrstack
命令以显示托管调用堆栈。 请记住,使用系统生成的核心转储文件运行同一命令时,你看到错误。 这一次,应会看到正确的托管调用堆栈。
这是一个很好的开端。 但是,显示的调用堆栈属于调试进程的主线程。 它不是引发异常的线程。
注意
如果在 Windows 上的 WinDbg 中打开故障转储文件,WinDbg 将直接选择导致崩溃的线程。 但是,在 lldb 中,情况并非如此。 在 lldb 中,WinDbg 不会自动选择触发调试器以生成内存转储的线程。
尽管在调试时,此 WinDbg 行为非常有用,但 lldb 中缺少此功能并不是世界末日。 相反,可以检查所有线程,以尝试确定可以引发异常的位置。 首先,使用 thread list
命令检查本机线程。
最好先运行对所有线程调用堆栈的快速检查,以便了解在生成转储文件时正在运行的内容。 首先查看具有 thread list
该命令的本机线程列表。
注意
星号 \ 在列表 (thread #1
) 中的第一个线程附近指示它是活动线程。
本机线程检查不会透露太多。 由于这是 .NET Core 应用程序,因此通过运行 SOS clrthreads
命令检查 CLR 线程列表。 此命令列出在应用程序中运行的托管线程。
此屏幕截图未显示所有托管线程。 但是,应关注的详细信息列在屏幕截图中。 Thread #15
系统日志中出现了一个异常。
检查该线程的调用堆栈。 为此,必须先选择有问题的线程。 在将要运行的内存转储分析中,线程数很可能不同。 若要选择另一个线程作为活动线程,请使用 thread select
该命令,并传递 lldb dbg 线程 ID。 例如,运行 thread select 15
以切换到线程 15。 然后,运行的每个后续命令都将在该线程的上下文中。 若要查看本机调用堆栈,请运行 bt
(回溯)命令。
如此屏幕截图所示,此线程肯定是触发崩溃的线程。
PROCEndProcess
并在PROCAbort()
未经处理的异常后调用。POCCreateCrashDump
告诉我们.NET Core 编写了故障转储。
可以通过运行 clrstack
命令来检查托管调用堆栈。 然而,这不会透露太多。 pe
运行命令以获取异常详细信息。
此信息指示:A System.Net.HttpWebRequest
在方法的 Crash3 页中 LogTheRequest()
触发。 这是帮助解决问题的重要信息。 但是,如果想要查找 HTTP 请求的 URL,该怎么办? 若要继续操作,请尝试检查堆栈上引用的对象,以查看是否可以从此列表中收集更多信息。 若要显示当前堆栈边界内找到的所有托管对象,请运行 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
以查找对象的地址。
现在,你能够识别有问题的对象的地址。 在此示例中,它是 00007f51300c0868
。 可以通过将该地址 dumpobj
传递给命令来调查对象的属性。 这将列出该对象的属性。 在此示例中,运行 dumpobj 00007f51300c0868
以检查对象的属性。
注意
正在调查对象 System.Net.HttpWebRequest
,其属性之一是 _requestUri
。 这是类型的 System.Uri
对象。 想要确定 URI。 因此,将属性的 _requestUri
地址传递给 dumpobj
命令
复制对象的 System.Uri
地址,然后再次使用它 dumpobj
进行调查。 运行 dumpobj 00007f51300bfbb8
。 生成的内存转储文件中对象的地址肯定会有所不同。 该列表将显示 _string
. System.Uri
的属性。
复制其 _string
地址,然后再次对其运行 dumpobj
命令。 运行 dumpobj 00007f51300bfb40
。 结果在以下屏幕截图中列出。
最后,可以找到 HttpWebRequest 的 URL: http://buggyamb/Problem/Api/NotExistingLoggingApi
。 顾名思义,这可能不是应用程序中的现有页面。
最后,关于坠机发生方式的理论如下:
- HttpWebRequest 是在 Crash3 网页中方法中
LogTheRequest()
对非现有 URL 进行的。 - 在实际应用程序中,解决此问题的解决方案是处理错误。
HttpWebRequest
但是,在这种情况下,解决方案要简单得多:不要向非现有页面发出HttpWebRequest
请求。
此时,你可能应该对导致崩溃的原因有更多的问题。 例如,选择“慢速”链接后,为何触发了崩溃?
随时可以继续调查。 下一步建议的步骤是使用HttpWebRequest
对象地址来运行gcroot
命令,以找出其根位置。 这可以帮助你制定崩溃发生方式的图片。
这结束了实验室。 按 Ctrl+C 或使用 q
命令退出 lldb 调试器。