diff options
Diffstat (limited to 'src/debug/di/rsclass.cpp')
-rw-r--r-- | src/debug/di/rsclass.cpp | 1194 |
1 files changed, 1194 insertions, 0 deletions
diff --git a/src/debug/di/rsclass.cpp b/src/debug/di/rsclass.cpp new file mode 100644 index 0000000000..f0cdda4a0b --- /dev/null +++ b/src/debug/di/rsclass.cpp @@ -0,0 +1,1194 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** + +// +// File: class.cpp +// +//***************************************************************************** +#include "stdafx.h" + +// We have an assert in ceemain.cpp that validates this assumption +#define FIELD_OFFSET_NEW_ENC_DB 0x07FFFFFB + +#include "winbase.h" +#include "corpriv.h" + + + +//----------------------------------------------------------------------------- +// class CordbClass +// Represents a IL-Class in the debuggee, eg: List<T>, System.Console, etc +// +// Parameters: +// m - module that the class is contained in. +// classMetadataToken - metadata token for the class, scoped to module m. +//----------------------------------------------------------------------------- +CordbClass::CordbClass(CordbModule *m, mdTypeDef classMetadataToken) + : CordbBase(m->GetProcess(), classMetadataToken, enumCordbClass), + m_loadLevel(Constructed), + m_fLoadEventSent(FALSE), + m_fHasBeenUnloaded(false), + m_pModule(m), + m_token(classMetadataToken), + m_fIsValueClassKnown(false), + m_fIsValueClass(false), + m_fHasTypeParams(false), + m_continueCounterLastSync(0), + m_fCustomNotificationsEnabled(false) +{ + m_classInfo.Clear(); +} + + +/* + A list of which resources owned by this object are accounted for. + + HANDLED: + CordbModule* m_module; // Assigned w/o AddRef() + FieldData *m_fields; // Deleted in ~CordbClass + CordbHangingFieldTable m_hangingFieldsStatic; // by value, ~CHashTableAndData frees +*/ + + +//----------------------------------------------------------------------------- +// Destructor for CordbClass +//----------------------------------------------------------------------------- +CordbClass::~CordbClass() +{ + // We should have been explicitly neutered before our internal ref went to 0. + _ASSERTE(IsNeutered()); +} + +//----------------------------------------------------------------------------- +// Neutered by CordbModule +// See CordbBase::Neuter for semantics. +//----------------------------------------------------------------------------- +void CordbClass::Neuter() +{ + // Reduce the reference count on the type object for this class + m_type.Clear(); + CordbBase::Neuter(); +} + + + + +//----------------------------------------------------------------------------- +// Standard IUnknown::QI implementation. +// See IUnknown::QI for standard semantics. +//----------------------------------------------------------------------------- +HRESULT CordbClass::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugClass) + { + *pInterface = static_cast<ICorDebugClass*>(this); + } + else if (id == IID_ICorDebugClass2) + { + *pInterface = static_cast<ICorDebugClass2*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugClass*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +//----------------------------------------------------------------------------- +// Get a ICorDebugValue for a static field on this class. +// +// Parameters: +// fieldDef - metadata token for field on this class. Can not be from an +// inherited class. +// pFrame - frame used to resolve Thread-static, AppDomain-static, etc. +// ppValue - OUT: gets value of the field. +// +// Returns: +// S_OK on success. +// CORDBG_E_STATIC_VAR_NOT_AVAILABLE +//----------------------------------------------------------------------------- +HRESULT CordbClass::GetStaticFieldValue(mdFieldDef fieldDef, + ICorDebugFrame *pFrame, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + *ppValue = NULL; + BOOL fEnCHangingField = FALSE; + + + IMetaDataImport * pImport = NULL; + EX_TRY + { + pImport = GetModule()->GetMetaDataImporter(); // throws + + // Validate the token. + if (!pImport->IsValidToken(fieldDef) || (TypeFromToken(fieldDef) != mdtFieldDef)) + { + ThrowHR(E_INVALIDARG); + } + + // Make sure we have enough info about the class. + Init(); + + // Uninstantiated generics (eg, Foo<T>) don't have static data. Must use instantiated (eg Foo<int>) + // But all CordbClass instances are uninstantiated. So this should fail for all generic types. + // Normally, debuggers should be using ICorDebugType instead. + // Though in the forward compat case, they'll hit this. + if (HasTypeParams()) + { + ThrowHR(CORDBG_E_STATIC_VAR_NOT_AVAILABLE); + } + + + // Lookup the field given its metadata token. + FieldData *pFieldData; + + hr = GetFieldInfo(fieldDef, &pFieldData); + + // This field was added by EnC, need to use EnC specific code path + if (hr == CORDBG_E_ENC_HANGING_FIELD) + { + // Static fields added with EnC hang off the EnCFieldDesc + hr = GetEnCHangingField(fieldDef, + &pFieldData, + NULL); + + if (SUCCEEDED(hr)) + { + fEnCHangingField = TRUE; + } + // Note: the FieldOffset in pFieldData has been cooked to produce + // the correct address of the field in the syncBlock. + // @todo: extend Debugger_IPCEFieldData so we don't have to cook the offset here + } + + IfFailThrow(hr); + + { + Instantiation emptyInst; + + hr = CordbClass::GetStaticFieldValue2(GetModule(), + pFieldData, + fEnCHangingField, + &emptyInst, + pFrame, + ppValue); + // Let hr fall through + } + } + EX_CATCH_HRESULT(hr); + + // Translate Failure HRs. + if (pImport != NULL) + { + hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef); + } + + return hr; + +} + +//----------------------------------------------------------------------------- +// Common helper for accessing statics from both CordbClass and CordbType. +// +// Arguments: +// pModule - module containing the class +// pFieldData - field data describing the field (this is correlated to a +// mdFieldDef, but has more specific data) +// fEnCHangingField - field storage hangs off the FieldDesc for EnC +// pInst - generic instantiation. +// pFrame - frame used for context for Thread-static, AD-static, etc. +// ppValue - OUT: out parameter to get value. +// +// Returns: +// S_OK on success. +// CORDBG_E_FIELD_NOT_STATIC - if field isn't static. +// CORDBG_E_STATIC_VAR_NOT_AVAILABLE - if field storage is not available. +// Else some other failure. +/* static */ +HRESULT CordbClass::GetStaticFieldValue2(CordbModule * pModule, + FieldData * pFieldData, + BOOL fEnCHangingField, + const Instantiation * pInst, + ICorDebugFrame * pFrame, + ICorDebugValue ** ppValue) +{ + FAIL_IF_NEUTERED(pModule); + INTERNAL_SYNC_API_ENTRY(pModule->GetProcess()); + _ASSERTE((pModule->GetProcess()->GetShim() == NULL) || pModule->GetProcess()->GetSynchronized()); + HRESULT hr = S_OK; + + if (!pFieldData->m_fFldIsStatic) + { + return CORDBG_E_FIELD_NOT_STATIC; + } + + CORDB_ADDRESS pRmtStaticValue = NULL; + CordbProcess * pProcess = pModule->GetProcess(); + + if (pFieldData->m_fFldIsCollectibleStatic) + { + EX_TRY + { + pRmtStaticValue = pProcess->GetDAC()->GetCollectibleTypeStaticAddress(pFieldData->m_vmFieldDesc, + pModule->GetAppDomain()->GetADToken()); + } + EX_CATCH_HRESULT(hr); + if(FAILED(hr)) + { + return hr; + } + } + else if (!pFieldData->m_fFldIsTLS && !pFieldData->m_fFldIsContextStatic) + { + // Statics never move, so we always address them using their absolute address. + _ASSERTE(pFieldData->OkToGetOrSetStaticAddress()); + pRmtStaticValue = pFieldData->GetStaticAddress(); + } + else + { + // We've got a thread or context local static + + if( fEnCHangingField ) + { + // fEnCHangingField is set for fields added with EnC which hang off the FieldDesc. + // Thread-local and context-local statics cannot be added with EnC, so we shouldn't be here + // if this is an EnC field is thread- or context-local. + _ASSERTE(!pFieldData->m_fFldIsTLS ); + _ASSERTE(!pFieldData->m_fFldIsContextStatic ); + } + else + { + if (pFrame == NULL) + { + return E_INVALIDARG; + } + + CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame); + _ASSERTE(pRealFrame != NULL); + + // Get the thread we are working on + CordbThread * pThread = pRealFrame->m_pThread; + + EX_TRY + { + pRmtStaticValue = pProcess->GetDAC()->GetThreadOrContextStaticAddress(pFieldData->m_vmFieldDesc, + pThread->m_vmThreadToken); + } + EX_CATCH_HRESULT(hr); + if(FAILED(hr)) + { + return hr; + } + + } + } + + if (pRmtStaticValue == NULL) + { + // type probably wasn't loaded yet. + // The debugger may chose to func-eval the creation of an instance of this type and try again. + return CORDBG_E_STATIC_VAR_NOT_AVAILABLE; + } + + SigParser sigParser; + hr = S_OK; + EX_TRY + { + hr = pFieldData->GetFieldSignature(pModule, &sigParser); + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + CordbType * pType; + IfFailRet (CordbType::SigToType(pModule, &sigParser, pInst, &pType)); + + bool fIsValueClass = false; + EX_TRY + { + fIsValueClass = pType->IsValueType(); // throws + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + // Static value classes are stored as handles so that GC can deal with them properly. Thus, we need to follow the + // handle like an objectref. Do this by forcing CreateValueByType to think this is an objectref. Note: we don't do + // this for value classes that have an RVA, since they're layed out at the RVA with no handle. + bool fIsBoxed = (fIsValueClass && + !pFieldData->m_fFldIsRVA && + !pFieldData->m_fFldIsPrimitive && + !pFieldData->m_fFldIsTLS && + !pFieldData->m_fFldIsContextStatic); + + TargetBuffer remoteValue(pRmtStaticValue, CordbValue::GetSizeForType(pType, fIsBoxed ? kBoxed : kUnboxed)); + ICorDebugValue * pValue; + + EX_TRY + { + CordbValue::CreateValueByType(pModule->GetAppDomain(), + pType, + fIsBoxed, + remoteValue, + MemoryRange(NULL, 0), + NULL, + &pValue); // throws + } + EX_CATCH_HRESULT(hr); + + if (SUCCEEDED(hr)) + { + *ppValue = pValue; + } + + return hr; +} + + +//----------------------------------------------------------------------------- +// Public method to build a CordbType from a CordbClass. +// This is used to build up generic types. Eg, build: +// List<T> + { int } --> List<int> +// +// Arguments: +// elementType - element type. Either ELEMENT_TYPE_CLASS, or ELEMENT_TYPE_VALUETYPE. +// We could technically figure this out from the metadata (by looking if it derives +// from System.ValueType). +// cTypeArgs - number of elements in rgpTypeArgs array +// rgpTypeArgs - array for type args. +// ppType - OUT: out parameter to hold resulting type. +// +// Returns: +// S_OK on success. Else false. +// +HRESULT CordbClass::GetParameterizedType(CorElementType elementType, + ULONG32 cTypeArgs, + ICorDebugType * rgpTypeArgs[], + ICorDebugType ** ppType) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // Note: Do not call Init() to find out if its a VC or not. + // Rather expect the client to tell us. This means the debug client + // can describe type instantiations not yet seen in the EE. + + if ((elementType != ELEMENT_TYPE_CLASS) && (elementType != ELEMENT_TYPE_VALUETYPE)) + { + return E_INVALIDARG; + } + + // Prefast overflow check: + S_UINT32 allocSize = S_UINT32( cTypeArgs ) * S_UINT32( sizeof(CordbType *) ); + + if (allocSize.IsOverflow()) + { + return E_INVALIDARG; + } + + CordbAppDomain * pClassAppDomain = GetAppDomain(); + + // Note: casting from (ICorDebugType **) to (CordbType **) is not valid. + // Offsets may differ. Copy and validate the type array. + CordbType ** ppArgTypes = reinterpret_cast<CordbType **>(_alloca( allocSize.Value())); + + for (unsigned int i = 0; i < cTypeArgs; i++) + { + ppArgTypes[i] = static_cast<CordbType *>( rgpTypeArgs[i] ); + + CordbAppDomain * pArgAppDomain = ppArgTypes[i]->GetAppDomain(); + + if ((pArgAppDomain != NULL) && (pArgAppDomain != pClassAppDomain)) + { + return CORDBG_E_APPDOMAIN_MISMATCH; + } + } + + { + CordbType * pResultType; + + Instantiation typeInstantiation(cTypeArgs, ppArgTypes); + + HRESULT hr = CordbType::MkType(pClassAppDomain, elementType, this, &typeInstantiation, &pResultType); + + if (FAILED(hr)) + { + return hr; + } + + *ppType = pResultType; + } + + _ASSERTE(*ppType); + + if (*ppType) + { + (*ppType)->AddRef(); + } + return S_OK; +} + +//----------------------------------------------------------------------------- +// Returns true if the field is a static literal. +// In this case, the debugger should get the value from the metadata. +//----------------------------------------------------------------------------- +bool IsFieldStaticLiteral(IMetaDataImport *pImport, mdFieldDef fieldDef) +{ + DWORD dwFieldAttr; + HRESULT hr2 = pImport->GetFieldProps( + fieldDef, + NULL, + NULL, + 0, + NULL, + &dwFieldAttr, + NULL, + 0, + NULL, + NULL, + 0); + + if (SUCCEEDED(hr2) && IsFdLiteral(dwFieldAttr)) + { + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Filter to determine a more descriptive failing HResult for a field lookup. +// +// Parameters: +// hr - incoming ambiguous HR. +// pImport - metadata importer for this class. +// feildDef - field being looked up. +// +// Returns: +// hr - the incoming HR if no further HR can be determined. +// else another failing HR that it judged to be more specific that the incoming HR. +//----------------------------------------------------------------------------- +HRESULT CordbClass::PostProcessUnavailableHRESULT(HRESULT hr, + IMetaDataImport *pImport, + mdFieldDef fieldDef) +{ + CONTRACTL + { + NOTHROW; // just translates an HR. shouldn't need to throw. + } + CONTRACTL_END; + + if (hr == CORDBG_E_FIELD_NOT_AVAILABLE) + { + if (IsFieldStaticLiteral(pImport, fieldDef)) + { + return CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL; + } + } + + return hr; +} + +//----------------------------------------------------------------------------- +// Public method to get the Module that this class lives in. +// +// Parameters: +// ppModule - OUT: holds module that this class gets in. +// +// Returns: +// S_OK on success. +//----------------------------------------------------------------------------- +HRESULT CordbClass::GetModule(ICorDebugModule **ppModule) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **); + + *ppModule = static_cast<ICorDebugModule*> (m_pModule); + m_pModule->ExternalAddRef(); + + return S_OK; +} + +//----------------------------------------------------------------------------- +// Get the mdTypeDef token that this class corresponds to. +// +// Parameters: +// pTypeDef - OUT: out param to get typedef token. +// +// Returns: +// S_OK - on success. +//----------------------------------------------------------------------------- +HRESULT CordbClass::GetToken(mdTypeDef *pTypeDef) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pTypeDef, mdTypeDef *); + + _ASSERTE(TypeFromToken(m_token) == mdtTypeDef); + + *pTypeDef = m_token; + + return S_OK; +} + +//----------------------------------------------------------------------------- +// Set the JMC status on all of our member functions. +// The current implementation just uses the metadata to enumerate all +// methods and then calls SetJMCStatus on each method. +// This isn't great perf, but this should never be needed in a +// perf-critical situation. +// +// Parameters: +// fIsUserCode - true to set entire class to user code. False to set to +// non-user code. +// +// Returns: +// S_OK on success. On failure, the user-code status of the methods in the +// class is random. +//----------------------------------------------------------------------------- +HRESULT CordbClass::SetJMCStatus(BOOL fIsUserCode) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // Get the member functions via a meta data interface + CordbModule * pModule = GetModule(); + + // Ensure that our process is in a sane state. + CordbProcess * pProcess = pModule->GetProcess(); + _ASSERTE(pProcess != NULL); + + IMetaDataImport * pImport = NULL; + HCORENUM phEnum = 0; + + HRESULT hr = S_OK; + + mdMethodDef rTokens[100]; + ULONG i; + ULONG count; + + EX_TRY + { + pImport = pModule->GetMetaDataImporter(); + do + { + hr = pImport->EnumMethods(&phEnum, m_token, rTokens, NumItems(rTokens), &count); + IfFailThrow(hr); + + for (i = 0; i < count; i++) + { + RSLockHolder lockHolder(pProcess->GetProcessLock()); + // Need the ICorDebugFunction to query for JMC status. + CordbFunction * pFunction = pModule->LookupOrCreateFunctionLatestVersion(rTokens[i]); + + lockHolder.Release(); // Must release before sending an IPC event + hr = pFunction->SetJMCStatus(fIsUserCode); + IfFailThrow(hr); + } + } + while (count > 0); + + _ASSERTE(SUCCEEDED(hr)); + } + EX_CATCH_HRESULT(hr); + + if ((pImport != NULL) && (phEnum != 0)) + { + pImport->CloseEnum(phEnum); + } + + return hr; + +} + +//----------------------------------------------------------------------------- +// We have to go the the EE to find out if a class is a value +// class or not. This is because there is no flag for this, but rather +// it depends on whether the class subclasses System.ValueType (apart +// from System.Enum...). Replicating all that resoultion logic +// does not seem like a good plan. +// +// We also accept other "evidence" that the class is or isn't a VC, in +// particular: +// - It is definitely a VC if it has been used after a +// E_T_VALUETYPE in a signature. +// - It is definitely not a VC if it has been used after a +// E_T_CLASS in a signature. +// - It is definitely a VC if it has been used in combination with +// E_T_VALUETYPE in one of COM API operations that take both +// a ICorDebugClass and a CorElementType (e.g. GetParameterizedType) +// +// !!!Note the following!!!! +// - A class may still be a VC even if it has been +// used in combination with E_T_CLASS in one of COM API operations that take both +// a ICorDebugClass and a CorElementType (e.g. GetParameterizedType). +// We allow the user of the API to specify E_T_CLASS when the VC status +// is not known or is not important. +// +// Return Value: +// indicates whether this is a value-class +// +// Notes: +// Throws CORDBG_E_CLASS_NOT_LOADED or synchronization errors on failure +//----------------------------------------------------------------------------- +bool CordbClass::IsValueClass() +{ + INTERNAL_API_ENTRY(this); + THROW_IF_NEUTERED(this); + + if (!m_fIsValueClassKnown) + { + ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR); + Init(); + } + return m_fIsValueClass; +} + +//----------------------------------------------------------------------------- +// Get a CordbType for the 'this' pointer of a method in a CordbClass. +// The 'this' pointer is argument #0 in an instance method. +// +// For ReferenceTypes (ELEMENT_TYPE_CLASS), the 'this' pointer is just a +// normal reference, and so GetThisType() behaves like GetParameterizedType(). +// For ValueTypes, the 'this' pointer is a byref. +// +// Arguments: +// pInst - instantiation info (eg, the type parameters) to produce CordbType +// ppResultType - OUT: out parameter to hold outgoing CordbType. +// +// Returns: +// S_OK on success. Else failure. +// +HRESULT CordbClass::GetThisType(const Instantiation * pInst, CordbType ** ppResultType) +{ + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + // Note: We have to call Init() here to find out if it really a VC or not. + bool fIsValueClass = false; + EX_TRY + { + fIsValueClass = IsValueClass(); + } + EX_CATCH_HRESULT(hr); + + if (FAILED(hr)) + { + return hr; + } + + if (fIsValueClass) + { + CordbType *pType; + + hr = CordbType::MkType(GetAppDomain(), // OK: this E_T_VALUETYPE will be normalized by MkType + ELEMENT_TYPE_VALUETYPE, + this, + pInst, + &pType); + + if (!SUCCEEDED(hr)) + { + return hr; + } + + hr = CordbType::MkType(GetAppDomain(), ELEMENT_TYPE_BYREF, 0, pType, ppResultType); + + if (!SUCCEEDED(hr)) + { + return hr; + } + } + else + { + hr = CordbType::MkType(GetAppDomain(), ELEMENT_TYPE_CLASS, this, pInst, ppResultType); + + if (!SUCCEEDED(hr)) + { + return hr; + } + } + + return hr; +} + + +//----------------------------------------------------------------------------- +// Initialize the CordbClass. +// This will collect all the field information via the DAC, and also determine +// whether this Type is a ReferenceType or ValueType. +// +// Parameters: +// fForceInit - if true, always reinitialize. If false, may skip +// initialization if we believe we already have the info. +// +// Note: +// Throws CORDBG_E_CLASS_NOT_LOADED on failure +//----------------------------------------------------------------------------- +void CordbClass::Init(ClassLoadLevel desiredLoadLevel) +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); + + CordbProcess * pProcess = GetProcess(); + IDacDbiInterface* pDac = pProcess->GetDAC(); + + // If we've done a continue since the last time we got hanging static fields, + // we should clear out our cache, since everything may have moved. + if (m_continueCounterLastSync < GetProcess()->m_continueCounter) + { + m_hangingFieldsStatic.Clear(); + m_continueCounterLastSync = GetProcess()->m_continueCounter; + } + + if (m_loadLevel < desiredLoadLevel) + { + // reset everything + m_loadLevel = Constructed; + m_fIsValueClass = false; + m_fIsValueClassKnown = false; + m_fHasTypeParams = false; + m_classInfo.Clear(); + // @dbgtodo Microsoft inspection: declare a constant to replace badbad + m_classInfo.m_objectSize = 0xbadbad; + VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); + + // basic info load level + if(desiredLoadLevel >= BasicInfo) + { + vmTypeHandle = pDac->GetTypeHandle(m_pModule->GetRuntimeModule(), GetToken()); + SetIsValueClass(pDac->IsValueType(vmTypeHandle)); + m_fHasTypeParams = !!pDac->HasTypeParams(vmTypeHandle); + m_loadLevel = BasicInfo; + } + + // full info load level + if(desiredLoadLevel == FullInfo) + { + VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr(); + VMPTR_DomainFile vmDomainFile = m_pModule->GetRuntimeDomainFile(); + if (!vmDomainFile.IsNull()) + { + DomainFileInfo info; + pDac->GetDomainFileData(vmDomainFile, &info); + vmAppDomain = info.vmAppDomain; + } + pDac->GetClassInfo(vmAppDomain, vmTypeHandle, &m_classInfo); + + BOOL fGotUnallocatedStatic = GotUnallocatedStatic(&m_classInfo.m_fieldList); + + // if we have an unallocated static don't record that we reached FullInfo stage + // this seems pretty ugly but I don't want to bite off cleaning this up just yet + // Not saving the FullInfo stage effectively means future calls to Init() will + // re-init everything and some parts of DBI may be depending on that re-initialization + // with alternate data in order to operate correctly + if(!fGotUnallocatedStatic) + m_loadLevel = FullInfo; + } + } +} // CordbClass::Init + +// determine if any fields for a type are unallocated statics +BOOL CordbClass::GotUnallocatedStatic(DacDbiArrayList<FieldData> * pFieldList) +{ + BOOL fGotUnallocatedStatic = FALSE; + int count = 0; + while ((count < pFieldList->Count()) && !fGotUnallocatedStatic ) + { + if ((*pFieldList)[count].OkToGetOrSetStaticAddress() && + (*pFieldList)[count].GetStaticAddress() == NULL ) + { + // The address for a regular static field isn't available yet + // How can this happen? Statics appear to get allocated during domain load. + // There may be some lazieness or a race-condition involved. + fGotUnallocatedStatic = TRUE; + } + ++count; + } + return fGotUnallocatedStatic; +} // CordbClass::GotUnallocatedStatic + +/* + * FieldData::GetFieldSignature + * + * Get the field's full metadata signature. This may be cached, but for dynamic modules we'll always read it from + * the metadata. + * + * Parameters: + * pModule - pointer to the module that contains the field + * + * pSigParser - OUT: the full signature for the field. + * + * Returns: + * HRESULT for success or failure. + * + */ +HRESULT FieldData::GetFieldSignature(CordbModule *pModule, + SigParser *pSigParser) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + INTERNAL_SYNC_API_ENTRY(pModule->GetProcess()); + + HRESULT hr = S_OK; + + IMetaDataImport * pImport = pModule->GetMetaDataImporter(); // throws; + + PCCOR_SIGNATURE fieldSignature = NULL; + ULONG size = ((ULONG) -1); + + _ASSERTE(pSigParser != NULL); + + // If the module is dynamic, there had better not be a cached field signature. + _ASSERTE(!pModule->IsDynamic() || (m_fldSignatureCache == NULL)); + + // If the field signature cache is null, or if this is a dynamic module, then go read the signature from the + // matadata. We always read from the metadata for dynamic modules because our metadata blob is constantly + // getting deleted and re-allocated. If we kept a pointer to the signature, we'd end up pointing to bad data. + if (m_fldSignatureCache == NULL) + { + // Go to the metadata for all fields: previously the left-side tranferred over + // single-byte signatures as part of the field info. Since the left-side + // goes to the metadata anyway, and we already fetch plenty of other metadata, + // I don't believe that fetching it here instead of transferring it over + // is going to slow things down at all, and + // in any case will not be where the primary optimizations lie... + + IfFailRet(pImport->GetFieldProps(m_fldMetadataToken, NULL, NULL, 0, NULL, NULL, + &fieldSignature, + &size, + NULL, NULL, NULL)); + + // Point past the calling convention + CorCallingConvention conv; + + // Move pointer, + BYTE * pOldPtr = (BYTE*) fieldSignature; + conv = (CorCallingConvention) CorSigUncompressData(fieldSignature); + _ASSERTE(conv == IMAGE_CEE_CS_CALLCONV_FIELD); + size -= (ULONG) (((BYTE*) fieldSignature) - pOldPtr); // since we updated filedSignature, adjust size + + // Although the pointer will keep updating, the size should be the same. So we assert that. + _ASSERTE((m_fldSignatureCacheSize == 0) || (m_fldSignatureCacheSize == size)); + + // Cache the value for non-dynamic modules, so this is faster later. + // Since we're caching in a FieldData, we can't store the actual SigParser object. + if (!pModule->IsDynamic()) + { + m_fldSignatureCache = fieldSignature; + m_fldSignatureCacheSize = size; + } + } + else + { + // We have a cached value, so return it. Note: we should never have a cached value for a field in a dynamic + // module. + CONSISTENCY_CHECK_MSGF((!pModule->IsDynamic()), + ("We should never cache a field signature in a dynamic module! Module=%p This=%p", + pModule, this)); + + fieldSignature = m_fldSignatureCache; + size = m_fldSignatureCacheSize; + } + + _ASSERTE(fieldSignature != NULL); + _ASSERTE(size != ((ULONG) -1)); + *pSigParser = SigParser(fieldSignature, size); + return hr; +} + +// CordbClass::InitEnCFieldInfo +// Initializes an instance of EnCHangingFieldInfo. +// Arguments: +// input: fStatic - flag to indicate whether the EnC field is static +// pObject - For instance fields, the Object instance containing the the sync-block. +// For static fields (if this is being called from GetStaticFieldValue) object is NULL. +// fieldToken - token for the EnC field +// metadataToken - metadata token for this instance of CordbClass +// output: pEncField - the fields of this class will be appropriately initialized +void CordbClass::InitEnCFieldInfo(EnCHangingFieldInfo * pEncField, + BOOL fStatic, + CordbObjectValue * pObject, + mdFieldDef fieldToken, + mdTypeDef classToken) +{ + IDacDbiInterface * pInterface = GetProcess()->GetDAC(); + + if (fStatic) + { + // the field is static, we don't need any additional data + pEncField->Init(VMPTR_Object::NullPtr(), /* vmObject */ + NULL, /* offsetToVars */ + fieldToken, + ELEMENT_TYPE_MAX, + classToken, + m_pModule->GetRuntimeDomainFile()); + } + else + { + // This is an instance field, we need to pass a bunch of type information back + _ASSERTE(pObject != NULL); + + pEncField->Init(pInterface->GetObject(pObject->m_id), // VMPTR to the object instance of interest. + pObject->GetInfo().objOffsetToVars, // The offset from the beginning of the object + // to the beginning of the fields. Fields added + // with EnC don't actually reside in the object + // (they hang off the sync block instead), so + // this is used to compute the returned field + // offset (fieldData.m_fldInstanceOffset). This + // makes it appear to be an offset from the object. + // Ideally we wouldn't do any of this, and just + // explicitly deal with absolute addresses (instead + // of "offsets") for EnC hanging instance fields. + fieldToken, // Field token for the added field. + pObject->GetInfo().objTypeData.elementType, // An indication of the type of object to which + // we're adding a field (specifically, + // whether it's a value type or a class). + // This is used only for log messages, and could + // be removed. + classToken, // metadata token for the class + m_pModule->GetRuntimeDomainFile()); // Domain file for the class + } +} // CordbClass::InitFieldData + +// CordbClass::GetEnCFieldFromDac +// Get information via the DAC about a field added with Edit and Continue. +// Arguments: +// input: fStatic - flag to indicate whether the EnC field is static +// pObject - For instance fields, the Object instance containing the the sync-block. +// For static fields (if this is being called from GetStaticFieldValue) object is NULL. +// fieldToken - token for the EnC field +// output: pointer to an initialized instance of FieldData that has been added to the appropriate table +// in our cache +FieldData * CordbClass::GetEnCFieldFromDac(BOOL fStatic, + CordbObjectValue * pObject, + mdFieldDef fieldToken) +{ + EnCHangingFieldInfo encField; + mdTypeDef metadataToken; + FieldData fieldData, + * pInfo = NULL; + BOOL fDacStatic; + CordbProcess * pProcess = GetModule()->GetProcess(); + + _ASSERTE(pProcess != NULL); + IfFailThrow(GetToken(&metadataToken)); + InitEnCFieldInfo(&encField, fStatic, pObject, fieldToken, metadataToken); + + // Go get this particular field. + pProcess->GetDAC()->GetEnCHangingFieldInfo(&encField, &fieldData, &fDacStatic); + _ASSERTE(fStatic == fDacStatic); + + // Save the field results in our cache and get a stable pointer to the data + if (fStatic) + { + pInfo = m_hangingFieldsStatic.AddFieldInfo(&fieldData); + } + else + { + pInfo = pObject->GetHangingFieldTable()->AddFieldInfo(&fieldData); + } + + // We should have a fresh copy of the data (don't want to return a pointer to data on our stack) + _ASSERTE((void *)pInfo != (void *)&fieldData); + _ASSERTE(pInfo->m_fFldIsStatic == (fStatic == TRUE)); + _ASSERTE(pInfo->m_fldMetadataToken == fieldToken); + + // Pass a pointer to the data out. + return pInfo; +} // CordbClass::GetEnCFieldFromDac + +//----------------------------------------------------------------------------- +// Internal helper to get a FieldData for fields added by EnC after the type +// was loaded. Since object and MethodTable layout has already been fixed, +// such added fields are "hanging" off some other data structure. For instance +// fields, they're stored in a syncblock off the object. For static fields +// they're stored off the EnCFieldDesc. +// +// The caller must have already determined this is a hanging field (i.e. +// GetFieldInfo returned CORDBG_E_ENC_HANGING_FIELDF). +// +// Arguments: +// input: fldToken - field of interest to get. +// pObject - For instance fields, the Object instance containing the the sync-block. +// For static fields (if this is being called from GetStaticFieldValue) object is NULL. +// output: ppFieldData - the FieldData matching the fldToken. +// +// Returns: +// S_OK on success, failure code otherwise. +//----------------------------------------------------------------------------- +HRESULT CordbClass::GetEnCHangingField(mdFieldDef fldToken, + FieldData **ppFieldData, + CordbObjectValue * pObject) +{ + FAIL_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + HRESULT hr = S_OK; + _ASSERTE(pObject == NULL || !pObject->IsNeutered() ); + + if (HasTypeParams()) + { + _ASSERTE(!"EnC hanging field not yet implemented on constructed types!"); + return E_FAIL; + } + + // This must be a static field if no object was supplied + BOOL fStatic = (pObject == NULL); + + // Look for cached field information + FieldData *pInfo = NULL; + if (fStatic) + { + // Static fields should _NOT_ be cleared, since they stick around. Thus + // the separate tables. + pInfo = m_hangingFieldsStatic.GetFieldInfo(fldToken); + } + else + { + // We must get new copies each time we call continue b/c we get the + // actual Object ptr from the left side, which can move during a GC. + pInfo = pObject->GetHangingFieldTable()->GetFieldInfo(fldToken); + } + + // We've found a previously located entry + if (pInfo != NULL) + { + *ppFieldData = pInfo; + return S_OK; + } + + // Field information not already available - go get it + EX_TRY + { + + // We're not going to be able to get the instance-specific field + // if we can't get the instance. + if (!fStatic && pObject->GetInfo().objRefBad) + { + ThrowHR(CORDBG_E_INVALID_OBJECT); + } + + *ppFieldData = GetEnCFieldFromDac(fStatic, pObject, fldToken); + } + EX_CATCH_HRESULT(hr); + return hr; +} + +//----------------------------------------------------------------------------- +// Get a FieldData (which rich information, including details about storage) +// from a metadata token. +// +// Parameters: +// fldToken - incoming metadata token specifying the field. +// ppFieldData - OUT: resulting FieldData structure. +// +// Returns: +// S_OK on success. else failure. +//----------------------------------------------------------------------------- +HRESULT CordbClass::GetFieldInfo(mdFieldDef fldToken, FieldData **ppFieldData) +{ + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + Init(); + return SearchFieldInfo(GetModule(), &m_classInfo.m_fieldList, m_token, fldToken, ppFieldData); +} + + +//----------------------------------------------------------------------------- +// Search an array of FieldData (pFieldList) for a field (fldToken). +// The FieldData array must match the class supplied by classToken, and live +// in the supplied module. +// +// Internal helper used by CordbType::GetFieldInfo, CordbClass::GetFieldInfo +// +// Parameters: +// module - module containing the class that the FieldData array matches. +// pFieldList - array of fields to search through and the number of elements in +// the array. +// classToken - class that the data array matches. class must live in +// the supplied moudle. +// fldToken - metadata token of the field to search for. This field should be +// on the class supplied by classToken. +// +// Returns: +// CORDBG_E_ENC_HANGING_FIELD for "hanging fields" (fields added via Enc) (common error). +// Returns S_OK, set ppFieldData = pointer into data array for matching field. (*retval)->m_fldMetadataToken == fldToken) +// Throws on other errors. +//----------------------------------------------------------------------------- +/* static */ +HRESULT CordbClass::SearchFieldInfo( + CordbModule * pModule, + DacDbiArrayList<FieldData> * pFieldList, + mdTypeDef classToken, + mdFieldDef fldToken, + FieldData **ppFieldData +) +{ + int i; + + IMetaDataImport * pImport = pModule->GetMetaDataImporter(); // throws + + HRESULT hr = S_OK; + for (i = 0; i < pFieldList->Count(); i++) + { + if ((*pFieldList)[i].m_fldMetadataToken == fldToken) + { + // If the storage for this field isn't yet available (i.e. it is newly added with EnC) + if (!(*pFieldList)[i].m_fFldStorageAvailable) + { + // If we're a static literal, then return special HR to let + // debugger know that it should look it up via the metadata. + // Check m_fFldIsStatic first b/c that's fast. + if ((*pFieldList)[i].m_fFldIsStatic) + { + if (IsFieldStaticLiteral(pImport, fldToken)) + { + ThrowHR(CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL); + } + } + + // This is a field added by EnC, caller needs to get instance-specific info. + return CORDBG_E_ENC_HANGING_FIELD; + } + + *ppFieldData = &((*pFieldList)[i]); + return S_OK; + } + } + + // Hmmm... we didn't find the field on this class. See if the field really belongs to this class or not. + mdTypeDef classTok; + + hr = pImport->GetFieldProps(fldToken, &classTok, NULL, 0, NULL, NULL, NULL, 0, NULL, NULL, NULL); + IfFailThrow(hr); + + if (classTok == (mdTypeDef) classToken) + { + // Well, the field belongs in this class. The assumption is that the Runtime optimized the field away. + ThrowHR(CORDBG_E_FIELD_NOT_AVAILABLE); + } + + // Well, the field doesn't even belong to this class... + ThrowHR(E_INVALIDARG); +} + |