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
11/*
12 * This file defines the support classes that allow us to operate on the object graph of the process that SOS
13 * is analyzing.
14 *
15 * The GCRoot algorithm is based on three simple principles:
16 * 1. Only consider an object once. When we inspect an object, read its references and don't ever touch
17 * it again. This ensures that our upper bound on the amount of time we spend walking the object
18 * graph very quickly reaches resolution. The objects we've already inspected (and thus won't inspect
19 * again) is tracked by the mConsidered variable.
20 * 2. Be extremely careful about reads from the target process. We use a linear cache for reading from
21 * object data. We also cache everything about the method tables we read out of, as well as caching
22 * the GCDesc which is required to walk the object's references.
23 * 3. Use O(1) data structures for anything perf-critical. Almost all of the data structures we use to
24 * keep track of data have very fast lookups. For example, to keep track of the objects we've considered
25 * we use a unordered_set. Similarly to keep track of MethodTable data we use a unordered_map to track the
26 * mt -> mtinfo mapping.
27 */
28
29#include "sos.h"
30#include "disasm.h"
31
32#ifdef _ASSERTE
33#undef _ASSERTE
34#endif
35
36#define _ASSERTE(a) {;}
37
38#include "gcdesc.h"
39
40#include "safemath.h"
41
42
43#undef _ASSERTE
44
45#ifdef _DEBUG
46#define _ASSERTE(expr) \
47 do { if (!(expr) ) { ExtErr("_ASSERTE fired:\n\t%s\n", #expr); if (IsDebuggerPresent()) DebugBreak(); } } while (0)
48#else
49#define _ASSERTE(x)
50#endif
51
52inline size_t ALIGN_DOWN( size_t val, size_t alignment )
53{
54 // alignment must be a power of 2 for this implementation to work (need modulo otherwise)
55 _ASSERTE( 0 == (alignment & (alignment - 1)) );
56 size_t result = val & ~(alignment - 1);
57 return result;
58}
59
60inline void* ALIGN_DOWN( void* val, size_t alignment )
61{
62 return (void*) ALIGN_DOWN( (size_t)val, alignment );
63}
64
65LinearReadCache::LinearReadCache(ULONG pageSize)
66 : mCurrPageStart(0), mPageSize(pageSize), mCurrPageSize(0), mPage(0)
67{
68 mPage = new BYTE[pageSize];
69 ClearStats();
70}
71
72LinearReadCache::~LinearReadCache()
73{
74 if (mPage)
75 delete [] mPage;
76}
77
78bool LinearReadCache::MoveToPage(TADDR addr, unsigned int size)
79{
80 if (size > mPageSize)
81 size = mPageSize;
82
83 mCurrPageStart = addr;
84 HRESULT hr = g_ExtData->ReadVirtual(mCurrPageStart, mPage, size, &mCurrPageSize);
85
86 if (hr != S_OK)
87 {
88 mCurrPageStart = 0;
89 mCurrPageSize = 0;
90 return false;
91 }
92
93#ifdef _DEBUG
94 mMisses++;
95#endif
96 return true;
97}
98
99
100static const char *NameForHandle(unsigned int type)
101{
102 switch (type)
103 {
104 case 0:
105 return "weak short";
106 case 1:
107 return "weak long";
108 case 2:
109 return "strong";
110 case 3:
111 return "pinned";
112 case 5:
113 return "ref counted";
114 case 6:
115 return "dependent";
116 case 7:
117 return "async pinned";
118 case 8:
119 return "sized ref";
120 }
121
122 return "unknown";
123}
124
125///////////////////////////////////////////////////////////////////////////////
126// GCRoot functions to cleanup data.
127///////////////////////////////////////////////////////////////////////////////
128void GCRootImpl::ClearSizeData()
129{
130 mConsidered.clear();
131 mSizes.clear();
132}
133
134void GCRootImpl::ClearAll()
135{
136 ClearNodes();
137
138 {
139 std::unordered_map<TADDR, MTInfo*>::iterator itr;
140 for (itr = mMTs.begin(); itr != mMTs.end(); ++itr)
141 delete itr->second;
142 }
143
144 {
145 std::unordered_map<TADDR, RootNode*>::iterator itr;
146 for (itr = mTargets.begin(); itr != mTargets.end(); ++itr)
147 delete itr->second;
148 }
149
150 mMTs.clear();
151 mTargets.clear();
152 mConsidered.clear();
153 mSizes.clear();
154 mDependentHandleMap.clear();
155 mCache.ClearStats();
156
157 mAll = false;
158 mSize = false;
159}
160
161void GCRootImpl::ClearNodes()
162{
163 std::list<RootNode*>::iterator itr;
164
165 for (itr = mCleanupList.begin(); itr != mCleanupList.end(); ++itr)
166 delete *itr;
167
168 mCleanupList.clear();
169 mRootNewList.clear();
170}
171
172GCRootImpl::RootNode *GCRootImpl::NewNode(TADDR obj, MTInfo *mtInfo, bool fromDependent)
173{
174 // We need to create/destroy a TON of nodes during execution of GCRoot functions.
175 // To avoid heap fragmentation (and since it's faster), we don't actually new/delete
176 // nodes unless we have to. Instead we keep a stl list with free nodes to use.
177 RootNode *toReturn = NULL;
178
179 if (mRootNewList.size())
180 {
181 toReturn = mRootNewList.back();
182 mRootNewList.pop_back();
183 }
184 else
185 {
186 toReturn = new RootNode();
187 mCleanupList.push_back(toReturn);
188 }
189
190 toReturn->Object = obj;
191 toReturn->MTInfo = mtInfo;
192 toReturn->FromDependentHandle = fromDependent;
193 return toReturn;
194}
195
196void GCRootImpl::DeleteNode(RootNode *node)
197{
198 // Add node to the "new list".
199 node->Clear();
200 mRootNewList.push_back(node);
201}
202
203void GCRootImpl::GetDependentHandleMap(std::unordered_map<TADDR, std::list<TADDR>> &map)
204{
205 unsigned int type = HNDTYPE_DEPENDENT;
206 ToRelease<ISOSHandleEnum> handles;
207
208 HRESULT hr = g_sos->GetHandleEnumForTypes(&type, 1, &handles);
209
210 if (FAILED(hr))
211 {
212 ExtOut("Failed to walk dependent handles. GCRoot may miss paths.\n");
213 return;
214 }
215
216 SOSHandleData data[4];
217 unsigned int fetched = 0;
218
219 do
220 {
221 hr = handles->Next(_countof(data), data, &fetched);
222
223 if (FAILED(hr))
224 {
225 ExtOut("Error walking dependent handles. GCRoot may miss paths.\n");
226 return;
227 }
228
229 for (unsigned int i = 0; i < fetched; ++i)
230 {
231 if (data[i].Secondary != 0)
232 {
233 TADDR obj = 0;
234 TADDR target = TO_TADDR(data[i].Secondary);
235
236 MOVE(obj, TO_TADDR(data[i].Handle));
237
238 map[obj].push_back(target);
239 }
240 }
241 } while (fetched == _countof(data));
242}
243
244///////////////////////////////////////////////////////////////////////////////
245// Public functions.
246///////////////////////////////////////////////////////////////////////////////
247int GCRootImpl::PrintRootsForObject(TADDR target, bool all, bool noStacks)
248{
249 ClearAll();
250 GetDependentHandleMap(mDependentHandleMap);
251
252 mAll = all;
253
254 // Add "target" to the mTargets list.
255 TADDR mt = ReadPointerCached(target);
256 RootNode *node = NewNode(target, GetMTInfo(mt));
257 mTargets[target] = node;
258
259 // Look for roots on the HandleTable, FQ, and all threads.
260 int count = 0;
261
262 if (!noStacks)
263 count += PrintRootsOnAllThreads();
264
265 count += PrintRootsOnHandleTable();
266 count += PrintRootsOnFQ();
267
268 if(count == 0)
269 {
270 count += PrintRootsOnFQ(true);
271 if(count)
272 {
273 ExtOut("Warning: These roots are from finalizable objects that are not yet ready for finalization.\n");
274 ExtOut("This is to handle the case where objects re-register themselves for finalization.\n");
275 ExtOut("These roots may be false positives.\n");
276 }
277 }
278
279 mCache.PrintStats(__FUNCTION__);
280 return count;
281}
282
283
284bool GCRootImpl::PrintPathToObject(TADDR root, TADDR target)
285{
286 ClearAll();
287 GetDependentHandleMap(mDependentHandleMap);
288
289 // Add "target" to the mTargets list.
290 TADDR mt = ReadPointerCached(target);
291 RootNode *node = NewNode(target, GetMTInfo(mt));
292 mTargets[target] = node;
293
294 // Check to see if the root reaches the target.
295 RootNode *path = FindPathToTarget(root);
296 if (path)
297 {
298 ExtOut("%p %S\n", SOS_PTR(path->Object), path->GetTypeName());
299 path = path->Next;
300
301 while (path)
302 {
303 ExtOut(" -> %p %S%s\n",SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
304 path = path->Next;
305 }
306
307 mCache.PrintStats(__FUNCTION__);
308 return true;
309 }
310
311 mCache.PrintStats(__FUNCTION__);
312 return false;
313}
314
315size_t GCRootImpl::ObjSize(TADDR root)
316{
317 // Calculates the size of the closure of objects kept alive by root.
318 ClearAll();
319 GetDependentHandleMap(mDependentHandleMap);
320
321 // mSize tells GCRootImpl to build the "mSizes" table with the total size
322 // each object roots.
323 mSize = true;
324
325 // Note that we are calling the same method as we would to locate the rooting
326 // chain for an object, but we haven't added any items to mTargets. This means
327 // the algorithm will scan all objects and never terminate until it has walked
328 // all objects in the closure.
329 FindPathToTarget(root);
330
331 mCache.PrintStats(__FUNCTION__);
332 return mSizes[root];
333}
334
335void GCRootImpl::ObjSize()
336{
337 ClearAll();
338 GetDependentHandleMap(mDependentHandleMap);
339 mSize = true;
340
341 // Walks all roots in the process, and prints out the object size for each one.
342 PrintRootsOnAllThreads();
343 PrintRootsOnHandleTable();
344 PrintRootsOnFQ();
345
346 mCache.PrintStats(__FUNCTION__);
347}
348
349
350const std::unordered_set<TADDR> &GCRootImpl::GetLiveObjects(bool excludeFQ)
351{
352 ClearAll();
353 GetDependentHandleMap(mDependentHandleMap);
354
355 // Walk each root in the process without setting a target. This has the effect of
356 // causing us to walk every object in the process, adding them to mConsidered as we
357 // go.
358 PrintRootsOnAllThreads();
359 PrintRootsOnHandleTable();
360
361 if (!excludeFQ)
362 PrintRootsOnFQ();
363
364 mCache.PrintStats(__FUNCTION__);
365 return mConsidered;
366}
367
368int GCRootImpl::FindRoots(int gen, TADDR target)
369{
370 ClearAll();
371 GetDependentHandleMap(mDependentHandleMap);
372
373 if (gen == -1 || ((UINT)gen) == GetMaxGeneration())
374 {
375 // If this is a gen 2 !FindRoots, just do a regular !GCRoot
376 return PrintRootsForObject(target, false, false);
377 }
378 else
379 {
380 // Otherwise walk all roots for only the given generation.
381 int count = PrintRootsInOlderGen();
382 count += PrintRootsOnHandleTable(gen);
383 count += PrintRootsOnFQ();
384 return count;
385 }
386}
387
388
389
390///////////////////////////////////////////////////////////////////////////////
391// GCRoot Methods for printing out results.
392///////////////////////////////////////////////////////////////////////////////
393void GCRootImpl::ReportSizeInfo(const SOSHandleData &handle, TADDR obj)
394{
395 // Print size for a handle (!objsize)
396 TADDR mt = ReadPointer(obj);
397 MTInfo *mtInfo = GetMTInfo(mt);
398
399 const WCHAR *type = mtInfo ? mtInfo->GetTypeName() : W("unknown type");
400
401 size_t size = mSizes[obj];
402 ExtOut("Handle (%s): %p -> %p: %d (0x%x) bytes (%S)\n", NameForHandle(handle.Type), SOS_PTR(handle.Handle),
403 SOS_PTR(obj), size, size, type);
404}
405
406
407void GCRootImpl::ReportSizeInfo(DWORD thread, const SOSStackRefData &stackRef, TADDR obj)
408{
409 // Print size for a stack root (!objsize)
410 WString frame;
411 if (stackRef.SourceType == SOS_StackSourceIP)
412 frame = MethodNameFromIP(stackRef.Source);
413 else
414 frame = GetFrameFromAddress(TO_TADDR(stackRef.Source));
415
416 WString regOutput = BuildRegisterOutput(stackRef);
417
418 TADDR mt = ReadPointer(obj);
419 MTInfo *mtInfo = GetMTInfo(mt);
420 const WCHAR *type = mtInfo ? mtInfo->GetTypeName() : W("unknown type");
421
422 size_t size = mSizes[obj];
423 ExtOut("Thread %x (%S): %S: %d (0x%x) bytes (%S)\n", thread, frame.c_str(), regOutput.c_str(), size, size, type);
424}
425
426void GCRootImpl::ReportOneHandlePath(const SOSHandleData &handle, RootNode *path, bool printHeader)
427{
428 if (printHeader)
429 ExtOut("HandleTable:\n");
430
431 ExtOut(" %p (%s handle)\n", SOS_PTR(handle.Handle), NameForHandle(handle.Type));
432 while (path)
433 {
434 ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
435 path = path->Next;
436 }
437
438 ExtOut("\n");
439}
440
441void GCRootImpl::ReportOnePath(DWORD thread, const SOSStackRefData &stackRef, RootNode *path, bool printThread, bool printFrame)
442{
443 if (printThread)
444 ExtOut("Thread %x:\n", thread);
445
446 if (printFrame)
447 {
448 if (stackRef.SourceType == SOS_StackSourceIP)
449 {
450 WString methodName = MethodNameFromIP(stackRef.Source);
451 ExtOut(" %p %p %S\n", SOS_PTR(stackRef.StackPointer), SOS_PTR(stackRef.Source), methodName.c_str());
452 }
453 else
454 {
455 WString frameName = GetFrameFromAddress(TO_TADDR(stackRef.Source));
456 ExtOut(" %p %S\n", SOS_PTR(stackRef.Source), frameName.c_str());
457 }
458 }
459
460 WString regOutput = BuildRegisterOutput(stackRef, false);
461 ExtOut(" %S\n", regOutput.c_str());
462
463 while (path)
464 {
465 ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
466 path = path->Next;
467 }
468
469 ExtOut("\n");
470}
471
472void GCRootImpl::ReportOneFQEntry(TADDR root, RootNode *path, bool printHeader)
473{
474 if (printHeader)
475 ExtOut("Finalizer Queue:\n");
476
477 ExtOut(" %p\n", SOS_PTR(root));
478 while (path)
479 {
480 ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
481 path = path->Next;
482 }
483
484 ExtOut("\n");
485}
486
487void GCRootImpl::ReportOlderGenEntry(TADDR root, RootNode *path, bool printHeader)
488{
489 if (printHeader)
490 ExtOut("Older Generation:\n");
491
492 ExtOut(" %p\n", SOS_PTR(root));
493 while (path)
494 {
495 ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
496 path = path->Next;
497 }
498
499 ExtOut("\n");
500}
501
502//////////////////////////////////////////////////////
503int GCRootImpl::PrintRootsInOlderGen()
504{
505 // Use a different linear read cache here instead of polluting the object read cache.
506 LinearReadCache cache(512);
507
508 if (!IsServerBuild())
509 {
510 DacpGcHeapAnalyzeData analyzeData;
511 if (analyzeData.Request(g_sos) != S_OK)
512 {
513 ExtErr("Error requesting gc heap analyze data\n");
514 return 0;
515 }
516
517 if (!analyzeData.heap_analyze_success)
518 {
519 ExtOut("Failed to gather needed data, possibly due to memory constraints in the debuggee.\n");
520 ExtOut("To try again re-issue the !FindRoots -gen <N> command.\n");
521 return 0;
522 }
523
524 ExtDbgOut("internal_root_array = %#p\n", SOS_PTR(analyzeData.internal_root_array));
525 ExtDbgOut("internal_root_array_index = %#p\n", SOS_PTR(analyzeData.internal_root_array_index));
526
527 TADDR start = TO_TADDR(analyzeData.internal_root_array);
528 TADDR stop = TO_TADDR(analyzeData.internal_root_array + sizeof(TADDR) * (size_t)analyzeData.internal_root_array_index);
529
530 return PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOlderGenEntry, true);
531 }
532 else
533 {
534 int total = 0;
535 DWORD dwAllocSize;
536 DWORD dwNHeaps = GetGcHeapCount();
537 if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
538 {
539 ExtErr("Failed to get GCHeaps: integer overflow\n");
540 return 0;
541 }
542
543 CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
544 if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
545 {
546 ExtErr("Failed to get GCHeaps\n");
547 return 0;
548 }
549
550 for (UINT n = 0; n < dwNHeaps; n ++)
551 {
552 DacpGcHeapAnalyzeData analyzeData;
553 if (analyzeData.Request(g_sos, heapAddrs[n]) != S_OK)
554 {
555 ExtErr("Error requesting gc heap analyze data for heap %p\n", SOS_PTR(heapAddrs[n]));
556 continue;
557 }
558
559 if (!analyzeData.heap_analyze_success)
560 {
561 ExtOut("Failed to gather needed data, possibly due to memory constraints in the debuggee.\n");
562 ExtOut("To try again re-issue the !FindRoots -gen <N> command.\n");
563 continue;
564 }
565
566 ExtDbgOut("internal_root_array = %#p\n", SOS_PTR(analyzeData.internal_root_array));
567 ExtDbgOut("internal_root_array_index = %#p\n", SOS_PTR(analyzeData.internal_root_array_index));
568
569 TADDR start = TO_TADDR(analyzeData.internal_root_array);
570 TADDR stop = TO_TADDR(analyzeData.internal_root_array + sizeof(TADDR) * (size_t)analyzeData.internal_root_array_index);
571
572 total += PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOlderGenEntry, total == 0);
573 }
574
575 return total;
576 }
577}
578
579
580int GCRootImpl::PrintRootsOnFQ(bool notReadyForFinalization)
581{
582 // Here are objects kept alive by objects in the finalizer queue.
583 DacpGcHeapDetails heapDetails;
584
585 // Use a different linear read cache here instead of polluting the object read cache.
586 LinearReadCache cache(512);
587
588 if (!IsServerBuild())
589 {
590 if (heapDetails.Request(g_sos) != S_OK)
591 {
592 ExtErr("Error requesting heap data.\n");
593 return 0;
594 }
595
596 // If we include objects that are not ready for finalization, we may report
597 // false positives. False positives occur if the object is not ready for finalization
598 // and does not re-register itself for finalization inside the finalizer.
599 TADDR start = 0;
600 TADDR stop = 0;
601 if(notReadyForFinalization)
602 {
603 start = TO_TADDR(SegQueue(heapDetails, gen_segment(GetMaxGeneration())));
604 stop = TO_TADDR(SegQueueLimit(heapDetails, CriticalFinalizerListSeg));
605 }
606 else
607 {
608 start = TO_TADDR(SegQueue(heapDetails, CriticalFinalizerListSeg));
609 stop = TO_TADDR(SegQueue(heapDetails, FinalizerListSeg));
610 }
611
612 return PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOneFQEntry, true);
613 }
614 else
615 {
616 DWORD dwAllocSize;
617 DWORD dwNHeaps = GetGcHeapCount();
618 if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
619 {
620 ExtErr("Failed to get GCHeaps: integer overflow\n");
621 return 0;
622 }
623
624 CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
625 if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
626 {
627 ExtErr("Error requesting heap data.\n");
628 return 0;
629 }
630
631 int total = 0;
632 for (UINT n = 0; n < dwNHeaps; n ++)
633 {
634 if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
635 {
636 ExtErr("Error requesting heap data for heap %d.\n", n);
637 continue;
638 }
639
640 // If we include objects that are not ready for finalization, we may report
641 // false positives. False positives occur if the object is not ready for finalization
642 // and does not re-register itself for finalization inside the finalizer.
643 TADDR start = 0;
644 TADDR stop = 0;
645 if(notReadyForFinalization)
646 {
647 start = TO_TADDR(SegQueue(heapDetails, gen_segment(GetMaxGeneration())));
648 stop = TO_TADDR(SegQueueLimit(heapDetails, CriticalFinalizerListSeg));
649 }
650 else
651 {
652 start = TO_TADDR(SegQueue(heapDetails, CriticalFinalizerListSeg));
653 stop = TO_TADDR(SegQueueLimit(heapDetails, FinalizerListSeg));
654 }
655
656 total += PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOneFQEntry, total == 0);
657 }
658
659 return total;
660 }
661}
662
663int GCRootImpl::PrintRootsInRange(LinearReadCache &cache, TADDR start, TADDR stop, ReportCallback func, bool printHeader)
664{
665 int total = 0;
666
667 // Walk the range [start, stop) and consider each pointer in the range as a root.
668 while (start < stop)
669 {
670 if (IsInterrupt())
671 return total;
672
673 // Use the cache parameter here instead of mCache. If you use mCache it will be reset
674 // when calling into FindPathToTarget.
675 TADDR root = 0;
676 bool res = cache.Read(start, &root, true);
677
678 if (res && root)
679 {
680 RootNode *path = FindPathToTarget(root);
681 if (path)
682 {
683 func(root, path, printHeader);
684 total++;
685 printHeader = false;
686 }
687 }
688
689 start += sizeof(TADDR);
690 }
691
692 return total;
693}
694
695int GCRootImpl::PrintRootsOnAllThreads()
696{
697 ArrayHolder<DWORD_PTR> threadList = NULL;
698 int numThreads = 0;
699
700 // GetThreadList calls ReportOOM so we don't need to do that here.
701 HRESULT hr = GetThreadList(&threadList, &numThreads);
702 if (FAILED(hr) || !threadList)
703 return 0;
704
705 // Walk each thread and process the roots on it.
706 int total = 0;
707 DacpThreadData vThread;
708 for (int i = 0; i < numThreads; i++)
709 {
710 if (IsInterrupt())
711 return total;
712
713 if (FAILED(vThread.Request(g_sos, threadList[i])))
714 continue;
715
716 if (vThread.osThreadId)
717 total += PrintRootsOnThread(vThread.osThreadId);
718 }
719
720 return total;
721}
722
723int GCRootImpl::PrintRootsOnThread(DWORD osThreadId)
724{
725 // Grab all object rootson the thread.
726 unsigned int refCount = 0;
727 ArrayHolder<SOSStackRefData> refs = NULL;
728
729 int total = 0;
730 bool first = true;
731 if (FAILED(::GetGCRefs(osThreadId, &refs, &refCount, NULL, NULL)))
732 {
733 ExtOut("Failed to walk thread %x\n", osThreadId);
734 return total;
735 }
736
737 // Walk each non-null root, find if it contains a path to the target,
738 // and if so print it out.
739 CLRDATA_ADDRESS prevSource = 0, prevSP = 0;
740 for (unsigned int i = 0; i < refCount; ++i)
741 {
742 if (IsInterrupt())
743 return total;
744
745 if (refs[i].Object)
746 {
747 if (mSize)
748 ClearSizeData();
749
750 RootNode *path = FindPathToTarget(TO_TADDR(refs[i].Object));
751 if (path)
752 {
753 bool reportFrame = refs[i].Source != prevSource || refs[i].StackPointer != prevSP;
754 ReportOnePath(osThreadId, refs[i], path, first, reportFrame);
755 first = false;
756 total++;
757 }
758
759 if (mSize)
760 ReportSizeInfo(osThreadId, refs[i], TO_TADDR(refs[i].Object));
761 }
762 }
763
764 return total;
765}
766
767int GCRootImpl::PrintRootsOnHandleTable(int gen)
768{
769 // Get handle data.
770 ToRelease<ISOSHandleEnum> pEnum = NULL;
771 HRESULT hr = S_OK;
772
773 if (gen == -1 || (ULONG)gen == GetMaxGeneration())
774 hr = g_sos->GetHandleEnum(&pEnum);
775 else
776 hr = g_sos->GetHandleEnumForGC(gen, &pEnum);
777
778 if (FAILED(hr))
779 {
780 ExtOut("Failed to walk the HandleTable!\n");
781 return 0;
782 }
783
784 int total = 0;
785 unsigned int fetched = 0;
786 SOSHandleData handles[8];
787
788 bool printHeader = true;
789 do
790 {
791 // Fetch more handles
792 hr = pEnum->Next(_countof(handles), handles, &fetched);
793 if (FAILED(hr))
794 {
795 ExtOut("Failed to request more handles.");
796 return total;
797 }
798
799 // Find rooting info for each handle.
800 for (unsigned int i = 0; i < fetched; ++i)
801 {
802 if (IsInterrupt())
803 return total;
804
805 // Ignore handles which aren't actually roots.
806 if (!handles[i].StrongReference)
807 continue;
808
809 // clear the size table
810 if (mAll)
811 ClearSizeData();
812
813 // Get the object the handle points to.
814 TADDR root = ReadPointer(TO_TADDR(handles[i].Handle));
815
816 // Only inspect handle if the object is non-null, and not one we've already walked.
817 if (root)
818 {
819 // Find all paths to the object and don't clean up the return value.
820 // It's tracked by mCleanupList.
821 RootNode *path = FindPathToTarget(root);
822 if (path)
823 {
824 ReportOneHandlePath(handles[i], path, printHeader);
825 printHeader = false;
826 total++;
827 }
828
829 if (mSize)
830 ReportSizeInfo(handles[i], root);
831 }
832 }
833 }
834 while (_countof(handles) == fetched);
835
836 return total;
837}
838
839GCRootImpl::RootNode *GCRootImpl::FilterRoots(RootNode *&list)
840{
841 // Filter the list of GC refs:
842 // - Remove objects that we have already considered
843 // - Check to see if we've located the target object (or an object which points to the target).
844 RootNode *curr = list;
845 RootNode *keep = NULL;
846
847 while (curr)
848 {
849 // We don't check for Control-C in this loop to avoid inconsistent data.
850 RootNode *curr_next = curr->Next;
851
852 std::unordered_map<TADDR, RootNode *>::iterator targetItr = mTargets.find(curr->Object);
853 if (targetItr != mTargets.end())
854 {
855 // We found the object we are looking for (or an object which points to it)!
856 // Return the target, propogate whether we got the target from a dependent handle.
857 targetItr->second->FromDependentHandle = curr->FromDependentHandle;
858 return targetItr->second;
859 }
860 else if (mConsidered.find(curr->Object) != mConsidered.end())
861 {
862 curr->Remove(list);
863
864 DeleteNode(curr);
865 }
866
867 curr = curr_next;
868 }
869
870 return NULL;
871}
872
873/* This is the core of the GCRoot algorithm. It is:
874 * 1. Start with a list of "targets" (objects we are trying to find the roots for) and a root
875 * in the process.
876 * 2. Let the root be "curr".
877 * 3. Find all objects curr points to and place them in curr->GCRefs (a linked list).
878 * 4. Walk curr->GCRefs. If we find any of the "targets", return the current path. If not,
879 * filter out any objects we've already considered (the mConsidered set).
880 * 5. Look at curr->GCRefs:
881 * 5a. If curr->GCRefs is NULL then we have walked all references of this object. Pop "curr"
882 * from the current path (curr = curr->Prev). If curr is NULL, we walked all objects and
883 * didn't find a target, return NULL. If curr is non-null, goto 5.
884 * 5b. If curr->GCRefs is non-NULL, pop one entry and push it onto the path (that is:
885 * curr->Next = curr->GCRefs; curr = curr->Next; curr->GCRefs = curr->GCRefs->Next)
886 * 6. Goto 3.
887 */
888GCRootImpl::RootNode *GCRootImpl::FindPathToTarget(TADDR root)
889{
890 // Early out. We may have already looked at this object.
891 std::unordered_map<TADDR, RootNode *>::iterator targetItr = mTargets.find(root);
892 if (targetItr != mTargets.end())
893 return targetItr->second;
894 else if (mConsidered.find(root) != mConsidered.end())
895 return NULL;
896
897 // Add obj as a considered node (since we are considering it now).
898 mConsidered.insert(root);
899
900 // Create path.
901 RootNode *path = NewNode(root);
902
903 RootNode *curr = path;
904 while (curr)
905 {
906 if (IsInterrupt())
907 return NULL;
908
909 // If this is a new reference we are walking, we haven't filled the list of objects
910 // this one points to. Update that first.
911 if (!curr->FilledRefs)
912 {
913 // Get the list of GC refs.
914 curr->GCRefs = GetGCRefs(path, curr);
915
916 // Filter the refs to remove objects we've already inspected.
917 RootNode *foundTarget = FilterRoots(curr->GCRefs);
918
919 // If we've found the target, great! Return the path to the target.
920 if (foundTarget)
921 {
922 // Link the current to the target.
923 curr->Next = foundTarget;
924 foundTarget->Prev = curr;
925
926 // If the user requested all paths, set each node in the path to be a target.
927 // Normally, we don't consider a node we've already seen, which means if we don't
928 // get a *completely* unique path, it's not printed out. By adding each of the
929 // nodes in the paths we find as potential targets, it prints out *every* path
930 // to the target, including ones with duplicate nodes. This is much slower.
931 if (mAll)
932 {
933 RootNode *tmp = path;
934
935 while (tmp)
936 {
937 if (mTargets.find(tmp->Object) != mTargets.end())
938 break;
939
940 mTargets[tmp->Object] = tmp;
941 tmp = tmp->Next;
942 }
943 }
944
945 return path;
946 }
947 }
948
949 // We have filled the references, now walk them depth-first.
950 if (curr->GCRefs)
951 {
952 RootNode *next = curr->GCRefs;
953 curr->GCRefs = next->Next;
954
955 if (mConsidered.find(next->Object) != mConsidered.end())
956 {
957 // Whoops. This object was considered deeper down the tree, so we
958 // don't need to do it again. Delete this node and continue looping.
959 DeleteNode(next);
960 }
961 else
962 {
963 // Clear associations.
964 if (curr->GCRefs)
965 curr->GCRefs->Prev = NULL;
966
967 next->Next = NULL;
968
969 // Link curr and next, make next the current node.
970 curr->Next = next;
971 next->Prev = curr;
972 curr = next;
973
974 // Finally, insert the current object into the considered set.
975 mConsidered.insert(curr->Object);
976 // Now the next iteration will operate on "next".
977 }
978 }
979 else
980 {
981 // This object has no more GCRefs. We now need to "pop" it from the current path.
982 RootNode *tmp = curr;
983 curr = curr->Prev;
984 DeleteNode(tmp);
985 }
986 }
987
988 return NULL;
989}
990
991
992GCRootImpl::RootNode *GCRootImpl::GetGCRefs(RootNode *path, RootNode *node)
993{
994 // If this node doesn't have the method table ready, fill it out
995 TADDR obj = node->Object;
996 if (!node->MTInfo)
997 {
998 TADDR mt = ReadPointerCached(obj);
999 MTInfo *mtInfo = GetMTInfo(mt);
1000 node->MTInfo = mtInfo;
1001 }
1002
1003 node->FilledRefs = true;
1004
1005 // MTInfo can be null if we encountered an error reading out of the target
1006 // process, just early out here as if it has no references.
1007 if (!node->MTInfo)
1008 return NULL;
1009
1010 // Only calculate the size if we need it.
1011 size_t objSize = 0;
1012 if (mSize || node->MTInfo->ContainsPointers || node->MTInfo->Collectible)
1013 {
1014 objSize = GetSizeOfObject(obj, node->MTInfo);
1015
1016 // Update object size list, if requested.
1017 if (mSize)
1018 {
1019 mSizes[obj] = 0;
1020
1021 while (path)
1022 {
1023 mSizes[path->Object] += objSize;
1024 path = path->Next;
1025 }
1026 }
1027 }
1028
1029 // Early out: If the object doesn't contain any pointers, return.
1030 if (!node->MTInfo->ContainsPointers && !node->MTInfo->Collectible)
1031 return NULL;
1032
1033 // Make sure we have the object's data in the cache.
1034 mCache.EnsureRangeInCache(obj, (unsigned int)objSize);
1035
1036 // Storage for the gc refs.
1037 RootNode *refs = NewNode();
1038 RootNode *curr = refs;
1039
1040 // Walk the GCDesc, fill "refs" with non-null references.
1041 CGCDesc *gcdesc = node->MTInfo->GCDesc;
1042 bool aovc = node->MTInfo->ArrayOfVC;
1043 for (sos::RefIterator itr(obj, gcdesc, aovc, &mCache); itr; ++itr)
1044 {
1045 if (*itr)
1046 {
1047 curr->Next = NewNode(*itr);
1048 curr->Next->Prev = curr;
1049 curr = curr->Next;
1050 }
1051 }
1052
1053 // Add edges from dependent handles.
1054 std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find(obj);
1055 if (itr != mDependentHandleMap.end())
1056 {
1057 for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr)
1058 {
1059 curr->Next = NewNode(*litr, NULL, true);
1060 curr->Next->Prev = curr;
1061 curr = curr->Next;
1062 }
1063 }
1064
1065 // The gcrefs actually start on refs->Next.
1066 curr = refs;
1067 refs = refs->Next;
1068 DeleteNode(curr);
1069
1070 return refs;
1071}
1072
1073DWORD GCRootImpl::GetComponents(TADDR obj, TADDR mt)
1074{
1075 // Get the number of components in the object (for arrays and such).
1076 DWORD Value = 0;
1077
1078 // If we fail to read out the number of components, let's assume 0 so we don't try to
1079 // read further data from the object.
1080 if (!mCache.Read(obj + sizeof(TADDR), &Value, false))
1081 return 0;
1082
1083 // The component size on a String does not contain the trailing NULL character,
1084 // so we must add that ourselves.
1085 if(TO_TADDR(g_special_usefulGlobals.StringMethodTable) == mt)
1086 return Value+1;
1087
1088 return Value;
1089}
1090
1091// Get the size of the object.
1092size_t GCRootImpl::GetSizeOfObject(TADDR obj, MTInfo *info)
1093{
1094 size_t res = info->BaseSize;
1095
1096 if (info->ComponentSize)
1097 {
1098 // this is an array, so the size has to include the size of the components. We read the number
1099 // of components from the target and multiply by the component size to get the size.
1100 DWORD components = GetComponents(obj, info->MethodTable);
1101 res += info->ComponentSize * components;
1102 }
1103
1104#ifdef _TARGET_WIN64_
1105 // On x64 we do an optimization to save 4 bytes in almost every string we create, so
1106 // pad to min object size if necessary.
1107 if (res < min_obj_size)
1108 res = min_obj_size;
1109#endif // _TARGET_WIN64_
1110
1111 return (res > 0x10000) ? AlignLarge(res) : Align(res);
1112}
1113
1114GCRootImpl::MTInfo *GCRootImpl::GetMTInfo(TADDR mt)
1115{
1116 // Remove lower bits in case we are in mark phase
1117 mt &= ~3;
1118
1119 // Do we already have this MethodTable?
1120 std::unordered_map<TADDR, MTInfo *>::iterator itr = mMTs.find(mt);
1121
1122 if (itr != mMTs.end())
1123 return itr->second;
1124
1125 MTInfo *curr = new MTInfo;
1126 curr->MethodTable = mt;
1127
1128 // Get Base/Component size.
1129 DacpMethodTableData dmtd;
1130
1131 if (dmtd.Request(g_sos, mt) != S_OK)
1132 {
1133 delete curr;
1134 return NULL;
1135 }
1136
1137 // Fill out size info.
1138 curr->BaseSize = (size_t)dmtd.BaseSize;
1139 curr->ComponentSize = (size_t)dmtd.ComponentSize;
1140 curr->ContainsPointers = dmtd.bContainsPointers ? true : false;
1141
1142 // The following request doesn't work on older runtimes. For those, the
1143 // objects would just look like non-collectible, which is acceptable.
1144 DacpMethodTableCollectibleData dmtcd;
1145 if (SUCCEEDED(dmtcd.Request(g_sos, mt)))
1146 {
1147 curr->Collectible = dmtcd.bCollectible ? true : false;
1148 curr->LoaderAllocatorObjectHandle = TO_TADDR(dmtcd.LoaderAllocatorObjectHandle);
1149 }
1150
1151 // If this method table contains pointers, fill out and cache the GCDesc.
1152 if (curr->ContainsPointers)
1153 {
1154 int nEntries;
1155
1156 if (FAILED(MOVE(nEntries, mt-sizeof(TADDR))))
1157 {
1158 ExtOut("Failed to request number of entries.");
1159 delete curr;
1160 return NULL;
1161 }
1162
1163 if (nEntries < 0)
1164 {
1165 curr->ArrayOfVC = true;
1166 nEntries = -nEntries;
1167 }
1168 else
1169 {
1170 curr->ArrayOfVC = false;
1171 }
1172
1173 size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(TADDR);
1174 curr->Buffer = new TADDR[nSlots];
1175
1176 if (curr->Buffer == NULL)
1177 {
1178 ReportOOM();
1179 delete curr;
1180 return NULL;
1181 }
1182
1183 if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(TADDR)), curr->Buffer, (ULONG)(nSlots*sizeof(TADDR)), NULL)))
1184 {
1185 ExtOut("Failed to read GCDesc for MethodTable %p.\n", SOS_PTR(mt));
1186 delete curr;
1187 return NULL;
1188 }
1189
1190 // Construct the GCDesc map and series.
1191 curr->GCDesc = (CGCDesc *)(curr->Buffer+nSlots);
1192 }
1193
1194 mMTs[mt] = curr;
1195 return curr;
1196}
1197
1198
1199TADDR GCRootImpl::ReadPointer(TADDR location)
1200{
1201 // Reads a pointer from the cache, but doesn't update the cache if it wasn't in it.
1202 TADDR obj = NULL;
1203 bool res = mCache.Read(location, &obj, false);
1204
1205 if (!res)
1206 return NULL;
1207
1208 return obj;
1209}
1210
1211TADDR GCRootImpl::ReadPointerCached(TADDR location)
1212{
1213 // Reads a pointer from the cache, but updates the cache if it wasn't in it.
1214 TADDR obj = NULL;
1215 bool res = mCache.Read(location, &obj, true);
1216
1217 if (!res)
1218 return NULL;
1219
1220 return obj;
1221}
1222
1223///////////////////////////////////////////////////////////////////////////////
1224
1225UINT FindAllPinnedAndStrong(DWORD_PTR handlearray[], UINT arraySize)
1226{
1227 unsigned int fetched = 0;
1228 SOSHandleData data[64];
1229 UINT pos = 0;
1230
1231 // We do not call GetHandleEnumByType here with a list of strong handles since we would be
1232 // statically setting the list of strong handles, which could change in a future release.
1233 // Instead we rely on the dac to provide whether a handle is strong or not.
1234 ToRelease<ISOSHandleEnum> handles;
1235 HRESULT hr = g_sos->GetHandleEnum(&handles);
1236 if (FAILED(hr))
1237 {
1238 // This should basically never happen unless there's an OOM.
1239 ExtOut("Failed to enumerate GC handles. HRESULT=%x.\n", hr);
1240 return 0;
1241 }
1242
1243 do
1244 {
1245 hr = handles->Next(_countof(data), data, &fetched);
1246
1247 if (FAILED(hr))
1248 {
1249 ExtOut("Failed to enumerate GC handles. HRESULT=%x.\n", hr);
1250 break;
1251 }
1252
1253 for (unsigned int i = 0; i < fetched; ++i)
1254 {
1255 if (pos >= arraySize)
1256 {
1257 ExtOut("Buffer overflow while enumerating handles.\n");
1258 return pos;
1259 }
1260
1261 if (data[i].StrongReference)
1262 {
1263 handlearray[pos++] = (DWORD_PTR)data[i].Handle;
1264 }
1265 }
1266 } while (fetched == _countof(data));
1267
1268 return pos;
1269}
1270
1271
1272
1273void PrintNotReachableInRange(TADDR rngStart, TADDR rngEnd, BOOL bExcludeReadyForFinalization, HeapStat* hpstat, BOOL bShort)
1274{
1275 GCRootImpl gcroot;
1276 const std::unordered_set<TADDR> &liveObjs = gcroot.GetLiveObjects(bExcludeReadyForFinalization == TRUE);
1277
1278 LinearReadCache cache(512);
1279 cache.EnsureRangeInCache(rngStart, (unsigned int)(rngEnd-rngStart));
1280
1281 for (TADDR p = rngStart; p < rngEnd; p += sizeof(TADDR))
1282 {
1283 if (IsInterrupt())
1284 break;
1285
1286 TADDR header = 0;
1287 TADDR obj = 0;
1288 TADDR taddrMT = 0;
1289
1290 bool read = cache.Read(p-sizeof(SIZEOF_OBJHEADER), &header);
1291 read = read && cache.Read(p, &obj);
1292 if (read && ((header & BIT_SBLK_FINALIZER_RUN) == 0) && liveObjs.find(obj) == liveObjs.end())
1293 {
1294 if (bShort)
1295 {
1296 DMLOut("%s\n", DMLObject(obj));
1297 }
1298 else
1299 {
1300 DMLOut("%s ", DMLObject(obj));
1301 if (SUCCEEDED(GetMTOfObject(obj, &taddrMT)) && taddrMT)
1302 {
1303 size_t s = ObjectSize(obj);
1304 if (hpstat)
1305 {
1306 hpstat->Add(taddrMT, (DWORD)s);
1307 }
1308 }
1309 }
1310 }
1311 }
1312
1313 if (!bShort)
1314 ExtOut("\n");
1315}
1316
1317
1318////////////////////////////////////////////////////////////////////////////////
1319//
1320// Some defines for cards taken from gc code
1321//
1322#define card_word_width ((size_t)32)
1323
1324//
1325// The value of card_size is determined empirically according to the average size of an object
1326// In the code we also rely on the assumption that one card_table entry (DWORD) covers an entire os page
1327//
1328#if defined (_TARGET_WIN64_)
1329#define card_size ((size_t)(2*DT_GC_PAGE_SIZE/card_word_width))
1330#else
1331#define card_size ((size_t)(DT_GC_PAGE_SIZE/card_word_width))
1332#endif //_TARGET_WIN64_
1333
1334// so card_size = 128 on x86, 256 on x64
1335
1336inline
1337size_t card_word (size_t card)
1338{
1339 return card / card_word_width;
1340}
1341
1342inline
1343unsigned card_bit (size_t card)
1344{
1345 return (unsigned)(card % card_word_width);
1346}
1347
1348inline
1349size_t card_of ( BYTE* object)
1350{
1351 return (size_t)(object) / card_size;
1352}
1353
1354BOOL CardIsSet(const DacpGcHeapDetails &heap, TADDR objAddr)
1355{
1356 // The card table has to be translated to look at the refcount, etc.
1357 // g_card_table[card_word(card_of(g_lowest_address))].
1358
1359 TADDR card_table = TO_TADDR(heap.card_table);
1360 card_table = card_table + card_word(card_of((BYTE *)heap.lowest_address))*sizeof(DWORD);
1361
1362 do
1363 {
1364 TADDR card_table_lowest_addr;
1365 TADDR card_table_next;
1366
1367 if (MOVE(card_table_lowest_addr, ALIGN_DOWN(card_table, 0x1000) + sizeof(PVOID)) != S_OK)
1368 {
1369 ExtErr("Error getting card table lowest address\n");
1370 return FALSE;
1371 }
1372
1373 if (MOVE(card_table_next, card_table - sizeof(PVOID)) != S_OK)
1374 {
1375 ExtErr("Error getting next card table\n");
1376 return FALSE;
1377 }
1378
1379 size_t card = (objAddr - card_table_lowest_addr) / card_size;
1380 DWORD value;
1381 if (MOVE(value, card_table + card_word(card)*sizeof(DWORD)) != S_OK)
1382 {
1383 ExtErr("Error reading card bits\n");
1384 return FALSE;
1385 }
1386
1387 if (value & 1<<card_bit(card))
1388 return TRUE;
1389
1390 card_table = card_table_next;
1391 }
1392 while(card_table);
1393
1394 return FALSE;
1395}
1396
1397BOOL NeedCard(TADDR parent, TADDR child)
1398{
1399 int iChildGen = g_snapshot.GetGeneration(child);
1400
1401 if (iChildGen == 2)
1402 return FALSE;
1403
1404 int iParentGen = g_snapshot.GetGeneration(parent);
1405
1406 return (iChildGen < iParentGen);
1407}
1408
1409////////////////////////////////////////////////////////////////////////////////
1410//
1411// Some defines for mark_array taken from gc code
1412//
1413
1414#define mark_bit_pitch 8
1415#define mark_word_width 32
1416#define mark_word_size (mark_word_width * mark_bit_pitch)
1417#define heap_segment_flags_swept 16
1418
1419inline
1420size_t mark_bit_bit_of(CLRDATA_ADDRESS add)
1421{
1422 return (size_t)((add / mark_bit_pitch) % mark_word_width);
1423}
1424
1425inline
1426size_t mark_word_of(CLRDATA_ADDRESS add)
1427{
1428 return (size_t)(add / mark_word_size);
1429}
1430
1431inline BOOL mark_array_marked(const DacpGcHeapDetails &heap, CLRDATA_ADDRESS add)
1432{
1433
1434 DWORD entry = 0;
1435 HRESULT hr = MOVE(entry, heap.mark_array + sizeof(DWORD) * mark_word_of(add));
1436
1437 if (FAILED(hr))
1438 ExtOut("Failed to read card table entry.\n");
1439
1440 return entry & (1 << mark_bit_bit_of(add));
1441}
1442
1443BOOL background_object_marked(const DacpGcHeapDetails &heap, CLRDATA_ADDRESS o)
1444{
1445 BOOL m = TRUE;
1446
1447 if ((o >= heap.background_saved_lowest_address) && (o < heap.background_saved_highest_address))
1448 m = mark_array_marked(heap, o);
1449
1450 return m;
1451}
1452
1453BOOL fgc_should_consider_object(const DacpGcHeapDetails &heap,
1454 CLRDATA_ADDRESS o,
1455 const DacpHeapSegmentData &seg,
1456 BOOL consider_bgc_mark_p,
1457 BOOL check_current_sweep_p,
1458 BOOL check_saved_sweep_p)
1459{
1460 // the logic for this function must be kept in sync with the analogous function in gc.cpp
1461 BOOL no_bgc_mark_p = FALSE;
1462
1463 if (consider_bgc_mark_p)
1464 {
1465 if (check_current_sweep_p && (o < heap.next_sweep_obj))
1466 {
1467 no_bgc_mark_p = TRUE;
1468 }
1469
1470 if (!no_bgc_mark_p)
1471 {
1472 if(check_saved_sweep_p && (o >= heap.saved_sweep_ephemeral_start))
1473 {
1474 no_bgc_mark_p = TRUE;
1475 }
1476
1477 if (!check_saved_sweep_p)
1478 {
1479 CLRDATA_ADDRESS background_allocated = seg.background_allocated;
1480 if (o >= background_allocated)
1481 {
1482 no_bgc_mark_p = TRUE;
1483 }
1484 }
1485 }
1486 }
1487 else
1488 {
1489 no_bgc_mark_p = TRUE;
1490 }
1491
1492 return no_bgc_mark_p ? TRUE : background_object_marked(heap, o);
1493}
1494
1495enum c_gc_state
1496{
1497 c_gc_state_marking,
1498 c_gc_state_planning,
1499 c_gc_state_free
1500};
1501
1502inline BOOL in_range_for_segment(const DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr)
1503{
1504 return (addr >= seg.mem) && (addr < seg.reserved);
1505}
1506
1507void should_check_bgc_mark(const DacpGcHeapDetails &heap,
1508 const DacpHeapSegmentData &seg,
1509 BOOL* consider_bgc_mark_p,
1510 BOOL* check_current_sweep_p,
1511 BOOL* check_saved_sweep_p)
1512{
1513 // the logic for this function must be kept in sync with the analogous function in gc.cpp
1514 *consider_bgc_mark_p = FALSE;
1515 *check_current_sweep_p = FALSE;
1516 *check_saved_sweep_p = FALSE;
1517
1518 if (heap.current_c_gc_state == c_gc_state_planning)
1519 {
1520 // We are doing the next_sweep_obj comparison here because we have yet to
1521 // turn on the swept flag for the segment but in_range_for_segment will return
1522 // FALSE if the address is the same as reserved.
1523 if ((seg.flags & heap_segment_flags_swept) || (heap.next_sweep_obj == seg.reserved))
1524 {
1525 // this seg was already swept.
1526 }
1527 else
1528 {
1529 *consider_bgc_mark_p = TRUE;
1530
1531 if (seg.segmentAddr == heap.saved_sweep_ephemeral_seg)
1532 {
1533 *check_saved_sweep_p = TRUE;
1534 }
1535
1536 if (in_range_for_segment(seg, heap.next_sweep_obj))
1537 {
1538 *check_current_sweep_p = TRUE;
1539 }
1540 }
1541 }
1542}
1543
1544// TODO: FACTOR TOGETHER THE OBJECT MEMBER WALKING CODE FROM
1545// TODO: VerifyObjectMember(), GetListOfRefs(), HeapTraverser::PrintRefs()
1546BOOL VerifyObjectMember(const DacpGcHeapDetails &heap, DWORD_PTR objAddr)
1547{
1548 BOOL ret = TRUE;
1549 BOOL bCheckCard = TRUE;
1550 size_t size = 0;
1551 {
1552 DWORD_PTR dwAddrCard = objAddr;
1553 while (dwAddrCard < objAddr + size)
1554 {
1555 if (CardIsSet(heap, dwAddrCard))
1556 {
1557 bCheckCard = FALSE;
1558 break;
1559 }
1560 dwAddrCard += card_size;
1561 }
1562
1563 if (bCheckCard)
1564 {
1565 dwAddrCard = objAddr + size - 2*sizeof(PVOID);
1566 if (CardIsSet(heap, dwAddrCard))
1567 {
1568 bCheckCard = FALSE;
1569 }
1570 }
1571 }
1572
1573 for (sos::RefIterator itr(TO_TADDR(objAddr)); itr; ++itr)
1574 {
1575 TADDR dwAddr1 = (DWORD_PTR)*itr;
1576 if (dwAddr1)
1577 {
1578 TADDR dwChild = dwAddr1;
1579 // Try something more efficient than IsObject here. Is the methodtable valid?
1580 size_t s;
1581 BOOL bPointers;
1582 TADDR dwAddrMethTable;
1583 if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) ||
1584 (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE))
1585 {
1586 DMLOut("object %s: bad member %p at %p\n", DMLObject(objAddr), SOS_PTR(dwAddr1), SOS_PTR(itr.GetOffset()));
1587 ret = FALSE;
1588 }
1589
1590 if (IsMTForFreeObj(dwAddrMethTable))
1591 {
1592 DMLOut("object %s contains free object %p at %p\n", DMLObject(objAddr),
1593 SOS_PTR(dwAddr1), SOS_PTR(objAddr+itr.GetOffset()));
1594 ret = FALSE;
1595 }
1596
1597 // verify card table
1598 if (bCheckCard && NeedCard(objAddr+itr.GetOffset(), dwAddr1))
1599 {
1600 DMLOut("object %s:%s missing card_table entry for %p\n",
1601 DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe",
1602 SOS_PTR(objAddr+itr.GetOffset()));
1603 ret = FALSE;
1604 }
1605 }
1606 }
1607
1608 return ret;
1609}
1610
1611// search for can_verify_deep in gc.cpp for examples of how these functions are used.
1612BOOL VerifyObject(const DacpGcHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize,
1613 BOOL bVerifyMember)
1614{
1615 if (IsMTForFreeObj(MTAddr))
1616 {
1617 return TRUE;
1618 }
1619
1620 if (objSize < min_obj_size)
1621 {
1622 DMLOut("object %s: size %d too small\n", DMLObject(objAddr), objSize);
1623 return FALSE;
1624 }
1625
1626 // If we requested to verify the object's members, the GC may be in a state where that's not possible.
1627 // Here we check to see if the object in question needs to have its members updated. If so, we turn off
1628 // verification for the object.
1629 if (bVerifyMember)
1630 {
1631 BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE;
1632 should_check_bgc_mark(heap, seg, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep);
1633 bVerifyMember = fgc_should_consider_object(heap, objAddr, seg, consider_bgc_mark, check_current_sweep, check_saved_sweep);
1634 }
1635
1636 return bVerifyMember ? VerifyObjectMember(heap, objAddr) : TRUE;
1637}
1638
1639
1640BOOL FindSegment(const DacpGcHeapDetails &heap, DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr)
1641{
1642 CLRDATA_ADDRESS dwAddrSeg = heap.generation_table[GetMaxGeneration()].start_segment;
1643
1644 // Request the inital segment.
1645 if (seg.Request(g_sos, dwAddrSeg, heap) != S_OK)
1646 {
1647 ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg));
1648 return FALSE;
1649 }
1650
1651 // Loop while the object is not in range of the segment.
1652 while (addr < TO_TADDR(seg.mem) ||
1653 addr >= (dwAddrSeg == heap.ephemeral_heap_segment ? heap.alloc_allocated : TO_TADDR(seg.allocated)))
1654 {
1655 // get the next segment
1656 dwAddrSeg = seg.next;
1657
1658 // We reached the last segment without finding the object.
1659 if (dwAddrSeg == NULL)
1660 return FALSE;
1661
1662 if (seg.Request(g_sos, dwAddrSeg, heap) != S_OK)
1663 {
1664 ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg));
1665 return FALSE;
1666 }
1667 }
1668
1669 return TRUE;
1670}
1671
1672BOOL VerifyObject(const DacpGcHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, BOOL bVerifyMember)
1673{
1674 // This is only used by the other VerifyObject function if bVerifyMember is true,
1675 // so we only initialize it if we need it for verifying object members.
1676 DacpHeapSegmentData seg;
1677
1678 if (bVerifyMember)
1679 {
1680 // if we fail to find the segment, we cannot verify the object's members
1681 bVerifyMember = FindSegment(heap, seg, objAddr);
1682 }
1683
1684 return VerifyObject(heap, seg, objAddr, MTAddr, objSize, bVerifyMember);
1685}
1686
1687////////////////////////////////////////////////////////////////////////////////
1688////////////////////////////////////////////////////////////////////////////////
1689typedef void (*TYPETREEVISIT)(size_t methodTable, size_t ID, LPVOID token);
1690
1691// TODO remove this. MethodTableCache already maps method tables to
1692// various information. We don't need TypeTree to do this too.
1693// Straightfoward to do, but low priority.
1694class TypeTree
1695{
1696private:
1697 size_t methodTable;
1698 size_t ID;
1699 TypeTree *pLeft;
1700 TypeTree *pRight;
1701
1702public:
1703 TypeTree(size_t MT) : methodTable(MT),ID(0),pLeft(NULL),pRight(NULL) { }
1704
1705 BOOL isIn(size_t MT, size_t *pID)
1706 {
1707 TypeTree *pCur = this;
1708
1709 while (pCur)
1710 {
1711 if (MT == pCur->methodTable)
1712 {
1713 if (pID)
1714 *pID = pCur->ID;
1715 return TRUE;
1716 }
1717 else if (MT < pCur->methodTable)
1718 pCur = pCur->pLeft;
1719 else
1720 pCur = pCur->pRight;
1721 }
1722
1723 return FALSE;
1724 }
1725
1726 BOOL insert(size_t MT)
1727 {
1728 TypeTree *pCur = this;
1729
1730 while (pCur)
1731 {
1732 if (MT == pCur->methodTable)
1733 return TRUE;
1734 else if ((MT < pCur->methodTable))
1735 {
1736 if (pCur->pLeft)
1737 pCur = pCur->pLeft;
1738 else
1739 break;
1740 }
1741 else if (pCur->pRight)
1742 pCur = pCur->pRight;
1743 else
1744 break;
1745 }
1746
1747 // If we got here, we need to append at the current node.
1748 TypeTree *pNewNode = new TypeTree(MT);
1749 if (pNewNode == NULL)
1750 return FALSE;
1751
1752 if (MT < pCur->methodTable)
1753 pCur->pLeft = pNewNode;
1754 else
1755 pCur->pRight = pNewNode;
1756
1757 return TRUE;
1758 }
1759
1760 static void destroy(TypeTree *pStart)
1761 {
1762 TypeTree *pCur = pStart;
1763
1764 if (pCur)
1765 {
1766 destroy(pCur->pLeft);
1767 destroy(pCur->pRight);
1768 delete [] pCur;
1769 }
1770 }
1771
1772 static void visit_inorder(TypeTree *pStart, TYPETREEVISIT pFunc, LPVOID token)
1773 {
1774 TypeTree *pCur = pStart;
1775
1776 if (pCur)
1777 {
1778 visit_inorder(pCur->pLeft, pFunc, token);
1779 pFunc (pCur->methodTable, pCur->ID, token);
1780 visit_inorder(pCur->pRight, pFunc, token);
1781 }
1782 }
1783
1784 static void setTypeIDs(TypeTree *pStart, size_t *pCurID)
1785 {
1786 TypeTree *pCur = pStart;
1787
1788 if (pCur)
1789 {
1790 setTypeIDs(pCur->pLeft, pCurID);
1791 pCur->ID = *pCurID;
1792 (*pCurID)++;
1793 setTypeIDs(pCur->pRight, pCurID);
1794 }
1795 }
1796
1797};
1798
1799///////////////////////////////////////////////////////////////////////////////
1800//
1801
1802HeapTraverser::HeapTraverser(bool verify)
1803{
1804 m_format = 0;
1805 m_file = NULL;
1806 m_objVisited = 0;
1807 m_pTypeTree = NULL;
1808 m_curNID = 1;
1809 m_verify = verify;
1810}
1811
1812HeapTraverser::~HeapTraverser()
1813{
1814 if (m_pTypeTree) {
1815 TypeTree::destroy(m_pTypeTree);
1816 m_pTypeTree = NULL;
1817 }
1818}
1819
1820BOOL HeapTraverser::Initialize()
1821{
1822 if (!GCHeapsTraverse (HeapTraverser::GatherTypes, this, m_verify))
1823 {
1824 ExtOut("Error during heap traverse\n");
1825 return FALSE;
1826 }
1827
1828 GCRootImpl::GetDependentHandleMap(mDependentHandleMap);
1829
1830 size_t startID = 1;
1831 TypeTree::setTypeIDs(m_pTypeTree, &startID);
1832
1833 return TRUE;
1834}
1835
1836BOOL HeapTraverser::CreateReport (FILE *fp, int format)
1837{
1838 if (fp == NULL || (format!=FORMAT_XML && format != FORMAT_CLRPROFILER))
1839 {
1840 return FALSE;
1841 }
1842
1843 m_file = fp;
1844 m_format = format;
1845
1846 PrintSection(TYPE_START,TRUE);
1847
1848 PrintSection(TYPE_TYPES,TRUE);
1849 TypeTree::visit_inorder(m_pTypeTree, HeapTraverser::PrintOutTree, this);
1850 PrintSection(TYPE_TYPES,FALSE);
1851
1852 ExtOut("tracing roots...\n");
1853 PrintSection(TYPE_ROOTS,TRUE);
1854 PrintRootHead();
1855
1856 TraceHandles();
1857 FindGCRootOnStacks();
1858
1859 PrintRootTail();
1860 PrintSection(TYPE_ROOTS,FALSE);
1861
1862 // now print type tree
1863 PrintSection(TYPE_OBJECTS,TRUE);
1864 ExtOut("\nWalking heap...\n");
1865 m_objVisited = 0; // for UI updates
1866 GCHeapsTraverse (HeapTraverser::PrintHeap, this, FALSE); // Never verify on the second pass
1867 PrintSection(TYPE_OBJECTS,FALSE);
1868
1869 PrintSection(TYPE_START,FALSE);
1870
1871 m_file = NULL;
1872 return TRUE;
1873}
1874
1875void HeapTraverser::insert(size_t mTable)
1876{
1877 if (m_pTypeTree == NULL)
1878 {
1879 m_pTypeTree = new TypeTree(mTable);
1880 if (m_pTypeTree == NULL)
1881 {
1882 ReportOOM();
1883 return;
1884 }
1885 }
1886 else
1887 {
1888 m_pTypeTree->insert(mTable);
1889 }
1890}
1891
1892size_t HeapTraverser::getID(size_t mTable)
1893{
1894 if (m_pTypeTree == NULL)
1895 {
1896 return 0;
1897 }
1898 // IDs start at 1, so we can return 0 if not found.
1899 size_t ret;
1900 if (m_pTypeTree->isIn(mTable,&ret))
1901 {
1902 return ret;
1903 }
1904
1905 return 0;
1906}
1907
1908#ifndef FEATURE_PAL
1909void replace(std::wstring &str, const WCHAR *toReplace, const WCHAR *replaceWith)
1910{
1911 const size_t replaceLen = _wcslen(toReplace);
1912 const size_t replaceWithLen = _wcslen(replaceWith);
1913
1914 size_t i = str.find(toReplace);
1915 while (i != std::wstring::npos)
1916 {
1917 str.replace(i, replaceLen, replaceWith);
1918 i = str.find(toReplace, i + replaceWithLen);
1919 }
1920}
1921#endif
1922
1923void HeapTraverser::PrintType(size_t ID,LPCWSTR name)
1924{
1925 if (m_format==FORMAT_XML)
1926 {
1927#ifndef FEATURE_PAL
1928 // Sanitize name based on XML spec.
1929 std::wstring wname = name;
1930 replace(wname, W("&"), W("&amp;"));
1931 replace(wname, W("\""), W("&quot;"));
1932 replace(wname, W("'"), W("&apos;"));
1933 replace(wname, W("<"), W("&lt;"));
1934 replace(wname, W(">"), W("&gt;"));
1935 name = wname.c_str();
1936#endif
1937 fprintf(m_file,
1938 "<type id=\"%d\" name=\"%S\"/>\n",
1939 ID, name);
1940 }
1941 else if (m_format==FORMAT_CLRPROFILER)
1942 {
1943 fprintf(m_file,
1944 "t %d 0 %S\n",
1945 ID,name);
1946 }
1947}
1948
1949void HeapTraverser::PrintObjectHead(size_t objAddr,size_t typeID,size_t Size)
1950{
1951 if (m_format==FORMAT_XML)
1952 {
1953 fprintf(m_file,
1954 "<object address=\"0x%p\" typeid=\"%d\" size=\"%d\">\n",
1955 (PBYTE)objAddr,typeID, Size);
1956 }
1957 else if (m_format==FORMAT_CLRPROFILER)
1958 {
1959 fprintf(m_file,
1960 "n %d 1 %d %d\n",
1961 m_curNID,typeID,Size);
1962
1963 fprintf(m_file,
1964 "! 1 0x%p %d\n",
1965 (PBYTE)objAddr,m_curNID);
1966
1967 m_curNID++;
1968
1969 fprintf(m_file,
1970 "o 0x%p %d %d ",
1971 (PBYTE)objAddr,typeID,Size);
1972 }
1973}
1974
1975void HeapTraverser::PrintLoaderAllocator(size_t memberValue)
1976{
1977 if (m_format == FORMAT_XML)
1978 {
1979 fprintf(m_file,
1980 " <loaderallocator address=\"0x%p\"/>\n",
1981 (PBYTE)memberValue);
1982 }
1983 else if (m_format == FORMAT_CLRPROFILER)
1984 {
1985 fprintf(m_file,
1986 " 0x%p",
1987 (PBYTE)memberValue);
1988 }
1989}
1990
1991void HeapTraverser::PrintObjectMember(size_t memberValue, bool dependentHandle)
1992{
1993 if (m_format==FORMAT_XML)
1994 {
1995 fprintf(m_file,
1996 " <member address=\"0x%p\"%s/>\n",
1997 (PBYTE)memberValue, dependentHandle ? " dependentHandle=\"1\"" : "");
1998 }
1999 else if (m_format==FORMAT_CLRPROFILER)
2000 {
2001 fprintf(m_file,
2002 " 0x%p",
2003 (PBYTE)memberValue);
2004 }
2005}
2006
2007void HeapTraverser::PrintObjectTail()
2008{
2009 if (m_format==FORMAT_XML)
2010 {
2011 fprintf(m_file,
2012 "</object>\n");
2013 }
2014 else if (m_format==FORMAT_CLRPROFILER)
2015 {
2016 fprintf(m_file,
2017 "\n");
2018 }
2019}
2020
2021void HeapTraverser::PrintRootHead()
2022{
2023 if (m_format==FORMAT_CLRPROFILER)
2024 {
2025 fprintf(m_file,
2026 "r ");
2027 }
2028}
2029
2030void HeapTraverser::PrintRoot(LPCWSTR kind,size_t Value)
2031{
2032 if (m_format==FORMAT_XML)
2033 {
2034 fprintf(m_file,
2035 "<root kind=\"%S\" address=\"0x%p\"/>\n",
2036 kind,
2037 (PBYTE)Value);
2038 }
2039 else if (m_format==FORMAT_CLRPROFILER)
2040 {
2041 fprintf(m_file,
2042 "0x%p ",
2043 (PBYTE)Value);
2044 }
2045}
2046
2047void HeapTraverser::PrintRootTail()
2048{
2049 if (m_format==FORMAT_CLRPROFILER)
2050 {
2051 fprintf(m_file,
2052 "\n");
2053 }
2054}
2055
2056void HeapTraverser::PrintSection(int Type,BOOL bOpening)
2057{
2058 const char *const pTypes[] = {"<gcheap>","<types>","<roots>","<objects>"};
2059 const char *const pTypeEnds[] = {"</gcheap>","</types>","</roots>","</objects>"};
2060
2061 if (m_format==FORMAT_XML)
2062 {
2063 if ((Type >= 0) && (Type < TYPE_HIGHEST))
2064 {
2065 fprintf(m_file,"%s\n",bOpening ? pTypes[Type] : pTypeEnds[Type]);
2066 }
2067 else
2068 {
2069 ExtOut ("INVALID TYPE %d\n", Type);
2070 }
2071 }
2072 else if (m_format==FORMAT_CLRPROFILER)
2073 {
2074 if ((Type == TYPE_START) && !bOpening) // a final newline is needed
2075 {
2076 fprintf(m_file,"\n");
2077 }
2078 }
2079}
2080
2081void HeapTraverser::FindGCRootOnStacks()
2082{
2083 ArrayHolder<DWORD_PTR> threadList = NULL;
2084 int numThreads = 0;
2085
2086 // GetThreadList calls ReportOOM so we don't need to do that here.
2087 HRESULT hr = GetThreadList(&threadList, &numThreads);
2088 if (FAILED(hr) || !threadList)
2089 {
2090 ExtOut("Failed to enumerate threads in the process.\n");
2091 return;
2092 }
2093
2094 int total = 0;
2095 DacpThreadData vThread;
2096 for (int i = 0; i < numThreads; i++)
2097 {
2098 if (FAILED(vThread.Request(g_sos, threadList[i])))
2099 continue;
2100
2101 if (vThread.osThreadId)
2102 {
2103 unsigned int refCount = 0;
2104 ArrayHolder<SOSStackRefData> refs = NULL;
2105
2106 if (FAILED(::GetGCRefs(vThread.osThreadId, &refs, &refCount, NULL, NULL)))
2107 {
2108 ExtOut("Failed to walk thread %x\n", vThread.osThreadId);
2109 continue;
2110 }
2111
2112 for (unsigned int i = 0; i < refCount; ++i)
2113 if (refs[i].Object)
2114 PrintRoot(W("stack"), TO_TADDR(refs[i].Object));
2115 }
2116 }
2117
2118}
2119
2120
2121/* static */ void HeapTraverser::PrintOutTree(size_t methodTable, size_t ID,
2122 LPVOID token)
2123{
2124 HeapTraverser *pHolder = (HeapTraverser *) token;
2125 NameForMT_s(methodTable, g_mdName, mdNameLen);
2126 pHolder->PrintType(ID,g_mdName);
2127}
2128
2129
2130/* static */ void HeapTraverser::PrintHeap(DWORD_PTR objAddr,size_t Size,
2131 DWORD_PTR methodTable, LPVOID token)
2132{
2133 if (!IsMTForFreeObj (methodTable))
2134 {
2135 HeapTraverser *pHolder = (HeapTraverser *) token;
2136 pHolder->m_objVisited++;
2137 size_t ID = pHolder->getID(methodTable);
2138
2139 pHolder->PrintObjectHead(objAddr, ID, Size);
2140 pHolder->PrintRefs(objAddr, methodTable, Size);
2141 pHolder->PrintObjectTail();
2142
2143 if (pHolder->m_objVisited % 1024 == 0) {
2144 ExtOut(".");
2145 if (pHolder->m_objVisited % (1024*64) == 0)
2146 ExtOut("\r\n");
2147 }
2148 }
2149}
2150
2151void HeapTraverser::TraceHandles()
2152{
2153 unsigned int fetched = 0;
2154 SOSHandleData data[64];
2155
2156 ToRelease<ISOSHandleEnum> handles;
2157 HRESULT hr = g_sos->GetHandleEnum(&handles);
2158 if (FAILED(hr))
2159 return;
2160
2161 do
2162 {
2163 hr = handles->Next(_countof(data), data, &fetched);
2164
2165 if (FAILED(hr))
2166 break;
2167
2168 for (unsigned int i = 0; i < fetched; ++i)
2169 PrintRoot(W("handle"), (size_t)data[i].Handle);
2170 } while (fetched == _countof(data));
2171}
2172
2173/* static */ void HeapTraverser::GatherTypes(DWORD_PTR objAddr,size_t Size,
2174 DWORD_PTR methodTable, LPVOID token)
2175{
2176 if (!IsMTForFreeObj (methodTable))
2177 {
2178 HeapTraverser *pHolder = (HeapTraverser *) token;
2179 pHolder->insert(methodTable);
2180 }
2181}
2182
2183void HeapTraverser::PrintRefs(size_t obj, size_t methodTable, size_t size)
2184{
2185 DWORD_PTR dwAddr = methodTable;
2186
2187 // TODO: pass info to callback having to lookup the MethodTableInfo again
2188 MethodTableInfo* info = g_special_mtCache.Lookup((DWORD_PTR)methodTable);
2189 _ASSERTE(info->IsInitialized()); // This is the second pass, so we should be initialized
2190
2191 if (!info->bContainsPointers && !info->bCollectible)
2192 return;
2193
2194 if (info->bContainsPointers)
2195 {
2196 // Fetch the GCInfo from the other process
2197 CGCDesc *map = info->GCInfo;
2198 if (map == NULL)
2199 {
2200 INT_PTR nEntries;
2201 move_xp (nEntries, dwAddr-sizeof(PVOID));
2202 bool arrayOfVC = false;
2203 if (nEntries<0)
2204 {
2205 arrayOfVC = true;
2206 nEntries = -nEntries;
2207 }
2208
2209 size_t nSlots = 1+nEntries*sizeof(CGCDescSeries)/sizeof(DWORD_PTR);
2210 info->GCInfoBuffer = new DWORD_PTR[nSlots];
2211 if (info->GCInfoBuffer == NULL)
2212 {
2213 ReportOOM();
2214 return;
2215 }
2216
2217 if (FAILED(rvCache->Read(TO_CDADDR(dwAddr - nSlots*sizeof(DWORD_PTR)),
2218 info->GCInfoBuffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL)))
2219 return;
2220
2221 map = info->GCInfo = (CGCDesc*)(info->GCInfoBuffer+nSlots);
2222 info->ArrayOfVC = arrayOfVC;
2223 }
2224 }
2225
2226 mCache.EnsureRangeInCache((TADDR)obj, (unsigned int)size);
2227 for (sos::RefIterator itr(obj, info->GCInfo, info->ArrayOfVC, &mCache); itr; ++itr)
2228 {
2229 if (*itr && (!m_verify || sos::IsObject(*itr)))
2230 {
2231 if (itr.IsLoaderAllocator())
2232 {
2233 PrintLoaderAllocator(*itr);
2234 }
2235 else
2236 {
2237 PrintObjectMember(*itr, false);
2238 }
2239 }
2240 }
2241
2242 std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find((TADDR)obj);
2243 if (itr != mDependentHandleMap.end())
2244 {
2245 for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr)
2246 {
2247 PrintObjectMember(*litr, true);
2248 }
2249 }
2250}
2251
2252
2253void sos::ObjectIterator::BuildError(char *out, size_t count, const char *format, ...) const
2254{
2255 if (out == NULL || count == 0)
2256 return;
2257
2258 va_list args;
2259 va_start(args, format);
2260
2261 int written = vsprintf_s(out, count, format, args);
2262 if (written > 0 && mLastObj)
2263 sprintf_s(out+written, count-written, "\nLast good object: %p.\n", (int*)mLastObj);
2264
2265 va_end(args);
2266}
2267
2268bool sos::ObjectIterator::VerifyObjectMembers(char *reason, size_t count) const
2269{
2270 if (!mCurrObj.HasPointers())
2271 return true;
2272
2273 size_t size = mCurrObj.GetSize();
2274 size_t objAddr = (size_t)mCurrObj.GetAddress();
2275 TADDR mt = mCurrObj.GetMT();
2276
2277 INT_PTR nEntries;
2278 MOVE(nEntries, mt-sizeof(PVOID));
2279 if (nEntries < 0)
2280 nEntries = -nEntries;
2281
2282 size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(DWORD_PTR);
2283 ArrayHolder<DWORD_PTR> buffer = new DWORD_PTR[nSlots];
2284
2285 if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(DWORD_PTR)),
2286 buffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL)))
2287 {
2288 BuildError(reason, count, "Object %s has a bad GCDesc.", DMLObject(objAddr));
2289 return false;
2290 }
2291
2292 CGCDesc *map = (CGCDesc *)(buffer+nSlots);
2293 CGCDescSeries* cur = map->GetHighestSeries();
2294 CGCDescSeries* last = map->GetLowestSeries();
2295
2296 const size_t bufferSize = sizeof(size_t)*128;
2297 size_t objBuffer[bufferSize/sizeof(size_t)];
2298 size_t dwBeginAddr = (size_t)objAddr;
2299 size_t bytesInBuffer = bufferSize;
2300 if (size < bytesInBuffer)
2301 bytesInBuffer = size;
2302
2303
2304 if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer,NULL)))
2305 {
2306 BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr));
2307 return false;
2308 }
2309
2310 BOOL bCheckCard = TRUE;
2311 {
2312 DWORD_PTR dwAddrCard = (DWORD_PTR)objAddr;
2313 while (dwAddrCard < objAddr + size)
2314 {
2315 if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard))
2316 {
2317 bCheckCard = FALSE;
2318 break;
2319 }
2320 dwAddrCard += card_size;
2321 }
2322 if (bCheckCard)
2323 {
2324 dwAddrCard = objAddr + size - 2*sizeof(PVOID);
2325 if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard))
2326 {
2327 bCheckCard = FALSE;
2328 }
2329 }
2330 }
2331
2332 if (cur >= last)
2333 {
2334 do
2335 {
2336 BYTE** parm = (BYTE**)((objAddr) + cur->GetSeriesOffset());
2337 BYTE** ppstop =
2338 (BYTE**)((BYTE*)parm + cur->GetSeriesSize() + (size));
2339 while (parm < ppstop)
2340 {
2341 CheckInterrupt();
2342 size_t dwAddr1;
2343
2344 // Do we run out of cache?
2345 if ((size_t)parm >= dwBeginAddr+bytesInBuffer)
2346 {
2347 // dwBeginAddr += bytesInBuffer;
2348 dwBeginAddr = (size_t)parm;
2349 if (dwBeginAddr >= objAddr + size)
2350 {
2351 return true;
2352 }
2353 bytesInBuffer = bufferSize;
2354 if (objAddr+size-dwBeginAddr < bytesInBuffer)
2355 {
2356 bytesInBuffer = objAddr+size-dwBeginAddr;
2357 }
2358 if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL)))
2359 {
2360 BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr));
2361 return false;
2362 }
2363 }
2364 dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)];
2365 if (dwAddr1) {
2366 DWORD_PTR dwChild = dwAddr1;
2367 // Try something more efficient than IsObject here. Is the methodtable valid?
2368 size_t s;
2369 BOOL bPointers;
2370 DWORD_PTR dwAddrMethTable;
2371 if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) ||
2372 (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE))
2373 {
2374 BuildError(reason, count, "object %s: bad member %p at %p", DMLObject(objAddr),
2375 SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
2376
2377 return false;
2378 }
2379
2380 if (IsMTForFreeObj(dwAddrMethTable))
2381 {
2382 sos::Throw<HeapCorruption>("object %s contains free object %p at %p", DMLObject(objAddr),
2383 SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
2384 }
2385
2386 // verify card table
2387 if (bCheckCard &&
2388 NeedCard(objAddr+(size_t)parm-objAddr,dwChild))
2389 {
2390 BuildError(reason, count, "Object %s: %s missing card_table entry for %p",
2391 DMLObject(objAddr), (dwChild == dwAddr1)? "" : " maybe",
2392 SOS_PTR(objAddr+(size_t)parm-objAddr));
2393
2394 return false;
2395 }
2396 }
2397 parm++;
2398 }
2399 cur--;
2400 CheckInterrupt();
2401
2402 } while (cur >= last);
2403 }
2404 else
2405 {
2406 int cnt = (int) map->GetNumSeries();
2407 BYTE** parm = (BYTE**)((objAddr) + cur->startoffset);
2408 while ((BYTE*)parm < (BYTE*)((objAddr)+(size)-plug_skew))
2409 {
2410 for (int __i = 0; __i > cnt; __i--)
2411 {
2412 CheckInterrupt();
2413
2414 unsigned skip = cur->val_serie[__i].skip;
2415 unsigned nptrs = cur->val_serie[__i].nptrs;
2416 BYTE** ppstop = parm + nptrs;
2417 do
2418 {
2419 size_t dwAddr1;
2420 // Do we run out of cache?
2421 if ((size_t)parm >= dwBeginAddr+bytesInBuffer)
2422 {
2423 // dwBeginAddr += bytesInBuffer;
2424 dwBeginAddr = (size_t)parm;
2425 if (dwBeginAddr >= objAddr + size)
2426 return true;
2427
2428 bytesInBuffer = bufferSize;
2429 if (objAddr+size-dwBeginAddr < bytesInBuffer)
2430 bytesInBuffer = objAddr+size-dwBeginAddr;
2431
2432 if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL)))
2433 {
2434 BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr));
2435 return false;
2436 }
2437 }
2438 dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)];
2439 {
2440 if (dwAddr1)
2441 {
2442 DWORD_PTR dwChild = dwAddr1;
2443 // Try something more efficient than IsObject here. Is the methodtable valid?
2444 size_t s;
2445 BOOL bPointers;
2446 DWORD_PTR dwAddrMethTable;
2447 if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) ||
2448 (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE))
2449 {
2450 BuildError(reason, count, "Object %s: Bad member %p at %p.\n", DMLObject(objAddr),
2451 SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
2452
2453 return false;
2454 }
2455
2456 if (IsMTForFreeObj(dwAddrMethTable))
2457 {
2458 BuildError(reason, count, "Object %s contains free object %p at %p.", DMLObject(objAddr),
2459 SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
2460 return false;
2461 }
2462
2463 // verify card table
2464 if (bCheckCard &&
2465 NeedCard (objAddr+(size_t)parm-objAddr,dwAddr1))
2466 {
2467 BuildError(reason, count, "Object %s:%s missing card_table entry for %p",
2468 DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe",
2469 SOS_PTR(objAddr+(size_t)parm-objAddr));
2470
2471 return false;
2472 }
2473 }
2474 }
2475 parm++;
2476 CheckInterrupt();
2477 } while (parm < ppstop);
2478 parm = (BYTE**)((BYTE*)parm + skip);
2479 }
2480 }
2481 }
2482
2483 return true;
2484}
2485
2486bool sos::ObjectIterator::Verify(char *reason, size_t count) const
2487{
2488 try
2489 {
2490 TADDR mt = mCurrObj.GetMT();
2491
2492 if (MethodTable::GetFreeMT() == mt)
2493 {
2494 return true;
2495 }
2496
2497 size_t size = mCurrObj.GetSize();
2498 if (size < min_obj_size)
2499 {
2500 BuildError(reason, count, "Object %s: Size %d is too small.", DMLObject(mCurrObj.GetAddress()), size);
2501 return false;
2502 }
2503
2504 if (mCurrObj.GetAddress() + mCurrObj.GetSize() > mSegmentEnd)
2505 {
2506 BuildError(reason, count, "Object %s is too large. End of segment at %p.", DMLObject(mCurrObj), mSegmentEnd);
2507 return false;
2508 }
2509
2510 BOOL bVerifyMember = TRUE;
2511
2512 // If we requested to verify the object's members, the GC may be in a state where that's not possible.
2513 // Here we check to see if the object in question needs to have its members updated. If so, we turn off
2514 // verification for the object.
2515 BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE;
2516 should_check_bgc_mark(mHeaps[mCurrHeap], mSegment, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep);
2517 bVerifyMember = fgc_should_consider_object(mHeaps[mCurrHeap], mCurrObj.GetAddress(), mSegment,
2518 consider_bgc_mark, check_current_sweep, check_saved_sweep);
2519
2520 if (bVerifyMember)
2521 return VerifyObjectMembers(reason, count);
2522 }
2523 catch(const sos::Exception &e)
2524 {
2525 BuildError(reason, count, e.GetMesssage());
2526 return false;
2527 }
2528
2529 return true;
2530}
2531
2532bool sos::ObjectIterator::Verify() const
2533{
2534 char *c = NULL;
2535 return Verify(c, 0);
2536}
2537