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 | |
52 | inline 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 | |
60 | inline void* ALIGN_DOWN( void* val, size_t alignment ) |
61 | { |
62 | return (void*) ALIGN_DOWN( (size_t)val, alignment ); |
63 | } |
64 | |
65 | LinearReadCache::LinearReadCache(ULONG pageSize) |
66 | : mCurrPageStart(0), mPageSize(pageSize), mCurrPageSize(0), mPage(0) |
67 | { |
68 | mPage = new BYTE[pageSize]; |
69 | ClearStats(); |
70 | } |
71 | |
72 | LinearReadCache::~LinearReadCache() |
73 | { |
74 | if (mPage) |
75 | delete [] mPage; |
76 | } |
77 | |
78 | bool 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 | |
100 | static 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 | /////////////////////////////////////////////////////////////////////////////// |
128 | void GCRootImpl::ClearSizeData() |
129 | { |
130 | mConsidered.clear(); |
131 | mSizes.clear(); |
132 | } |
133 | |
134 | void 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 | |
161 | void 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 | |
172 | GCRootImpl::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 | |
196 | void GCRootImpl::DeleteNode(RootNode *node) |
197 | { |
198 | // Add node to the "new list". |
199 | node->Clear(); |
200 | mRootNewList.push_back(node); |
201 | } |
202 | |
203 | void 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 | /////////////////////////////////////////////////////////////////////////////// |
247 | int 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 | |
284 | bool 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 | |
315 | size_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 | |
335 | void 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 | |
350 | const 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 | |
368 | int 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 | /////////////////////////////////////////////////////////////////////////////// |
393 | void 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 | |
407 | void 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 | |
426 | void GCRootImpl::ReportOneHandlePath(const SOSHandleData &handle, RootNode *path, bool ) |
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 | |
441 | void 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 | |
472 | void GCRootImpl::ReportOneFQEntry(TADDR root, RootNode *path, bool ) |
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 | |
487 | void GCRootImpl::ReportOlderGenEntry(TADDR root, RootNode *path, bool ) |
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 | ////////////////////////////////////////////////////// |
503 | int 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 | |
580 | int 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 | |
663 | int GCRootImpl::PrintRootsInRange(LinearReadCache &cache, TADDR start, TADDR stop, ReportCallback func, bool ) |
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 | |
695 | int 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 | |
723 | int 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 | |
767 | int 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 = 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 | |
839 | GCRootImpl::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 | */ |
888 | GCRootImpl::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 | |
992 | GCRootImpl::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 | |
1073 | DWORD 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. |
1092 | size_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 | |
1114 | GCRootImpl::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 | |
1199 | TADDR 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 | |
1211 | TADDR 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 | |
1225 | UINT 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 | |
1273 | void 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 = 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 | |
1336 | inline |
1337 | size_t card_word (size_t card) |
1338 | { |
1339 | return card / card_word_width; |
1340 | } |
1341 | |
1342 | inline |
1343 | unsigned card_bit (size_t card) |
1344 | { |
1345 | return (unsigned)(card % card_word_width); |
1346 | } |
1347 | |
1348 | inline |
1349 | size_t card_of ( BYTE* object) |
1350 | { |
1351 | return (size_t)(object) / card_size; |
1352 | } |
1353 | |
1354 | BOOL 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 | |
1397 | BOOL 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 | |
1419 | inline |
1420 | size_t mark_bit_bit_of(CLRDATA_ADDRESS add) |
1421 | { |
1422 | return (size_t)((add / mark_bit_pitch) % mark_word_width); |
1423 | } |
1424 | |
1425 | inline |
1426 | size_t mark_word_of(CLRDATA_ADDRESS add) |
1427 | { |
1428 | return (size_t)(add / mark_word_size); |
1429 | } |
1430 | |
1431 | inline 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 | |
1443 | BOOL 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 | |
1453 | BOOL 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 | |
1495 | enum c_gc_state |
1496 | { |
1497 | c_gc_state_marking, |
1498 | c_gc_state_planning, |
1499 | c_gc_state_free |
1500 | }; |
1501 | |
1502 | inline BOOL in_range_for_segment(const DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr) |
1503 | { |
1504 | return (addr >= seg.mem) && (addr < seg.reserved); |
1505 | } |
1506 | |
1507 | void 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() |
1546 | BOOL 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. |
1612 | BOOL 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 | |
1640 | BOOL 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 | |
1672 | BOOL 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 | //////////////////////////////////////////////////////////////////////////////// |
1689 | typedef 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. |
1694 | class TypeTree |
1695 | { |
1696 | private: |
1697 | size_t methodTable; |
1698 | size_t ID; |
1699 | TypeTree *pLeft; |
1700 | TypeTree *pRight; |
1701 | |
1702 | public: |
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 | |
1802 | HeapTraverser::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 | |
1812 | HeapTraverser::~HeapTraverser() |
1813 | { |
1814 | if (m_pTypeTree) { |
1815 | TypeTree::destroy(m_pTypeTree); |
1816 | m_pTypeTree = NULL; |
1817 | } |
1818 | } |
1819 | |
1820 | BOOL 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 | |
1836 | BOOL 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 | |
1875 | void 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 | |
1892 | size_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 |
1909 | void 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 | |
1923 | void 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("&" )); |
1931 | replace(wname, W("\"" ), W(""" )); |
1932 | replace(wname, W("'" ), W("'" )); |
1933 | replace(wname, W("<" ), W("<" )); |
1934 | replace(wname, W(">" ), W(">" )); |
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 | |
1949 | void 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 | |
1975 | void 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 | |
1991 | void 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 | |
2007 | void 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 | |
2021 | void HeapTraverser::PrintRootHead() |
2022 | { |
2023 | if (m_format==FORMAT_CLRPROFILER) |
2024 | { |
2025 | fprintf(m_file, |
2026 | "r " ); |
2027 | } |
2028 | } |
2029 | |
2030 | void 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 | |
2047 | void HeapTraverser::PrintRootTail() |
2048 | { |
2049 | if (m_format==FORMAT_CLRPROFILER) |
2050 | { |
2051 | fprintf(m_file, |
2052 | "\n" ); |
2053 | } |
2054 | } |
2055 | |
2056 | void 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 | |
2081 | void 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 | |
2151 | void 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 | |
2183 | void 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 | |
2253 | void 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 | |
2268 | bool 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 | |
2486 | bool 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 | |
2532 | bool sos::ObjectIterator::Verify() const |
2533 | { |
2534 | char *c = NULL; |
2535 | return Verify(c, 0); |
2536 | } |
2537 | |