1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5// ==++==
6//
7
8//
9// ==--==
10#include <assert.h>
11#include "sos.h"
12#include "safemath.h"
13
14
15// This is the increment for the segment lookup data
16const int nSegLookupStgIncrement = 100;
17
18#define CCH_STRING_PREFIX_SUMMARY 64
19
20/**********************************************************************\
21* Routine Description: *
22* *
23* This function is called to update GC heap statistics. *
24* *
25\**********************************************************************/
26void HeapStat::Add(DWORD_PTR aData, DWORD aSize)
27{
28 if (head == 0)
29 {
30 head = new Node();
31 if (head == NULL)
32 {
33 ReportOOM();
34 ControlC = TRUE;
35 return;
36 }
37
38 if (bHasStrings)
39 {
40 size_t capacity_pNew = _wcslen((WCHAR*)aData) + 1;
41 WCHAR *pNew = new WCHAR[capacity_pNew];
42 if (pNew == NULL)
43 {
44 ReportOOM();
45 ControlC = TRUE;
46 return;
47 }
48 wcscpy_s(pNew, capacity_pNew, (WCHAR*)aData);
49 aData = (DWORD_PTR)pNew;
50 }
51
52 head->data = aData;
53 }
54 Node *walk = head;
55 int cmp = 0;
56
57 for (;;)
58 {
59 if (IsInterrupt())
60 return;
61
62 cmp = CompareData(aData, walk->data);
63
64 if (cmp == 0)
65 break;
66
67 if (cmp < 0)
68 {
69 if (walk->left == NULL)
70 break;
71 walk = walk->left;
72 }
73 else
74 {
75 if (walk->right == NULL)
76 break;
77 walk = walk->right;
78 }
79 }
80
81 if (cmp == 0)
82 {
83 walk->count ++;
84 walk->totalSize += aSize;
85 }
86 else
87 {
88 Node *node = new Node();
89 if (node == NULL)
90 {
91 ReportOOM();
92 ControlC = TRUE;
93 return;
94 }
95
96 if (bHasStrings)
97 {
98 size_t capacity_pNew = _wcslen((WCHAR*)aData) + 1;
99 WCHAR *pNew = new WCHAR[capacity_pNew];
100 if (pNew == NULL)
101 {
102 ReportOOM();
103 ControlC = TRUE;
104 return;
105 }
106 wcscpy_s(pNew, capacity_pNew, (WCHAR*)aData);
107 aData = (DWORD_PTR)pNew;
108 }
109
110 node->data = aData;
111 node->totalSize = aSize;
112 node->count ++;
113
114 if (cmp < 0)
115 {
116 walk->left = node;
117 }
118 else
119 {
120 walk->right = node;
121 }
122 }
123}
124/**********************************************************************\
125* Routine Description: *
126* *
127* This function compares two nodes in the tree. *
128* *
129\**********************************************************************/
130int HeapStat::CompareData(DWORD_PTR d1, DWORD_PTR d2)
131{
132 if (bHasStrings)
133 return _wcscmp((WCHAR*)d1, (WCHAR*)d2);
134
135 if (d1 > d2)
136 return 1;
137
138 if (d1 < d2)
139 return -1;
140
141 return 0;
142}
143
144/**********************************************************************\
145* Routine Description: *
146* *
147* This function is called to sort all entries in the heap stat. *
148* *
149\**********************************************************************/
150void HeapStat::Sort ()
151{
152 Node *root = head;
153 head = NULL;
154 ReverseLeftMost (root);
155
156 Node *sortRoot = NULL;
157 while (head)
158 {
159 Node *tmp = head;
160 head = head->left;
161 if (tmp->right)
162 ReverseLeftMost (tmp->right);
163 // add tmp
164 tmp->right = NULL;
165 tmp->left = NULL;
166 SortAdd (sortRoot, tmp);
167 }
168 head = sortRoot;
169
170 Linearize();
171
172 //reverse the order
173 root = head;
174 head = NULL;
175 sortRoot = NULL;
176 while (root)
177 {
178 Node *tmp = root->right;
179 root->left = NULL;
180 root->right = NULL;
181 LinearAdd (sortRoot, root);
182 root = tmp;
183 }
184 head = sortRoot;
185}
186
187void HeapStat::Linearize()
188{
189 // Change binary tree to a linear tree
190 Node *root = head;
191 head = NULL;
192 ReverseLeftMost (root);
193 Node *sortRoot = NULL;
194 while (head)
195 {
196 Node *tmp = head;
197 head = head->left;
198 if (tmp->right)
199 ReverseLeftMost (tmp->right);
200 // add tmp
201 tmp->right = NULL;
202 tmp->left = NULL;
203 LinearAdd (sortRoot, tmp);
204 }
205 head = sortRoot;
206 fLinear = TRUE;
207}
208
209void HeapStat::ReverseLeftMost (Node *root)
210{
211 while (root)
212 {
213 Node *tmp = root->left;
214 root->left = head;
215 head = root;
216 root = tmp;
217 }
218}
219
220/**********************************************************************\
221* Routine Description: *
222* *
223* This function is called to help to sort heap stat. *
224* *
225\**********************************************************************/
226void HeapStat::SortAdd (Node *&root, Node *entry)
227{
228 if (root == NULL)
229 {
230 root = entry;
231 }
232 else
233 {
234 Node *parent = root;
235 Node *ptr = root;
236 while (ptr)
237 {
238 parent = ptr;
239 if (ptr->totalSize < entry->totalSize)
240 ptr = ptr->right;
241 else
242 ptr = ptr->left;
243 }
244 if (parent->totalSize < entry->totalSize)
245 parent->right = entry;
246 else
247 parent->left = entry;
248 }
249}
250
251void HeapStat::LinearAdd(Node *&root, Node *entry)
252{
253 if (root == NULL)
254 {
255 root = entry;
256 }
257 else
258 {
259 entry->right = root;
260 root = entry;
261 }
262}
263
264/**********************************************************************\
265* Routine Description: *
266* *
267* This function is called to print GC heap statistics. *
268* *
269\**********************************************************************/
270void HeapStat::Print(const char* label /* = NULL */)
271{
272 if (label == NULL)
273 {
274 label = "Statistics:\n";
275 }
276 ExtOut(label);
277 if (bHasStrings)
278 ExtOut("%8s %12s %s\n", "Count", "TotalSize", "String Value");
279 else
280 ExtOut("%" POINTERSIZE "s %8s %12s %s\n","MT", "Count", "TotalSize", "Class Name");
281
282 Node *root = head;
283 int ncount = 0;
284 while (root)
285 {
286 if (IsInterrupt())
287 return;
288
289 ncount += root->count;
290
291 if (bHasStrings)
292 {
293 ExtOut("%8d %12I64u \"%S\"\n", root->count, (unsigned __int64)root->totalSize, root->data);
294 }
295 else
296 {
297 DMLOut("%s %8d %12I64u ", DMLDumpHeapMT(root->data), root->count, (unsigned __int64)root->totalSize);
298 if (IsMTForFreeObj(root->data))
299 {
300 ExtOut("%9s\n", "Free");
301 }
302 else
303 {
304 wcscpy_s(g_mdName, mdNameLen, W("UNKNOWN"));
305 NameForMT_s((DWORD_PTR) root->data, g_mdName, mdNameLen);
306 ExtOut("%S\n", g_mdName);
307 }
308 }
309 root = root->right;
310
311 }
312 ExtOut ("Total %d objects\n", ncount);
313}
314
315void HeapStat::Delete()
316{
317 if (head == NULL)
318 return;
319
320 // Ensure the data structure is already linearized.
321 if (!fLinear)
322 Linearize();
323
324 while (head)
325 {
326 // The list is linearized on such that the left node is always null.
327 Node *tmp = head;
328 head = head->right;
329
330 if (bHasStrings)
331 delete[] ((WCHAR*)tmp->data);
332 delete tmp;
333 }
334
335 // return to default state
336 bHasStrings = FALSE;
337 fLinear = FALSE;
338}
339
340// -----------------------------------------------------------------------
341//
342// MethodTableCache implementation
343//
344// Used during heap traversals for quick object size computation
345//
346MethodTableInfo* MethodTableCache::Lookup (DWORD_PTR aData)
347{
348 Node** addHere = &head;
349 if (head != 0) {
350 Node *walk = head;
351 int cmp = 0;
352
353 for (;;)
354 {
355 cmp = CompareData(aData, walk->data);
356
357 if (cmp == 0)
358 return &walk->info;
359
360 if (cmp < 0)
361 {
362 if (walk->left == NULL)
363 {
364 addHere = &walk->left;
365 break;
366 }
367 walk = walk->left;
368 }
369 else
370 {
371 if (walk->right == NULL)
372 {
373 addHere = &walk->right;
374 break;
375 }
376 walk = walk->right;
377 }
378 }
379 }
380 Node* newNode = new Node(aData);
381 if (newNode == NULL)
382 {
383 ReportOOM();
384 return NULL;
385 }
386 *addHere = newNode;
387 return &newNode->info;
388}
389
390/**********************************************************************\
391* Routine Description: *
392* *
393* This function compares two nodes in the tree. *
394* *
395\**********************************************************************/
396int MethodTableCache::CompareData(DWORD_PTR d1, DWORD_PTR d2)
397{
398 if (d1 > d2)
399 return 1;
400
401 if (d1 < d2)
402 return -1;
403
404 return 0;
405}
406
407void MethodTableCache::ReverseLeftMost (Node *root)
408{
409 if (root)
410 {
411 if (root->left) ReverseLeftMost(root->left);
412 if (root->right) ReverseLeftMost(root->right);
413 delete root;
414 }
415}
416
417void MethodTableCache::Clear()
418{
419 Node *root = head;
420 head = NULL;
421 ReverseLeftMost (root);
422}
423
424MethodTableCache g_special_mtCache;
425
426size_t Align (size_t nbytes)
427{
428 return (nbytes + ALIGNCONST) & ~ALIGNCONST;
429}
430
431size_t AlignLarge(size_t nbytes)
432{
433 return (nbytes + ALIGNCONSTLARGE) & ~ALIGNCONSTLARGE;
434}
435
436/**********************************************************************\
437* Routine Description: *
438* *
439* Print the gc heap info. *
440* *
441\**********************************************************************/
442void GCPrintGenerationInfo(const DacpGcHeapDetails &heap)
443{
444 UINT n;
445 for (n = 0; n <= GetMaxGeneration(); n ++)
446 {
447 if (IsInterrupt())
448 return;
449 ExtOut("generation %d starts at 0x%p\n",
450 n, SOS_PTR(heap.generation_table[n].allocation_start));
451 }
452
453 // We also need to look at the gen0 alloc context.
454 ExtOut("ephemeral segment allocation context: ");
455 if (heap.generation_table[0].allocContextPtr)
456 {
457 ExtOut("(0x%p, 0x%p)\n",
458 SOS_PTR(heap.generation_table[0].allocContextPtr),
459 SOS_PTR(heap.generation_table[0].allocContextLimit + Align(min_obj_size)));
460 }
461 else
462 {
463 ExtOut("none\n");
464 }
465}
466
467
468void GCPrintSegmentInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size)
469{
470 DWORD_PTR dwAddrSeg;
471 DacpHeapSegmentData segment;
472
473 dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment;
474 total_size = 0;
475 // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
476 while (dwAddrSeg != (DWORD_PTR)heap.generation_table[0].start_segment)
477 {
478 if (IsInterrupt())
479 return;
480 if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK)
481 {
482 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
483 return;
484 }
485 ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg),
486 SOS_PTR(segment.mem), SOS_PTR(segment.allocated),
487 (ULONG_PTR)(segment.allocated - segment.mem),
488 (ULONG_PTR)(segment.allocated - segment.mem));
489 total_size += (DWORD_PTR) (segment.allocated - segment.mem);
490 dwAddrSeg = (DWORD_PTR)segment.next;
491 }
492
493 if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK)
494 {
495 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
496 return;
497 }
498
499 DWORD_PTR end = (DWORD_PTR)heap.alloc_allocated;
500 ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg),
501 SOS_PTR(segment.mem), SOS_PTR(end),
502 (ULONG_PTR)(end - (DWORD_PTR)segment.mem),
503 (ULONG_PTR)(end - (DWORD_PTR)segment.mem));
504
505 total_size += end - (DWORD_PTR)segment.mem;
506
507}
508
509
510void GCPrintLargeHeapSegmentInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size)
511{
512 DWORD_PTR dwAddrSeg;
513 DacpHeapSegmentData segment;
514 dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment;
515
516 // total_size = 0;
517 // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
518 while (dwAddrSeg != NULL)
519 {
520 if (IsInterrupt())
521 return;
522 if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK)
523 {
524 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
525 return;
526 }
527 ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg),
528 SOS_PTR(segment.mem), SOS_PTR(segment.allocated),
529 (ULONG_PTR)(segment.allocated - segment.mem),
530 segment.allocated - segment.mem);
531 total_size += (DWORD_PTR) (segment.allocated - segment.mem);
532 dwAddrSeg = (DWORD_PTR)segment.next;
533 }
534}
535
536void GCHeapInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size)
537{
538 GCPrintGenerationInfo(heap);
539 ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", "segment", "begin", "allocated", "size");
540 GCPrintSegmentInfo(heap, total_size);
541 ExtOut("Large object heap starts at 0x%p\n",
542 SOS_PTR(heap.generation_table[GetMaxGeneration()+1].allocation_start));
543 ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", "segment", "begin", "allocated", "size");
544 GCPrintLargeHeapSegmentInfo(heap,total_size);
545}
546
547BOOL GCObjInGeneration(TADDR taddrObj, const DacpGcHeapDetails &heap,
548 const TADDR_SEGINFO& /*seg*/, int& gen, TADDR_RANGE& allocCtx)
549{
550 gen = -1;
551 for (UINT n = 0; n <= GetMaxGeneration(); n ++)
552 {
553 if (taddrObj >= TO_TADDR(heap.generation_table[n].allocation_start))
554 {
555 gen = n;
556 break;
557 }
558 }
559
560 // We also need to look at the gen0 alloc context.
561 if (heap.generation_table[0].allocContextPtr
562 && taddrObj >= TO_TADDR(heap.generation_table[0].allocContextPtr)
563 && taddrObj < TO_TADDR(heap.generation_table[0].allocContextLimit) + Align(min_obj_size))
564 {
565 gen = 0;
566 allocCtx.start = (TADDR)heap.generation_table[0].allocContextPtr;
567 allocCtx.end = (TADDR)heap.generation_table[0].allocContextLimit;
568 }
569 else
570 {
571 allocCtx.start = allocCtx.end = 0;
572 }
573 return (gen != -1);
574}
575
576
577BOOL GCObjInSegment(TADDR taddrObj, const DacpGcHeapDetails &heap,
578 TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx)
579{
580 TADDR taddrSeg;
581 DacpHeapSegmentData dacpSeg;
582
583 taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment;
584 // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
585 while (taddrSeg != (TADDR)heap.generation_table[0].start_segment)
586 {
587 if (IsInterrupt())
588 return FALSE;
589 if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
590 {
591 ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
592 return FALSE;
593 }
594 if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(dacpSeg.allocated))
595 {
596 rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
597 rngSeg.start = (TADDR)dacpSeg.mem;
598 rngSeg.end = (TADDR)dacpSeg.allocated;
599 gen = 2;
600 allocCtx.start = allocCtx.end = 0;
601 return TRUE;
602 }
603 taddrSeg = (TADDR)dacpSeg.next;
604 }
605
606 // the ephemeral segment
607 if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
608 {
609 ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
610 return FALSE;
611 }
612
613 if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(heap.alloc_allocated))
614 {
615 if (GCObjInGeneration(taddrObj, heap, rngSeg, gen, allocCtx))
616 {
617 rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
618 rngSeg.start = (TADDR)dacpSeg.mem;
619 rngSeg.end = (TADDR)heap.alloc_allocated;
620 return TRUE;
621 }
622 }
623
624 return FALSE;
625}
626
627BOOL GCObjInLargeSegment(TADDR taddrObj, const DacpGcHeapDetails &heap, TADDR_SEGINFO& rngSeg)
628{
629 TADDR taddrSeg;
630 DacpHeapSegmentData dacpSeg;
631 taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()+1].start_segment;
632
633 // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
634 while (taddrSeg != NULL)
635 {
636 if (IsInterrupt())
637 return FALSE;
638 if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
639 {
640 ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
641 return FALSE;
642 }
643 if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj && taddrObj < TO_TADDR(dacpSeg.allocated))
644 {
645 rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
646 rngSeg.start = (TADDR)dacpSeg.mem;
647 rngSeg.end = (TADDR)dacpSeg.allocated;
648 return TRUE;
649 }
650 taddrSeg = (TADDR)dacpSeg.next;
651 }
652 return FALSE;
653}
654
655BOOL GCObjInHeap(TADDR taddrObj, const DacpGcHeapDetails &heap,
656 TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx, BOOL &bLarge)
657{
658 if (GCObjInSegment(taddrObj, heap, rngSeg, gen, allocCtx))
659 {
660 bLarge = FALSE;
661 return TRUE;
662 }
663 if (GCObjInLargeSegment(taddrObj, heap, rngSeg))
664 {
665 bLarge = TRUE;
666 gen = GetMaxGeneration()+1;
667 allocCtx.start = allocCtx.end = 0;
668 return TRUE;
669 }
670 return FALSE;
671}
672
673#ifndef FEATURE_PAL
674// this function updates genUsage to reflect statistics from the range defined by [start, end)
675void GCGenUsageStats(TADDR start, TADDR end, const std::unordered_set<TADDR> &liveObjs,
676 const DacpGcHeapDetails &heap, BOOL bLarge, const AllocInfo *pAllocInfo, GenUsageStat *genUsage)
677{
678 // if this is an empty segment or generation return
679 if (start >= end)
680 {
681 return;
682 }
683
684 // otherwise it should start with a valid object
685 _ASSERTE(sos::IsObject(start));
686
687 // update the "allocd" field
688 genUsage->allocd += end - start;
689
690 size_t objSize = 0;
691 for (TADDR taddrObj = start; taddrObj < end; taddrObj += objSize)
692 {
693 TADDR taddrMT;
694
695 move_xp(taddrMT, taddrObj);
696 taddrMT &= ~3;
697
698 // skip allocation contexts
699 if (!bLarge)
700 {
701 // Is this the beginning of an allocation context?
702 int i;
703 for (i = 0; i < pAllocInfo->num; i ++)
704 {
705 if (taddrObj == (TADDR)pAllocInfo->array[i].alloc_ptr)
706 {
707 ExtDbgOut("Skipping allocation context: [%#p-%#p)\n",
708 SOS_PTR(pAllocInfo->array[i].alloc_ptr), SOS_PTR(pAllocInfo->array[i].alloc_limit));
709 taddrObj =
710 (TADDR)pAllocInfo->array[i].alloc_limit + Align(min_obj_size);
711 break;
712 }
713 }
714 if (i < pAllocInfo->num)
715 {
716 // we already adjusted taddrObj, so reset objSize
717 objSize = 0;
718 continue;
719 }
720
721 // We also need to look at the gen0 alloc context.
722 if (taddrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr)
723 {
724 taddrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size);
725 // we already adjusted taddrObj, so reset objSize
726 objSize = 0;
727 continue;
728 }
729
730 // Are we at the end of gen 0?
731 if (taddrObj == end - Align(min_obj_size))
732 {
733 objSize = 0;
734 break;
735 }
736 }
737
738 BOOL bContainsPointers;
739 BOOL bMTOk = GetSizeEfficient(taddrObj, taddrMT, bLarge, objSize, bContainsPointers);
740 if (!bMTOk)
741 {
742 ExtErr("bad object: %#p - bad MT %#p\n", SOS_PTR(taddrObj), SOS_PTR(taddrMT));
743 // set objSize to size_t to look for the next valid MT
744 objSize = sizeof(TADDR);
745 continue;
746 }
747
748 // at this point we should have a valid objSize, and there whould be no
749 // integer overflow when moving on to next object in heap
750 _ASSERTE(objSize > 0 && taddrObj < taddrObj + objSize);
751 if (objSize == 0 || taddrObj > taddrObj + objSize)
752 {
753 break;
754 }
755
756 if (IsMTForFreeObj(taddrMT))
757 {
758 genUsage->freed += objSize;
759 }
760 else if (!(liveObjs.empty()) && liveObjs.find(taddrObj) == liveObjs.end())
761 {
762 genUsage->unrooted += objSize;
763 }
764 }
765}
766#endif // !FEATURE_PAL
767
768BOOL GCHeapUsageStats(const DacpGcHeapDetails& heap, BOOL bIncUnreachable, HeapUsageStat *hpUsage)
769{
770 memset(hpUsage, 0, sizeof(*hpUsage));
771
772 AllocInfo allocInfo;
773 allocInfo.Init();
774
775 // 1. Start with small object segments
776 TADDR taddrSeg;
777 DacpHeapSegmentData dacpSeg;
778
779 taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment;
780
781#ifndef FEATURE_PAL
782 // this will create the bitmap of rooted objects only if bIncUnreachable is true
783 GCRootImpl gcroot;
784 std::unordered_set<TADDR> emptyLiveObjs;
785 const std::unordered_set<TADDR> &liveObjs = (bIncUnreachable ? gcroot.GetLiveObjects() : emptyLiveObjs);
786
787 // 1a. enumerate all non-ephemeral segments
788 while (taddrSeg != (TADDR)heap.generation_table[0].start_segment)
789 {
790 if (IsInterrupt())
791 return FALSE;
792
793 if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
794 {
795 ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
796 return FALSE;
797 }
798 GCGenUsageStats((TADDR)dacpSeg.mem, (TADDR)dacpSeg.allocated, liveObjs, heap, FALSE, &allocInfo, &hpUsage->genUsage[2]);
799 taddrSeg = (TADDR)dacpSeg.next;
800 }
801#endif
802
803 // 1b. now handle the ephemeral segment
804 if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
805 {
806 ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
807 return FALSE;
808 }
809
810 TADDR endGen = TO_TADDR(heap.alloc_allocated);
811 for (UINT n = 0; n <= GetMaxGeneration(); n ++)
812 {
813 TADDR startGen;
814 // gen 2 starts at the beginning of the segment
815 if (n == GetMaxGeneration())
816 {
817 startGen = TO_TADDR(dacpSeg.mem);
818 }
819 else
820 {
821 startGen = TO_TADDR(heap.generation_table[n].allocation_start);
822 }
823
824#ifndef FEATURE_PAL
825 GCGenUsageStats(startGen, endGen, liveObjs, heap, FALSE, &allocInfo, &hpUsage->genUsage[n]);
826#endif
827 endGen = startGen;
828 }
829
830 // 2. Now process LOH
831 taddrSeg = (TADDR) heap.generation_table[GetMaxGeneration()+1].start_segment;
832 while (taddrSeg != NULL)
833 {
834 if (IsInterrupt())
835 return FALSE;
836
837 if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
838 {
839 ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
840 return FALSE;
841 }
842
843#ifndef FEATURE_PAL
844 GCGenUsageStats((TADDR) dacpSeg.mem, (TADDR) dacpSeg.allocated, liveObjs, heap, TRUE, NULL, &hpUsage->genUsage[3]);
845#endif
846 taddrSeg = (TADDR)dacpSeg.next;
847 }
848
849 return TRUE;
850}
851
852DWORD GetNumComponents(TADDR obj)
853{
854 // The number of components is always the second pointer in the object.
855 DWORD Value = NULL;
856 HRESULT hr = MOVE(Value, obj + sizeof(size_t));
857
858 // If we fail to read out the number of components, let's assume 0 so we don't try to
859 // read further data from the object.
860 if (FAILED(hr))
861 return 0;
862
863 // The component size on a String does not contain the trailing NULL character,
864 // so we must add that ourselves.
865 if(IsStringObject(obj))
866 return Value+1;
867
868 return Value;
869}
870
871static MethodTableInfo* GetMethodTableInfo(DWORD_PTR dwAddrMethTable)
872{
873 // Remove lower bits in case we are in mark phase
874 dwAddrMethTable = dwAddrMethTable & ~3;
875 MethodTableInfo* info = g_special_mtCache.Lookup(dwAddrMethTable);
876 if (!info->IsInitialized()) // An uninitialized entry
877 {
878 // this is the first time we see this method table, so we need to get the information
879 // from the target
880 DacpMethodTableData dmtd;
881 // see code:ClrDataAccess::RequestMethodTableData for details
882 if (dmtd.Request(g_sos, dwAddrMethTable) != S_OK)
883 return NULL;
884
885
886 info->BaseSize = dmtd.BaseSize;
887 info->ComponentSize = dmtd.ComponentSize;
888 info->bContainsPointers = dmtd.bContainsPointers;
889
890 // The following request doesn't work on older runtimes. For those, the
891 // objects would just look like non-collectible, which is acceptable.
892 DacpMethodTableCollectibleData dmtcd;
893 if (SUCCEEDED(dmtcd.Request(g_sos, dwAddrMethTable)))
894 {
895 info->bCollectible = dmtcd.bCollectible;
896 info->LoaderAllocatorObjectHandle = TO_TADDR(dmtcd.LoaderAllocatorObjectHandle);
897 }
898 }
899
900 return info;
901}
902
903BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj,
904 DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers)
905{
906 MethodTableInfo* info = GetMethodTableInfo(dwAddrMethTable);
907 if (info == NULL)
908 {
909 return FALSE;
910 }
911
912 bContainsPointers = info->bContainsPointers;
913 s = info->BaseSize;
914
915 if (info->ComponentSize)
916 {
917 // this is an array, so the size has to include the size of the components. We read the number
918 // of components from the target and multiply by the component size to get the size.
919 s += info->ComponentSize*GetNumComponents(dwAddrCurrObj);
920 }
921
922 // On x64 we do an optimization to save 4 bytes in almost every string we create
923 // IMPORTANT: This cannot be done in ObjectSize, which is a wrapper to this function,
924 // because we must Align only after these changes are made
925#ifdef _TARGET_WIN64_
926 // Pad to min object size if necessary
927 if (s < min_obj_size)
928 s = min_obj_size;
929#endif // _TARGET_WIN64_
930
931 s = (bLarge ? AlignLarge(s) : Align (s));
932 return TRUE;
933}
934
935BOOL GetCollectibleDataEfficient(DWORD_PTR dwAddrMethTable, BOOL& bCollectible, TADDR& loaderAllocatorObjectHandle)
936{
937 MethodTableInfo* info = GetMethodTableInfo(dwAddrMethTable);
938 if (info == NULL)
939 {
940 return FALSE;
941 }
942
943 bCollectible = info->bCollectible;
944 loaderAllocatorObjectHandle = info->LoaderAllocatorObjectHandle;
945
946 return TRUE;
947}
948
949// This function expects stat to be valid, and ready to get statistics.
950void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort)
951{
952 DWORD_PTR dwAddr=0;
953 UINT m;
954
955 if (!bShort)
956 {
957 for (m = 0; m <= GetMaxGeneration(); m ++)
958 {
959 if (IsInterrupt())
960 return;
961
962 ExtOut("generation %d has %d finalizable objects ", m,
963 (SegQueueLimit(heapDetails,gen_segment(m)) - SegQueue(heapDetails,gen_segment(m))) / sizeof(size_t));
964
965 ExtOut ("(%p->%p)\n",
966 SOS_PTR(SegQueue(heapDetails,gen_segment(m))),
967 SOS_PTR(SegQueueLimit(heapDetails,gen_segment(m))));
968 }
969 }
970#ifndef FEATURE_PAL
971 if (bAllReady)
972 {
973 if (!bShort)
974 {
975 ExtOut ("Finalizable but not rooted: ");
976 }
977
978 TADDR rngStart = (TADDR)SegQueue(heapDetails, gen_segment(GetMaxGeneration()));
979 TADDR rngEnd = (TADDR)SegQueueLimit(heapDetails, gen_segment(0));
980
981 PrintNotReachableInRange(rngStart, rngEnd, TRUE, bAllReady ? stat : NULL, bShort);
982 }
983#endif
984
985 if (!bShort)
986 {
987 ExtOut ("Ready for finalization %d objects ",
988 (SegQueueLimit(heapDetails,FinalizerListSeg)-SegQueue(heapDetails,CriticalFinalizerListSeg)) / sizeof(size_t));
989 ExtOut ("(%p->%p)\n",
990 SOS_PTR(SegQueue(heapDetails,CriticalFinalizerListSeg)),
991 SOS_PTR(SegQueueLimit(heapDetails,FinalizerListSeg)));
992 }
993
994 // if bAllReady we only count objects that are ready for finalization,
995 // otherwise we count all finalizable objects.
996 TADDR taddrLowerLimit = (bAllReady ? (TADDR)SegQueue(heapDetails, CriticalFinalizerListSeg) :
997 (DWORD_PTR)SegQueue(heapDetails, gen_segment(GetMaxGeneration())));
998 for (dwAddr = taddrLowerLimit;
999 dwAddr < (DWORD_PTR)SegQueueLimit(heapDetails, FinalizerListSeg);
1000 dwAddr += sizeof (dwAddr))
1001 {
1002 if (IsInterrupt())
1003 {
1004 return;
1005 }
1006
1007 DWORD_PTR objAddr = NULL,
1008 MTAddr = NULL;
1009
1010 if (SUCCEEDED(MOVE(objAddr, dwAddr)) && SUCCEEDED(GetMTOfObject(objAddr, &MTAddr)) && MTAddr)
1011 {
1012 if (bShort)
1013 {
1014 DMLOut("%s\n", DMLObject(objAddr));
1015 }
1016 else
1017 {
1018 size_t s = ObjectSize(objAddr);
1019 stat->Add(MTAddr, (DWORD)s);
1020 }
1021 }
1022 }
1023}
1024
1025BOOL GCHeapTraverse(const DacpGcHeapDetails &heap, AllocInfo* pallocInfo, VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify)
1026{
1027 DWORD_PTR begin_youngest;
1028 DWORD_PTR end_youngest;
1029 begin_youngest = (DWORD_PTR)heap.generation_table[0].allocation_start;
1030 DWORD_PTR dwAddr = (DWORD_PTR)heap.ephemeral_heap_segment;
1031 DacpHeapSegmentData segment;
1032
1033 end_youngest = (DWORD_PTR)heap.alloc_allocated;
1034
1035 DWORD_PTR dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment;
1036 dwAddr = dwAddrSeg;
1037
1038 if (segment.Request(g_sos, dwAddr, heap) != S_OK)
1039 {
1040 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
1041 return FALSE;
1042 }
1043
1044 // DWORD_PTR dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].allocation_start;
1045 DWORD_PTR dwAddrCurrObj = (DWORD_PTR)segment.mem;
1046
1047 size_t s, sPrev=0;
1048 BOOL bPrevFree=FALSE;
1049 DWORD_PTR dwAddrMethTable;
1050 DWORD_PTR dwAddrPrevObj=0;
1051
1052 while(1)
1053 {
1054 if (IsInterrupt())
1055 {
1056 ExtOut("<heap walk interrupted>\n");
1057 return FALSE;
1058 }
1059 DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated;
1060 if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment)
1061 {
1062 end_of_segment = end_youngest;
1063 if (dwAddrCurrObj - SIZEOF_OBJHEADER == end_youngest - Align(min_obj_size))
1064 break;
1065 }
1066 if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment)
1067 {
1068 if (dwAddrCurrObj > (DWORD_PTR)end_of_segment)
1069 {
1070 ExtOut ("curr_object: %p > heap_segment_allocated (seg: %p)\n",
1071 SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg));
1072 if (dwAddrPrevObj) {
1073 ExtOut ("Last good object: %p\n", SOS_PTR(dwAddrPrevObj));
1074 }
1075 return FALSE;
1076 }
1077 dwAddrSeg = (DWORD_PTR)segment.next;
1078 if (dwAddrSeg)
1079 {
1080 dwAddr = dwAddrSeg;
1081 if (segment.Request(g_sos, dwAddr, heap) != S_OK)
1082 {
1083 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
1084 return FALSE;
1085 }
1086 dwAddrCurrObj = (DWORD_PTR)segment.mem;
1087 continue;
1088 }
1089 else
1090 break; // Done Verifying Heap
1091 }
1092
1093 if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment
1094 && dwAddrCurrObj >= end_youngest)
1095 {
1096 if (dwAddrCurrObj > end_youngest)
1097 {
1098 // prev_object length is too long
1099 ExtOut("curr_object: %p > end_youngest: %p\n",
1100 SOS_PTR(dwAddrCurrObj), SOS_PTR(end_youngest));
1101 if (dwAddrPrevObj) {
1102 DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj));
1103 }
1104 return FALSE;
1105 }
1106 return FALSE;
1107 }
1108
1109 if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable)))
1110 {
1111 return FALSE;
1112 }
1113
1114 dwAddrMethTable = dwAddrMethTable & ~3;
1115 if (dwAddrMethTable == 0)
1116 {
1117 // Is this the beginning of an allocation context?
1118 int i;
1119 for (i = 0; i < pallocInfo->num; i ++)
1120 {
1121 if (dwAddrCurrObj == (DWORD_PTR)pallocInfo->array[i].alloc_ptr)
1122 {
1123 dwAddrCurrObj =
1124 (DWORD_PTR)pallocInfo->array[i].alloc_limit + Align(min_obj_size);
1125 break;
1126 }
1127 }
1128 if (i < pallocInfo->num)
1129 continue;
1130
1131 // We also need to look at the gen0 alloc context.
1132 if (dwAddrCurrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr)
1133 {
1134 dwAddrCurrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size);
1135 continue;
1136 }
1137 }
1138
1139 BOOL bContainsPointers;
1140 BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, FALSE, s, bContainsPointers);
1141 if (verify && bMTOk)
1142 bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE);
1143 if (!bMTOk)
1144 {
1145 DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj));
1146 if (dwAddrPrevObj)
1147 DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj));
1148
1149 ExtOut ("----------------\n");
1150 return FALSE;
1151 }
1152
1153 pFunc (dwAddrCurrObj, s, dwAddrMethTable, token);
1154
1155 // We believe we did this alignment in ObjectSize above.
1156 assert((s & ALIGNCONST) == 0);
1157 dwAddrPrevObj = dwAddrCurrObj;
1158 sPrev = s;
1159 bPrevFree = IsMTForFreeObj(dwAddrMethTable);
1160
1161 dwAddrCurrObj += s;
1162 }
1163
1164 // Now for the large object generation:
1165 dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment;
1166 dwAddr = dwAddrSeg;
1167
1168 if (segment.Request(g_sos, dwAddr, heap) != S_OK)
1169 {
1170 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
1171 return FALSE;
1172 }
1173
1174 // dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].allocation_start;
1175 dwAddrCurrObj = (DWORD_PTR)segment.mem;
1176
1177 dwAddrPrevObj=0;
1178
1179 while(1)
1180 {
1181 if (IsInterrupt())
1182 {
1183 ExtOut("<heap traverse interrupted>\n");
1184 return FALSE;
1185 }
1186
1187 DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated;
1188
1189 if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment)
1190 {
1191 if (dwAddrCurrObj > (DWORD_PTR)end_of_segment)
1192 {
1193 ExtOut("curr_object: %p > heap_segment_allocated (seg: %p)\n",
1194 SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg));
1195 if (dwAddrPrevObj) {
1196 ExtOut("Last good object: %p\n", SOS_PTR(dwAddrPrevObj));
1197 }
1198 return FALSE;
1199 }
1200 dwAddrSeg = (DWORD_PTR)segment.next;
1201 if (dwAddrSeg)
1202 {
1203 dwAddr = dwAddrSeg;
1204 if (segment.Request(g_sos, dwAddr, heap) != S_OK)
1205 {
1206 ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
1207 return FALSE;
1208 }
1209 dwAddrCurrObj = (DWORD_PTR)segment.mem;
1210 continue;
1211 }
1212 else
1213 break; // Done Verifying Heap
1214 }
1215
1216 if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable)))
1217 {
1218 return FALSE;
1219 }
1220
1221 dwAddrMethTable = dwAddrMethTable & ~3;
1222 BOOL bContainsPointers;
1223 BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, TRUE, s, bContainsPointers);
1224 if (verify && bMTOk)
1225 bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE);
1226 if (!bMTOk)
1227 {
1228 DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj));
1229
1230 if (dwAddrPrevObj)
1231 DMLOut("Last good object: %s\n", dwAddrPrevObj);
1232
1233 ExtOut ("----------------\n");
1234 return FALSE;
1235 }
1236
1237 pFunc (dwAddrCurrObj, s, dwAddrMethTable, token);
1238
1239 // We believe we did this alignment in ObjectSize above.
1240 assert((s & ALIGNCONSTLARGE) == 0);
1241 dwAddrPrevObj = dwAddrCurrObj;
1242 dwAddrCurrObj += s;
1243 }
1244
1245 return TRUE;
1246}
1247
1248BOOL GCHeapsTraverse(VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify)
1249{
1250 // Obtain allocation context for each managed thread.
1251 AllocInfo allocInfo;
1252 allocInfo.Init();
1253
1254 if (!IsServerBuild())
1255 {
1256 DacpGcHeapDetails heapDetails;
1257 if (heapDetails.Request(g_sos) != S_OK)
1258 {
1259 ExtOut("Error requesting gc heap details\n");
1260 return FALSE;
1261 }
1262
1263 return GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify);
1264 }
1265 else
1266 {
1267 DacpGcHeapData gcheap;
1268 if (gcheap.Request(g_sos) != S_OK)
1269 {
1270 ExtOut("Error requesting GC Heap data\n");
1271 return FALSE;
1272 }
1273
1274 DWORD dwAllocSize;
1275 DWORD dwNHeaps = gcheap.HeapCount;
1276 if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
1277 {
1278 ExtOut("Failed to get GCHeaps: integer overflow error\n");
1279 return FALSE;
1280 }
1281 CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
1282 if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
1283 {
1284 ExtOut("Failed to get GCHeaps\n");
1285 return FALSE;
1286 }
1287
1288 DWORD n;
1289 for (n = 0; n < dwNHeaps; n ++)
1290 {
1291 DacpGcHeapDetails heapDetails;
1292 if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
1293 {
1294 ExtOut("Error requesting details\n");
1295 return FALSE;
1296 }
1297
1298 if (!GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify))
1299 {
1300 ExtOut("Traversing a gc heap failed\n");
1301 return FALSE;
1302 }
1303 }
1304 }
1305
1306 return TRUE;
1307}
1308
1309GCHeapSnapshot::GCHeapSnapshot()
1310{
1311 m_isBuilt = FALSE;
1312 m_heapDetails = NULL;
1313}
1314
1315///////////////////////////////////////////////////////////
1316SegmentLookup::SegmentLookup()
1317{
1318 m_iSegmentsSize = m_iSegmentCount = 0;
1319
1320 m_segments = new DacpHeapSegmentData[nSegLookupStgIncrement];
1321 if (m_segments == NULL)
1322 {
1323 ReportOOM();
1324 }
1325 else
1326 {
1327 m_iSegmentsSize = nSegLookupStgIncrement;
1328 }
1329}
1330
1331BOOL SegmentLookup::AddSegment(DacpHeapSegmentData *pData)
1332{
1333 // appends the address of a new (initialized) instance of DacpHeapSegmentData to the list of segments
1334 // (m_segments) adding space for a segment when necessary.
1335 // @todo Microsoft: The field name m_iSegmentSize is a little misleading. It's not the size in bytes,
1336 // but the number of elements allocated for the array. It probably should have been named something like
1337 // m_iMaxSegments instead.
1338 if (m_iSegmentCount >= m_iSegmentsSize)
1339 {
1340 // expand buffer--allocate enough space to hold the elements we already have plus nSegLookupStgIncrement
1341 // more elements
1342 DacpHeapSegmentData *pNewBuffer = new DacpHeapSegmentData[m_iSegmentsSize+nSegLookupStgIncrement];
1343 if (pNewBuffer==NULL)
1344 return FALSE;
1345
1346 // copy the old elements into the new array
1347 memcpy(pNewBuffer, m_segments, sizeof(DacpHeapSegmentData)*m_iSegmentsSize);
1348
1349 // record the new number of elements available
1350 m_iSegmentsSize+=nSegLookupStgIncrement;
1351
1352 // delete the old array
1353 delete [] m_segments;
1354
1355 // set m_segments to point to the new array
1356 m_segments = pNewBuffer;
1357 }
1358
1359 // add pData to the array
1360 m_segments[m_iSegmentCount++] = *pData;
1361
1362 return TRUE;
1363}
1364
1365SegmentLookup::~SegmentLookup()
1366{
1367 if (m_segments)
1368 {
1369 delete [] m_segments;
1370 m_segments = NULL;
1371 }
1372}
1373
1374void SegmentLookup::Clear()
1375{
1376 m_iSegmentCount = 0;
1377}
1378
1379CLRDATA_ADDRESS SegmentLookup::GetHeap(CLRDATA_ADDRESS object, BOOL& bFound)
1380{
1381 CLRDATA_ADDRESS ret = NULL;
1382 bFound = FALSE;
1383
1384 // Visit our segments
1385 for (int i=0; i<m_iSegmentCount; i++)
1386 {
1387 if (TO_TADDR(m_segments[i].mem) <= TO_TADDR(object) &&
1388 TO_TADDR(m_segments[i].highAllocMark) > TO_TADDR(object))
1389 {
1390 ret = m_segments[i].gc_heap;
1391 bFound = TRUE;
1392 break;
1393 }
1394 }
1395
1396 return ret;
1397}
1398
1399///////////////////////////////////////////////////////////////////////////
1400
1401BOOL GCHeapSnapshot::Build()
1402{
1403 Clear();
1404
1405 m_isBuilt = FALSE;
1406
1407 ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1408 /// 1. Get some basic information such as the heap type (SVR or WKS), how many heaps there are, mode and max generation
1409 /// (See code:ClrDataAccess::RequestGCHeapData)
1410 ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1411 if (m_gcheap.Request(g_sos) != S_OK)
1412 {
1413 ExtOut("Error requesting GC Heap data\n");
1414 return FALSE;
1415 }
1416
1417 ArrayHolder<CLRDATA_ADDRESS> heapAddrs = NULL;
1418
1419 ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1420 /// 2. Get a list of the addresses of the heaps when we have multiple heaps in server mode
1421 ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1422 if (m_gcheap.bServerMode)
1423 {
1424 UINT AllocSize;
1425 // allocate an array to hold the starting addresses of each heap when we're in server mode
1426 if (!ClrSafeInt<UINT>::multiply(sizeof(CLRDATA_ADDRESS), m_gcheap.HeapCount, AllocSize) ||
1427 (heapAddrs = new CLRDATA_ADDRESS [m_gcheap.HeapCount]) == NULL)
1428 {
1429 ReportOOM();
1430 return FALSE;
1431 }
1432
1433 // and initialize it with their addresses (see code:ClrDataAccess::RequestGCHeapList
1434 // for details)
1435 if (g_sos->GetGCHeapList(m_gcheap.HeapCount, heapAddrs, NULL) != S_OK)
1436 {
1437 ExtOut("Failed to get GCHeaps\n");
1438 return FALSE;
1439 }
1440 }
1441
1442 ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1443 /// 3. Get some necessary information about each heap, such as the card table location, the generation
1444 /// table, the heap bounds, etc., and retrieve the heap segments
1445 ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1446
1447 // allocate an array to hold the information
1448 m_heapDetails = new DacpGcHeapDetails[m_gcheap.HeapCount];
1449
1450 if (m_heapDetails == NULL)
1451 {
1452 ReportOOM();
1453 return FALSE;
1454 }
1455
1456 // get the heap information for each heap
1457 // See code:ClrDataAccess::RequestGCHeapDetails for details
1458 for (UINT n = 0; n < m_gcheap.HeapCount; n ++)
1459 {
1460 if (m_gcheap.bServerMode)
1461 {
1462 if (m_heapDetails[n].Request(g_sos, heapAddrs[n]) != S_OK)
1463 {
1464 ExtOut("Error requesting details\n");
1465 return FALSE;
1466 }
1467 }
1468 else
1469 {
1470 if (m_heapDetails[n].Request(g_sos) != S_OK)
1471 {
1472 ExtOut("Error requesting details\n");
1473 return FALSE;
1474 }
1475 }
1476
1477 // now get information about the heap segments for this heap
1478 if (!AddSegments(m_heapDetails[n]))
1479 {
1480 ExtOut("Failed to retrieve segments for gc heap\n");
1481 return FALSE;
1482 }
1483 }
1484
1485 m_isBuilt = TRUE;
1486 return TRUE;
1487}
1488
1489BOOL GCHeapSnapshot::AddSegments(DacpGcHeapDetails& details)
1490{
1491 int n = 0;
1492 DacpHeapSegmentData segment;
1493
1494 // This array of two addresses gives us access to all the segments. The generation segments are linked
1495 // to each other, starting with the maxGeneration segment. The second address gives us the large object heap.
1496 CLRDATA_ADDRESS AddrSegs[] =
1497 {
1498 details.generation_table[GetMaxGeneration()].start_segment,
1499 details.generation_table[GetMaxGeneration()+1].start_segment // large object heap
1500 };
1501
1502 // this loop will get information for all the heap segments in this heap. The outer loop iterates once
1503 // for the "normal" generation segments and once for the large object heap. The inner loop follows the chain
1504 // of segments rooted at AddrSegs[i]
1505 for (unsigned int i = 0; i < sizeof(AddrSegs)/sizeof(AddrSegs[0]); ++i)
1506 {
1507 CLRDATA_ADDRESS AddrSeg = AddrSegs[i];
1508
1509 while (AddrSeg != NULL)
1510 {
1511 if (IsInterrupt())
1512 {
1513 return FALSE;
1514 }
1515 // Initialize segment by copying fields from the target's heap segment at AddrSeg.
1516 // See code:ClrDataAccess::RequestGCHeapSegment for details.
1517 if (segment.Request(g_sos, AddrSeg, details) != S_OK)
1518 {
1519 ExtOut("Error requesting heap segment %p\n", SOS_PTR(AddrSeg));
1520 return FALSE;
1521 }
1522 if (n++ > nMaxHeapSegmentCount) // that would be insane
1523 {
1524 ExtOut("More than %d heap segments, there must be an error\n", nMaxHeapSegmentCount);
1525 return FALSE;
1526 }
1527
1528 // add the new segment to the array of segments. This will expand the array if necessary
1529 if (!m_segments.AddSegment(&segment))
1530 {
1531 ExtOut("strike: Failed to store segment\n");
1532 return FALSE;
1533 }
1534 // get the next segment in the chain
1535 AddrSeg = segment.next;
1536 }
1537 }
1538
1539 return TRUE;
1540}
1541
1542void GCHeapSnapshot::Clear()
1543{
1544 if (m_heapDetails != NULL)
1545 {
1546 delete [] m_heapDetails;
1547 m_heapDetails = NULL;
1548 }
1549
1550 m_segments.Clear();
1551
1552 m_isBuilt = FALSE;
1553}
1554
1555GCHeapSnapshot g_snapshot;
1556
1557DacpGcHeapDetails *GCHeapSnapshot::GetHeap(CLRDATA_ADDRESS objectPointer)
1558{
1559 // We need bFound because heap will be NULL if we are Workstation Mode.
1560 // We still need a way to know if the address was found in our segment
1561 // list.
1562 BOOL bFound = FALSE;
1563 CLRDATA_ADDRESS heap = m_segments.GetHeap(objectPointer, bFound);
1564 if (heap)
1565 {
1566 for (UINT i=0; i<m_gcheap.HeapCount; i++)
1567 {
1568 if (m_heapDetails[i].heapAddr == heap)
1569 return m_heapDetails + i;
1570 }
1571 }
1572 else if (!m_gcheap.bServerMode)
1573 {
1574 if (bFound)
1575 {
1576 return m_heapDetails;
1577 }
1578 }
1579
1580 // Not found
1581 return NULL;
1582}
1583
1584// TODO: Do we need to handle the LOH here?
1585int GCHeapSnapshot::GetGeneration(CLRDATA_ADDRESS objectPointer)
1586{
1587 DacpGcHeapDetails *pDetails = GetHeap(objectPointer);
1588 if (pDetails == NULL)
1589 {
1590 ExtOut("Object %p has no generation\n", SOS_PTR(objectPointer));
1591 return 0;
1592 }
1593
1594 TADDR taObj = TO_TADDR(objectPointer);
1595 // The DAC doesn't fill the generation table with true CLRDATA_ADDRESS values
1596 // but rather with ULONG64 values (i.e. non-sign-extended 64-bit values)
1597 // We use the TO_TADDR below to ensure we won't break if this will ever
1598 // be fixed in the DAC.
1599 if (taObj >= TO_TADDR(pDetails->generation_table[0].allocation_start) &&
1600 taObj <= TO_TADDR(pDetails->alloc_allocated))
1601 return 0;
1602
1603 if (taObj >= TO_TADDR(pDetails->generation_table[1].allocation_start) &&
1604 taObj <= TO_TADDR(pDetails->generation_table[0].allocation_start))
1605 return 1;
1606
1607 return 2;
1608}
1609
1610
1611DWORD_PTR g_trav_totalSize = 0;
1612DWORD_PTR g_trav_wastedSize = 0;
1613
1614void LoaderHeapTraverse(CLRDATA_ADDRESS blockData,size_t blockSize,BOOL blockIsCurrentBlock)
1615{
1616 DWORD_PTR dwAddr1;
1617 DWORD_PTR curSize = 0;
1618 char ch;
1619 for (dwAddr1 = (DWORD_PTR)blockData;
1620 dwAddr1 < (DWORD_PTR)blockData + blockSize;
1621 dwAddr1 += OSPageSize())
1622 {
1623 if (IsInterrupt())
1624 break;
1625 if (SafeReadMemory(dwAddr1, &ch, sizeof(ch), NULL))
1626 {
1627 curSize += OSPageSize();
1628 }
1629 else
1630 break;
1631 }
1632
1633 if (!blockIsCurrentBlock)
1634 {
1635 g_trav_wastedSize += blockSize - curSize;
1636 }
1637
1638 g_trav_totalSize += curSize;
1639 ExtOut("%p(%x:%x) ", SOS_PTR(blockData), blockSize, curSize);
1640}
1641
1642/**********************************************************************\
1643* Routine Description: *
1644* *
1645* This function prints out the size for various heaps. *
1646* total - the total size of the heap *
1647* wasted - the amount of size wasted by the heap. *
1648* *
1649\**********************************************************************/
1650void PrintHeapSize(DWORD_PTR total, DWORD_PTR wasted)
1651{
1652 ExtOut("Size: 0x%" POINTERSIZE_TYPE "x (%" POINTERSIZE_TYPE "u) bytes", total, total);
1653 if (wasted)
1654 ExtOut(" total, 0x%" POINTERSIZE_TYPE "x (%" POINTERSIZE_TYPE "u) bytes wasted", wasted, wasted);
1655 ExtOut(".\n");
1656}
1657
1658/**********************************************************************\
1659* Routine Description: *
1660* *
1661* This function prints out the size information for the JIT heap. *
1662* *
1663* Returns: The size of this heap. *
1664* *
1665\**********************************************************************/
1666DWORD_PTR JitHeapInfo()
1667{
1668 // walk ExecutionManager__m_pJitList
1669 unsigned int count = 0;
1670 if (FAILED(g_sos->GetJitManagerList(0, NULL, &count)))
1671 {
1672 ExtOut("Unable to get JIT info\n");
1673 return 0;
1674 }
1675
1676 ArrayHolder<DacpJitManagerInfo> pArray = new DacpJitManagerInfo[count];
1677 if (pArray==NULL)
1678 {
1679 ReportOOM();
1680 return 0;
1681 }
1682
1683 if (g_sos->GetJitManagerList(count, pArray, NULL) != S_OK)
1684 {
1685 ExtOut("Unable to get array of JIT Managers\n");
1686 return 0;
1687 }
1688
1689 DWORD_PTR totalSize = 0;
1690 DWORD_PTR wasted = 0;
1691
1692 for (unsigned int n=0; n < count; n++)
1693 {
1694 if (IsInterrupt())
1695 break;
1696
1697 if (IsMiIL(pArray[n].codeType)) // JIT
1698 {
1699 unsigned int heapCount = 0;
1700 if (FAILED(g_sos->GetCodeHeapList(pArray[n].managerAddr, 0, NULL, &heapCount)))
1701 {
1702 ExtOut("Error getting EEJitManager code heaps\n");
1703 break;
1704 }
1705
1706 if (heapCount > 0)
1707 {
1708 ArrayHolder<DacpJitCodeHeapInfo> codeHeapInfo = new DacpJitCodeHeapInfo[heapCount];
1709 if (codeHeapInfo == NULL)
1710 {
1711 ReportOOM();
1712 break;
1713 }
1714
1715 if (g_sos->GetCodeHeapList(pArray[n].managerAddr, heapCount, codeHeapInfo, NULL) != S_OK)
1716 {
1717 ExtOut("Unable to get code heap info\n");
1718 break;
1719 }
1720
1721 for (unsigned int iHeaps = 0; iHeaps < heapCount; iHeaps++)
1722 {
1723 if (IsInterrupt())
1724 break;
1725
1726 if (codeHeapInfo[iHeaps].codeHeapType == CODEHEAP_LOADER)
1727 {
1728 ExtOut("LoaderCodeHeap: ");
1729 totalSize += LoaderHeapInfo(codeHeapInfo[iHeaps].LoaderHeap, &wasted);
1730 }
1731 else if (codeHeapInfo[iHeaps].codeHeapType == CODEHEAP_HOST)
1732 {
1733 ExtOut("HostCodeHeap: ");
1734 ExtOut("%p ", SOS_PTR(codeHeapInfo[iHeaps].HostData.baseAddr));
1735 DWORD dwSize = (DWORD)(codeHeapInfo[iHeaps].HostData.currentAddr - codeHeapInfo[iHeaps].HostData.baseAddr);
1736 PrintHeapSize(dwSize, 0);
1737 totalSize += dwSize;
1738 }
1739 }
1740 }
1741 }
1742 else if (!IsMiNative(pArray[n].codeType)) // ignore native heaps for now
1743 {
1744 ExtOut("Unknown Jit encountered, ignored\n");
1745 }
1746 }
1747
1748 ExtOut("Total size: ");
1749 PrintHeapSize(totalSize, wasted);
1750
1751 return totalSize;
1752}
1753
1754
1755/**********************************************************************\
1756* Routine Description: *
1757* *
1758* This function prints out the loader heap info for a single AD. *
1759* pLoaderHeapAddr - pointer to the loader heap *
1760* wasted - a pointer to store the number of bytes wasted in this *
1761* VSDHeap (this pointer can be NULL) *
1762* *
1763* Returns: The size of this heap. *
1764* *
1765\**********************************************************************/
1766DWORD_PTR LoaderHeapInfo(CLRDATA_ADDRESS pLoaderHeapAddr, DWORD_PTR *wasted)
1767{
1768 g_trav_totalSize = 0;
1769 g_trav_wastedSize = 0;
1770
1771 if (pLoaderHeapAddr)
1772 g_sos->TraverseLoaderHeap(pLoaderHeapAddr, LoaderHeapTraverse);
1773
1774 PrintHeapSize(g_trav_totalSize, g_trav_wastedSize);
1775
1776 if (wasted)
1777 *wasted += g_trav_wastedSize;
1778 return g_trav_totalSize;
1779}
1780
1781
1782/**********************************************************************\
1783* Routine Description: *
1784* *
1785* This function prints out the heap info for a single VSDHeap. *
1786* name - the name to print *
1787* type - the type of heap *
1788* appDomain - the app domain in which this resides *
1789* wasted - a pointer to store the number of bytes wasted in this *
1790* VSDHeap (this pointer can be NULL) *
1791* *
1792* Returns: The size of this heap. *
1793* *
1794\**********************************************************************/
1795static DWORD_PTR PrintOneVSDHeap(const char *name, VCSHeapType type, CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted)
1796{
1797 g_trav_totalSize = 0; g_trav_wastedSize = 0;
1798
1799 ExtOut(name);
1800 g_sos->TraverseVirtCallStubHeap(appDomain, type, LoaderHeapTraverse);
1801
1802 PrintHeapSize(g_trav_totalSize, g_trav_wastedSize);
1803 if (wasted)
1804 *wasted += g_trav_wastedSize;
1805 return g_trav_totalSize;
1806}
1807
1808
1809/**********************************************************************\
1810* Routine Description: *
1811* *
1812* This function prints out the heap info for VSDHeaps. *
1813* appDomain - The AppDomain to print info for. *
1814* wasted - a pointer to store the number of bytes wasted in this *
1815* AppDomain (this pointer can be NULL) *
1816* *
1817* Returns: The size of this heap. *
1818* *
1819\**********************************************************************/
1820DWORD_PTR VSDHeapInfo(CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted)
1821{
1822 DWORD_PTR totalSize = 0;
1823
1824 if (appDomain)
1825 {
1826 totalSize += PrintOneVSDHeap(" IndcellHeap: ", IndcellHeap, appDomain, wasted);
1827 totalSize += PrintOneVSDHeap(" LookupHeap: ", LookupHeap, appDomain, wasted);
1828 totalSize += PrintOneVSDHeap(" ResolveHeap: ", ResolveHeap, appDomain, wasted);
1829 totalSize += PrintOneVSDHeap(" DispatchHeap: ", DispatchHeap, appDomain, wasted);
1830 totalSize += PrintOneVSDHeap(" CacheEntryHeap: ", CacheEntryHeap, appDomain, wasted);
1831 }
1832
1833 return totalSize;
1834}
1835
1836
1837/**********************************************************************\
1838* Routine Description: *
1839* *
1840* This function prints out the heap info for a domain *
1841* name - the name of the domain (to be printed) *
1842* adPtr - a pointer to the AppDomain to print info about *
1843* outSize - a pointer to an int to store the size at (this may be *
1844* NULL) *
1845* outWasted - a pointer to an int to store the number of bytes this *
1846* domain is wasting (this may be NULL) *
1847* *
1848* returns: SUCCESS if we successfully printed out the domain heap *
1849* info, FAILED otherwise; if FAILED, outSize and *
1850* outWasted are untouched. *
1851* *
1852\**********************************************************************/
1853HRESULT PrintDomainHeapInfo(const char *name, CLRDATA_ADDRESS adPtr, DWORD_PTR *outSize, DWORD_PTR *outWasted)
1854{
1855 DacpAppDomainData appDomain;
1856 HRESULT hr = appDomain.Request(g_sos, adPtr);
1857 if (FAILED(hr))
1858 {
1859 ExtOut("Unable to get information for %s.\n", name);
1860 return hr;
1861 }
1862
1863 ExtOut("--------------------------------------\n");
1864
1865 const int column = 19;
1866 ExtOut("%s:", name);
1867 WhitespaceOut(column - (int)strlen(name) - 1);
1868 DMLOut("%s\n", DMLDomain(adPtr));
1869
1870 DWORD_PTR domainHeapSize = 0;
1871 DWORD_PTR wasted = 0;
1872
1873 ExtOut("LowFrequencyHeap: ");
1874 domainHeapSize += LoaderHeapInfo(appDomain.pLowFrequencyHeap, &wasted);
1875
1876 ExtOut("HighFrequencyHeap: ");
1877 domainHeapSize += LoaderHeapInfo(appDomain.pHighFrequencyHeap, &wasted);
1878
1879 ExtOut("StubHeap: ");
1880 domainHeapSize += LoaderHeapInfo(appDomain.pStubHeap, &wasted);
1881
1882 ExtOut("Virtual Call Stub Heap:\n");
1883 domainHeapSize += VSDHeapInfo(appDomain.AppDomainPtr, &wasted);
1884
1885 ExtOut("Total size: ");
1886 PrintHeapSize(domainHeapSize, wasted);
1887
1888 if (outSize)
1889 *outSize += domainHeapSize;
1890 if (outWasted)
1891 *outWasted += wasted;
1892
1893 return hr;
1894}
1895
1896/**********************************************************************\
1897* Routine Description: *
1898* *
1899* This function prints out the heap info for a list of modules. *
1900* moduleList - an array of modules *
1901* count - the number of modules in moduleList *
1902* type - the type of heap *
1903* outWasted - a pointer to store the number of bytes wasted in this *
1904* heap (this pointer can be NULL) *
1905* *
1906* Returns: The size of this heap. *
1907* *
1908\**********************************************************************/
1909DWORD_PTR PrintModuleHeapInfo(__out_ecount(count) DWORD_PTR *moduleList, int count, ModuleHeapType type, DWORD_PTR *outWasted)
1910{
1911 DWORD_PTR toReturn = 0;
1912 DWORD_PTR wasted = 0;
1913
1914 if (IsMiniDumpFile())
1915 {
1916 ExtOut("<no information>\n");
1917 }
1918 else
1919 {
1920 DWORD_PTR thunkHeapSize = 0;
1921
1922 for (int i = 0; i < count; i++)
1923 {
1924 CLRDATA_ADDRESS addr = moduleList[i];
1925 DacpModuleData dmd;
1926 if (dmd.Request(g_sos, addr) != S_OK)
1927 {
1928 ExtOut("Unable to read module %p\n", SOS_PTR(addr));
1929 }
1930 else
1931 {
1932 DMLOut("Module %s: ", DMLModule(addr));
1933 CLRDATA_ADDRESS heap = type == ModuleHeapType_ThunkHeap ? dmd.pThunkHeap : dmd.pLookupTableHeap;
1934 thunkHeapSize += LoaderHeapInfo(heap, &wasted);
1935 }
1936 }
1937
1938 ExtOut("Total size: " WIN86_8SPACES);
1939 PrintHeapSize(thunkHeapSize, wasted);
1940
1941 toReturn = thunkHeapSize;
1942 }
1943
1944 if (outWasted)
1945 *outWasted += wasted;
1946
1947 return toReturn;
1948}
1949