Windbg C# dump 分析:如何找回对象所属的模块?

Windbg C# dump analysis: how to retrieve the module an object belongs to?

提问人:Dominique 提问时间:6/14/2023 更新时间:6/15/2023 访问量:245

问:

我已经获取了正在运行的进程的内存转储(任务管理器,右键单击“创建转储文件”),现在我正在使用 Windbg 对其进行调查。

!Dumpheap -stat揭示了大量的对象,这些对象似乎是 14 个条目的集合: 结尾看起来如下(前两列包含超链接):!Dumpheap -stat

3f62cc58 70b7d878       68     
3f62cc9c 70b7d878       68     
3f62cce0 70b7d878       68     
3f62cd24 70b7d878       68     
3f62cd68 70b7d878       68     

单击此类对象将显示以下内容:

0:000> !DumpObj /d 3f62ebb0
Name:        System.Object[]
MethodTable: 70b7d878
EEClass:     70754b80
Size:        68(0x44) bytes
Array:       Rank 1, Number of elements 14, Type CLASS (Print Array)
Fields:
None

单击“打印数组”会得到这个:

0:000> !DumpArray /d 3f62ebb0
Name:        System.Object[]
MethodTable: 70b7d878
EEClass:     70754b80
Size:        68(0x44) bytes
Array:       Rank 1, Number of elements 14, Type CLASS
Element Methodtable: 70b7d824
[0] null
[1] 38f2cfd8
[2] null
[3] null
[4] null
[5] null
[6] null
[7] null
[8] null
[9] null
[10] null
[11] null
[12] null
[13] null

我想得到更多的信息,尤其是这些东西是什么样的收藏品。为此,我想加载调试符号并显示模块名称。

  • 显示模块名称:我该怎么做?如何查看对象属于哪个模块?

  • 加载调试符号:我该怎么做?我已经将一些“*.pdb”文件放在一个目录中,并填写了“文件”,“符号文件路径”,但在这两种情况下,模块都保持延迟:C:\Temp_Folder\C:\Temp_Folder\*

      0:000> lm
      start    end        module name
      00480000 004b8000   application_being_debugged   (deferred)             
      1ce60000 1ce63000   security   (deferred)             
      1d900000 1d91b000   opccomn_ps   (deferred)             
      1d930000 1d956000   opcproxy   (deferred)
    

我希望,设法配置调试符号路径,可能会透露很多信息。

有谁知道怎么做?

提前致谢

调试 模块 windbg debug-symbols sos

评论

1赞 Thomas Weller 6/14/2023
“如何检索对象所属的模块?” - 恕我直言,不会存储此类信息。为每个小对象引用一个模块的开销太大了。
1赞 Thomas Weller 6/14/2023
有一个功能“通过DLL进行堆标记”,但这是针对本机堆(使用Windows堆管理器的所有内容),而.NET则没有。这也不是每个对象,而是每个堆。

答:

0赞 Thomas Weller 6/15/2023 #1

我已经获取了正在运行的进程的内存转储 [...]

为了提供更好的指导,了解创建内存转储的原因非常重要。只是为了好玩,为了学习还是为了做真正的分析?如果是真正的分析,什么样的分析?您想了解高内存使用率、挂起、性能问题(CPU 峰值)还是您的应用程序是否崩溃?

[...](任务管理器,右键单击“创建转储文件”,现在我正在使用 Windbg 对其进行调查。

你没有报告转储的任何问题,但请注意,任务管理器不一定是最佳选择。任务管理器有两个版本,32 位版本和 64 位版本,每个版本都采用该位数的内存转储。查看用于创建故障转储的其他选项,这些选项可能更合适,具体取决于“原因”问题。

!Dumpheap -stat揭示了大量的物体,[...]

这很正常。一个带有空窗口的简单 Hello World 样式的 WinForms 应用程序具有 5000 多个对象,而您可能认为您只有 1 个窗体。

当你说“巨大”时,很难把它放在上下文中。在调试方面,最好是精确的。说“!Dumpheap 列出了 123.000.468 个对象”。

结尾的样子如下!Dumpheap -stat

不,对不起。要么您的应用程序中的某些内容最终被破坏了,要么这根本不是 的输出。 有 4 列:!dumpheap -stat!dumpheap -stat

  1. 类型信息(MT = 方法表)
  2. 对象计数
  3. 该类型的所有对象的总大小
  4. 对象的名称

您发布的内容可能是 (without ) 输出的一部分。这通常不是很有用,因为事先不知道自己在寻找什么。!dumpheap-stat

[...]这些东西是什么样的收藏品

嗯,a 是一个类型的数组,通常在 C# 中声明为 .关于这个系列,没什么好说的了。System.Object[]System.Objectobject[]

其他集合可能是 或 2D 数组,例如 或 C# 代码中的泛型。System.Windows.Forms.Control[]System.Int32[][]System.Collections.Generic.List'1[[System.Windows.Forms.Application+ParkingWindow, System.Windows.Forms]]List<ParkingWindow>

我想过加载调试符号

这是个好主意。没有符号,调试是不值得的。

显示模块名称:我该怎么做?

您可以使用 显示模块。 为您提供版本号和日期信息。 为您提供完整的路径。您可以通过附加 .添加到之前的任何命令。lmlmvlmf m modulename

如何查看对象属于哪个模块?

那是不可能的。为什么?我没有参考,所以让我们做一个有根据的猜测。

a) 跟踪每个小对象的 DLL 至少会增加指针大小开销。对于 8 字节,这可能是 4 个字节。 b) 你会跟踪哪个DLL?调用堆栈上始终有多个 DLL。一个模块调用另一个模块,另一个模块调用下一个模块。最后,你可能总是在Microsoft DLL的某个地方结束,因为这是字符串,整数和很多东西的来源。你不想要那样。在另一个极端,可执行文件是首先分配内存的主要原因。因此,要确定要跟踪哪些程序集以及不希望在该列表中显示哪些程序集并不简单。 c) 查看调用堆栈是一项非常昂贵的操作。这就是为什么抛出大量异常会使您的应用程序变慢的原因。int

总而言之,我想说的是,将一个模块记录到每个对象是不可行的。

我能想到的最接近的是 DLL 的堆标记,但它适用于 Windows 堆管理器,而 .NET 不使用。此外,您只知道哪个堆属于哪个 DLL,而不是对象。

加载调试符号:我该怎么做?

通常只需两个步骤即可:

  1. 使用 Microsoft 符号:.symfix
  2. 添加符号:.sympath+ C:\some\directory

有关详细信息,请参阅如何设置符号,其中详细介绍了更多详细信息。

一些“*.pdb”文件

不是一些。全部使用。

C:\Temp_Folder\*

无需使用通配符。只需指定目录即可。

模块保持延迟

这是想要的。加载符号的速度很慢。它们将在需要时加载。如果您现在想加载它们,请在等待时使用并给自己喝杯咖啡。ld *

可能会透露很多信息。

确定。通过调试,您可以获得大量信息。你可以看看一切。然而,当你面前有大海捞针时,知道你在寻找什么总是好的。我仍然不清楚。为什么要调试?你在找什么?

评论

0赞 Dominique 6/15/2023
感谢您快速而详尽的回答。至于“为什么”的问题:我现在在一家公司工作,用C#编程。我们有一个应用程序,在客户的系统上运行,有时我们看到内存使用率和 CPU 使用率增加。我们想知道为什么。我尝试使用性能分析器,但这似乎仅适用于本地进程。我宁愿不测量性能,因为 CPU 和内存使用率的增加是不可预测的。因此,我们的想法是观察该过程,并在增加时进行转储。
0赞 Thomas Weller 6/15/2023
好。然后看看 ProcDump。IIRC,您可以配置何时进行内存转储的内存限制。请注意,.NET 是一种 GC 语言。一个后果是你无法控制内存。如果内存峰值并且该峰值稍后消失,这是完全正常和预期的。
1赞 Thomas Weller 6/15/2023
有一本 1100 页的书是关于 .NET GC 的。这是一个复杂的主题,.NET 执行 GC 或有时不执行 GC 的原因是......嗯,很难。恕我直言:长时间监控内存,看看你是否能找到一个不断增加的平均值。这可能是日志、缓存或内存泄漏。这值得弄清楚。恕我直言,一个尖峰是不值得的。但这不是我能决定的。
1赞 Thomas Weller 6/15/2023
接下来,获得更好的工具。在 WinDbg 中查看对象很麻烦。为每个对象键入一个命令。在这种情况下,您需要的是类似 dotMemory 的东西,您可以在其中加载内存快照、筛选对象并查看对象之间的依赖关系。
1赞 Thomas Weller 6/15/2023
您甚至可以将 DMP 文件导入 dotMemory