Skip to content

Commit

Permalink
Add faster DAC EnumMemoryRegion option with less memory usage (#74300) (
Browse files Browse the repository at this point in the history
#74464)

Issue: #72148

Instead of drilling down into all the individual MT/MD/EEClass, etc. data structures, add the LoaderAllocator/LoaderHeaps regions directly.

Add new CLRDATA_ENUM_MEM_HEAP2 flag for the fast path.

To reduce risk of incomplete core dumps this is enabled by the COMPlus_EnableFastHeapDumps env var. This env var is only looked at by the Linux/MacOS createdump. It is currently ignored on Windows. The new HEAP2 flag is works when passed to the EnumMemoryRegions API on Windows but createdump can't set it because MiniDumpWriteDump in dbghelp.dll loads/calls the DAC API.

Fix MacOS dlopen error message
  • Loading branch information
mikem8361 committed Aug 24, 2022
1 parent b211223 commit d8cc63f
Show file tree
Hide file tree
Showing 23 changed files with 162 additions and 108 deletions.
33 changes: 22 additions & 11 deletions src/coreclr/debug/createdump/crashinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

#include "createdump.h"
#include <clrconfignocache.h>

// This is for the PAL_VirtualUnwindOutOfProc read memory adapter.
CrashInfo* g_crashInfo;
Expand Down Expand Up @@ -328,24 +329,34 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType)
{
TRACE("EnumerateMemoryRegionsWithDAC: Memory enumeration STARTED (%d %d)\n", m_enumMemoryPagesAdded, m_dataTargetPagesAdded);

// Since on both Linux and MacOS all the RW regions will be added for heap
// dumps by createdump, the only thing differentiating a MiniDumpNormal and
// a MiniDumpWithPrivateReadWriteMemory is that the later uses the EnumMemory
// APIs. This is kind of expensive on larger applications (4 minutes, or even
// more), and this should already be in RW pages. Change the dump type to the
// faster normal one. This one already ensures necessary DAC globals, etc.
// without the costly assembly, module, class, type runtime data structures
// enumeration.
// Since on MacOS all the RW regions will be added for heap dumps by createdump, the
// only thing differentiating a MiniDumpNormal and a MiniDumpWithPrivateReadWriteMemory
// is that the later uses the EnumMemoryRegions APIs. This is kind of expensive on larger
// applications (4 minutes, or even more), and this should already be in RW pages. Change
// the dump type to the faster normal one. This one already ensures necessary DAC globals,
// etc. without the costly assembly, module, class, type runtime data structures enumeration.
CLRDataEnumMemoryFlags flags = CLRDATA_ENUM_MEM_DEFAULT;
if (minidumpType & MiniDumpWithPrivateReadWriteMemory)
{
char* fastHeapDumps = getenv("COMPlus_DbgEnableFastHeapDumps");
if (fastHeapDumps != nullptr && strcmp(fastHeapDumps, "1") == 0)
// This is the old fast heap env var for backwards compatibility for VS4Mac.
CLRConfigNoCache fastHeapDumps = CLRConfigNoCache::Get("DbgEnableFastHeapDumps", /*noprefix*/ false, &getenv);
DWORD val = 0;
if (fastHeapDumps.IsSet() && fastHeapDumps.TryAsInteger(10, val) && val == 1)
{
minidumpType = MiniDumpNormal;
}
// This the new variable that also skips the expensive (in both time and memory usage)
// enumeration of the low level data structures and adds all the loader allocator heaps
// instead. The above original env var didn't generate a complete enough heap dump on
// Linux and this new one does.
fastHeapDumps = CLRConfigNoCache::Get("EnableFastHeapDumps", /*noprefix*/ false, &getenv);
if (fastHeapDumps.IsSet() && fastHeapDumps.TryAsInteger(10, val) && val == 1)
{
flags = CLRDATA_ENUM_MEM_HEAP2;
}
}
// Calls CrashInfo::EnumMemoryRegion for each memory region found by the DAC
HRESULT hr = m_pClrDataEnumRegions->EnumMemoryRegions(this, minidumpType, CLRDATA_ENUM_MEM_DEFAULT);
HRESULT hr = m_pClrDataEnumRegions->EnumMemoryRegions(this, minidumpType, flags);
if (FAILED(hr))
{
printf_error("EnumMemoryRegions FAILED %s (%08x)\n", GetHResultString(hr), hr);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/debug/createdump/crashinfomac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ ModuleInfo::LoadModule()
}
else
{
TRACE("LoadModule: dlopen(%s) FAILED %d %s\n", m_moduleName.c_str(), errno, strerror(errno));
TRACE("LoadModule: dlopen(%s) FAILED %s\n", m_moduleName.c_str(), dlerror());
}
}
}
27 changes: 8 additions & 19 deletions src/coreclr/debug/daccess/daccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6246,24 +6246,15 @@ bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*=
{
if (!IsFullyReadable(addr, size))
{
if (!fExpectSuccess)
if (fExpectSuccess)
{
// We know the read might fail (eg. we're trying to find mapped pages in
// a module image), so just skip this block silently.
// Note that the EnumMemoryRegion callback won't necessarily do anything if any part of
// the region is unreadable, and so there is no point in calling it. For cases where we expect
// the read might fail, but we want to report any partial blocks, we have to break up the region
// into pages and try reporting each page anyway
return true;
// We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort
// reporting and continue with the next data structure (where the exception is caught),
// just like we would for a DAC read error (otherwise we might do something stupid
// like get into an infinite loop, or otherwise waste time with corrupt data).
TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering");
return false;
}

// We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort
// reporting and continue with the next data structure (where the exception is caught),
// just like we would for a DAC read error (otherwise we might do something stupid
// like get into an infinite loop, or otherwise waste time with corrupt data).

TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering");
return false;
}
}

Expand All @@ -6275,9 +6266,7 @@ bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*=
// data structure at all. Hopefully experience will help guide this going forward.
// @dbgtodo : Extend dump-gathering API to allow a dump-log to be included.
const TSIZE_T kMaxMiniDumpRegion = 4*1024*1024 - 3; // 4MB-3
if( size > kMaxMiniDumpRegion
&& (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI
|| m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE))
if (size > kMaxMiniDumpRegion && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE))
{
TARGET_CONSISTENCY_CHECK( false, "Dump target consistency failure - truncating minidump data structure");
size = kMaxMiniDumpRegion;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3618,7 +3618,7 @@ void DacDbiInterfaceImpl::EnumerateMemRangesForLoaderAllocator(PTR_LoaderAllocat

// GetVirtualCallStubManager returns VirtualCallStubManager*, but it's really an address to target as
// pLoaderAllocator is DACized. Cast it so we don't try to to a Host to Target translation.
VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager(TO_TADDR(pLoaderAllocator->GetVirtualCallStubManager()));
VirtualCallStubManager *pVcsMgr = pLoaderAllocator->GetVirtualCallStubManager();
LOG((LF_CORDB, LL_INFO10000, "DDBII::EMRFLA: VirtualCallStubManager 0x%x\n", PTR_HOST_TO_TADDR(pVcsMgr)));
if (pVcsMgr)
{
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ class ClrDataAccess

HRESULT EnumMemCollectImages();
HRESULT EnumMemCLRStatic(CLRDataEnumMemoryFlags flags);
HRESULT EnumMemDumpJitManagerInfo(IN CLRDataEnumMemoryFlags flags);
HRESULT EnumMemCLRHeapCrticalStatic(CLRDataEnumMemoryFlags flags);
HRESULT EnumMemDumpModuleList(CLRDataEnumMemoryFlags flags);
HRESULT EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags);
Expand Down
35 changes: 33 additions & 2 deletions src/coreclr/debug/daccess/enummem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,21 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags)
return S_OK;
}

HRESULT ClrDataAccess::EnumMemDumpJitManagerInfo(IN CLRDataEnumMemoryFlags flags)
{
SUPPORTS_DAC;

HRESULT status = S_OK;

if (flags == CLRDATA_ENUM_MEM_HEAP2)
{
EEJitManager* managerPtr = ExecutionManager::GetEEJitManager();
managerPtr->EnumMemoryRegions(flags);
}

return status;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// This function reports memory that a heap dump need to debug CLR
Expand Down Expand Up @@ -325,6 +340,9 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags fla
// Dump AppDomain-specific info
CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); )

// Dump jit manager info
CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpJitManagerInfo(flags); )

// Dump the Debugger object data needed
CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); )

Expand Down Expand Up @@ -680,6 +698,11 @@ HRESULT ClrDataAccess::EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags)
{
SUPPORTS_DAC;

if (flags == CLRDATA_ENUM_MEM_HEAP2)
{
SystemDomain::System()->GetLoaderAllocator()->EnumMemoryRegions(flags);
}

AppDomainIterator adIter(FALSE);
EX_TRY
{
Expand Down Expand Up @@ -1853,7 +1876,7 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags)
// triage micro-dump
status = EnumMemoryRegionsWorkerMicroTriage(flags);
}
else if (flags == CLRDATA_ENUM_MEM_HEAP)
else if (flags == CLRDATA_ENUM_MEM_HEAP || flags == CLRDATA_ENUM_MEM_HEAP2)
{
status = EnumMemoryRegionsWorkerHeap(flags);
}
Expand Down Expand Up @@ -1946,7 +1969,15 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback,
if (miniDumpFlags & MiniDumpWithPrivateReadWriteMemory)
{
// heap dump
status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_HEAP);
if (flags == CLRDATA_ENUM_MEM_HEAP2)
{
DacLogMessage("EnumMemoryRegions(CLRDATA_ENUM_MEM_HEAP2)\n");
}
else
{
flags = CLRDATA_ENUM_MEM_HEAP;
}
status = EnumMemoryRegionsWrapper(flags);
}
else if (miniDumpFlags & MiniDumpWithFullAuxiliaryState)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3497,7 +3497,7 @@ ClrDataAccess::TraverseVirtCallStubHeap(CLRDATA_ADDRESS pAppDomain, VCSHeapType
SOSDacEnter();

BaseDomain* pBaseDomain = PTR_BaseDomain(TO_TADDR(pAppDomain));
VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager((TADDR)pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager());
VirtualCallStubManager *pVcsMgr = pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager();
if (!pVcsMgr)
{
hr = E_POINTER;
Expand Down
11 changes: 6 additions & 5 deletions src/coreclr/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16596,22 +16596,23 @@ Debugger::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
DAC_ENUM_VTHIS();
SUPPORTS_DAC;
_ASSERTE(m_rgHijackFunction != NULL);

if ( flags != CLRDATA_ENUM_MEM_TRIAGE)
if (flags != CLRDATA_ENUM_MEM_TRIAGE)
{
if (m_pMethodInfos.IsValid())
{
m_pMethodInfos->EnumMemoryRegions(flags);
}

DacEnumMemoryRegion(dac_cast<TADDR>(m_pLazyData),
sizeof(DebuggerLazyInit));
DacEnumMemoryRegion(dac_cast<TADDR>(m_pLazyData), sizeof(DebuggerLazyInit));
}

// Needed for stack walking from an initial native context. If the debugger can find the
// on-disk image of clr.dll, then this is not necessary.
DacEnumMemoryRegion(dac_cast<TADDR>(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions);
if (m_rgHijackFunction.IsValid())
{
DacEnumMemoryRegion(dac_cast<TADDR>(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions);
}
}


Expand Down
8 changes: 3 additions & 5 deletions src/coreclr/debug/ee/functioninfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2456,9 +2456,7 @@ DebuggerMethodInfoEntry::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)

// For a MiniDumpNormal, what is needed for modules is already enumerated elsewhere.
// Don't waste time doing it here an extra time. Also, this will add many MB extra into the dump.
if ((key.pModule.IsValid()) &&
CLRDATA_ENUM_MEM_MINI != flags
&& CLRDATA_ENUM_MEM_TRIAGE != flags)
if ((key.pModule.IsValid()) && CLRDATA_ENUM_MEM_MINI != flags && CLRDATA_ENUM_MEM_TRIAGE != flags && CLRDATA_ENUM_MEM_HEAP2 != flags)
{
key.pModule->EnumMemoryRegions(flags, true);
}
Expand All @@ -2476,7 +2474,7 @@ DebuggerMethodInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
DAC_ENUM_DTHIS();
SUPPORTS_DAC;

if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE)
if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2)
{
// Modules are enumerated already for minidumps, save the empty calls.
if (m_module.IsValid())
Expand Down Expand Up @@ -2505,7 +2503,7 @@ DebuggerJitInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
m_methodInfo->EnumMemoryRegions(flags);
}

if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE)
if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2)
{
if (m_nativeCodeVersion.GetMethodDesc().IsValid())
{
Expand Down
10 changes: 6 additions & 4 deletions src/coreclr/inc/clrdata.idl
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,12 @@ interface ICLRDataLoggingCallback : IUnknown
typedef enum CLRDataEnumMemoryFlags
{
CLRDATA_ENUM_MEM_DEFAULT = 0x0,
CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, // generating skinny mini-dump
CLRDATA_ENUM_MEM_HEAP = 0x1, // generating heap dump
CLRDATA_ENUM_MEM_TRIAGE = 0x2, // generating triage mini-dump

CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, // generating skinny mini-dump
CLRDATA_ENUM_MEM_HEAP = 0x1, // generating heap dump
CLRDATA_ENUM_MEM_TRIAGE = 0x2, // generating triage mini-dump
/* Generate heap dumps faster with less memory usage than CLRDATA_ENUM_MEM_HEAP by adding
the loader heaps instead of traversing all the individual runtime data structures. */
CLRDATA_ENUM_MEM_HEAP2 = 0x3,
/* More bits to be added here later */
} CLRDataEnumMemoryFlags;

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/daccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -2466,7 +2466,7 @@ typedef DPTR(PTR_PCODE) PTR_PTR_PCODE;

// Helper macro for tracking EnumMemoryRegions progress.
#if 0
#define EMEM_OUT(args) DacWarning args
#define EMEM_OUT(args) DacLogMessage args
#else
#define EMEM_OUT(args)
#endif
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/pal/prebuilt/inc/clrdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,8 @@ enum CLRDataEnumMemoryFlags
CLRDATA_ENUM_MEM_DEFAULT = 0,
CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT,
CLRDATA_ENUM_MEM_HEAP = 0x1,
CLRDATA_ENUM_MEM_TRIAGE = 0x2
CLRDATA_ENUM_MEM_TRIAGE = 0x2,
CLRDATA_ENUM_MEM_HEAP2 = 0x3
} CLRDataEnumMemoryFlags;


Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/utilcode/loaderheap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ RangeList::RangeListBlock::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
// code:LoaderHeap::UnlockedReservePages adds a range for the entire reserved region, instead
// of updating the RangeList when pages are committed. But in that case, the committed region of
// memory will be enumerated by the LoaderHeap anyway, so it's OK if this fails
EMEM_OUT(("MEM: RangeListBlock %p - %p\n", range->start, range->end));
DacEnumMemoryRegion(range->start, size, false);
}
}
Expand Down Expand Up @@ -1933,8 +1934,6 @@ void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
WRAPPER_NO_CONTRACT;

DAC_ENUM_DTHIS();

PTR_LoaderHeapBlock block = m_pFirstBlock;
while (block.IsValid())
{
Expand All @@ -1946,6 +1945,7 @@ void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
// but it seems wasteful (eg. makes each AppDomain objects 32 bytes larger on x64).
TADDR addr = dac_cast<TADDR>(block->pVirtualAddress);
TSIZE_T size = block->dwVirtualSize;
EMEM_OUT(("MEM: UnlockedLoaderHeap %p - %p\n", addr, addr + size));
DacEnumMemoryRegion(addr, size, false);

block = block->pNext;
Expand Down
37 changes: 13 additions & 24 deletions src/coreclr/vm/appdomain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5086,42 +5086,28 @@ DomainLocalModule::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
}

void
BaseDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags,
bool enumThis)
{
SUPPORTS_DAC;
if (enumThis)
{
// This is wrong. Don't do it.
// BaseDomain cannot be instantiated.
// The only thing this code can hope to accomplish is to potentially break
// memory enumeration walking through the derived class if we
// explicitly call the base class enum first.
// DAC_ENUM_VTHIS();
}

EMEM_OUT(("MEM: %p BaseDomain\n", dac_cast<TADDR>(this)));
}

void
AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags,
bool enumThis)
AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis)
{
SUPPORTS_DAC;

if (enumThis)
{
//sizeof(AppDomain) == 0xeb0
DAC_ENUM_VTHIS();
EMEM_OUT(("MEM: %p AppDomain\n", dac_cast<TADDR>(this)));
}
BaseDomain::EnumMemoryRegions(flags, false);

// We don't need AppDomain name in triage dumps.
if (flags != CLRDATA_ENUM_MEM_TRIAGE)
{
m_friendlyName.EnumMemoryRegions(flags);
}

if (flags == CLRDATA_ENUM_MEM_HEAP2)
{
GetLoaderAllocator()->EnumMemoryRegions(flags);
}

m_Assemblies.EnumMemoryRegions(flags);
AssemblyIterator assem = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution));
CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
Expand All @@ -5133,16 +5119,19 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags,
}

void
SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags,
bool enumThis)
SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis)
{
SUPPORTS_DAC;
if (enumThis)
{
DAC_ENUM_VTHIS();
EMEM_OUT(("MEM: %p SystemAppomain\n", dac_cast<TADDR>(this)));
}
BaseDomain::EnumMemoryRegions(flags, false);

if (flags == CLRDATA_ENUM_MEM_HEAP2)
{
GetLoaderAllocator()->EnumMemoryRegions(flags);
}
if (m_pSystemPEAssembly.IsValid())
{
m_pSystemPEAssembly->EnumMemoryRegions(flags);
Expand Down
Loading

0 comments on commit d8cc63f

Please sign in to comment.