0

I have taken the memory dump of a running process (Task manager, right-click, "Create dump file", and now I'm investigating it using Windbg.

!Dumpheap -stat has revealed an enormous amount of objects, which seem to be collections of 14 entries: the end of the !Dumpheap -stat looks as follows (the first two columns contain hyperlinks):

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

Clicking on such an object reveals the following:

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

Clicking on "Print Array" gives this:

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

I would like to have more information, more especially what kind of collections those things are. In order to do that, I have thought of loading the debugging symbols and showing the module name.

  • Showing the module name : how do I do that? How can I see to which module an object belongs?

  • loading debugging symbols: how do I do that? I have put some "*.pdb" files in a directory and have filled in "File", "Symbol File Path" as C:\Temp_Folder\ and C:\Temp_Folder\* but in both cases, the modules stay deferred:

      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)
    

I hope that, managing to configure the debugging symbol path, might reveal a lot of information.

Does anybody know how to do this?

Thanks in advance

Dominique
  • 16,450
  • 15
  • 56
  • 112
  • 1
    "how to retrieve the module an object belongs to?" - IMHO such information is not stored. It would be too much overhead to reference a module for every little object. – Thomas Weller Jun 14 '23 at 14:17
  • 1
    There is a feature "Heap tagging by DLL", but that is for native Heaps (everything which uses the Windows Heap Manager), which .NET doesn't. And that's also not per object but per Heap. – Thomas Weller Jun 14 '23 at 14:19

1 Answers1

0

I have taken the memory dump of a running process [...]

To give better directions, it would be important to know why you created the memory dump. Just for fun, for learning or for doing a real analysis? If it's a real analysis, what kind of analysis? Do you want to find out about high memory usage, a hang, a performance issue (CPU spike) or does your application crash?

[...] (Task manager, right-click, "Create dump file", and now I'm investigating it using Windbg.

You didn't report any problems with your dump, but be aware that Task Manager is not necessarily the best choice. There are two versions of Task Manager, the 32 bit version and the 64 bit version and each of them takes the memory dump in that bitness. Check out other options for creating a crash dump, which might be better suitable, depending on the why question.

!Dumpheap -stat has revealed an enormous amount of objects, [...]

That's normal. A simple Hello World style WinForms application with an empty window has more than 5000 objects, when you might be thinking you only have 1 form.

When you say "enormous", that's hard to put into context. It's better to be precise when it comes to debugging. Say "!Dumpheap lists 123.000.468 objects".

the end of the !Dumpheap -stat looks as follows

No, sorry. Either something is ultimately broken in your app or this is simply not the output of !dumpheap -stat. !dumpheap -stat has 4 columns:

  1. Type information (MT = Method Table)
  2. Object count
  3. Total size of all objects of that type
  4. Name of the object

What you posted might be a part of the output of !dumpheap (without -stat). And that's typically not very useful, without prior knowledge of what one's looking for.

[...] what kind of collections those things are

Well, a System.Object[] is an array of type System.Object, typically declared in C# as object[]. There's nothing more to say about this collection.

Other collections may be System.Windows.Forms.Control[] or 2D arrays like System.Int32[][] or System.Collections.Generic.List'1[[System.Windows.Forms.Application+ParkingWindow, System.Windows.Forms]] which is a generic List<ParkingWindow> in C# code.

I have thought of loading the debugging symbols

That's a very good idea. Without symbols, debugging is not worth it.

Showing the module name : how do I do that?

You can show modules with lm. lmv gives you version number and date information. lmf gives you the full path. You can filter for a specific module by appending m modulename. to any of the commands before.

How can I see to which module an object belongs?

That's not possible. Why? I have no reference, so let's make an educated guess.

a) tracking the DLL of every small object would add at least a pointer size overhead. That might be 8 bytes for a 4 byte int. b) which DLL would you track? There are always several DLLs on the call stack. One module calls the other, which calls the next. Finally, you might always end up somewhere in a Microsoft DLL, because that's where strings, ints and a lot of stuff comes from. You don't want that. On the other extreme, the executable is the main reason why memory is allocated in the first place. So, it wouldn't exactly be simple to figure out which assemblies to track and which assemblies you don't want in that list. c) Looking at the call stack is quite an expensive operation. That's why throwing a lot of exceptions makes your application slow.

All in all, I'd say it's not feasible to record a module to each object.

The closest I can think of that has been implemented is Heap Tagging by DLL but that works with the Windows Heap Manager, which .NET doesn't use. Also, you just know which heap belongs to which DLL, not the objects.

loading debugging symbols: how do I do that?

Two steps should usually be sufficient:

  1. Use Microsoft symbols: .symfix
  2. Add your symbols: .sympath+ C:\some\directory

For more details, see How to set up symbols, which explains more details.

some "*.pdb" files

Not some. Use all.

C:\Temp_Folder\*

You don't need to use wildcards. Just specify the directory.

the modules stay deferred

That's wanted. Loading symbols is slow. They will be loaded when needed. If you want to load them now, use ld * and get yourself a coffee while waiting.

might reveal a lot of information.

Sure. With debugging, you can get tons of information. You can have a look at everything. However, it's always good to know what you're looking for, when you have a haystack in front of you. That's still unclear to me. Why are you debugging? What are you looking for?

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
  • Thanks for your quick and elaborate answer. As for the "why" question: I'm now working for a company, programming in C#. We have an application, running on a customer's system, and sometimes we see that memory usage and CPU usage increase. We would like to know why. I've tried working with the performance profiler, but this only seems to work on local processes. I prefer not measuring the performance because the CPU and memory usage increase is unpredictable. Therefore it's the idea to watch that process and take a dump when the increase is happening. – Dominique Jun 15 '23 at 06:54
  • Okay. Then have a look at ProcDump. IIRC, you can configure a memory limit when it shall take a memory dump. Please note that .NET is a GC'd language. One consequence is that you have no control over memory. If the memory spikes and that spike goes away later, that's totally normal and expected. – Thomas Weller Jun 15 '23 at 06:57
  • 1
    There is a [1100 page book about .NET GC alone](https://www.amazon.de/dp/148424026X/). This is a complex topic and the reasons why .NET performs a GC or sometimes does not perform a GC are ... well, hard. IMHO: monitor the memory for a longer period of time and see if you can find an ever increasing average. That might be a log, a cache or a memory leak. That's worth figuring out. A single spike IMHO is not worth it. But that's not me to decide. – Thomas Weller Jun 15 '23 at 07:01
  • 1
    Next, get better tools. Looking at objects in WinDbg is cumbersome. You type a command for every single object. What you need in such a case is something like [dotMemory](https://www.jetbrains.com/dotmemory/) where you can load a memory snapshot, filter objects and see the dependencies between objects. – Thomas Weller Jun 15 '23 at 07:02
  • 1
    You can even [import a DMP file into dotMemory](https://www.jetbrains.com/help/dotmemory/Importing_Process_Dumps.html), it seems. – Thomas Weller Jun 15 '23 at 07:04
  • Next: customers often measure the wrong memory. They often observe memory increases in Task Manager. And Task Manager is the wrong tool in >90% of the cases. Task Manager shows the Working Set size. That's irrelevant, because that's about physical RAM. In >90% you want to look at Private Bytes, which is virtual memory. A spike in Working Set size has nothing to do with .NET or GC. It's just that Windows thinks it has enough physical RAM to give to your application. – Thomas Weller Jun 15 '23 at 07:08