// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. //***************************************************************************** // File: enummem.cpp // // // ICLRDataEnumMemoryRegions implementation. // //***************************************************************************** #include "stdafx.h" #include #include #include #include "typestring.h" #include "daccess.h" #include "ipcmanagerinterface.h" #include "binder.h" #include "win32threadpool.h" #include "gcscan.h" #ifdef FEATURE_APPX #include "appxutil.h" #endif // FEATURE_APPX #if defined(DAC_MEASURE_PERF) unsigned __int64 g_nTotalTime; unsigned __int64 g_nStackTotalTime; unsigned __int64 g_nReadVirtualTotalTime; unsigned __int64 g_nFindTotalTime; unsigned __int64 g_nFindHashTotalTime; unsigned __int64 g_nFindHits; unsigned __int64 g_nFindCalls; unsigned __int64 g_nFindFails; unsigned __int64 g_nStackWalk; unsigned __int64 g_nFindStackTotalTime; #endif // #if defined(DAC_MEASURE_PERF) // // EnumMemCollectImages - collect all images of interest for heap dumps // // This is used primarily to save ngen images. // This is necessary so that heap dumps contain the full native code for the // process. Normally mini/heap dump debugging requires that the images be // available at debug-time, (in fact, watson explicitly does not want to // be downloading 3rd party images). Not including images is the main size // advantage of heap dumps over full dumps. However, since ngen images are // produced on the client, we can't always ensure that the debugger will // have access to the exact ngen image used in the dump. Therefore, managed // heap dumps also include full copies of all NGen images in the process. // // We also currently include in-memory modules (provided by a host, or loaded // from a Byte[]). // HRESULT ClrDataAccess::EnumMemCollectImages() { SUPPORTS_DAC; ProcessModIter modIter; Module* modDef = NULL; HRESULT status = S_OK; PEFile *file; TADDR pStartAddr = 0; ULONG32 ulSize = 0; ULONG32 ulSizeBlock; TSIZE_T cbMemoryReported = m_cbMemoryReported; // // Collect the ngen images - Iterating through module list // EX_TRY { while ((modDef = modIter.NextModule())) { EX_TRY { ulSize = 0; file = modDef->GetFile(); // We want to save all native images if (file->HasNativeImage()) { // We should only skip if signed by Microsoft! pStartAddr = PTR_TO_TADDR(file->GetLoadedNative()->GetBase()); ulSize = file->GetLoadedNative()->GetSize(); } // We also want to save any in-memory images. These show up like mapped files // and so would not be in a heap dump by default. Technically it's not clear we // should include them in the dump - you can often have the files available // after-the-fact. But in-memory modules may be harder to track down at debug time // and people may have come to rely on this - so we'll include them for now. else if ( // With Copy On Write feature enabled // IL images would not be dumped as part of the private pages. // We need to explicitly dump them here. #ifndef FEATURE_LAZY_COW_PAGES file->GetPath().IsEmpty() && // is in-memory #endif // FEATURE_LAZY_COW_PAGES file->HasMetadata() && // skip resource assemblies file->IsLoaded(FALSE) && // skip files not yet loaded !file->IsDynamic()) // skip dynamic (GetLoadedIL asserts anyway) { pStartAddr = PTR_TO_TADDR(file->GetLoadedIL()->GetBase()); ulSize = file->GetLoadedIL()->GetSize(); } // memory are mapped in in OS_PAGE_SIZE size. // Some memory are mapped in but some are not. You cannot // write all in one block. So iterating through page size // while (ulSize > 0) { // // Note that we have talked about not writing IL and Metadata to save size. // It turns out IL was rarely mapped in. // Metadata is needed. The RVA field is needed for it is pointed to a // MethodHeader MethodDesc::GetILHeader. Without this RVA, // all locals are broken. In case, you are asked about this question again. // ulSizeBlock = ulSize > OS_PAGE_SIZE ? OS_PAGE_SIZE : ulSize; ReportMem(pStartAddr, ulSizeBlock, false); pStartAddr += ulSizeBlock; ulSize -= ulSizeBlock; } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED m_dumpStats.m_cbNgen = m_cbMemoryReported - cbMemoryReported; return status; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // collecting memory for mscorwks's heap dump critical statics // This include the stress log, config structure, and IPC block // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemCLRHeapCrticalStatic(IN CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; TSIZE_T cbMemoryReported = m_cbMemoryReported; // Write out the stress log structure itself DacEnumHostDPtrMem(g_pStressLog); // This is pointing to a static buffer DacEnumHostDPtrMem(g_pConfig); // dump GC heap structures. Note that the managed heap is not dumped out. // We are just dump the GC heap structures. // CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumWksGlobalMemoryRegions(flags); ); #ifdef FEATURE_SVR_GC CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumSvrGlobalMemoryRegions(flags); ); #endif #ifdef FEATURE_IPCMAN // // Write Out IPC Blocks // EX_TRY { g_pIPCManagerInterface.EnumMem(); if (g_pIPCManagerInterface.IsValid()) { // write out the instance DacEnumHostDPtrMem(g_pIPCManagerInterface); // Then write out the public and private block ReportMem(PTR_TO_TADDR(g_pIPCManagerInterface->GetBlockStart()), g_pIPCManagerInterface->GetBlockSize()); ReportMem(PTR_TO_TADDR(g_pIPCManagerInterface->GetBlockTableStart()), g_pIPCManagerInterface->GetBlockTableSize()); } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED #endif // FEATURE_IPCMAN m_dumpStats.m_cbClrHeapStatics = m_cbMemoryReported - cbMemoryReported; return S_OK; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // collecting memory for mscorwks's statics. This is the minimal // set of global and statics that we need to have !threads, !pe, !ClrStack // to work. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; TSIZE_T cbMemoryReported = m_cbMemoryReported; // // write out the static and global content that we care. // // The followig macro will report memory all of the dac related mscorwks static and // global variables. But it won't report the structures that are pointed by // global pointers. // #define DEFINE_DACVAR(id_type, size_type, id, var) \ ReportMem(m_globalBase + g_dacGlobals.id, sizeof(size_type)); #define DEFINE_DACVAR_SVR(id_type, size_type, id, var) \ ReportMem(m_globalBase + g_dacGlobals.id, sizeof(size_type)); // Cannot use CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED // around conditional preprocessor directives in a sane fashion. EX_TRY { #include "dacvars.h" } EX_CATCH { // Catch the exception and keep going unless COR_E_OPERATIONCANCELED // was thrown. Used generating dumps, where rethrow will cancel dump. } EX_END_CATCH(RethrowCancelExceptions) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( // StressLog is not defined on Rotor build for DAC ReportMem(m_globalBase + g_dacGlobals.dac__g_pStressLog, sizeof(StressLog *)); ); EX_TRY { // These two static pointers are pointed to static data of byte[] // then run constructor in place // ReportMem(m_globalBase + g_dacGlobals.SystemDomain__m_pSystemDomain, sizeof(SystemDomain)); ReportMem(m_globalBase + g_dacGlobals.SharedDomain__m_pSharedDomain, sizeof(SharedDomain)); // We need IGCHeap pointer to make EEVersion work ReportMem(m_globalBase + g_dacGlobals.dac__g_pGCHeap, sizeof(IGCHeap *)); // see synblk.cpp, the pointer is pointed to a static byte[] SyncBlockCache::s_pSyncBlockCache.EnumMem(); #ifndef FEATURE_IMPLICIT_TLS ReportMem(m_globalBase + g_dacGlobals.dac__gThreadTLSIndex, sizeof(DWORD)); ReportMem(m_globalBase + g_dacGlobals.dac__gAppDomainTLSIndex, sizeof(DWORD)); #endif ReportMem( m_globalBase + g_dacGlobals.dac__g_FCDynamicallyAssignedImplementations, sizeof(TADDR)*ECall::NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED #ifndef FEATURE_PAL CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_runtimeLoadedBaseAddress.EnumMem(); ) #endif // !FEATURE_PAL // These are the structures that are pointed by global pointers and we care. // Some may reside in heap and some may reside as a static byte array in mscorwks.dll // That is ok. We will report them explicitly. // CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pConfig.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pPredefinedArrayTypes.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pObjectClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pStringClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pArrayClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pExceptionClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pThreadAbortExceptionClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pOutOfMemoryExceptionClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pStackOverflowExceptionClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pExecutionEngineExceptionClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDelegateClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pMulticastDelegateClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pValueTypeClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pEnumClass.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pThreadClass.EnumMem(); ) #ifdef FEATURE_CER CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pCriticalFinalizerObjectClass.EnumMem(); ) #endif CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pFreeObjectMethodTable.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pObjectCtorMD.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_fHostConfig.EnumMem(); ) // These two static pointers are pointed to static data of byte[] // then run constructor in place // CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( SystemDomain::m_pSystemDomain.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( SharedDomain::m_pSharedDomain.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pEEInterface.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugInterface.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pEEDbgInterfaceImpl.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_CORDebuggerControlFlags.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_Mscorlib.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT]->EnumMemoryRegions(flags); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( StubManager::EnumMemoryRegions(flags); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pFinalizerThread.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pSuspensionThread.EnumMem(); ) #ifdef FEATURE_SVR_GC CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( IGCHeap::gcHeapType.EnumMem(); ); #endif // FEATURE_SVR_GC m_dumpStats.m_cbClrStatics = m_cbMemoryReported - cbMemoryReported; return S_OK; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // This function reports memory that a heap dump need to debug CLR // and managed code efficiently. // // We will write out - // 1. mscorwks.dll's image read/write pages // 2. IPC blocks - shared memory (needed for debugging service and perf counter) // 3. ngen images excluding Metadata and IL for size perf // 4. We may want to touch the code pages on the stack - to be safe.... // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; HRESULT status = S_OK; m_instances.ClearEnumMemMarker(); // clear all of the previous cached memory Flush(); // collect ngen image CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCollectImages(); ); // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); ); CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRHeapCrticalStatic(flags); ); // Note that we do not need to flush out all of the dac instance manager's instance. // This is because it is a heap dump here. Assembly and AppDomain objects will be reported // by the default heap collection mechanism by dbghelp.lib // // Microsoft: I suspect if we have all private read-write pages the preceding statement // would be true, but I don't think we have that guarantee here. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); ); #ifdef FEATURE_LAZY_COW_PAGES // Iterating to all threads' stacks, as we have to collect data stored inside (core)clr.dll CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); ) // Dump AppDomain-specific info CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) // Dump the Debugger object data needed CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); ) // now dump the memory get dragged in by using DAC API implicitly. m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); #endif // FEATURE_LAZY_COW_PAGES // end of code status = m_memStatus; return S_OK; } // EnumMemoryRegionsWorkerHeap //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Helper function for skinny mini-dump // Pass in an managed object, this function will dump the EEClass hierachy // and field desc of object so SOS's !DumpObj will work // // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::DumpManagedObject(CLRDataEnumMemoryFlags flags, OBJECTREF objRef) { SUPPORTS_DAC; HRESULT status = S_OK; if (objRef == NULL) { return status; } if (!GCScan::GetGcRuntimeStructuresValid ()) { // GC is in progress, don't dump this object return S_OK; } EX_TRY { // write out the current EE class and the direct/indirect inherited EE Classes MethodTable *pMethodTable = objRef->GetGCSafeMethodTable(); while (pMethodTable) { EX_TRY { pMethodTable->EnumMemoryRegions(flags); StackSString s; // This might look odd. We are not using variable s after forming it. // That is because our DAC inspecting API is using TypeString to form // full type name. Form the full name here is a implicit reference to needed // memory. // TypeString::AppendType(s, TypeHandle(pMethodTable), TypeString::FormatNamespace|TypeString::FormatFullInst); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // Walk up to parent MethodTable pMethodTable = pMethodTable->GetParentMethodTable(); } // now dump the content for the managed object objRef->EnumMemoryRegions(); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED return status; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Helper function for skinny mini-dump // Pass in an managed excption object, this function will dump // the managed exception object and some of its fields, such as message, stack trace string, // inner exception. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::DumpManagedExcepObject(CLRDataEnumMemoryFlags flags, OBJECTREF objRef) { SUPPORTS_DAC; if (objRef == NULL) { return S_OK; } if (!GCScan::GetGcRuntimeStructuresValid ()) { // GC is in progress, don't dump this object return S_OK; } // write out the managed object for exception. This will only write out the // direct field value. After this, we need to visit some selected fields, such as // exception message and stack trace field, and dump out the object referenced via // the fields. // DumpManagedObject(flags, objRef); // If this is not a pre-allocated exception type, then we'll need to dump out enough memory to ensure // that the lookup codepath from the Module to information for the type of this Exception will // be present. Simply dumping the managed object itself isn't enough. Sos doesn't need this. EX_TRY { MethodTable * pMethodTable = objRef->GetGCSafeMethodTable(); PTR_Module pModule = pMethodTable->GetModule(); mdTypeDef exceptionTypeDef = pMethodTable->GetCl(); if (TypeFromToken(exceptionTypeDef) != mdtTypeDef) { _ASSERTE(!"Module should have contained a TypeDef, dump will likely be missing exception type lookup!"); } // The lookup from the Module that contains this TypeDef: pModule->LookupTypeDef(RidFromToken(exceptionTypeDef)); // If it's a generic class, we need to implicitly enumerate the memory needed to look it up // and enable the calls that ICD will want to make against the TypeHandle when retrieving the // Exception info. TypeHandle th; th = ClassLoader::LookupTypeDefOrRefInModule(pModule, exceptionTypeDef); th.EnumMemoryRegions(flags); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // store the exception type name EX_TRY { MethodTable * pMethodTable = objRef->GetGCSafeMethodTable(); StackSString s; TypeString::AppendType(s, TypeHandle(pMethodTable), TypeString::FormatNamespace|TypeString::FormatFullInst); DacMdCacheAddEEName(dac_cast(pMethodTable), s); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS EXCEPTIONREF exceptRef = (EXCEPTIONREF)objRef; if (flags != CLRDATA_ENUM_MEM_TRIAGE) { // dump the exception message field DumpManagedObject(flags, (OBJECTREF)exceptRef->GetMessage()); } // dump the exception's stack trace field DumpManagedStackTraceStringObject(flags, exceptRef->GetStackTraceString()); // dump the exception's remote stack trace field only if we are not generating a triage dump, or // if we are generating a triage dump of an AppX process, or the exception type does not override // the StackTrace getter (see Exception.InternalPreserveStackTrace to understand why) if (flags != CLRDATA_ENUM_MEM_TRIAGE || #ifdef FEATURE_APPX AppX::DacIsAppXProcess() || #endif // FEATURE_APPX !ExceptionTypeOverridesStackTraceGetter(exceptRef->GetGCSafeMethodTable())) { DumpManagedStackTraceStringObject(flags, exceptRef->GetRemoteStackTraceString()); } // Dump inner exception DumpManagedExcepObject(flags, exceptRef->GetInnerException()); // Dump the stack trace array object and its underlying type I1ARRAYREF stackTraceArrayObj = exceptRef->GetStackTraceArrayObject(); // There are cases where a managed exception does not have a stack trace. // These cases are: // * exception was thrown by VM and no managed frames are on the thread. // * exception thrown is a preallocated exception. if (stackTraceArrayObj != NULL) { // first dump the array's element type TypeHandle arrayTypeHandle = stackTraceArrayObj->GetTypeHandle(); ArrayTypeDesc* pArrayTypeDesc = arrayTypeHandle.AsArray(); TypeHandle elementTypeHandle = pArrayTypeDesc->GetArrayElementTypeHandle(); elementTypeHandle.AsMethodTable()->EnumMemoryRegions(flags); elementTypeHandle.AsMethodTable()->GetClass()->EnumMemoryRegions(flags, elementTypeHandle.AsMethodTable()); // now dump the actual stack trace array object DumpManagedObject(flags, (OBJECTREF)stackTraceArrayObj); } // Dump the stack trace native structure. Unfortunately, we need to write out the // native structure and also dump the MethodDesc that we care about! // We need to ensure the entire _stackTrace from the Exception is enumerated and // included in the dump. When we touch the header and each element looking for the // MD this happens. StackTraceArray stackTrace; exceptRef->GetStackTrace(stackTrace); for(size_t i = 0; i < stackTrace.Size(); i++) { MethodDesc* pMD = stackTrace[i].pFunc; if (!DacHasMethodDescBeenEnumerated(pMD) && DacValidateMD(pMD)) { pMD->EnumMemoryRegions(flags); // The following calls are to ensure that mscordacwks!DacDbiInterfaceImpl::GetNativeCodeInfo // will succeed for all dumps. // Pulls in data to translate from token to MethodDesc FindLoadedMethodRefOrDef(pMD->GetMethodTable()->GetModule(), pMD->GetMemberDef()); // Pulls in sequence points. DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, pMD); PCODE addr = pMD->GetNativeCode(); if (addr != NULL) { IJitManager::MethodRegionInfo methodRegionInfo = { NULL, 0, NULL, 0 }; EECodeInfo codeInfo(addr); codeInfo.GetMethodRegionInfo(&methodRegionInfo); } } // Enumerate the code around call site to help SOS resolve the source lines TADDR callEnd = PCODEToPINSTR(stackTrace[i].ip); DacEnumCodeForStackwalk(callEnd); } return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Helper function for skinny mini-dump // Pass in a string object representing a managed stack trace, this function will // dump it and "poison" the contents with a PII-free version of the stack trace. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::DumpManagedStackTraceStringObject(CLRDataEnumMemoryFlags flags, STRINGREF orefStackTrace) { SUPPORTS_DAC; if (orefStackTrace == NULL) { return S_OK; } // dump the stack trace string object DumpManagedObject(flags, (OBJECTREF)orefStackTrace); if (flags == CLRDATA_ENUM_MEM_TRIAGE) { // StringObject::GetSString does not support DAC, use GetBuffer/GetStringLength SString stackTrace(dac_cast((TADDR)orefStackTrace->GetBuffer()), orefStackTrace->GetStringLength()); StripFileInfoFromStackTrace(stackTrace); COUNT_T traceCharCount = stackTrace.GetCount(); _ASSERTE(traceCharCount <= orefStackTrace->GetStringLength()); // fill the rest of the string with \0 WCHAR *buffer = stackTrace.OpenUnicodeBuffer(orefStackTrace->GetStringLength()); memset(buffer + traceCharCount, 0, sizeof(WCHAR) * (orefStackTrace->GetStringLength() - traceCharCount)); // replace the string DacUpdateMemoryRegion(dac_cast(orefStackTrace) + StringObject::GetBufferOffset(), sizeof(WCHAR) * orefStackTrace->GetStringLength(), (BYTE *)buffer); } return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Iterating through module list and report the memory. // Remember to call // m_instances.DumpAllInstances(m_enumMemCb); // when all memory enumeration are done if you call this function! // This is because using ProcessModIter will drag in some memory implicitly. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemDumpModuleList(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; ProcessModIter modIter; Module* modDef; TADDR base; ULONG32 length; PEFile *file; TSIZE_T cbMemoryReported = m_cbMemoryReported; #ifdef FEATURE_PREJIT COUNT_T count; #endif // FEATURE_PREJIT // // Iterating through module list // // Cannot use CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED around // conditional pre-processor directives in a sane fashion EX_TRY { while ((modDef = modIter.NextModule())) { // We also want to dump the link from the Module back to the AppDomain, // since the stackwalker uses it to find the AD. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( // Pass false to ensure we force enumeration of this module's references. modDef->EnumMemoryRegions(flags, false); ); EX_TRY { // To enable a debugger to check on whether a module is an NI or IL image, they need // the DOS header, PE headers, and IMAGE_COR20_HEADER for the Flags member. // We expose no API today to find this out. PTR_PEFile pPEFile = modDef->GetFile(); PEImage * pILImage = pPEFile->GetILimage(); PEImage * pNIImage = pPEFile->GetNativeImage(); // Implicitly gets the COR header. if ((pILImage) && (pILImage->HasLoadedLayout())) { pILImage->GetCorHeaderFlags(); } if ((pNIImage) && (pNIImage->HasLoadedLayout())) { pNIImage->GetCorHeaderFlags(); } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED EX_TRY { file = modDef->GetFile(); base = PTR_TO_TADDR(file->GetLoadedImageContents(&length)); file->EnumMemoryRegions(flags); #ifdef FEATURE_PREJIT // If module has native image and it has debug map, we need to get the debug map. // if (modDef->HasNativeImage() && modDef->GetNativeImage()->HasNativeDebugMap()) { modDef->GetNativeImage()->GetNativeDebugMap(&count); } #endif // FEATURE_PREJIT } EX_CATCH { // Catch the exception and keep going unless COR_E_OPERATIONCANCELED // was thrown. Used generating dumps, where rethrow will cancel dump. } EX_END_CATCH(RethrowCancelExceptions) } } EX_CATCH { // Catch the exception and keep going unless COR_E_OPERATIONCANCELED // was thrown. Used generating dumps, where rethrow will cancel dump. } EX_END_CATCH(RethrowCancelExceptions) m_dumpStats.m_cbModuleList = m_cbMemoryReported - cbMemoryReported; return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Iterate through AppDomains and report specific memory needed // for all dumps, such as the Module lookup path. // This is intended for MiniDumpNormal and should be kept small. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; AppDomainIterator adIter(FALSE); EX_TRY { while (adIter.Next()) { CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( // Note that the flags being CLRDATA_ENUM_MEM_MINI prevents // you from pulling entire files loaded into memory into the dump. adIter.GetDomain()->EnumMemoryRegions(flags, true); ); } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Iterating through each frame to make sure // we dump out MethodDesc, DJI etc related info // This is a generic helper for walking stack. However, if you call // this function, make sure to flush instance in the DAC Instance manager. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemWalkStackHelper(CLRDataEnumMemoryFlags flags, IXCLRDataStackWalk *pStackWalk, Thread * pThread) { SUPPORTS_DAC; #if defined(DAC_MEASURE_PERF) g_nStackWalk = 1; unsigned __int64 nStart= GetCycleCount(); #endif HRESULT status = S_OK; ReleaseHolder pFrame(NULL); ReleaseHolder pMethod(NULL); ReleaseHolder pMethodDefinition(NULL); ReleaseHolder pTypeInstance(NULL); MethodDesc * pMethodDesc = NULL; EX_TRY { TADDR previousSP = 0; //start at zero; this allows first check to always succeed. TADDR currentSP; currentSP = dac_cast(pThread->GetCachedStackLimit()) + sizeof(TADDR); // exhaust the frames using DAC api for (; status == S_OK; ) { bool frameHadContext = false; status = pStackWalk->GetFrame(&pFrame); PCODE addr = NULL; if (status == S_OK && pFrame != NULL) { // write out the code that ip pointed to T_CONTEXT context; REGDISPLAY regDisp; if ((status=pFrame->GetContext(CONTEXT_ALL, sizeof(T_CONTEXT), NULL, (BYTE *)&context))==S_OK) { // Enumerate the code around the call site to help debugger stack walking heuristics ::FillRegDisplay(®Disp, &context); addr = GetControlPC(®Disp); TADDR callEnd = PCODEToPINSTR(addr); DacEnumCodeForStackwalk(callEnd); frameHadContext = true; } // // There are identical stack pointer checking semantics in code:Thread::EnumMemoryRegionsWorker // See that code for comments. // You ***MUST*** maintain identical semantics for both checks! // CLRDataSimpleFrameType simpleFrameType; CLRDataDetailedFrameType detailedFrameType; if (SUCCEEDED(pFrame->GetFrameType(&simpleFrameType, &detailedFrameType))) { if (!frameHadContext) { _ASSERTE(!"Stack frame should always have an associated context!"); break; } // This is StackFrameIterator::SFITER_FRAMELESS_METHOD, initialized by Code:ClrDataStackWalk::GetFrame // from code:ClrDataStackWalk::RawGetFrameType if (simpleFrameType == CLRDATA_SIMPFRAME_MANAGED_METHOD) { currentSP = (TADDR)GetRegdisplaySP(®Disp); if (currentSP <= previousSP) { _ASSERTE(!"Target stack has been corrupted, SP for current frame must be larger than previous frame."); break; } if (currentSP % sizeof(TADDR) != 0) { _ASSERTE(!"Target stack has been corrupted, SP must be aligned."); break; } if (!pThread->IsAddressInStack(currentSP)) { _ASSERTE(!"Target stack has been corrupted, SP must in in the stack range."); break; } } } else { _ASSERTE(!"The stack frame should always know what type it is!"); break; } status = pFrame->GetMethodInstance(&pMethod); if (status == S_OK && pMethod != NULL) { // managed frame if (SUCCEEDED(pMethod->GetTypeInstance(&pTypeInstance)) && (pTypeInstance != NULL)) { pTypeInstance.Clear(); } if(SUCCEEDED(pMethod->GetDefinition(&pMethodDefinition)) && (pMethodDefinition != NULL)) { pMethodDesc = ((ClrDataMethodDefinition *)pMethodDefinition.GetValue())->GetMethodDesc(); if (pMethodDesc) { // If this is a generic, we'll need to pull in enough extra info that // we get decent results later when stackwalking. Note that we do not guarantee // we'll always get an exact type for any reference type; most of the time the // stack walk will just show System.__Canon, which is the level of support we // guarantee for minidumps without full memory. EX_TRY { if ((pMethodDesc->AcquiresInstMethodTableFromThis()) || (pMethodDesc->RequiresInstMethodTableArg())) { // MethodTable ReleaseHolder pDV(NULL); ReleaseHolder pAssociatedValue(NULL); CLRDATA_ADDRESS address; PTR_Object pObjThis = NULL; if (SUCCEEDED(pFrame->GetArgumentByIndex(0, &pDV, 0, NULL, NULL)) && SUCCEEDED(pDV->GetAssociatedValue(&pAssociatedValue)) && SUCCEEDED(pAssociatedValue->GetAddress(&address))) { // Implicitly enumerate the object itself. TADDR addrObjThis = CLRDATA_ADDRESS_TO_TADDR(address); pObjThis = dac_cast(addrObjThis); } // And now get the extra info we need for the AcquiresInstMethodTableFromThis case. if (pMethodDesc->AcquiresInstMethodTableFromThis()) { // When working with the 'this' case, we need to pick up the MethodTable from // object lookup. PTR_MethodTable pMT = NULL; if (pObjThis != NULL) { pMT = pObjThis->GetMethodTable(); } TypeHandle th; if (pMT != NULL) { th = TypeHandle(pMT); } Instantiation classInst = pMethodDesc->GetExactClassInstantiation(th); Instantiation methodInst = pMethodDesc->GetMethodInstantiation(); } } else if (pMethodDesc->RequiresInstMethodDescArg()) { // This method has a generic type token which is required to figure out the exact instantiation // of the method. // We need to to use the variable index of the generic type token in order to do the look up. CLRDATA_ADDRESS address = NULL; DWORD dwExactGenericArgsTokenIndex = 0; ReleaseHolder pDV(NULL); ReleaseHolder pAssociatedValue(NULL); ReleaseHolder pFrame2(NULL); if (SUCCEEDED(pFrame->QueryInterface(__uuidof(IXCLRDataFrame2), (void**)&pFrame2)) && SUCCEEDED(pFrame2->GetExactGenericArgsToken(&pDV)) && SUCCEEDED(pDV->GetAssociatedValue(&pAssociatedValue)) && SUCCEEDED(pAssociatedValue->GetAddress(&address))) { TADDR addrMD = CLRDATA_ADDRESS_TO_TADDR(address); PTR_MethodDesc pMD = dac_cast(addrMD); pMD->EnumMemoryRegions(flags); } pMethodDesc->EnumMemoryRegions(flags); MethodTable * pCanonicalMT = pMethodDesc->GetCanonicalMethodTable(); MethodTable * pNormalMT = pMethodDesc->GetMethodTable(); pCanonicalMT->EnumMemoryRegions(flags); pNormalMT->EnumMemoryRegions(flags); } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED pMethodDesc->EnumMemoryRegions(flags); // The following calls are to ensure that mscordacwks!DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo // will succeed for all dumps. Local variable info usefulness is somewhat questionable // since most dumps will be for optimized targets. However, being able to map // back to source lines for functions on stacks is very useful and we don't // want to allow the function to fail for all targets. // Pulls in sequence points and local variable info DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, pMethodDesc); #ifdef WIN64EXCEPTIONS if (addr != NULL) { EECodeInfo codeInfo(addr); // We want IsFilterFunclet to work for anything on the stack codeInfo.GetJitManager()->IsFilterFunclet(&codeInfo); // The stackwalker needs GC info to find the parent 'stack pointer' or PSP GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); PTR_BYTE pGCInfo = dac_cast(gcInfoToken.Info); if (pGCInfo != NULL) { GcInfoDecoder gcDecoder(gcInfoToken, DECODE_PSP_SYM, 0); DacEnumMemoryRegion(dac_cast(pGCInfo), gcDecoder.GetNumBytesRead(), true); } } #endif // WIN64EXCEPTIONS } pMethodDefinition.Clear(); } pMethod.Clear(); } pFrame.Clear(); } previousSP = currentSP; status = pStackWalk->Next(); } } EX_CATCH { status = E_FAIL; // Catch the exception and keep going unless a COR_E_OPERATIONCANCELED // was thrown. In which case, rethrow to cancel the dump gathering } EX_END_CATCH(RethrowCancelExceptions) #if defined(DAC_MEASURE_PERF) unsigned __int64 nEnd = GetCycleCount(); g_nStackTotalTime += nEnd - nStart; g_nStackWalk = 0; #endif // #if defined(DAC_MEASURE_PERF) return status; } // code: ClrDataAccess::EnumMemDumpAllThreadsStack needs a trivial implementation of // an un-DACized container class to track what exceptions have happened so far. // It shouldn't get used anywhere else. class DebuggingExceptionTrackerList { private: struct TrivialTADDRNode { TADDR m_exceptionAddress; TrivialTADDRNode * m_pNext; TrivialTADDRNode(TrivialTADDRNode *pNext, TADDR address) : m_exceptionAddress(address), m_pNext(pNext) { SUPPORTS_DAC_HOST_ONLY; } private: TrivialTADDRNode() { _ASSERTE(!"You should never call this ctor."); } }; TrivialTADDRNode *m_pHead; bool Find(TADDR address) { SUPPORTS_DAC_HOST_ONLY; for (TrivialTADDRNode *pFind = m_pHead; pFind != NULL; pFind = pFind->m_pNext) if (pFind->m_exceptionAddress == address) return true; return false; } public: DebuggingExceptionTrackerList() : m_pHead(NULL) { SUPPORTS_DAC_HOST_ONLY; } bool AddNewAddressOnly(TADDR address) { SUPPORTS_DAC_HOST_ONLY; if (Find(address)) { return false; } else { TrivialTADDRNode *pNew = new TrivialTADDRNode(m_pHead, address); m_pHead = pNew; return true; } } ~DebuggingExceptionTrackerList() { SUPPORTS_DAC_HOST_ONLY; for (TrivialTADDRNode *pTemp = m_pHead; m_pHead != NULL; pTemp = m_pHead) { m_pHead = m_pHead->m_pNext; delete pTemp; } } }; //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // This function will walk all threads, all the context in the // exception state to report memory. This can also drag in memory implicitly. // So do call // m_instances.DumpAllInstances(m_enumMemCb); // when function is done. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemDumpAllThreadsStack(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; #ifdef FEATURE_COMINTEROP // Dump the exception object stored in the WinRT stowed exception EnumMemStowedException(flags); #endif HRESULT status = S_OK; TSIZE_T cbMemoryReported = m_cbMemoryReported; #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Duplicate the enumeration code below, to allow Exception stacks to be enumerated first. // These exception stacks will get MethodDesc names cached to the DacStreamManager before // MethodDescs residing on the "regular" callstacks EX_TRY { DebuggingExceptionTrackerList exceptionTrackingInner; CLRDATA_ENUM handle; ReleaseHolder pIXCLRDataTask(NULL); ReleaseHolder pExcepState(NULL); Thread *pThread = NULL; // enumerating through each thread StartEnumTasks(&handle); status = EnumTask(&handle, &pIXCLRDataTask); for (unsigned nbThreads = 0; status == S_OK && pIXCLRDataTask != NULL; nbThreads++) { // Avoid infinite loop if target process is corrupted. if (nbThreads > 100000) { break; } EX_TRY { // get Thread * pThread = ((ClrDataTask *)pIXCLRDataTask.GetValue())->GetThread(); // dump the exception object DumpManagedExcepObject(flags, pThread->LastThrownObject()); // Now probe into the exception info status = pIXCLRDataTask->GetCurrentExceptionState(&pExcepState); while (status == S_OK && pExcepState != NULL) { EX_TRY { // touch the throwable in exception state PTR_UNCHECKED_OBJECTREF throwRef(((ClrDataExceptionState *)pExcepState.GetValue())->m_throwable); // If we've already attempted enumeration for this exception, it's time to quit. if (!exceptionTrackingInner.AddNewAddressOnly(throwRef.GetAddr())) { break; } DumpManagedExcepObject(flags, *throwRef); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // get the previous exception IXCLRDataExceptionState * pExcepStatePrev = NULL; status = pExcepState->GetPrevious(&pExcepStatePrev); // Release our current exception object, and transfer ref ownership of the previous // exception object into the holder. pExcepState = pExcepStatePrev; } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // get next thread pIXCLRDataTask.Clear(); status = EnumTask(&handle, &pIXCLRDataTask); } EndEnumTasks(handle); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // exceptionTracking is used for exactly that; it is a per-dump list of the // addresses of all exceptions enumerated for this dump. If an exception is // enumerated more than once it indicates that we have multiple threads pointing to // the same object, or the same thread has an InnerException chain with a cycle. // In either case, we need to terminate exception reporting. DebuggingExceptionTrackerList exceptionTracking; EX_TRY { CLRDATA_ENUM handle; ReleaseHolder pIXCLRDataTask(NULL); ReleaseHolder pExcepState(NULL); ReleaseHolder pStackWalk(NULL); Thread *pThread = NULL; // enumerating through each thread's each frame, dump out some interesting // code memory needed to debugger to recognize frame // ThreadStore::EnumMemoryRegions(flags); // enumerating through each thread StartEnumTasks(&handle); status = EnumTask(&handle, &pIXCLRDataTask); for (unsigned nbThreads = 0; status == S_OK && pIXCLRDataTask != NULL; nbThreads++) { // Avoid infinite loop if target process is corrupted. if (nbThreads > 100000) { break; } EX_TRY { // get Thread * pThread = ((ClrDataTask *)pIXCLRDataTask.GetValue())->GetThread(); // Write out the Thread instance DacEnumHostDPtrMem(pThread); // Write out the context pointed by the thread DacEnumHostDPtrMem(pThread->GetContext()); // @TODO // write TEB pointed by the thread // DacEnumHostDPtrMem(pThread->GetTEB()); // @TODO // If CLR is hosted, we want to write out fiber data // Dump the managed thread object DumpManagedObject(flags, pThread->GetExposedObjectRaw()); #ifndef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // dump the exception object DumpManagedExcepObject(flags, pThread->LastThrownObject()); #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Stack Walking // We need for the ClrDataTask::CreateStackWalk from IXCLRDataTask to work, which is the // following walk. However, the CordbStackWalk code requires some different (extra) data // to walk the stack, such as info being present for // mscordacwks!DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo. status = pIXCLRDataTask->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED | CLRDATA_SIMPFRAME_MANAGED_METHOD | CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE, &pStackWalk); if (status == S_OK && pStackWalk != NULL) { status = EnumMemWalkStackHelper(flags, pStackWalk, pThread); pStackWalk.Clear(); } // Now probe into the exception info status = pIXCLRDataTask->GetCurrentExceptionState(&pExcepState); while (status == S_OK && pExcepState != NULL) { EX_TRY { // touch the throwable in exception state PTR_UNCHECKED_OBJECTREF throwRef(((ClrDataExceptionState *)pExcepState.GetValue())->m_throwable); // If we've already attempted enumeration for this exception, it's time to quit. if (!exceptionTracking.AddNewAddressOnly(throwRef.GetAddr())) { break; } #ifndef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS DumpManagedExcepObject(flags, *throwRef); #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // get the type of the exception ReleaseHolder pValue(NULL); status = pExcepState->GetManagedObject(&pValue); if (status == S_OK && pValue != NULL) { ReleaseHolder pTypeInstance(NULL); // Make sure that we can get back a TypeInstance during inspection status = pValue->GetType(&pTypeInstance); pValue.Clear(); } // If Exception state has a new context, we will walk with the stashed context as well. // Note that in stack overflow exception's case, m_pContext is null. // // It is possible that we are in exception's catch clause when we // try to walk the stack below. This is a very weird situation where // stack is logically unwind and not physically unwind. We may not be able // to walk the stack correctly here. Anyway, we try to catch exception thrown // by bad stack walk in EnumMemWalkStackHelper. // PTR_CONTEXT pContext = ((ClrDataExceptionState*)pExcepState.GetValue())->GetCurrentContextRecord(); if (pContext != NULL) { T_CONTEXT newContext; newContext = *pContext; // We need to trigger stack walk again using the exception's context! status = pIXCLRDataTask->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED | CLRDATA_SIMPFRAME_MANAGED_METHOD | CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE, &pStackWalk); if (status == S_OK && pStackWalk != NULL) { status = pStackWalk->SetContext2(CLRDATA_STACK_SET_CURRENT_CONTEXT, sizeof(T_CONTEXT), (BYTE *) &newContext); if (status == S_OK) { status = EnumMemWalkStackHelper(flags, pStackWalk, pThread); } pStackWalk.Clear(); } } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // get the previous exception IXCLRDataExceptionState * pExcepStatePrev = NULL; status = pExcepState->GetPrevious(&pExcepStatePrev); // Release our current exception object, and transfer ref ownership of the previous // exception object into the holder. pExcepState = pExcepStatePrev; } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // get next thread pIXCLRDataTask.Clear(); status = EnumTask(&handle, &pIXCLRDataTask); } EndEnumTasks(handle); } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // updating the statistics m_dumpStats.m_cbStack = m_cbMemoryReported - cbMemoryReported; return status; } #ifdef FEATURE_COMINTEROP //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // WinRT stowed exception holds the (CCW)pointer to a managed exception object. // We should check for the presence of a such an exception object and dump it if available. // This can also drag in memory implicitly. // So do call // m_instances.DumpAllInstances(m_enumMemCb); // when function is done. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemStowedException(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; ICLRDataTarget3 *pTarget3 = GetLegacyTarget3(); if (pTarget3 == NULL) return S_OK; // get the thread that raised the exception ULONG32 exThreadID = 0; if (FAILED(pTarget3->GetExceptionThreadID(&exThreadID)) || exThreadID == 0) return S_OK; // // check that the thread is one of the known managed threads // BOOL foundThread = FALSE; CLRDATA_ENUM handle; ReleaseHolder pIXCLRDataTask(NULL); // enumerate through each thread StartEnumTasks(&handle); HRESULT status = EnumTask(&handle, &pIXCLRDataTask); for (unsigned nbThreads = 0; status == S_OK && pIXCLRDataTask != NULL; ++nbThreads) { // Avoid infinite loop if target process is corrupted. if (nbThreads > 100000) { break; } EX_TRY { if (((ClrDataTask *)pIXCLRDataTask.GetValue())->GetThread()->GetOSThreadId() == exThreadID) { // found the thread foundThread = TRUE; break; } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED // get next thread pIXCLRDataTask.Clear(); status = EnumTask(&handle, &pIXCLRDataTask); } EndEnumTasks(handle); if (!foundThread) return S_OK; // // Read the remote stowed exceptions. // // EXCEPTION_RECORD.ExceptionCode: STATUS_STOWED_EXCEPTION. // EXCEPTION_RECORD.NumberParameters: 2. // EXCEPTION_RECORD.ExceptionInformation[0]: pointer to an array of pointers // to STOWED_EXCEPTION_INFORMATION structures. // EXCEPTION_RECORD.ExceptionInformation[1]: count of elements in the array. // ULONG32 bytesRead = 0; MINIDUMP_EXCEPTION minidumpException = { 0 }; if (FAILED(pTarget3->GetExceptionRecord(sizeof(MINIDUMP_EXCEPTION), &bytesRead, (PBYTE)&minidumpException))) return S_OK; TADDR remoteStowedExceptionArray = (TADDR)minidumpException.ExceptionInformation[0]; ULONG stowedExceptionCount = (ULONG)minidumpException.ExceptionInformation[1]; if (bytesRead != sizeof(MINIDUMP_EXCEPTION) || minidumpException.ExceptionCode != STATUS_STOWED_EXCEPTION || minidumpException.NumberParameters != 2 || stowedExceptionCount < 1 // there must atleast be 1 stowed exception || stowedExceptionCount > 256 // upper bound: 256 || remoteStowedExceptionArray == NULL) { return S_OK; } for (ULONG i = 0; i < stowedExceptionCount; ++i) { // Read the i-th stowed exception TADDR remoteStowedException = NULL; if (FAILED(m_pTarget->ReadVirtual(TO_CDADDR(remoteStowedExceptionArray + (i * sizeof(TADDR))), (PBYTE)&remoteStowedException, sizeof(TADDR), &bytesRead)) || bytesRead != sizeof(TADDR) || remoteStowedException == NULL) { continue; } // check if this is a v2 stowed exception STOWED_EXCEPTION_INFORMATION_V2 stowedException = { 0 }; if (FAILED(m_pTarget->ReadVirtual(TO_CDADDR(remoteStowedException), (PBYTE)&stowedException, sizeof(STOWED_EXCEPTION_INFORMATION_HEADER), &bytesRead)) || bytesRead != sizeof(STOWED_EXCEPTION_INFORMATION_HEADER) || stowedException.Header.Signature != STOWED_EXCEPTION_INFORMATION_V2_SIGNATURE) { continue; } // Read the full v2 stowed exception and get the CCW pointer out of it if (FAILED(m_pTarget->ReadVirtual(TO_CDADDR(remoteStowedException), (PBYTE)&stowedException, sizeof(STOWED_EXCEPTION_INFORMATION_V2), &bytesRead)) || bytesRead != sizeof(STOWED_EXCEPTION_INFORMATION_V2) || stowedException.NestedExceptionType != STOWED_EXCEPTION_NESTED_TYPE_LEO || stowedException.NestedException == NULL) { continue; } // Find out if NestedException is a pointer to CCW and then dump the exception object in it DumpStowedExceptionObject(flags, TO_CDADDR(stowedException.NestedException)); } return S_OK; } HRESULT ClrDataAccess::DumpStowedExceptionObject(CLRDataEnumMemoryFlags flags, CLRDATA_ADDRESS ccwPtr) { SUPPORTS_DAC; if (ccwPtr == NULL) return S_OK; // dump the managed exception object wrapped in CCW // memory of the CCW object itself is dumped later by DacInstanceManager::DumpAllInstances DacpCCWData ccwData; GetCCWData(ccwPtr, &ccwData); // this call collects some memory implicitly DumpManagedExcepObject(flags, OBJECTREF(TO_TADDR(ccwData.managedObject))); // dump memory of the 2nd slot in the CCW's vtable // this is used in DACGetCCWFromAddress to identify if the passed in pointer is a valid CCW. ULONG32 bytesRead = 0; TADDR vTableAddress = NULL; if (FAILED(m_pTarget->ReadVirtual(ccwPtr, (PBYTE)&vTableAddress, sizeof(TADDR), &bytesRead)) || bytesRead != sizeof (TADDR) || vTableAddress == NULL) { return S_OK; } CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( ReportMem(vTableAddress + sizeof(PBYTE)* TEAR_OFF_SLOT, sizeof(TADDR)); ); return S_OK; } #endif #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Reports critical data from the CLR main module // that needs to be present in all minidumps. // Implicitly reports memory, so remember to call // m_instances.DumpAllInstances(m_enumMemCb); // after this function completes. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemCLRMainModuleInfo() { SUPPORTS_DAC; HRESULT status = S_OK; // PEDecoder is DACized, so we just need to touch what we want to // make subsequent lookup work. PEDecoder pe(m_globalBase); // We currently only actually have one debug directory entry. // Post-processing, such as optimization, may add an extra directory. // These directories are of type IMAGE_DEBUG_TYPE_RESERVED10, while our // standard CodeView directory with pdb info is IMAGE_DEBUG_TYPE_CODEVIEW. UINT i; for (i = 0; pe.GetDebugDirectoryEntry(i); i++) { } if (i < 1) { status = E_UNEXPECTED; _ASSERTE(!"Collecting dump of target with no debug directory entries!"); } // For CLRv4+, the resource directory contains the necessary info // to retrieve the DBI/DAC from a symbol server. // Specifically, in v4 it contains a mscoree!PE_FIXEDFILEINFO. // This is also required since OpenVirtualProcess will check against // this content to determine if a target module is indeed a CLR // main module. // Retrieve all resources in clr.dll. Right now, the entire resource // content is very small (~0x600 bytes of raw data), so getting all is // the easy thing to do. If resources become larger in later // releases, we'll have to specifically get just the debugging-related resources. _ASSERTE(pe.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)); if (pe.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) { COUNT_T size = 0; TADDR pResourceDirData = pe.GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_RESOURCE, &size); _ASSERTE(size < 0x2000); ReportMem((TADDR)pResourceDirData, size, true); } else { // In later releases, we should log the ERROR_RESOURCE_DATA_NOT_FOUND. status = E_UNEXPECTED; } return status; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Generating skinny mini-dump. Skinny mini-dump will only support stack trace, module list, // and Exception list viewing. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerSkinny(IN CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; HRESULT status = S_OK; // clear all of the previous cached memory Flush(); #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Enable caching enumerated metadata of interest InitStreamsForWriting(flags); #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS //TODO: actually *do* something with potential failures. It would be relatively easy to // hook up an official dump stream to put info on our failures and other 'metadata' // about dumping into in a generic sort of way. Our code doesn't have access to // MDWD's callbacks, so we can't just do it ourselves. Thus we could have useful info // baked into the dump, like we failed to enumerate mem for certain threads, etc. // Each enumeration function below should be wrapped in a try/catch // so that we have a chance to create a debuggable dump in the face of target problems. // Iterating to all threads' stacks CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); ) // Iterating to module list. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); ) // // iterating through static that we care // // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); ) // now dump the memory get dragged in by using DAC API implicitly. m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); status = m_memStatus; // Dump AppDomain-specific info needed for MiniDumpNormal. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) // Dump the Debugger object data needed CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); ) #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Dump the extra data needed for metadata-free debugging CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumStreams(flags); ) #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Do not let any remaining implicitly enumerated memory leak out. Flush(); return S_OK; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Generating triage micro-dump. Triage dumps will only support stack trace // and Exception viewing.More than that triage dumps have to be PII free, // so all exception messages have to be poisoned with 0xcc mask. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerMicroTriage(IN CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; HRESULT status = S_OK; // clear all of the previous cached memory Flush(); #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Enable caching enumerated metadata of interest InitStreamsForWriting(flags); #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Iterating to all threads' stacks CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); ) // Iterating to module list. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); ) // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); ) // now dump the memory get dragged in by using DAC API implicitly. m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); status = m_memStatus; // Dump AppDomain-specific info needed for triage dumps methods enumeration (k command). CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) // Dump the Debugger object data needed CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); ) #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Dump the extra data needed for metadata-free debugging CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumStreams(flags); ) #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS // Do not let any remaining implicitly enumerated memory leak out. Flush(); return S_OK; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Write out mscorwks's data segment. This will write out the whole // data segment for mscorwks. It is about 200 or 300K. Most of it (90%) are // vtable definition that we don't really care. But we don't have a // good walk to just write out all globals and statics. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemWriteDataSegment() { SUPPORTS_DAC; NewHolder pedecoder(NULL); EX_TRY { // Collecting mscorwks's data segment { // m_globalBase is the base address of target process's mscorwks module pedecoder = new PEDecoder(dac_cast(m_globalBase)); PTR_IMAGE_SECTION_HEADER pSection = (PTR_IMAGE_SECTION_HEADER) pedecoder->FindFirstSection(); PTR_IMAGE_SECTION_HEADER pSectionEnd = pSection + VAL16(pedecoder->GetNumberOfSections()); while (pSection < pSectionEnd) { if (pSection->Name[0] == '.' && pSection->Name[1] == 'd' && pSection->Name[2] == 'a' && pSection->Name[3] == 't' && pSection->Name[4] == 'a') { // This is the .data section of mscorwks ReportMem(m_globalBase + pSection->VirtualAddress, pSection->Misc.VirtualSize); } pSection++; } } } EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED return S_OK; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Custom Dump. Depending on the value of g_ECustomDumpFlavor, different dump // will be taken. You can set this global variable using hosting API // ICLRErrorReportingManager::BeginCustomDump. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerCustom() { SUPPORTS_DAC; HRESULT status = S_OK; ECustomDumpFlavor eFlavor; #ifdef FEATURE_INCLUDE_ALL_INTERFACES eFlavor = CCLRErrorReportingManager::g_ECustomDumpFlavor; #else eFlavor = DUMP_FLAVOR_Default; #endif m_enumMemFlags = CLRDATA_ENUM_MEM_MINI; // clear all of the previous cached memory Flush(); if (eFlavor == DUMP_FLAVOR_Mini) { // Iterating to all threads' stacks CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(m_enumMemFlags); ) // Iterating to module list. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(m_enumMemFlags); ) // // iterating through static that we care // // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(m_enumMemFlags); ) // we are done... // now dump the memory get dragged in implicitly m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); } else if (eFlavor == DUMP_FLAVOR_CriticalCLRState) { // We need to walk Threads stack to view managed frames. // Iterating through module list // Iterating to all threads' stacks CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(m_enumMemFlags); ) // Iterating to module list. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(m_enumMemFlags); ) // // iterating through static that we care // // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(m_enumMemFlags); ) // Collecting some CLR secondary critical data CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRHeapCrticalStatic(m_enumMemFlags); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemWriteDataSegment(); ) // we are done... // now dump the memory get dragged in implicitly m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); } else if (eFlavor == DUMP_FLAVOR_NonHeapCLRState) { // since all CLR hosted heap will be dump by the host, // the EE structures that are not loaded using LoadLibrary will // be included by the host. // // Thus we only need to include mscorwks's critical data and ngen images m_enumMemFlags = CLRDATA_ENUM_MEM_HEAP; CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(m_enumMemFlags); ) // Collecting some CLR secondary critical data CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRHeapCrticalStatic(m_enumMemFlags); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemWriteDataSegment(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCollectImages(); ) } else { status = E_INVALIDARG; } status = m_memStatus; return S_OK; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Minidumps traverse a giant static calltree. We already try to catch // exceptions at various lower level places and continue to report memory. // // However, if we'll jump to the top-level catcher and skip the rest of the tree, // that may mean some key data may not get emitted to the minidump. // In the case that a user requests a dump is canceled, we should skip the rest // of the tree. When a COR_E_OPERATIONCANCELED exception is thrown, is allowed to // escape all the way to this function. If any exception makes it here and is not // COR_E_OPERATIONCANCELED that indicates an issue, and the assert is meant to catch that. // Unfortunately the stack unwind will already have happened. // // Internal API to support minidump and heap dump. It just delegate // to proper function but with a top level catch. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags) { // This is infrastructure code - we don't want DacCop complaining about the calls as a result // of the use of EX_CATCH_HRESULT here. We're careful to mark EnumMemoryRegionsWorkerSkinny // and EnumMemoryRegionsWorkerHeap as just SUPPORTS_DAC so that we still get analysis. SUPPORTS_DAC_HOST_ONLY; HRESULT status = S_OK; m_enumMemFlags = flags; EX_TRY { // The various EnumMemoryRegions() implementations should understand // CLRDATA_ENUM_MEM_MINI to mean that the bare minimimum memory // to make a MiniDumpNormal work should be included. if ( flags == CLRDATA_ENUM_MEM_MINI) { // skinny mini-dump status = EnumMemoryRegionsWorkerSkinny(flags); } else if ( flags == CLRDATA_ENUM_MEM_TRIAGE) { // triage micro-dump status = EnumMemoryRegionsWorkerMicroTriage(flags); } else if ( flags == CLRDATA_ENUM_MEM_HEAP) { status = EnumMemoryRegionsWorkerHeap(flags); } else { _ASSERTE(!"Bad flags passing to EnumMemoryRegionsWrapper!"); } } EX_CATCH_HRESULT(status); // The only exception that should reach here is the cancel exception _ASSERTE(SUCCEEDED(status) || status == COR_E_OPERATIONCANCELED); return status; } #define MiniDumpWithPrivateReadWriteMemory 0x00000200 #define MiniDumpWithFullAuxiliaryState 0x00008000 #define MiniDumpFilterTriage 0x00100000 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Entry function for generating CLR aware dump. This function is called // for minidump, heap dump, and custom dumps. CLR specific memory will // be reported to outer level dumper (usually dbghelp's MiniDumpWriteDump api) // through the callback. We do not write out to file directly. // // N.B.: The CLR may report duplicate memory chunks and it's up to // the debugger to coalesce memory. *However* the debugger's current // implementation coalesces memory we enumerate and memory that // they enumerate; the two sets of memory are not guaranteed to be // coalesced. The dump produced may thus have memory blocks in the // MemoryListStream that overlap or are totally contained in other blocks. // This issue was resolved by-design by dbgteam. Win7 #407019. // Note also that Memory64ListStream (when passing MiniDumpWithFullMemory) // will have no duplicates, be sorted, etc. In that case, none of // our code is called. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STDMETHODIMP ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, IN ULONG32 miniDumpFlags, IN CLRDataEnumMemoryFlags flags) // reserved not used { SUPPORTS_DAC; HRESULT status; #if defined(DAC_MEASURE_PERF) g_nTotalTime = 0; g_nStackTotalTime = 0; g_nReadVirtualTotalTime = 0; g_nFindTotalTime = 0; g_nFindHashTotalTime = 0; g_nFindHits = 0; g_nFindCalls = 0; g_nFindFails = 0; g_nStackWalk = 0; g_nFindStackTotalTime = 0; LARGE_INTEGER nClockFrequency; unsigned __int64 nStart = 0; unsigned __int64 nEnd = 0; QueryPerformanceFrequency(&nClockFrequency); FILE* fp = fopen("c:\\dumpLog.txt", "a"); if (fp) { fprintf(fp, "\nMinidumpFlags = %d\n", miniDumpFlags); fclose(fp); } nStart = GetCycleCount(); #endif // #if defined(DAC_MEASURE_PERF) DAC_ENTER(); // We should not be trying to enumerate while we have an enumeration outstanding _ASSERTE(m_enumMemCb==NULL); m_memStatus = S_OK; m_enumMemCb = callback; // QI for ICLRDataEnumMemoryRegionsCallback2 will succeed only for Win8+. // It is expected to fail on pre Win8 OSes. callback->QueryInterface(IID_ICLRDataEnumMemoryRegionsCallback2, (void **)&m_updateMemCb); EX_TRY { ClearDumpStats(); if (miniDumpFlags & MiniDumpWithPrivateReadWriteMemory) { // heap dump status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_HEAP); } else if (miniDumpFlags & MiniDumpWithFullAuxiliaryState) { // This is the host custom dump. status = EnumMemoryRegionsWorkerCustom(); } else if (miniDumpFlags & MiniDumpFilterTriage) { // triage micro-dump status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_TRIAGE); } else { // minidump status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_MINI); } // For all dump types, we need to capture the chain to the IMAGE_DIRECTORY_ENTRY_DEBUG // contents, so that DAC can validate against the TimeDateStamp even if the // debugger can't find the main CLR module on disk. // If we already failed, don't bother. if (SUCCEEDED(status)) { // In case there's implicitly enumerated memory hanging around // let's not accidentally pick it up. Flush(); if (SUCCEEDED(status = EnumMemCLRMainModuleInfo())) { m_instances.DumpAllInstances(m_enumMemCb); } } Flush(); } EX_CATCH { m_enumMemCb = NULL; // We should never actually be here b/c none of the EMR functions should throw. // They should all either be written robustly w/ ptr.IsValid() and catching their // own exceptions. if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) { _ASSERTE_MSG(false, "Got unexpected exception in EnumMemoryRegions"); EX_RETHROW; } } EX_END_CATCH(SwallowAllExceptions) // fix for issue 866100: DAC is too late in releasing ICLRDataEnumMemoryRegionsCallback2* if (m_updateMemCb) { m_updateMemCb->Release(); m_updateMemCb = NULL; } m_enumMemCb = NULL; DAC_LEAVE(); #if defined(DAC_MEASURE_PERF) nEnd = GetCycleCount(); g_nTotalTime= nEnd - nStart; fp = fopen("c:\\dumpLog.txt", "a"); fprintf(fp, "Total = %g msec\n" "ReadVirtual = %g msec\n" "StackWalk = %g msec; Find: %g msec\n" "Find = %g msec; Hash = %g msec; Calls = %I64u; Hits = %I64u; Not found = %I64u\n\n=====\n", (float) (1000*g_nTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nReadVirtualTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nStackTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nFindStackTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nFindTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nFindHashTotalTime/nClockFrequency.QuadPart), g_nFindCalls, g_nFindHits, g_nFindFails ); fclose(fp); #endif // #if defined(DAC_MEASURE_PERF) return status; } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Clear the statistics for the dump. For each dump generation, we // clear the dump statistics. At the end of the dump generation, you can // view the statics data member m_dumpStats and see how many bytes that // we have reported to our debugger host. // //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void ClrDataAccess::ClearDumpStats() { SUPPORTS_DAC; m_cbMemoryReported = 0; memset(&m_dumpStats, 0, sizeof(DumpMemoryReportStatics)); }