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.
5 #include "stdafx.h" // Precompiled header key.
6 #include "loaderheap.h"
9 #define DONOT_DEFINE_ETW_CALLBACK
10 #include "eventtracebase.h"
12 #define LHF_EXECUTABLE 0x1
14 #ifndef DACCESS_COMPILE
16 INDEBUG(DWORD UnlockedLoaderHeap::s_dwNumInstancesOfLoaderHeaps = 0;)
18 #ifdef RANDOMIZE_ALLOC
23 Random() { seed = (unsigned int)time(NULL); }
26 return ((seed = seed * 214013L + 2531011L) >> 16) & 0x7fff;
35 #if !defined(SELF_NO_HOST) // ETW available only in the runtime
36 inline void EtwAllocRequest(UnlockedLoaderHeap * const pHeap, void* ptr, size_t dwSize)
38 FireEtwAllocRequest(pHeap, ptr, static_cast<unsigned int>(dwSize), 0, 0, GetClrInstanceId());
41 #define EtwAllocRequest(pHeap, ptr, dwSize) ((void)0)
42 #endif // SELF_NO_HOST
46 // RangeLists are constructed so they can be searched from multiple
47 // threads without locking. They do require locking in order to
48 // be safely modified, though.
51 RangeList::RangeList()
55 InitBlock(&m_starterBlock);
57 m_firstEmptyBlock = &m_starterBlock;
58 m_firstEmptyRange = 0;
61 RangeList::~RangeList()
63 LIMITED_METHOD_CONTRACT;
65 RangeListBlock *b = m_starterBlock.next;
69 RangeListBlock *bNext = b->next;
75 void RangeList::InitBlock(RangeListBlock *b)
77 LIMITED_METHOD_CONTRACT;
80 Range *rEnd = r + RANGE_COUNT;
87 BOOL RangeList::AddRangeWorker(const BYTE *start, const BYTE *end, void *id)
94 INJECT_FAULT(return FALSE;);
100 RangeListBlock *b = m_firstEmptyBlock;
101 Range *r = b->ranges + m_firstEmptyRange;
102 Range *rEnd = b->ranges + RANGE_COUNT;
110 r->start = (TADDR)start;
116 m_firstEmptyBlock = b;
117 m_firstEmptyRange = r - b->ranges;
125 // If there are no more blocks, allocate a
131 RangeListBlock *newBlock = new (nothrow) RangeListBlock;
133 if (newBlock == NULL)
135 m_firstEmptyBlock = b;
136 m_firstEmptyRange = r - b->ranges;
142 newBlock->next = NULL;
152 rEnd = r + RANGE_COUNT;
156 void RangeList::RemoveRangesWorker(void *id, const BYTE* start, const BYTE* end)
167 RangeListBlock *b = &m_starterBlock;
168 Range *r = b->ranges;
169 Range *rEnd = r + RANGE_COUNT;
172 // Find the first free element, & mark it.
178 // Clear entries in this block.
187 _ASSERTE(end != NULL);
189 if (r->start >= (TADDR)start && r->start < (TADDR)end)
191 CONSISTENCY_CHECK_MSGF(r->end >= (TADDR)start &&
192 r->end <= (TADDR)end,
193 ("r: %p start: %p end: %p", r, start, end));
197 else if (r->id == (TADDR)id)
207 // If there are no more blocks, we're done.
212 m_firstEmptyRange = 0;
213 m_firstEmptyBlock = &m_starterBlock;
224 rEnd = r + RANGE_COUNT;
228 #endif // #ifndef DACCESS_COMPILE
230 BOOL RangeList::IsInRangeWorker(TADDR address, TADDR *pID /* = NULL */)
243 RangeListBlock* b = &m_starterBlock;
244 Range* r = b->ranges;
245 Range* rEnd = r + RANGE_COUNT;
248 // Look for a matching element
269 // If there are no more blocks, we're done.
281 rEnd = r + RANGE_COUNT;
285 #ifdef DACCESS_COMPILE
288 RangeList::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
293 // This class is almost always contained in something
294 // else so there's no enumeration of 'this'.
296 RangeListBlock* block = &m_starterBlock;
297 block->EnumMemoryRegions(flags);
299 while (block->next.IsValid())
301 block->next.EnumMem();
304 block->EnumMemoryRegions(flags);
309 RangeList::RangeListBlock::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
318 // The code below iterates each range stored in the RangeListBlock and
319 // dumps the memory region represented by each range.
320 // It is too much memory for a mini-dump, so we just bail out for mini-dumps.
321 if (flags == CLRDATA_ENUM_MEM_MINI || flags == CLRDATA_ENUM_MEM_TRIAGE)
326 WIN64_ONLY( BADFOOD = 0xbaadf00dbaadf00d; );
327 NOT_WIN64( BADFOOD = 0xbaadf00d; );
329 for (i=0; i<RANGE_COUNT; i++)
331 range = &(this->ranges[i]);
332 if (range->id == NULL || range->start == NULL || range->end == NULL ||
333 // just looking at the lower 4bytes is good enough on WIN64
334 range->start == BADFOOD || range->end == BADFOOD)
339 size = range->end - range->start;
340 _ASSERTE( size < ULONG_MAX ); // ranges should be less than 4gig!
342 // We can't be sure this entire range is mapped. For example, the code:StubLinkStubManager
343 // keeps track of all ranges in the code:BaseDomain::m_pStubHeap LoaderHeap, and
344 // code:LoaderHeap::UnlockedReservePages adds a range for the entire reserved region, instead
345 // of updating the RangeList when pages are committed. But in that case, the committed region of
346 // memory will be enumerated by the LoaderHeap anyway, so it's OK if this fails
347 DacEnumMemoryRegion(range->start, size, false);
351 #endif // #ifdef DACCESS_COMPILE
354 //=====================================================================================
355 // In DEBUG builds only, we tag live blocks with the requested size and the type of
356 // allocation (AllocMem, AllocAlignedMem, AllocateOntoReservedMem). This is strictly
357 // to validate that those who call Backout* are passing in the right values.
359 // For simplicity, we'll use one LoaderHeapValidationTag structure for all types even
360 // though not all fields are applicable to all types.
361 //=====================================================================================
369 struct LoaderHeapValidationTag
371 size_t m_dwRequestedSize; // What the caller requested (not what was actually allocated)
372 AllocationType m_allocationType; // Which api allocated this block.
373 const char * m_szFile; // Who allocated me
374 int m_lineNum; // Who allocated me
383 //=====================================================================================
384 // These classes do detailed loaderheap sniffing to help in debugging heap crashes
385 //=====================================================================================
388 // This structure logs the results of an Alloc or Free call. They are stored in reverse time order
389 // with UnlockedLoaderHeap::m_pEventList pointing to the most recent event.
390 struct LoaderHeapEvent
392 LoaderHeapEvent *m_pNext;
393 AllocationType m_allocationType; //Which api was called
394 const char *m_szFile; //Caller Id
395 int m_lineNum; //Caller Id
396 const char *m_szAllocFile; //(BackoutEvents): Who allocated the block?
397 int m_allocLineNum; //(BackoutEvents): Who allocated the block?
398 void *m_pMem; //Starting address of block
399 size_t m_dwRequestedSize; //Requested size of block
400 size_t m_dwSize; //Actual size of block (including validation tags, padding, everything)
403 void Describe(SString *pSString)
413 pSString->AppendASCII("\n");
417 if (m_allocationType == kFreedMem)
419 buf.Printf(" Freed at: %s (line %d)\n", m_szFile, m_lineNum);
420 buf.Printf(" (block originally allocated at %s (line %d)\n", m_szAllocFile, m_allocLineNum);
424 buf.Printf(" Allocated at: %s (line %d)\n", m_szFile, m_lineNum);
426 pSString->Append(buf);
429 if (!QuietValidate())
431 pSString->AppendASCII(" *** THIS BLOCK HAS BEEN CORRUPTED ***\n");
438 buf.Printf(" Type: ");
439 switch (m_allocationType)
442 buf.AppendASCII("AllocMem()\n");
445 buf.AppendASCII("Free\n");
450 pSString->Append(buf);
456 buf.Printf(" Start of block: 0x%p\n", m_pMem);
457 pSString->Append(buf);
462 buf.Printf(" End of block: 0x%p\n", ((BYTE*)m_pMem) + m_dwSize - 1);
463 pSString->Append(buf);
468 buf.Printf(" Requested size: %lu (0x%lx)\n", (ULONG)m_dwRequestedSize, (ULONG)m_dwRequestedSize);
469 pSString->Append(buf);
474 buf.Printf(" Actual size: %lu (0x%lx)\n", (ULONG)m_dwSize, (ULONG)m_dwSize);
475 pSString->Append(buf);
478 pSString->AppendASCII("\n");
483 BOOL QuietValidate();
488 class LoaderHeapSniffer
491 static DWORD InitDebugFlags()
495 DWORD dwDebugFlags = 0;
496 if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_LoaderHeapCallTracing))
498 dwDebugFlags |= UnlockedLoaderHeap::kCallTracing;
504 static VOID RecordEvent(UnlockedLoaderHeap *pHeap,
505 AllocationType allocationType,
506 __in const char *szFile,
508 __in const char *szAllocFile,
511 size_t dwRequestedSize,
515 static VOID ClearEvents(UnlockedLoaderHeap *pHeap)
517 STATIC_CONTRACT_NOTHROW;
518 STATIC_CONTRACT_FORBID_FAULT;
520 LoaderHeapEvent *pEvent = pHeap->m_pEventList;
523 LoaderHeapEvent *pNext = pEvent->m_pNext;
527 pHeap->m_pEventList = NULL;
531 static VOID CompactEvents(UnlockedLoaderHeap *pHeap)
533 STATIC_CONTRACT_NOTHROW;
534 STATIC_CONTRACT_FORBID_FAULT;
536 LoaderHeapEvent **ppEvent = &(pHeap->m_pEventList);
539 LoaderHeapEvent *pEvent = *ppEvent;
540 if (pEvent->m_allocationType != kFreedMem)
542 ppEvent = &(pEvent->m_pNext);
546 LoaderHeapEvent **ppWalk = &(pEvent->m_pNext);
547 BOOL fMatchFound = FALSE;
548 while (*ppWalk && !fMatchFound)
550 LoaderHeapEvent *pWalk = *ppWalk;
551 if (pWalk->m_allocationType != kFreedMem &&
552 pWalk->m_pMem == pEvent->m_pMem &&
553 pWalk->m_dwRequestedSize == pEvent->m_dwRequestedSize)
555 // Delete matched pairs
557 // Order is important here - updating *ppWalk may change pEvent->m_pNext, and we want
558 // to get the updated value when we unlink pEvent.
559 *ppWalk = pWalk->m_pNext;
560 *ppEvent = pEvent->m_pNext;
568 ppWalk = &(pWalk->m_pNext);
574 ppEvent = &(pEvent->m_pNext);
579 static VOID PrintEvents(UnlockedLoaderHeap *pHeap)
581 STATIC_CONTRACT_NOTHROW;
582 STATIC_CONTRACT_FORBID_FAULT;
584 printf("\n------------- LoaderHeapEvents (in reverse time order!) --------------------");
586 LoaderHeapEvent *pEvent = pHeap->m_pEventList;
590 switch (pEvent->m_allocationType)
592 case kAllocMem: printf("AllocMem "); break;
593 case kFreedMem: printf("BackoutMem "); break;
596 printf(" ptr = 0x%-8p", pEvent->m_pMem);
597 printf(" rqsize = 0x%-8x", pEvent->m_dwRequestedSize);
598 printf(" actsize = 0x%-8x", pEvent->m_dwSize);
599 printf(" (at %s@%d)", pEvent->m_szFile, pEvent->m_lineNum);
600 if (pEvent->m_allocationType == kFreedMem)
602 printf(" (original allocation at %s@%d)", pEvent->m_szAllocFile, pEvent->m_allocLineNum);
605 pEvent = pEvent->m_pNext;
608 printf("\n------------- End of LoaderHeapEvents --------------------------------------");
614 static VOID PitchSniffer(SString *pSString)
617 pSString->AppendASCII("\n"
618 "\nBecause call-tracing wasn't turned on, we couldn't provide details about who last owned the affected memory block. To get more precise diagnostics,"
619 "\nset the following registry DWORD value:"
621 "\n HKLM\\Software\\Microsoft\\.NETFramework\\LoaderHeapCallTracing = 1"
623 "\nand rerun the scenario that crashed."
628 static LoaderHeapEvent *FindEvent(UnlockedLoaderHeap *pHeap, void *pAddr)
630 LIMITED_METHOD_CONTRACT;
632 LoaderHeapEvent *pEvent = pHeap->m_pEventList;
635 if (pAddr >= pEvent->m_pMem && pAddr <= ( ((BYTE*)pEvent->m_pMem) + pEvent->m_dwSize - 1))
639 pEvent = pEvent->m_pNext;
646 static void ValidateFreeList(UnlockedLoaderHeap *pHeap);
648 static void WeGotAFaultNowWhat(UnlockedLoaderHeap *pHeap)
651 ValidateFreeList(pHeap);
653 //If none of the above popped up an assert, pop up a generic one.
654 _ASSERTE(!("Unexpected AV inside LoaderHeap. The usual reason is that someone overwrote the end of a block or wrote into a freed block.\n"));
665 #define LOADER_HEAP_BEGIN_TRAP_FAULT BOOL __faulted = FALSE; EX_TRY {
666 #define LOADER_HEAP_END_TRAP_FAULT } EX_CATCH {__faulted = TRUE; } EX_END_CATCH(SwallowAllExceptions) if (__faulted) LoaderHeapSniffer::WeGotAFaultNowWhat(pHeap);
668 #define LOADER_HEAP_BEGIN_TRAP_FAULT
669 #define LOADER_HEAP_END_TRAP_FAULT
673 size_t AllocMem_TotalSize(size_t dwRequestedSize, UnlockedLoaderHeap *pHeap);
675 //=====================================================================================
676 // This freelist implementation is a first cut and probably needs to be tuned.
677 // It should be tuned with the following assumptions:
679 // - Freeing LoaderHeap memory is done primarily for OOM backout. LoaderHeaps
680 // weren't designed to be general purpose heaps and shouldn't be used that way.
682 // - And hence, when memory is freed, expect it to be freed in large clumps and in a
683 // LIFO order. Since the LoaderHeap normally hands out memory with sequentially
684 // increasing addresses, blocks will typically be freed with sequentially decreasing
687 // The first cut of the freelist is a single-linked list of free blocks using first-fit.
688 // Assuming the above alloc-free pattern holds, the list will end up mostly sorted
689 // in increasing address order. When a block is freed, we'll attempt to coalesce it
690 // with the first block in the list. We could also choose to be more aggressive about
691 // sorting and coalescing but this should probably catch most cases in practice.
692 //=====================================================================================
694 // When a block is freed, we place this structure on the first bytes of the freed block (Allocations
695 // are bumped in size if necessary to make sure there's room.)
696 struct LoaderHeapFreeBlock
699 LoaderHeapFreeBlock *m_pNext; // Pointer to next block on free list
700 size_t m_dwSize; // Total size of this block (including this header)
701 //! Try not to grow the size of this structure. It places a minimum size on LoaderHeap allocations.
703 static void InsertFreeBlock(LoaderHeapFreeBlock **ppHead, void *pMem, size_t dwTotalSize, UnlockedLoaderHeap *pHeap)
705 STATIC_CONTRACT_NOTHROW;
706 STATIC_CONTRACT_GC_NOTRIGGER;
708 LOADER_HEAP_BEGIN_TRAP_FAULT
710 // It's illegal to insert a free block that's smaller than the minimum sized allocation -
711 // it may stay stranded on the freelist forever.
713 if (!(dwTotalSize >= AllocMem_TotalSize(1, pHeap)))
715 LoaderHeapSniffer::ValidateFreeList(pHeap);
716 _ASSERTE(dwTotalSize >= AllocMem_TotalSize(1, pHeap));
719 if (!(0 == (dwTotalSize & ALLOC_ALIGN_CONSTANT)))
721 LoaderHeapSniffer::ValidateFreeList(pHeap);
722 _ASSERTE(0 == (dwTotalSize & ALLOC_ALIGN_CONSTANT));
726 INDEBUG(memset(pMem, 0xcc, dwTotalSize);)
727 LoaderHeapFreeBlock *pNewBlock = (LoaderHeapFreeBlock*)pMem;
728 pNewBlock->m_pNext = *ppHead;
729 pNewBlock->m_dwSize = dwTotalSize;
732 MergeBlock(pNewBlock, pHeap);
734 LOADER_HEAP_END_TRAP_FAULT
738 static void *AllocFromFreeList(LoaderHeapFreeBlock **ppHead, size_t dwSize, BOOL fRemoveFromFreeList, UnlockedLoaderHeap *pHeap)
740 STATIC_CONTRACT_NOTHROW;
741 STATIC_CONTRACT_GC_NOTRIGGER;
743 INCONTRACT(_ASSERTE_IMPL(!ARE_FAULTS_FORBIDDEN()));
745 void *pResult = NULL;
746 LOADER_HEAP_BEGIN_TRAP_FAULT
748 LoaderHeapFreeBlock **ppWalk = ppHead;
751 LoaderHeapFreeBlock *pCur = *ppWalk;
752 size_t dwCurSize = pCur->m_dwSize;
753 if (dwCurSize == dwSize)
756 // Exact match. Hooray!
757 if (fRemoveFromFreeList)
759 *ppWalk = pCur->m_pNext;
763 else if (dwCurSize > dwSize && (dwCurSize - dwSize) >= AllocMem_TotalSize(1, pHeap))
765 // Partial match. Ok...
767 if (fRemoveFromFreeList)
769 *ppWalk = pCur->m_pNext;
770 InsertFreeBlock(ppWalk, ((BYTE*)pCur) + dwSize, dwCurSize - dwSize, pHeap );
775 // Either block is too small or splitting the block would leave a remainder that's smaller than
776 // the minimum block size. Onto next one.
778 ppWalk = &( pCur->m_pNext );
781 if (pResult && fRemoveFromFreeList)
783 // Callers of loaderheap assume allocated memory is zero-inited so we must preserve this invariant!
784 memset(pResult, 0, dwSize);
786 LOADER_HEAP_END_TRAP_FAULT
795 // Try to merge pFreeBlock with its immediate successor. Return TRUE if a merge happened. FALSE if no merge happened.
796 static BOOL MergeBlock(LoaderHeapFreeBlock *pFreeBlock, UnlockedLoaderHeap *pHeap)
798 STATIC_CONTRACT_NOTHROW;
802 LOADER_HEAP_BEGIN_TRAP_FAULT
804 LoaderHeapFreeBlock *pNextBlock = pFreeBlock->m_pNext;
805 size_t dwSize = pFreeBlock->m_dwSize;
807 if (pNextBlock == NULL || ((BYTE*)pNextBlock) != (((BYTE*)pFreeBlock) + dwSize))
813 size_t dwCombinedSize = dwSize + pNextBlock->m_dwSize;
814 LoaderHeapFreeBlock *pNextNextBlock = pNextBlock->m_pNext;
815 INDEBUG(memset(pFreeBlock, 0xcc, dwCombinedSize);)
816 pFreeBlock->m_pNext = pNextNextBlock;
817 pFreeBlock->m_dwSize = dwCombinedSize;
822 LOADER_HEAP_END_TRAP_FAULT
832 //=====================================================================================
833 // These helpers encapsulate the actual layout of a block allocated by AllocMem
834 // and UnlockedAllocMem():
836 // ==> Starting address is always pointer-aligned.
838 // - x bytes of user bytes (where "x" is the actual dwSize passed into AllocMem)
840 // - y bytes of "EE" (DEBUG-ONLY) (where "y" == LOADER_HEAP_DEBUG_BOUNDARY (normally 0))
841 // - z bytes of pad (DEBUG-ONLY) (where "z" is just enough to pointer-align the following byte)
842 // - a bytes of tag (DEBUG-ONLY) (where "a" is sizeof(LoaderHeapValidationTag)
844 // - b bytes of pad (if total size after all this < sizeof(LoaderHeapFreeBlock), pad enough to make it the size of LoaderHeapFreeBlock)
845 // - c bytes of pad (where "c" is just enough to pointer-align the following byte)
847 // ==> Following address is always pointer-aligned
848 //=====================================================================================
850 // Convert the requested size into the total # of bytes we'll actually allocate (including padding)
851 inline size_t AllocMem_TotalSize(size_t dwRequestedSize, UnlockedLoaderHeap *pHeap)
853 LIMITED_METHOD_CONTRACT;
855 size_t dwSize = dwRequestedSize;
857 dwSize += LOADER_HEAP_DEBUG_BOUNDARY;
858 dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT));
860 if (!pHeap->m_fExplicitControl)
862 dwSize += sizeof(LoaderHeapValidationTag);
865 if (!pHeap->m_fExplicitControl)
867 if (dwSize < sizeof(LoaderHeapFreeBlock))
869 dwSize = sizeof(LoaderHeapFreeBlock);
872 dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT));
879 LoaderHeapValidationTag *AllocMem_GetTag(LPVOID pBlock, size_t dwRequestedSize)
881 LIMITED_METHOD_CONTRACT;
883 size_t dwSize = dwRequestedSize;
884 dwSize += LOADER_HEAP_DEBUG_BOUNDARY;
885 dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT));
886 return (LoaderHeapValidationTag *)( ((BYTE*)pBlock) + dwSize );
894 //=====================================================================================
895 // UnlockedLoaderHeap methods
896 //=====================================================================================
898 #ifndef DACCESS_COMPILE
900 UnlockedLoaderHeap::UnlockedLoaderHeap(DWORD dwReserveBlockSize,
901 DWORD dwCommitBlockSize,
902 const BYTE* dwReservedRegionAddress,
903 SIZE_T dwReservedRegionSize,
904 size_t *pPrivatePerfCounter_LoaderBytes,
905 RangeList *pRangeList,
906 BOOL fMakeExecutable)
917 m_pFirstBlock = NULL;
919 m_dwReserveBlockSize = dwReserveBlockSize;
920 m_dwCommitBlockSize = dwCommitBlockSize;
922 m_pPtrToEndOfCommittedRegion = NULL;
923 m_pEndReservedRegion = NULL;
926 m_pRangeList = pRangeList;
928 // Round to VIRTUAL_ALLOC_RESERVE_GRANULARITY
932 m_dwDebugWastedBytes = 0;
933 s_dwNumInstancesOfLoaderHeaps++;
935 m_dwDebugFlags = LoaderHeapSniffer::InitDebugFlags();
936 m_fPermitStubsWithUnwindInfo = FALSE;
937 m_fStubUnwindInfoUnregistered= FALSE;
940 m_pPrivatePerfCounter_LoaderBytes = pPrivatePerfCounter_LoaderBytes;
944 #ifndef CROSSGEN_COMPILE
946 m_Options |= LHF_EXECUTABLE;
947 #endif // CROSSGEN_COMPILE
949 m_pFirstFreeBlock = NULL;
951 if (dwReservedRegionAddress != NULL && dwReservedRegionSize > 0)
953 m_reservedBlock.Init((void *)dwReservedRegionAddress, dwReservedRegionSize, FALSE);
957 // ~LoaderHeap is not synchronised (obviously)
958 UnlockedLoaderHeap::~UnlockedLoaderHeap()
968 _ASSERTE(!m_fPermitStubsWithUnwindInfo || m_fStubUnwindInfoUnregistered);
970 if (m_pRangeList != NULL)
971 m_pRangeList->RemoveRanges((void *) this);
973 LoaderHeapBlock *pSearch, *pNext;
975 for (pSearch = m_pFirstBlock; pSearch; pSearch = pNext)
977 void * pVirtualAddress;
980 pVirtualAddress = pSearch->pVirtualAddress;
981 fReleaseMemory = pSearch->m_fReleaseMemory;
982 pNext = pSearch->pNext;
987 fSuccess = ClrVirtualFree(pVirtualAddress, 0, MEM_RELEASE);
992 if (m_reservedBlock.m_fReleaseMemory)
995 fSuccess = ClrVirtualFree(m_reservedBlock.pVirtualAddress, 0, MEM_RELEASE);
999 if (m_pPrivatePerfCounter_LoaderBytes)
1000 *m_pPrivatePerfCounter_LoaderBytes = *m_pPrivatePerfCounter_LoaderBytes - (DWORD) m_dwTotalAlloc;
1002 INDEBUG(s_dwNumInstancesOfLoaderHeaps --;)
1005 void UnlockedLoaderHeap::UnlockedSetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory)
1007 WRAPPER_NO_CONTRACT;
1008 _ASSERTE(m_reservedBlock.pVirtualAddress == NULL);
1009 m_reservedBlock.Init((void *)dwReservedRegionAddress, dwReservedRegionSize, fReleaseMemory);
1012 #endif // #ifndef DACCESS_COMPILE
1015 // Disables access to all pages in the heap - useful when trying to determine if someone is
1016 // accessing something in the low frequency heap
1017 void UnlockedLoaderHeap::DebugGuardHeap()
1019 WRAPPER_NO_CONTRACT;
1020 LoaderHeapBlock *pSearch, *pNext;
1022 for (pSearch = m_pFirstBlock; pSearch; pSearch = pNext)
1025 void * pVirtualAddress;
1027 pVirtualAddress = pSearch->pVirtualAddress;
1028 pNext = pSearch->pNext;
1030 pResult = ClrVirtualAlloc(pVirtualAddress, pSearch->dwVirtualSize, MEM_COMMIT, PAGE_NOACCESS);
1031 _ASSERTE(pResult != NULL);
1036 size_t UnlockedLoaderHeap::GetBytesAvailCommittedRegion()
1038 LIMITED_METHOD_CONTRACT;
1040 if (m_pAllocPtr < m_pPtrToEndOfCommittedRegion)
1041 return (size_t)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr);
1046 size_t UnlockedLoaderHeap::GetBytesAvailReservedRegion()
1048 LIMITED_METHOD_CONTRACT;
1050 if (m_pAllocPtr < m_pEndReservedRegion)
1051 return (size_t)(m_pEndReservedRegion- m_pAllocPtr);
1056 #define SETUP_NEW_BLOCK(pData, dwSizeToCommit, dwSizeToReserve) \
1057 m_pPtrToEndOfCommittedRegion = (BYTE *) (pData) + (dwSizeToCommit); \
1058 m_pAllocPtr = (BYTE *) (pData) + sizeof(LoaderHeapBlock); \
1059 m_pEndReservedRegion = (BYTE *) (pData) + (dwSizeToReserve);
1062 #ifndef DACCESS_COMPILE
1064 BOOL UnlockedLoaderHeap::UnlockedReservePages(size_t dwSizeToCommit)
1070 INJECT_FAULT(return FALSE;);
1074 size_t dwSizeToReserve;
1076 // Add sizeof(LoaderHeapBlock)
1077 dwSizeToCommit += sizeof(LoaderHeapBlock);
1079 // Round to page size again
1080 dwSizeToCommit = ALIGN_UP(dwSizeToCommit, GetOsPageSize());
1083 BOOL fReleaseMemory = TRUE;
1085 // We were provided with a reserved memory block at instance creation time, so use it if it's big enough.
1086 if (m_reservedBlock.pVirtualAddress != NULL &&
1087 m_reservedBlock.dwVirtualSize >= dwSizeToCommit)
1089 // Get the info out of the block.
1090 pData = m_reservedBlock.pVirtualAddress;
1091 dwSizeToReserve = m_reservedBlock.dwVirtualSize;
1092 fReleaseMemory = m_reservedBlock.m_fReleaseMemory;
1094 // Zero the block so this memory doesn't get used again.
1095 m_reservedBlock.Init(NULL, 0, FALSE);
1097 // The caller is asking us to allocate the memory
1100 if (m_fExplicitControl)
1105 // Figure out how much to reserve
1106 dwSizeToReserve = max(dwSizeToCommit, m_dwReserveBlockSize);
1108 // Round to VIRTUAL_ALLOC_RESERVE_GRANULARITY
1109 dwSizeToReserve = ALIGN_UP(dwSizeToReserve, VIRTUAL_ALLOC_RESERVE_GRANULARITY);
1111 _ASSERTE(dwSizeToCommit <= dwSizeToReserve);
1117 pData = ClrVirtualAllocExecutable(dwSizeToReserve, MEM_RESERVE, PAGE_NOACCESS);
1124 // When the user passes in the reserved memory, the commit size is 0 and is adjusted to be the sizeof(LoaderHeap).
1125 // If for some reason this is not true then we just catch this via an assertion and the dev who changed code
1126 // would have to add logic here to handle the case when committed mem is more than the reserved mem. One option
1127 // could be to leak the users memory and reserve+commit a new block, Another option would be to fail the alloc mem
1128 // and notify the user to provide more reserved mem.
1129 _ASSERTE((dwSizeToCommit <= dwSizeToReserve) && "Loaderheap tried to commit more memory than reserved by user");
1133 //_ASSERTE(!"Unable to ClrVirtualAlloc reserve in a loaderheap");
1137 // Commit first set of pages, since it will contain the LoaderHeapBlock
1138 void *pTemp = ClrVirtualAlloc(pData, dwSizeToCommit, MEM_COMMIT, (m_Options & LHF_EXECUTABLE) ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
1141 //_ASSERTE(!"Unable to ClrVirtualAlloc commit in a loaderheap");
1143 // Unable to commit - release pages
1145 ClrVirtualFree(pData, 0, MEM_RELEASE);
1150 if (m_pPrivatePerfCounter_LoaderBytes)
1151 *m_pPrivatePerfCounter_LoaderBytes = *m_pPrivatePerfCounter_LoaderBytes + (DWORD) dwSizeToCommit;
1153 // Record reserved range in range list, if one is specified
1154 // Do this AFTER the commit - otherwise we'll have bogus ranges included.
1155 if (m_pRangeList != NULL)
1157 if (!m_pRangeList->AddRange((const BYTE *) pData,
1158 ((const BYTE *) pData) + dwSizeToReserve,
1163 ClrVirtualFree(pData, 0, MEM_RELEASE);
1169 m_dwTotalAlloc += dwSizeToCommit;
1171 LoaderHeapBlock *pNewBlock;
1173 pNewBlock = (LoaderHeapBlock *) pData;
1175 pNewBlock->dwVirtualSize = dwSizeToReserve;
1176 pNewBlock->pVirtualAddress = pData;
1177 pNewBlock->pNext = NULL;
1178 pNewBlock->m_fReleaseMemory = fReleaseMemory;
1180 LoaderHeapBlock *pCurBlock = m_pCurBlock;
1182 // Add to linked list
1183 while (pCurBlock != NULL &&
1184 pCurBlock->pNext != NULL)
1185 pCurBlock = pCurBlock->pNext;
1187 if (pCurBlock != NULL)
1188 m_pCurBlock->pNext = pNewBlock;
1190 m_pFirstBlock = pNewBlock;
1192 // If we want to use the memory immediately...
1193 m_pCurBlock = pNewBlock;
1195 SETUP_NEW_BLOCK(pData, dwSizeToCommit, dwSizeToReserve);
1200 // Get some more committed pages - either commit some more in the current reserved region, or, if it
1201 // has run out, reserve another set of pages.
1202 // Returns: FALSE if we can't get any more memory
1203 // TRUE: We can/did get some more memory - check to see if it's sufficient for
1204 // the caller's needs (see UnlockedAllocMem for example of use)
1205 BOOL UnlockedLoaderHeap::GetMoreCommittedPages(size_t dwMinSize)
1211 INJECT_FAULT(return FALSE;);
1215 // If we have memory we can use, what are you doing here!
1216 _ASSERTE(dwMinSize > (SIZE_T)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr));
1218 // Does this fit in the reserved region?
1219 if (dwMinSize <= (size_t)(m_pEndReservedRegion - m_pAllocPtr))
1221 SIZE_T dwSizeToCommit = (m_pAllocPtr + dwMinSize) - m_pPtrToEndOfCommittedRegion;
1223 if (dwSizeToCommit < m_dwCommitBlockSize)
1224 dwSizeToCommit = min((SIZE_T)(m_pEndReservedRegion - m_pPtrToEndOfCommittedRegion), (SIZE_T)m_dwCommitBlockSize);
1226 // Round to page size
1227 dwSizeToCommit = ALIGN_UP(dwSizeToCommit, GetOsPageSize());
1229 // Yes, so commit the desired number of reserved pages
1230 void *pData = ClrVirtualAlloc(m_pPtrToEndOfCommittedRegion, dwSizeToCommit, MEM_COMMIT, (m_Options & LHF_EXECUTABLE) ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
1234 if (m_pPrivatePerfCounter_LoaderBytes)
1235 *m_pPrivatePerfCounter_LoaderBytes = *m_pPrivatePerfCounter_LoaderBytes + (DWORD) dwSizeToCommit;
1237 m_dwTotalAlloc += dwSizeToCommit;
1239 m_pPtrToEndOfCommittedRegion += dwSizeToCommit;
1243 // Need to allocate a new set of reserved pages
1244 INDEBUG(m_dwDebugWastedBytes += (size_t)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr);)
1246 // Note, there are unused reserved pages at end of current region -can't do much about that
1247 // Provide dwMinSize here since UnlockedReservePages will round up the commit size again
1248 // after adding in the size of the LoaderHeapBlock header.
1249 return UnlockedReservePages(dwMinSize);
1252 void *UnlockedLoaderHeap::UnlockedAllocMem(size_t dwSize
1253 COMMA_INDEBUG(__in const char *szFile)
1254 COMMA_INDEBUG(int lineNum))
1261 INJECT_FAULT(ThrowOutOfMemory(););
1262 POSTCONDITION(CheckPointer(RETVAL));
1266 void *pResult = UnlockedAllocMem_NoThrow(
1267 dwSize COMMA_INDEBUG(szFile) COMMA_INDEBUG(lineNum));
1269 if (pResult == NULL)
1276 static DWORD ShouldInjectFault()
1278 static DWORD fInjectFault = 99;
1280 if (fInjectFault == 99)
1281 fInjectFault = (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_InjectFault) != 0);
1282 return fInjectFault;
1285 #define SHOULD_INJECT_FAULT(return_statement) \
1287 if (ShouldInjectFault() & 0x1) \
1289 char *a = new (nothrow) char; \
1300 #define SHOULD_INJECT_FAULT(return_statement) do { (void)((void *)0); } while (FALSE)
1304 void *UnlockedLoaderHeap::UnlockedAllocMem_NoThrow(size_t dwSize
1305 COMMA_INDEBUG(__in const char *szFile)
1306 COMMA_INDEBUG(int lineNum))
1313 INJECT_FAULT(CONTRACT_RETURN NULL;);
1314 PRECONDITION(dwSize != 0);
1315 POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
1319 SHOULD_INJECT_FAULT(RETURN NULL);
1321 INDEBUG(size_t dwRequestedSize = dwSize;)
1323 INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN()));
1325 #ifdef RANDOMIZE_ALLOC
1326 if (!m_fExplicitControl)
1327 dwSize += s_random.Next() % 256;
1330 dwSize = AllocMem_TotalSize(dwSize, this);
1335 // Any memory available on the free list?
1336 void *pData = LoaderHeapFreeBlock::AllocFromFreeList(&m_pFirstFreeBlock, dwSize, TRUE /*fRemoveFromFreeList*/, this);
1339 // Enough bytes available in committed region?
1340 if (dwSize <= GetBytesAvailCommittedRegion())
1342 pData = m_pAllocPtr;
1343 m_pAllocPtr += dwSize;
1351 BYTE *pAllocatedBytes = (BYTE *)pData;
1352 #if LOADER_HEAP_DEBUG_BOUNDARY > 0
1353 // Don't fill the memory we allocated - it is assumed to be zeroed - fill the memory after it
1354 memset(pAllocatedBytes + dwRequestedSize, 0xEE, LOADER_HEAP_DEBUG_BOUNDARY);
1356 if (dwRequestedSize > 0)
1358 _ASSERTE_MSG(pAllocatedBytes[0] == 0 && memcmp(pAllocatedBytes, pAllocatedBytes + 1, dwRequestedSize - 1) == 0,
1359 "LoaderHeap must return zero-initialized memory");
1362 if (!m_fExplicitControl)
1364 LoaderHeapValidationTag *pTag = AllocMem_GetTag(pData, dwRequestedSize);
1365 pTag->m_allocationType = kAllocMem;
1366 pTag->m_dwRequestedSize = dwRequestedSize;
1367 pTag->m_szFile = szFile;
1368 pTag->m_lineNum = lineNum;
1371 if (m_dwDebugFlags & kCallTracing)
1373 LoaderHeapSniffer::RecordEvent(this,
1387 EtwAllocRequest(this, pData, dwSize);
1392 // Need to commit some more pages in reserved region.
1393 // If we run out of pages in the reserved region, ClrVirtualAlloc some more pages
1394 if (GetMoreCommittedPages(dwSize))
1397 // We could not satisfy this allocation request
1401 void UnlockedLoaderHeap::UnlockedBackoutMem(void *pMem,
1402 size_t dwRequestedSize
1403 COMMA_INDEBUG(__in const char *szFile)
1404 COMMA_INDEBUG(int lineNum)
1405 COMMA_INDEBUG(__in const char *szAllocFile)
1406 COMMA_INDEBUG(int allocLineNum))
1416 // Because the primary use of this function is backout, we'll be nice and
1417 // define Backout(NULL) be a legal NOP.
1425 DEBUG_ONLY_REGION();
1427 LoaderHeapValidationTag *pTag = AllocMem_GetTag(pMem, dwRequestedSize);
1429 if (pTag->m_dwRequestedSize != dwRequestedSize || pTag->m_allocationType != kAllocMem)
1431 CONTRACT_VIOLATION(ThrowsViolation|FaultViolation); // We're reporting a heap corruption - who cares about violations
1433 StackSString message;
1434 message.Printf("HEAP VIOLATION: Invalid BackoutMem() call made at:\n"
1439 "Attempting to free block originally allocated at:\n"
1444 "The arguments to BackoutMem() were:\n"
1447 " Size: %lu (0x%lx)\n"
1454 ,(ULONG)dwRequestedSize
1455 ,(ULONG)dwRequestedSize
1459 if (m_dwDebugFlags & kCallTracing)
1461 message.AppendASCII("*** CALLTRACING ENABLED ***\n");
1462 LoaderHeapEvent *pEvent = LoaderHeapSniffer::FindEvent(this, pMem);
1465 message.AppendASCII("This pointer doesn't appear to have come from this LoaderHeap.\n");
1469 message.AppendASCII(pMem == pEvent->m_pMem ? "We have the following data about this pointer:" : "This pointer points to the middle of the following block:");
1470 pEvent->Describe(&message);
1474 if (pTag->m_dwRequestedSize != dwRequestedSize)
1478 "Possible causes:\n"
1480 " - This pointer wasn't allocated from this loaderheap.\n"
1481 " - This pointer was allocated by AllocAlignedMem and you didn't adjust for the \"extra.\"\n"
1482 " - This pointer has already been freed.\n"
1483 " - You passed in the wrong size. You must pass the exact same size you passed to AllocMem().\n"
1484 " - Someone wrote past the end of this block making it appear as if one of the above were true.\n"
1486 message.Append(buf);
1491 message.AppendASCII("This memory block is completely unrecognizable.\n");
1495 if (!(m_dwDebugFlags & kCallTracing))
1497 LoaderHeapSniffer::PitchSniffer(&message);
1500 StackScratchBuffer scratch;
1501 DbgAssertDialog(szFile, lineNum, (char*) message.GetANSI(scratch));
1507 size_t dwSize = AllocMem_TotalSize(dwRequestedSize, this);
1510 if (m_dwDebugFlags & kCallTracing)
1512 DEBUG_ONLY_REGION();
1514 LoaderHeapValidationTag *pTag = m_fExplicitControl ? NULL : AllocMem_GetTag(pMem, dwRequestedSize);
1517 LoaderHeapSniffer::RecordEvent(this,
1521 (pTag && (allocLineNum < 0)) ? pTag->m_szFile : szAllocFile,
1522 (pTag && (allocLineNum < 0)) ? pTag->m_lineNum : allocLineNum,
1530 if (m_pAllocPtr == ( ((BYTE*)pMem) + dwSize ))
1532 // Cool. This was the last block allocated. We can just undo the allocation instead
1533 // of going to the freelist.
1534 memset(pMem, 0x00, dwSize); // Fill freed region with 0
1535 m_pAllocPtr = (BYTE*)pMem;
1539 LoaderHeapFreeBlock::InsertFreeBlock(&m_pFirstFreeBlock, pMem, dwSize, this);
1545 // Allocates memory aligned on power-of-2 boundary.
1547 // The return value is a pointer that's guaranteed to be aligned.
1549 // FREEING THIS BLOCK: Underneath, the actual block allocated may
1550 // be larger and start at an address prior to the one you got back.
1551 // It is this adjusted size and pointer that you pass to BackoutMem.
1552 // The required adjustment is passed back thru the pdwExtra pointer.
1554 // Here is how to properly backout the memory:
1557 // void *pMem = UnlockedAllocAlignedMem(dwRequestedSize, alignment, &dwExtra);
1558 // _ASSERTE( 0 == (pMem & (alignment - 1)) );
1559 // UnlockedBackoutMem( ((BYTE*)pMem) - dExtra, dwRequestedSize + dwExtra );
1561 // If you use the AllocMemHolder or AllocMemTracker, all this is taken care of
1562 // behind the scenes.
1565 void *UnlockedLoaderHeap::UnlockedAllocAlignedMem_NoThrow(size_t dwRequestedSize,
1568 COMMA_INDEBUG(__in const char *szFile)
1569 COMMA_INDEBUG(int lineNum))
1575 // Macro syntax can't handle this INJECT_FAULT expression - we'll use a precondition instead
1576 //INJECT_FAULT( do{ if (*pdwExtra) {*pdwExtra = 0} RETURN NULL; } while(0) );
1578 PRECONDITION( alignment != 0 );
1579 PRECONDITION(0 == (alignment & (alignment - 1))); // require power of 2
1580 POSTCONDITION( (RETVAL) ?
1581 (0 == ( ((UINT_PTR)(RETVAL)) & (alignment - 1))) : // If non-null, pointer must be aligned
1582 (pdwExtra == NULL || 0 == *pdwExtra) // or else *pdwExtra must be set to 0
1587 STATIC_CONTRACT_FAULT;
1589 // Set default value
1595 SHOULD_INJECT_FAULT(RETURN NULL);
1599 INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN()));
1601 // Check for overflow if we align the allocation
1602 if (dwRequestedSize + alignment < dwRequestedSize)
1607 // We don't know how much "extra" we need to satisfy the alignment until we know
1608 // which address will be handed out which in turn we don't know because we don't
1609 // know whether the allocation will fit within the current reserved range.
1611 // Thus, we'll request as much heap growth as is needed for the worst case (extra == alignment)
1612 size_t dwRoomSize = AllocMem_TotalSize(dwRequestedSize + alignment, this);
1613 if (dwRoomSize > GetBytesAvailCommittedRegion())
1615 if (!GetMoreCommittedPages(dwRoomSize))
1621 pResult = m_pAllocPtr;
1623 size_t extra = alignment - ((size_t)pResult & ((size_t)alignment - 1));
1625 // On DEBUG, we force a non-zero extra so people don't forget to adjust for it on backout
1627 if (extra == alignment)
1633 S_SIZE_T cbAllocSize = S_SIZE_T( dwRequestedSize ) + S_SIZE_T( extra );
1634 if( cbAllocSize.IsOverflow() )
1639 size_t dwSize = AllocMem_TotalSize( cbAllocSize.Value(), this);
1640 m_pAllocPtr += dwSize;
1643 ((BYTE*&)pResult) += extra;
1646 BYTE *pAllocatedBytes = (BYTE *)pResult;
1647 #if LOADER_HEAP_DEBUG_BOUNDARY > 0
1648 // Don't fill the entire memory - we assume it is all zeroed -just the memory after our alloc
1649 memset(pAllocatedBytes + dwRequestedSize, 0xee, LOADER_HEAP_DEBUG_BOUNDARY);
1652 if (dwRequestedSize != 0)
1654 _ASSERTE_MSG(pAllocatedBytes[0] == 0 && memcmp(pAllocatedBytes, pAllocatedBytes + 1, dwRequestedSize - 1) == 0,
1655 "LoaderHeap must return zero-initialized memory");
1658 if (m_dwDebugFlags & kCallTracing)
1660 LoaderHeapSniffer::RecordEvent(this,
1666 ((BYTE*)pResult) - extra,
1667 dwRequestedSize + extra,
1672 EtwAllocRequest(this, pResult, dwSize);
1674 if (!m_fExplicitControl)
1676 LoaderHeapValidationTag *pTag = AllocMem_GetTag(((BYTE*)pResult) - extra, dwRequestedSize + extra);
1677 pTag->m_allocationType = kAllocMem;
1678 pTag->m_dwRequestedSize = dwRequestedSize + extra;
1679 pTag->m_szFile = szFile;
1680 pTag->m_lineNum = lineNum;
1695 void *UnlockedLoaderHeap::UnlockedAllocAlignedMem(size_t dwRequestedSize,
1698 COMMA_INDEBUG(__in const char *szFile)
1699 COMMA_INDEBUG(int lineNum))
1704 INJECT_FAULT(ThrowOutOfMemory());
1708 void *pResult = UnlockedAllocAlignedMem_NoThrow(dwRequestedSize,
1711 COMMA_INDEBUG(szFile)
1712 COMMA_INDEBUG(lineNum));
1726 void *UnlockedLoaderHeap::UnlockedAllocMemForCode_NoThrow(size_t dwHeaderSize, size_t dwCodeSize, DWORD dwCodeAlignment, size_t dwReserveForJumpStubs)
1732 INJECT_FAULT(CONTRACT_RETURN NULL;);
1733 PRECONDITION(0 == (dwCodeAlignment & (dwCodeAlignment - 1))); // require power of 2
1734 POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
1738 _ASSERTE(m_fExplicitControl);
1740 INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN()));
1742 // We don't know how much "extra" we need to satisfy the alignment until we know
1743 // which address will be handed out which in turn we don't know because we don't
1744 // know whether the allocation will fit within the current reserved range.
1746 // Thus, we'll request as much heap growth as is needed for the worst case (we request an extra dwCodeAlignment - 1 bytes)
1748 S_SIZE_T cbAllocSize = S_SIZE_T(dwHeaderSize) + S_SIZE_T(dwCodeSize) + S_SIZE_T(dwCodeAlignment - 1) + S_SIZE_T(dwReserveForJumpStubs);
1749 if( cbAllocSize.IsOverflow() )
1754 if (cbAllocSize.Value() > GetBytesAvailCommittedRegion())
1756 if (GetMoreCommittedPages(cbAllocSize.Value()) == FALSE)
1762 BYTE *pResult = (BYTE *)ALIGN_UP(m_pAllocPtr + dwHeaderSize, dwCodeAlignment);
1763 EtwAllocRequest(this, pResult, (pResult + dwCodeSize) - m_pAllocPtr);
1764 m_pAllocPtr = pResult + dwCodeSize;
1770 #endif // #ifndef DACCESS_COMPILE
1772 BOOL UnlockedLoaderHeap::IsExecutable()
1774 return (m_Options & LHF_EXECUTABLE);
1777 #ifdef DACCESS_COMPILE
1779 void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
1781 WRAPPER_NO_CONTRACT;
1785 PTR_LoaderHeapBlock block = m_pFirstBlock;
1786 while (block.IsValid())
1788 // All we know is the virtual size of this block. We don't have any way to tell how
1789 // much of this space was actually comitted, so don't expect that this will always
1791 // @dbgtodo : Ideally we'd reduce the risk of corruption causing problems here.
1792 // We could extend LoaderHeapBlock to track a commit size,
1793 // but it seems wasteful (eg. makes each AppDomain objects 32 bytes larger on x64).
1794 TADDR addr = dac_cast<TADDR>(block->pVirtualAddress);
1795 TSIZE_T size = block->dwVirtualSize;
1796 DacEnumMemoryRegion(addr, size, false);
1798 block = block->pNext;
1802 #endif // #ifdef DACCESS_COMPILE
1805 void UnlockedLoaderHeap::EnumPageRegions (EnumPageRegionsCallback *pCallback, PTR_VOID pvArgs)
1807 WRAPPER_NO_CONTRACT;
1809 PTR_LoaderHeapBlock block = m_pFirstBlock;
1812 if ((*pCallback)(pvArgs, block->pVirtualAddress, block->dwVirtualSize))
1817 block = block->pNext;
1824 void UnlockedLoaderHeap::DumpFreeList()
1826 LIMITED_METHOD_CONTRACT;
1827 if (m_pFirstFreeBlock == NULL)
1829 printf("FREEDUMP: FreeList is empty\n");
1833 LoaderHeapFreeBlock *pBlock = m_pFirstFreeBlock;
1834 while (pBlock != NULL)
1836 size_t dwsize = pBlock->m_dwSize;
1838 BOOL sizeunaligned = FALSE;
1839 BOOL sizesmall = FALSE;
1841 if ( 0 != (dwsize & ALLOC_ALIGN_CONSTANT) )
1843 sizeunaligned = TRUE;
1845 if ( dwsize < sizeof(LoaderHeapBlock))
1850 for (size_t i = sizeof(LoaderHeapFreeBlock); i < dwsize; i++)
1852 if ( ((BYTE*)pBlock)[i] != 0xcc )
1859 printf("Addr = %pxh, Size = %lxh", pBlock, ((ULONG)dwsize));
1860 if (ccbad) printf(" *** ERROR: NOT CC'd ***");
1861 if (sizeunaligned) printf(" *** ERROR: size not a multiple of ALLOC_ALIGN_CONSTANT ***");
1862 if (sizesmall) printf(" *** ERROR: size smaller than sizeof(LoaderHeapFreeBlock) ***");
1865 pBlock = pBlock->m_pNext;
1871 void UnlockedLoaderHeap::UnlockedClearEvents()
1873 WRAPPER_NO_CONTRACT;
1874 LoaderHeapSniffer::ClearEvents(this);
1877 void UnlockedLoaderHeap::UnlockedCompactEvents()
1879 WRAPPER_NO_CONTRACT;
1880 LoaderHeapSniffer::CompactEvents(this);
1883 void UnlockedLoaderHeap::UnlockedPrintEvents()
1885 WRAPPER_NO_CONTRACT;
1886 LoaderHeapSniffer::PrintEvents(this);
1892 //************************************************************************************
1893 // LOADERHEAP SNIFFER METHODS
1894 //************************************************************************************
1897 /*static*/ VOID LoaderHeapSniffer::RecordEvent(UnlockedLoaderHeap *pHeap,
1898 AllocationType allocationType,
1899 __in const char *szFile,
1901 __in const char *szAllocFile,
1904 size_t dwRequestedSize,
1912 FORBID_FAULT; //If we OOM in here, we just throw the event away.
1916 LoaderHeapEvent *pNewEvent;
1920 pNewEvent = new (nothrow) LoaderHeapEvent;
1924 if (!(pHeap->m_dwDebugFlags & pHeap->kEncounteredOOM))
1926 pHeap->m_dwDebugFlags |= pHeap->kEncounteredOOM;
1927 _ASSERTE(!"LOADERHEAPSNIFFER: Failed allocation of LoaderHeapEvent. Call tracing information will be incomplete.");
1932 pNewEvent->m_allocationType = allocationType;
1933 pNewEvent->m_szFile = szFile;
1934 pNewEvent->m_lineNum = lineNum;
1935 pNewEvent->m_szAllocFile = szAllocFile;
1936 pNewEvent->m_allocLineNum = allocLineNum;
1937 pNewEvent->m_pMem = pMem;
1938 pNewEvent->m_dwRequestedSize = dwRequestedSize;
1939 pNewEvent->m_dwSize = dwSize;
1941 pNewEvent->m_pNext = pHeap->m_pEventList;
1942 pHeap->m_pEventList = pNewEvent;
1950 void LoaderHeapSniffer::ValidateFreeList(UnlockedLoaderHeap *pHeap)
1952 CANNOT_HAVE_CONTRACT;
1954 // No contract. This routine is only called if we've AV'd inside the
1955 // loaderheap. The system is already toast. We're trying to be a hero
1956 // and produce the best diagnostic info we can. Last thing we need here
1957 // is a secondary assert inside the contract stuff.
1959 // This contract violation is permanent.
1960 CONTRACT_VIOLATION(ThrowsViolation|FaultViolation|GCViolation|ModeViolation); // This violation won't be removed
1962 LoaderHeapFreeBlock *pFree = pHeap->m_pFirstFreeBlock;
1963 LoaderHeapFreeBlock *pPrev = NULL;
1966 void *pBadAddr = NULL;
1967 LoaderHeapFreeBlock *pProbeThis = NULL;
1968 const char *pExpected = NULL;
1970 while (pFree != NULL)
1972 if ( 0 != ( ((ULONG_PTR)pFree) & ALLOC_ALIGN_CONSTANT ))
1974 // Not aligned - can't be a valid freeblock. Most likely we followed a bad pointer from the previous block.
1976 pBadAddr = pPrev ? &(pPrev->m_pNext) : &(pHeap->m_pFirstFreeBlock);
1977 pExpected = "a pointer to a valid LoaderHeapFreeBlock";
1981 size_t dwSize = pFree->m_dwSize;
1982 if (dwSize < AllocMem_TotalSize(1, pHeap) ||
1983 0 != (dwSize & ALLOC_ALIGN_CONSTANT))
1985 // Size is not a valid value (out of range or unaligned.)
1987 pBadAddr = &(pFree->m_dwSize);
1988 pExpected = "a valid block size (multiple of pointer size)";
1993 for (i = sizeof(LoaderHeapFreeBlock); i < dwSize; i++)
1995 if ( ((BYTE*)pFree)[i] != 0xcc )
1998 pBadAddr = i + ((BYTE*)pFree);
1999 pExpected = "0xcc (our fill value for free blocks)";
2011 pFree = pFree->m_pNext;
2016 return; // No problems found
2020 StackSString message;
2022 message.Printf("A loaderheap freelist has been corrupted. The bytes at or near address 0x%p appears to have been overwritten. We expected to see %s here.\n"
2024 " LoaderHeap: 0x%p\n"
2025 " Suspect address at: 0x%p\n"
2026 " Start of suspect freeblock: 0x%p\n"
2035 if (!(pHeap->m_dwDebugFlags & pHeap->kCallTracing))
2037 message.AppendASCII("\nThe usual reason is that someone wrote past the end of a block or wrote into a block after freeing it."
2038 "\nOf course, the culprit is long gone so it's probably too late to debug this now. Try turning on call-tracing"
2039 "\nand reproing. We can attempt to find out who last owned the surrounding pieces of memory."
2041 "\nTo turn on call-tracing, set the following registry DWORD value:"
2043 "\n HKLM\\Software\\Microsoft\\.NETFramework\\LoaderHeapCallTracing = 1"
2050 LoaderHeapEvent *pBadAddrEvent = FindEvent(pHeap, pBadAddr);
2052 message.AppendASCII("*** CALL TRACING ENABLED ***\n\n");
2056 message.AppendASCII("\nThe last known owner of the corrupted address was:\n");
2057 pBadAddrEvent->Describe(&message);
2061 message.AppendASCII("\nNo known owner of last corrupted address.\n");
2064 LoaderHeapEvent *pPrevEvent = FindEvent(pHeap, ((BYTE*)pProbeThis) - 1);
2068 pPrevEvent != NULL &&
2069 ( ((UINT_PTR)pProbeThis) - ((UINT_PTR)(pPrevEvent->m_pMem)) + pPrevEvent->m_dwSize ) < 1024)
2071 message.AppendASCII("\nThis block is located close to the corruption point. ");
2072 if (pPrevEvent->QuietValidate())
2074 message.AppendASCII("If it was overrun, it might have caused this.");
2078 message.AppendASCII("*** CORRUPTION DETECTED IN THIS BLOCK ***");
2080 pPrevEvent->Describe(&message);
2081 pPrevEvent = FindEvent(pHeap, ((BYTE*)(pPrevEvent->m_pMem)) - 1);
2087 StackScratchBuffer scratch;
2088 DbgAssertDialog(__FILE__, __LINE__, (char*) message.GetANSI(scratch));
2097 BOOL LoaderHeapEvent::QuietValidate()
2099 WRAPPER_NO_CONTRACT;
2101 if (m_allocationType == kAllocMem)
2103 LoaderHeapValidationTag *pTag = AllocMem_GetTag(m_pMem, m_dwRequestedSize);
2104 return (pTag->m_allocationType == m_allocationType && pTag->m_dwRequestedSize == m_dwRequestedSize);
2108 // We can't easily validate freed blocks.
2116 #ifndef DACCESS_COMPILE
2118 AllocMemTracker::AllocMemTracker()
2128 m_FirstBlock.m_pNext = NULL;
2129 m_FirstBlock.m_nextFree = 0;
2130 m_pFirstBlock = &m_FirstBlock;
2132 m_fReleased = FALSE;
2135 AllocMemTracker::~AllocMemTracker()
2146 AllocMemTrackerBlock *pBlock = m_pFirstBlock;
2149 // Do the loop in reverse - loaderheaps work best if
2150 // we allocate and backout in LIFO order.
2151 for (int i = pBlock->m_nextFree - 1; i >= 0; i--)
2153 AllocMemTrackerNode *pNode = &(pBlock->m_Node[i]);
2154 pNode->m_pHeap->RealBackoutMem(pNode->m_pMem
2155 ,pNode->m_dwRequestedSize
2159 ,pNode->m_szAllocFile
2160 ,pNode->m_allocLineNum
2166 pBlock = pBlock->m_pNext;
2171 AllocMemTrackerBlock *pBlock = m_pFirstBlock;
2172 while (pBlock != &m_FirstBlock)
2174 AllocMemTrackerBlock *pNext = pBlock->m_pNext;
2179 INDEBUG(memset(this, 0xcc, sizeof(*this));)
2182 void *AllocMemTracker::Track(TaggedMemAllocPtr tmap)
2187 INJECT_FAULT(ThrowOutOfMemory(););
2193 void *pv = Track_NoThrow(tmap);
2201 void *AllocMemTracker::Track_NoThrow(TaggedMemAllocPtr tmap)
2206 INJECT_FAULT(return NULL;);
2212 // Calling Track() after calling SuppressRelease() is almost certainly a bug. You're supposed to call SuppressRelease() only after you're
2213 // sure no subsequent failure will force you to backout the memory.
2214 _ASSERTE( (!m_fReleased) && "You've already called SuppressRelease on this AllocMemTracker which implies you've passed your point of no failure. Why are you still doing allocations?");
2217 if (tmap.m_pMem != NULL)
2219 AllocMemHolder<void*> holder(tmap); // If anything goes wrong in here, this holder will backout the allocation for the caller.
2222 holder.SuppressRelease();
2224 AllocMemTrackerBlock *pBlock = m_pFirstBlock;
2225 if (pBlock->m_nextFree == kAllocMemTrackerBlockSize)
2227 AllocMemTrackerBlock *pNewBlock = new (nothrow) AllocMemTrackerBlock;
2233 pNewBlock->m_pNext = m_pFirstBlock;
2234 pNewBlock->m_nextFree = 0;
2236 m_pFirstBlock = pNewBlock;
2241 // From here on, we can't fail
2242 pBlock->m_Node[pBlock->m_nextFree].m_pHeap = tmap.m_pHeap;
2243 pBlock->m_Node[pBlock->m_nextFree].m_pMem = tmap.m_pMem;
2244 pBlock->m_Node[pBlock->m_nextFree].m_dwRequestedSize = tmap.m_dwRequestedSize;
2246 pBlock->m_Node[pBlock->m_nextFree].m_szAllocFile = tmap.m_szFile;
2247 pBlock->m_Node[pBlock->m_nextFree].m_allocLineNum = tmap.m_lineNum;
2250 pBlock->m_nextFree++;
2252 holder.SuppressRelease();
2256 return (void *)tmap;
2263 void AllocMemTracker::SuppressRelease()
2265 LIMITED_METHOD_CONTRACT;
2272 #endif //#ifndef DACCESS_COMPILE