mirror of
https://github.com/ZuneDev/dotnet-runtime.git
synced 2026-07-27 13:22:01 -07:00
- Remove `GetDomain` functions on `MethodDesc`, `MethodTable`, `TypeDesc`, and `TypeHandle`. They always return the single current app domain. - Remove `GetDomain` functions and variables on `Assembly`, `DomainAssembly`, and `Module`. The actual domain they are constructed with is always the single current app domain.
5281 lines
157 KiB
C++
5281 lines
157 KiB
C++
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
#include "common.h"
|
|
|
|
#include "appdomain.hpp"
|
|
#include "peimagelayout.inl"
|
|
#include "field.h"
|
|
#include "strongnameinternal.h"
|
|
#include "excep.h"
|
|
#include "eeconfig.h"
|
|
#include "gcheaputilities.h"
|
|
#include "eventtrace.h"
|
|
#include "eeprofinterfaces.h"
|
|
#include "dbginterface.h"
|
|
#ifndef DACCESS_COMPILE
|
|
#include "eedbginterfaceimpl.h"
|
|
#endif
|
|
#include "comdynamic.h"
|
|
#include "mlinfo.h"
|
|
#include "posterror.h"
|
|
#include "assemblynative.hpp"
|
|
#include "shimload.h"
|
|
#include "stringliteralmap.h"
|
|
#include "frozenobjectheap.h"
|
|
#include "codeman.h"
|
|
#include "comcallablewrapper.h"
|
|
#include "eventtrace.h"
|
|
#include "comdelegate.h"
|
|
#include "siginfo.hpp"
|
|
#include "typekey.h"
|
|
#include "castcache.h"
|
|
|
|
#include "caparser.h"
|
|
#include "ecall.h"
|
|
#include "finalizerthread.h"
|
|
#include "threadsuspend.h"
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
#include "comtoclrcall.h"
|
|
#include "runtimecallablewrapper.h"
|
|
#include "mngstdinterfaces.h"
|
|
#include "olevariant.h"
|
|
#include "olecontexthelpers.h"
|
|
#endif // FEATURE_COMINTEROP
|
|
|
|
#if defined(FEATURE_COMWRAPPERS)
|
|
#include "rcwrefcache.h"
|
|
#endif // FEATURE_COMWRAPPERS
|
|
|
|
#include "typeequivalencehash.hpp"
|
|
|
|
#include "appdomain.inl"
|
|
|
|
#ifndef TARGET_UNIX
|
|
#include "dwreport.h"
|
|
#endif // !TARGET_UNIX
|
|
|
|
#include "stringarraylist.h"
|
|
|
|
#include "../binder/inc/bindertracing.h"
|
|
#include "../binder/inc/defaultassemblybinder.h"
|
|
#include "../binder/inc/assemblybindercommon.hpp"
|
|
|
|
// this file handles string conversion errors for itself
|
|
#undef MAKE_TRANSLATIONFAILED
|
|
|
|
// Define these macro's to do strict validation for jit lock and class
|
|
// init entry leaks. This defines determine if the asserts that
|
|
// verify for these leaks are defined or not. These asserts can
|
|
// sometimes go off even if no entries have been leaked so this
|
|
// defines should be used with caution.
|
|
//
|
|
// If we are inside a .cctor when the application shut's down then the
|
|
// class init lock's head will be set and this will cause the assert
|
|
// to go off.
|
|
//
|
|
// If we are jitting a method when the application shut's down then
|
|
// the jit lock's head will be set causing the assert to go off.
|
|
|
|
//#define STRICT_CLSINITLOCK_ENTRY_LEAK_DETECTION
|
|
|
|
static const WCHAR DEFAULT_DOMAIN_FRIENDLY_NAME[] = W("DefaultDomain");
|
|
|
|
#define STATIC_OBJECT_TABLE_BUCKET_SIZE 1020
|
|
|
|
// Statics
|
|
|
|
SPTR_IMPL(AppDomain, AppDomain, m_pTheAppDomain);
|
|
SPTR_IMPL(SystemDomain, SystemDomain, m_pSystemDomain);
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
// Base Domain Statics
|
|
CrstStatic BaseDomain::m_MethodTableExposedClassObjectCrst;
|
|
|
|
int BaseDomain::m_iNumberOfProcessors = 0;
|
|
|
|
// System Domain Statics
|
|
GlobalStringLiteralMap* SystemDomain::m_pGlobalStringLiteralMap = NULL;
|
|
FrozenObjectHeapManager* SystemDomain::m_FrozenObjectHeapManager = NULL;
|
|
|
|
DECLSPEC_ALIGN(16)
|
|
static BYTE g_pSystemDomainMemory[sizeof(SystemDomain)];
|
|
|
|
CrstStatic SystemDomain::m_SystemDomainCrst;
|
|
CrstStatic SystemDomain::m_DelayedUnloadCrst;
|
|
|
|
// Constructor for the PinnedHeapHandleBucket class.
|
|
PinnedHeapHandleBucket::PinnedHeapHandleBucket(PinnedHeapHandleBucket *pNext, PTRARRAYREF pinnedHandleArrayObj, DWORD size, BaseDomain *pDomain)
|
|
: m_pNext(pNext)
|
|
, m_ArraySize(size)
|
|
, m_CurrentPos(0)
|
|
, m_CurrentEmbeddedFreePos(0) // hint for where to start a search for an embedded free item
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pDomain));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Retrieve the pointer to the data inside the array. This is legal since the array
|
|
// is located in the pinned object heap and is guaranteed not to move.
|
|
m_pArrayDataPtr = (OBJECTREF *)pinnedHandleArrayObj->GetDataPtr();
|
|
|
|
// Store the array in a strong handle to keep it alive.
|
|
m_hndHandleArray = pDomain->CreateStrongHandle((OBJECTREF)pinnedHandleArrayObj);
|
|
}
|
|
|
|
|
|
// Destructor for the PinnedHeapHandleBucket class.
|
|
PinnedHeapHandleBucket::~PinnedHeapHandleBucket()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (m_hndHandleArray)
|
|
{
|
|
DestroyStrongHandle(m_hndHandleArray);
|
|
m_hndHandleArray = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Allocate handles from the bucket.
|
|
OBJECTREF *PinnedHeapHandleBucket::AllocateHandles(DWORD nRequested)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
_ASSERTE(nRequested > 0 && nRequested <= GetNumRemainingHandles());
|
|
_ASSERTE(m_pArrayDataPtr == (OBJECTREF*)((PTRARRAYREF)ObjectFromHandle(m_hndHandleArray))->GetDataPtr());
|
|
|
|
// Store the handles in the buffer that was passed in
|
|
OBJECTREF* ret = &m_pArrayDataPtr[m_CurrentPos];
|
|
m_CurrentPos += nRequested;
|
|
|
|
return ret;
|
|
}
|
|
|
|
// look for a free item embedded in the table
|
|
OBJECTREF *PinnedHeapHandleBucket::TryAllocateEmbeddedFreeHandle()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
OBJECTREF pPreallocatedSentinelObject = ObjectFromHandle(g_pPreallocatedSentinelObject);
|
|
_ASSERTE(pPreallocatedSentinelObject != NULL);
|
|
|
|
for (int i = m_CurrentEmbeddedFreePos; i < m_CurrentPos; i++)
|
|
{
|
|
if (m_pArrayDataPtr[i] == pPreallocatedSentinelObject)
|
|
{
|
|
m_CurrentEmbeddedFreePos = i;
|
|
m_pArrayDataPtr[i] = NULL;
|
|
return &m_pArrayDataPtr[i];
|
|
}
|
|
}
|
|
|
|
// didn't find it (we don't bother wrapping around for a full search, it's not worth it to try that hard, we'll get it next time)
|
|
|
|
m_CurrentEmbeddedFreePos = 0;
|
|
return NULL;
|
|
}
|
|
|
|
// enumerate the handles in the bucket
|
|
void PinnedHeapHandleBucket::EnumStaticGCRefs(promote_func* fn, ScanContext* sc)
|
|
{
|
|
for (int i = 0; i < m_CurrentPos; i++)
|
|
{
|
|
fn((Object**)&m_pArrayDataPtr[i], sc, 0);
|
|
}
|
|
}
|
|
|
|
|
|
// Maximum bucket size will be 64K on 32-bit and 128K on 64-bit.
|
|
// We subtract out a small amount to leave room for the object
|
|
// header and length of the array.
|
|
|
|
#define MAX_BUCKETSIZE (16384 - 4)
|
|
|
|
// Constructor for the PinnedHeapHandleTable class.
|
|
PinnedHeapHandleTable::PinnedHeapHandleTable(BaseDomain *pDomain, DWORD InitialBucketSize)
|
|
: m_pHead(NULL)
|
|
, m_pDomain(pDomain)
|
|
, m_NextBucketSize(InitialBucketSize)
|
|
, m_pFreeSearchHint(NULL)
|
|
, m_cEmbeddedFree(0)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pDomain));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
m_Crst.Init(CrstPinnedHeapHandleTable, CRST_UNSAFE_COOPGC);
|
|
}
|
|
|
|
|
|
// Destructor for the PinnedHeapHandleTable class.
|
|
PinnedHeapHandleTable::~PinnedHeapHandleTable()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Delete the buckets.
|
|
while (m_pHead)
|
|
{
|
|
PinnedHeapHandleBucket *pOld = m_pHead;
|
|
m_pHead = pOld->GetNext();
|
|
delete pOld;
|
|
}
|
|
}
|
|
|
|
|
|
// Allocate handles from the large heap handle table.
|
|
// This function is thread-safe for concurrent invocation by multiple callers
|
|
OBJECTREF* PinnedHeapHandleTable::AllocateHandles(DWORD nRequested)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(nRequested > 0);
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
#ifdef _DEBUG
|
|
_ASSERTE(!m_Crst.OwnedByCurrentThread());
|
|
#endif
|
|
|
|
// beware: we leave and re-enter this lock below in this method
|
|
CrstHolderWithState lockHolder(&m_Crst);
|
|
|
|
if (nRequested == 1 && m_cEmbeddedFree != 0)
|
|
{
|
|
// special casing singleton requests to look for slots that can be re-used
|
|
|
|
// we need to do this because string literals are allocated one at a time and then sometimes
|
|
// released. we do not wish for the number of handles consumed by string literals to
|
|
// increase forever as assemblies are loaded and unloaded
|
|
|
|
if (m_pFreeSearchHint == NULL)
|
|
m_pFreeSearchHint = m_pHead;
|
|
|
|
while (m_pFreeSearchHint)
|
|
{
|
|
OBJECTREF* pObjRef = m_pFreeSearchHint->TryAllocateEmbeddedFreeHandle();
|
|
if (pObjRef != NULL)
|
|
{
|
|
// the slot is to have been prepared with a null ready to go
|
|
_ASSERTE(*pObjRef == NULL);
|
|
m_cEmbeddedFree--;
|
|
return pObjRef;
|
|
}
|
|
m_pFreeSearchHint = m_pFreeSearchHint->GetNext();
|
|
}
|
|
|
|
// the search doesn't wrap around so it's possible that we might have embedded free items
|
|
// and not find them but that's ok, we'll get them on the next alloc... all we're trying to do
|
|
// is to not have big leaks over time.
|
|
}
|
|
|
|
|
|
// Retrieve the remaining number of handles in the bucket.
|
|
DWORD numRemainingHandlesInBucket = (m_pHead != NULL) ? m_pHead->GetNumRemainingHandles() : 0;
|
|
PTRARRAYREF pinnedHandleArrayObj = NULL;
|
|
DWORD nextBucketSize = min(m_NextBucketSize * 2, MAX_BUCKETSIZE);
|
|
|
|
// create a new block if this request doesn't fit in the current block
|
|
if (nRequested > numRemainingHandlesInBucket)
|
|
{
|
|
// create a new bucket for this allocation
|
|
// We need a block big enough to hold the requested handles
|
|
DWORD newBucketSize = max(m_NextBucketSize, nRequested);
|
|
|
|
// Leave the lock temporarily to do the GC allocation
|
|
//
|
|
// Why do we need to do certain work outside the lock? Because if we didn't this can happen:
|
|
// 1. AllocateHandles needs the GC to allocate
|
|
// 2. Anything which invokes the GC might also get suspended by the managed debugger which
|
|
// will block the thread inside the lock
|
|
// 3. The managed debugger can run function-evaluation on any thread
|
|
// 4. Those func-evals might need to allocate handles
|
|
// 5. The func-eval can't acquire the lock to allocate handles because the thread in
|
|
// step (3) still holds the lock. The thread in step (3) won't release the lock until the
|
|
// debugger allows it to resume. The debugger won't resume until the funceval completes.
|
|
// 6. This either creates a deadlock or forces the debugger to abort the func-eval with a bad
|
|
// user experience.
|
|
//
|
|
// This is only a partial fix to the func-eval problem. Some of the callers to AllocateHandles()
|
|
// are holding their own different locks farther up the stack. To address this more completely
|
|
// we probably need to change the fundamental invariant that all GC suspend points are also valid
|
|
// debugger suspend points. Changes in that area have proven to be error-prone in the past and we
|
|
// don't yet have the appropriate testing to validate that a future attempt gets it correct.
|
|
lockHolder.Release();
|
|
{
|
|
OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED);
|
|
pinnedHandleArrayObj = (PTRARRAYREF)AllocateObjectArray(newBucketSize, g_pObjectClass, /* bAllocateInPinnedHeap = */TRUE);
|
|
}
|
|
lockHolder.Acquire();
|
|
|
|
// after leaving and re-entering the lock anything we verified or computed above using internal state could
|
|
// have changed. We need to retest if we still need the new allocation.
|
|
numRemainingHandlesInBucket = (m_pHead != NULL) ? m_pHead->GetNumRemainingHandles() : 0;
|
|
if (nRequested > numRemainingHandlesInBucket)
|
|
{
|
|
if (m_pHead != NULL)
|
|
{
|
|
// mark the handles in that remaining region as available for re-use
|
|
ReleaseHandlesLocked(m_pHead->CurrentPos(), numRemainingHandlesInBucket);
|
|
|
|
// mark what's left as having been used
|
|
m_pHead->ConsumeRemaining();
|
|
}
|
|
|
|
m_pHead = new PinnedHeapHandleBucket(m_pHead, pinnedHandleArrayObj, newBucketSize, m_pDomain);
|
|
|
|
// we already computed nextBucketSize to be double the previous size above, but it is possible that
|
|
// other threads increased m_NextBucketSize while the lock was unheld. We want to ensure
|
|
// m_NextBucketSize never shrinks even if nextBucketSize is no longer m_NextBucketSize*2.
|
|
m_NextBucketSize = max(m_NextBucketSize, nextBucketSize);
|
|
}
|
|
else
|
|
{
|
|
// we didn't need the allocation after all
|
|
// no handle has been created to root this so the GC may be able to reclaim and reuse it
|
|
pinnedHandleArrayObj = NULL;
|
|
}
|
|
}
|
|
|
|
return m_pHead->AllocateHandles(nRequested);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// Release object handles allocated using AllocateHandles().
|
|
// This function is thread-safe for concurrent invocation by multiple callers
|
|
void PinnedHeapHandleTable::ReleaseHandles(OBJECTREF *pObjRef, DWORD nReleased)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pObjRef));
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
#ifdef _DEBUG
|
|
_ASSERTE(!m_Crst.OwnedByCurrentThread());
|
|
#endif
|
|
|
|
CrstHolder ch(&m_Crst);
|
|
ReleaseHandlesLocked(pObjRef, nReleased);
|
|
}
|
|
|
|
void PinnedHeapHandleTable::ReleaseHandlesLocked(OBJECTREF *pObjRef, DWORD nReleased)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pObjRef));
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
#ifdef _DEBUG
|
|
_ASSERTE(m_Crst.OwnedByCurrentThread());
|
|
#endif
|
|
|
|
OBJECTREF pPreallocatedSentinelObject = ObjectFromHandle(g_pPreallocatedSentinelObject);
|
|
_ASSERTE(pPreallocatedSentinelObject != NULL);
|
|
|
|
|
|
// Add the released handles to the list of available handles.
|
|
for (DWORD i = 0; i < nReleased; i++)
|
|
{
|
|
SetObjectReference(&pObjRef[i], pPreallocatedSentinelObject);
|
|
}
|
|
|
|
m_cEmbeddedFree += nReleased;
|
|
}
|
|
|
|
// enumerate the handles in the handle table
|
|
void PinnedHeapHandleTable::EnumStaticGCRefs(promote_func* fn, ScanContext* sc)
|
|
{
|
|
for (PinnedHeapHandleBucket *pBucket = m_pHead; pBucket != nullptr; pBucket = pBucket->GetNext())
|
|
{
|
|
pBucket->EnumStaticGCRefs(fn, sc);
|
|
}
|
|
}
|
|
|
|
// Constructor for the ThreadStaticHandleBucket class.
|
|
ThreadStaticHandleBucket::ThreadStaticHandleBucket(ThreadStaticHandleBucket *pNext, DWORD Size, BaseDomain *pDomain)
|
|
: m_pNext(pNext)
|
|
, m_ArraySize(Size)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pDomain));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
PTRARRAYREF HandleArrayObj;
|
|
|
|
// Allocate the array on the GC heap.
|
|
OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED);
|
|
HandleArrayObj = (PTRARRAYREF)AllocateObjectArray(Size, g_pObjectClass);
|
|
|
|
// Store the array in a strong handle to keep it alive.
|
|
m_hndHandleArray = pDomain->CreateStrongHandle((OBJECTREF)HandleArrayObj);
|
|
}
|
|
|
|
// Destructor for the ThreadStaticHandleBucket class.
|
|
ThreadStaticHandleBucket::~ThreadStaticHandleBucket()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (m_hndHandleArray)
|
|
{
|
|
DestroyStrongHandle(m_hndHandleArray);
|
|
m_hndHandleArray = NULL;
|
|
}
|
|
}
|
|
|
|
// Allocate handles from the bucket.
|
|
OBJECTHANDLE ThreadStaticHandleBucket::GetHandles()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
return m_hndHandleArray;
|
|
}
|
|
|
|
// Constructor for the ThreadStaticHandleTable class.
|
|
ThreadStaticHandleTable::ThreadStaticHandleTable(BaseDomain *pDomain)
|
|
: m_pHead(NULL)
|
|
, m_pDomain(pDomain)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pDomain));
|
|
}
|
|
CONTRACTL_END;
|
|
}
|
|
|
|
// Destructor for the ThreadStaticHandleTable class.
|
|
ThreadStaticHandleTable::~ThreadStaticHandleTable()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Delete the buckets.
|
|
while (m_pHead)
|
|
{
|
|
ThreadStaticHandleBucket *pOld = m_pHead;
|
|
m_pHead = pOld->GetNext();
|
|
delete pOld;
|
|
}
|
|
}
|
|
|
|
// Allocate handles from the large heap handle table.
|
|
OBJECTHANDLE ThreadStaticHandleTable::AllocateHandles(DWORD nRequested)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(nRequested > 0);
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// create a new bucket for this allocation
|
|
m_pHead = new ThreadStaticHandleBucket(m_pHead, nRequested, m_pDomain);
|
|
|
|
return m_pHead->GetHandles();
|
|
}
|
|
|
|
|
|
|
|
//*****************************************************************************
|
|
// BaseDomain
|
|
//*****************************************************************************
|
|
void BaseDomain::Attach()
|
|
{
|
|
m_MethodTableExposedClassObjectCrst.Init(CrstMethodTableExposedObject);
|
|
}
|
|
|
|
BaseDomain::BaseDomain()
|
|
{
|
|
// initialize fields so the domain can be safely destructed
|
|
// shouldn't call anything that can fail here - use ::Init instead
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
FORBID_FAULT;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
m_pDefaultBinder = NULL;
|
|
|
|
// Make sure the container is set to NULL so that it gets loaded when it is used.
|
|
m_pPinnedHeapHandleTable = NULL;
|
|
|
|
// Note that m_handleStore is overridden by app domains
|
|
m_handleStore = GCHandleUtilities::GetGCHandleManager()->GetGlobalHandleStore();
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
m_pMngStdInterfacesInfo = NULL;
|
|
#endif
|
|
m_FileLoadLock.PreInit();
|
|
m_JITLock.PreInit();
|
|
m_ClassInitLock.PreInit();
|
|
m_ILStubGenLock.PreInit();
|
|
m_NativeTypeLoadLock.PreInit();
|
|
} //BaseDomain::BaseDomain
|
|
|
|
//*****************************************************************************
|
|
void BaseDomain::Init()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
//
|
|
// Initialize the domain locks
|
|
//
|
|
|
|
if (this == reinterpret_cast<BaseDomain*>(&g_pSystemDomainMemory[0]))
|
|
m_DomainCrst.Init(CrstSystemBaseDomain);
|
|
else
|
|
m_DomainCrst.Init(CrstBaseDomain);
|
|
|
|
m_DomainCacheCrst.Init(CrstAppDomainCache);
|
|
m_DomainLocalBlockCrst.Init(CrstDomainLocalBlock);
|
|
|
|
// NOTE: CRST_UNSAFE_COOPGC prevents a GC mode switch to preemptive when entering this crst.
|
|
// If you remove this flag, we will switch to preemptive mode when entering
|
|
// m_FileLoadLock, which means all functions that enter it will become
|
|
// GC_TRIGGERS. (This includes all uses of PEFileListLockHolder, LoadLockHolder, etc.) So be sure
|
|
// to update the contracts if you remove this flag.
|
|
m_FileLoadLock.Init(CrstAssemblyLoader,
|
|
CrstFlags(CRST_HOST_BREAKABLE), TRUE);
|
|
|
|
//
|
|
// The JIT lock and the CCtor locks are at the same level (and marked as
|
|
// UNSAFE_SAME_LEVEL) because they are all part of the same deadlock detection mechanism. We
|
|
// see through cycles of JITting and .cctor execution and then explicitly allow the cycle to
|
|
// be broken by giving access to uninitialized classes. If there is no cycle or if the cycle
|
|
// involves other locks that arent part of this special deadlock-breaking semantics, then
|
|
// we continue to block.
|
|
//
|
|
m_JITLock.Init(CrstJit, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_SAMELEVEL), TRUE);
|
|
m_ClassInitLock.Init(CrstClassInit, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_SAMELEVEL), TRUE);
|
|
|
|
m_ILStubGenLock.Init(CrstILStubGen, CrstFlags(CRST_REENTRANCY), TRUE);
|
|
m_NativeTypeLoadLock.Init(CrstInteropData, CrstFlags(CRST_REENTRANCY), TRUE);
|
|
|
|
m_crstLoaderAllocatorReferences.Init(CrstLoaderAllocatorReferences);
|
|
m_crstStaticBoxInitLock.Init(CrstStaticBoxInit);
|
|
// Has to switch thread to GC_NOTRIGGER while being held (see code:BaseDomain#AssemblyListLock)
|
|
m_crstAssemblyList.Init(CrstAssemblyList, CrstFlags(
|
|
CRST_GC_NOTRIGGER_WHEN_TAKEN | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN));
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
// Allocate the managed standard interfaces information.
|
|
m_pMngStdInterfacesInfo = new MngStdInterfacesInfo();
|
|
#endif // FEATURE_COMINTEROP
|
|
|
|
m_dwSizedRefHandles = 0;
|
|
// For server GC this value indicates the number of GC heaps used in circular order to allocate sized
|
|
// ref handles. It must not exceed the array size allocated by the handle table (see getNumberOfSlots
|
|
// in objecthandle.cpp). We might want to use GetNumberOfHeaps if it were accessible here.
|
|
m_iNumberOfProcessors = min(GetCurrentProcessCpuCount(), GetTotalProcessorCount());
|
|
}
|
|
|
|
#undef LOADERHEAP_PROFILE_COUNTER
|
|
|
|
void BaseDomain::InitVSD()
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
m_typeIDMap.Init();
|
|
|
|
GetLoaderAllocator()->InitVirtualCallStubManager(this);
|
|
}
|
|
|
|
void BaseDomain::InitThreadStaticBlockTypeMap()
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
m_NonGCThreadStaticBlockTypeIDMap.Init();
|
|
m_GCThreadStaticBlockTypeIDMap.Init();
|
|
}
|
|
|
|
void BaseDomain::ClearBinderContext()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_PREEMPTIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (m_pDefaultBinder)
|
|
{
|
|
delete m_pDefaultBinder;
|
|
m_pDefaultBinder = NULL;
|
|
}
|
|
}
|
|
|
|
void AppDomain::ShutdownFreeLoaderAllocators()
|
|
{
|
|
// If we're called from managed code (i.e. the finalizer thread) we take a lock in
|
|
// LoaderAllocator::CleanupFailedTypeInit, which may throw. Otherwise we're called
|
|
// from the app-domain shutdown path in which we can avoid taking the lock.
|
|
CONTRACTL
|
|
{
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
MODE_ANY;
|
|
CAN_TAKE_LOCK;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
CrstHolder ch(GetLoaderAllocatorReferencesLock());
|
|
|
|
// Shutdown the LoaderAllocators associated with collectible assemblies
|
|
while (m_pDelayedLoaderAllocatorUnloadList != NULL)
|
|
{
|
|
LoaderAllocator * pCurrentLoaderAllocator = m_pDelayedLoaderAllocatorUnloadList;
|
|
// Remove next loader allocator from the list
|
|
m_pDelayedLoaderAllocatorUnloadList = m_pDelayedLoaderAllocatorUnloadList->m_pLoaderAllocatorDestroyNext;
|
|
|
|
// For loader allocator finalization, we need to be careful about cleaning up per-appdomain allocations
|
|
// and synchronizing with GC using delay unload list. We need to wait for next Gen2 GC to finish to ensure
|
|
// that GC heap does not have any references to the MethodTables being unloaded.
|
|
|
|
pCurrentLoaderAllocator->CleanupFailedTypeInit();
|
|
|
|
pCurrentLoaderAllocator->CleanupHandles();
|
|
|
|
GCX_COOP();
|
|
SystemDomain::System()->AddToDelayedUnloadList(pCurrentLoaderAllocator);
|
|
}
|
|
} // AppDomain::ShutdownFreeLoaderAllocators
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//
|
|
// Register the loader allocator for deletion in code:AppDomain::ShutdownFreeLoaderAllocators.
|
|
//
|
|
void AppDomain::RegisterLoaderAllocatorForDeletion(LoaderAllocator * pLoaderAllocator)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
GC_TRIGGERS;
|
|
NOTHROW;
|
|
MODE_ANY;
|
|
CAN_TAKE_LOCK;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
CrstHolder ch(GetLoaderAllocatorReferencesLock());
|
|
|
|
pLoaderAllocator->m_pLoaderAllocatorDestroyNext = m_pDelayedLoaderAllocatorUnloadList;
|
|
m_pDelayedLoaderAllocatorUnloadList = pLoaderAllocator;
|
|
}
|
|
|
|
void AppDomain::SetNativeDllSearchDirectories(LPCWSTR wszNativeDllSearchDirectories)
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
SString sDirectories(wszNativeDllSearchDirectories);
|
|
|
|
if (sDirectories.GetCount() > 0)
|
|
{
|
|
SString::CIterator start = sDirectories.Begin();
|
|
SString::CIterator itr = sDirectories.Begin();
|
|
SString::CIterator end = sDirectories.End();
|
|
SString qualifiedPath;
|
|
|
|
while (itr != end)
|
|
{
|
|
start = itr;
|
|
BOOL found = sDirectories.Find(itr, PATH_SEPARATOR_CHAR_W);
|
|
if (!found)
|
|
{
|
|
itr = end;
|
|
}
|
|
|
|
SString qualifiedPath(sDirectories, start, itr);
|
|
|
|
if (found)
|
|
{
|
|
itr++;
|
|
}
|
|
|
|
unsigned len = qualifiedPath.GetCount();
|
|
|
|
if (len > 0)
|
|
{
|
|
if (qualifiedPath[len - 1] != DIRECTORY_SEPARATOR_CHAR_W)
|
|
{
|
|
qualifiedPath.Append(DIRECTORY_SEPARATOR_CHAR_W);
|
|
}
|
|
|
|
NewHolder<SString> stringHolder(new SString(qualifiedPath));
|
|
IfFailThrow(m_NativeDllSearchDirectories.Append(stringHolder.GetValue()));
|
|
stringHolder.SuppressRelease();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
OBJECTREF* BaseDomain::AllocateObjRefPtrsInLargeTable(int nRequested, OBJECTREF** ppLazyAllocate)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION((nRequested > 0));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (ppLazyAllocate && *ppLazyAllocate)
|
|
{
|
|
// Allocation already happened
|
|
return *ppLazyAllocate;
|
|
}
|
|
|
|
GCX_COOP();
|
|
|
|
// Make sure the large heap handle table is initialized.
|
|
if (!m_pPinnedHeapHandleTable)
|
|
InitPinnedHeapHandleTable();
|
|
|
|
// Allocate the handles.
|
|
OBJECTREF* result = m_pPinnedHeapHandleTable->AllocateHandles(nRequested);
|
|
if (ppLazyAllocate)
|
|
{
|
|
// race with other threads that might be doing the same concurrent allocation
|
|
if (InterlockedCompareExchangeT<OBJECTREF*>(ppLazyAllocate, result, NULL) != NULL)
|
|
{
|
|
// we lost the race, release our handles and use the handles from the
|
|
// winning thread
|
|
m_pPinnedHeapHandleTable->ReleaseHandles(result, nRequested);
|
|
result = *ppLazyAllocate;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif // !DACCESS_COMPILE
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
OBJECTREF AppDomain::GetMissingObject()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (!m_hndMissing)
|
|
{
|
|
// Get the field
|
|
FieldDesc *pValueFD = CoreLibBinder::GetField(FIELD__MISSING__VALUE);
|
|
|
|
pValueFD->CheckRunClassInitThrowing();
|
|
|
|
// Retrieve the value static field and store it.
|
|
OBJECTHANDLE hndMissing = CreateHandle(pValueFD->GetStaticOBJECTREF());
|
|
|
|
if (InterlockedCompareExchangeT(&m_hndMissing, hndMissing, NULL) != NULL)
|
|
{
|
|
// Exchanged failed. The m_hndMissing did not equal NULL and was returned.
|
|
DestroyHandle(hndMissing);
|
|
}
|
|
}
|
|
|
|
return ObjectFromHandle(m_hndMissing);
|
|
}
|
|
|
|
#endif // DACCESS_COMPILE
|
|
#endif // FEATURE_COMINTEROP
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
|
|
STRINGREF *BaseDomain::IsStringInterned(STRINGREF *pString)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pString));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
return GetLoaderAllocator()->IsStringInterned(pString);
|
|
}
|
|
|
|
STRINGREF *BaseDomain::GetOrInternString(STRINGREF *pString)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
MODE_COOPERATIVE;
|
|
PRECONDITION(CheckPointer(pString));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
return GetLoaderAllocator()->GetOrInternString(pString);
|
|
}
|
|
|
|
void BaseDomain::InitPinnedHeapHandleTable()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
PinnedHeapHandleTable* pTable = new PinnedHeapHandleTable(this, STATIC_OBJECT_TABLE_BUCKET_SIZE);
|
|
if(InterlockedCompareExchangeT<PinnedHeapHandleTable*>(&m_pPinnedHeapHandleTable, pTable, NULL) != NULL)
|
|
{
|
|
// another thread beat us to initializing the field, delete our copy
|
|
delete pTable;
|
|
}
|
|
}
|
|
|
|
|
|
//*****************************************************************************
|
|
//*****************************************************************************
|
|
//*****************************************************************************
|
|
|
|
void *SystemDomain::operator new(size_t size, void *pInPlace)
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
return pInPlace;
|
|
}
|
|
|
|
|
|
void SystemDomain::operator delete(void *pMem)
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
// Do nothing - new() was in-place
|
|
}
|
|
|
|
void SystemDomain::Attach()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(m_pSystemDomain == NULL);
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Initialize stub managers
|
|
PrecodeStubManager::Init();
|
|
JumpStubStubManager::Init();
|
|
RangeSectionStubManager::Init();
|
|
ILStubManager::Init();
|
|
InteropDispatchStubManager::Init();
|
|
StubLinkStubManager::Init();
|
|
ThunkHeapStubManager::Init();
|
|
TailCallStubManager::Init();
|
|
#ifdef FEATURE_TIERED_COMPILATION
|
|
CallCountingStubManager::Init();
|
|
#endif
|
|
|
|
m_SystemDomainCrst.Init(CrstSystemDomain, (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN));
|
|
m_DelayedUnloadCrst.Init(CrstSystemDomainDelayedUnloadList, CRST_UNSAFE_COOPGC);
|
|
|
|
// Initialize the ID dispenser that is used for domain neutral module IDs
|
|
g_pModuleIndexDispenser = new IdDispenser();
|
|
|
|
// Create the global SystemDomain and initialize it.
|
|
m_pSystemDomain = new (&g_pSystemDomainMemory[0]) SystemDomain();
|
|
// No way it can fail since g_pSystemDomainMemory is a static array.
|
|
CONSISTENCY_CHECK(CheckPointer(m_pSystemDomain));
|
|
|
|
LOG((LF_CLASSLOADER,
|
|
LL_INFO10,
|
|
"Created system domain at %p\n",
|
|
m_pSystemDomain));
|
|
|
|
// We need to initialize the memory pools etc. for the system domain.
|
|
m_pSystemDomain->BaseDomain::Init(); // Setup the memory heaps
|
|
|
|
// Create the one and only app domain
|
|
AppDomain::Create();
|
|
|
|
// Each domain gets its own ReJitManager, and ReJitManager has its own static
|
|
// initialization to run
|
|
ReJitManager::InitStatic();
|
|
|
|
#ifdef FEATURE_READYTORUN
|
|
InitReadyToRunStandaloneMethodMetadata();
|
|
#endif // FEATURE_READYTORUN
|
|
}
|
|
|
|
|
|
void SystemDomain::DetachBegin()
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
// Shut down the domain and its children (but don't deallocate anything just
|
|
// yet).
|
|
|
|
// TODO: we should really not running managed DLLMain during process detach.
|
|
if (GetThreadNULLOk() == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(m_pSystemDomain)
|
|
m_pSystemDomain->Stop();
|
|
}
|
|
|
|
void SystemDomain::DetachEnd()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
// Shut down the domain and its children (but don't deallocate anything just
|
|
// yet).
|
|
if(m_pSystemDomain)
|
|
{
|
|
GCX_PREEMP();
|
|
m_pSystemDomain->ClearBinderContext();
|
|
AppDomain* pAppDomain = GetAppDomain();
|
|
if (pAppDomain)
|
|
pAppDomain->ClearBinderContext();
|
|
}
|
|
}
|
|
|
|
void SystemDomain::Stop()
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
AppDomain::GetCurrentDomain()->Stop();
|
|
}
|
|
|
|
void SystemDomain::PreallocateSpecialObjects()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
_ASSERTE(g_pPreallocatedSentinelObject == NULL);
|
|
|
|
OBJECTREF pPreallocatedSentinelObject = AllocateObject(g_pObjectClass);
|
|
g_pPreallocatedSentinelObject = CreatePinningHandle( pPreallocatedSentinelObject );
|
|
}
|
|
|
|
void SystemDomain::CreatePreallocatedExceptions()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
EXCEPTIONREF pOutOfMemory = (EXCEPTIONREF)AllocateObject(g_pOutOfMemoryExceptionClass);
|
|
pOutOfMemory->SetHResult(COR_E_OUTOFMEMORY);
|
|
pOutOfMemory->SetXCode(EXCEPTION_COMPLUS);
|
|
_ASSERTE(g_pPreallocatedOutOfMemoryException == NULL);
|
|
g_pPreallocatedOutOfMemoryException = CreateHandle(pOutOfMemory);
|
|
|
|
|
|
EXCEPTIONREF pStackOverflow = (EXCEPTIONREF)AllocateObject(g_pStackOverflowExceptionClass);
|
|
pStackOverflow->SetHResult(COR_E_STACKOVERFLOW);
|
|
pStackOverflow->SetXCode(EXCEPTION_COMPLUS);
|
|
_ASSERTE(g_pPreallocatedStackOverflowException == NULL);
|
|
g_pPreallocatedStackOverflowException = CreateHandle(pStackOverflow);
|
|
|
|
|
|
EXCEPTIONREF pExecutionEngine = (EXCEPTIONREF)AllocateObject(g_pExecutionEngineExceptionClass);
|
|
pExecutionEngine->SetHResult(COR_E_EXECUTIONENGINE);
|
|
pExecutionEngine->SetXCode(EXCEPTION_COMPLUS);
|
|
_ASSERTE(g_pPreallocatedExecutionEngineException == NULL);
|
|
g_pPreallocatedExecutionEngineException = CreateHandle(pExecutionEngine);
|
|
}
|
|
|
|
void SystemDomain::Init()
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
#ifdef _DEBUG
|
|
LOG((
|
|
LF_EEMEM,
|
|
LL_INFO10,
|
|
"sizeof(EEClass) = %d\n"
|
|
"sizeof(MethodTable) = %d\n"
|
|
"sizeof(MethodDesc)= %d\n"
|
|
"sizeof(FieldDesc) = %d\n"
|
|
"sizeof(Module) = %d\n",
|
|
sizeof(EEClass),
|
|
sizeof(MethodTable),
|
|
sizeof(MethodDesc),
|
|
sizeof(FieldDesc),
|
|
sizeof(Module)
|
|
));
|
|
#endif // _DEBUG
|
|
|
|
// The base domain is initialized in SystemDomain::Attach()
|
|
// to allow stub caches to use the memory pool. Do not
|
|
// initialize it here!
|
|
|
|
m_pSystemPEAssembly = NULL;
|
|
m_pSystemAssembly = NULL;
|
|
|
|
DWORD size = 0;
|
|
|
|
// Get the install directory so we can find CoreLib
|
|
hr = GetInternalSystemDirectory(NULL, &size);
|
|
if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
|
|
ThrowHR(hr);
|
|
|
|
// GetInternalSystemDirectory returns a size, including the null!
|
|
WCHAR* buffer = m_SystemDirectory.OpenUnicodeBuffer(size - 1);
|
|
IfFailThrow(GetInternalSystemDirectory(buffer, &size));
|
|
m_SystemDirectory.CloseBuffer();
|
|
m_SystemDirectory.Normalize();
|
|
|
|
// At this point m_SystemDirectory should already be canonicalized
|
|
m_BaseLibrary.Append(m_SystemDirectory);
|
|
if (!m_BaseLibrary.EndsWith(SString{ DIRECTORY_SEPARATOR_CHAR_W }))
|
|
{
|
|
m_BaseLibrary.Append(DIRECTORY_SEPARATOR_CHAR_W);
|
|
}
|
|
m_BaseLibrary.Append(g_pwBaseLibrary);
|
|
m_BaseLibrary.Normalize();
|
|
|
|
LoadBaseSystemClasses();
|
|
|
|
{
|
|
// We are about to start allocating objects, so we must be in cooperative mode.
|
|
// However, many of the entrypoints to the system (DllGetClassObject and all
|
|
// N/Direct exports) get called multiple times. Sometimes they initialize the EE,
|
|
// but generally they remain in preemptive mode. So we really want to push/pop
|
|
// the state here:
|
|
GCX_COOP();
|
|
|
|
CreatePreallocatedExceptions();
|
|
PreallocateSpecialObjects();
|
|
|
|
// Finish loading CoreLib now.
|
|
m_pSystemAssembly->GetDomainAssembly()->EnsureActive();
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
BOOL fPause = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PauseOnLoad);
|
|
|
|
while (fPause)
|
|
{
|
|
ClrSleepEx(20, TRUE);
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
|
|
void SystemDomain::LazyInitGlobalStringLiteralMap()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Allocate the global string literal map.
|
|
NewHolder<GlobalStringLiteralMap> pGlobalStringLiteralMap(new GlobalStringLiteralMap());
|
|
|
|
// Initialize the global string literal map.
|
|
pGlobalStringLiteralMap->Init();
|
|
|
|
if (InterlockedCompareExchangeT<GlobalStringLiteralMap *>(&m_pGlobalStringLiteralMap, pGlobalStringLiteralMap, NULL) == NULL)
|
|
{
|
|
pGlobalStringLiteralMap.SuppressRelease();
|
|
}
|
|
}
|
|
|
|
void SystemDomain::LazyInitFrozenObjectsHeap()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
NewHolder<FrozenObjectHeapManager> pFoh(new FrozenObjectHeapManager());
|
|
if (InterlockedCompareExchangeT<FrozenObjectHeapManager*>(&m_FrozenObjectHeapManager, pFoh, nullptr) == nullptr)
|
|
{
|
|
pFoh.SuppressRelease();
|
|
}
|
|
}
|
|
|
|
/*static*/ void SystemDomain::EnumAllStaticGCRefs(promote_func* fn, ScanContext* sc)
|
|
{
|
|
CONTRACT_VOID
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACT_END;
|
|
|
|
_ASSERTE(GCHeapUtilities::IsGCInProgress() &&
|
|
GCHeapUtilities::IsServerHeap() &&
|
|
IsGCSpecialThread());
|
|
|
|
SystemDomain* sysDomain = SystemDomain::System();
|
|
if (sysDomain)
|
|
{
|
|
AppDomain* pAppDomain = ::GetAppDomain();
|
|
if (pAppDomain && pAppDomain->IsActive())
|
|
{
|
|
pAppDomain->EnumStaticGCRefs(fn, sc);
|
|
}
|
|
}
|
|
|
|
RETURN;
|
|
}
|
|
|
|
// Only called when EE is suspended.
|
|
DWORD SystemDomain::GetTotalNumSizedRefHandles()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
SystemDomain* sysDomain = SystemDomain::System();
|
|
DWORD dwTotalNumSizedRefHandles = 0;
|
|
if (sysDomain)
|
|
{
|
|
AppDomain* pAppDomain = ::GetAppDomain();
|
|
if (pAppDomain && pAppDomain->IsActive())
|
|
{
|
|
dwTotalNumSizedRefHandles += pAppDomain->GetNumSizedRefHandles();
|
|
}
|
|
}
|
|
|
|
return dwTotalNumSizedRefHandles;
|
|
}
|
|
|
|
void SystemDomain::LoadBaseSystemClasses()
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
ETWOnStartup(LdSysBases_V1, LdSysBasesEnd_V1);
|
|
|
|
EX_TRY
|
|
{
|
|
m_pSystemPEAssembly = PEAssembly::OpenSystem();
|
|
|
|
// Only partially load the system assembly. Other parts of the code will want to access
|
|
// the globals in this function before finishing the load.
|
|
m_pSystemAssembly = DefaultDomain()->LoadDomainAssembly(NULL, m_pSystemPEAssembly, FILE_LOAD_POST_LOADLIBRARY)->GetAssembly();
|
|
|
|
// Set up binder for CoreLib
|
|
CoreLibBinder::AttachModule(m_pSystemAssembly->GetModule());
|
|
|
|
// Load Object
|
|
g_pObjectClass = CoreLibBinder::GetClass(CLASS__OBJECT);
|
|
|
|
// Now that ObjectClass is loaded, we can set up
|
|
// the system for finalizers. There is no point in deferring this, since we need
|
|
// to know this before we allocate our first object.
|
|
g_pObjectFinalizerMD = CoreLibBinder::GetMethod(METHOD__OBJECT__FINALIZE);
|
|
|
|
|
|
g_pCanonMethodTableClass = CoreLibBinder::GetClass(CLASS____CANON);
|
|
|
|
// NOTE: !!!IMPORTANT!!! ValueType and Enum MUST be loaded one immediately after
|
|
// the other, because we have coded MethodTable::IsChildValueType
|
|
// in such a way that it depends on this behaviour.
|
|
// Load the ValueType class
|
|
g_pValueTypeClass = CoreLibBinder::GetClass(CLASS__VALUE_TYPE);
|
|
|
|
// Load the enum class
|
|
g_pEnumClass = CoreLibBinder::GetClass(CLASS__ENUM);
|
|
_ASSERTE(!g_pEnumClass->IsValueType());
|
|
|
|
// Load System.RuntimeType
|
|
g_pRuntimeTypeClass = CoreLibBinder::GetClass(CLASS__CLASS);
|
|
_ASSERTE(g_pRuntimeTypeClass->IsFullyLoaded());
|
|
|
|
// Load Array class
|
|
g_pArrayClass = CoreLibBinder::GetClass(CLASS__ARRAY);
|
|
|
|
// Calling a method on IList<T> for an array requires redirection to a method on
|
|
// the SZArrayHelper class. Retrieving such methods means calling
|
|
// GetActualImplementationForArrayGenericIListMethod, which calls FetchMethod for
|
|
// the corresponding method on SZArrayHelper. This basically results in a class
|
|
// load due to a method call, which the debugger cannot handle, so we pre-load
|
|
// the SZArrayHelper class here.
|
|
g_pSZArrayHelperClass = CoreLibBinder::GetClass(CLASS__SZARRAYHELPER);
|
|
|
|
// Load Nullable class
|
|
g_pNullableClass = CoreLibBinder::GetClass(CLASS__NULLABLE);
|
|
|
|
// Load the Object array class.
|
|
g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT] = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pObjectClass));
|
|
|
|
// We have delayed allocation of CoreLib's static handles until we load the object class
|
|
CoreLibBinder::GetModule()->AllocateRegularStaticHandles();
|
|
|
|
// Boolean has to be loaded first to break cycle in IComparisonOperations and IEqualityOperators
|
|
CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_BOOLEAN);
|
|
|
|
// Int32 has to be loaded next to break cycle in IShiftOperators
|
|
CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I4);
|
|
|
|
// Make sure all primitive types are loaded
|
|
for (int et = ELEMENT_TYPE_VOID; et <= ELEMENT_TYPE_R8; et++)
|
|
CoreLibBinder::LoadPrimitiveType((CorElementType)et);
|
|
|
|
CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I);
|
|
CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_U);
|
|
|
|
g_TypedReferenceMT = CoreLibBinder::GetClass(CLASS__TYPED_REFERENCE);
|
|
|
|
// unfortunately, the following cannot be delay loaded since the jit
|
|
// uses it to compute method attributes within a function that cannot
|
|
// handle Complus exception and the following call goes through a path
|
|
// where a complus exception can be thrown. It is unfortunate, because
|
|
// we know that the delegate class and multidelegate class are always
|
|
// guaranteed to be found.
|
|
g_pDelegateClass = CoreLibBinder::GetClass(CLASS__DELEGATE);
|
|
g_pMulticastDelegateClass = CoreLibBinder::GetClass(CLASS__MULTICAST_DELEGATE);
|
|
|
|
// further loading of nonprimitive types may need casting support.
|
|
// initialize cast cache here.
|
|
CastCache::Initialize();
|
|
ECall::PopulateManagedCastHelpers();
|
|
|
|
// used by IsImplicitInterfaceOfSZArray
|
|
CoreLibBinder::GetClass(CLASS__IENUMERABLEGENERIC);
|
|
CoreLibBinder::GetClass(CLASS__ICOLLECTIONGENERIC);
|
|
CoreLibBinder::GetClass(CLASS__ILISTGENERIC);
|
|
CoreLibBinder::GetClass(CLASS__IREADONLYCOLLECTIONGENERIC);
|
|
CoreLibBinder::GetClass(CLASS__IREADONLYLISTGENERIC);
|
|
|
|
// Load String
|
|
g_pStringClass = CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_STRING);
|
|
|
|
ECall::PopulateManagedStringConstructors();
|
|
|
|
g_pExceptionClass = CoreLibBinder::GetClass(CLASS__EXCEPTION);
|
|
g_pOutOfMemoryExceptionClass = CoreLibBinder::GetException(kOutOfMemoryException);
|
|
g_pStackOverflowExceptionClass = CoreLibBinder::GetException(kStackOverflowException);
|
|
g_pExecutionEngineExceptionClass = CoreLibBinder::GetException(kExecutionEngineException);
|
|
g_pThreadAbortExceptionClass = CoreLibBinder::GetException(kThreadAbortException);
|
|
|
|
g_pThreadClass = CoreLibBinder::GetClass(CLASS__THREAD);
|
|
|
|
g_pWeakReferenceClass = CoreLibBinder::GetClass(CLASS__WEAKREFERENCE);
|
|
g_pWeakReferenceOfTClass = CoreLibBinder::GetClass(CLASS__WEAKREFERENCEGENERIC);
|
|
|
|
g_pCastHelpers = CoreLibBinder::GetClass(CLASS__CASTHELPERS);
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
if (g_pConfig->IsBuiltInCOMSupported())
|
|
{
|
|
g_pBaseCOMObject = CoreLibBinder::GetClass(CLASS__COM_OBJECT);
|
|
}
|
|
else
|
|
{
|
|
g_pBaseCOMObject = NULL;
|
|
}
|
|
#endif
|
|
|
|
g_pIDynamicInterfaceCastableInterface = CoreLibBinder::GetClass(CLASS__IDYNAMICINTERFACECASTABLE);
|
|
|
|
#ifdef FEATURE_ICASTABLE
|
|
g_pICastableInterface = CoreLibBinder::GetClass(CLASS__ICASTABLE);
|
|
#endif // FEATURE_ICASTABLE
|
|
|
|
#ifdef FEATURE_EH_FUNCLETS
|
|
g_pEHClass = CoreLibBinder::GetClass(CLASS__EH);
|
|
g_pExceptionServicesInternalCallsClass = CoreLibBinder::GetClass(CLASS__EXCEPTIONSERVICES_INTERNALCALLS);
|
|
g_pStackFrameIteratorClass = CoreLibBinder::GetClass(CLASS__STACKFRAMEITERATOR);
|
|
#endif
|
|
|
|
// Make sure that FCall mapping for Monitor.Enter is initialized. We need it in case Monitor.Enter is used only as JIT helper.
|
|
// For more details, see comment in code:JITutil_MonEnterWorker around "__me = GetEEFuncEntryPointMacro(JIT_MonEnter)".
|
|
ECall::GetFCallImpl(CoreLibBinder::GetMethod(METHOD__MONITOR__ENTER));
|
|
|
|
#ifdef PROFILING_SUPPORTED
|
|
// Note that g_profControlBlock.fBaseSystemClassesLoaded must be set to TRUE only after
|
|
// all base system classes are loaded. Profilers are not allowed to call any type-loading
|
|
// APIs until g_profControlBlock.fBaseSystemClassesLoaded is TRUE. It is important that
|
|
// all base system classes need to be loaded before profilers can trigger the type loading.
|
|
g_profControlBlock.fBaseSystemClassesLoaded = TRUE;
|
|
#endif // PROFILING_SUPPORTED
|
|
|
|
// Perform any once-only SafeHandle initialization.
|
|
SafeHandle::Init();
|
|
|
|
#if defined(_DEBUG)
|
|
g_CoreLib.Check();
|
|
g_CoreLib.CheckExtended();
|
|
#endif // _DEBUG
|
|
}
|
|
EX_HOOK
|
|
{
|
|
Exception *ex = GET_EXCEPTION();
|
|
|
|
LogErrorToHost("Failed to load System.Private.CoreLib.dll (error code 0x%08X)", ex->GetHR());
|
|
MAKE_UTF8PTR_FROMWIDE_NOTHROW(filePathUtf8, SystemDomain::System()->BaseLibrary())
|
|
if (filePathUtf8 != NULL)
|
|
{
|
|
LogErrorToHost("Path: %s", filePathUtf8);
|
|
}
|
|
SString err;
|
|
ex->GetMessage(err);
|
|
LogErrorToHost("Error message: %s", err.GetUTF8());
|
|
}
|
|
EX_END_HOOK;
|
|
}
|
|
|
|
#endif // !DACCESS_COMPILE
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
#if defined(FEATURE_COMINTEROP_APARTMENT_SUPPORT)
|
|
|
|
Thread::ApartmentState SystemDomain::GetEntryPointThreadAptState(IMDInternalImport* pScope, mdMethodDef mdMethod)
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
HRESULT hr;
|
|
IfFailThrow(hr = pScope->GetCustomAttributeByName(mdMethod,
|
|
DEFAULTDOMAIN_MTA_TYPE,
|
|
NULL,
|
|
NULL));
|
|
BOOL fIsMTA = FALSE;
|
|
if(hr == S_OK)
|
|
fIsMTA = TRUE;
|
|
|
|
IfFailThrow(hr = pScope->GetCustomAttributeByName(mdMethod,
|
|
DEFAULTDOMAIN_STA_TYPE,
|
|
NULL,
|
|
NULL));
|
|
BOOL fIsSTA = FALSE;
|
|
if (hr == S_OK)
|
|
fIsSTA = TRUE;
|
|
|
|
if (fIsSTA && fIsMTA)
|
|
COMPlusThrowHR(COR_E_CUSTOMATTRIBUTEFORMAT);
|
|
|
|
if (fIsSTA)
|
|
return Thread::AS_InSTA;
|
|
else if (fIsMTA)
|
|
return Thread::AS_InMTA;
|
|
|
|
return Thread::AS_Unknown;
|
|
}
|
|
|
|
void SystemDomain::SetThreadAptState (Thread::ApartmentState state)
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
Thread* pThread = GetThread();
|
|
if(state == Thread::AS_InSTA)
|
|
{
|
|
Thread::ApartmentState pState = pThread->SetApartment(Thread::AS_InSTA);
|
|
_ASSERTE(pState == Thread::AS_InSTA);
|
|
}
|
|
else
|
|
{
|
|
// If an apartment state was not explicitly requested, default to MTA
|
|
Thread::ApartmentState pState = pThread->SetApartment(Thread::AS_InMTA);
|
|
_ASSERTE(pState == Thread::AS_InMTA);
|
|
}
|
|
}
|
|
#endif // defined(FEATURE_COMINTEROP_APARTMENT_SUPPORT)
|
|
|
|
/*static*/
|
|
bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
MethodTable* pCaller = pMeth->GetMethodTable();
|
|
|
|
// All reflection invocation methods are defined in CoreLib.
|
|
if (!pCaller->GetModule()->IsSystem())
|
|
return false;
|
|
|
|
// Check for dynamically generated Invoke methods.
|
|
if (pMeth->IsLCGMethod())
|
|
{
|
|
// Even if a user-created DynamicMethod uses the same naming convention, it will likely not
|
|
// get here since since DynamicMethods by default are created in a special (non-system) module.
|
|
// If this is not sufficient for conflict prevention, we can create a new private module.
|
|
return (strncmp(pMeth->GetName(), "InvokeStub_", ARRAY_SIZE("InvokeStub_") - 1) == 0);
|
|
}
|
|
|
|
/* List of types that should be skipped to identify true caller */
|
|
static const BinderClassID reflectionInvocationTypes[] = {
|
|
CLASS__METHOD,
|
|
CLASS__METHOD_BASE,
|
|
CLASS__METHOD_INFO,
|
|
CLASS__CONSTRUCTOR,
|
|
CLASS__CONSTRUCTOR_INFO,
|
|
CLASS__CLASS,
|
|
CLASS__TYPE_HANDLE,
|
|
CLASS__METHOD_HANDLE,
|
|
CLASS__FIELD_HANDLE,
|
|
CLASS__TYPE,
|
|
CLASS__FIELD,
|
|
CLASS__RT_FIELD_INFO,
|
|
CLASS__FIELD_INFO,
|
|
CLASS__EVENT,
|
|
CLASS__EVENT_INFO,
|
|
CLASS__PROPERTY,
|
|
CLASS__PROPERTY_INFO,
|
|
CLASS__ACTIVATOR,
|
|
CLASS__ARRAY,
|
|
CLASS__ASSEMBLYBASE,
|
|
CLASS__ASSEMBLY,
|
|
CLASS__TYPE_DELEGATOR,
|
|
CLASS__RUNTIME_HELPERS,
|
|
CLASS__DYNAMICMETHOD,
|
|
CLASS__DELEGATE,
|
|
CLASS__MULTICAST_DELEGATE,
|
|
CLASS__METHODBASEINVOKER,
|
|
};
|
|
|
|
static bool fInited = false;
|
|
|
|
if (!VolatileLoad(&fInited))
|
|
{
|
|
// Make sure all types are loaded so that we can use faster GetExistingClass()
|
|
for (unsigned i = 0; i < ARRAY_SIZE(reflectionInvocationTypes); i++)
|
|
{
|
|
CoreLibBinder::GetClass(reflectionInvocationTypes[i]);
|
|
}
|
|
|
|
VolatileStore(&fInited, true);
|
|
}
|
|
|
|
if (!pCaller->HasInstantiation())
|
|
{
|
|
for (unsigned i = 0; i < ARRAY_SIZE(reflectionInvocationTypes); i++)
|
|
{
|
|
if (CoreLibBinder::GetExistingClass(reflectionInvocationTypes[i]) == pCaller)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct CallersDataWithStackMark
|
|
{
|
|
StackCrawlMark* stackMark;
|
|
BOOL foundMe;
|
|
MethodDesc* pFoundMethod;
|
|
MethodDesc* pPrevMethod;
|
|
};
|
|
|
|
/*static*/
|
|
Module* SystemDomain::GetCallersModule(StackCrawlMark* stackMark)
|
|
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (stackMark == NULL)
|
|
return NULL;
|
|
|
|
GCX_COOP();
|
|
|
|
CallersDataWithStackMark cdata;
|
|
ZeroMemory(&cdata, sizeof(CallersDataWithStackMark));
|
|
cdata.stackMark = stackMark;
|
|
|
|
GetThread()->StackWalkFrames(CallersMethodCallbackWithStackMark, &cdata, FUNCTIONSONLY | LIGHTUNWIND);
|
|
|
|
if(cdata.pFoundMethod) {
|
|
return cdata.pFoundMethod->GetModule();
|
|
} else
|
|
return NULL;
|
|
}
|
|
|
|
struct CallersData
|
|
{
|
|
int skip;
|
|
MethodDesc* pMethod;
|
|
};
|
|
|
|
/*static*/
|
|
Assembly* SystemDomain::GetCallersAssembly(StackCrawlMark *stackMark)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
Module* mod = GetCallersModule(stackMark);
|
|
if (mod)
|
|
return mod->GetAssembly();
|
|
return NULL;
|
|
}
|
|
|
|
/*private static*/
|
|
StackWalkAction SystemDomain::CallersMethodCallbackWithStackMark(CrawlFrame* pCf, VOID* data)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
|
|
MethodDesc *pFunc = pCf->GetFunction();
|
|
|
|
/* We asked to be called back only for functions */
|
|
_ASSERTE(pFunc);
|
|
|
|
CallersDataWithStackMark* pCaller = (CallersDataWithStackMark*) data;
|
|
if (pCaller->stackMark)
|
|
{
|
|
if (!pCf->IsInCalleesFrames(pCaller->stackMark))
|
|
{
|
|
// save the current in case it is the one we want
|
|
pCaller->pPrevMethod = pFunc;
|
|
return SWA_CONTINUE;
|
|
}
|
|
|
|
// LookForMe stack crawl marks needn't worry about reflection or
|
|
// remoting frames on the stack. Each frame above (newer than) the
|
|
// target will be captured by the logic above. Once we transition to
|
|
// finding the stack mark below the AofRA, we know that we hit the
|
|
// target last time round and immediately exit with the cached result.
|
|
|
|
if (*(pCaller->stackMark) == LookForMe)
|
|
{
|
|
pCaller->pFoundMethod = pCaller->pPrevMethod;
|
|
return SWA_ABORT;
|
|
}
|
|
}
|
|
|
|
// Skip reflection and remoting frames that could lie between a stack marked
|
|
// method and its true caller (or that caller and its own caller). These
|
|
// frames are infrastructure and logically transparent to the stack crawling
|
|
// algorithm.
|
|
|
|
// Skipping remoting frames. We always skip entire client to server spans
|
|
// (though we see them in the order server then client during a stack crawl
|
|
// obviously).
|
|
|
|
// We spot the server dispatcher end because all calls are dispatched
|
|
// through a single method: StackBuilderSink._PrivateProcessMessage.
|
|
|
|
Frame* frame = pCf->GetFrame();
|
|
_ASSERTE(pCf->IsFrameless() || frame);
|
|
|
|
// Skipping reflection frames. We don't need to be quite as exhaustive here
|
|
// as the security or reflection stack walking code since we know this logic
|
|
// is only invoked for selected methods in CoreLib itself. So we're
|
|
// reasonably sure we won't have any sensitive methods late bound invoked on
|
|
// constructors, properties or events. This leaves being invoked via
|
|
// MethodInfo, Type or Delegate (and depending on which invoke overload is
|
|
// being used, several different reflection classes may be involved).
|
|
|
|
if (SystemDomain::IsReflectionInvocationMethod(pFunc))
|
|
return SWA_CONTINUE;
|
|
|
|
if (frame && frame->GetFrameType() == Frame::TYPE_MULTICAST)
|
|
{
|
|
// This must be either a multicast delegate invocation.
|
|
|
|
_ASSERTE(pFunc->GetMethodTable()->IsDelegate());
|
|
|
|
DELEGATEREF del = (DELEGATEREF)((MulticastFrame*)frame)->GetThis(); // This can throw.
|
|
|
|
_ASSERTE(COMDelegate::IsTrueMulticastDelegate(del));
|
|
return SWA_CONTINUE;
|
|
}
|
|
|
|
// Return the first non-reflection/remoting frame if no stack mark was
|
|
// supplied.
|
|
if (!pCaller->stackMark)
|
|
{
|
|
pCaller->pFoundMethod = pFunc;
|
|
return SWA_ABORT;
|
|
}
|
|
|
|
// If we got here, we must already be in the frame containing the stack mark and we are not looking for "me".
|
|
_ASSERTE(pCaller->stackMark &&
|
|
pCf->IsInCalleesFrames(pCaller->stackMark) &&
|
|
*(pCaller->stackMark) != LookForMe);
|
|
|
|
// When looking for caller's caller, we delay returning results for another
|
|
// round (the way this is structured, we will still be able to skip
|
|
// reflection and remoting frames between the caller and the caller's
|
|
// caller).
|
|
|
|
if ((*(pCaller->stackMark) == LookForMyCallersCaller) &&
|
|
(pCaller->pFoundMethod == NULL))
|
|
{
|
|
pCaller->pFoundMethod = pFunc;
|
|
return SWA_CONTINUE;
|
|
}
|
|
|
|
pCaller->pFoundMethod = pFunc;
|
|
|
|
return SWA_ABORT;
|
|
}
|
|
|
|
/*private static*/
|
|
StackWalkAction SystemDomain::CallersMethodCallback(CrawlFrame* pCf, VOID* data)
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
MethodDesc *pFunc = pCf->GetFunction();
|
|
|
|
/* We asked to be called back only for functions */
|
|
_ASSERTE(pFunc);
|
|
|
|
CallersData* pCaller = (CallersData*) data;
|
|
if(pCaller->skip == 0) {
|
|
pCaller->pMethod = pFunc;
|
|
return SWA_ABORT;
|
|
}
|
|
else {
|
|
pCaller->skip--;
|
|
return SWA_CONTINUE;
|
|
}
|
|
}
|
|
|
|
void AppDomain::Create()
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
AppDomainRefHolder pDomain(new AppDomain());
|
|
|
|
pDomain->Init();
|
|
|
|
// allocate a Virtual Call Stub Manager for the default domain
|
|
pDomain->InitVSD();
|
|
|
|
// allocate a thread static block to index map
|
|
pDomain->InitThreadStaticBlockTypeMap();
|
|
|
|
pDomain->SetStage(AppDomain::STAGE_OPEN);
|
|
pDomain->CreateDefaultBinder();
|
|
|
|
pDomain.SuppressRelease();
|
|
|
|
m_pTheAppDomain = pDomain;
|
|
|
|
LOG((LF_CLASSLOADER | LF_CORDB,
|
|
LL_INFO10,
|
|
"Created the app domain at %p\n", m_pTheAppDomain));
|
|
}
|
|
|
|
#ifdef DEBUGGING_SUPPORTED
|
|
|
|
void SystemDomain::PublishAppDomainAndInformDebugger (AppDomain *pDomain)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
if(!g_fEEInit) {THROWS;} else {DISABLED(NOTHROW);};
|
|
if(!g_fEEInit) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);};
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
LOG((LF_CORDB, LL_INFO100, "SD::PADAID: Adding 0x%x\n", pDomain));
|
|
|
|
// Call the publisher API to add this appdomain entry to the list
|
|
// The publisher will handle failures, so we don't care if this succeeds or fails.
|
|
if (g_pDebugInterface != NULL)
|
|
{
|
|
g_pDebugInterface->AddAppDomainToIPC(pDomain);
|
|
}
|
|
}
|
|
|
|
#endif // DEBUGGING_SUPPORTED
|
|
|
|
#ifdef PROFILING_SUPPORTED
|
|
void SystemDomain::NotifyProfilerStartup()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_PREEMPTIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System());
|
|
(&g_profControlBlock)->AppDomainCreationStarted((AppDomainID) System());
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System());
|
|
(&g_profControlBlock)->AppDomainCreationFinished((AppDomainID) System(), S_OK);
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System()->DefaultDomain());
|
|
(&g_profControlBlock)->AppDomainCreationStarted((AppDomainID) System()->DefaultDomain());
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System()->DefaultDomain());
|
|
(&g_profControlBlock)->AppDomainCreationFinished((AppDomainID) System()->DefaultDomain(), S_OK);
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
}
|
|
|
|
HRESULT SystemDomain::NotifyProfilerShutdown()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_PREEMPTIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System());
|
|
(&g_profControlBlock)->AppDomainShutdownStarted((AppDomainID) System());
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System());
|
|
(&g_profControlBlock)->AppDomainShutdownFinished((AppDomainID) System(), S_OK);
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System()->DefaultDomain());
|
|
(&g_profControlBlock)->AppDomainShutdownStarted((AppDomainID) System()->DefaultDomain());
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
|
|
{
|
|
BEGIN_PROFILER_CALLBACK(CORProfilerTrackAppDomainLoads());
|
|
_ASSERTE(System()->DefaultDomain());
|
|
(&g_profControlBlock)->AppDomainShutdownFinished((AppDomainID) System()->DefaultDomain(), S_OK);
|
|
END_PROFILER_CALLBACK();
|
|
}
|
|
return (S_OK);
|
|
}
|
|
#endif // PROFILING_SUPPORTED
|
|
|
|
AppDomain::AppDomain()
|
|
{
|
|
// initialize fields so the appdomain can be safely destructed
|
|
// shouldn't call anything that can fail here - use ::Init instead
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
FORBID_FAULT;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
m_cRef=1;
|
|
|
|
m_pRootAssembly = NULL;
|
|
|
|
m_dwFlags = 0;
|
|
#ifdef FEATURE_COMINTEROP
|
|
m_pRCWCache = NULL;
|
|
#endif //FEATURE_COMINTEROP
|
|
#ifdef FEATURE_COMWRAPPERS
|
|
m_pRCWRefCache = NULL;
|
|
#endif // FEATURE_COMWRAPPERS
|
|
|
|
m_handleStore = NULL;
|
|
|
|
#ifdef _DEBUG
|
|
m_Assemblies.Debug_SetAppDomain(this);
|
|
#endif // _DEBUG
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
m_pRefDispIDCache = NULL;
|
|
m_hndMissing = NULL;
|
|
#endif
|
|
|
|
m_pRefClassFactHash = NULL;
|
|
|
|
m_ForceTrivialWaitOperations = false;
|
|
m_Stage=STAGE_CREATING;
|
|
|
|
#ifdef FEATURE_TYPEEQUIVALENCE
|
|
m_pTypeEquivalenceTable = NULL;
|
|
#endif // FEATURE_TYPEEQUIVALENCE
|
|
|
|
} // AppDomain::AppDomain
|
|
|
|
AppDomain::~AppDomain()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
m_AssemblyCache.Clear();
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//*****************************************************************************
|
|
//*****************************************************************************
|
|
void AppDomain::Init()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
STANDARD_VM_CHECK;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
m_pDelayedLoaderAllocatorUnloadList = NULL;
|
|
|
|
SetStage( STAGE_CREATING);
|
|
|
|
BaseDomain::Init();
|
|
|
|
// Set up the binding caches
|
|
m_AssemblyCache.Init(&m_DomainCacheCrst, GetHighFrequencyHeap());
|
|
|
|
m_MemoryPressure = 0;
|
|
|
|
|
|
// Default domain reuses the handletablemap that was created during EEStartup
|
|
m_handleStore = GCHandleUtilities::GetGCHandleManager()->GetGlobalHandleStore();
|
|
|
|
if (!m_handleStore)
|
|
{
|
|
COMPlusThrowOM();
|
|
}
|
|
|
|
|
|
#ifdef FEATURE_TYPEEQUIVALENCE
|
|
m_TypeEquivalenceCrst.Init(CrstTypeEquivalenceMap);
|
|
#endif
|
|
|
|
m_ReflectionCrst.Init(CrstReflection, CRST_UNSAFE_ANYMODE);
|
|
m_RefClassFactCrst.Init(CrstClassFactInfoHash);
|
|
|
|
SetStage(STAGE_READYFORMANAGEDCODE);
|
|
|
|
|
|
#ifdef FEATURE_TIERED_COMPILATION
|
|
m_tieredCompilationManager.Init();
|
|
#endif
|
|
|
|
m_nativeImageLoadCrst.Init(CrstNativeImageLoad);
|
|
} // AppDomain::Init
|
|
|
|
void AppDomain::Stop()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
MODE_ANY;
|
|
GC_TRIGGERS;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
#ifdef FEATURE_MULTICOREJIT
|
|
GetMulticoreJitManager().StopProfile(true);
|
|
#endif
|
|
|
|
// Set the unloaded flag before notifying the debugger
|
|
GetLoaderAllocator()->SetIsUnloaded();
|
|
|
|
#ifdef DEBUGGING_SUPPORTED
|
|
if (IsDebuggerAttached())
|
|
NotifyDebuggerUnload();
|
|
|
|
if (NULL != g_pDebugInterface)
|
|
{
|
|
// Call the publisher API to delete this appdomain entry from the list
|
|
CONTRACT_VIOLATION(ThrowsViolation);
|
|
g_pDebugInterface->RemoveAppDomainFromIPC (this);
|
|
}
|
|
#endif // DEBUGGING_SUPPORTED
|
|
}
|
|
|
|
|
|
#endif //!DACCESS_COMPILE
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
void AppDomain::AddAssembly(DomainAssembly * assem)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
{
|
|
CrstHolder ch(GetAssemblyListLock());
|
|
|
|
// Attempt to find empty space in assemblies list
|
|
DWORD asmCount = m_Assemblies.GetCount_Unlocked();
|
|
for (DWORD i = 0; i < asmCount; ++i)
|
|
{
|
|
if (m_Assemblies.Get_UnlockedNoReference(i) == NULL)
|
|
{
|
|
m_Assemblies.Set_Unlocked(i, assem);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If empty space not found, simply add to end of list
|
|
IfFailThrow(m_Assemblies.Append_Unlocked(assem));
|
|
}
|
|
}
|
|
|
|
void AppDomain::RemoveAssembly(DomainAssembly * pAsm)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
CrstHolder ch(GetAssemblyListLock());
|
|
DWORD asmCount = m_Assemblies.GetCount_Unlocked();
|
|
for (DWORD i = 0; i < asmCount; ++i)
|
|
{
|
|
if (m_Assemblies.Get_UnlockedNoReference(i) == pAsm)
|
|
{
|
|
m_Assemblies.Set_Unlocked(i, NULL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
_ASSERTE(!"Unreachable");
|
|
}
|
|
|
|
BOOL AppDomain::ContainsAssembly(Assembly * assem)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)(
|
|
kIncludeLoaded | kIncludeExecution));
|
|
CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
|
|
|
|
while (i.Next(pDomainAssembly.This()))
|
|
{
|
|
CollectibleAssemblyHolder<Assembly *> pAssembly = pDomainAssembly->GetAssembly();
|
|
if (pAssembly == assem)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
EEClassFactoryInfoHashTable* AppDomain::SetupClassFactHash()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
CrstHolder ch(&m_ReflectionCrst);
|
|
|
|
if (m_pRefClassFactHash == NULL)
|
|
{
|
|
AllocMemHolder<void> pCache(GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof (EEClassFactoryInfoHashTable))));
|
|
EEClassFactoryInfoHashTable *tmp = new (pCache) EEClassFactoryInfoHashTable;
|
|
LockOwner lock = {&m_RefClassFactCrst,IsOwnerOfCrst};
|
|
if (!tmp->Init(20, &lock))
|
|
COMPlusThrowOM();
|
|
pCache.SuppressRelease();
|
|
m_pRefClassFactHash = tmp;
|
|
}
|
|
|
|
return m_pRefClassFactHash;
|
|
}
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
DispIDCache* AppDomain::SetupRefDispIDCache()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
CrstHolder ch(&m_ReflectionCrst);
|
|
|
|
if (m_pRefDispIDCache == NULL)
|
|
{
|
|
AllocMemHolder<void> pCache = GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof (DispIDCache)));
|
|
|
|
DispIDCache *tmp = new (pCache) DispIDCache;
|
|
tmp->Init();
|
|
|
|
pCache.SuppressRelease();
|
|
m_pRefDispIDCache = tmp;
|
|
}
|
|
|
|
return m_pRefDispIDCache;
|
|
}
|
|
|
|
#endif // FEATURE_COMINTEROP
|
|
|
|
FileLoadLock *FileLoadLock::Create(PEFileListLock *pLock, PEAssembly * pPEAssembly, DomainAssembly *pDomainAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(pLock->HasLock());
|
|
PRECONDITION(pLock->FindFileLock(pPEAssembly) == NULL);
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
NewHolder<FileLoadLock> result(new FileLoadLock(pLock, pPEAssembly, pDomainAssembly));
|
|
|
|
pLock->AddElement(result);
|
|
result->AddRef(); // Add one ref on behalf of the ListLock's reference. The corresponding Release() happens in FileLoadLock::CompleteLoadLevel.
|
|
return result.Extract();
|
|
}
|
|
|
|
FileLoadLock::~FileLoadLock()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
DESTRUCTOR_CHECK;
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
((PEAssembly *) m_data)->Release();
|
|
}
|
|
|
|
DomainAssembly *FileLoadLock::GetDomainAssembly()
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
return m_pDomainAssembly;
|
|
}
|
|
|
|
FileLoadLevel FileLoadLock::GetLoadLevel()
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
return m_level;
|
|
}
|
|
|
|
// Acquire will return FALSE and not take the lock if the file
|
|
// has already been loaded to the target level. Otherwise,
|
|
// it will return TRUE and take the lock.
|
|
//
|
|
// Note that the taker must release the lock via IncrementLoadLevel.
|
|
|
|
BOOL FileLoadLock::Acquire(FileLoadLevel targetLevel)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
|
|
// If we are already loaded to the desired level, the lock is "free".
|
|
if (m_level >= targetLevel)
|
|
return FALSE;
|
|
|
|
if (!DeadlockAwareEnter())
|
|
{
|
|
// We failed to get the lock due to a deadlock.
|
|
return FALSE;
|
|
}
|
|
|
|
if (m_level >= targetLevel)
|
|
{
|
|
Leave();
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL FileLoadLock::CanAcquire(FileLoadLevel targetLevel)
|
|
{
|
|
// If we are already loaded to the desired level, the lock is "free".
|
|
if (m_level >= targetLevel)
|
|
return FALSE;
|
|
|
|
return CanDeadlockAwareEnter();
|
|
}
|
|
|
|
#if !defined(DACCESS_COMPILE) && (defined(LOGGING) || defined(STRESS_LOG))
|
|
static const char *fileLoadLevelName[] =
|
|
{
|
|
"CREATE", // FILE_LOAD_CREATE
|
|
"BEGIN", // FILE_LOAD_BEGIN
|
|
"FIND_NATIVE_IMAGE", // FILE_LOAD_FIND_NATIVE_IMAGE
|
|
"VERIFY_NATIVE_IMAGE_DEPENDENCIES", // FILE_LOAD_VERIFY_NATIVE_IMAGE_DEPENDENCIES
|
|
"ALLOCATE", // FILE_LOAD_ALLOCATE
|
|
"ADD_DEPENDENCIES", // FILE_LOAD_ADD_DEPENDENCIES
|
|
"PRE_LOADLIBRARY", // FILE_LOAD_PRE_LOADLIBRARY
|
|
"LOADLIBRARY", // FILE_LOAD_LOADLIBRARY
|
|
"POST_LOADLIBRARY", // FILE_LOAD_POST_LOADLIBRARY
|
|
"EAGER_FIXUPS", // FILE_LOAD_EAGER_FIXUPS
|
|
"DELIVER_EVENTS", // FILE_LOAD_DELIVER_EVENTS
|
|
"VTABLE FIXUPS", // FILE_LOAD_VTABLE_FIXUPS
|
|
"LOADED", // FILE_LOADED
|
|
"ACTIVE", // FILE_ACTIVE
|
|
};
|
|
#endif // !DACCESS_COMPILE && (LOGGING || STRESS_LOG)
|
|
|
|
BOOL FileLoadLock::CompleteLoadLevel(FileLoadLevel level, BOOL success)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
MODE_ANY;
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
PRECONDITION(HasLock());
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Increment may happen more than once if reentrancy occurs (e.g. LoadLibrary)
|
|
if (level > m_level)
|
|
{
|
|
// Must complete each level in turn, unless we have an error
|
|
CONSISTENCY_CHECK(m_pDomainAssembly->IsError() || (level == (m_level+1)));
|
|
// Remove the lock from the list if the load is completed
|
|
if (level >= FILE_ACTIVE)
|
|
{
|
|
{
|
|
GCX_COOP();
|
|
PEFileListLockHolder lock((PEFileListLock*)m_pList);
|
|
|
|
#if _DEBUG
|
|
BOOL fDbgOnly_SuccessfulUnlink =
|
|
#endif
|
|
m_pList->Unlink(this);
|
|
_ASSERTE(fDbgOnly_SuccessfulUnlink);
|
|
|
|
m_pDomainAssembly->ClearLoading();
|
|
|
|
CONSISTENCY_CHECK(m_dwRefCount >= 2); // Caller (LoadDomainAssembly) should have 1 refcount and m_pList should have another which was acquired in FileLoadLock::Create.
|
|
|
|
m_level = (FileLoadLevel)level;
|
|
|
|
// Dev11 bug 236344
|
|
// In AppDomain::IsLoading, if the lock is taken on m_pList and then FindFileLock returns NULL,
|
|
// we depend on the DomainAssembly's load level being up to date. Hence we must update the load
|
|
// level while the m_pList lock is held.
|
|
if (success)
|
|
m_pDomainAssembly->SetLoadLevel(level);
|
|
}
|
|
|
|
|
|
Release(); // Release m_pList's refcount on this lock, which was acquired in FileLoadLock::Create
|
|
|
|
}
|
|
else
|
|
{
|
|
m_level = (FileLoadLevel)level;
|
|
|
|
if (success)
|
|
m_pDomainAssembly->SetLoadLevel(level);
|
|
}
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
switch(level)
|
|
{
|
|
case FILE_LOAD_ALLOCATE:
|
|
case FILE_LOAD_ADD_DEPENDENCIES:
|
|
case FILE_LOAD_DELIVER_EVENTS:
|
|
case FILE_LOADED:
|
|
case FILE_ACTIVE: // The timing of stress logs is not critical, so even for the FILE_ACTIVE stage we need not do it while the m_pList lock is held.
|
|
STRESS_LOG3(LF_CLASSLOADER, LL_INFO100, "Completed Load Level %s for DomainAssembly %p - success = %i\n", fileLoadLevelName[level], m_pDomainAssembly, success);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void FileLoadLock::SetError(Exception *ex)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
MODE_ANY;
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
PRECONDITION(CheckPointer(ex));
|
|
PRECONDITION(HasLock());
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
m_cachedHR = ex->GetHR();
|
|
|
|
LOG((LF_LOADER, LL_WARNING, "LOADER: ***%s*\t!!!Non-transient error 0x%x\n",
|
|
m_pDomainAssembly->GetSimpleName(), m_cachedHR));
|
|
|
|
m_pDomainAssembly->SetError(ex);
|
|
|
|
CompleteLoadLevel(FILE_ACTIVE, FALSE);
|
|
}
|
|
|
|
void FileLoadLock::AddRef()
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
InterlockedIncrement((LONG *) &m_dwRefCount);
|
|
}
|
|
|
|
UINT32 FileLoadLock::Release()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
LONG count = InterlockedDecrement((LONG *) &m_dwRefCount);
|
|
if (count == 0)
|
|
delete this;
|
|
|
|
return count;
|
|
}
|
|
|
|
FileLoadLock::FileLoadLock(PEFileListLock *pLock, PEAssembly * pPEAssembly, DomainAssembly *pDomainAssembly)
|
|
: ListLockEntry(pLock, pPEAssembly, "File load lock"),
|
|
m_level((FileLoadLevel) (FILE_LOAD_CREATE)),
|
|
m_pDomainAssembly(pDomainAssembly),
|
|
m_cachedHR(S_OK)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
pPEAssembly->AddRef();
|
|
}
|
|
|
|
void FileLoadLock::HolderLeave(FileLoadLock *pThis)
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
pThis->Leave();
|
|
}
|
|
|
|
|
|
//
|
|
// Assembly loading:
|
|
//
|
|
// Assembly loading is carefully layered to avoid deadlocks in the
|
|
// presence of circular loading dependencies.
|
|
// A LoadLevel is associated with each assembly as it is being loaded. During the
|
|
// act of loading (abstractly, increasing its load level), its lock is
|
|
// held, and the current load level is stored on the thread. Any
|
|
// recursive loads during that period are automatically restricted to
|
|
// only partially load the dependent assembly to the same level as the
|
|
// caller (or to one short of that level in the presence of a deadlock
|
|
// loop.)
|
|
//
|
|
// Each loading stage must be carfully constructed so that
|
|
// this constraint is expected and can be dealt with.
|
|
//
|
|
// Note that there is one case where this still doesn't handle recursion, and that is the
|
|
// security subsystem. The security system runs managed code, and thus must typically fully
|
|
// initialize assemblies of permission sets it is trying to use. (And of course, these may be used
|
|
// while those assemblies are initializing.) This is dealt with in the historical manner - namely
|
|
// the security system passes in a special flag which says that it will deal with null return values
|
|
// in the case where a load cannot be safely completed due to such issues.
|
|
//
|
|
|
|
void AppDomain::LoadSystemAssemblies()
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
// The only reason to make an assembly a "system assembly" is if the EE is caching
|
|
// pointers to stuff in the assembly. Because this is going on, we need to preserve
|
|
// the invariant that the assembly is loaded into every app domain.
|
|
//
|
|
// Right now we have only one system assembly. We shouldn't need to add any more.
|
|
|
|
LoadAssembly(NULL, SystemDomain::System()->SystemPEAssembly(), FILE_ACTIVE);
|
|
}
|
|
|
|
// This checks if the thread has initiated (or completed) loading at the given level. A false guarantees that
|
|
// (a) The current thread (or a thread blocking on the current thread) has not started loading the file
|
|
// at the given level, and
|
|
// (b) No other thread had started loading the file at this level at the start of this function call.
|
|
|
|
// Note that another thread may start loading the file at that level in a race with the completion of
|
|
// this function. However, the caller still has the guarantee that such a load started after this
|
|
// function was called (and e.g. any state in place before the function call will be seen by the other thread.)
|
|
//
|
|
// Conversely, a true guarantees that either the current thread has started the load step, or another
|
|
// thread has completed the load step.
|
|
//
|
|
|
|
BOOL AppDomain::IsLoading(DomainAssembly *pFile, FileLoadLevel level)
|
|
{
|
|
// Cheap out
|
|
if (pFile->GetLoadLevel() < level)
|
|
{
|
|
FileLoadLock *pLock = NULL;
|
|
{
|
|
LoadLockHolder lock(this);
|
|
|
|
pLock = (FileLoadLock *) lock->FindFileLock(pFile->GetPEAssembly());
|
|
|
|
if (pLock == NULL)
|
|
{
|
|
// No thread involved with loading
|
|
return pFile->GetLoadLevel() >= level;
|
|
}
|
|
|
|
pLock->AddRef();
|
|
}
|
|
|
|
FileLoadLockRefHolder lockRef(pLock);
|
|
|
|
if (pLock->Acquire(level))
|
|
{
|
|
// We got the lock - therefore no other thread has started this loading step yet.
|
|
pLock->Leave();
|
|
return FALSE;
|
|
}
|
|
|
|
// We didn't get the lock - either this thread is already doing the load,
|
|
// or else the load has already finished.
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// CheckLoading is a weaker form of IsLoading, which will not block on
|
|
// other threads waiting for their status. This is appropriate for asserts.
|
|
CHECK AppDomain::CheckLoading(DomainAssembly *pFile, FileLoadLevel level)
|
|
{
|
|
// Cheap out
|
|
if (pFile->GetLoadLevel() < level)
|
|
{
|
|
FileLoadLock *pLock = NULL;
|
|
|
|
LoadLockHolder lock(this);
|
|
|
|
pLock = (FileLoadLock *) lock->FindFileLock(pFile->GetPEAssembly());
|
|
|
|
if (pLock != NULL
|
|
&& pLock->CanAcquire(level))
|
|
{
|
|
// We can get the lock - therefore no other thread has started this loading step yet.
|
|
CHECK_FAILF(("Loading step %d has not been initiated yet", level));
|
|
}
|
|
|
|
// We didn't get the lock - either this thread is already doing the load,
|
|
// or else the load has already finished.
|
|
}
|
|
|
|
CHECK_OK;
|
|
}
|
|
|
|
CHECK AppDomain::CheckCanLoadTypes(Assembly *pAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
CHECK_MSG(CheckValidModule(pAssembly->GetModule()),
|
|
"Type loading can occur only when executing in the assembly's app domain");
|
|
CHECK_OK;
|
|
}
|
|
|
|
CHECK AppDomain::CheckCanExecuteManagedCode(MethodDesc* pMD)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
Module* pModule=pMD->GetModule();
|
|
|
|
CHECK_MSG(CheckValidModule(pModule),
|
|
"Managed code can only run when executing in the module's app domain");
|
|
|
|
if (!pMD->IsInterface() || pMD->IsStatic()) //interfaces require no activation for instance methods
|
|
{
|
|
//cctor could have been interrupted by ADU
|
|
CHECK_MSG(pModule->CheckActivated(),
|
|
"Managed code can only run when its module has been activated in the current app domain");
|
|
}
|
|
|
|
CHECK_OK;
|
|
}
|
|
|
|
#endif // !DACCESS_COMPILE
|
|
|
|
void AppDomain::LoadDomainAssembly(DomainAssembly *pFile,
|
|
FileLoadLevel targetLevel)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS;
|
|
if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS;
|
|
if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM();); }
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Quick exit if finished
|
|
if (pFile->GetLoadLevel() >= targetLevel)
|
|
return;
|
|
|
|
// Handle the error case
|
|
pFile->ThrowIfError(targetLevel);
|
|
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
if (pFile->IsLoading())
|
|
{
|
|
GCX_PREEMP();
|
|
|
|
// Load some more if appropriate
|
|
LoadLockHolder lock(this);
|
|
|
|
FileLoadLock* pLockEntry = (FileLoadLock *) lock->FindFileLock(pFile->GetPEAssembly());
|
|
if (pLockEntry == NULL)
|
|
{
|
|
_ASSERTE (!pFile->IsLoading());
|
|
return;
|
|
}
|
|
|
|
pLockEntry->AddRef();
|
|
|
|
lock.Release();
|
|
|
|
LoadDomainAssembly(pLockEntry, targetLevel);
|
|
}
|
|
|
|
#else // DACCESS_COMPILE
|
|
DacNotImpl();
|
|
#endif // DACCESS_COMPILE
|
|
}
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
FileLoadLevel AppDomain::GetThreadFileLoadLevel()
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
if (GetThread()->GetLoadLevelLimiter() == NULL)
|
|
return FILE_ACTIVE;
|
|
else
|
|
return (FileLoadLevel)(GetThread()->GetLoadLevelLimiter()->GetLoadLevel()-1);
|
|
}
|
|
|
|
|
|
Assembly *AppDomain::LoadAssembly(AssemblySpec* pIdentity,
|
|
PEAssembly * pPEAssembly,
|
|
FileLoadLevel targetLevel)
|
|
{
|
|
CONTRACT(Assembly *)
|
|
{
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pPEAssembly));
|
|
POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); // May be NULL in recursive load case
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
DomainAssembly *pAssembly = LoadDomainAssembly(pIdentity, pPEAssembly, targetLevel);
|
|
PREFIX_ASSUME(pAssembly != NULL);
|
|
|
|
RETURN pAssembly->GetAssembly();
|
|
}
|
|
|
|
DomainAssembly* AppDomain::LoadDomainAssembly(AssemblySpec* pSpec,
|
|
PEAssembly * pPEAssembly,
|
|
FileLoadLevel targetLevel)
|
|
{
|
|
STATIC_CONTRACT_THROWS;
|
|
|
|
if (pSpec == nullptr)
|
|
{
|
|
// skip caching, since we don't have anything to base it on
|
|
return LoadDomainAssemblyInternal(pSpec, pPEAssembly, targetLevel);
|
|
}
|
|
|
|
DomainAssembly* pRetVal = NULL;
|
|
EX_TRY
|
|
{
|
|
pRetVal = LoadDomainAssemblyInternal(pSpec, pPEAssembly, targetLevel);
|
|
}
|
|
EX_HOOK
|
|
{
|
|
Exception* pEx = GET_EXCEPTION();
|
|
if (!pEx->IsTransient())
|
|
{
|
|
// Setup the binder reference in AssemblySpec from the PEAssembly if one is not already set.
|
|
AssemblyBinder* pCurrentBinder = pSpec->GetBinder();
|
|
AssemblyBinder* pBinderFromPEAssembly = pPEAssembly->GetAssemblyBinder();
|
|
|
|
if (pCurrentBinder == NULL)
|
|
{
|
|
// Set the binding context we got from the PEAssembly if AssemblySpec does not
|
|
// have that information
|
|
_ASSERTE(pBinderFromPEAssembly != NULL);
|
|
pSpec->SetBinder(pBinderFromPEAssembly);
|
|
}
|
|
#if defined(_DEBUG)
|
|
else
|
|
{
|
|
// Binding context in the spec should be the same as the binding context in the PEAssembly
|
|
_ASSERTE(pCurrentBinder == pBinderFromPEAssembly);
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
if (!EEFileLoadException::CheckType(pEx))
|
|
{
|
|
StackSString name;
|
|
pSpec->GetDisplayName(0, name);
|
|
pEx=new EEFileLoadException(name, pEx->GetHR(), pEx);
|
|
AddExceptionToCache(pSpec, pEx);
|
|
PAL_CPP_THROW(Exception *, pEx);
|
|
}
|
|
else
|
|
AddExceptionToCache(pSpec, pEx);
|
|
}
|
|
}
|
|
EX_END_HOOK;
|
|
|
|
return pRetVal;
|
|
}
|
|
|
|
|
|
DomainAssembly *AppDomain::LoadDomainAssemblyInternal(AssemblySpec* pIdentity,
|
|
PEAssembly * pPEAssembly,
|
|
FileLoadLevel targetLevel)
|
|
{
|
|
CONTRACT(DomainAssembly *)
|
|
{
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pPEAssembly));
|
|
PRECONDITION(::GetAppDomain()==this);
|
|
POSTCONDITION(CheckPointer(RETVAL));
|
|
POSTCONDITION(RETVAL->GetLoadLevel() >= GetThreadFileLoadLevel()
|
|
|| RETVAL->GetLoadLevel() >= targetLevel);
|
|
POSTCONDITION(RETVAL->CheckNoError(targetLevel));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
|
|
DomainAssembly * result;
|
|
|
|
// Go into preemptive mode since this may take a while.
|
|
GCX_PREEMP();
|
|
|
|
// Check for existing fully loaded assembly, or for an assembly which has failed during the loading process.
|
|
result = FindAssembly(pPEAssembly, FindAssemblyOptions_IncludeFailedToLoad);
|
|
|
|
if (result == NULL)
|
|
{
|
|
LoaderAllocator *pLoaderAllocator = NULL;
|
|
|
|
AssemblyBinder *pAssemblyBinder = pPEAssembly->GetAssemblyBinder();
|
|
// Assemblies loaded with CustomAssemblyBinder need to use a different LoaderAllocator if
|
|
// marked as collectible
|
|
pLoaderAllocator = pAssemblyBinder->GetLoaderAllocator();
|
|
if (pLoaderAllocator == NULL)
|
|
{
|
|
pLoaderAllocator = this->GetLoaderAllocator();
|
|
}
|
|
|
|
// Allocate the DomainAssembly a bit early to avoid GC mode problems. We could potentially avoid
|
|
// a rare redundant allocation by moving this closer to FileLoadLock::Create, but it's not worth it.
|
|
NewHolder<DomainAssembly> pDomainAssembly = new DomainAssembly(pPEAssembly, pLoaderAllocator);
|
|
|
|
LoadLockHolder lock(this);
|
|
|
|
// Find the list lock entry
|
|
FileLoadLock * fileLock = (FileLoadLock *)lock->FindFileLock(pPEAssembly);
|
|
bool registerNewAssembly = false;
|
|
if (fileLock == NULL)
|
|
{
|
|
// Check again in case we were racing
|
|
result = FindAssembly(pPEAssembly, FindAssemblyOptions_IncludeFailedToLoad);
|
|
if (result == NULL)
|
|
{
|
|
// We are the first one in - create the DomainAssembly
|
|
registerNewAssembly = true;
|
|
fileLock = FileLoadLock::Create(lock, pPEAssembly, pDomainAssembly);
|
|
pDomainAssembly.SuppressRelease();
|
|
if (pDomainAssembly->IsCollectible())
|
|
{
|
|
// We add the assembly to the LoaderAllocator only when we are sure that it can be added
|
|
// and won't be deleted in case of a concurrent load from the same ALC
|
|
((AssemblyLoaderAllocator *)pLoaderAllocator)->AddDomainAssembly(pDomainAssembly);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fileLock->AddRef();
|
|
}
|
|
|
|
lock.Release();
|
|
|
|
if (result == NULL)
|
|
{
|
|
// We pass our ref on fileLock to LoadDomainAssembly to release.
|
|
|
|
// Note that if we throw here, we will poison fileLock with an error condition,
|
|
// so it will not be removed until app domain unload. So there is no need
|
|
// to release our ref count.
|
|
result = (DomainAssembly *)LoadDomainAssembly(fileLock, targetLevel);
|
|
}
|
|
else
|
|
{
|
|
result->EnsureLoadLevel(targetLevel);
|
|
}
|
|
|
|
if (registerNewAssembly)
|
|
{
|
|
pPEAssembly->GetAssemblyBinder()->AddLoadedAssembly(pDomainAssembly->GetAssembly());
|
|
}
|
|
}
|
|
else
|
|
result->EnsureLoadLevel(targetLevel);
|
|
|
|
// Cache result in all cases, since found pPEAssembly could be from a different AssemblyRef than pIdentity
|
|
if (pIdentity == NULL)
|
|
{
|
|
AssemblySpec spec;
|
|
spec.InitializeSpec(result->GetPEAssembly());
|
|
GetAppDomain()->AddAssemblyToCache(&spec, result);
|
|
}
|
|
else
|
|
{
|
|
GetAppDomain()->AddAssemblyToCache(pIdentity, result);
|
|
}
|
|
|
|
RETURN result;
|
|
} // AppDomain::LoadDomainAssembly
|
|
|
|
DomainAssembly *AppDomain::LoadDomainAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel)
|
|
{
|
|
CONTRACT(DomainAssembly *)
|
|
{
|
|
STANDARD_VM_CHECK;
|
|
PRECONDITION(CheckPointer(pLock));
|
|
PRECONDITION(AppDomain::GetCurrentDomain() == this);
|
|
POSTCONDITION(RETVAL->GetLoadLevel() >= GetThreadFileLoadLevel()
|
|
|| RETVAL->GetLoadLevel() >= targetLevel);
|
|
POSTCONDITION(RETVAL->CheckNoError(targetLevel));
|
|
}
|
|
CONTRACT_END;
|
|
|
|
DomainAssembly *pFile = pLock->GetDomainAssembly();
|
|
|
|
// Make sure we release the lock on exit
|
|
FileLoadLockRefHolder lockRef(pLock);
|
|
|
|
// Do a quick out check for the already loaded case.
|
|
if (pLock->GetLoadLevel() >= targetLevel)
|
|
{
|
|
pFile->ThrowIfError(targetLevel);
|
|
|
|
RETURN pFile;
|
|
}
|
|
|
|
// Initialize a loading queue. This will hold any loads which are triggered recursively but
|
|
// which cannot be immediately satisfied due to anti-deadlock constraints.
|
|
|
|
// PendingLoadQueues are allocated on the stack during a load, and
|
|
// shared with all nested loads on the same thread. (Note that we won't use
|
|
// "candidate" if we are in a recursive load; that's OK since they are cheap to
|
|
// construct.)
|
|
FileLoadLevel immediateTargetLevel = targetLevel;
|
|
{
|
|
LoadLevelLimiter limit;
|
|
limit.Activate();
|
|
|
|
// We cannot set a target level higher than that allowed by the limiter currently.
|
|
// This is because of anti-deadlock constraints.
|
|
if (immediateTargetLevel > limit.GetLoadLevel())
|
|
immediateTargetLevel = limit.GetLoadLevel();
|
|
|
|
LOG((LF_LOADER, LL_INFO100, "LOADER: ***%s*\t>>>Load initiated, %s/%s\n",
|
|
pFile->GetSimpleName(),
|
|
fileLoadLevelName[immediateTargetLevel], fileLoadLevelName[targetLevel]));
|
|
|
|
// Now loop and do the load incrementally to the target level.
|
|
if (pLock->GetLoadLevel() < immediateTargetLevel)
|
|
{
|
|
while (pLock->Acquire(immediateTargetLevel))
|
|
{
|
|
FileLoadLevel workLevel;
|
|
{
|
|
FileLoadLockHolder fileLock(pLock);
|
|
|
|
// Work level is next step to do
|
|
workLevel = (FileLoadLevel)(fileLock->GetLoadLevel()+1);
|
|
|
|
// Set up the anti-deadlock constraint: we cannot safely recursively load any assemblies
|
|
// on this thread to a higher level than this assembly is being loaded now.
|
|
// Note that we do allow work at a parallel level; any deadlocks caused here will
|
|
// be resolved by the deadlock detection in the FileLoadLocks.
|
|
limit.SetLoadLevel(workLevel);
|
|
|
|
LOG((LF_LOADER,
|
|
(workLevel == FILE_LOAD_BEGIN
|
|
|| workLevel == FILE_LOADED
|
|
|| workLevel == FILE_ACTIVE)
|
|
? LL_INFO10 : LL_INFO1000,
|
|
"LOADER: %p:***%s*\t loading at level %s\n",
|
|
this, pFile->GetSimpleName(), fileLoadLevelName[workLevel]));
|
|
|
|
TryIncrementalLoad(pFile, workLevel, fileLock);
|
|
}
|
|
}
|
|
|
|
if (pLock->GetLoadLevel() == immediateTargetLevel-1)
|
|
{
|
|
LOG((LF_LOADER, LL_INFO100, "LOADER: ***%s*\t<<<Load limited due to detected deadlock, %s\n",
|
|
pFile->GetSimpleName(),
|
|
fileLoadLevelName[immediateTargetLevel-1]));
|
|
}
|
|
}
|
|
|
|
LOG((LF_LOADER, LL_INFO100, "LOADER: ***%s*\t<<<Load completed, %s\n",
|
|
pFile->GetSimpleName(),
|
|
fileLoadLevelName[pLock->GetLoadLevel()]));
|
|
|
|
}
|
|
|
|
// There may have been an error stored on the domain file by another thread, or from a previous load
|
|
pFile->ThrowIfError(targetLevel);
|
|
|
|
// There are two normal results from the above loop.
|
|
//
|
|
// 1. We succeeded in loading the file to the current thread's load level.
|
|
// 2. We succeeded in loading the file to the current thread's load level - 1, due
|
|
// to deadlock condition with another thread loading the same assembly.
|
|
//
|
|
// Either of these are considered satisfactory results, as code inside a load must expect
|
|
// a parial load result.
|
|
//
|
|
// However, if load level elevation has occurred, then it is possible for a deadlock to
|
|
// prevent us from loading an assembly which was loading before the elevation at a radically
|
|
// lower level. In such a case, we throw an exception which transiently fails the current
|
|
// load, since it is likely we have not satisfied the caller.
|
|
// (An alternate, and possibly preferable, strategy here would be for all callers to explicitly
|
|
// specify the minimum load level acceptable and throw if not reached.)
|
|
|
|
pFile->RequireLoadLevel((FileLoadLevel)(immediateTargetLevel-1));
|
|
|
|
|
|
RETURN pFile;
|
|
}
|
|
|
|
void AppDomain::TryIncrementalLoad(DomainAssembly *pFile, FileLoadLevel workLevel, FileLoadLockHolder &lockHolder)
|
|
{
|
|
STANDARD_VM_CONTRACT;
|
|
|
|
// This is factored out so we don't call EX_TRY in a loop (EX_TRY can _alloca)
|
|
|
|
BOOL released = FALSE;
|
|
FileLoadLock* pLoadLock = lockHolder.GetValue();
|
|
|
|
EX_TRY
|
|
{
|
|
|
|
// Special case: for LoadLibrary, we cannot hold the lock during the
|
|
// actual LoadLibrary call, because we might get a callback from _CorDllMain on any
|
|
// other thread. (Note that this requires DomainAssembly's LoadLibrary to be independently threadsafe.)
|
|
|
|
if (workLevel == FILE_LOAD_LOADLIBRARY)
|
|
{
|
|
lockHolder.Release();
|
|
released = TRUE;
|
|
}
|
|
|
|
// Do the work
|
|
BOOL success = pFile->DoIncrementalLoad(workLevel);
|
|
if (released)
|
|
{
|
|
// Reobtain lock to increment level. (Note that another thread may
|
|
// have already done it which is OK.
|
|
if (pLoadLock->Acquire(workLevel))
|
|
{
|
|
// note lockHolder.Acquire isn't wired up to actually take the lock
|
|
lockHolder = pLoadLock;
|
|
released = FALSE;
|
|
}
|
|
}
|
|
|
|
if (!released)
|
|
{
|
|
// Complete the level.
|
|
if (pLoadLock->CompleteLoadLevel(workLevel, success) &&
|
|
pLoadLock->GetLoadLevel()==FILE_LOAD_DELIVER_EVENTS)
|
|
{
|
|
lockHolder.Release();
|
|
released = TRUE;
|
|
pFile->DeliverAsyncEvents();
|
|
};
|
|
}
|
|
}
|
|
EX_HOOK
|
|
{
|
|
Exception *pEx = GET_EXCEPTION();
|
|
|
|
|
|
//We will cache this error and wire this load to forever fail,
|
|
// unless the exception is transient or the file is loaded OK but just cannot execute
|
|
if (!pEx->IsTransient() && !pFile->IsLoaded())
|
|
{
|
|
|
|
if (released)
|
|
{
|
|
// Reobtain lock to increment level. (Note that another thread may
|
|
// have already done it which is OK.
|
|
if (pLoadLock->Acquire(workLevel)) // note pLockHolder->Acquire isn't wired up to actually take the lock
|
|
{
|
|
// note lockHolder.Acquire isn't wired up to actually take the lock
|
|
lockHolder = pLoadLock;
|
|
released = FALSE;
|
|
}
|
|
}
|
|
|
|
if (!released)
|
|
{
|
|
// Report the error in the lock
|
|
pLoadLock->SetError(pEx);
|
|
}
|
|
|
|
if (!EEFileLoadException::CheckType(pEx))
|
|
EEFileLoadException::Throw(pFile->GetPEAssembly(), pEx->GetHR(), pEx);
|
|
}
|
|
|
|
// Otherwise, we simply abort this load, and can retry later on.
|
|
// @todo cleanup: make sure that each level is restartable after an exception, and
|
|
// leaves no bad side effects
|
|
}
|
|
EX_END_HOOK;
|
|
}
|
|
|
|
// Checks whether the module is valid to be in the given app domain (need not be yet loaded)
|
|
CHECK AppDomain::CheckValidModule(Module * pModule)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (pModule->GetDomainAssembly() != NULL)
|
|
CHECK_OK;
|
|
|
|
CHECK_OK;
|
|
}
|
|
|
|
void AppDomain::SetupSharedStatics()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
LOG((LF_CLASSLOADER, LL_INFO10000, "STATICS: SetupSharedStatics()"));
|
|
|
|
// don't do any work in init stage. If not init only do work in non-shared case if are default domain
|
|
_ASSERTE(!g_fEEInit);
|
|
|
|
// Because we are allocating/referencing objects, need to be in cooperative mode
|
|
GCX_COOP();
|
|
|
|
DomainLocalModule *pLocalModule = CoreLibBinder::GetModule()->GetDomainLocalModule();
|
|
|
|
// This is a convenient place to initialize String.Empty.
|
|
// It is treated as intrinsic by the JIT as so the static constructor would never run.
|
|
// Leaving it uninitialized would confuse debuggers.
|
|
|
|
// String should not have any static constructors.
|
|
_ASSERTE(g_pStringClass->IsClassPreInited());
|
|
|
|
FieldDesc * pEmptyStringFD = CoreLibBinder::GetField(FIELD__STRING__EMPTY);
|
|
OBJECTREF* pEmptyStringHandle = (OBJECTREF*)
|
|
((TADDR)pLocalModule->GetPrecomputedGCStaticsBasePointer()+pEmptyStringFD->GetOffset());
|
|
SetObjectReference( pEmptyStringHandle, StringObject::GetEmptyString());
|
|
}
|
|
|
|
DomainAssembly * AppDomain::FindAssembly(PEAssembly * pPEAssembly, FindAssemblyOptions options/* = FindAssemblyOptions_None*/)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
const bool includeFailedToLoad = (options & FindAssemblyOptions_IncludeFailedToLoad) != 0;
|
|
|
|
if (pPEAssembly->HasHostAssembly())
|
|
{
|
|
DomainAssembly * pDA = pPEAssembly->GetHostAssembly()->GetDomainAssembly();
|
|
if (pDA != nullptr && (pDA->IsLoaded() || (includeFailedToLoad && pDA->IsError())))
|
|
{
|
|
return pDA;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)(
|
|
kIncludeLoaded |
|
|
(includeFailedToLoad ? kIncludeFailedToLoad : 0) |
|
|
kIncludeExecution));
|
|
CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
|
|
|
|
while (i.Next(pDomainAssembly.This()))
|
|
{
|
|
PEAssembly * pManifestFile = pDomainAssembly->GetPEAssembly();
|
|
if (pManifestFile &&
|
|
pManifestFile->Equals(pPEAssembly))
|
|
{
|
|
return pDomainAssembly.GetValue();
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void AppDomain::SetFriendlyName(LPCWSTR pwzFriendlyName, BOOL fDebuggerCares/*=TRUE*/)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Do all computations into a temporary until we're ensured of success
|
|
SString tmpFriendlyName;
|
|
|
|
|
|
if (pwzFriendlyName)
|
|
tmpFriendlyName.Set(pwzFriendlyName);
|
|
else
|
|
{
|
|
// If there is an assembly, try to get the name from it.
|
|
// If no assembly, but if it's the DefaultDomain, then give it a name
|
|
|
|
if (m_pRootAssembly)
|
|
{
|
|
tmpFriendlyName.SetUTF8(m_pRootAssembly->GetSimpleName());
|
|
|
|
SString::Iterator i = tmpFriendlyName.End();
|
|
if (tmpFriendlyName.FindBack(i, '.'))
|
|
tmpFriendlyName.Truncate(i);
|
|
}
|
|
else
|
|
{
|
|
tmpFriendlyName.Set(DEFAULT_DOMAIN_FRIENDLY_NAME);
|
|
}
|
|
}
|
|
|
|
tmpFriendlyName.Normalize();
|
|
|
|
|
|
m_friendlyName = tmpFriendlyName;
|
|
m_friendlyName.Normalize();
|
|
|
|
if(g_pDebugInterface)
|
|
{
|
|
// update the name in the IPC publishing block
|
|
if (SUCCEEDED(g_pDebugInterface->UpdateAppDomainEntryInIPC(this)))
|
|
{
|
|
// inform the attached debugger that the name of this appdomain has changed.
|
|
if (IsDebuggerAttached() && fDebuggerCares)
|
|
g_pDebugInterface->NameChangeEvent(this, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
LPCWSTR AppDomain::GetFriendlyName(BOOL fDebuggerCares/*=TRUE*/)
|
|
{
|
|
CONTRACT (LPCWSTR)
|
|
{
|
|
THROWS;
|
|
if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
if (m_friendlyName.IsEmpty())
|
|
SetFriendlyName(NULL, fDebuggerCares);
|
|
|
|
RETURN m_friendlyName;
|
|
}
|
|
|
|
LPCWSTR AppDomain::GetFriendlyNameForLogging()
|
|
{
|
|
CONTRACT(LPCWSTR)
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL,NULL_OK));
|
|
}
|
|
CONTRACT_END;
|
|
RETURN (m_friendlyName.IsEmpty() ?W(""):(LPCWSTR)m_friendlyName);
|
|
}
|
|
|
|
LPCWSTR AppDomain::GetFriendlyNameForDebugger()
|
|
{
|
|
CONTRACT (LPCWSTR)
|
|
{
|
|
NOTHROW;
|
|
if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL));
|
|
}
|
|
CONTRACT_END;
|
|
|
|
|
|
if (m_friendlyName.IsEmpty())
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
|
|
EX_TRY
|
|
{
|
|
SetFriendlyName(NULL);
|
|
|
|
fSuccess = TRUE;
|
|
}
|
|
EX_CATCH
|
|
{
|
|
// Gobble all exceptions.
|
|
}
|
|
EX_END_CATCH(SwallowAllExceptions);
|
|
|
|
if (!fSuccess)
|
|
{
|
|
RETURN W("");
|
|
}
|
|
}
|
|
|
|
RETURN m_friendlyName;
|
|
}
|
|
|
|
|
|
#endif // !DACCESS_COMPILE
|
|
|
|
#ifdef DACCESS_COMPILE
|
|
|
|
PVOID AppDomain::GetFriendlyNameNoSet(bool* isUtf8)
|
|
{
|
|
SUPPORTS_DAC;
|
|
|
|
if (!m_friendlyName.IsEmpty())
|
|
{
|
|
*isUtf8 = false;
|
|
return m_friendlyName.DacGetRawContent();
|
|
}
|
|
else if (m_pRootAssembly)
|
|
{
|
|
*isUtf8 = true;
|
|
return (PVOID)m_pRootAssembly->GetSimpleName();
|
|
}
|
|
else if (dac_cast<TADDR>(this) ==
|
|
dac_cast<TADDR>(SystemDomain::System()->DefaultDomain()))
|
|
{
|
|
*isUtf8 = false;
|
|
return (PVOID)DEFAULT_DOMAIN_FRIENDLY_NAME;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#endif // DACCESS_COMPILE
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
BOOL AppDomain::AddFileToCache(AssemblySpec* pSpec, PEAssembly * pPEAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pSpec));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
GCX_PREEMP();
|
|
DomainCacheCrstHolderForGCCoop holder(this);
|
|
|
|
return m_AssemblyCache.StorePEAssembly(pSpec, pPEAssembly);
|
|
}
|
|
|
|
BOOL AppDomain::AddAssemblyToCache(AssemblySpec* pSpec, DomainAssembly *pAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pSpec));
|
|
PRECONDITION(CheckPointer(pAssembly));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
GCX_PREEMP();
|
|
DomainCacheCrstHolderForGCCoop holder(this);
|
|
|
|
// !!! suppress exceptions
|
|
BOOL bRetVal = m_AssemblyCache.StoreAssembly(pSpec, pAssembly);
|
|
return bRetVal;
|
|
}
|
|
|
|
BOOL AppDomain::AddExceptionToCache(AssemblySpec* pSpec, Exception *ex)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pSpec));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (ex->IsTransient())
|
|
return TRUE;
|
|
|
|
GCX_PREEMP();
|
|
DomainCacheCrstHolderForGCCoop holder(this);
|
|
|
|
// !!! suppress exceptions
|
|
return m_AssemblyCache.StoreException(pSpec, ex);
|
|
}
|
|
|
|
void AppDomain::AddUnmanagedImageToCache(LPCWSTR libraryName, NATIVE_LIBRARY_HANDLE hMod)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(libraryName));
|
|
PRECONDITION(CheckPointer(hMod));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
DomainCacheCrstHolderForGCPreemp lock(this);
|
|
|
|
const UnmanagedImageCacheEntry *existingEntry = m_unmanagedCache.LookupPtr(libraryName);
|
|
if (existingEntry != NULL)
|
|
{
|
|
_ASSERTE(existingEntry->Handle == hMod);
|
|
return;
|
|
}
|
|
|
|
size_t len = (u16_strlen(libraryName) + 1) * sizeof(WCHAR);
|
|
AllocMemHolder<WCHAR> copiedName(GetLowFrequencyHeap()->AllocMem(S_SIZE_T(len)));
|
|
memcpy(copiedName, libraryName, len);
|
|
|
|
m_unmanagedCache.Add(UnmanagedImageCacheEntry{ copiedName, hMod });
|
|
copiedName.SuppressRelease();
|
|
}
|
|
|
|
NATIVE_LIBRARY_HANDLE AppDomain::FindUnmanagedImageInCache(LPCWSTR libraryName)
|
|
{
|
|
CONTRACT(NATIVE_LIBRARY_HANDLE)
|
|
{
|
|
THROWS;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(libraryName));
|
|
POSTCONDITION(CheckPointer(RETVAL,NULL_OK));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
DomainCacheCrstHolderForGCPreemp lock(this);
|
|
|
|
const UnmanagedImageCacheEntry *existingEntry = m_unmanagedCache.LookupPtr(libraryName);
|
|
if (existingEntry == NULL)
|
|
RETURN NULL;
|
|
|
|
RETURN existingEntry->Handle;
|
|
}
|
|
|
|
BOOL AppDomain::RemoveFileFromCache(PEAssembly * pPEAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
GC_TRIGGERS;
|
|
PRECONDITION(CheckPointer(pPEAssembly));
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
LoadLockHolder lock(this);
|
|
FileLoadLock *fileLock = (FileLoadLock *)lock->FindFileLock(pPEAssembly);
|
|
|
|
if (fileLock == NULL)
|
|
return FALSE;
|
|
|
|
VERIFY(lock->Unlink(fileLock));
|
|
|
|
fileLock->Release();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL AppDomain::RemoveAssemblyFromCache(DomainAssembly* pAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(CheckPointer(pAssembly));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
GCX_PREEMP();
|
|
DomainCacheCrstHolderForGCCoop holder(this);
|
|
|
|
return m_AssemblyCache.RemoveAssembly(pAssembly);
|
|
}
|
|
|
|
BOOL AppDomain::IsCached(AssemblySpec *pSpec)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
|
|
// Check to see if this fits our rather loose idea of a reference to CoreLib.
|
|
// If so, don't use fusion to bind it - do it ourselves.
|
|
if (pSpec->IsCoreLib())
|
|
return TRUE;
|
|
|
|
return m_AssemblyCache.Contains(pSpec);
|
|
}
|
|
|
|
PEAssembly* AppDomain::FindCachedFile(AssemblySpec* pSpec, BOOL fThrow /*=TRUE*/)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
if (fThrow) {
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
}
|
|
else {
|
|
GC_NOTRIGGER;
|
|
NOTHROW;
|
|
}
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Check to see if this fits our rather loose idea of a reference to CoreLib.
|
|
// If so, don't use fusion to bind it - do it ourselves.
|
|
if (fThrow && pSpec->IsCoreLib())
|
|
{
|
|
CONSISTENCY_CHECK(SystemDomain::System()->SystemAssembly() != NULL);
|
|
PEAssembly * pPEAssembly = SystemDomain::System()->SystemPEAssembly();
|
|
pPEAssembly->AddRef();
|
|
return pPEAssembly;
|
|
}
|
|
|
|
return m_AssemblyCache.LookupFile(pSpec, fThrow);
|
|
}
|
|
|
|
|
|
BOOL AppDomain::PostBindResolveAssembly(AssemblySpec *pPrePolicySpec,
|
|
AssemblySpec *pPostPolicySpec,
|
|
HRESULT hrBindResult,
|
|
AssemblySpec **ppFailedSpec)
|
|
{
|
|
STATIC_CONTRACT_THROWS;
|
|
STATIC_CONTRACT_GC_TRIGGERS;
|
|
PRECONDITION(CheckPointer(pPrePolicySpec));
|
|
PRECONDITION(CheckPointer(pPostPolicySpec));
|
|
PRECONDITION(CheckPointer(ppFailedSpec));
|
|
|
|
BOOL fFailure = TRUE;
|
|
*ppFailedSpec = pPrePolicySpec;
|
|
|
|
PEAssemblyHolder result;
|
|
|
|
if ((EEFileLoadException::GetFileLoadKind(hrBindResult) == kFileNotFoundException) ||
|
|
(hrBindResult == FUSION_E_REF_DEF_MISMATCH) ||
|
|
(hrBindResult == FUSION_E_INVALID_NAME))
|
|
{
|
|
result = TryResolveAssemblyUsingEvent(*ppFailedSpec);
|
|
|
|
if (result != NULL)
|
|
{
|
|
fFailure = FALSE;
|
|
|
|
// Given the post-policy resolve event construction of the CLR binder,
|
|
// chained managed resolve events can race with each other, therefore we do allow
|
|
// the adding of the result to fail. Checking for already chached specs
|
|
// is not an option as it would introduce another race window.
|
|
// The binder does a re-fetch of the
|
|
// original binding spec and therefore will not cause inconsistency here.
|
|
// For the purposes of the resolve event, failure to add to the cache still is a success.
|
|
AddFileToCache(pPrePolicySpec, result);
|
|
if (*ppFailedSpec != pPrePolicySpec)
|
|
{
|
|
AddFileToCache(pPostPolicySpec, result);
|
|
}
|
|
}
|
|
}
|
|
|
|
return fFailure;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
PEAssembly * AppDomain::BindAssemblySpec(
|
|
AssemblySpec * pSpec,
|
|
BOOL fThrowOnFileNotFound)
|
|
{
|
|
STATIC_CONTRACT_THROWS;
|
|
STATIC_CONTRACT_GC_TRIGGERS;
|
|
PRECONDITION(CheckPointer(pSpec));
|
|
PRECONDITION(pSpec->GetAppDomain() == this);
|
|
PRECONDITION(this==::GetAppDomain());
|
|
|
|
GCX_PREEMP();
|
|
|
|
BOOL fForceReThrow = FALSE;
|
|
|
|
BinderTracing::AssemblyBindOperation bindOperation(pSpec);
|
|
|
|
HRESULT hrBindResult = S_OK;
|
|
PEAssemblyHolder result;
|
|
|
|
bool isCached = false;
|
|
EX_TRY
|
|
{
|
|
isCached = IsCached(pSpec);
|
|
if (!isCached)
|
|
{
|
|
|
|
{
|
|
ReleaseHolder<BINDER_SPACE::Assembly> boundAssembly;
|
|
hrBindResult = pSpec->Bind(this, &boundAssembly);
|
|
|
|
if (boundAssembly)
|
|
{
|
|
if (SystemDomain::SystemPEAssembly() && boundAssembly->GetAssemblyName()->IsCoreLib())
|
|
{
|
|
// Avoid rebinding to another copy of CoreLib
|
|
result = SystemDomain::SystemPEAssembly();
|
|
result.SuppressRelease(); // Didn't get a refcount
|
|
}
|
|
else
|
|
{
|
|
// IsSystem on the PEAssembly should be false, even for CoreLib satellites
|
|
result = PEAssembly::Open(boundAssembly);
|
|
}
|
|
|
|
// Setup the reference to the binder, which performed the bind, into the AssemblySpec
|
|
AssemblyBinder* pBinder = result->GetAssemblyBinder();
|
|
_ASSERTE(pBinder != NULL);
|
|
pSpec->SetBinder(pBinder);
|
|
|
|
// Failure to add simply means someone else beat us to it. In that case
|
|
// the FindCachedFile call below (after catch block) will update result
|
|
// to the cached value.
|
|
AddFileToCache(pSpec, result);
|
|
}
|
|
else
|
|
{
|
|
// Don't trigger the resolve event for the CoreLib satellite assembly. A misbehaving resolve event may
|
|
// return an assembly that does not match, and this can cause recursive resource lookups during error
|
|
// reporting. The CoreLib satellite assembly is loaded from relative locations based on the culture, see
|
|
// AssemblySpec::Bind().
|
|
if (!pSpec->IsCoreLibSatellite())
|
|
{
|
|
// Trigger the resolve event also for non-throw situation.
|
|
AssemblySpec NewSpec(this);
|
|
AssemblySpec *pFailedSpec = NULL;
|
|
|
|
fForceReThrow = TRUE; // Managed resolve event handler can throw
|
|
|
|
BOOL fFailure = PostBindResolveAssembly(pSpec, &NewSpec, hrBindResult, &pFailedSpec);
|
|
|
|
if (fFailure && fThrowOnFileNotFound)
|
|
{
|
|
EEFileLoadException::Throw(pFailedSpec, COR_E_FILENOTFOUND, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EX_CATCH
|
|
{
|
|
Exception *ex = GET_EXCEPTION();
|
|
|
|
AssemblySpec NewSpec(this);
|
|
AssemblySpec *pFailedSpec = NULL;
|
|
|
|
// Let transient exceptions or managed resolve event handler exceptions propagate
|
|
if (ex->IsTransient() || fForceReThrow)
|
|
{
|
|
EX_RETHROW;
|
|
}
|
|
|
|
{
|
|
BOOL fFailure = PostBindResolveAssembly(pSpec, &NewSpec, ex->GetHR(), &pFailedSpec);
|
|
if (fFailure)
|
|
{
|
|
BOOL bFileNotFoundException =
|
|
(EEFileLoadException::GetFileLoadKind(ex->GetHR()) == kFileNotFoundException);
|
|
|
|
if (!bFileNotFoundException)
|
|
{
|
|
fFailure = AddExceptionToCache(pFailedSpec, ex);
|
|
} // else, fFailure stays TRUE
|
|
// Effectively, fFailure == bFileNotFoundException || AddExceptionToCache(pFailedSpec, ex)
|
|
|
|
// Only throw this exception if we are the first in the cache
|
|
if (fFailure)
|
|
{
|
|
// Store the failure information for DAC to read
|
|
if (IsDebuggerAttached()) {
|
|
FailedAssembly *pFailed = new FailedAssembly();
|
|
pFailed->Initialize(pFailedSpec, ex);
|
|
IfFailThrow(m_failedAssemblies.Append(pFailed));
|
|
}
|
|
|
|
if (!bFileNotFoundException || fThrowOnFileNotFound)
|
|
{
|
|
// V1.1 App-compatibility workaround. See VSW530166 if you want to whine about it.
|
|
//
|
|
// In Everett, if we failed to download an assembly because of a broken network cable,
|
|
// we returned a FileNotFoundException with a COR_E_FILENOTFOUND hr embedded inside
|
|
// (which would be exposed when marshaled to native.)
|
|
//
|
|
// In Whidbey, we now set the more appropriate INET_E_RESOURCE_NOT_FOUND hr. But
|
|
// the online/offline switch code in VSTO for Everett hardcoded a check for
|
|
// COR_E_FILENOTFOUND.
|
|
//
|
|
// So now, to keep that code from breaking, we have to remap INET_E_RESOURCE_NOT_FOUND
|
|
// back to COR_E_FILENOTFOUND. We're doing it here rather down in Fusion so as to affect
|
|
// the least number of callers.
|
|
|
|
if (ex->GetHR() == INET_E_RESOURCE_NOT_FOUND)
|
|
{
|
|
EEFileLoadException::Throw(pFailedSpec, COR_E_FILENOTFOUND, ex);
|
|
}
|
|
|
|
if (EEFileLoadException::CheckType(ex))
|
|
{
|
|
if (pFailedSpec == pSpec)
|
|
{
|
|
EX_RETHROW; //preserve the information
|
|
}
|
|
else
|
|
{
|
|
StackSString exceptionDisplayName, failedSpecDisplayName;
|
|
|
|
((EEFileLoadException*)ex)->GetName(exceptionDisplayName);
|
|
pFailedSpec->GetDisplayName(0, failedSpecDisplayName);
|
|
|
|
if (exceptionDisplayName.CompareCaseInsensitive(failedSpecDisplayName) == 0)
|
|
{
|
|
EX_RETHROW; // Throw the original exception. Otherwise, we'd throw an exception that contains the same message twice.
|
|
}
|
|
}
|
|
}
|
|
|
|
EEFileLoadException::Throw(pFailedSpec, ex->GetHR(), ex);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EX_END_CATCH(RethrowTerminalExceptions);
|
|
|
|
// Now, if it's a cacheable bind we need to re-fetch the result from the cache, as we may have been racing with another
|
|
// thread to store our result. Note that we may throw from here, if there is a cached exception.
|
|
// This will release the refcount of the current result holder (if any), and will replace
|
|
// it with a non-addref'ed result
|
|
result = FindCachedFile(pSpec);
|
|
|
|
if (result != NULL)
|
|
result->AddRef();
|
|
|
|
bindOperation.SetResult(result.GetValue(), isCached);
|
|
return result.Extract();
|
|
} // AppDomain::BindAssemblySpec
|
|
|
|
|
|
|
|
PEAssembly *AppDomain::TryResolveAssemblyUsingEvent(AssemblySpec *pSpec)
|
|
{
|
|
STATIC_CONTRACT_THROWS;
|
|
STATIC_CONTRACT_GC_TRIGGERS;
|
|
STATIC_CONTRACT_MODE_ANY;
|
|
|
|
// No assembly resolve on codebase binds
|
|
if (pSpec->GetName() == nullptr)
|
|
return nullptr;
|
|
|
|
PEAssembly *result = nullptr;
|
|
EX_TRY
|
|
{
|
|
Assembly *pAssembly = RaiseAssemblyResolveEvent(pSpec);
|
|
if (pAssembly != nullptr)
|
|
{
|
|
PEAssembly* pPEAssembly = pAssembly->GetPEAssembly();
|
|
pPEAssembly->AddRef();
|
|
result = pPEAssembly;
|
|
}
|
|
|
|
BinderTracing::ResolutionAttemptedOperation::TraceAppDomainAssemblyResolve(pSpec, result);
|
|
}
|
|
EX_HOOK
|
|
{
|
|
Exception *pEx = GET_EXCEPTION();
|
|
BinderTracing::ResolutionAttemptedOperation::TraceAppDomainAssemblyResolve(pSpec, nullptr, pEx);
|
|
if (!pEx->IsTransient())
|
|
{
|
|
AddExceptionToCache(pSpec, pEx);
|
|
if (!EEFileLoadException::CheckType(pEx))
|
|
EEFileLoadException::Throw(pSpec, pEx->GetHR(), pEx);
|
|
}
|
|
}
|
|
EX_END_HOOK;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
ULONG AppDomain::AddRef()
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
ULONG AppDomain::Release()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(m_cRef > 0);
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
ULONG cRef = InterlockedDecrement(&m_cRef);
|
|
if (!cRef)
|
|
{
|
|
_ASSERTE (m_Stage == STAGE_CREATING);
|
|
delete this;
|
|
}
|
|
return (cRef);
|
|
}
|
|
|
|
|
|
|
|
void AppDomain::RaiseLoadingAssemblyEvent(DomainAssembly *pAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
PRECONDITION(this == GetAppDomain());
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
if (pAssembly->GetPEAssembly()->IsSystem())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GCX_COOP();
|
|
FAULT_NOT_FATAL();
|
|
OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED);
|
|
|
|
EX_TRY
|
|
{
|
|
if (CoreLibBinder::GetField(FIELD__ASSEMBLYLOADCONTEXT__ASSEMBLY_LOAD)->GetStaticOBJECTREF() != NULL)
|
|
{
|
|
struct {
|
|
OBJECTREF orThis;
|
|
} gc;
|
|
gc.orThis = NULL;
|
|
|
|
ARG_SLOT args[1];
|
|
GCPROTECT_BEGIN(gc);
|
|
|
|
gc.orThis = pAssembly->GetExposedAssemblyObject();
|
|
|
|
MethodDescCallSite onAssemblyLoad(METHOD__ASSEMBLYLOADCONTEXT__ON_ASSEMBLY_LOAD);
|
|
|
|
// GetExposedAssemblyObject may cause a gc, so call this before filling args[0]
|
|
args[0] = ObjToArgSlot(gc.orThis);
|
|
|
|
onAssemblyLoad.Call(args);
|
|
|
|
GCPROTECT_END();
|
|
}
|
|
}
|
|
EX_CATCH
|
|
{
|
|
}
|
|
EX_END_CATCH(SwallowAllExceptions);
|
|
}
|
|
|
|
BOOL AppDomain::OnUnhandledException(OBJECTREF *pThrowable, BOOL isTerminating/*=TRUE*/)
|
|
{
|
|
STATIC_CONTRACT_NOTHROW;
|
|
STATIC_CONTRACT_GC_TRIGGERS;
|
|
STATIC_CONTRACT_MODE_ANY;
|
|
|
|
BOOL retVal = FALSE;
|
|
|
|
GCX_COOP();
|
|
|
|
EX_TRY
|
|
{
|
|
retVal = GetAppDomain()->RaiseUnhandledExceptionEvent(pThrowable, isTerminating);
|
|
}
|
|
EX_CATCH
|
|
{
|
|
}
|
|
EX_END_CATCH(SwallowAllExceptions) // Swallow any errors.
|
|
|
|
return retVal;
|
|
}
|
|
|
|
void AppDomain::RaiseExitProcessEvent()
|
|
{
|
|
if (!g_fEEStarted)
|
|
return;
|
|
|
|
STATIC_CONTRACT_MODE_COOPERATIVE;
|
|
STATIC_CONTRACT_THROWS;
|
|
STATIC_CONTRACT_GC_TRIGGERS;
|
|
|
|
// Only finalizer thread during shutdown can call this function.
|
|
_ASSERTE ((g_fEEShutDown&ShutDown_Finalize1) && GetThread() == FinalizerThread::GetFinalizerThread());
|
|
|
|
_ASSERTE (GetThread()->PreemptiveGCDisabled());
|
|
|
|
MethodDescCallSite onProcessExit(METHOD__APPCONTEXT__ON_PROCESS_EXIT);
|
|
onProcessExit.Call(NULL);
|
|
}
|
|
|
|
BOOL
|
|
AppDomain::RaiseUnhandledExceptionEvent(OBJECTREF *pThrowable, BOOL isTerminating)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
_ASSERTE(pThrowable != NULL && IsProtectedByGCFrame(pThrowable));
|
|
|
|
_ASSERTE(this == GetThread()->GetDomain());
|
|
|
|
OBJECTREF orDelegate = CoreLibBinder::GetField(FIELD__APPCONTEXT__UNHANDLED_EXCEPTION)->GetStaticOBJECTREF();
|
|
if (orDelegate == NULL)
|
|
return FALSE;
|
|
|
|
struct {
|
|
OBJECTREF Delegate;
|
|
OBJECTREF Sender;
|
|
} gc;
|
|
gc.Delegate = orDelegate;
|
|
gc.Sender = NULL;
|
|
|
|
GCPROTECT_BEGIN(gc);
|
|
if (orDelegate != NULL)
|
|
{
|
|
DistributeUnhandledExceptionReliably(&gc.Delegate, &gc.Sender, pThrowable, isTerminating);
|
|
}
|
|
GCPROTECT_END();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DefaultAssemblyBinder *AppDomain::CreateDefaultBinder()
|
|
{
|
|
CONTRACT(DefaultAssemblyBinder *)
|
|
{
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
if (!m_pDefaultBinder)
|
|
{
|
|
ETWOnStartup (FusionAppCtx_V1, FusionAppCtxEnd_V1);
|
|
|
|
GCX_PREEMP();
|
|
|
|
// Initialize the assembly binder for the default context loads for CoreCLR.
|
|
IfFailThrow(BINDER_SPACE::AssemblyBinderCommon::CreateDefaultBinder(&m_pDefaultBinder));
|
|
}
|
|
|
|
RETURN m_pDefaultBinder;
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//
|
|
// AppDomain::IsDebuggerAttached - is a debugger attached to this process
|
|
//
|
|
// Arguments:
|
|
// None
|
|
//
|
|
// Return Value:
|
|
// TRUE if a debugger is attached to this process, FALSE otherwise.
|
|
//
|
|
// Notes:
|
|
// This is identical to CORDebuggerAttached. This exists idependantly for legacy reasons - we used to
|
|
// support attaching to individual AppDomains. This should probably go away eventually.
|
|
//
|
|
|
|
BOOL AppDomain::IsDebuggerAttached()
|
|
{
|
|
LIMITED_METHOD_CONTRACT;
|
|
|
|
if (CORDebuggerAttached())
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUGGING_SUPPORTED
|
|
|
|
// This is called from the debugger to request notification events from
|
|
// Assemblies, Modules, Types in this appdomain.
|
|
BOOL AppDomain::NotifyDebuggerLoad(int flags, BOOL attaching)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
BOOL result = FALSE;
|
|
|
|
if (!attaching && !IsDebuggerAttached())
|
|
return FALSE;
|
|
|
|
AssemblyIterator i;
|
|
|
|
// Attach to our assemblies
|
|
LOG((LF_CORDB, LL_INFO100, "AD::NDA: Iterating assemblies\n"));
|
|
i = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution));
|
|
CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
|
|
while (i.Next(pDomainAssembly.This()))
|
|
{
|
|
result = (pDomainAssembly->NotifyDebuggerLoad(flags, attaching) ||
|
|
result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void AppDomain::NotifyDebuggerUnload()
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
if (!IsDebuggerAttached())
|
|
return;
|
|
|
|
LOG((LF_CORDB, LL_INFO10, "AD::NDD domain %#08x\n", this));
|
|
|
|
LOG((LF_CORDB, LL_INFO100, "AD::NDD: Interating domain bound assemblies\n"));
|
|
AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution));
|
|
CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
|
|
|
|
// Detach from our assemblies
|
|
while (i.Next(pDomainAssembly.This()))
|
|
{
|
|
LOG((LF_CORDB, LL_INFO100, "AD::NDD: Iterating assemblies\n"));
|
|
pDomainAssembly->NotifyDebuggerUnload();
|
|
}
|
|
}
|
|
#endif // DEBUGGING_SUPPORTED
|
|
|
|
|
|
#ifdef FEATURE_COMWRAPPERS
|
|
|
|
RCWRefCache *AppDomain::GetRCWRefCache()
|
|
{
|
|
CONTRACT(RCWRefCache*)
|
|
{
|
|
THROWS;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL));
|
|
}
|
|
CONTRACT_END;
|
|
|
|
if (!m_pRCWRefCache) {
|
|
NewHolder<RCWRefCache> pRCWRefCache = new RCWRefCache(this);
|
|
if (InterlockedCompareExchangeT(&m_pRCWRefCache, (RCWRefCache *)pRCWRefCache, NULL) == NULL)
|
|
{
|
|
pRCWRefCache.SuppressRelease();
|
|
}
|
|
}
|
|
RETURN m_pRCWRefCache;
|
|
}
|
|
#endif // FEATURE_COMWRAPPERS
|
|
|
|
#ifdef FEATURE_COMINTEROP
|
|
|
|
RCWCache *AppDomain::CreateRCWCache()
|
|
{
|
|
CONTRACT(RCWCache*)
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
POSTCONDITION(CheckPointer(RETVAL));
|
|
}
|
|
CONTRACT_END;
|
|
|
|
// Initialize the global RCW cleanup list here as well. This is so that it
|
|
// it guaranteed to exist if any RCW's are created, but it is not created
|
|
// unconditionally.
|
|
if (!g_pRCWCleanupList)
|
|
{
|
|
SystemDomain::LockHolder lh;
|
|
|
|
if (!g_pRCWCleanupList)
|
|
g_pRCWCleanupList = new RCWCleanupList();
|
|
}
|
|
_ASSERTE(g_pRCWCleanupList);
|
|
|
|
{
|
|
BaseDomain::LockHolder lh(this);
|
|
|
|
if (!m_pRCWCache)
|
|
m_pRCWCache = new RCWCache(this);
|
|
}
|
|
|
|
RETURN m_pRCWCache;
|
|
}
|
|
|
|
void AppDomain::ReleaseRCWs(LPVOID pCtxCookie)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
if (m_pRCWCache)
|
|
m_pRCWCache->ReleaseWrappersWorker(pCtxCookie);
|
|
}
|
|
|
|
void AppDomain::DetachRCWs()
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
if (m_pRCWCache)
|
|
m_pRCWCache->DetachWrappersWorker();
|
|
}
|
|
|
|
#endif // FEATURE_COMINTEROP
|
|
|
|
void AppDomain::ExceptionUnwind(Frame *pFrame)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
DISABLED(GC_TRIGGERS); // EEResourceException
|
|
DISABLED(THROWS); // EEResourceException
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::ExceptionUnwind for %8.8x\n", pFrame));
|
|
Thread *pThread = GetThread();
|
|
|
|
LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::ExceptionUnwind: not first transition or abort\n"));
|
|
}
|
|
|
|
|
|
#endif // !DACCESS_COMPILE
|
|
|
|
DWORD DomainLocalModule::GetClassFlags(MethodTable* pMT, DWORD iClassIndex /*=(DWORD)-1*/)
|
|
{
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
} CONTRACTL_END;
|
|
|
|
{
|
|
CONSISTENCY_CHECK(GetDomainAssembly()->GetModule() == pMT->GetModuleForStatics());
|
|
}
|
|
|
|
if (pMT->IsDynamicStatics())
|
|
{
|
|
_ASSERTE(!pMT->ContainsGenericVariables());
|
|
DWORD dynamicClassID = pMT->GetModuleDynamicEntryID();
|
|
if(m_aDynamicEntries <= dynamicClassID)
|
|
return FALSE;
|
|
return (m_pDynamicClassTable[dynamicClassID].m_dwFlags);
|
|
}
|
|
else
|
|
{
|
|
if (iClassIndex == (DWORD)-1)
|
|
iClassIndex = pMT->GetClassIndex();
|
|
return GetPrecomputedStaticsClassData()[iClassIndex];
|
|
}
|
|
}
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
|
|
void DomainLocalModule::SetClassInitialized(MethodTable* pMT)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
BaseDomain::DomainLocalBlockLockHolder lh(AppDomain::GetCurrentDomain());
|
|
|
|
_ASSERTE(!IsClassInitialized(pMT));
|
|
_ASSERTE(!IsClassInitError(pMT));
|
|
|
|
SetClassFlags(pMT, ClassInitFlags::INITIALIZED_FLAG);
|
|
}
|
|
|
|
void DomainLocalModule::SetClassInitError(MethodTable* pMT)
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
|
|
BaseDomain::DomainLocalBlockLockHolder lh(AppDomain::GetCurrentDomain());
|
|
|
|
SetClassFlags(pMT, ClassInitFlags::ERROR_FLAG);
|
|
}
|
|
|
|
void DomainLocalModule::SetClassFlags(MethodTable* pMT, DWORD dwFlags)
|
|
{
|
|
CONTRACTL {
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
PRECONDITION(GetDomainAssembly()->GetModule() == pMT->GetModuleForStatics());
|
|
// Assumes BaseDomain::DomainLocalBlockLockHolder is taken
|
|
PRECONDITION(AppDomain::GetCurrentDomain()->OwnDomainLocalBlockLock());
|
|
} CONTRACTL_END;
|
|
|
|
if (pMT->IsDynamicStatics())
|
|
{
|
|
_ASSERTE(!pMT->ContainsGenericVariables());
|
|
DWORD dwID = pMT->GetModuleDynamicEntryID();
|
|
EnsureDynamicClassIndex(dwID);
|
|
m_pDynamicClassTable[dwID].m_dwFlags |= dwFlags;
|
|
}
|
|
else
|
|
{
|
|
GetPrecomputedStaticsClassData()[pMT->GetClassIndex()] |= dwFlags;
|
|
}
|
|
}
|
|
|
|
void DomainLocalModule::EnsureDynamicClassIndex(DWORD dwID)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
// Assumes BaseDomain::DomainLocalBlockLockHolder is taken
|
|
PRECONDITION(AppDomain::GetCurrentDomain()->OwnDomainLocalBlockLock());
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
SIZE_T oldDynamicEntries = m_aDynamicEntries.Load();
|
|
|
|
if (dwID < oldDynamicEntries)
|
|
{
|
|
_ASSERTE(m_pDynamicClassTable.Load() != NULL);
|
|
return;
|
|
}
|
|
|
|
SIZE_T aDynamicEntries = max(16, oldDynamicEntries);
|
|
while (aDynamicEntries <= dwID)
|
|
{
|
|
aDynamicEntries *= 2;
|
|
}
|
|
|
|
DynamicClassInfo* pNewDynamicClassTable;
|
|
pNewDynamicClassTable = (DynamicClassInfo*)
|
|
(void*)GetDomainAssembly()->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(
|
|
S_SIZE_T(sizeof(DynamicClassInfo)) * S_SIZE_T(aDynamicEntries));
|
|
|
|
if (oldDynamicEntries != 0)
|
|
{
|
|
memcpy((void*)pNewDynamicClassTable, m_pDynamicClassTable, sizeof(DynamicClassInfo) * oldDynamicEntries);
|
|
}
|
|
|
|
// Note: Memory allocated on loader heap is zero filled
|
|
// memset(pNewDynamicClassTable + m_aDynamicEntries, 0, (aDynamicEntries - m_aDynamicEntries) * sizeof(DynamicClassInfo));
|
|
|
|
_ASSERTE(m_aDynamicEntries%2 == 0);
|
|
|
|
// Commit new dynamic table. The lock-free helpers depend on the order.
|
|
MemoryBarrier();
|
|
m_pDynamicClassTable = pNewDynamicClassTable;
|
|
MemoryBarrier();
|
|
m_aDynamicEntries = aDynamicEntries;
|
|
}
|
|
|
|
void DomainLocalModule::AllocateDynamicClass(MethodTable *pMT)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
// Assumes BaseDomain::DomainLocalBlockLockHolder is taken
|
|
PRECONDITION(AppDomain::GetCurrentDomain()->OwnDomainLocalBlockLock());
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
_ASSERTE(!pMT->ContainsGenericVariables());
|
|
_ASSERTE(!pMT->IsSharedByGenericInstantiations());
|
|
_ASSERTE(GetDomainAssembly()->GetModule() == pMT->GetModuleForStatics());
|
|
_ASSERTE(pMT->IsDynamicStatics());
|
|
|
|
DWORD dynamicEntryIDIndex = pMT->GetModuleDynamicEntryID();
|
|
|
|
EnsureDynamicClassIndex(dynamicEntryIDIndex);
|
|
|
|
_ASSERTE(m_aDynamicEntries > dynamicEntryIDIndex);
|
|
|
|
EEClass *pClass = pMT->GetClass();
|
|
|
|
DWORD dwStaticBytes = pClass->GetNonGCRegularStaticFieldBytes();
|
|
DWORD dwNumHandleStatics = pClass->GetNumHandleRegularStatics();
|
|
|
|
_ASSERTE(!IsClassAllocated(pMT));
|
|
_ASSERTE(!IsClassInitialized(pMT));
|
|
_ASSERTE(!IsClassInitError(pMT));
|
|
|
|
DynamicEntry *pDynamicStatics = m_pDynamicClassTable[dynamicEntryIDIndex].m_pDynamicEntry;
|
|
|
|
// We need this check because maybe a class had a cctor but no statics
|
|
if (dwStaticBytes > 0 || dwNumHandleStatics > 0)
|
|
{
|
|
if (pDynamicStatics == NULL)
|
|
{
|
|
LoaderHeap * pLoaderAllocator = GetDomainAssembly()->GetLoaderAllocator()->GetHighFrequencyHeap();
|
|
|
|
if (pMT->Collectible())
|
|
{
|
|
pDynamicStatics = (DynamicEntry*)(void*)pLoaderAllocator->AllocMem(S_SIZE_T(sizeof(CollectibleDynamicEntry)));
|
|
}
|
|
else
|
|
{
|
|
SIZE_T dynamicEntrySize = DynamicEntry::GetOffsetOfDataBlob() + dwStaticBytes;
|
|
|
|
#ifdef FEATURE_64BIT_ALIGNMENT
|
|
// Allocate memory with extra alignment only if it is really necessary
|
|
if (dwStaticBytes >= MAX_PRIMITIVE_FIELD_SIZE)
|
|
{
|
|
static_assert_no_msg(sizeof(NormalDynamicEntry) % MAX_PRIMITIVE_FIELD_SIZE == 0);
|
|
pDynamicStatics = (DynamicEntry*)(void*)pLoaderAllocator->AllocAlignedMem(dynamicEntrySize, MAX_PRIMITIVE_FIELD_SIZE);
|
|
}
|
|
else
|
|
#endif
|
|
pDynamicStatics = (DynamicEntry*)(void*)pLoaderAllocator->AllocMem(S_SIZE_T(dynamicEntrySize));
|
|
}
|
|
|
|
// Note: Memory allocated on loader heap is zero filled
|
|
|
|
m_pDynamicClassTable[dynamicEntryIDIndex].m_pDynamicEntry = pDynamicStatics;
|
|
}
|
|
|
|
if (pMT->Collectible() && (dwStaticBytes != 0))
|
|
{
|
|
GCX_COOP();
|
|
OBJECTREF nongcStaticsArray = NULL;
|
|
GCPROTECT_BEGIN(nongcStaticsArray);
|
|
#ifdef FEATURE_64BIT_ALIGNMENT
|
|
// Allocate memory with extra alignment only if it is really necessary
|
|
if (dwStaticBytes >= MAX_PRIMITIVE_FIELD_SIZE)
|
|
nongcStaticsArray = AllocatePrimitiveArray(ELEMENT_TYPE_I8, (dwStaticBytes + (sizeof(CLR_I8)-1)) / (sizeof(CLR_I8)));
|
|
else
|
|
#endif
|
|
nongcStaticsArray = AllocatePrimitiveArray(ELEMENT_TYPE_U1, dwStaticBytes);
|
|
((CollectibleDynamicEntry *)pDynamicStatics)->m_hNonGCStatics = GetDomainAssembly()->GetModule()->GetLoaderAllocator()->AllocateHandle(nongcStaticsArray);
|
|
GCPROTECT_END();
|
|
}
|
|
if (dwNumHandleStatics > 0)
|
|
{
|
|
if (!pMT->Collectible())
|
|
{
|
|
GetAppDomain()->AllocateStaticFieldObjRefPtrs(dwNumHandleStatics,
|
|
&((NormalDynamicEntry *)pDynamicStatics)->m_pGCStatics);
|
|
}
|
|
else
|
|
{
|
|
GCX_COOP();
|
|
OBJECTREF gcStaticsArray = NULL;
|
|
GCPROTECT_BEGIN(gcStaticsArray);
|
|
gcStaticsArray = AllocateObjectArray(dwNumHandleStatics, g_pObjectClass);
|
|
((CollectibleDynamicEntry *)pDynamicStatics)->m_hGCStatics = GetDomainAssembly()->GetModule()->GetLoaderAllocator()->AllocateHandle(gcStaticsArray);
|
|
GCPROTECT_END();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DomainLocalModule::PopulateClass(MethodTable *pMT)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
_ASSERTE(!pMT->ContainsGenericVariables());
|
|
|
|
// <todo> the only work actually done here for non-dynamics is the freezing related work.
|
|
// See if we can eliminate this and make this a dynamic-only path </todo>
|
|
DWORD iClassIndex = pMT->GetClassIndex();
|
|
|
|
if (!IsClassAllocated(pMT, iClassIndex))
|
|
{
|
|
BaseDomain::DomainLocalBlockLockHolder lh(AppDomain::GetCurrentDomain());
|
|
|
|
if (!IsClassAllocated(pMT, iClassIndex))
|
|
{
|
|
// Allocate dynamic space if necessary
|
|
if (pMT->IsDynamicStatics())
|
|
AllocateDynamicClass(pMT);
|
|
|
|
// determine flags to set on the statics block
|
|
DWORD dwFlags = ClassInitFlags::ALLOCATECLASS_FLAG;
|
|
|
|
if (!pMT->HasClassConstructor() && !pMT->HasBoxedRegularStatics())
|
|
{
|
|
_ASSERTE(!IsClassInitialized(pMT));
|
|
_ASSERTE(!IsClassInitError(pMT));
|
|
dwFlags |= ClassInitFlags::INITIALIZED_FLAG;
|
|
}
|
|
|
|
if (pMT->Collectible())
|
|
{
|
|
dwFlags |= ClassInitFlags::COLLECTIBLE_FLAG;
|
|
}
|
|
|
|
// Set all flags at the same time to avoid races
|
|
SetClassFlags(pMT, dwFlags);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
DomainAssembly* AppDomain::RaiseTypeResolveEventThrowing(DomainAssembly* pAssembly, LPCSTR szName, ASSEMBLYREF *pResultingAssemblyRef)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
MODE_ANY;
|
|
GC_TRIGGERS;
|
|
THROWS;
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED);
|
|
|
|
DomainAssembly* pResolvedAssembly = NULL;
|
|
_ASSERTE(strcmp(szName, g_AppDomainClassName));
|
|
|
|
GCX_COOP();
|
|
|
|
struct {
|
|
OBJECTREF AssemblyRef;
|
|
STRINGREF str;
|
|
} gc;
|
|
gc.AssemblyRef = NULL;
|
|
gc.str = NULL;
|
|
|
|
GCPROTECT_BEGIN(gc);
|
|
|
|
if (pAssembly != NULL)
|
|
gc.AssemblyRef = pAssembly->GetExposedAssemblyObject();
|
|
|
|
MethodDescCallSite onTypeResolve(METHOD__ASSEMBLYLOADCONTEXT__ON_TYPE_RESOLVE);
|
|
|
|
gc.str = StringObject::NewString(szName);
|
|
ARG_SLOT args[2] =
|
|
{
|
|
ObjToArgSlot(gc.AssemblyRef),
|
|
ObjToArgSlot(gc.str)
|
|
};
|
|
ASSEMBLYREF ResultingAssemblyRef = (ASSEMBLYREF) onTypeResolve.Call_RetOBJECTREF(args);
|
|
|
|
if (ResultingAssemblyRef != NULL)
|
|
{
|
|
pResolvedAssembly = ResultingAssemblyRef->GetDomainAssembly();
|
|
|
|
if (pResultingAssemblyRef)
|
|
*pResultingAssemblyRef = ResultingAssemblyRef;
|
|
else
|
|
{
|
|
if (pResolvedAssembly->IsCollectible())
|
|
{
|
|
COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleBoundNonCollectible"));
|
|
}
|
|
}
|
|
}
|
|
GCPROTECT_END();
|
|
|
|
return pResolvedAssembly;
|
|
}
|
|
|
|
|
|
Assembly* AppDomain::RaiseResourceResolveEvent(DomainAssembly* pAssembly, LPCSTR szName)
|
|
{
|
|
CONTRACT(Assembly*)
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
Assembly* pResolvedAssembly = NULL;
|
|
|
|
GCX_COOP();
|
|
|
|
struct {
|
|
OBJECTREF AssemblyRef;
|
|
STRINGREF str;
|
|
} gc;
|
|
gc.AssemblyRef = NULL;
|
|
gc.str = NULL;
|
|
|
|
GCPROTECT_BEGIN(gc);
|
|
|
|
if (pAssembly != NULL)
|
|
gc.AssemblyRef=pAssembly->GetExposedAssemblyObject();
|
|
|
|
MethodDescCallSite onResourceResolve(METHOD__ASSEMBLYLOADCONTEXT__ON_RESOURCE_RESOLVE);
|
|
gc.str = StringObject::NewString(szName);
|
|
ARG_SLOT args[2] =
|
|
{
|
|
ObjToArgSlot(gc.AssemblyRef),
|
|
ObjToArgSlot(gc.str)
|
|
};
|
|
ASSEMBLYREF ResultingAssemblyRef = (ASSEMBLYREF) onResourceResolve.Call_RetOBJECTREF(args);
|
|
if (ResultingAssemblyRef != NULL)
|
|
{
|
|
pResolvedAssembly = ResultingAssemblyRef->GetAssembly();
|
|
if (pResolvedAssembly->IsCollectible())
|
|
{
|
|
COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleAssemblyResolve"));
|
|
}
|
|
}
|
|
GCPROTECT_END();
|
|
|
|
RETURN pResolvedAssembly;
|
|
}
|
|
|
|
|
|
Assembly *
|
|
AppDomain::RaiseAssemblyResolveEvent(
|
|
AssemblySpec * pSpec)
|
|
{
|
|
CONTRACT(Assembly*)
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
|
|
INJECT_FAULT(COMPlusThrowOM(););
|
|
}
|
|
CONTRACT_END;
|
|
|
|
StackSString ssName;
|
|
pSpec->GetDisplayName(0, ssName);
|
|
|
|
// Elevate threads allowed loading level. This allows the host to load an assembly even in a restricted
|
|
// condition. Note, however, that this exposes us to possible recursion failures, if the host tries to
|
|
// load the assemblies currently being loaded. (Such cases would then throw an exception.)
|
|
|
|
OVERRIDE_LOAD_LEVEL_LIMIT(FILE_ACTIVE);
|
|
OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED);
|
|
|
|
GCX_COOP();
|
|
|
|
Assembly* pAssembly = NULL;
|
|
|
|
struct {
|
|
OBJECTREF AssemblyRef;
|
|
STRINGREF str;
|
|
} gc;
|
|
gc.AssemblyRef = NULL;
|
|
gc.str = NULL;
|
|
|
|
GCPROTECT_BEGIN(gc);
|
|
{
|
|
if (pSpec->GetParentAssembly() != NULL)
|
|
{
|
|
gc.AssemblyRef=pSpec->GetParentAssembly()->GetExposedAssemblyObject();
|
|
}
|
|
|
|
MethodDescCallSite onAssemblyResolve(METHOD__ASSEMBLYLOADCONTEXT__ON_ASSEMBLY_RESOLVE);
|
|
|
|
gc.str = StringObject::NewString(ssName);
|
|
ARG_SLOT args[2] = {
|
|
ObjToArgSlot(gc.AssemblyRef),
|
|
ObjToArgSlot(gc.str)
|
|
};
|
|
|
|
ASSEMBLYREF ResultingAssemblyRef = (ASSEMBLYREF) onAssemblyResolve.Call_RetOBJECTREF(args);
|
|
|
|
if (ResultingAssemblyRef != NULL)
|
|
{
|
|
pAssembly = ResultingAssemblyRef->GetAssembly();
|
|
if (pAssembly->IsCollectible())
|
|
{
|
|
COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleAssemblyResolve"));
|
|
}
|
|
}
|
|
}
|
|
GCPROTECT_END();
|
|
|
|
if (pAssembly != NULL)
|
|
{
|
|
// Check that the public key token matches the one specified in the spec
|
|
// MatchPublicKeys throws as appropriate
|
|
pSpec->MatchPublicKeys(pAssembly);
|
|
}
|
|
|
|
RETURN pAssembly;
|
|
} // AppDomain::RaiseAssemblyResolveEvent
|
|
|
|
enum WorkType
|
|
{
|
|
WT_UnloadDomain = 0x1,
|
|
WT_ThreadAbort = 0x2,
|
|
WT_FinalizerThread = 0x4
|
|
};
|
|
|
|
static Volatile<DWORD> s_WorkType = 0;
|
|
|
|
void SystemDomain::ProcessDelayedUnloadLoaderAllocators()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_COOPERATIVE;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
int iGCRefPoint=GCHeapUtilities::GetGCHeap()->CollectionCount(GCHeapUtilities::GetGCHeap()->GetMaxGeneration());
|
|
if (GCHeapUtilities::GetGCHeap()->IsConcurrentGCInProgress())
|
|
iGCRefPoint--;
|
|
|
|
LoaderAllocator * pAllocatorsToDelete = NULL;
|
|
|
|
{
|
|
CrstHolder lh(&m_DelayedUnloadCrst);
|
|
|
|
LoaderAllocator ** ppAllocator=&m_pDelayedUnloadListOfLoaderAllocators;
|
|
while (*ppAllocator!= NULL)
|
|
{
|
|
LoaderAllocator * pAllocator = *ppAllocator;
|
|
if (0 < iGCRefPoint - pAllocator->GetGCRefPoint())
|
|
{
|
|
*ppAllocator = pAllocator->m_pLoaderAllocatorDestroyNext;
|
|
|
|
pAllocator->m_pLoaderAllocatorDestroyNext = pAllocatorsToDelete;
|
|
pAllocatorsToDelete = pAllocator;
|
|
}
|
|
else
|
|
{
|
|
ppAllocator = &pAllocator->m_pLoaderAllocatorDestroyNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete collected loader allocators on the finalizer thread. We cannot offload it to appdomain unload thread because of
|
|
// there is not guaranteed to be one, and it is not that expensive operation anyway.
|
|
while (pAllocatorsToDelete != NULL)
|
|
{
|
|
LoaderAllocator * pAllocator = pAllocatorsToDelete;
|
|
pAllocatorsToDelete = pAllocator->m_pLoaderAllocatorDestroyNext;
|
|
delete pAllocator;
|
|
}
|
|
}
|
|
|
|
|
|
void AppDomain::EnumStaticGCRefs(promote_func* fn, ScanContext* sc)
|
|
{
|
|
CONTRACT_VOID
|
|
{
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
}
|
|
CONTRACT_END;
|
|
|
|
_ASSERTE(GCHeapUtilities::IsGCInProgress() &&
|
|
GCHeapUtilities::IsServerHeap() &&
|
|
IsGCSpecialThread());
|
|
|
|
if (m_pPinnedHeapHandleTable != nullptr)
|
|
{
|
|
m_pPinnedHeapHandleTable->EnumStaticGCRefs(fn, sc);
|
|
}
|
|
|
|
RETURN;
|
|
}
|
|
|
|
#endif // !DACCESS_COMPILE
|
|
|
|
//------------------------------------------------------------------------
|
|
PTR_LoaderAllocator BaseDomain::GetLoaderAllocator()
|
|
{
|
|
WRAPPER_NO_CONTRACT;
|
|
return SystemDomain::GetGlobalLoaderAllocator(); // The one and only domain is not unloadable
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
UINT32 BaseDomain::GetTypeID(PTR_MethodTable pMT) {
|
|
CONTRACTL {
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
} CONTRACTL_END;
|
|
|
|
return m_typeIDMap.GetTypeID(pMT, true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Returns the ID of the type if found. If not found, returns INVALID_TYPE_ID
|
|
UINT32 BaseDomain::LookupTypeID(PTR_MethodTable pMT)
|
|
{
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
WRAPPER(GC_TRIGGERS);
|
|
} CONTRACTL_END;
|
|
|
|
return m_typeIDMap.LookupTypeID(pMT);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
PTR_MethodTable BaseDomain::LookupType(UINT32 id) {
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
WRAPPER(GC_TRIGGERS);
|
|
CONSISTENCY_CHECK(id != TYPE_ID_THIS_CLASS);
|
|
} CONTRACTL_END;
|
|
|
|
PTR_MethodTable pMT = m_typeIDMap.LookupType(id);
|
|
|
|
CONSISTENCY_CHECK(CheckPointer(pMT));
|
|
CONSISTENCY_CHECK(pMT->IsInterface());
|
|
return pMT;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
UINT32 BaseDomain::GetNonGCThreadStaticTypeIndex(PTR_MethodTable pMT)
|
|
{
|
|
CONTRACTL {
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
} CONTRACTL_END;
|
|
|
|
return m_NonGCThreadStaticBlockTypeIDMap.GetTypeID(pMT, false);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
PTR_MethodTable BaseDomain::LookupNonGCThreadStaticBlockType(UINT32 id) {
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
WRAPPER(GC_TRIGGERS);
|
|
CONSISTENCY_CHECK(id != TYPE_ID_THIS_CLASS);
|
|
} CONTRACTL_END;
|
|
|
|
PTR_MethodTable pMT = m_NonGCThreadStaticBlockTypeIDMap.LookupType(id);
|
|
|
|
CONSISTENCY_CHECK(CheckPointer(pMT));
|
|
return pMT;
|
|
}
|
|
//------------------------------------------------------------------------
|
|
UINT32 BaseDomain::GetGCThreadStaticTypeIndex(PTR_MethodTable pMT)
|
|
{
|
|
CONTRACTL {
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
} CONTRACTL_END;
|
|
|
|
return m_GCThreadStaticBlockTypeIDMap.GetTypeID(pMT, false);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
PTR_MethodTable BaseDomain::LookupGCThreadStaticBlockType(UINT32 id) {
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
WRAPPER(GC_TRIGGERS);
|
|
CONSISTENCY_CHECK(id != TYPE_ID_THIS_CLASS);
|
|
} CONTRACTL_END;
|
|
|
|
PTR_MethodTable pMT = m_GCThreadStaticBlockTypeIDMap.LookupType(id);
|
|
|
|
CONSISTENCY_CHECK(CheckPointer(pMT));
|
|
return pMT;
|
|
}
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
//---------------------------------------------------------------------------------------
|
|
void BaseDomain::RemoveTypesFromTypeIDMap(LoaderAllocator* pLoaderAllocator)
|
|
{
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
} CONTRACTL_END;
|
|
|
|
m_typeIDMap.RemoveTypes(pLoaderAllocator);
|
|
}
|
|
#endif // DACCESS_COMPILE
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//
|
|
BOOL
|
|
AppDomain::AssemblyIterator::Next(
|
|
CollectibleAssemblyHolder<DomainAssembly *> * pDomainAssemblyHolder)
|
|
{
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
WRAPPER(GC_TRIGGERS); // Triggers only in MODE_COOPERATIVE (by taking the lock)
|
|
MODE_ANY;
|
|
} CONTRACTL_END;
|
|
|
|
CrstHolder ch(m_pAppDomain->GetAssemblyListLock());
|
|
return Next_Unlocked(pDomainAssemblyHolder);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------
|
|
//
|
|
// Note: Does not lock the assembly list, but locks collectible assemblies for adding references.
|
|
//
|
|
BOOL
|
|
AppDomain::AssemblyIterator::Next_Unlocked(
|
|
CollectibleAssemblyHolder<DomainAssembly *> * pDomainAssemblyHolder)
|
|
{
|
|
CONTRACTL {
|
|
NOTHROW;
|
|
GC_NOTRIGGER;
|
|
MODE_ANY;
|
|
} CONTRACTL_END;
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
_ASSERTE(m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread());
|
|
#endif
|
|
|
|
while (m_Iterator.Next())
|
|
{
|
|
// Get element from the list/iterator (without adding reference to the assembly)
|
|
DomainAssembly * pDomainAssembly = dac_cast<PTR_DomainAssembly>(m_Iterator.GetElement());
|
|
if (pDomainAssembly == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pDomainAssembly->IsError())
|
|
{
|
|
if (m_assemblyIterationFlags & kIncludeFailedToLoad)
|
|
{
|
|
*pDomainAssemblyHolder = pDomainAssembly;
|
|
return TRUE;
|
|
}
|
|
continue; // reject
|
|
}
|
|
|
|
// First, reject DomainAssemblies whose load status is not to be included in
|
|
// the enumeration
|
|
|
|
if (pDomainAssembly->IsAvailableToProfilers() &&
|
|
(m_assemblyIterationFlags & kIncludeAvailableToProfilers))
|
|
{
|
|
// The assembly has reached the state at which we would notify profilers,
|
|
// and we're supposed to include such assemblies in the enumeration. So
|
|
// don't reject it (i.e., noop here, and don't bother with the rest of
|
|
// the load status checks). Check for this first, since
|
|
// kIncludeAvailableToProfilers contains some loaded AND loading
|
|
// assemblies.
|
|
}
|
|
else if (pDomainAssembly->IsLoaded())
|
|
{
|
|
// A loaded assembly
|
|
if (!(m_assemblyIterationFlags & kIncludeLoaded))
|
|
{
|
|
continue; // reject
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A loading assembly
|
|
if (!(m_assemblyIterationFlags & kIncludeLoading))
|
|
{
|
|
continue; // reject
|
|
}
|
|
}
|
|
|
|
// Next, reject DomainAssemblies whose execution status is
|
|
// not to be included in the enumeration
|
|
|
|
// execution assembly
|
|
if (!(m_assemblyIterationFlags & kIncludeExecution))
|
|
{
|
|
continue; // reject
|
|
}
|
|
|
|
// Next, reject collectible assemblies
|
|
if (pDomainAssembly->IsCollectible())
|
|
{
|
|
if (m_assemblyIterationFlags & kExcludeCollectible)
|
|
{
|
|
_ASSERTE(!(m_assemblyIterationFlags & kIncludeCollected));
|
|
continue; // reject
|
|
}
|
|
|
|
// Un-tenured collectible assemblies should not be returned. (This can only happen in a brief
|
|
// window during collectible assembly creation. No thread should need to have a pointer
|
|
// to the just allocated DomainAssembly at this stage.)
|
|
if (!pDomainAssembly->GetAssembly()->GetModule()->IsTenured())
|
|
{
|
|
continue; // reject
|
|
}
|
|
|
|
if (pDomainAssembly->GetLoaderAllocator()->AddReferenceIfAlive())
|
|
{ // The assembly is alive
|
|
|
|
// Set the holder value (incl. increasing ref-count)
|
|
*pDomainAssemblyHolder = pDomainAssembly;
|
|
|
|
// Now release the reference we took in the if-condition
|
|
pDomainAssembly->GetLoaderAllocator()->Release();
|
|
return TRUE;
|
|
}
|
|
// The assembly is not alive anymore (and we didn't increase its ref-count in the
|
|
// if-condition)
|
|
|
|
if (!(m_assemblyIterationFlags & kIncludeCollected))
|
|
{
|
|
continue; // reject
|
|
}
|
|
// Set the holder value to assembly with 0 ref-count without increasing the ref-count (won't
|
|
// call Release either)
|
|
pDomainAssemblyHolder->Assign(pDomainAssembly, FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
*pDomainAssemblyHolder = pDomainAssembly;
|
|
return TRUE;
|
|
}
|
|
|
|
*pDomainAssemblyHolder = NULL;
|
|
return FALSE;
|
|
} // AppDomain::AssemblyIterator::Next_Unlocked
|
|
|
|
#if !defined(DACCESS_COMPILE)
|
|
|
|
// Returns S_OK if the assembly was successfully loaded
|
|
HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToBindWithin, BINDER_SPACE::AssemblyName *pAssemblyName, DefaultAssemblyBinder *pDefaultBinder, AssemblyBinder *pBinder, BINDER_SPACE::Assembly **ppLoadedAssembly)
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
PRECONDITION(pAssemblyName != NULL);
|
|
PRECONDITION(ppLoadedAssembly != NULL);
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
// Switch to COOP mode since we are going to work with managed references
|
|
GCX_COOP();
|
|
|
|
struct
|
|
{
|
|
ASSEMBLYNAMEREF oRefAssemblyName;
|
|
ASSEMBLYREF oRefLoadedAssembly;
|
|
} _gcRefs;
|
|
|
|
ZeroMemory(&_gcRefs, sizeof(_gcRefs));
|
|
|
|
GCPROTECT_BEGIN(_gcRefs);
|
|
|
|
BINDER_SPACE::Assembly *pResolvedAssembly = NULL;
|
|
|
|
bool fResolvedAssembly = false;
|
|
BinderTracing::ResolutionAttemptedOperation tracer{pAssemblyName, 0 /*binderID*/, pManagedAssemblyLoadContextToBindWithin, hr};
|
|
|
|
// Allocate an AssemblyName managed object
|
|
_gcRefs.oRefAssemblyName = (ASSEMBLYNAMEREF) AllocateObject(CoreLibBinder::GetClass(CLASS__ASSEMBLY_NAME));
|
|
|
|
// Initialize the AssemblyName object
|
|
AssemblySpec::InitializeAssemblyNameRef(pAssemblyName, &_gcRefs.oRefAssemblyName);
|
|
|
|
bool isSatelliteAssemblyRequest = !pAssemblyName->IsNeutralCulture();
|
|
|
|
EX_TRY
|
|
{
|
|
if (pDefaultBinder != NULL)
|
|
{
|
|
// Step 2 (of CustomAssemblyBinder::BindAssemblyByName) - Invoke Load method
|
|
// This is not invoked for TPA Binder since it always returns NULL.
|
|
tracer.GoToStage(BinderTracing::ResolutionAttemptedOperation::Stage::AssemblyLoadContextLoad);
|
|
|
|
// Finally, setup arguments for invocation
|
|
MethodDescCallSite methLoadAssembly(METHOD__ASSEMBLYLOADCONTEXT__RESOLVE);
|
|
|
|
// Setup the arguments for the call
|
|
ARG_SLOT args[2] =
|
|
{
|
|
PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance
|
|
ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance
|
|
};
|
|
|
|
// Make the call
|
|
_gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methLoadAssembly.Call_RetOBJECTREF(args);
|
|
if (_gcRefs.oRefLoadedAssembly != NULL)
|
|
{
|
|
fResolvedAssembly = true;
|
|
}
|
|
|
|
hr = fResolvedAssembly ? S_OK : COR_E_FILENOTFOUND;
|
|
|
|
// Step 3 (of CustomAssemblyBinder::BindAssemblyByName)
|
|
if (!fResolvedAssembly && !isSatelliteAssemblyRequest)
|
|
{
|
|
tracer.GoToStage(BinderTracing::ResolutionAttemptedOperation::Stage::DefaultAssemblyLoadContextFallback);
|
|
|
|
// If we could not resolve the assembly using Load method, then attempt fallback with TPA Binder.
|
|
// Since TPA binder cannot fallback to itself, this fallback does not happen for binds within TPA binder.
|
|
//
|
|
// Switch to pre-emp mode before calling into the binder
|
|
GCX_PREEMP();
|
|
BINDER_SPACE::Assembly *pCoreCLRFoundAssembly = NULL;
|
|
hr = pDefaultBinder->BindUsingAssemblyName(pAssemblyName, &pCoreCLRFoundAssembly);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_ASSERTE(pCoreCLRFoundAssembly != NULL);
|
|
pResolvedAssembly = pCoreCLRFoundAssembly;
|
|
fResolvedAssembly = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fResolvedAssembly && isSatelliteAssemblyRequest)
|
|
{
|
|
// Step 4 (of CustomAssemblyBinder::BindAssemblyByName)
|
|
//
|
|
// Attempt to resolve it using the ResolveSatelliteAssembly method.
|
|
// Finally, setup arguments for invocation
|
|
tracer.GoToStage(BinderTracing::ResolutionAttemptedOperation::Stage::ResolveSatelliteAssembly);
|
|
|
|
MethodDescCallSite methResolveSateliteAssembly(METHOD__ASSEMBLYLOADCONTEXT__RESOLVESATELLITEASSEMBLY);
|
|
|
|
// Setup the arguments for the call
|
|
ARG_SLOT args[2] =
|
|
{
|
|
PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance
|
|
ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance
|
|
};
|
|
|
|
// Make the call
|
|
_gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methResolveSateliteAssembly.Call_RetOBJECTREF(args);
|
|
if (_gcRefs.oRefLoadedAssembly != NULL)
|
|
{
|
|
// Set the flag indicating we found the assembly
|
|
fResolvedAssembly = true;
|
|
}
|
|
|
|
hr = fResolvedAssembly ? S_OK : COR_E_FILENOTFOUND;
|
|
}
|
|
|
|
if (!fResolvedAssembly)
|
|
{
|
|
// Step 5 (of CustomAssemblyBinder::BindAssemblyByName)
|
|
//
|
|
// If we couldn't resolve the assembly using TPA LoadContext as well, then
|
|
// attempt to resolve it using the Resolving event.
|
|
// Finally, setup arguments for invocation
|
|
tracer.GoToStage(BinderTracing::ResolutionAttemptedOperation::Stage::AssemblyLoadContextResolvingEvent);
|
|
|
|
MethodDescCallSite methResolveUsingEvent(METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUSINGEVENT);
|
|
|
|
// Setup the arguments for the call
|
|
ARG_SLOT args[2] =
|
|
{
|
|
PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance
|
|
ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance
|
|
};
|
|
|
|
// Make the call
|
|
_gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methResolveUsingEvent.Call_RetOBJECTREF(args);
|
|
if (_gcRefs.oRefLoadedAssembly != NULL)
|
|
{
|
|
// Set the flag indicating we found the assembly
|
|
fResolvedAssembly = true;
|
|
}
|
|
|
|
hr = fResolvedAssembly ? S_OK : COR_E_FILENOTFOUND;
|
|
}
|
|
|
|
if (fResolvedAssembly && pResolvedAssembly == NULL)
|
|
{
|
|
// If we are here, assembly was successfully resolved via Load or Resolving events.
|
|
_ASSERTE(_gcRefs.oRefLoadedAssembly != NULL);
|
|
|
|
// We were able to get the assembly loaded. Now, get its name since the host could have
|
|
// performed the resolution using an assembly with different name.
|
|
DomainAssembly *pDomainAssembly = _gcRefs.oRefLoadedAssembly->GetDomainAssembly();
|
|
PEAssembly *pLoadedPEAssembly = NULL;
|
|
bool fFailLoad = false;
|
|
if (!pDomainAssembly)
|
|
{
|
|
// Reflection emitted assemblies will not have a domain assembly.
|
|
fFailLoad = true;
|
|
}
|
|
else
|
|
{
|
|
pLoadedPEAssembly = pDomainAssembly->GetPEAssembly();
|
|
if (!pLoadedPEAssembly->HasHostAssembly())
|
|
{
|
|
// Reflection emitted assemblies will not have a domain assembly.
|
|
fFailLoad = true;
|
|
}
|
|
}
|
|
|
|
// The loaded assembly's BINDER_SPACE::Assembly* is saved as HostAssembly in PEAssembly
|
|
if (fFailLoad)
|
|
{
|
|
PathString name;
|
|
pAssemblyName->GetDisplayName(name, BINDER_SPACE::AssemblyName::INCLUDE_ALL);
|
|
COMPlusThrowHR(COR_E_INVALIDOPERATION, IDS_HOST_ASSEMBLY_RESOLVER_DYNAMICALLY_EMITTED_ASSEMBLIES_UNSUPPORTED, name);
|
|
}
|
|
|
|
// For collectible assemblies, ensure that the parent loader allocator keeps the assembly's loader allocator
|
|
// alive for all its lifetime.
|
|
if (pDomainAssembly->IsCollectible())
|
|
{
|
|
LoaderAllocator *pResultAssemblyLoaderAllocator = pDomainAssembly->GetLoaderAllocator();
|
|
LoaderAllocator *pParentLoaderAllocator = pBinder->GetLoaderAllocator();
|
|
if (pParentLoaderAllocator == NULL)
|
|
{
|
|
// The AssemblyLoadContext for which we are resolving the Assembly is not collectible.
|
|
COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleBoundNonCollectible"));
|
|
}
|
|
|
|
_ASSERTE(pResultAssemblyLoaderAllocator);
|
|
pParentLoaderAllocator->EnsureReference(pResultAssemblyLoaderAllocator);
|
|
}
|
|
|
|
pResolvedAssembly = pLoadedPEAssembly->GetHostAssembly();
|
|
}
|
|
|
|
if (fResolvedAssembly)
|
|
{
|
|
_ASSERTE(pResolvedAssembly != NULL);
|
|
|
|
// Get the BINDER_SPACE::Assembly reference to return back to.
|
|
*ppLoadedAssembly = clr::SafeAddRef(pResolvedAssembly);
|
|
hr = S_OK;
|
|
|
|
tracer.SetFoundAssembly(static_cast<BINDER_SPACE::Assembly *>(pResolvedAssembly));
|
|
}
|
|
else
|
|
{
|
|
hr = COR_E_FILENOTFOUND;
|
|
}
|
|
}
|
|
EX_HOOK
|
|
{
|
|
Exception* ex = GET_EXCEPTION();
|
|
tracer.SetException(ex);
|
|
}
|
|
EX_END_HOOK
|
|
|
|
GCPROTECT_END();
|
|
|
|
return hr;
|
|
}
|
|
#endif // !defined(DACCESS_COMPILE)
|
|
|
|
//approximate size of loader data
|
|
//maintained for each assembly
|
|
#define APPROX_LOADER_DATA_PER_ASSEMBLY 8196
|
|
|
|
size_t AppDomain::EstimateSize()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
NOTHROW;
|
|
GC_TRIGGERS;
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
size_t retval = sizeof(AppDomain);
|
|
retval += GetLoaderAllocator()->EstimateSize();
|
|
//very rough estimate
|
|
retval += GetAssemblyCount() * APPROX_LOADER_DATA_PER_ASSEMBLY;
|
|
return retval;
|
|
}
|
|
|
|
#ifdef DACCESS_COMPILE
|
|
|
|
void
|
|
DomainLocalModule::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
|
|
{
|
|
SUPPORTS_DAC;
|
|
|
|
// Enumerate the DomainLocalModule itself. DLMs are allocated to be larger than
|
|
// sizeof(DomainLocalModule) to make room for ClassInit flags and non-GC statics.
|
|
// "DAC_ENUM_DTHIS()" probably does not account for this, so we might not enumerate
|
|
// all of the ClassInit flags and non-GC statics.
|
|
// sizeof(DomainLocalModule) == 0x28
|
|
DAC_ENUM_DTHIS();
|
|
|
|
if (m_pDomainAssembly.IsValid())
|
|
{
|
|
m_pDomainAssembly->EnumMemoryRegions(flags);
|
|
}
|
|
|
|
if (m_pDynamicClassTable.Load().IsValid())
|
|
{
|
|
DacEnumMemoryRegion(dac_cast<TADDR>(m_pDynamicClassTable.Load()),
|
|
m_aDynamicEntries * sizeof(DynamicClassInfo));
|
|
|
|
for (SIZE_T i = 0; i < m_aDynamicEntries; i++)
|
|
{
|
|
PTR_DynamicEntry entry = dac_cast<PTR_DynamicEntry>(m_pDynamicClassTable[i].m_pDynamicEntry.Load());
|
|
if (entry.IsValid())
|
|
{
|
|
// sizeof(DomainLocalModule::DynamicEntry) == 8
|
|
entry.EnumMem();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
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)));
|
|
}
|
|
|
|
// 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;
|
|
|
|
while (assem.Next(pDomainAssembly.This()))
|
|
{
|
|
pDomainAssembly->EnumMemoryRegions(flags);
|
|
}
|
|
}
|
|
|
|
void
|
|
SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis)
|
|
{
|
|
SUPPORTS_DAC;
|
|
if (enumThis)
|
|
{
|
|
DAC_ENUM_VTHIS();
|
|
EMEM_OUT(("MEM: %p SystemAppomain\n", dac_cast<TADDR>(this)));
|
|
}
|
|
|
|
if (flags == CLRDATA_ENUM_MEM_HEAP2)
|
|
{
|
|
GetLoaderAllocator()->EnumMemoryRegions(flags);
|
|
}
|
|
if (m_pSystemPEAssembly.IsValid())
|
|
{
|
|
m_pSystemPEAssembly->EnumMemoryRegions(flags);
|
|
}
|
|
if (m_pSystemAssembly.IsValid())
|
|
{
|
|
m_pSystemAssembly->EnumMemoryRegions(flags);
|
|
}
|
|
if (AppDomain::GetCurrentDomain())
|
|
{
|
|
AppDomain::GetCurrentDomain()->EnumMemoryRegions(flags, true);
|
|
}
|
|
}
|
|
|
|
#endif //DACCESS_COMPILE
|
|
|
|
|
|
PTR_LoaderAllocator SystemDomain::GetGlobalLoaderAllocator()
|
|
{
|
|
return PTR_LoaderAllocator(PTR_HOST_MEMBER_TADDR(SystemDomain,System(),m_GlobalAllocator));
|
|
}
|
|
|
|
#if defined(FEATURE_TYPEEQUIVALENCE)
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
TypeEquivalenceHashTable * AppDomain::GetTypeEquivalenceCache()
|
|
{
|
|
CONTRACTL
|
|
{
|
|
THROWS;
|
|
GC_TRIGGERS;
|
|
INJECT_FAULT(COMPlusThrowOM());
|
|
MODE_ANY;
|
|
}
|
|
CONTRACTL_END;
|
|
|
|
// Take the critical section all of the time in debug builds to ensure that it is safe to take
|
|
// the critical section in the unusual times when it may actually be needed in retail builds
|
|
#ifdef _DEBUG
|
|
CrstHolder ch(&m_TypeEquivalenceCrst);
|
|
#endif
|
|
|
|
if (m_pTypeEquivalenceTable.Load() == NULL)
|
|
{
|
|
#ifndef _DEBUG
|
|
CrstHolder ch(&m_TypeEquivalenceCrst);
|
|
#endif
|
|
if (m_pTypeEquivalenceTable.Load() == NULL)
|
|
{
|
|
m_pTypeEquivalenceTable = TypeEquivalenceHashTable::Create(this, /* bucket count */ 12, &m_TypeEquivalenceCrst);
|
|
}
|
|
}
|
|
return m_pTypeEquivalenceTable;
|
|
}
|
|
#endif //!DACCESS_COMPILE
|
|
|
|
#endif //FEATURE_TYPEEQUIVALENCE
|
|
|
|
#ifndef DACCESS_COMPILE
|
|
// Return native image for a given composite image file name, NULL when not found.
|
|
PTR_NativeImage AppDomain::GetNativeImage(LPCUTF8 simpleFileName)
|
|
{
|
|
CrstHolder ch(&m_nativeImageLoadCrst);
|
|
PTR_NativeImage pExistingImage;
|
|
if (m_nativeImageMap.Lookup(simpleFileName, &pExistingImage))
|
|
{
|
|
return pExistingImage;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
PTR_NativeImage AppDomain::SetNativeImage(LPCUTF8 simpleFileName, PTR_NativeImage pNativeImage)
|
|
{
|
|
CrstHolder ch(&m_nativeImageLoadCrst);
|
|
PTR_NativeImage pExistingImage;
|
|
if (m_nativeImageMap.Lookup(simpleFileName, &pExistingImage))
|
|
{
|
|
return pExistingImage;
|
|
}
|
|
m_nativeImageMap.Add(simpleFileName, pNativeImage);
|
|
return nullptr;
|
|
}
|
|
#endif//DACCESS_COMPILE
|