// 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: DacDbiImpl.cpp // // // Implement DAC/DBI interface // //***************************************************************************** #include "stdafx.h" #include "dacdbiinterface.h" #include "typestring.h" #include "holder.h" #include "debuginfostore.h" #include "peimagelayout.inl" #include "encee.h" #include "switches.h" #include "generics.h" #include "stackwalk.h" #include "dacdbiimpl.h" #ifndef FEATURE_CORECLR #include "assemblyusagelogmanager.h" #endif #ifdef FEATURE_COMINTEROP #include "runtimecallablewrapper.h" #include "comcallablewrapper.h" #endif // FEATURE_COMINTEROP //----------------------------------------------------------------------------- // Have standard enter and leave macros at the DacDbi boundary to enforce // standard behavior. // 1. catch exceptions and convert them at the boundary. // 2. provide a space to hook logging and transitions. // 3. provide a hook to verify return values. // // Usage notes: // - use this at the DacDbi boundary; but not at internal functions // - it's ok to Return from the middle. // // Expected usage is: // Foo() // { // DD_ENTER_MAY_THROW // ... // if (...) { ThrowHr(E_SOME_FAILURE); } // ... // if (...) { return; } // early success case // ... // } //----------------------------------------------------------------------------- // Global allocator for DD. Access is protected under the g_dacCritSec lock. IDacDbiInterface::IAllocator * g_pAllocator = NULL; //--------------------------------------------------------------------------------------- // // Extra sugar for wrapping IAllocator under friendly New/Delete operators. // // Sample usage: // void Foo(TestClass ** ppOut) // { // *ppOut = NULL; // TestClass * p = new (forDbi) TestClass(); // ... // if (ok) // { // *ppOut = p; // return; // DBI will then free this memory. // } // ... // DeleteDbiMemory(p); // } // // Be very careful when using this on classes since Dbi and DAC may be in // separate dlls. This is best used when operating on blittable data-structures. // (no ctor/dtor, plain data fields) to guarantee the proper DLL isolation. // You don't want to call the ctor in DAC's context and the dtor in DBI's context // unless you really know what you're doing and that it's safe. // // Need a class to serve as a tag that we can use to overload New/Delete. forDbiWorker forDbi; void * operator new(size_t lenBytes, const forDbiWorker &) { _ASSERTE(g_pAllocator != NULL); void *result = g_pAllocator->Alloc(lenBytes); if (result == NULL) { ThrowOutOfMemory(); } return result; } void * operator new[](size_t lenBytes, const forDbiWorker &) { _ASSERTE(g_pAllocator != NULL); void *result = g_pAllocator->Alloc(lenBytes); if (result == NULL) { ThrowOutOfMemory(); } return result; } // Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that // this delete operator will be invoked automatically to destroy the object. void operator delete(void *p, const forDbiWorker &) { if (p == NULL) { return; } _ASSERTE(g_pAllocator != NULL); g_pAllocator->Free((BYTE*) p); } // Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that // this delete operator will be invoked automatically to destroy the object. void operator delete[](void *p, const forDbiWorker &) { if (p == NULL) { return; } _ASSERTE(g_pAllocator != NULL); g_pAllocator->Free((BYTE*) p); } // @dbgtodo dac support: determine how to handle an array of class instances to ensure the dtors get // called correctly or document that they won't // Delete memory and invoke dtor for memory allocated with 'operator (forDbi) new' template void DeleteDbiMemory(T *p) { if (p == NULL) { return; } p->~T(); _ASSERTE(g_pAllocator != NULL); g_pAllocator->Free((BYTE*) p); } //--------------------------------------------------------------------------------------- // Creates the DacDbiInterface object, used by Dbi. // // Arguments: // pTarget - pointer to a Data-Target // baseAddress - non-zero base address of mscorwks in target to debug. // pAllocator - pointer to client allocator object. This lets DD allocate objects and // pass them out back to the client, which can then delete them. // DD takes a weak ref to this, so client must keep it alive until it // calls Destroy. // pMetadataLookup - callback interface to do internal metadata lookup. This is because // metadata is not dac-ized. // ppInterface - mandatory out-parameter // // Return Value: // S_OK on success. // // // Notes: // On Windows, this is public function that can be retrieved by GetProcAddress. // On Mac, this is used internally by DacDbiMarshalStubInstance below // This will yield an IDacDbiInterface to provide structured access to the // data-target. // // Must call Destroy to on interface to free its resources. // //--------------------------------------------------------------------------------------- STDAPI DacDbiInterfaceInstance( ICorDebugDataTarget * pTarget, CORDB_ADDRESS baseAddress, IDacDbiInterface::IAllocator * pAllocator, IDacDbiInterface::IMetaDataLookup * pMetaDataLookup, IDacDbiInterface ** ppInterface) { // No marshalling is done by the instantiationf function - we just need to setup the infrastructure. // We don't want to warn if this involves creating and accessing undacized data structures, // because it's for the infrastructure, not DACized code itself. SUPPORTS_DAC_HOST_ONLY; // Since this is public, verify it. if ((ppInterface == NULL) || (pTarget == NULL) || (baseAddress == 0)) { return E_INVALIDARG; } *ppInterface = NULL; // // Actually allocate the real object and initialize it. // DacDbiInterfaceImpl * pDac = new (nothrow) DacDbiInterfaceImpl(pTarget, baseAddress, pAllocator, pMetaDataLookup); if (!pDac) { return E_OUTOFMEMORY; } HRESULT hrStatus = pDac->Initialize(); if (SUCCEEDED(hrStatus)) { *ppInterface = pDac; } else { delete pDac; } return hrStatus; } //--------------------------------------------------------------------------------------- // Constructor. Instantiates a DAC/DBI interface around a DataTarget. // // Arguments: // pTarget - pointer to a Data-Target // baseAddress - non-zero base address of mscorwks in target to debug. // pAllocator - pointer to client allocator object. This lets DD allocate objects and // pass them out back to the client, which can then delete them. // DD takes a weak ref to this, so client must keep it alive until it // calls Destroy. // pMetadataLookup - callback interface to do internal metadata lookup. This is because // metadata is not dac-ized. // // Notes: // pAllocator is a weak reference. //--------------------------------------------------------------------------------------- DacDbiInterfaceImpl::DacDbiInterfaceImpl( ICorDebugDataTarget* pTarget, CORDB_ADDRESS baseAddress, IAllocator * pAllocator, IMetaDataLookup * pMetaDataLookup ) : ClrDataAccess(pTarget), m_pAllocator(pAllocator), m_pMetaDataLookup(pMetaDataLookup), m_pCachedPEFile(VMPTR_PEFile::NullPtr()), m_pCachedImporter(NULL), m_isCachedHijackFunctionValid(FALSE) { _ASSERTE(baseAddress != NULL); m_globalBase = CORDB_ADDRESS_TO_TADDR(baseAddress); _ASSERTE(pMetaDataLookup != NULL); _ASSERTE(pAllocator != NULL); _ASSERTE(pTarget != NULL); #ifdef _DEBUG // Enable verification asserts in ICorDebug scenarios. ICorDebug never guesses at the DAC path, so any // mismatch should be fatal, and so always of interest to the user. // This overrides the assignment in the base class ctor (which runs first). m_fEnableDllVerificationAsserts = true; #endif } //----------------------------------------------------------------------------- // Destructor. // // Notes: // This gets invoked after Destroy(). //----------------------------------------------------------------------------- DacDbiInterfaceImpl::~DacDbiInterfaceImpl() { SUPPORTS_DAC_HOST_ONLY; // This will automatically chain to the base class dtor } //----------------------------------------------------------------------------- // Called from DAC-ized code to get a IMDInternalImport // // Arguments: // pPEFile - PE file for which to get importer for // fThrowEx - if true, throw instead of returning NULL. // // Returns: // an Internal importer object for this file. // May return NULL or throw (depending on fThrowEx). // May throw in exceptional circumstances (eg, corrupt debuggee). // // Assumptions: // This is called from DAC-ized code within the VM, which // was in turn called from some DD primitive. The returned importer will // be used by the DAC-ized code in the callstack, but it won't be cached. // // Notes: // This is an Internal importer, not a public Metadata importer. // interface IMDInternalImport* DacDbiInterfaceImpl::GetMDImport( const PEFile* pPEFile, const ReflectionModule * pReflectionModule, bool fThrowEx) { // Since this is called from an existing DAC-primitive, we already hold the g_dacCritSec lock. // The lock conveniently protects our cache. SUPPORTS_DAC; IDacDbiInterface::IMetaDataLookup * pLookup = m_pMetaDataLookup; _ASSERTE(pLookup != NULL); VMPTR_PEFile vmPEFile = VMPTR_PEFile::NullPtr(); if (pPEFile != NULL) { vmPEFile.SetHostPtr(pPEFile); } else if (pReflectionModule != NULL) { // SOS and ClrDataAccess rely on special logic to find the metadata for methods in dynamic modules. // We don't need to. The RS has already taken care of the special logic for us. // So here we just grab the PEFile off of the ReflectionModule and continue down the normal // code path. See code:ClrDataAccess::GetMDImport for comparison. vmPEFile.SetHostPtr(pReflectionModule->GetFile()); } // Optimize for the case where the VM queries the same Importer many times in a row. if (m_pCachedPEFile == vmPEFile) { return m_pCachedImporter; } // Go to DBI to find the metadata. IMDInternalImport * pInternal = NULL; bool isILMetaDataForNI = false; EX_TRY { // If test needs it in the future, prop isILMetaDataForNI back up to // ClrDataAccess.m_mdImports.Add() call. // example in code:ClrDataAccess::GetMDImport // CordbModule::GetMetaDataInterface also looks up MetaData and would need attention. // This is the new codepath that uses ICorDebugMetaDataLookup. // To get the old codepath that uses the v2 metadata lookup methods, // you'd have to load DAC only and then you'll get ClrDataAccess's implementation // of this function. pInternal = pLookup->LookupMetaData(vmPEFile, isILMetaDataForNI); } EX_CATCH { // Any expected error we should ignore. if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) && (GET_EXCEPTION()->GetHR() != CORDBG_E_SYMBOLS_NOT_AVAILABLE) && (GET_EXCEPTION()->GetHR() != CORDBG_E_MODULE_LOADED_FROM_DISK)) { EX_RETHROW; } } EX_END_CATCH(SwallowAllExceptions) if (pInternal == NULL) { SIMPLIFYING_ASSUMPTION(!"MD lookup failed"); if (fThrowEx) { ThrowHR(E_FAIL); } return NULL; } else { // Cache it such that it we look for the exact same Importer again, we'll return it. m_pCachedPEFile = vmPEFile; m_pCachedImporter = pInternal; } return pInternal; } //----------------------------------------------------------------------------- // Implementation of IDacDbiInterface // See DacDbiInterface.h for full descriptions of all of these functions //----------------------------------------------------------------------------- // Destroy the connection, freeing up any resources. void DacDbiInterfaceImpl::Destroy() { m_pAllocator = NULL; this->Release(); // Memory is deleted, don't access this object any more } // Check whether the version of the DBI matches the version of the runtime. // See code:CordbProcess::CordbProcess#DBIVersionChecking for more information regarding version checking. HRESULT DacDbiInterfaceImpl::CheckDbiVersion(const DbiVersion * pVersion) { DD_ENTER_MAY_THROW; if (pVersion->m_dwFormat != kCurrentDbiVersionFormat) { return CORDBG_E_INCOMPATIBLE_PROTOCOL; } if ((pVersion->m_dwProtocolBreakingChangeCounter != kCurrentDacDbiProtocolBreakingChangeCounter) || (pVersion->m_dwReservedMustBeZero1 != 0)) { return CORDBG_E_INCOMPATIBLE_PROTOCOL; } return S_OK; } // Flush the DAC cache. This should be called when target memory changes. HRESULT DacDbiInterfaceImpl::FlushCache() { // Non-reentrant. We don't want to flush cached instances from a callback. // That would remove host DAC instances while they're being used. DD_NON_REENTRANT_MAY_THROW; m_pCachedPEFile = VMPTR_PEFile::NullPtr(); m_pCachedImporter = NULL; m_isCachedHijackFunctionValid = FALSE; HRESULT hr = ClrDataAccess::Flush(); // Current impl of Flush() should always succeed. If it ever fails, we want to know. _ASSERTE(SUCCEEDED(hr)); return hr; } // enable or disable DAC target consistency checks void DacDbiInterfaceImpl::DacSetTargetConsistencyChecks(bool fEnableAsserts) { // forward on to our ClrDataAccess base class ClrDataAccess::SetTargetConsistencyChecks(fEnableAsserts); } // Query if Left-side is started up? BOOL DacDbiInterfaceImpl::IsLeftSideInitialized() { DD_ENTER_MAY_THROW; if (g_pDebugger != NULL) { // This check is "safe". // The initialize order in the left-side is: // 1) g_pDebugger is an RVA based global initialized to NULL when the module is loaded. // 2) Allocate a "Debugger" object. // 3) run the ctor, which will set m_fLeftSideInitialized = FALSE. // 4) assign the object to g_pDebugger. // 5) later, LS initialization code will assign g_pDebugger->m_fLeftSideInitialized = TRUE. // // The memory write in #5 is atomic. There is no window where we're reading unitialized data. return (g_pDebugger->m_fLeftSideInitialized != 0); } return FALSE; } // Determines if a given adddress is a CLR stub. BOOL DacDbiInterfaceImpl::IsTransitionStub(CORDB_ADDRESS address) { DD_ENTER_MAY_THROW; BOOL fIsStub = FALSE; #if defined(FEATURE_PAL) // Currently IsIPInModule() is not implemented in the PAL. Rather than skipping the check, we should // either E_NOTIMPL this API or implement IsIPInModule() in the PAL. Since ICDProcess::IsTransitionStub() // is only called by VS in mixed-mode debugging scenarios, and mixed-mode debugging is not supported on // POSIX systems, there is really no incentive to implement this API at this point. ThrowHR(E_NOTIMPL); #else // !FEATURE_PAL TADDR ip = (TADDR)address; if (ip == NULL) { fIsStub = FALSE; } else { fIsStub = StubManager::IsStub(ip); } // If it's in Mscorwks, count that as a stub too. if (fIsStub == FALSE) { fIsStub = IsIPInModule(m_globalBase, ip); } #endif // FEATURE_PAL return fIsStub; } // Gets the type of 'address'. IDacDbiInterface::AddressType DacDbiInterfaceImpl::GetAddressType(CORDB_ADDRESS address) { DD_ENTER_MAY_THROW; TADDR taAddr = CORDB_ADDRESS_TO_TADDR(address); if (IsPossibleCodeAddress(taAddr) == S_OK) { if (ExecutionManager::IsManagedCode(taAddr)) { return kAddressManagedMethod; } if (StubManager::IsStub(taAddr)) { return kAddressRuntimeUnmanagedStub; } } return kAddressUnrecognized; } // Get a VM appdomain pointer that matches the appdomain ID VMPTR_AppDomain DacDbiInterfaceImpl::GetAppDomainFromId(ULONG appdomainId) { DD_ENTER_MAY_THROW; VMPTR_AppDomain vmAppDomain; // @dbgtodo dac support - We would like to wean ourselves off the IXClrData interfaces. IXCLRDataProcess * pDAC = this; ReleaseHolder pDacAppDomain; HRESULT hrStatus = pDAC->GetAppDomainByUniqueID(appdomainId, &pDacAppDomain); IfFailThrow(hrStatus); IXCLRDataAppDomain * pIAppDomain = pDacAppDomain; AppDomain * pAppDomain = (static_cast (pIAppDomain))->GetAppDomain(); SIMPLIFYING_ASSUMPTION(pAppDomain != NULL); if (pAppDomain == NULL) { ThrowHR(E_FAIL); // corrupted left-side? } TADDR addrAppDomain = PTR_HOST_TO_TADDR(pAppDomain); vmAppDomain.SetDacTargetPtr(addrAppDomain); return vmAppDomain; } // Get the AppDomain ID for an AppDomain. ULONG DacDbiInterfaceImpl::GetAppDomainId(VMPTR_AppDomain vmAppDomain) { DD_ENTER_MAY_THROW; if (vmAppDomain.IsNull()) { return 0; } else { AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); return pAppDomain->GetId().m_dwId; } } // Get the managed AppDomain object for an AppDomain. VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetAppDomainObject(VMPTR_AppDomain vmAppDomain) { DD_ENTER_MAY_THROW; AppDomain* pAppDomain = vmAppDomain.GetDacPtr(); OBJECTHANDLE hAppDomainManagedObject = pAppDomain->GetRawExposedObjectHandleForDebugger(); VMPTR_OBJECTHANDLE vmObj = VMPTR_OBJECTHANDLE::NullPtr(); vmObj.SetDacTargetPtr(hAppDomainManagedObject); return vmObj; } // Determine if the specified AppDomain is the default domain BOOL DacDbiInterfaceImpl::IsDefaultDomain(VMPTR_AppDomain vmAppDomain) { DD_ENTER_MAY_THROW; AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); BOOL fDefaultDomain = pAppDomain->IsDefaultDomain(); return fDefaultDomain; } // Get the full AD friendly name for the given EE AppDomain. void DacDbiInterfaceImpl::GetAppDomainFullName( VMPTR_AppDomain vmAppDomain, IStringHolder * pStrName ) { DD_ENTER_MAY_THROW; AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); // Get the AppDomain name from the VM without changing anything // We might be able to simplify this, eg. by returning an SString. bool fIsUtf8; PVOID pRawName = pAppDomain->GetFriendlyNameNoSet(&fIsUtf8); if (!pRawName) { ThrowHR(E_NOINTERFACE); } HRESULT hrStatus = S_OK; if (fIsUtf8) { // we have to allocate a temporary string // we could avoid this by adding a version of IStringHolder::AssignCopy that takes a UTF8 string // We should also probably check to see when fIsUtf8 is ever true (it looks like it should normally be false). ULONG32 dwNameLen = 0; hrStatus = ConvertUtf8((LPCUTF8)pRawName, 0, &dwNameLen, NULL); if (SUCCEEDED( hrStatus )) { NewArrayHolder pwszName(new WCHAR[dwNameLen]); hrStatus = ConvertUtf8((LPCUTF8)pRawName, dwNameLen, &dwNameLen, pwszName ); IfFailThrow(hrStatus); hrStatus = pStrName->AssignCopy(pwszName); } } else { hrStatus = pStrName->AssignCopy(static_cast(pRawName)); } // Very important that this either sets pStrName or Throws. // Don't set it and then then throw. IfFailThrow(hrStatus); } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // JIT Compiler Flags //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Get the values of the JIT Optimization and EnC flags. void DacDbiInterfaceImpl::GetCompilerFlags ( VMPTR_DomainFile vmDomainFile, BOOL *pfAllowJITOpts, BOOL *pfEnableEnC) { DD_ENTER_MAY_THROW; DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); if (pDomainFile == NULL) { ThrowHR(E_FAIL); } // Get the underlying module - none of this is AppDomain specific Module * pModule = pDomainFile->GetModule(); DWORD dwBits = pModule->GetDebuggerInfoBits(); *pfAllowJITOpts = !CORDisableJITOptimizations(dwBits); *pfEnableEnC = pModule->IsEditAndContinueEnabled(); } //GetCompilerFlags //----------------------------------------------------------------------------- // Helper function for SetCompilerFlags to set EnC status. // Arguments: // Input: // pModule - The runtime module for which flags are being set. // // Return value: // true if the Enc bits can be set on this module //----------------------------------------------------------------------------- bool DacDbiInterfaceImpl::CanSetEnCBits(Module * pModule) { _ASSERTE(pModule != NULL); #ifdef EnC_SUPPORTED // If we're using explicit sequence points (from the PDB), then we can't do EnC // because EnC won't get updated pdbs and so the sequence points will be wrong. bool fIgnorePdbs = ((pModule->GetDebuggerInfoBits() & DACF_IGNORE_PDBS) != 0); bool fAllowEnc = pModule->IsEditAndContinueCapable() && #ifdef PROFILING_SUPPORTED_DATA !CORProfilerPresent() && // this queries target #endif fIgnorePdbs; #else // ! EnC_SUPPORTED // Enc not supported on any other platforms. bool fAllowEnc = false; #endif return fAllowEnc; } // DacDbiInterfaceImpl::SetEnCBits // Set the values of the JIT optimization and EnC flags. HRESULT DacDbiInterfaceImpl::SetCompilerFlags(VMPTR_DomainFile vmDomainFile, BOOL fAllowJitOpts, BOOL fEnableEnC) { DD_ENTER_MAY_THROW; DWORD dwBits = 0; DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); Module * pModule = pDomainFile->GetCurrentModule(); HRESULT hr = S_OK; #ifdef FEATURE_PREJIT if (pModule->HasNativeImage()) { ThrowHR(CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE); } #endif _ASSERTE(pModule != NULL); // Initialize dwBits. dwBits = (pModule->GetDebuggerInfoBits() & ~(DACF_ALLOW_JIT_OPTS | DACF_ENC_ENABLED)); dwBits &= DACF_CONTROL_FLAGS_MASK; if (fAllowJitOpts) { dwBits |= DACF_ALLOW_JIT_OPTS; } if (fEnableEnC) { if (CanSetEnCBits(pModule)) { dwBits |= DACF_ENC_ENABLED; } else { hr = CORDBG_S_NOT_ALL_BITS_SET; } } // Settings from the debugger take precedence over all other settings. dwBits |= DACF_USER_OVERRIDE; // set flags. This will write back to the target pModule->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits); LOG((LF_CORDB, LL_INFO100, "D::HIPCE, Changed Jit-Debug-Info: fOpt=%d, fEnableEnC=%d, new bits=0x%08x\n", (dwBits & DACF_ALLOW_JIT_OPTS) != 0, (dwBits & DACF_ENC_ENABLED) != 0, dwBits)); _ASSERTE(SUCCEEDED(hr)); return hr; } // DacDbiInterfaceImpl::SetCompilerFlags //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // sequence points and var info //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Initialize the native/IL sequence points and native var info for a function. void DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddr, BOOL fCodeAvailable, NativeVarData * pNativeVarData, SequencePoints * pSequencePoints) { DD_ENTER_MAY_THROW; _ASSERTE(!vmMethodDesc.IsNull()); MethodDesc * pMD = vmMethodDesc.GetDacPtr(); _ASSERTE(fCodeAvailable != 0); // get information about the locations of arguments and local variables GetNativeVarData(pMD, startAddr, GetArgCount(pMD), pNativeVarData); // get the sequence points GetSequencePoints(pMD, startAddr, pSequencePoints); } // GetNativeCodeSequencePointsAndVarInfo //----------------------------------------------------------------------------- // Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer. // This does not include other implicit arguments or varargs. This is used to compute a variable ID // (see comment in CordbJITILFrame::ILVariableToNative for more detail) // Arguments: // input: pMD pointer to the method desc for the function // output: none // Return value: // the number of fixed arguments to the function //----------------------------------------------------------------------------- SIZE_T DacDbiInterfaceImpl::GetArgCount(MethodDesc * pMD) { // Create a MetaSig for the given method's sig. (Easier than // picking the sig apart ourselves.) PCCOR_SIGNATURE pCallSig; DWORD cbCallSigSize; pMD->GetSig(&pCallSig, &cbCallSigSize); if (pCallSig == NULL) { // Sig should only be null if the image is corrupted. (Even for lightweight-codegen) // We expect the jit+verifier to catch this, so that we never land here. // But just in case ... CONSISTENCY_CHECK_MSGF(false, ("Corrupted image, null sig.(%s::%s)", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName)); return 0; } MetaSig msig(pCallSig, cbCallSigSize, pMD->GetModule(), NULL, MetaSig::sigMember); // Get the arg count. UINT32 NumArguments = msig.NumFixedArgs(); // Account for the 'this' argument. if (!pMD->IsStatic()) { NumArguments++; } /* SigParser sigParser(pCallSig, cbCallSigSize); sigParser.SkipMethodHeaderSignature(&m_allArgsCount); */ return NumArguments; } //GetArgCount // Allocator to pass to DebugInfoStores, allocating forDBI BYTE* InfoStoreForDbiNew(void * pData, size_t cBytes) { return new(forDbi) BYTE[cBytes]; } // Allocator to pass to the debug-info-stores... BYTE* InfoStoreNew(void * pData, size_t cBytes) { return new BYTE[cBytes]; } //----------------------------------------------------------------------------- // Get locations and code offsets for local variables and arguments in a function // This information is used to find the location of a value at a given IP. // Arguments: // input: // pMethodDesc pointer to the method desc for the function // startAddr starting address of the function--used to differentiate // EnC versions // fixedArgCount number of fixed arguments to the function // output: // pVarInfo data structure containing a list of variable and // argument locations by range of IP offsets // Note: this function may throw //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetNativeVarData(MethodDesc * pMethodDesc, CORDB_ADDRESS startAddr, SIZE_T fixedArgCount, NativeVarData * pVarInfo) { // make sure we haven't done this already if (pVarInfo->IsInitialized()) { return; } NewHolder nativeVars(NULL); DebugInfoRequest request; request.InitFromStartingAddr(pMethodDesc, CORDB_ADDRESS_TO_TADDR(startAddr)); ULONG32 entryCount; BOOL success = DebugInfoManager::GetBoundariesAndVars(request, InfoStoreNew, NULL, // allocator NULL, NULL, &entryCount, &nativeVars); if (!success) ThrowHR(E_FAIL); // set key fields of pVarInfo pVarInfo->InitVarDataList(nativeVars, (int)fixedArgCount, (int)entryCount); } // GetNativeVarData //----------------------------------------------------------------------------- // Given a instrumented IL map from the profiler that maps: // Original offset IL_A -> Instrumentend offset IL_B // And a native mapping from the JIT that maps: // Instrumented offset IL_B -> native offset Native_C // This function merges the two maps and stores the result back into the nativeMap. // The nativeMap now maps: // Original offset IL_A -> native offset Native_C // pEntryCount is the number of valid entries in nativeMap, and it may be adjusted downwards // as part of the composition. //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::ComposeMapping(InstrumentedILOffsetMapping profilerILMap, ICorDebugInfo::OffsetMapping nativeMap[], ULONG32* pEntryCount) { // Translate the IL offset if the profiler has provided us with a mapping. // The ICD public API should always expose the original IL offsets, but GetBoundaries() // directly accesses the debug info, which stores the instrumented IL offsets. ULONG32 entryCount = *pEntryCount; if (!profilerILMap.IsNull()) { // If we did instrument, then we can't have any sequence points that // are "in-between" the old-->new map that the profiler gave us. // Ex, if map is: // (6 old -> 36 new) // (8 old -> 50 new) // And the jit gives us an entry for 44 new, that will map back to 6 old. // Since the map can only have one entry for 6 old, we remove 44 new. // First Pass: invalidate all the duplicate entries by setting their IL offset to MAX_ILNUM ULONG32 cDuplicate = 0; ULONG32 prevILOffset = (ULONG32)(ICorDebugInfo::MAX_ILNUM); for (ULONG32 i = 0; i < entryCount; i++) { ULONG32 origILOffset = TranslateInstrumentedILOffsetToOriginal(nativeMap[i].ilOffset, &profilerILMap); if (origILOffset == prevILOffset) { // mark this sequence point as invalid; refer to the comment above nativeMap[i].ilOffset = (ULONG32)(ICorDebugInfo::MAX_ILNUM); cDuplicate += 1; } else { // overwrite the instrumented IL offset with the original IL offset nativeMap[i].ilOffset = origILOffset; prevILOffset = origILOffset; } } // Second Pass: move all the valid entries up front ULONG32 realIndex = 0; for (ULONG32 curIndex = 0; curIndex < entryCount; curIndex++) { if (nativeMap[curIndex].ilOffset != (ULONG32)(ICorDebugInfo::MAX_ILNUM)) { // This is a valid entry. Move it up front. nativeMap[realIndex] = nativeMap[curIndex]; realIndex += 1; } } // make sure we have done the bookkeeping correctly _ASSERTE((realIndex + cDuplicate) == entryCount); // Final Pass: derecement entryCount entryCount -= cDuplicate; *pEntryCount = entryCount; } } //----------------------------------------------------------------------------- // Get the native/IL sequence points for a function // Arguments: // input: // pMethodDesc pointer to the method desc for the function // startAddr starting address of the function--used to differentiate // output: // pNativeMap data structure containing a list of sequence points // Note: this function may throw //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetSequencePoints(MethodDesc * pMethodDesc, CORDB_ADDRESS startAddr, SequencePoints * pSeqPoints) { // make sure we haven't done this already if (pSeqPoints->IsInitialized()) { return; } // Use the DebugInfoStore to get IL->Native maps. // It doesn't matter whether we're jitted, ngenned etc. DebugInfoRequest request; request.InitFromStartingAddr(pMethodDesc, CORDB_ADDRESS_TO_TADDR(startAddr)); // Bounds info. NewArrayHolder mapCopy(NULL); ULONG32 entryCount; BOOL success = DebugInfoManager::GetBoundariesAndVars(request, InfoStoreNew, NULL, // allocator &entryCount, &mapCopy, NULL, NULL); if (!success) ThrowHR(E_FAIL); // if there is a rejit IL map for this function, apply that in preference to load-time mapping #ifdef FEATURE_REJIT ReJitManager * pReJitMgr = pMethodDesc->GetReJitManager(); ReJitInfo* pReJitInfo = pReJitMgr->FindReJitInfo(dac_cast(pMethodDesc), (PCODE)startAddr, 0); if (pReJitInfo != NULL) { InstrumentedILOffsetMapping rejitMapping = pReJitInfo->m_pShared->m_instrumentedILMap; ComposeMapping(rejitMapping, mapCopy, &entryCount); } else { #endif // if there is a profiler load-time mapping and not a rejit mapping, apply that instead InstrumentedILOffsetMapping loadTimeMapping = pMethodDesc->GetModule()->GetInstrumentedILOffsetMapping(pMethodDesc->GetMemberDef()); ComposeMapping(loadTimeMapping, mapCopy, &entryCount); #ifdef FEATURE_REJIT } #endif pSeqPoints->InitSequencePoints(entryCount); // mapCopy and pSeqPoints have elements of different types. Thus, we // need to copy the individual members from the elements of mapCopy to the // elements of pSeqPoints. Once we're done, we can release mapCopy pSeqPoints->CopyAndSortSequencePoints(mapCopy); } // GetSequencePoints // ---------------------------------------------------------------------------- // DacDbiInterfaceImpl::TranslateInstrumentedILOffsetToOriginal // // Description: // Helper function to convert an instrumented IL offset to the corresponding original IL offset. // // Arguments: // * ilOffset - offset to be translated // * pMapping - the profiler-provided mapping between original IL offsets and instrumented IL offsets // // Return Value: // Return the translated offset. // ULONG DacDbiInterfaceImpl::TranslateInstrumentedILOffsetToOriginal(ULONG ilOffset, const InstrumentedILOffsetMapping * pMapping) { SIZE_T cMap = pMapping->GetCount(); ARRAY_PTR_COR_IL_MAP rgMap = pMapping->GetOffsets(); _ASSERTE((cMap == 0) == (rgMap == NULL)); // Early out if there is no mapping, or if we are dealing with a special IL offset such as // prolog, epilog, etc. if ((cMap == 0) || ((int)ilOffset < 0)) { return ilOffset; } SIZE_T i = 0; for (i = 1; i < cMap; i++) { if (ilOffset < rgMap[i].newOffset) { return rgMap[i - 1].oldOffset; } } return rgMap[i - 1].oldOffset; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Function Data //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // GetILCodeAndSig returns the function's ILCode and SigToken given // a module and a token. The info will come from a MethodDesc, if // one exists or from metadata. // void DacDbiInterfaceImpl::GetILCodeAndSig(VMPTR_DomainFile vmDomainFile, mdToken functionToken, TargetBuffer * pCodeInfo, mdToken * pLocalSigToken) { DD_ENTER_MAY_THROW; DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); Module * pModule = pDomainFile->GetCurrentModule(); RVA methodRVA = 0; DWORD implFlags; // preinitialize out params pCodeInfo->Clear(); *pLocalSigToken = mdSignatureNil; // Get the RVA and impl flags for this method. IfFailThrow(pModule->GetMDImport()->GetMethodImplProps(functionToken, &methodRVA, &implFlags)); MethodDesc* pMethodDesc = FindLoadedMethodRefOrDef(pModule, functionToken); // If the RVA is 0 or it's native, then the method is not IL if (methodRVA == 0) { LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: Function is not IL - methodRVA == NULL!\n")); // return (CORDBG_E_FUNCTION_NOT_IL); // Sanity check this.... if(!pMethodDesc || !pMethodDesc->IsIL()) { LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: And the MD agrees..\n")); ThrowHR(CORDBG_E_FUNCTION_NOT_IL); } else { LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: But the MD says it's IL..\n")); } if (pMethodDesc != NULL && pMethodDesc->GetRVA() == 0) { LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: Actually, MD says RVA is 0 too - keep going...!\n")); } } if (IsMiNative(implFlags)) { LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: Function is not IL - IsMiNative!\n")); ThrowHR(CORDBG_E_FUNCTION_NOT_IL); } *pLocalSigToken = GetILCodeAndSigHelper(pModule, pMethodDesc, functionToken, methodRVA, pCodeInfo); #ifdef LOGGING else { LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: GetMethodImplProps failed!\n")); } #endif } // GetILCodeAndSig //--------------------------------------------------------------------------------------- // // This is just a worker function for GetILCodeAndSig. It returns the function's ILCode and SigToken // given a module, a token, and the RVA. If a MethodDesc is provided, it has to be consistent with // the token and the RVA. // // Arguments: // pModule - the Module containing the specified method // pMD - the specified method; can be NULL // mdMethodToken - the MethodDef token of the specified method // methodRVA - the RVA of the IL for the specified method // pIL - out parameter; return the target address and size of the IL of the specified method // // Return Value: // Return the local variable signature token of the specified method. Can be mdSignatureNil. // mdSignature DacDbiInterfaceImpl::GetILCodeAndSigHelper(Module * pModule, MethodDesc * pMD, mdMethodDef mdMethodToken, RVA methodRVA, TargetBuffer * pIL) { _ASSERTE(pModule != NULL); // If a MethodDesc is provided, it has to be consistent with the MethodDef token and the RVA. _ASSERTE((pMD == NULL) || ((pMD->GetMemberDef() == mdMethodToken) && (pMD->GetRVA() == methodRVA))); TADDR pTargetIL; // target address of start of IL blob // This works for methods in dynamic modules, and methods overriden by a profiler. pTargetIL = pModule->GetDynamicIL(mdMethodToken, TRUE); // Method not overriden - get the original copy of the IL by going to the PE file/RVA // If this is in a dynamic module then don't even attempt this since ReflectionModule::GetIL isn't // implemend for DAC. if (pTargetIL == 0 && !pModule->IsReflection()) { pTargetIL = (TADDR)pModule->GetIL(methodRVA); } mdSignature mdSig = mdSignatureNil; if (pTargetIL == 0) { // Currently this should only happen for LCG methods (including IL stubs). // LCG methods have a 0 RVA, and so we don't currently have any way to get the IL here. _ASSERTE(pMD->IsDynamicMethod()); _ASSERTE(pMD->AsDynamicMethodDesc()->IsLCGMethod()|| pMD->AsDynamicMethodDesc()->IsILStub()); // Clear the buffer. pIL->Clear(); } else { // Now we have the target address of the IL blob, we need to bring it over to the host. // DacGetILMethod will copy the COR_ILMETHOD information that we need COR_ILMETHOD * pHostIL = DacGetIlMethod(pTargetIL); // host address of start of IL blob COR_ILMETHOD_DECODER header(pHostIL); // host address of header // Get the IL code info. We need the address of the IL itself, which will be beyond the header // at the beginning of the blob. We ultimately need the target address. To get this, we take // target address of the target IL blob and add the offset from the beginning of the host IL blob // (the header) to the beginning of the IL itself (we get this information from the header). pIL->pAddress = pTargetIL + ((SIZE_T)(header.Code) - (SIZE_T)pHostIL); pIL->cbSize = header.GetCodeSize(); // Now we get the signature token if (header.LocalVarSigTok != NULL) { mdSig = header.GetLocalVarSigTok(); } else { mdSig = mdSignatureNil; } } return mdSig; } bool DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEFile vmPEFile, DWORD &dwTimeStamp, DWORD &dwSize, bool &isNGEN, IStringHolder* pStrFilename) { #if !defined(FEATURE_PREJIT) return false; #else // defined(FEATURE_PREJIT) DD_ENTER_MAY_THROW; DWORD dwDataSize; DWORD dwRvaHint; PEFile * pPEFile = vmPEFile.GetDacPtr(); _ASSERTE(pPEFile != NULL); if (pPEFile == NULL) return false; WCHAR wszFilePath[MAX_LONGPATH] = {0}; DWORD cchFilePath = MAX_LONGPATH; bool ret = ClrDataAccess::GetMetaDataFileInfoFromPEFile(pPEFile, dwTimeStamp, dwSize, dwDataSize, dwRvaHint, isNGEN, wszFilePath, cchFilePath); pStrFilename->AssignCopy(wszFilePath); return ret; #endif // !defined(FEATURE_PREJIT) } bool DacDbiInterfaceImpl::GetILImageInfoFromNgenPEFile(VMPTR_PEFile vmPEFile, DWORD &dwTimeStamp, DWORD &dwSize, IStringHolder* pStrFilename) { #if !defined(FEATURE_PREJIT) return false; #else // defined(FEATURE_PREJIT) DD_ENTER_MAY_THROW; PEFile * pPEFile = vmPEFile.GetDacPtr(); _ASSERTE(pPEFile != NULL); if (pPEFile == NULL) { return false; } WCHAR wszFilePath[MAX_LONGPATH] = {0}; DWORD cchFilePath = MAX_LONGPATH; bool ret = ClrDataAccess::GetILImageInfoFromNgenPEFile(pPEFile, dwTimeStamp, dwSize, wszFilePath, cchFilePath); pStrFilename->AssignCopy(wszFilePath); return ret; #endif // !defined(FEATURE_PREJIT) } // Get start addresses and sizes for hot and cold regions for a native code blob. // Arguments: // Input: // pMethodDesc - method desc for the function we are inspecting // Output (required): // pCodeInfo - initializes the m_rgCodeRegions field of this structure // if the native code is available. Otherwise, // pCodeInfo->IsValid() is false. void DacDbiInterfaceImpl::GetMethodRegionInfo(MethodDesc * pMethodDesc, NativeCodeFunctionData * pCodeInfo) { CONTRACTL { SO_INTOLERANT; GC_NOTRIGGER; PRECONDITION(CheckPointer(pCodeInfo)); } CONTRACTL_END; IJitManager::MethodRegionInfo methodRegionInfo = {NULL, 0, NULL, 0}; PCODE functionAddress = pMethodDesc->GetNativeCode(); // get the start address of the hot region and initialize the jit manager pCodeInfo->m_rgCodeRegions[kHot].pAddress = CORDB_ADDRESS(PCODEToPINSTR(functionAddress)); // if the start address is NULL, the code isn't available yet, so just return if (functionAddress != NULL) { EECodeInfo codeInfo(functionAddress); _ASSERTE(codeInfo.IsValid()); codeInfo.GetMethodRegionInfo(&methodRegionInfo); // now get the rest of the region information pCodeInfo->m_rgCodeRegions[kHot].cbSize = (ULONG)methodRegionInfo.hotSize; pCodeInfo->m_rgCodeRegions[kCold].Init(PCODEToPINSTR(methodRegionInfo.coldStartAddress), (ULONG)methodRegionInfo.coldSize); _ASSERTE(pCodeInfo->IsValid()); } else { _ASSERTE(!pCodeInfo->IsValid()); } } // GetMethodRegionInfo // Gets the following information about a native code blob: // - its method desc // - whether it's an instantiated generic // - its EnC version number // - hot and cold region information. // If the hot region start address is NULL at the end, it means the native code // isn't currently available. In this case, all values in pCodeInfo will be // cleared. void DacDbiInterfaceImpl::GetNativeCodeInfo(VMPTR_DomainFile vmDomainFile, mdToken functionToken, NativeCodeFunctionData * pCodeInfo) { DD_ENTER_MAY_THROW; _ASSERTE(pCodeInfo != NULL); // pre-initialize: pCodeInfo->Clear(); DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); Module * pModule = pDomainFile->GetCurrentModule(); MethodDesc* pMethodDesc = FindLoadedMethodRefOrDef(pModule, functionToken); pCodeInfo->vmNativeCodeMethodDescToken.SetHostPtr(pMethodDesc); // if we are loading a module and trying to bind a previously set breakpoint, we may not have // a method desc yet, so check for that situation if(pMethodDesc != NULL) { GetMethodRegionInfo(pMethodDesc, pCodeInfo); if (pCodeInfo->m_rgCodeRegions[kHot].pAddress != NULL) { pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); LookupEnCVersions(pModule, pCodeInfo->vmNativeCodeMethodDescToken, functionToken, pCodeInfo->m_rgCodeRegions[kHot].pAddress, &(pCodeInfo->encVersion)); } } } // GetNativeCodeInfo // Gets the following information about a native code blob: // - its method desc // - whether it's an instantiated generic // - its EnC version number // - hot and cold region information. void DacDbiInterfaceImpl::GetNativeCodeInfoForAddr(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS hotCodeStartAddr, NativeCodeFunctionData * pCodeInfo) { DD_ENTER_MAY_THROW; _ASSERTE(pCodeInfo != NULL); if (hotCodeStartAddr == NULL) { // if the start address is NULL, the code isn't available yet, so just return _ASSERTE(!pCodeInfo->IsValid()); return; } IJitManager::MethodRegionInfo methodRegionInfo = {NULL, 0, NULL, 0}; TADDR codeAddr = CORDB_ADDRESS_TO_TADDR(hotCodeStartAddr); #ifdef _TARGET_ARM_ // TADDR should not have the thumb code bit set. _ASSERTE((codeAddr & THUMB_CODE) == 0); codeAddr &= ~THUMB_CODE; #endif EECodeInfo codeInfo(codeAddr); _ASSERTE(codeInfo.IsValid()); // We may not have the memory for the cold code region in a minidump. // Do not fail stackwalking because of this. EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY { codeInfo.GetMethodRegionInfo(&methodRegionInfo); } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY; // Even if GetMethodRegionInfo() fails to retrieve the cold code region info, // we should still be able to get the hot code region info. We are counting on this for // stackwalking to work in dump debugging scenarios. _ASSERTE(methodRegionInfo.hotStartAddress == codeAddr); // now get the rest of the region information pCodeInfo->m_rgCodeRegions[kHot].Init(PCODEToPINSTR(methodRegionInfo.hotStartAddress), (ULONG)methodRegionInfo.hotSize); pCodeInfo->m_rgCodeRegions[kCold].Init(PCODEToPINSTR(methodRegionInfo.coldStartAddress), (ULONG)methodRegionInfo.coldSize); _ASSERTE(pCodeInfo->IsValid()); MethodDesc* pMethodDesc = vmMethodDesc.GetDacPtr(); pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); pCodeInfo->vmNativeCodeMethodDescToken = vmMethodDesc; SIZE_T unusedLatestEncVersion; Module * pModule = pMethodDesc->GetModule(); _ASSERTE(pModule != NULL); LookupEnCVersions(pModule, vmMethodDesc, pMethodDesc->GetMemberDef(), codeAddr, &unusedLatestEncVersion, //unused by caller &(pCodeInfo->encVersion)); } // GetNativeCodeInfo //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // Functions to get Type and Class information // //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //----------------------------------------------------------------------------- //DacDbiInterfaceImpl::GetTypeHandles // Get the approximate and exact type handles for a type // Arguments: // input: // vmThExact - VMPTR of the exact type handle. If this method is called // to get information for a new generic instantiation, this will already // be initialized. If it's called to get type information for an arbitrary // type (i.e., called to initialize an instance of CordbClass), it will be NULL // vmThApprox - VMPTR of the approximate type handle. If this method is called // to get information for a new generic instantiation, this will already // be initialized. If it's called to get type information for an arbitrary // type (i.e., called to initialize an instance of CordbClass), it will be NULL // output: // pThExact - handle for exact type information for a generic instantiation // pThApprox - handle for type information // Notes: // pThExact and pTHApprox must be pointers to existing memory. //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetTypeHandles(VMPTR_TypeHandle vmThExact, VMPTR_TypeHandle vmThApprox, TypeHandle * pThExact, TypeHandle * pThApprox) { _ASSERTE((pThExact != NULL) && (pThApprox != NULL)); *pThExact = TypeHandle::FromPtr(vmThExact.GetDacPtr()); *pThApprox = TypeHandle::FromPtr(vmThApprox.GetDacPtr()); // If we can't find the class, return the proper HR to the right side. Note: if the class is not a value class and // the class is also not restored, then we must pretend that the class is still not loaded. We are gonna let // unrestored value classes slide, though, and special case access to the class's parent below. if ((pThApprox->IsNull()) || ((!pThApprox->IsValueType()) && (!pThApprox->IsRestored()))) { LOG((LF_CORDB, LL_INFO10000, "D::GASCI: class isn't loaded.\n")); ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } // If the exact type handle is not restored ignore it. if (!pThExact->IsNull() && !pThExact->IsRestored()) { *pThExact = TypeHandle(); } } // DacDbiInterfaceImpl::GetTypeHandles //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetTotalFieldCount // Gets the total number of fields for a type. // Input Argument: thApprox - type handle used to determine the number of fields // Return Value: count of the total fields of the type. //----------------------------------------------------------------------------- unsigned int DacDbiInterfaceImpl::GetTotalFieldCount(TypeHandle thApprox) { MethodTable *pMT = thApprox.GetMethodTable(); // Count the instance and static fields for this class (not including parent). // This will not include any newly added EnC fields. unsigned int IFCount = pMT->GetNumIntroducedInstanceFields(); unsigned int SFCount = pMT->GetNumStaticFields(); #ifdef EnC_SUPPORTED PTR_Module pModule = pMT->GetModule(); // Stats above don't include EnC fields. So add them now. if (pModule->IsEditAndContinueEnabled()) { PTR_EnCEEClassData pEncData = (dac_cast(pModule))->GetEnCEEClassData(pMT, TRUE); if (pEncData != NULL) { _ASSERTE(pEncData->GetMethodTable() == pMT); // EnC only adds fields, never removes them. IFCount += pEncData->GetAddedInstanceFields(); SFCount += pEncData->GetAddedStaticFields(); } } #endif return IFCount + SFCount; } // DacDbiInterfaceImpl::GetTotalFieldCount //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::InitClassData // initializes various values of the ClassInfo data structure, including the // field count, generic args count, size and value class flag // Arguments: // input: thApprox - used to get access to all the necessary values // fIsInstantiatedType - used to determine how to compute the size // output: pData - contains fields to be initialized //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::InitClassData(TypeHandle thApprox, BOOL fIsInstantiatedType, ClassInfo * pData) { pData->m_fieldList.Alloc(GetTotalFieldCount(thApprox)); // For Generic classes you must get the object size via the type handle, which // will get you to the right information for the particular instantiation // you're working with... pData->m_objectSize = 0; if ((!thApprox.GetNumGenericArgs()) || fIsInstantiatedType) { pData->m_objectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes(); } } // DacDbiInterfaceImpl::InitClassData //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetStaticsBases // Gets the base table addresses for both GC and non-GC statics // Arguments: // input: thExact - exact type handle for the class // pAppDomain - AppDomain in which the class is loaded // output: ppGCStaticsBase - base pointer for GC statics // ppNonGCStaticsBase - base pointer for non GC statics // Notes: // If this is a non-generic type, or an instantiated type, then we'll be able to get the static var bases // If the typeHandle represents a generic type constructor (i.e. an uninstantiated generic class), then // the static bases will be null (since statics are per-instantiation). //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetStaticsBases(TypeHandle thExact, AppDomain * pAppDomain, PTR_BYTE * ppGCStaticsBase, PTR_BYTE * ppNonGCStaticsBase) { MethodTable * pMT = thExact.GetMethodTable(); Module * pModuleForStatics = pMT->GetModuleForStatics(); if (pModuleForStatics != NULL) { PTR_DomainLocalModule pLocalModule = pModuleForStatics->GetDomainLocalModule(pAppDomain); if (pLocalModule != NULL) { *ppGCStaticsBase = pLocalModule->GetGCStaticsBasePointer(pMT); *ppNonGCStaticsBase = pLocalModule->GetNonGCStaticsBasePointer(pMT); } } } // DacDbiInterfaceImpl::GetStaticsBases //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::ComputeFieldData // Computes the field info for pFD and stores it in pcurrentFieldData // Arguments: // input: pFD - FieldDesc used to get necessary information // pGCStaticsBase - base table address for GC statics // pNonGCStaticsBase - base table address for non-GC statics // output: pCurrentFieldData - contains fields to be initialized //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::ComputeFieldData(PTR_FieldDesc pFD, PTR_BYTE pGCStaticsBase, PTR_BYTE pNonGCStaticsBase, FieldData * pCurrentFieldData) { pCurrentFieldData->Initialize(pFD->IsStatic(), pFD->IsPrimitive(), pFD->GetMemberDef()); #ifdef EnC_SUPPORTED // If the field was newly introduced via EnC, and hasn't yet // been fixed up, then we'll send back a marker indicating // that it isn't yet available. if (pFD->IsEnCNew()) { // @dbgtodo Microsoft inspection: eliminate the debugger token when ICDClass and ICDType are // completely DACized pCurrentFieldData->m_vmFieldDesc.SetHostPtr(pFD); pCurrentFieldData->m_fFldStorageAvailable = FALSE; pCurrentFieldData->m_fFldIsTLS = FALSE; pCurrentFieldData->m_fFldIsContextStatic = FALSE; pCurrentFieldData->m_fFldIsRVA = FALSE; pCurrentFieldData->m_fFldIsCollectibleStatic = FALSE; } else #endif // EnC_SUPPORTED { // Otherwise, we'll compute the info & send it back. pCurrentFieldData->m_fFldStorageAvailable = TRUE; // @dbgtodo Microsoft inspection: eliminate the debugger token when ICDClass and ICDType are // completely DACized pCurrentFieldData->m_vmFieldDesc.SetHostPtr(pFD); pCurrentFieldData->m_fFldIsTLS = (pFD->IsThreadStatic() == TRUE); pCurrentFieldData->m_fFldIsContextStatic = (pFD->IsContextStatic() == TRUE); pCurrentFieldData->m_fFldIsRVA = (pFD->IsRVA() == TRUE); pCurrentFieldData->m_fFldIsCollectibleStatic = (pFD->IsStatic() == TRUE && pFD->GetEnclosingMethodTable()->Collectible()); // Compute the address of the field if (pFD->IsStatic()) { // statics are addressed using an absolute address. if (pFD->IsRVA()) { // RVA statics are relative to a base module address DWORD offset = pFD->GetOffset(); PTR_VOID addr = pFD->GetModule()->GetRvaField(offset, pFD->IsZapped()); if (pCurrentFieldData->OkToGetOrSetStaticAddress()) { pCurrentFieldData->SetStaticAddress(PTR_TO_TADDR(addr)); } } else if (pFD->IsThreadStatic() || pFD->IsContextStatic() || pCurrentFieldData->m_fFldIsCollectibleStatic) { // this is a special type of static that must be queried using DB_IPCE_GET_SPECIAL_STATIC } else { // This is a normal static variable in the GC or Non-GC static base table PTR_BYTE base = pFD->IsPrimitive() ? pNonGCStaticsBase : pGCStaticsBase; if (base == NULL) { // static var not available. This may be an open generic class (not an instantiated type), // or we might only have approximate type information because the type hasn't been // initialized yet. if (pCurrentFieldData->OkToGetOrSetStaticAddress()) { pCurrentFieldData->SetStaticAddress(NULL); } } else { if (pCurrentFieldData->OkToGetOrSetStaticAddress()) { // calculate the absolute address using the base and the offset from the base pCurrentFieldData->SetStaticAddress(PTR_TO_TADDR(base) + pFD->GetOffset()); } } } } else { // instance variables are addressed using an offset within the instance if (pCurrentFieldData->OkToGetOrSetInstanceOffset()) { pCurrentFieldData->SetInstanceOffset(pFD->GetOffset()); } } } } // DacDbiInterfaceImpl::ComputeFieldData //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::CollectFields // Gets information for all the fields for a given type // Arguments: // input: thExact - used to determine whether we need to get statics base tables // thApprox - used to get the field desc iterator // pAppDomain - used to get statics base tables // output: // pFieldList - contains fields to be initialized // Note: the caller must ensure that *ppFields is NULL (i.e., any previously allocated memory // must have been deallocated. //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::CollectFields(TypeHandle thExact, TypeHandle thApprox, AppDomain * pAppDomain, DacDbiArrayList * pFieldList) { PTR_BYTE pGCStaticsBase = NULL; PTR_BYTE pNonGCStaticsBase = NULL; if (!thExact.IsNull() && !thExact.GetMethodTable()->Collectible()) { // get base tables for static fields GetStaticsBases(thExact, pAppDomain, &pGCStaticsBase, &pNonGCStaticsBase); } unsigned int fieldCount = 0; // we are losing exact type information for static fields in generic types. We have // field desc iterators only for approximate types, but statics are per instantiation, so we // need an exact type to be able to handle these correctly. We need to use // FieldDesc::GetExactDeclaringType to get at the correct field. This requires the exact // TypeHandle. EncApproxFieldDescIterator fdIterator(thApprox.GetMethodTable(), ApproxFieldDescIterator::ALL_FIELDS, FALSE); // don't fixup EnC (we can't, we're stopped) PTR_FieldDesc pCurrentFD; int index = 0; while (((pCurrentFD = fdIterator.Next()) != NULL) && (index < pFieldList->Count())) { // fill in the pCurrentEntry structure ComputeFieldData(pCurrentFD, pGCStaticsBase, pNonGCStaticsBase, &((*pFieldList)[index])); // Bump our counts and pointers. fieldCount++; index++; } _ASSERTE(fieldCount == (unsigned int)pFieldList->Count()); } // DacDbiInterfaceImpl::CollectFields // Determine if a type is a ValueType BOOL DacDbiInterfaceImpl::IsValueType (VMPTR_TypeHandle vmTypeHandle) { DD_ENTER_MAY_THROW; TypeHandle th = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr()); return th.IsValueType(); } // Determine if a type has generic parameters BOOL DacDbiInterfaceImpl::HasTypeParams (VMPTR_TypeHandle vmTypeHandle) { DD_ENTER_MAY_THROW; TypeHandle th = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr()); return th.ContainsGenericVariables(); } // DacDbi API: Get type information for a class void DacDbiInterfaceImpl::GetClassInfo(VMPTR_AppDomain vmAppDomain, VMPTR_TypeHandle vmThExact, ClassInfo * pData) { DD_ENTER_MAY_THROW; AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); TypeHandle thExact; TypeHandle thApprox; GetTypeHandles(vmThExact, vmThExact, &thExact, &thApprox); // initialize field count, generic args count, size and value class flag InitClassData(thApprox, false, pData); if (pAppDomain != NULL) CollectFields(thExact, thApprox, pAppDomain, &(pData->m_fieldList)); } // DacDbiInterfaceImpl::GetClassInfo // DacDbi API: Get field information and object size for an instantiated generic type void DacDbiInterfaceImpl::GetInstantiationFieldInfo (VMPTR_DomainFile vmDomainFile, VMPTR_TypeHandle vmThExact, VMPTR_TypeHandle vmThApprox, DacDbiArrayList * pFieldList, SIZE_T * pObjectSize) { DD_ENTER_MAY_THROW; DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); _ASSERTE(pDomainFile != NULL); AppDomain * pAppDomain = pDomainFile->GetAppDomain(); TypeHandle thExact; TypeHandle thApprox; GetTypeHandles(vmThExact, vmThApprox, &thExact, &thApprox); *pObjectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes(); pFieldList->Alloc(GetTotalFieldCount(thApprox)); CollectFields(thExact, thApprox, pAppDomain, pFieldList); } // DacDbiInterfaceImpl::GetInstantiationFieldInfo //----------------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk member functions //----------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // TypeDataWalk constructor--initialize the buffer and number of remaining items from input data // Arguments: pData - pointer to a list of records containing information about type parameters for an // instantiated type // nData - number of entries in pData //----------------------------------------------------------------------------- DacDbiInterfaceImpl::TypeDataWalk::TypeDataWalk(DebuggerIPCE_TypeArgData * pData, unsigned int nData) { m_pCurrentData = pData; m_nRemaining = nData; } // DacDbiInterfaceImpl::TypeDataWalk::TypeDataWalk //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ReadOne // read and return a single node from the list of type parameters // Arguments: none (uses internal state) // Return value: information about the next type parameter in m_pCurrentData //----------------------------------------------------------------------------- DebuggerIPCE_TypeArgData * DacDbiInterfaceImpl::TypeDataWalk::ReadOne() { LIMITED_METHOD_CONTRACT; if (m_nRemaining) { m_nRemaining--; return m_pCurrentData++; } else { return NULL; } } // DacDbiInterfaceImpl::TypeDataWalk::ReadOne //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::Skip // Skip a single node from the list of type handles along with any children it might have // Arguments: none (uses internal state) // Return value: none (updates internal state) //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::TypeDataWalk::Skip() { LIMITED_METHOD_CONTRACT; DebuggerIPCE_TypeArgData * pData = ReadOne(); if (pData) { for (unsigned int i = 0; i < pData->numTypeArgs; i++) { Skip(); } } } // DacDbiInterfaceImpl::TypeDataWalk::Skip //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeArg // Read a type handle when it is used in the position of a generic argument or // argument of an array or address type. Take into account generic code sharing if we // have been requested to find the canonical representation amongst a set of shared- // code generic types. That is, if generics code sharing is enabled then return "Object" // for all reference types, and canonicalize underneath value types, e.g. V --> V. // Return TypeHandle() if any of the type handles are not loaded. // // Arguments: retrieveWhich - indicates whether to retrieve a canonical representation or // an exact representation // Return value: the type handle for the type parameter //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeArg(TypeHandleReadType retrieveWhich) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; #if !defined(FEATURE_SHARE_GENERIC_CODE) return ReadLoadedTypeHandle(kGetExact); #else if (retrieveWhich == kGetExact) return ReadLoadedTypeHandle(kGetExact); // This nasty bit of code works out what the "canonicalization" of a // parameter to a generic is once we take into account generics code sharing. // // This logic is somewhat a duplication of logic in vm\typehandle.cpp, though // that logic operates on a TypeHandle format, i.e. assumes we're finding the // canonical form of a type that has already been loaded. Here we are finding // the canonical form of a type that may not have been loaded (but where we expect // its canonical form to have been loaded). // // Ideally this logic would not be duplicated in this way, but it is difficult // to arrange for that. DebuggerIPCE_TypeArgData * pData = ReadOne(); if (!pData) return TypeHandle(); // If we have code sharing then the process of canonicalizing is trickier. // unfortunately we have to include the exact specification of compatibility at // this point. CorElementType elementType = pData->data.elementType; switch (elementType) { case ELEMENT_TYPE_PTR: _ASSERTE(pData->numTypeArgs == 1); return PtrOrByRefTypeArg(pData, retrieveWhich); break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_VALUETYPE: return ClassTypeArg(pData, retrieveWhich); break; case ELEMENT_TYPE_FNPTR: return FnPtrTypeArg(pData, retrieveWhich); break; default: return ObjRefOrPrimitiveTypeArg(pData, elementType); break; } #endif // FEATURE_SHARE_GENERIC_CODE } // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeArg //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandles // Iterate through the type argument data, creating type handles as we go. // // Arguments: // input: retrieveWhich - indicates whether we can return a canonical type handle // or we must return an exact type handle // nTypeArgs - number of type arguments to be read // output: ppResults - pointer to a list of TypeHandles that will hold the type handles // for each type parameter // // Return Value: FALSE iff any of the type handles are not loaded. //----------------------------------------------------------------------------- BOOL DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandles(TypeHandleReadType retrieveWhich, unsigned int nTypeArgs, TypeHandle * ppResults) { WRAPPER_NO_CONTRACT; BOOL allOK = true; for (unsigned int i = 0; i < nTypeArgs; i++) { ppResults[i] = ReadLoadedTypeArg(retrieveWhich); allOK &= !ppResults[i].IsNull(); } return allOK; } // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandles //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedInstantiation // Read an instantiation of a generic type if it has already been created. // // Arguments: // input: retrieveWhich - indicates whether we can return a canonical type handle // or we must return an exact type handle // pModule - module in which the instantiated type is loaded // mdToken - metadata token for the type // nTypeArgs - number of type arguments to be read // Return value: the type handle for the instantiated type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedInstantiation(TypeHandleReadType retrieveWhich, Module * pModule, mdTypeDef mdToken, unsigned int nTypeArgs) { WRAPPER_NO_CONTRACT; NewHolder pInst(new TypeHandle[nTypeArgs]); // get the type handle for each of the type parameters if (!ReadLoadedTypeHandles(retrieveWhich, nTypeArgs, pInst)) { return TypeHandle(); } // get the type handle for the particular instantiation that corresponds to // the given type parameters return FindLoadedInstantiation(pModule, mdToken, nTypeArgs, pInst); } // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedInstantiation //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandle // // Compute the type handle for a given type. // This is the top-level function that will return the type handle for an // arbitrary type. It uses mutual recursion with ReadLoadedTypeArg to get // the type handle for a (possibly parameterized) type. Note that the referent of // address types or the element type of an array type are viewed as type parameters. // // For example, assume that we are retrieving only exact types, and we have as our // top level type an array defined as int [][]. // We start by noting that the type is an array type, so we call ReadLoadedTypeArg to // get the element type. We find that the element type is also an array:int []. // ReadLoadedTypeArg will call ReadLoadedTypeHandle with this type information. // Again, we determine that the top-level type is an array, so we call ReadLoadedTypeArg // to get the element type, int. ReadLoadedTypeArg will again call ReadLoadedTypeHandle // which will find that this time, the top-level type is a primitive type. It will request // the loaded type handle from the loader and return it. On return, we get the type handle // for an array of int from the loader. We return again and request the type handle for an // array of arrays of int. This is the type handle we will return. // // Arguments: // input: retrieveWhich - determines whether we can return the type handle for // a canonical type or only for an exact type // we use the list of type data stored in the TypeDataWalk data members // for other input information // Return value: type handle for the current type. //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandle(TypeHandleReadType retrieveWhich) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; // get the type information at the head of the list m_pCurrentData DebuggerIPCE_TypeArgData * pData = ReadOne(); if (!pData) return TypeHandle(); // get the type handle that corresponds to its elementType TypeHandle typeHandle; switch (pData->data.elementType) { case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: typeHandle = ArrayTypeArg(pData, retrieveWhich); break; case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_BYREF: typeHandle = PtrOrByRefTypeArg(pData, retrieveWhich); break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_VALUETYPE: { Module * pModule = pData->data.ClassTypeData.vmModule.GetDacPtr(); typeHandle = ReadLoadedInstantiation(retrieveWhich, pModule, pData->data.ClassTypeData.metadataToken, pData->numTypeArgs); } break; case ELEMENT_TYPE_FNPTR: { typeHandle = FnPtrTypeArg(pData, retrieveWhich); } break; default: typeHandle = FindLoadedElementType(pData->data.elementType); break; } return typeHandle; } // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ArrayTypeArg // get a loaded type handle for an array type (E_T_ARRAY or E_T_SZARRAY) // // Arguments: // input: pArrayTypeInfo - type information for an array type // Although this is in fact a pointer (in)to a list, we treat it here // simply as a pointer to a single instance of DebuggerIPCE_TypeArgData // which holds type information for an array. // This is the most recent type node (for an array type) retrieved // by TypeDataWalk::ReadOne(). The call to ReadLoadedTypeArg will // result in call(s) to ReadOne to retrieve one or more type nodes // that are needed to compute the type handle for the // element type of the array. When we return from that call, we pass // pArrayTypeInfo along with arrayElementTypeArg to FindLoadedArrayType // to get the type handle for this particular array type. // Note: // On entry, we know that pArrayTypeInfo is the same as m_pCurrentData - 1, // but by the time we need to use it, this is no longer true. Because // we can't predict how many nodes will be consumed by the call to // ReadLoadedTypeArg, we can't compute this value from the member fields // of TypeDataWalk and therefore pass it as a parameter. // retrieveWhich - determines whether we can return the type handle for // a canonical type or only for an exact type // Return value: the type handle corresponding to the array type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ArrayTypeArg(DebuggerIPCE_TypeArgData * pArrayTypeInfo, TypeHandleReadType retrieveWhich) { TypeHandle arrayElementTypeArg = ReadLoadedTypeArg(retrieveWhich); if (!arrayElementTypeArg.IsNull()) { return FindLoadedArrayType(pArrayTypeInfo->data.elementType, arrayElementTypeArg, pArrayTypeInfo->data.ArrayTypeData.arrayRank); } return TypeHandle(); } // DacDbiInterfaceImpl::TypeDataWalk::ArrayTypeArg //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::PtrOrByRefTypeArg // get a loaded type handle for an address type (E_T_PTR or E_T_BYREF) // // Arguments: // input: pPtrOrByRefTypeInfo - type information for a pointer or byref type // Although this is in fact a pointer (in)to a list, we treat it here // simply as a pointer to a single instance of DebuggerIPCE_TypeArgData // which holds type information for a pointer or byref type. // This is the most recent type node (for a pointer or byref type) retrieved // by TypeDataWalk::ReadOne(). The call to ReadLoadedTypeArg will // result in call(s) to ReadOne to retrieve one or more type nodes // that are needed to compute the type handle for the // referent type of the pointer. When we return from that call, we pass // pPtrOrByRefTypeInfo along with referentTypeArg to FindLoadedPointerOrByrefType // to get the type handle for this particular pointer or byref type. // Note: // On entry, we know that pPtrOrByRefTypeInfo is the same as m_pCurrentData - 1, // but by the time we need to use it, this is no longer true. Because // we can't predict how many nodes will be consumed by the call to // ReadLoadedTypeArg, we can't compute this value from the member fields // of TypeDataWalk and therefore pass it as a parameter. // retrieveWhich - determines whether we can return the type handle for // a canonical type or only for an exact type // Return value: the type handle corresponding to the address type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::PtrOrByRefTypeArg(DebuggerIPCE_TypeArgData * pPtrOrByRefTypeInfo, TypeHandleReadType retrieveWhich) { TypeHandle referentTypeArg = ReadLoadedTypeArg(retrieveWhich); if (!referentTypeArg.IsNull()) { return FindLoadedPointerOrByrefType(pPtrOrByRefTypeInfo->data.elementType, referentTypeArg); } return TypeHandle(); } // DacDbiInterfaceImpl::TypeDataWalk::PtrOrByRefTypeArg //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ClassTypeArg // get a loaded type handle for a class type (E_T_CLASS or E_T_VALUETYPE) // // Arguments: // input: pClassTypeInfo - type information for a class type // Although this is in fact a pointer (in)to a list, we treat it here // simply as a pointer to a single instance of DebuggerIPCE_TypeArgData // which holds type information for a pointer or byref type. // This is the most recent type node (for a pointer or byref type) retrieved // by TypeDataWalk::ReadOne(). The call to ReadLoadedInstantiation will // result in call(s) to ReadOne to retrieve one or more type nodes // that are needed to compute the type handle for the type parameters // for the class. If we can't find an exact loaded type for the class, we will // instead return a canonical method table. In this case, we need to skip // the type parameter information for each actual parameter to the class. // This is necessary because we may be getting a type handle for a class which is // in turn an argument to a parent type. If the parent type has more arguments, we // need to be at the right place in the list when we return. We use // pClassTypeInfo to get the number of type arguments that we need to skip. // retrieveWhich - determines whether we can return the type handle for // a canonical type or only for an exact type // Return value: the type handle corresponding to the class type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ClassTypeArg(DebuggerIPCE_TypeArgData * pClassTypeInfo, TypeHandleReadType retrieveWhich) { Module * pModule = pClassTypeInfo->data.ClassTypeData.vmModule.GetDacPtr(); TypeHandle typeDef = ClassLoader::LookupTypeDefOrRefInModule(pModule, pClassTypeInfo->data.ClassTypeData.metadataToken); if ((!typeDef.IsNull() && typeDef.IsValueType()) || (pClassTypeInfo->data.elementType == ELEMENT_TYPE_VALUETYPE)) { return ReadLoadedInstantiation(retrieveWhich, pModule, pClassTypeInfo->data.ClassTypeData.metadataToken, pClassTypeInfo->numTypeArgs); } else { _ASSERTE(retrieveWhich == kGetCanonical); // skip the instantiation - no need to look at it since the type canonicalizes to "Object" for (unsigned int i = 0; i < pClassTypeInfo->numTypeArgs; i++) { Skip(); } return TypeHandle(g_pCanonMethodTableClass); } }// DacDbiInterfaceImpl::TypeDataWalk::ClassTypeArg //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::FnPtrTypeArg // get a loaded type handle for a function pointer type (E_T_FNPTR) // // Arguments: // input: pFnPtrTypeInfo - type information for a pointer or byref type // Although this is in fact a pointer (in)to a list, we treat it here // simply as a pointer to a single instance of DebuggerIPCE_TypeArgData // which holds type information for a function pointer type. // This is the most recent type node (for a function pointer type) retrieved // by TypeDataWalk::ReadOne(). The call to ReadLoadedTypeHandles will // result in call(s) to ReadOne to retrieve one or more type nodes // that are needed to compute the type handle for the return type and // parameter types of the function. When we return from that call, we pass // pFnPtrTypeInfo along with pInst to FindLoadedFnptrType // to get the type handle for this particular function pointer type. // retrieveWhich - determines whether we can return the type handle for // a canonical type or only for an exact type // Return value: the type handle corresponding to the function pointer type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::FnPtrTypeArg(DebuggerIPCE_TypeArgData * pFnPtrTypeInfo, TypeHandleReadType retrieveWhich) { // allocate space to store a list of type handles, one for the return type and one for each // of the parameter types of the function to which the FnPtr type refers. NewHolder pInst(new TypeHandle[sizeof(TypeHandle) * pFnPtrTypeInfo->numTypeArgs]); if (ReadLoadedTypeHandles(retrieveWhich, pFnPtrTypeInfo->numTypeArgs, pInst)) { return FindLoadedFnptrType(pFnPtrTypeInfo->numTypeArgs, pInst); } return TypeHandle(); } // DacDbiInterfaceImpl::TypeDataWalk::FnPtrTypeArg //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeDataWalk::ObjRefOrPrimitiveTypeArg // get a loaded type handle for a primitive type or ObjRef // // Arguments: // input: pArgInfo - type information for an objref or primitive type. // This is called only when the objref or primitive type // is a type argument for a parent type. In this case, // we treat all objrefs the same, that is, we don't care // about type parameters for the referent. Instead, we will // simply return the canonical object type handle as the type // of the referent. <@dbgtodo Microsoft: why is this?> // If this is a primitive type, we'll simply get the // type handle for that type. // elementType - type of the argument // Return value: the type handle corresponding to the elementType //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ObjRefOrPrimitiveTypeArg(DebuggerIPCE_TypeArgData * pArgInfo, CorElementType elementType) { // If there are any type args (e.g. for arrays) they can be skipped. The thing // is a reference type anyway. for (unsigned int i = 0; i < pArgInfo->numTypeArgs; i++) { Skip(); } // for an ObjRef, just return the CLASS____CANON type handle if (CorTypeInfo::IsObjRef_NoThrow(elementType)) { return TypeHandle(g_pCanonMethodTableClass); } else { return FindLoadedElementType(elementType); } } // DacDbiInterfaceImpl::TypeDataWalk::ObjRefOrPrimitiveTypeArg //------------------------------------------------------------------------- // end of TypeDataWalk implementations //------------------------------------------------------------------------- //------------------------------------------------------------------------- // functions to use loader to get type handles // ------------------------------------------------------------------------ // Note, in these functions, the use of ClassLoader::DontLoadTypes was chosen // instead of FailIfNotLoaded because, although we may want to debug unrestored // VCs, we can't do it because the debug API is not set up to handle them. // //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::FindLoadedArrayType // Use ClassLoader to find a loaded type handle for an array type (E_T_ARRAY or E_T_SZARRAY) // Arguments: // input: arrayType - type of the array // TypeArg - type handle for the base type // rank - array rank // Return Value: type handle for the array type //----------------------------------------------------------------------------- // static TypeHandle DacDbiInterfaceImpl::FindLoadedArrayType(CorElementType arrayType, TypeHandle typeArg, unsigned rank) { // Lookup operations run the class loader in non-load mode. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); if (typeArg.IsNull()) { return TypeHandle(); } else { return ClassLoader::LoadArrayTypeThrowing(typeArg, arrayType, rank, ClassLoader::DontLoadTypes ); } } // DacDbiInterfaceImpl::FindLoadedArrayType; //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::FindLoadedPointerOrByrefType // Use ClassLoader to find a loaded type handle for an address type (E_T_PTR or E_T_BYREF) // Arguments: // input: addressType - type of the address type // TypeArg - type handle for the base type // Return Value: type handle for the address type //----------------------------------------------------------------------------- // static TypeHandle DacDbiInterfaceImpl::FindLoadedPointerOrByrefType(CorElementType addressType, TypeHandle typeArg) { // Lookup operations run the class loader in non-load mode. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); return ClassLoader::LoadPointerOrByrefTypeThrowing(addressType, typeArg, ClassLoader::DontLoadTypes); } // DacDbiInterfaceImpl::FindLoadedPointerOrByrefType //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::FindLoadedFnptrType // Use ClassLoader to find a loaded type handle for a function pointer type (E_T_FNPTR) // Arguments: // input: pInst - type handles of the function's return value and arguments // numTypeArgs - number of type handles in pInst // Return Value: type handle for the function pointer type //----------------------------------------------------------------------------- // static TypeHandle DacDbiInterfaceImpl::FindLoadedFnptrType(DWORD numTypeArgs, TypeHandle * pInst) { // Lookup operations run the class loader in non-load mode. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // @dbgtodo : Do we need to worry about calling convention here? // LoadFnptrTypeThrowing expects the count of arguments, not // including return value, so we subtract 1 from numTypeArgs. return ClassLoader::LoadFnptrTypeThrowing(0, numTypeArgs - 1, pInst, ClassLoader::DontLoadTypes); } // DacDbiInterfaceImpl::FindLoadedFnptrType //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::FindLoadedInstantiation // Use ClassLoader to find a loaded type handle for a particular instantiation of a // class type (E_T_CLASS or E_T_VALUECLASS) // // Arguments: // input: pModule - module in which the type is loaded // mdToken - metadata token for the type // nTypeArgs - number of type arguments in pInst // pInst - list of type handles for the type parameters // Return value: type handle for the instantiated class type //----------------------------------------------------------------------------- // static TypeHandle DacDbiInterfaceImpl::FindLoadedInstantiation(Module * pModule, mdTypeDef mdToken, DWORD nTypeArgs, TypeHandle * pInst) { // Lookup operations run the class loader in non-load mode. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); return ClassLoader::LoadGenericInstantiationThrowing(pModule, mdToken, Instantiation(pInst,nTypeArgs), ClassLoader::DontLoadTypes); } // DacDbiInterfaceImpl::FindLoadedInstantiation //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::FindLoadedElementType // Get the type handle for a primitive type // Arguments: // input: elementType - type of the primitive type // Return Value: Type handle for the primitive type //----------------------------------------------------------------------------- // static TypeHandle DacDbiInterfaceImpl::FindLoadedElementType(CorElementType elementType) { // Lookup operations run the class loader in non-load mode. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); MethodTable * pMethodTable = (&g_Mscorlib)->GetElementType(elementType); return TypeHandle(pMethodTable); } // DacDbiInterfaceImpl::FindLoadedElementType //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetArrayTypeInfo // Gets additional information to convert a type handle to an instance of CordbType if the type is E_T_ARRAY. // Specifically, we get the rank and the type of the array elements // // Arguments: // input: typeHandle - type handle for the array type // pAppDomain - AppDomain into which the type is loaded // output: pTypeInfo - information for the array rank and element type // //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetArrayTypeInfo(TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo, AppDomain * pAppDomain) { _ASSERTE(typeHandle.IsArray()); pTypeInfo->ArrayTypeData.arrayRank = typeHandle.AsArray()->GetRank(); TypeHandleToBasicTypeInfo(typeHandle.AsArray()->GetArrayElementTypeHandle(), &(pTypeInfo->ArrayTypeData.arrayTypeArg), pAppDomain); } // DacDbiInterfaceImpl::GetArrayTypeInfo //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetPtrTypeInfo // Gets additional information to convert a type handle to an instance of CordbType if the type is // E_T_PTR or E_T_BYREF. Specifically, we get the type for the referent of the address type // // Arguments: // input: boxed - indicates what, if anything, is boxed (see code:AreValueTypesBoxed for // more specific information) // typeHandle - type handle for the address type // pAppDomain - AppDomain into which the type is loaded // output: pTypeInfo - information for the referent type // //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetPtrTypeInfo(AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo, AppDomain * pAppDomain) { if (boxed == AllBoxed) { GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain); } else { _ASSERTE(typeHandle.IsTypeDesc()); TypeHandleToBasicTypeInfo(typeHandle.AsTypeDesc()->GetTypeParam(), &(pTypeInfo->UnaryTypeData.unaryTypeArg), pAppDomain); } } // DacDbiInterfaceImpl::GetPtrTypeInfo //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetFnPtrTypeInfo // Gets additional information to convert a type handle to an instance of CordbType if the type is // E_T_FNPTR, specifically the typehandle for the referent. // // Arguments // input: boxed - indicates what, if anything, is boxed (see code:AreValueTypesBoxed for // more specific information) // typeHandle - type handle for the address type // pAppDomain - AppDomain into which the type is loaded // output: pTypeInfo - information for the referent type // //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetFnPtrTypeInfo(AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo, AppDomain * pAppDomain) { if (boxed == AllBoxed) { GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain); } else { pTypeInfo->NaryTypeData.typeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); } } // DacDbiInterfaceImpl::GetFnPtrTypeInfo //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetClassTypeInfo // Gets additional information to convert a type handle to an instance of CordbType if the type is // E_T_CLASS or E_T_VALUETYPE // // Arguments // input: typeHandle - type handle for the address type // pAppDomain - AppDomain into which the type is loaded // output: pTypeInfo - information for the referent type // //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetClassTypeInfo(TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo, AppDomain * pAppDomain) { Module * pModule = typeHandle.GetModule(); if (typeHandle.HasInstantiation()) // the type handle represents a generic instantiation { pTypeInfo->ClassTypeData.typeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); } else // non-generic { pTypeInfo->ClassTypeData.typeHandle = VMPTR_TypeHandle::NullPtr(); } pTypeInfo->ClassTypeData.metadataToken = typeHandle.GetCl(); _ASSERTE(pModule); pTypeInfo->ClassTypeData.vmModule.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule)); if (pAppDomain) { pTypeInfo->ClassTypeData.vmDomainFile.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule->GetDomainFile(pAppDomain))); } else { pTypeInfo->ClassTypeData.vmDomainFile = VMPTR_DomainFile::NullPtr(); } } // DacDbiInterfaceImpl::GetClassTypeInfo //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetElementType // Gets the correct CorElementType value from a type handle // // Arguments // input: typeHandle - type handle for the address type // Return Value: the CorElementType enum value for the type handle //----------------------------------------------------------------------------- CorElementType DacDbiInterfaceImpl::GetElementType (TypeHandle typeHandle) { if (typeHandle.IsNull()) { return ELEMENT_TYPE_VOID; } else if (typeHandle.GetMethodTable() == g_pObjectClass) { return ELEMENT_TYPE_OBJECT; } else if (typeHandle.GetMethodTable() == g_pStringClass) { return ELEMENT_TYPE_STRING; } else { // GetSignatureCorElementType returns E_T_CLASS for E_T_STRING... :-( return typeHandle.GetSignatureCorElementType(); } } // DacDbiInterfaceImpl::GetElementType //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::TypeHandleToBasicTypeInfo // Gets additional information to convert a type handle to an instance of CordbType for the referent of an // E_T_BYREF or E_T_PTR or for the element type of an E_T_ARRAY or E_T_SZARRAY // // Arguments: // input: typeHandle - type handle for the address type // pAppDomain - AppDomain into which the type is loaded // output: pTypeInfo - information for the referent type // //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::TypeHandleToBasicTypeInfo(TypeHandle typeHandle, DebuggerIPCE_BasicTypeData * pTypeInfo, AppDomain * pAppDomain) { pTypeInfo->elementType = GetElementType(typeHandle); switch (pTypeInfo->elementType) { case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: case ELEMENT_TYPE_FNPTR: case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_BYREF: pTypeInfo->vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); pTypeInfo->metadataToken = mdTokenNil; pTypeInfo->vmDomainFile = VMPTR_DomainFile::NullPtr(); break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_VALUETYPE: { Module * pModule = typeHandle.GetModule(); if (typeHandle.HasInstantiation()) // only set if instantiated { pTypeInfo->vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); } else { pTypeInfo->vmTypeHandle = VMPTR_TypeHandle::NullPtr(); } pTypeInfo->metadataToken = typeHandle.GetCl(); _ASSERTE(pModule); pTypeInfo->vmModule.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule)); if (pAppDomain) { pTypeInfo->vmDomainFile.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule->GetDomainFile(pAppDomain))); } else { pTypeInfo->vmDomainFile = VMPTR_DomainFile::NullPtr(); } break; } default: pTypeInfo->vmTypeHandle = VMPTR_TypeHandle::NullPtr(); pTypeInfo->metadataToken = mdTokenNil; pTypeInfo->vmDomainFile = VMPTR_DomainFile::NullPtr(); break; } return; } // DacDbiInterfaceImpl::TypeHandleToBasicTypeInfo void DacDbiInterfaceImpl::GetObjectExpandedTypeInfoFromID(AreValueTypesBoxed boxed, VMPTR_AppDomain vmAppDomain, COR_TYPEID id, DebuggerIPCE_ExpandedTypeData *pTypeInfo) { DD_ENTER_MAY_THROW; PTR_MethodTable pMT(TO_TADDR(id.token1)); if (pMT->IsArray()) { // ArrayBase::GetTypeHandle() may return a NULL handle in corner case scenarios. This check prevents // us from an AV but doesn't actually fix the problem. See DevDiv 653441 for more info. TypeHandle arrayHandle = ArrayBase::GetTypeHandle(pMT); if (arrayHandle.IsNull()) { ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, arrayHandle, pTypeInfo); } else { TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, TypeHandle::FromPtr(TO_TADDR(id.token1)), pTypeInfo); } } void DacDbiInterfaceImpl::GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed, VMPTR_AppDomain vmAppDomain, CORDB_ADDRESS addr, DebuggerIPCE_ExpandedTypeData *pTypeInfo) { DD_ENTER_MAY_THROW; PTR_Object obj(TO_TADDR(addr)); TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, obj->GetGCSafeTypeHandle(), pTypeInfo); } // DacDbi API: use a type handle to get the information needed to create the corresponding RS CordbType instance void DacDbiInterfaceImpl::TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, VMPTR_AppDomain vmAppDomain, VMPTR_TypeHandle vmTypeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo) { DD_ENTER_MAY_THROW; TypeHandle typeHandle = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr()); TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, typeHandle, pTypeInfo); } void DacDbiInterfaceImpl::TypeHandleToExpandedTypeInfoImpl(AreValueTypesBoxed boxed, VMPTR_AppDomain vmAppDomain, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData * pTypeInfo) { AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); pTypeInfo->elementType = GetElementType(typeHandle); switch (pTypeInfo->elementType) { case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: GetArrayTypeInfo(typeHandle, pTypeInfo, pAppDomain); break; case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_BYREF: GetPtrTypeInfo(boxed, typeHandle, pTypeInfo, pAppDomain); break; case ELEMENT_TYPE_VALUETYPE: if (boxed == OnlyPrimitivesUnboxed || boxed == AllBoxed) { pTypeInfo->elementType = ELEMENT_TYPE_CLASS; } GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain); break; case ELEMENT_TYPE_CLASS: GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain); break; case ELEMENT_TYPE_FNPTR: GetFnPtrTypeInfo(boxed, typeHandle, pTypeInfo, pAppDomain); break; default: if (boxed == AllBoxed) { pTypeInfo->elementType = ELEMENT_TYPE_CLASS; GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain); } // else the element type is sufficient break; } LOG((LF_CORDB, LL_INFO10000, "D::THTETI: converted left-side type handle to expanded right-side type info, pTypeInfo->ClassTypeData.typeHandle = 0x%08x.\n", pTypeInfo->ClassTypeData.typeHandle.GetRawPtr())); return; } // DacDbiInterfaceImpl::TypeHandleToExpandedTypeInfo // Get type handle for a TypeDef token, if one exists. For generics this returns the open type. VMPTR_TypeHandle DacDbiInterfaceImpl::GetTypeHandle(VMPTR_Module vmModule, mdTypeDef metadataToken) { DD_ENTER_MAY_THROW; Module* pModule = vmModule.GetDacPtr(); VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); TypeHandle th = ClassLoader::LookupTypeDefOrRefInModule(pModule, metadataToken); if (th.IsNull()) { LOG((LF_CORDB, LL_INFO10000, "D::GTH: class isn't loaded.\n")); ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } vmTypeHandle.SetDacTargetPtr(th.AsTAddr()); return vmTypeHandle; } // DacDbi API: GetAndSendApproxTypeHandle finds the type handle for the layout of the instance fields of an // instantiated type if it is available. VMPTR_TypeHandle DacDbiInterfaceImpl::GetApproxTypeHandle(TypeInfoList * pTypeData) { DD_ENTER_MAY_THROW; LOG((LF_CORDB, LL_INFO10000, "D::GATH: getting info.\n")); TypeDataWalk walk(&((*pTypeData)[0]), pTypeData->Count()); TypeHandle typeHandle = walk.ReadLoadedTypeHandle(TypeDataWalk::kGetCanonical); VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); if (!typeHandle.IsNull()) { vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); } else { ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } LOG((LF_CORDB, LL_INFO10000, "D::GATH: sending result, result = 0x%0x8\n", typeHandle)); return vmTypeHandle; } // DacDbiInterfaceImpl::GetApproxTypeHandle // DacDbiInterface API: Get the exact type handle from type data HRESULT DacDbiInterfaceImpl::GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData * pTypeData, ArgInfoList * pArgInfo, VMPTR_TypeHandle& vmTypeHandle) { DD_ENTER_MAY_THROW; LOG((LF_CORDB, LL_INFO10000, "D::GETH: getting info.\n")); HRESULT hr = S_OK; EX_TRY { vmTypeHandle = vmTypeHandle.NullPtr(); // convert the type information to a type handle TypeHandle typeHandle = ExpandedTypeInfoToTypeHandle(pTypeData, pArgInfo); _ASSERTE(!typeHandle.IsNull()); vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr()); } EX_CATCH_HRESULT(hr); return hr; } // DacDbiInterfaceImpl::GetExactTypeHandle // Retrieve the generic type params for a given MethodDesc. This function is specifically // for stackwalking because it requires the generic type token on the stack. void DacDbiInterfaceImpl::GetMethodDescParams( VMPTR_AppDomain vmAppDomain, VMPTR_MethodDesc vmMethodDesc, GENERICS_TYPE_TOKEN genericsToken, UINT32 * pcGenericClassTypeParams, TypeParamsList * pGenericTypeParams) { DD_ENTER_MAY_THROW; if (vmAppDomain.IsNull() || vmMethodDesc.IsNull()) { ThrowHR(E_INVALIDARG); } _ASSERTE((pcGenericClassTypeParams != NULL) && (pGenericTypeParams != NULL)); MethodDesc * pMD = vmMethodDesc.GetDacPtr(); // Retrieve the number of type parameters for the class and // the number of type parameters for the method itself. // For example, the method Foo::Bar() has 2 class type parameters and 1 method type parameters. UINT32 cGenericClassTypeParams = pMD->GetNumGenericClassArgs(); UINT32 cGenericMethodTypeParams = pMD->GetNumGenericMethodArgs(); UINT32 cTotalGenericTypeParams = cGenericClassTypeParams + cGenericMethodTypeParams; // Set the out parameter. *pcGenericClassTypeParams = cGenericClassTypeParams; TypeHandle thSpecificClass; MethodDesc * pSpecificMethod; // Try to retrieve a more specific MethodDesc and TypeHandle via the generics type token. // The generics token is not always guaranteed to be available. // For example, it may be unavailable in prologs and epilogs. // In dumps, not available can also mean a thrown exception for missing memory. BOOL fExact = FALSE; ALLOW_DATATARGET_MISSING_MEMORY( fExact = Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation( pMD, PTR_VOID((TADDR)genericsToken), &thSpecificClass, &pSpecificMethod); ); if (!fExact || !thSpecificClass.GetMethodTable()->SanityCheck() || !pSpecificMethod->GetMethodTable()->SanityCheck()) { // Use the canonical MethodTable and MethodDesc if the exact generics token is not available. thSpecificClass = TypeHandle(pMD->GetMethodTable()); pSpecificMethod = pMD; } // Retrieve the array of class type parameters and the array of method type parameters. Instantiation classInst = pSpecificMethod->GetExactClassInstantiation(thSpecificClass); Instantiation methodInst = pSpecificMethod->GetMethodInstantiation(); _ASSERTE((classInst.IsEmpty()) == (cGenericClassTypeParams == 0)); _ASSERTE((methodInst.IsEmpty()) == (cGenericMethodTypeParams == 0)); // allocate memory for the return array pGenericTypeParams->Alloc(cTotalGenericTypeParams); for (UINT32 i = 0; i < cTotalGenericTypeParams; i++) { // Retrieve the current type parameter depending on the index. TypeHandle thCurrent; if (i < cGenericClassTypeParams) { thCurrent = classInst[i]; } else { thCurrent = methodInst[i - cGenericClassTypeParams]; } // There is the possiblity that we'll get this far with a dump and not fail, but still // not be able to get full info for a particular param. EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // Fill in the struct using the TypeHandle of the current type parameter if we can. VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); vmTypeHandle.SetDacTargetPtr(thCurrent.AsTAddr()); TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, vmAppDomain, vmTypeHandle, &((*pGenericTypeParams)[i])); } EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // On failure for a particular type, default it back to System.__Canon. VMPTR_TypeHandle vmTHCanon = VMPTR_TypeHandle::NullPtr(); TypeHandle thCanon = TypeHandle(g_pCanonMethodTableClass); vmTHCanon.SetDacTargetPtr(thCanon.AsTAddr()); TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, vmAppDomain, vmTHCanon, &((*pGenericTypeParams)[i])); } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER } } //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetClassOrValueTypeHandle // get a typehandle for a class or valuetype from basic type data (metadata token // and domain file). // Arguments: // input: pData - contains the metadata token and domain file // Return value: the type handle for the corresponding type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::GetClassOrValueTypeHandle(DebuggerIPCE_BasicTypeData * pData) { TypeHandle typeHandle; // if we already have a type handle, just return it if (!pData->vmTypeHandle.IsNull()) { typeHandle = TypeHandle::FromPtr(pData->vmTypeHandle.GetDacPtr()); } // otherwise, have the loader look it up using the metadata token and domain file else { DomainFile * pDomainFile = pData->vmDomainFile.GetDacPtr(); Module * pModule = pDomainFile->GetModule(); typeHandle = ClassLoader::LookupTypeDefOrRefInModule(pModule, pData->metadataToken); if (typeHandle.IsNull()) { LOG((LF_CORDB, LL_INFO10000, "D::BTITTH: class isn't loaded.\n")); ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } _ASSERTE(typeHandle.GetNumGenericArgs() == 0); } return typeHandle; } // DacDbiInterfaceImpl::GetClassOrValueTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetExactArrayTypeHandle // get an exact type handle for an array type // Arguments: // input: pTopLevelTypeData - type information for a top-level array type // pArgInfo - contains the following information: // m_genericArgsCount - number of generic parameters for the element type--this should be 1 // m_pGenericArgs - pointer to the generic parameter for the element type--this is // effectively a one-element list. These are the actual parameters // Return Value: the exact type handle for the type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::GetExactArrayTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData, ArgInfoList * pArgInfo) { TypeHandle typeArg; _ASSERTE(pArgInfo->Count() == 1); // get the type handle for the element type typeArg = BasicTypeInfoToTypeHandle(&((*pArgInfo)[0])); // get the exact type handle for the array type return FindLoadedArrayType(pTopLevelTypeData->elementType, typeArg, pTopLevelTypeData->ArrayTypeData.arrayRank); } // DacDbiInterfaceImpl::GetExactArrayTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetExactPtrOrByRefTypeHandle // get an exact type handle for a PTR or BYREF type // Arguments: // input: pTopLevelTypeData - type information for the PTR or BYREF type // pArgInfo - contains the following information: // m_genericArgsCount - number of generic parameters for the element type--this should be 1 // m_pGenericArgs - pointer to the generic parameter for the element type--this is // effectively a one-element list. These are the actual parameters // Return Value: the exact type handle for the type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::GetExactPtrOrByRefTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData, ArgInfoList * pArgInfo) { TypeHandle typeArg; _ASSERTE(pArgInfo->Count() == 1); // get the type handle for the referent typeArg = BasicTypeInfoToTypeHandle(&((*pArgInfo)[0])); // get the exact type handle for the PTR or BYREF type return FindLoadedPointerOrByrefType(pTopLevelTypeData->elementType, typeArg); } // DacDbiInterfaceImpl::GetExactPtrOrByRefTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetExactClassTypeHandle // get an exact type handle for a CLASS or VALUETYPE type // Arguments: // input: pTopLevelTypeData - type information for the CLASS or VALUETYPE type // pArgInfo - contains the following information: // m_genericArgsCount - number of generic parameters for the class // m_pGenericArgs - list of generic parameters for the class--these // are the actual parameters // Return Value: the exact type handle for the type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::GetExactClassTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData, ArgInfoList * pArgInfo) { Module * pModule = pTopLevelTypeData->ClassTypeData.vmModule.GetDacPtr(); int argCount = pArgInfo->Count(); TypeHandle typeConstructor = ClassLoader::LookupTypeDefOrRefInModule(pModule, pTopLevelTypeData->ClassTypeData.metadataToken); // If we can't find the class, throw the appropriate HR. Note: if the class is not a value class and // the class is also not restored, then we must pretend that the class is still not loaded. We are gonna let // unrestored value classes slide, though, and special case access to the class's parent below. if (typeConstructor.IsNull()) { LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: class isn't loaded.\n")); ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } // if there are no generic parameters, we already have the correct type handle if (argCount == 0) { return typeConstructor; } // we have generic parameters--first validate we have a number consistent with the list // of parameters we received if ((unsigned int)argCount != typeConstructor.GetNumGenericArgs()) { LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: wrong number of type parameters, %d given, %d expected\n", argCount, typeConstructor.GetNumGenericArgs())); _ASSERTE((unsigned int)argCount == typeConstructor.GetNumGenericArgs()); ThrowHR(E_FAIL); } // now we allocate a list to store the type handles for each parameter S_UINT32 allocSize = S_UINT32(argCount) * S_UINT32(sizeof(TypeHandle)); if (allocSize.IsOverflow()) { ThrowHR(E_OUTOFMEMORY); } NewHolder pInst(new TypeHandle[allocSize.Value()]); // convert the type information for each parameter to its corresponding type handle // and store it in the list for (unsigned int i = 0; i < (unsigned int)argCount; i++) { pInst[i] = BasicTypeInfoToTypeHandle(&((*pArgInfo)[i])); } // Finally, we find the type handle corresponding to this particular instantiation return FindLoadedInstantiation(typeConstructor.GetModule(), typeConstructor.GetCl(), argCount, pInst); } // DacDbiInterfaceImpl::GetExactClassTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetExactFnPtrTypeHandle // get an exact type handle for a FNPTR type // Arguments: // input: pArgInfo - Contains the following information: // m_genericArgsCount - number of generic parameters for the referent // m_pGenericArgs - list of generic parameters for the referent--these // are the actual parameters for the function signature // Return Value: the exact type handle for the type //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::GetExactFnPtrTypeHandle(ArgInfoList * pArgInfo) { // allocate a list to store the type handles for each parameter S_UINT32 allocSize = S_UINT32(pArgInfo->Count()) * S_UINT32(sizeof(TypeHandle)); if( allocSize.IsOverflow() ) { ThrowHR(E_OUTOFMEMORY); } NewHolder pInst(new TypeHandle[allocSize.Value()]); // convert the type information for each parameter to its corresponding type handle // and store it in the list for (int i = 0; i < pArgInfo->Count(); i++) { pInst[i] = BasicTypeInfoToTypeHandle(&((*pArgInfo)[i])); } // find the type handle corresponding to this particular FNPTR return FindLoadedFnptrType(pArgInfo->Count(), pInst); } // DacDbiInterfaceImpl::GetExactFnPtrTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::BasicTypeInfoToTypeHandle // Convert basic type info for a type parameter that came from a top-level type to // the corresponding type handle. If the type parameter is an array or pointer // type, we simply extract the LS type handle from the VMPTR_TypeHandle that is // part of the type information. If the type parameter is a class or value type, // we use the metadata token and domain file in the type info to look up the // appropriate type handle. If the type parameter is any other types, we get the // type handle by having the loader look up the type handle for the element type. // Arguments: // input: pArgTypeData - basic type information for the type. // Return Value: the type handle for the type. //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData * pArgTypeData) { LOG((LF_CORDB, LL_INFO10000, "D::BTITTH: expanding basic right-side type to left-side type, ELEMENT_TYPE: %d.\n", pArgTypeData->elementType)); TypeHandle typeHandle = TypeHandle(); switch (pArgTypeData->elementType) { case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_BYREF: case ELEMENT_TYPE_FNPTR: _ASSERTE(!pArgTypeData->vmTypeHandle.IsNull()); typeHandle = TypeHandle::FromPtr(pArgTypeData->vmTypeHandle.GetDacPtr()); break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_VALUETYPE: typeHandle = GetClassOrValueTypeHandle(pArgTypeData); break; default: typeHandle = FindLoadedElementType(pArgTypeData->elementType); break; } if (typeHandle.IsNull()) { ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } return typeHandle; } // DacDbiInterfaceImpl::BasicTypeInfoToTypeHandle //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::ExpandedTypeInfoToTypeHandle // Convert type information for a top-level type to an exact type handle. This // information includes information about the element type if the top-level type is // an array type, the referent if the top-level type is a pointer type, or actual // parameters if the top-level type is a generic class or value type. // Arguments: // input: pTopLevelTypeData - type information for the top-level type // pArgInfo - contains the following information: // m_genericArtsCount - number of parameters // m_pGenericArgs - list of actual parameters // Return Value: the exact type handle corresponding to the type represented by // pTopLevelTypeData //----------------------------------------------------------------------------- TypeHandle DacDbiInterfaceImpl::ExpandedTypeInfoToTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData, ArgInfoList * pArgInfo) { WRAPPER_NO_CONTRACT; LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: expanding right-side type to left-side type, ELEMENT_TYPE: %d.\n", pData->elementType)); TypeHandle typeHandle = TypeHandle(); // depending on the top-level type, get the type handle incorporating information about any type arguments switch (pTopLevelTypeData->elementType) { case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: typeHandle = GetExactArrayTypeHandle(pTopLevelTypeData, pArgInfo); break; case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_BYREF: typeHandle = GetExactPtrOrByRefTypeHandle(pTopLevelTypeData, pArgInfo); break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_VALUETYPE: typeHandle = GetExactClassTypeHandle(pTopLevelTypeData, pArgInfo); break; case ELEMENT_TYPE_FNPTR: typeHandle = GetExactFnPtrTypeHandle(pArgInfo); break; default: typeHandle = FindLoadedElementType(pTopLevelTypeData->elementType); break; } // end switch (pData->elementType) if (typeHandle.IsNull()) { // This may fail because there are cases when a type can be used (and so visible to the // debugger), but not yet loaded to the point of being available in the EETypeHashTable. // For example, generic value types (without explicit constructors) may not need their // exact instantiation type to be loaded in order to be used as a field of an object // created on the heap LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: type isn't loaded.\n")); ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } return typeHandle; } // DacDbiInterfaceImpl::ExpandedTypeInfoToTypeHandle // ---------------------------------------------------------------------------- // DacDbi API: GetThreadOrContextStaticAddress // Get the target field address of a context or thread local static. // // Notes: // The address is constant and could be cached. // // If this is a context-static, the function uses the thread's current context. // [This is important because it means that you can't lookup a context static // unless you have a thread in that context. If anybody actually cared about contexts, // we might have to revise this in the future] // // This can commonly fail, in which case, it will return NULL. // ---------------------------------------------------------------------------- CORDB_ADDRESS DacDbiInterfaceImpl::GetThreadOrContextStaticAddress(VMPTR_FieldDesc vmField, VMPTR_Thread vmRuntimeThread) { DD_ENTER_MAY_THROW; Thread * pRuntimeThread = vmRuntimeThread.GetDacPtr(); PTR_FieldDesc pFieldDesc = vmField.GetDacPtr(); TADDR fieldAddress = NULL; _ASSERTE(pRuntimeThread != NULL); // Find out whether the field is thread local or context local and get its // address. if (pFieldDesc->IsThreadStatic()) { fieldAddress = pRuntimeThread->GetStaticFieldAddrNoCreate(pFieldDesc, NULL); } #ifdef FEATURE_REMOTING else if (pFieldDesc->IsContextStatic()) { fieldAddress = PTR_TO_TADDR(pRuntimeThread->GetContext()->GetStaticFieldAddrNoCreate(pFieldDesc)); } #endif else { // In case we have more special cases added later, this will allow us to notice the need to // update this function. ThrowHR(E_NOTIMPL); } return fieldAddress; } // DacDbiInterfaceImpl::GetThreadOrContextStaticAddress // Get the target field address of a collectible types static. CORDB_ADDRESS DacDbiInterfaceImpl::GetCollectibleTypeStaticAddress(VMPTR_FieldDesc vmField, VMPTR_AppDomain vmAppDomain) { DD_ENTER_MAY_THROW; AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); PTR_FieldDesc pFieldDesc = vmField.GetDacPtr(); _ASSERTE(pAppDomain != NULL); // // Verify this field is of the right type // if(!pFieldDesc->IsStatic() || pFieldDesc->IsSpecialStatic()) { _ASSERTE(!"BUG: Unsupported static field type for collectible types"); } // // Check that the data is available // /* TODO: Ideally we should be checking if the class is allocated first, however we don't appear to be doing this even for non-collectible statics and we have never seen an issue. */ // // Get the address // PTR_VOID base = pFieldDesc->GetBaseInDomain(pAppDomain); if (base == PTR_NULL) { return PTR_HOST_TO_TADDR(NULL); } // // Store the result and return // PTR_VOID addr = pFieldDesc->GetStaticAddressHandle(base); return PTR_TO_TADDR(addr); } // DacDbiInterfaceImpl::GetCollectibleTypeStaticAddress // DacDbi API: GetTypeHandleParams // - gets the necessary data for a type handle, i.e. its type parameters, e.g. "String" and "List" from the type handle // for "Dict>", and sends it back to the right side. // - pParams is allocated and initialized by this function // - This should not fail except for OOM void DacDbiInterfaceImpl::GetTypeHandleParams(VMPTR_AppDomain vmAppDomain, VMPTR_TypeHandle vmTypeHandle, TypeParamsList * pParams) { DD_ENTER_MAY_THROW TypeHandle typeHandle = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr()); LOG((LF_CORDB, LL_INFO10000, "D::GTHP: getting type parameters for 0x%08x 0x%0x8.\n", vmAppDomain.GetDacPtr(), typeHandle.AsPtr())); // Find the class given its type handle. _ASSERTE(pParams->IsEmpty()); pParams->Alloc(typeHandle.GetNumGenericArgs()); // collect type information for each type parameter for (int i = 0; i < pParams->Count(); ++i) { VMPTR_TypeHandle thInst = VMPTR_TypeHandle::NullPtr(); thInst.SetDacTargetPtr(typeHandle.GetInstantiation()[i].AsTAddr()); TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, vmAppDomain, thInst, &((*pParams)[i])); } LOG((LF_CORDB, LL_INFO10000, "D::GTHP: sending result")); } // DacDbiInterfaceImpl::GetTypeHandleParams //----------------------------------------------------------------------------- // DacDbi API: GetSimpleType // gets the metadata token and domain file corresponding to a simple type //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetSimpleType(VMPTR_AppDomain vmAppDomain, CorElementType simpleType, mdTypeDef *pMetadataToken, VMPTR_Module *pVmModule, VMPTR_DomainFile *pVmDomainFile) { DD_ENTER_MAY_THROW; AppDomain *pAppDomain = vmAppDomain.GetDacPtr(); // if we fail to get either a valid type handle or module, we will want to send back // a NULL domain file too, so we'll to preinitialize this here. _ASSERTE(pVmDomainFile != NULL); *pVmDomainFile = VMPTR_DomainFile::NullPtr(); // FindLoadedElementType will return NULL if the type hasn't been loaded yet. TypeHandle typeHandle = FindLoadedElementType(simpleType); if (typeHandle.IsNull()) { ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } else { _ASSERTE(pMetadataToken != NULL); *pMetadataToken = typeHandle.GetCl(); Module * pModule = typeHandle.GetModule(); if (pModule == NULL) ThrowHR(CORDBG_E_TARGET_INCONSISTENT); pVmModule->SetHostPtr(pModule); if (pAppDomain) { pVmDomainFile->SetHostPtr(pModule->GetDomainFile(pAppDomain)); if (pVmDomainFile->IsNull()) ThrowHR(CORDBG_E_TARGET_INCONSISTENT); } } LOG((LF_CORDB, LL_INFO10000, "D::STI: sending result.\n")); } // DacDbiInterfaceImpl::GetSimpleType BOOL DacDbiInterfaceImpl::IsExceptionObject(VMPTR_Object vmObject) { DD_ENTER_MAY_THROW; Object* objPtr = vmObject.GetDacPtr(); MethodTable* pMT = objPtr->GetMethodTable(); return IsExceptionObject(pMT); } BOOL DacDbiInterfaceImpl::IsExceptionObject(MethodTable* pMT) { PTR_MethodTable pExMT = g_pExceptionClass; TADDR targetMT = dac_cast(pMT); TADDR exceptionMT = dac_cast(pExMT); do { if (targetMT == exceptionMT) return TRUE; pMT = pMT->GetParentMethodTable(); targetMT = dac_cast(pMT); } while (pMT); return FALSE; } void DacDbiInterfaceImpl::GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList& dacStackFrames) { DD_ENTER_MAY_THROW; PTR_Object objPtr = vmObject.GetDacPtr(); #ifdef _DEBUG // ensure we have an Exception object MethodTable* pMT = objPtr->GetMethodTable(); _ASSERTE(IsExceptionObject(pMT)); #endif OBJECTREF objRef = ObjectToOBJECTREF(objPtr); DebugStackTrace::GetStackFramesData stackFramesData; stackFramesData.pDomain = NULL; stackFramesData.skip = 0; stackFramesData.NumFramesRequested = 0; DebugStackTrace::GetStackFramesFromException(&objRef, &stackFramesData); INT32 dacStackFramesLength = stackFramesData.cElements; if (dacStackFramesLength > 0) { dacStackFrames.Alloc(dacStackFramesLength); for (INT32 index = 0; index < dacStackFramesLength; ++index) { DebugStackTrace::DebugStackTraceElement const& currentElement = stackFramesData.pElements[index]; DacExceptionCallStackData& currentFrame = dacStackFrames[index]; Module* pModule = currentElement.pFunc->GetModule(); BaseDomain* pBaseDomain = currentElement.pFunc->GetAssembly()->GetDomain(); AppDomain* pDomain = NULL; DomainFile* pDomainFile = NULL; if (pBaseDomain->IsSharedDomain()) pDomain = SystemDomain::System()->DefaultDomain(); else pDomain = pBaseDomain->AsAppDomain(); _ASSERTE(pDomain != NULL); pDomainFile = pModule->FindDomainFile(pDomain); _ASSERTE(pDomainFile != NULL); currentFrame.vmAppDomain.SetHostPtr(pDomain); currentFrame.vmDomainFile.SetHostPtr(pDomainFile); currentFrame.ip = currentElement.ip; currentFrame.methodDef = currentElement.pFunc->GetMemberDef(); #if defined(FEATURE_EXCEPTIONDISPATCHINFO) currentFrame.isLastForeignExceptionFrame = currentElement.fIsLastFrameFromForeignStackTrace; #else // for CLRs lacking exception dispatch info just set it to 0 currentFrame.isLastForeignExceptionFrame = 0; #endif } } } #ifdef FEATURE_COMINTEROP PTR_RCW GetRcwFromVmptrObject(VMPTR_Object vmObject) { PTR_RCW pRCW = NULL; Object* objPtr = vmObject.GetDacPtr(); PTR_SyncBlock pSyncBlock = NULL; pSyncBlock = objPtr->PassiveGetSyncBlock(); if (pSyncBlock == NULL) return pRCW; PTR_InteropSyncBlockInfo pInfo = NULL; pInfo = pSyncBlock->GetInteropInfoNoCreate(); if (pInfo == NULL) return pRCW; pRCW = dac_cast(pInfo->DacGetRawRCW()); return pRCW; } #endif BOOL DacDbiInterfaceImpl::IsRcw(VMPTR_Object vmObject) { #ifdef FEATURE_COMINTEROP DD_ENTER_MAY_THROW; return GetRcwFromVmptrObject(vmObject) != NULL; #else return FALSE; #endif // FEATURE_COMINTEROP } void DacDbiInterfaceImpl::GetRcwCachedInterfaceTypes( VMPTR_Object vmObject, VMPTR_AppDomain vmAppDomain, BOOL bIInspectableOnly, DacDbiArrayList * pDacInterfaces) { #ifdef FEATURE_COMINTEROP DD_ENTER_MAY_THROW; Object* objPtr = vmObject.GetDacPtr(); InlineSArray rgMT; PTR_RCW pRCW = GetRcwFromVmptrObject(vmObject); if (pRCW != NULL) { pRCW->GetCachedInterfaceTypes(bIInspectableOnly, &rgMT); pDacInterfaces->Alloc(rgMT.GetCount()); for (COUNT_T i = 0; i < rgMT.GetCount(); ++i) { // There is the possiblity that we'll get this far with a dump and not fail, but still // not be able to get full info for a particular param. EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // Fill in the struct using the current TypeHandle VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); TypeHandle th = TypeHandle::FromTAddr(dac_cast(rgMT[i])); vmTypeHandle.SetDacTargetPtr(th.AsTAddr()); TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, vmAppDomain, vmTypeHandle, &((*pDacInterfaces)[i])); } EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // On failure for a particular type, default it to NULL. (*pDacInterfaces)[i].elementType = ELEMENT_TYPE_END; } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER } } else #endif // FEATURE_COMINTEROP { pDacInterfaces->Alloc(0); } } void DacDbiInterfaceImpl::GetRcwCachedInterfacePointers( VMPTR_Object vmObject, BOOL bIInspectableOnly, DacDbiArrayList * pDacItfPtrs) { #ifdef FEATURE_COMINTEROP DD_ENTER_MAY_THROW; Object* objPtr = vmObject.GetDacPtr(); InlineSArray rgUnks; PTR_RCW pRCW = GetRcwFromVmptrObject(vmObject); if (pRCW != NULL) { pRCW->GetCachedInterfacePointers(bIInspectableOnly, &rgUnks); pDacItfPtrs->Alloc(rgUnks.GetCount()); for (COUNT_T i = 0; i < rgUnks.GetCount(); ++i) { (*pDacItfPtrs)[i] = (CORDB_ADDRESS)(rgUnks[i]); } } else #endif // FEATURE_COMINTEROP { pDacItfPtrs->Alloc(0); } } void DacDbiInterfaceImpl::GetCachedWinRTTypesForIIDs( VMPTR_AppDomain vmAppDomain, DacDbiArrayList & iids, OUT DacDbiArrayList * pTypes) { #ifdef FEATURE_COMINTEROP DD_ENTER_MAY_THROW; AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); if (pAppDomain->IsUnloading()) { return; } { pTypes->Alloc(iids.Count()); for (int i = 0; i < iids.Count(); ++i) { // There is the possiblity that we'll get this far with a dump and not fail, but still // not be able to get full info for a particular param. EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { PTR_MethodTable pMT = pAppDomain->LookupTypeByGuid(iids[i]); // Fill in the struct using the current TypeHandle VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); TypeHandle th = TypeHandle::FromTAddr(dac_cast(pMT)); vmTypeHandle.SetDacTargetPtr(th.AsTAddr()); TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, vmAppDomain, vmTypeHandle, &((*pTypes)[i])); } EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // On failure for a particular type, default it to NULL. (*pTypes)[i].elementType = ELEMENT_TYPE_END; } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER } } #else // FEATURE_COMINTEROP { pTypes->Alloc(0); } #endif // FEATURE_COMINTEROP } void DacDbiInterfaceImpl::GetCachedWinRTTypes( VMPTR_AppDomain vmAppDomain, OUT DacDbiArrayList * pGuids, OUT DacDbiArrayList * pTypes) { #ifdef FEATURE_COMINTEROP DD_ENTER_MAY_THROW; AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); if (pAppDomain->IsUnloading()) { return; } InlineSArray rgMT; InlineSArray rgGuid; { pAppDomain->GetCachedWinRTTypes(&rgMT, &rgGuid, 0, NULL); pTypes->Alloc(rgMT.GetCount()); pGuids->Alloc(rgGuid.GetCount()); for (COUNT_T i = 0; i < rgMT.GetCount(); ++i) { // There is the possiblity that we'll get this far with a dump and not fail, but still // not be able to get full info for a particular param. EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // Fill in the struct using the current TypeHandle VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); TypeHandle th = TypeHandle::FromTAddr(dac_cast(rgMT[i])); vmTypeHandle.SetDacTargetPtr(th.AsTAddr()); TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, vmAppDomain, vmTypeHandle, &((*pTypes)[i])); } EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER { // On failure for a particular type, default it to NULL. (*pTypes)[i].elementType = ELEMENT_TYPE_END; } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER (*pGuids)[i] = rgGuid[i]; } } #else // FEATURE_COMINTEROP { pTypes->Alloc(0); } #endif // FEATURE_COMINTEROP } //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::FindField // Finds information for a particular class field // Arguments: // input: thApprox - type handle for the type to which the field belongs // fldToken - metadata token for the field // Return Value: FieldDesc containing information for the field if found or NULL otherwise //----------------------------------------------------------------------------- PTR_FieldDesc DacDbiInterfaceImpl::FindField(TypeHandle thApprox, mdFieldDef fldToken) { EncApproxFieldDescIterator fdIterator(thApprox.GetMethodTable(), ApproxFieldDescIterator::ALL_FIELDS, FALSE); // don't fixup EnC (we can't, we're stopped) PTR_FieldDesc pCurrentFD; while ((pCurrentFD = fdIterator.Next()) != NULL) { // We're looking for a specific fieldDesc, see if we got it. if (pCurrentFD->GetMemberDef() == fldToken) { return pCurrentFD; } } // we never found it... return NULL; } // DacDbiInterfaceImpl::FindField //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetEnCFieldDesc // Get the FieldDesc corresponding to a particular EnC field token // Arguments: // input: pEnCFieldInfo // Return Value: pointer to the FieldDesc that corresponds to the EnC field // Note: this function may throw //----------------------------------------------------------------------------- FieldDesc * DacDbiInterfaceImpl::GetEnCFieldDesc(const EnCHangingFieldInfo * pEnCFieldInfo) { FieldDesc * pFD = NULL; DomainFile * pDomainFile = pEnCFieldInfo->GetObjectTypeData().vmDomainFile.GetDacPtr(); Module * pModule = pDomainFile->GetModule(); // get the type handle for the object TypeHandle typeHandle = ClassLoader::LookupTypeDefOrRefInModule(pModule, pEnCFieldInfo->GetObjectTypeData().metadataToken); if (typeHandle == NULL) { ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } // and find the field desc pFD = FindField(typeHandle, pEnCFieldInfo->GetFieldToken()); if (pFD == NULL) { // FieldDesc is not yet available, so can't get EnC field info ThrowHR(CORDBG_E_ENC_HANGING_FIELD); } return pFD; } // DacDbiInterfaceImpl::GetEnCFieldDesc //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::GetPtrToEnCField // Get the address of a field added with EnC. // Arguments: // input: pFD - field desc for the added field // pEnCFieldInfo - information about the new field // Return Value: The field address if the field is available (i.e., it has been accessed) // or NULL otherwise // Note: this function may throw //----------------------------------------------------------------------------- PTR_CBYTE DacDbiInterfaceImpl::GetPtrToEnCField(FieldDesc * pFD, const EnCHangingFieldInfo * pEnCFieldInfo) { #ifndef EnC_SUPPORTED _ASSERTE(!"Trying to get the address of an EnC field where EnC is not supported! "); return NULL; #else PTR_EditAndContinueModule pEnCModule; DomainFile * pDomainFile = pEnCFieldInfo->GetObjectTypeData().vmDomainFile.GetDacPtr(); Module * pModule = pDomainFile->GetModule(); // make sure we actually have an EditAndContinueModule _ASSERTE(pModule->IsEditAndContinueCapable()); pEnCModule = dac_cast(pModule); // we should also have an EnCFieldDesc _ASSERTE(pFD->IsEnCNew()); EnCFieldDesc * pEnCFieldDesc; pEnCFieldDesc = dac_cast(pFD); // If it hasn't been fixed up yet, then we can't return the pointer. if (pEnCFieldDesc->NeedsFixup()) { ThrowHR(CORDBG_E_ENC_HANGING_FIELD); } // Get a pointer to the field PTR_CBYTE pORField = NULL; PTR_Object pObject = pEnCFieldInfo->GetVmObject().GetDacPtr(); pORField = pEnCModule->ResolveField(ObjectToOBJECTREF(pObject), pEnCFieldDesc); // The field could be absent because the code hasn't accessed it yet. If so, we're not going to add it // since we can't allocate anyway. if (pORField == NULL) { ThrowHR(CORDBG_E_ENC_HANGING_FIELD); } return pORField; #endif // EnC_SUPPORTED } // DacDbiInterfaceImpl::GetPtrToEnCField //----------------------------------------------------------------------------- // DacDbiInterfaceImpl::InitFieldData // Initialize information about a field added with EnC // Arguments : // input: // pFD - provides information about whether the field is static, // the metadata token, etc. // pORField - provides the field address or offset // pEnCFieldData - provides the offset to the fields of the object // output: pFieldData - initialized in accordance with the input information //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::InitFieldData(const FieldDesc * pFD, const PTR_CBYTE pORField, const EnCHangingFieldInfo * pEnCFieldData, FieldData * pFieldData) { pFieldData->ClearFields(); pFieldData->m_fFldIsStatic = (pFD->IsStatic() != 0); pFieldData->m_vmFieldDesc.SetHostPtr(pFD); pFieldData->m_fFldIsTLS = (pFD->IsThreadStatic() == TRUE); pFieldData->m_fldMetadataToken = pFD->GetMemberDef(); pFieldData->m_fFldIsRVA = (pFD->IsRVA() == TRUE); pFieldData->m_fFldIsContextStatic = (pFD->IsContextStatic() == TRUE); pFieldData->m_fFldIsCollectibleStatic = FALSE; pFieldData->m_fFldStorageAvailable = true; if (pFieldData->m_fFldIsStatic) { //EnC is only supported on regular static fields _ASSERTE(!pFieldData->m_fFldIsContextStatic); _ASSERTE(!pFieldData->m_fFldIsTLS); _ASSERTE(!pFieldData->m_fFldIsRVA); // pORField contains the absolute address pFieldData->SetStaticAddress(PTR_TO_TADDR(pORField)); } else { // fldInstanceOffset is computed to work correctly with GetFieldValue // which computes: // addr of pORField = object + pEnCFieldInfo->m_offsetToVars + offsetToFld pFieldData->SetInstanceOffset(PTR_TO_TADDR(pORField) - (PTR_TO_TADDR(pEnCFieldData->GetVmObject().GetDacPtr()) + pEnCFieldData->GetOffsetToVars())); } } // DacDbiInterfaceImpl::InitFieldData // ---------------------------------------------------------------------------- // DacDbi API: GetEnCHangingFieldInfo // After a class has been loaded, if a field has been added via EnC we'll have to jump through // some hoops to get at it (it hangs off the sync block or FieldDesc). // // GENERICS: TODO: this method will need to be modified if we ever support EnC on // generic classes. //----------------------------------------------------------------------------- void DacDbiInterfaceImpl::GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, FieldData * pFieldData, BOOL * pfStatic) { DD_ENTER_MAY_THROW; LOG((LF_CORDB, LL_INFO100000, "DDI::IEnCHFI: Obj:0x%x, objType" ":0x%x, offset:0x%x\n", pEnCFieldInfo->m_pObject, pEnCFieldInfo->m_objectTypeData.elementType, pEnCFieldInfo->m_offsetToVars)); FieldDesc * pFD = NULL; PTR_CBYTE pORField = NULL; pFD = GetEnCFieldDesc(pEnCFieldInfo); _ASSERTE(pFD->IsEnCNew()); // We shouldn't be here if it wasn't added to an // already loaded class. #ifdef EnC_SUPPORTED pORField = GetPtrToEnCField(pFD, pEnCFieldInfo); #else _ASSERTE(!"We shouldn't be here: EnC not supported"); #endif // EnC_SUPPORTED InitFieldData(pFD, pORField, pEnCFieldInfo, pFieldData); *pfStatic = (pFD->IsStatic() != 0); } // DacDbiInterfaceImpl::GetEnCHangingFieldInfo //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DacDbiInterfaceImpl::GetAssemblyFromDomainAssembly(VMPTR_DomainAssembly vmDomainAssembly, VMPTR_Assembly *vmAssembly) { DD_ENTER_MAY_THROW; _ASSERTE(vmAssembly != NULL); DomainAssembly * pDomainAssembly = vmDomainAssembly.GetDacPtr(); vmAssembly->SetHostPtr(pDomainAssembly->GetAssembly()); } // Determines whether the runtime security system has assigned full-trust to this assembly. BOOL DacDbiInterfaceImpl::IsAssemblyFullyTrusted(VMPTR_DomainAssembly vmDomainAssembly) { DD_ENTER_MAY_THROW; DomainAssembly * pAssembly = vmDomainAssembly.GetDacPtr(); IAssemblySecurityDescriptor * pSecDisc = pAssembly->GetSecurityDescriptor(); return pSecDisc->IsFullyTrusted(); } // Get the full path and file name to the assembly's manifest module. BOOL DacDbiInterfaceImpl::GetAssemblyPath( VMPTR_Assembly vmAssembly, IStringHolder * pStrFilename) { DD_ENTER_MAY_THROW; // Get the manifest module for this assembly Assembly * pAssembly = vmAssembly.GetDacPtr(); Module * pManifestModule = pAssembly->GetManifestModule(); // Get the path for the manifest module. // since we no longer support Win9x, we assume all paths will be in unicode format already const WCHAR * szPath = pManifestModule->GetPath().DacGetRawUnicode(); HRESULT hrStatus = pStrFilename->AssignCopy(szPath); IfFailThrow(hrStatus); if(szPath == NULL || *szPath=='\0') { // The asembly has no (and will never have a) file name, but we didn't really fail return FALSE; } return TRUE; } // DAC/DBI API // Get a resolved type def from a type ref. The type ref may come from a module other than the // referencing module. void DacDbiInterfaceImpl::ResolveTypeReference(const TypeRefData * pTypeRefInfo, TypeRefData * pTargetRefInfo) { DD_ENTER_MAY_THROW; DomainFile * pDomainFile = pTypeRefInfo->vmDomainFile.GetDacPtr(); Module * pReferencingModule = pDomainFile->GetCurrentModule(); BOOL fSuccess = FALSE; // Resolve the type ref // g_pEEInterface->FindLoadedClass is almost what we want, but it isn't guaranteed to work if // the typeRef was originally loaded from a different assembly. Also, we need to ensure that // we can resolve even unloaded types in fully loaded assemblies, so APIs such as // LoadTypeDefOrRefThrowing aren't acceptable. Module * pTargetModule = NULL; mdTypeDef targetTypeDef = mdTokenNil; // The loader won't need to trigger a GC or throw because we've told it not to load anything ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); fSuccess = ClassLoader::ResolveTokenToTypeDefThrowing(pReferencingModule, pTypeRefInfo->typeToken, &pTargetModule, &targetTypeDef, Loader::SafeLookup //don't load, no locks/allocations ); if (fSuccess) { _ASSERTE(pTargetModule != NULL); _ASSERTE( TypeFromToken(targetTypeDef) == mdtTypeDef ); AppDomain * pAppDomain = pDomainFile->GetAppDomain(); pTargetRefInfo->vmDomainFile.SetDacTargetPtr(PTR_HOST_TO_TADDR(pTargetModule->GetDomainFile(pAppDomain))); pTargetRefInfo->typeToken = targetTypeDef; } else { // failed - presumably because the target assembly isn't loaded ThrowHR(CORDBG_E_CLASS_NOT_LOADED); } } // DacDbiInterfaceImpl::ResolveTypeReference // Get the full path and file name to the module (if any). BOOL DacDbiInterfaceImpl::GetModulePath(VMPTR_Module vmModule, IStringHolder * pStrFilename) { DD_ENTER_MAY_THROW; Module * pModule = vmModule.GetDacPtr(); PEFile * pFile = pModule->GetFile(); if (pFile != NULL) { if( !pFile->GetPath().IsEmpty() ) { // Module has an on-disk path const WCHAR * szPath = pFile->GetPath().DacGetRawUnicode(); if (szPath == NULL) { szPath = pFile->GetModuleFileNameHint().DacGetRawUnicode(); if (szPath == NULL) { goto NoFileName; } } IfFailThrow(pStrFilename->AssignCopy(szPath)); return TRUE; } } NoFileName: // no filename IfFailThrow(pStrFilename->AssignCopy(W(""))); return FALSE; } // Get the full path and file name to the ngen image for the module (if any). BOOL DacDbiInterfaceImpl::GetModuleNGenPath(VMPTR_Module vmModule, IStringHolder * pStrFilename) { DD_ENTER_MAY_THROW; #ifdef FEATURE_PREJIT Module * pModule = vmModule.GetDacPtr(); PEFile * pFile = pModule->GetFile(); if (pFile != NULL && pFile->HasNativeImage()) { PEImage * pImage = pFile->GetPersistentNativeImage(); if (pImage != NULL && pImage->IsFile()) { // We have an on-disk ngen image. Return the path. // since we no longer support Win9x, we assume all paths will be in unicode format already const WCHAR * szPath = pImage->GetPath().DacGetRawUnicode(); if (szPath == NULL) { szPath = pFile->GetModuleFileNameHint().DacGetRawUnicode(); if (szPath == NULL) { goto NoFileName; } } IfFailThrow(pStrFilename->AssignCopy(szPath)); return TRUE; } } #endif // FEATURE_PREJIT NoFileName: // no ngen filename IfFailThrow(pStrFilename->AssignCopy(W(""))); return FALSE; } // Implementation of IDacDbiInterface::GetModuleSimpleName void DacDbiInterfaceImpl::GetModuleSimpleName(VMPTR_Module vmModule, IStringHolder * pStrFilename) { DD_ENTER_MAY_THROW; _ASSERTE(pStrFilename != NULL); Module * pModule = vmModule.GetDacPtr(); LPCUTF8 szNameUtf8 = pModule->GetSimpleName(); SString convert(SString::Utf8, szNameUtf8); IfFailThrow(pStrFilename->AssignCopy(convert.GetUnicode())); } // Helper to intialize a TargetBuffer from a MemoryRange // // Arguments: // memoryRange - memory range. // pTargetBuffer - required out parameter to be initialized to value of memory range. // // Notes: // MemoryRange and TargetBuffer both conceptually describe a single contiguous buffer of memory in the // target. MemoryRange is a VM structure, which can't bleed across the DacDbi boundary. TargetBuffer is // a DacDbi structure, which can cross the DacDbi boundary. void InitTargetBufferFromMemoryRange(const MemoryRange memoryRange, TargetBuffer * pTargetBuffer) { SUPPORTS_DAC; _ASSERTE(pTargetBuffer != NULL); PTR_CVOID p = memoryRange.StartAddress(); CORDB_ADDRESS addr = PTR_TO_CORDB_ADDRESS(PTR_TO_TADDR(p)); _ASSERTE(memoryRange.Size() <= 0xffffffff); pTargetBuffer->Init(addr, (ULONG)memoryRange.Size()); } // Helper to intialize a TargetBuffer (host representation of target) from an SBuffer (target) // // Arguments: // pBuffer - target pointer to a SBuffer structure. If pBuffer is NULL, then target buffer will be empty. // pTargetBuffer - required out pointer to hold buffer description. // // Notes: // PTR_SBuffer and TargetBuffer are both semantically equivalent structures. They both are a pointer and length // describing a buffer in the target address space. (SBufer also has ownership semantics, but for DAC's // read-only nature, that doesn't matter). // Neither of these will actually copy the target buffer into the host without explicit action. // The important difference is that TargetBuffer is a host datastructure and so easier to manipulate. // void InitTargetBufferFromTargetSBuffer(PTR_SBuffer pBuffer, TargetBuffer * pTargetBuffer) { SUPPORTS_DAC; _ASSERTE(pTargetBuffer != NULL); SBuffer * pBufferHost = pBuffer; if (pBufferHost == NULL) { pTargetBuffer->Clear(); return; } MemoryRange m = pBufferHost->DacGetRawBuffer(); InitTargetBufferFromMemoryRange(m, pTargetBuffer); } // Implementation of IDacDbiInterface::GetMetadata void DacDbiInterfaceImpl::GetMetadata(VMPTR_Module vmModule, TargetBuffer * pTargetBuffer) { DD_ENTER_MAY_THROW; pTargetBuffer->Clear(); Module * pModule = vmModule.GetDacPtr(); // Target should only be asking about modules that are visible to debugger. _ASSERTE(pModule->IsVisibleToDebugger()); // For dynamic modules, metadata is stored as an eagerly-serialized buffer hanging off the Reflection Module. if (pModule->IsReflection()) { // Here is the fetch. ReflectionModule * pReflectionModule = pModule->GetReflectionModule(); InitTargetBufferFromTargetSBuffer(pReflectionModule->GetDynamicMetadataBuffer(), pTargetBuffer); } else { PEFile * pFile = pModule->GetFile(); // For non-dynamic modules, metadata is in the pe-image. COUNT_T size; CORDB_ADDRESS address = PTR_TO_CORDB_ADDRESS(dac_cast(pFile->GetLoadedMetadata(&size))); pTargetBuffer->Init(address, (ULONG) size); } if (pTargetBuffer->IsEmpty()) { // We never expect this to happen in a well-behaved scenario. But just in case. ThrowHR(CORDBG_E_MISSING_METADATA); } } // Implementation of IDacDbiInterface::GetSymbolsBuffer void DacDbiInterfaceImpl::GetSymbolsBuffer(VMPTR_Module vmModule, TargetBuffer * pTargetBuffer, SymbolFormat * pSymbolFormat) { DD_ENTER_MAY_THROW; pTargetBuffer->Clear(); *pSymbolFormat = kSymbolFormatNone; Module * pModule = vmModule.GetDacPtr(); // Target should only be asking about modules that are visible to debugger. _ASSERTE(pModule->IsVisibleToDebugger()); PTR_CGrowableStream pStream = pModule->GetInMemorySymbolStream(); if (pStream == NULL) { // Common case is to not have PDBs in-memory. return; } const MemoryRange m = pStream->GetRawBuffer(); if (m.Size() == 0) { // We may be prepared to store symbols (in some particular format) but none are there yet. // We treat this the same as not having any symbols above. return; } InitTargetBufferFromMemoryRange(m, pTargetBuffer); // Set the symbol format appropriately ESymbolFormat symFormat = pModule->GetInMemorySymbolStreamFormat(); switch (symFormat) { case eSymbolFormatPDB: *pSymbolFormat = kSymbolFormatPDB; break; case eSymbolFormatILDB: *pSymbolFormat = kSymbolFormatILDB; break; default: CONSISTENCY_CHECK_MSGF(false, "Unexpected symbol format"); pTargetBuffer->Clear(); ThrowHR(E_UNEXPECTED); } } void DacDbiInterfaceImpl::GetModuleForDomainFile(VMPTR_DomainFile vmDomainFile, OUT VMPTR_Module * pModule) { DD_ENTER_MAY_THROW; _ASSERTE(pModule != NULL); DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); pModule->SetHostPtr(pDomainFile->GetModule()); } // Implement IDacDbiInterface::GetDomainFileData void DacDbiInterfaceImpl::GetDomainFileData(VMPTR_DomainFile vmDomainFile, DomainFileInfo * pData) { DD_ENTER_MAY_THROW; _ASSERTE(pData != NULL); ZeroMemory(pData, sizeof(*pData)); DomainFile * pDomainFile = vmDomainFile.GetDacPtr(); AppDomain * pAppDomain = pDomainFile->GetAppDomain(); // @dbgtodo - is this efficient DAC usage (perhaps a dac-cop rule)? Are we round-tripping the pointer? // Should we have a GetDomainAssembly() that returns a PTR_DomainAssembly? pData->vmDomainAssembly.SetHostPtr(pDomainFile->GetDomainAssembly()); pData->vmAppDomain.SetHostPtr(pAppDomain); } // Implement IDacDbiInterface::GetModuleData void DacDbiInterfaceImpl::GetModuleData(VMPTR_Module vmModule, ModuleInfo * pData) { DD_ENTER_MAY_THROW; _ASSERTE(pData != NULL); ZeroMemory(pData, sizeof(*pData)); Module * pModule = vmModule.GetDacPtr(); PEFile * pFile = pModule->GetFile(); pData->vmPEFile.SetHostPtr(pFile); pData->vmAssembly.SetHostPtr(pModule->GetAssembly()); // Is it dynamic? BOOL fIsDynamic = pModule->IsReflection(); pData->fIsDynamic = fIsDynamic; // Get PE BaseAddress and Size // For dynamic modules, these are 0. Else, pData->pPEBaseAddress = NULL; pData->nPESize = 0; if (!fIsDynamic) { COUNT_T size = 0; pData->pPEBaseAddress = PTR_TO_TADDR(pFile->GetDebuggerContents(&size)); pData->nPESize = (ULONG) size; } // In-memory is determined by whether the module has a filename. pData->fInMemory = FALSE; if (pFile != NULL) { pData->fInMemory = pFile->GetPath().IsEmpty(); } } // Enumerate all AppDomains in the process. void DacDbiInterfaceImpl::EnumerateAppDomains( FP_APPDOMAIN_ENUMERATION_CALLBACK fpCallback, void * pUserData) { DD_ENTER_MAY_THROW; _ASSERTE(fpCallback != NULL); // Only include active appdomains in the enumeration. // This includes appdomains sent before the AD load event, // and does not include appdomains that are in shutdown after the AD exit event. const BOOL bOnlyActive = TRUE; AppDomainIterator iterator(bOnlyActive); while(iterator.Next()) { // It's critical that we don't yield appdomains after the unload event has been sent. // See code:IDacDbiInterface#Enumeration for details. AppDomain * pAppDomain = iterator.GetDomain(); if (pAppDomain->IsUnloading()) { continue; } VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr(); vmAppDomain.SetHostPtr(pAppDomain); fpCallback(vmAppDomain, pUserData); } } // Enumerate all Assemblies in an appdomain. void DacDbiInterfaceImpl::EnumerateAssembliesInAppDomain( VMPTR_AppDomain vmAppDomain, FP_ASSEMBLY_ENUMERATION_CALLBACK fpCallback, void * pUserData ) { DD_ENTER_MAY_THROW; _ASSERTE(fpCallback != NULL); // Iterate through all Assemblies (including shared) in the appdomain. AppDomain::AssemblyIterator iterator; // If the containing appdomain is unloading, then don't enumerate any assemblies // in the domain. This is to enforce rules at code:IDacDbiInterface#Enumeration. // See comment in code:DacDbiInterfaceImpl::EnumerateModulesInAssembly code for details. AppDomain * pAppDomain = vmAppDomain.GetDacPtr(); if (pAppDomain->IsUnloading()) { return; } // Pass the magical flags to the loader enumerator to get all Execution-only assemblies. iterator = pAppDomain->IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoading | kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pDomainAssembly; while (iterator.Next(pDomainAssembly.This())) { if (!pDomainAssembly->IsVisibleToDebugger()) { continue; } VMPTR_DomainAssembly vmDomainAssembly = VMPTR_DomainAssembly::NullPtr(); vmDomainAssembly.SetHostPtr(pDomainAssembly); fpCallback(vmDomainAssembly, pUserData); } } // Implementation of IDacDbiInterface::EnumerateModulesInAssembly, // Enumerate all the modules (non-resource) in an assembly. void DacDbiInterfaceImpl::EnumerateModulesInAssembly( VMPTR_DomainAssembly vmAssembly, FP_MODULE_ENUMERATION_CALLBACK fpCallback, void * pUserData) { DD_ENTER_MAY_THROW; _ASSERTE(fpCallback != NULL); DomainAssembly * pDomainAssembly = vmAssembly.GetDacPtr(); // If the appdomain or assembly containing this module is unloading, then don't enumerate any modules. // in the domain. This is to enforce rules at code:IDacDbiInterface#Enumeration, specifically // that new objects are not available after the unload event is sent. // This is a very large hammer, but since modules only unload with appdomains or assemblies, we're // erring on the side of safety. If the debugger happens to have VMPTR_DomainFiles (CordbModules) already // cached, it can still use those until the unload event. if (pDomainAssembly->IsUnloading()) { return; } // If the domain is not yet fully-loaded, don't advertise it yet. // It's not ready to be inspected. DomainModuleIterator iterator = pDomainAssembly->IterateModules(kModIterIncludeLoaded); while (iterator.Next()) { DomainFile * pDomainFile = iterator.GetDomainFile(); // Debugger isn't notified of Resource / Inspection-only modules. if (!pDomainFile->GetModule()->IsVisibleToDebugger()) { continue; } _ASSERTE(pDomainFile->IsLoaded()); VMPTR_DomainFile vmDomainFile = VMPTR_DomainFile::NullPtr(); vmDomainFile.SetHostPtr(pDomainFile); fpCallback(vmDomainFile, pUserData); } } // Implementation of IDacDbiInterface::ResolveAssembly // Returns NULL if not found. VMPTR_DomainAssembly DacDbiInterfaceImpl::ResolveAssembly( VMPTR_DomainFile vmScope, mdToken tkAssemblyRef) { DD_ENTER_MAY_THROW; DomainFile * pDomainFile = vmScope.GetDacPtr(); AppDomain * pAppDomain = pDomainFile->GetAppDomain(); Module * pModule = pDomainFile->GetCurrentModule(); VMPTR_DomainAssembly vmDomainAssembly = VMPTR_DomainAssembly::NullPtr(); Assembly * pAssembly = pModule->LookupAssemblyRef(tkAssemblyRef); if (pAssembly != NULL) { DomainAssembly * pDomainAssembly = pAssembly->FindDomainAssembly(pAppDomain); vmDomainAssembly.SetHostPtr(pDomainAssembly); } return vmDomainAssembly; } // When stopped at an event, request a synchronization. // See DacDbiInterface.h for full comments void DacDbiInterfaceImpl::RequestSyncAtEvent() { DD_ENTER_MAY_THROW; // To request a sync, we just need to set g_pDebugger->m_RSRequestedSync high. if (g_pDebugger != NULL) { TADDR addr = PTR_HOST_MEMBER_TADDR(Debugger, g_pDebugger, m_RSRequestedSync); BOOL fTrue = TRUE; SafeWriteStructOrThrow(addr, &fTrue); } } HRESULT DacDbiInterfaceImpl::SetSendExceptionsOutsideOfJMC(BOOL sendExceptionsOutsideOfJMC) { DD_ENTER_MAY_THROW HRESULT hr = S_OK; EX_TRY { if (g_pDebugger != NULL) { TADDR addr = PTR_HOST_MEMBER_TADDR(Debugger, g_pDebugger, m_sendExceptionsOutsideOfJMC); SafeWriteStructOrThrow(addr, &sendExceptionsOutsideOfJMC); } } EX_CATCH_HRESULT(hr); return hr; } // Notify the debuggee that a debugger attach is pending. // See DacDbiInterface.h for full comments void DacDbiInterfaceImpl::MarkDebuggerAttachPending() { DD_ENTER_MAY_THROW; if (g_pDebugger != NULL) { DWORD flags = g_CORDebuggerControlFlags; flags |= DBCF_PENDING_ATTACH; // Uses special DAC writing. PTR_TO_TADDR doesn't fetch for globals. // @dbgtodo dac support - the exact mechanism of writing to the target needs to be flushed out, // especially as it relates to DAC cop and enforcing undac-ized writes. g_CORDebuggerControlFlags = flags; } else { // Caller should have gauranteed that the LS is loaded. // If we're detaching, then don't throw because we don't care. ThrowHR(CORDBG_E_NOTREADY); } } // Notify the debuggee that a debugger is attached. // See DacDbiInterface.h for full comments void DacDbiInterfaceImpl::MarkDebuggerAttached(BOOL fAttached) { DD_ENTER_MAY_THROW; if (g_pDebugger != NULL) { // To be attached, we need to set the following // g_CORDebuggerControlFlags |= DBCF_ATTACHED; // To detach (if !fAttached), we need to do the opposite. DWORD flags = g_CORDebuggerControlFlags; if (fAttached) { flags |= DBCF_ATTACHED; } else { flags &= ~ (DBCF_ATTACHED | DBCF_PENDING_ATTACH); } // Uses special DAC writing. PTR_TO_TADDR doesn't fetch for globals. // @dbgtodo dac support - the exact mechanism of writing to the target needs to be flushed out, // especially as it relates to DAC cop and enforcing undac-ized writes. g_CORDebuggerControlFlags = flags; } else if (fAttached) { // Caller should have gauranteed that the LS is loaded. // If we're detaching, then don't throw because we don't care. ThrowHR(CORDBG_E_NOTREADY); } } #ifdef FEATURE_INCLUDE_ALL_INTERFACES // Enumerate all the Connections in the process. void DacDbiInterfaceImpl::EnumerateConnections(FP_CONNECTION_CALLBACK fpCallback, void * pUserData) { DD_ENTER_MAY_THROW; ConnectionNameHashEntry * pConnection; HASHFIND hashfind; pConnection = CCLRDebugManager::FindFirst(&hashfind); while (pConnection) { DWORD id = pConnection->m_dwConnectionId; LPCWSTR pName = pConnection->m_pwzName; fpCallback(id, pName, pUserData); // now get the next connection record pConnection = CCLRDebugManager::FindNext(&hashfind); } } #endif // Enumerate all threads in the process. void DacDbiInterfaceImpl::EnumerateThreads(FP_THREAD_ENUMERATION_CALLBACK fpCallback, void * pUserData) { DD_ENTER_MAY_THROW; if (ThreadStore::s_pThreadStore == NULL) { return; } Thread *pThread = ThreadStore::GetThreadList(NULL); while (pThread != NULL) { // Don't want to publish threads via enumeration before they're ready to be inspected. // Use the same window that we used in whidbey. Thread::ThreadState threadState = pThread->GetSnapshotState(); if (!((IsThreadMarkedDeadWorker(pThread)) || (threadState & Thread::TS_Unstarted))) { VMPTR_Thread vmThread = VMPTR_Thread::NullPtr(); vmThread.SetHostPtr(pThread); fpCallback(vmThread, pUserData); } pThread = ThreadStore::GetThreadList(pThread); } } // public implementation of IsThreadMarkedDead bool DacDbiInterfaceImpl::IsThreadMarkedDead(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); return IsThreadMarkedDeadWorker(pThread); } // Private worker for IsThreadMarkedDead // // Arguments: // pThread - valid thread to check if dead // // Returns: // true iff thread is marked as dead. // // Notes: // This is an internal method that skips public validation. // See code:IDacDbiInterface::#IsThreadMarkedDead for purpose. bool DacDbiInterfaceImpl::IsThreadMarkedDeadWorker(Thread * pThread) { _ASSERTE(pThread != NULL); Thread::ThreadState threadState = pThread->GetSnapshotState(); bool fIsDead = (threadState & Thread::TS_Dead) != 0; return fIsDead; } // Return the handle of the specified thread. HANDLE DacDbiInterfaceImpl::GetThreadHandle(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); return pThread->GetThreadHandle(); } // Return the object handle for the managed Thread object corresponding to the specified thread. VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetThreadObject(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); Thread::ThreadState threadState = pThread->GetSnapshotState(); if ( (threadState & Thread::TS_Dead) || (threadState & Thread::TS_Unstarted) || (threadState & Thread::TS_Detached) || g_fProcessDetach ) { ThrowHR(CORDBG_E_BAD_THREAD_STATE); } else { VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr(); vmObjHandle.SetDacTargetPtr(pThread->GetExposedObjectHandleForDebugger()); return vmObjHandle; } } // Set and reset the TSNC_DebuggerUserSuspend bit on the state of the specified thread // according to the CorDebugThreadState. void DacDbiInterfaceImpl::SetDebugState(VMPTR_Thread vmThread, CorDebugThreadState debugState) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); // update the field on the host copy if (debugState == THREAD_SUSPEND) { pThread->SetThreadStateNC(Thread::TSNC_DebuggerUserSuspend); } else if (debugState == THREAD_RUN) { pThread->ResetThreadStateNC(Thread::TSNC_DebuggerUserSuspend); } else { ThrowHR(E_INVALIDARG); } // update the field on the target copy TADDR taThreadState = PTR_HOST_MEMBER_TADDR(Thread, pThread, m_StateNC); SafeWriteStructOrThrow(taThreadState, &(pThread->m_StateNC)); } // Gets the debugger unhandled exception threadstate flag BOOL DacDbiInterfaceImpl::HasUnhandledException(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); // some managed exceptions don't have any underlying // native exception processing going on. They just consist // of a managed throwable that we have stashed away followed // by a debugger notification and some form of failfast. // Everything that comes through EEFatalError is in this category if(pThread->IsLastThrownObjectUnhandled()) { return TRUE; } // most managed exceptions are just a throwable bound to a // native exception. In that case this handle will be non-null OBJECTHANDLE ohException = pThread->GetThrowableAsHandle(); if (ohException != NULL) { // during the UEF we set the unhandled bit, if it is set the exception // was unhandled // however if the exception has intercept info then we consider it handled // again return pThread->GetExceptionState()->GetFlags()->IsUnhandled() && !(pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo()); } return FALSE; } // Return the user state of the specified thread. CorDebugUserState DacDbiInterfaceImpl::GetUserState(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; UINT result = 0; result = GetPartialUserState(vmThread); if (!IsThreadAtGCSafePlace(vmThread)) { result |= USER_UNSAFE_POINT; } return (CorDebugUserState)result; } // Return the connection ID of the specified thread. CONNID DacDbiInterfaceImpl::GetConnectionID(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); return pThread->GetConnectionId(); } // Return the task ID of the specified thread. TASKID DacDbiInterfaceImpl::GetTaskID(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); return pThread->GetTaskId(); } // Return the OS thread ID of the specified thread DWORD DacDbiInterfaceImpl::TryGetVolatileOSThreadID(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); _ASSERTE(pThread != NULL); DWORD dwThreadId = pThread->GetOSThreadIdForDebugger(); // If the thread ID is a the magical cookie value, then this is really // a switched out thread and doesn't have an OS tid. In that case, the // DD contract is to return 0 (a much more sane value) const DWORD dwSwitchedOutThreadId = SWITCHED_OUT_FIBER_OSID; if (dwThreadId == dwSwitchedOutThreadId) { return 0; } return dwThreadId; } // Return the unique thread ID of the specified thread. DWORD DacDbiInterfaceImpl::GetUniqueThreadID(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); _ASSERTE(pThread != NULL); if (CLRTaskHosted()) { return pThread->GetThreadId(); } else { return pThread->GetOSThreadId(); } } // Return the object handle to the managed Exception object of the current exception // on the specified thread. The return value could be NULL if there is no current exception. VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetCurrentException(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); // OBJECTHANDLEs are really just TADDRs. OBJECTHANDLE ohException = pThread->GetThrowableAsHandle(); // ohException can be NULL if (ohException == NULL) { if (pThread->IsLastThrownObjectUnhandled()) { ohException = pThread->LastThrownObjectHandle(); } } VMPTR_OBJECTHANDLE vmObjHandle; vmObjHandle.SetDacTargetPtr(ohException); return vmObjHandle; } // Return the object handle to the managed object for a given CCW pointer. VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetObjectForCCW(CORDB_ADDRESS ccwPtr) { DD_ENTER_MAY_THROW; OBJECTHANDLE ohCCW = NULL; #ifdef FEATURE_COMINTEROP ComCallWrapper *pCCW = DACGetCCWFromAddress(ccwPtr); if (pCCW) { ohCCW = pCCW->GetObjectHandle(); } #endif VMPTR_OBJECTHANDLE vmObjHandle; vmObjHandle.SetDacTargetPtr(ohCCW); return vmObjHandle; } // Return the object handle to the managed CustomNotification object of the current notification // on the specified thread. The return value could be NULL if there is no current notification. // Arguments: // input: vmThread - the thread on which the notification occurred // Return value: object handle for the current notification (if any) on the thread. This will return non-null // if and only if we are currently inside a CustomNotification Callback (or a dump was generated while in this // callback) // VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetCurrentCustomDebuggerNotification(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); // OBJECTHANDLEs are really just TADDRs. OBJECTHANDLE ohNotification = pThread->GetThreadCurrNotification(); // ohNotification can be NULL VMPTR_OBJECTHANDLE vmObjHandle; vmObjHandle.SetDacTargetPtr(ohNotification); return vmObjHandle; } // Return the current appdomain the specified thread is in. VMPTR_AppDomain DacDbiInterfaceImpl::GetCurrentAppDomain(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); AppDomain * pAppDomain = pThread->GetDomain(); if (pAppDomain == NULL) { ThrowHR(E_FAIL); } VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr(); vmAppDomain.SetDacTargetPtr(PTR_HOST_TO_TADDR(pAppDomain)); return vmAppDomain; } // Returns a bitfield reflecting the managed debugging state at the time of // the jit attach. CLR_DEBUGGING_PROCESS_FLAGS DacDbiInterfaceImpl::GetAttachStateFlags() { DD_ENTER_MAY_THROW; CLR_DEBUGGING_PROCESS_FLAGS res = (CLR_DEBUGGING_PROCESS_FLAGS)0; if (g_pDebugger != NULL) { res = g_pDebugger->GetAttachStateFlags(); } else { // When launching the process under a managed debugger we // request these flags when CLR is loaded (before g_pDebugger // had a chance to be initialized). In these cases simply // return 0 } return res; } //--------------------------------------------------------------------------------------- // Helper to get the address of the 2nd-chance hijack function Or throw // // Returns: // Non-null Target Address of hijack function. TADDR DacDbiInterfaceImpl::GetHijackAddress() { TADDR addr = NULL; if (g_pDebugger != NULL) { // Get the start address of the redirect function for unhandled exceptions. addr = dac_cast(g_pDebugger->m_rgHijackFunction[Debugger::kUnhandledException].StartAddress()); } if (addr == NULL) { ThrowHR(CORDBG_E_NOTREADY); } return addr; } //--------------------------------------------------------------------------------------- // Helper to determine whether a control PC is in any native stub which the runtime knows how to unwind. // // Arguments: // taControlPC - control PC to be checked // // Returns: // Returns true if the control PC is in a runtime unwindable stub. // // Notes: // Currently this function only recognizes the ExceptionHijack() stub, // which is used for unhandled exceptions. // bool DacDbiInterfaceImpl::IsRuntimeUnwindableStub(PCODE targetControlPC) { TADDR controlPC = PCODEToPINSTR(targetControlPC); // we call this function a lot while walking the stack and the values here will never change // Getting the g_pDebugger and each entry in the m_rgHijackFunction is potentially ~7 DAC // accesses per frame. Caching the data into a single local array is much faster. This optimization // recovered a few % of DAC stackwalking time if(!m_isCachedHijackFunctionValid) { Debugger* pDebugger = g_pDebugger; if ((pDebugger == NULL) || (pDebugger->m_rgHijackFunction == NULL)) { // The in-process debugging infrastructure hasn't been fully initialized, which means that we could // NOT have hijacked anything yet. return false; } // PERF NOTE: if needed this array copy could probably be made more efficient // hitting the DAC only once for a single memory block, or even better // put the array inline in the Debugger object so that we only do 1 DAC // access for this entire thing for (int i = 0; i < Debugger::kMaxHijackFunctions; i++) { InitTargetBufferFromMemoryRange(pDebugger->m_rgHijackFunction[i], &m_pCachedHijackFunction[i] ); } m_isCachedHijackFunctionValid = TRUE; } // Check whether the control PC is in any of the thread redirection functions. for (int i = 0; i < Debugger::kMaxHijackFunctions; i++) { CORDB_ADDRESS start = m_pCachedHijackFunction[i].pAddress; CORDB_ADDRESS end = start + m_pCachedHijackFunction[i].cbSize; if ((start <= controlPC) && (controlPC < end)) { return true; } } return false; } //--------------------------------------------------------------------------------------- // Align a stack pointer for the given architecture // // Arguments: // pEsp - in/out: pointer to stack pointer. // void DacDbiInterfaceImpl::AlignStackPointer(CORDB_ADDRESS * pEsp) { SUPPORTS_DAC; // Nop on x86. #if defined(_WIN64) // on 64-bit, stack pointer must be 16-byte aligned. // Stacks grown down, so round down to nearest 0xF bits. *pEsp &= ~((CORDB_ADDRESS) 0xF); #endif } //--------------------------------------------------------------------------------------- // Emulate pushing something on a thread's stack. // // Arguments: // pEsp - in/out: pointer to stack pointer to push object at. On output, // updated stack pointer. // pData - object to push on the stack. // fAlignStack - whether to align the stack pointer before and after the push. // Callers which specify FALSE must be very careful and know exactly // what they are doing. // // Return: // address of pushed object. Throws on error. template CORDB_ADDRESS DacDbiInterfaceImpl::PushHelper(CORDB_ADDRESS * pEsp, const T * pData, BOOL fAlignStack) { SUPPORTS_DAC; if (fAlignStack == TRUE) { AlignStackPointer(pEsp); } *pEsp -= sizeof(T); if (fAlignStack == TRUE) { AlignStackPointer(pEsp); } SafeWriteStructOrThrow(*pEsp, pData); return *pEsp; } //--------------------------------------------------------------------------------------- // Write an EXCEPTION_RECORD structure to the remote target at the specified address while taking // into account the number of exception parameters. On 64-bit OS and on the WOW64, the OS always // pushes the entire EXCEPTION_RECORD onto the stack. However, on native x86 OS, the OS only pushes // enough of the EXCEPTION_RECORD to cover the specified number of exception parameters. Thus we // need to be extra careful when we overwrite an EXCEPTION_RECORD on the stack. // // Arguments: // pRemotePtr - address of the EXCEPTION_RECORD in the remote target // pExcepRecord - EXCEPTION_RECORD to be written // // Notes: // This function is only used by the code which hijacks a therad when there's an unhandled exception. // It only works when we are actually debugging a live process, not a dump. // void DacDbiInterfaceImpl::WriteExceptionRecordHelper(CORDB_ADDRESS pRemotePtr, const EXCEPTION_RECORD * pExcepRecord) { // Calculate the correct size to push onto the stack. ULONG32 cbSize = offsetof(EXCEPTION_RECORD, ExceptionInformation); cbSize += pExcepRecord->NumberParameters * sizeof(pExcepRecord->ExceptionInformation[0]); // Use the data target to write to the remote target. Here we are assuming that we are debugging a // live process, since this function is only called by the hijacking code for unhandled exceptions. HRESULT hr = m_pMutableTarget->WriteVirtual(pRemotePtr, reinterpret_cast(pExcepRecord), cbSize); if (FAILED(hr)) { ThrowHR(hr); } } // Implement IDacDbiInterface::Hijack void DacDbiInterfaceImpl::Hijack( VMPTR_Thread vmThread, ULONG32 dwThreadId, const EXCEPTION_RECORD * pRecord, T_CONTEXT * pOriginalContext, ULONG32 cbSizeContext, EHijackReason::EHijackReason reason, void * pUserData, CORDB_ADDRESS * pRemoteContextAddr) { DD_ENTER_MAY_THROW; // // Validate parameters // // pRecord may be NULL if we're not hijacking at an exception // pOriginalContext may be NULL if caller doesn't want a copy of the context. // (The hijack function already has the context) _ASSERTE((pOriginalContext == NULL) == (cbSizeContext == 0)); _ASSERTE(EHijackReason::IsValid(reason)); #ifdef PLATFORM_UNIX _ASSERTE(!"Not supported on this platform"); #endif // // If we hijack a thread which might not be managed we can set vmThread = NULL // The only side-effect in this case is that we can't reuse CONTEXT and // EXCEPTION_RECORD space on the stack by an already underway in-process exception // filter. If you depend on those being used and updated you must provide the vmThread // Thread* pThread = NULL; if(!vmThread.IsNull()) { pThread = vmThread.GetDacPtr(); _ASSERTE(pThread->GetOSThreadIdForDebugger() == dwThreadId); } TADDR pfnHijackFunction = GetHijackAddress(); // // Setup context for hijack // T_CONTEXT ctx; HRESULT hr = m_pTarget->GetThreadContext( dwThreadId, CONTEXT_FULL, sizeof(ctx), (BYTE*) &ctx); IfFailThrow(hr); // If caller requested, copy back the original context that we're hijacking from. if (pOriginalContext != NULL) { // Since Dac + DBI are tightly coupled, context sizes should be the same. if (cbSizeContext != sizeof(T_CONTEXT)) { ThrowHR(E_INVALIDARG); } memcpy(pOriginalContext, &ctx, cbSizeContext); } // Make sure the trace flag isn't on. This can happen if we were single stepping the thread when we faulted. This // will ensure that we don't try to single step through the OS's exception logic, which greatly confuses our second // chance hijack logic. This also mimics what the OS does for us automaically when single stepping in process, i.e., // when you turn the trace flag on in-process and go, if there is a fault, the fault is reported and the trace flag // is automatically turned off. // // The debugger could always re-enable the single-step flag if it wants to. #ifndef _TARGET_ARM_ UnsetSSFlag(reinterpret_cast(&ctx)); #endif // Push pointers void* espContext = NULL; void* espRecord = NULL; const void* pData = pUserData; // @dbgtodo cross-plat - this is not cross plat CORDB_ADDRESS esp = GetSP(&ctx); // // Find out where the OS exception dispatcher has pushed the EXCEPTION_RECORD and CONTEXT. The ExInfo and // ExceptionTracker have pointers to these data structures, but when we get the unhandled exception // notification, the OS exception dispatcher is no longer on the stack, so these pointers are no longer // valid. We need to either update these pointers in the ExInfo/ExcepionTracker, or reuse the stack // space used by the OS exception dispatcher. We are using the latter approach here. // CORDB_ADDRESS espOSContext = NULL; CORDB_ADDRESS espOSRecord = NULL; if (pThread != NULL && pThread->IsExceptionInProgress()) { espOSContext = (CORDB_ADDRESS)PTR_TO_TADDR(pThread->GetExceptionState()->GetContextRecord()); espOSRecord = (CORDB_ADDRESS)PTR_TO_TADDR(pThread->GetExceptionState()->GetExceptionRecord()); // The managed exception may not be related to the unhandled exception for which we are trying to // hijack. An example would be when a thread hits a managed exception, VS tries to do func eval on // the thread, but the func eval causes an unhandled exception (e.g. AV in mscorwks.dll). In this // case, the pointers stored on the ExInfo/ExceptionTracker are closer to the root than the current // SP of the thread. The check below makes sure we don't reuse the pointers in this case. if (espOSContext < esp) { SafeWriteStructOrThrow(espOSContext, &ctx); espContext = CORDB_ADDRESS_TO_PTR(espOSContext); // We should have an EXCEPTION_RECORD if we are hijacked at an exception. // We need to be careful when we overwrite the exception record. On x86, the OS doesn't // always push the full record onto the stack, and so we can't blindly use sizeof(EXCEPTION_RECORD). // Instead, we have to look at the number of exception parameters and calculate the size. _ASSERTE(pRecord != NULL); WriteExceptionRecordHelper(espOSRecord, pRecord); espRecord = CORDB_ADDRESS_TO_PTR(espOSRecord); esp = min(espOSContext, espOSRecord); } } // If we haven't reused the pointers, then push everything at the leaf of the stack. if (espContext == NULL) { _ASSERTE(espRecord == NULL); // Push on full Context and ExceptionRecord structures. We'll then push pointers to these, // and those pointers will serve as the actual args to the function. espContext = CORDB_ADDRESS_TO_PTR(PushHelper(&esp, &ctx, TRUE)); // If caller didn't pass an exception-record, then we're not being hijacked at an exception. // We'll just pass NULL for the exception-record to the Hijack function. if (pRecord != NULL) { espRecord = CORDB_ADDRESS_TO_PTR(PushHelper(&esp, pRecord, TRUE)); } } if(pRemoteContextAddr != NULL) { *pRemoteContextAddr = PTR_TO_CORDB_ADDRESS(espContext); } // // Push args onto the stack to be able to call the hijack function // // Prototype of hijack is: // void __stdcall ExceptionHijackWorker(CONTEXT * pContext, EXCEPTION_RECORD * pRecord, EHijackReason, void * pData) // Set up everything so that the hijack stub can just do a "call" instruction. // // Regarding stack overflow: We could do an explicit check against the thread's stack base limit. // However, we don't need an explicit overflow check because if the stack does overflow, // the hijack will just hit a regular stack-overflow exception. #if defined(_TARGET_X86_) // TARGET // X86 calling convention is to push args on the stack in reverse order. // If we fail here, the stack is written, but esp hasn't been committed yet so it shouldn't matter. PushHelper(&esp, &pData, TRUE); PushHelper(&esp, &reason, TRUE); PushHelper(&esp, &espRecord, TRUE); PushHelper(&esp, &espContext, TRUE); #elif defined (_TARGET_AMD64_) // TARGET // AMD64 calling convention is to place first 4 parameters in: rcx, rdx, r8 and r9 ctx.Rcx = (DWORD64) espContext; ctx.Rdx = (DWORD64) espRecord; ctx.R8 = (DWORD64) reason; ctx.R9 = (DWORD64) pData; // Caller must allocate stack space to spill for args. // Push the arguments onto the outgoing argument homes. // Make sure we push pointer-sized values to keep the stack aligned. PushHelper(&esp, reinterpret_cast(&(ctx.R9)), FALSE); PushHelper(&esp, reinterpret_cast(&(ctx.R8)), FALSE); PushHelper(&esp, reinterpret_cast(&(ctx.Rdx)), FALSE); PushHelper(&esp, reinterpret_cast(&(ctx.Rcx)), FALSE); #elif defined(_TARGET_ARM_) ctx.R0 = (DWORD)espContext; ctx.R1 = (DWORD)espRecord; ctx.R2 = (DWORD)reason; ctx.R3 = (DWORD)pData; #elif defined(_TARGET_ARM64_) ctx.X0 = (DWORD64)espContext; ctx.X1 = (DWORD64)espRecord; ctx.X2 = (DWORD64)reason; ctx.X3 = (DWORD64)pData; #else PORTABILITY_ASSERT("CordbThread::HijackForUnhandledException is not implemented on this platform."); #endif SetSP(&ctx, CORDB_ADDRESS_TO_TADDR(esp)); // @dbgtodo cross-plat - not cross-platform safe SetIP(&ctx, pfnHijackFunction); // // Commit the context. // hr = m_pMutableTarget->SetThreadContext(dwThreadId, sizeof(ctx), reinterpret_cast (&ctx)); IfFailThrow(hr); } // Return the filter CONTEXT on the LS. VMPTR_CONTEXT DacDbiInterfaceImpl::GetManagedStoppedContext(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; VMPTR_CONTEXT vmContext = VMPTR_CONTEXT::NullPtr(); Thread * pThread = vmThread.GetDacPtr(); if (pThread->GetInteropDebuggingHijacked()) { _ASSERTE(!ISREDIRECTEDTHREAD(pThread)); vmContext = VMPTR_CONTEXT::NullPtr(); } else { DT_CONTEXT * pLSContext = reinterpret_cast(pThread->GetFilterContext()); if (pLSContext != NULL) { _ASSERTE(!ISREDIRECTEDTHREAD(pThread)); vmContext.SetHostPtr(pLSContext); } else if (ISREDIRECTEDTHREAD(pThread)) { pLSContext = reinterpret_cast(GETREDIRECTEDCONTEXT(pThread)); _ASSERTE(pLSContext != NULL); if (pLSContext != NULL) { vmContext.SetHostPtr(pLSContext); } } } return vmContext; } // Return a TargetBuffer for the raw vararg signature. TargetBuffer DacDbiInterfaceImpl::GetVarArgSig(CORDB_ADDRESS VASigCookieAddr, CORDB_ADDRESS * pArgBase) { DD_ENTER_MAY_THROW; _ASSERTE(pArgBase != NULL); *pArgBase = NULL; // First, read the VASigCookie pointer. TADDR taVASigCookie = NULL; SafeReadStructOrThrow(VASigCookieAddr, &taVASigCookie); // Now create a DAC copy of VASigCookie. VASigCookie * pVACookie = PTR_VASigCookie(taVASigCookie); // Figure out where the first argument is. #if defined(_TARGET_X86_) // (STACK_GROWS_DOWN_ON_ARGS_WALK) *pArgBase = VASigCookieAddr + pVACookie->sizeOfArgs; #else // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK) *pArgBase = VASigCookieAddr + sizeof(VASigCookie *); #endif // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK) return TargetBuffer(PTR_TO_CORDB_ADDRESS(pVACookie->signature.GetRawSig()), pVACookie->signature.GetRawSigLen()); } // returns TRUE if the type requires 8-byte alignment BOOL DacDbiInterfaceImpl::RequiresAlign8(VMPTR_TypeHandle thExact) { DD_ENTER_MAY_THROW; #ifdef FEATURE_64BIT_ALIGNMENT TypeHandle th = TypeHandle::FromPtr(thExact.GetDacPtr()); PTR_MethodTable mt = th.AsMethodTable(); return mt->RequiresAlign8(); #else ThrowHR(E_NOTIMPL); #endif } // Resolve the raw generics token to the real generics type token. The resolution is based on the // given index. GENERICS_TYPE_TOKEN DacDbiInterfaceImpl::ResolveExactGenericArgsToken(DWORD dwExactGenericArgsTokenIndex, GENERICS_TYPE_TOKEN rawToken) { DD_ENTER_MAY_THROW; if (dwExactGenericArgsTokenIndex == 0) { // In this case the real generics type token is the MethodTable of the "this" object. // Note that we want the target address here. // Incoming rawToken is actually a PTR_Object for the 'this' pointer. // Need to do some casting to convert GENERICS_TYPE_TOKEN --> PTR_Object TADDR addrObjThis = CORDB_ADDRESS_TO_TADDR(rawToken); PTR_Object pObjThis = dac_cast(addrObjThis); PTR_MethodTable pMT = pObjThis->GetMethodTable(); // Now package up the PTR_MethodTable back into a GENERICS_TYPE_TOKEN TADDR addrMT = dac_cast(pMT); GENERICS_TYPE_TOKEN realToken = (GENERICS_TYPE_TOKEN) addrMT; return realToken; } else if (dwExactGenericArgsTokenIndex == (DWORD)ICorDebugInfo::TYPECTXT_ILNUM) { // rawToken is already initialized correctly. Nothing to do here. return rawToken; } // The index of the generics type token should not be anything else. // This is indeed an error condition, and so we throw here. _ASSERTE(!"DDII::REGAT - Unexpected generics type token index."); ThrowHR(CORDBG_E_TARGET_INCONSISTENT); } // Check if the given method is an IL stub or an LCD method. IDacDbiInterface::DynamicMethodType DacDbiInterfaceImpl::IsILStubOrLCGMethod(VMPTR_MethodDesc vmMethodDesc) { DD_ENTER_MAY_THROW; MethodDesc * pMD = vmMethodDesc.GetDacPtr(); if (pMD->IsILStub()) { return kILStub; } else if (pMD->IsLCGMethod()) { return kLCGMethod; } else { return kNone; } } //--------------------------------------------------------------------------------------- // // Determine whether the specified thread is at a GC safe place. // // Arguments: // vmThread - the thread to be examined // // Return Value: // Return TRUE if the thread is at a GC safe place. // and under what conditions // // Notes: // This function basically does a one-frame stackwalk. // The logic is adopted from Debugger::IsThreadAtSafePlace(). // BOOL DacDbiInterfaceImpl::IsThreadAtGCSafePlace(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; BOOL fIsGCSafe = FALSE; Thread * pThread = vmThread.GetDacPtr(); // Check if the runtime has entered "Shutdown for Finalizer" mode. if ((g_fEEShutDown & ShutDown_Finalize2) != 0) { fIsGCSafe = TRUE; } else { T_CONTEXT ctx; REGDISPLAY rd; SetUpRegdisplayForStackWalk(pThread, &ctx, &rd); ULONG32 flags = (QUICKUNWIND | HANDLESKIPPEDFRAMES | DISABLE_MISSING_FRAME_DETECTION); StackFrameIterator iter; iter.Init(pThread, pThread->GetFrame(), &rd, flags); CrawlFrame * pCF = &(iter.m_crawl); if (pCF->IsFrameless() && pCF->IsActiveFunc()) { if (pCF->IsGcSafe()) { fIsGCSafe = TRUE; } } } return fIsGCSafe; } //--------------------------------------------------------------------------------------- // // Return a partial user state of the specified thread. The returned user state doesn't contain // information about USER_UNSAFE_POINT. The caller needs to call IsThreadAtGCSafePlace() to get // the full user state. // // Arguments: // vmThread - the specified thread // // Return Value: // Return the partial user state except for USER_UNSAFE_POINT // CorDebugUserState DacDbiInterfaceImpl::GetPartialUserState(VMPTR_Thread vmThread) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); Thread::ThreadState ts = pThread->GetSnapshotState(); UINT result = 0; if (ts & Thread::TS_Background) { result |= USER_BACKGROUND; } if (ts & Thread::TS_Unstarted) { result |= USER_UNSTARTED; } // Don't report a StopRequested if the thread has actually stopped. if (ts & Thread::TS_Dead) { result |= USER_STOPPED; } // The interruptible flag is unreliable (see issue 699245) // The Debugger_SleepWaitJoin is always accurate when it is present, but it is still // just a band-aid fix to cover some of the race conditions interruptible has. if (ts & Thread::TS_Interruptible || pThread->HasThreadStateNC(Thread::TSNC_DebuggerSleepWaitJoin)) { result |= USER_WAIT_SLEEP_JOIN; } // Don't report a SuspendRequested if the thread has actually Suspended. if ((ts & Thread::TS_UserSuspendPending) && (ts & Thread::TS_SyncSuspended)) { result |= USER_SUSPENDED; } else if (ts & Thread::TS_UserSuspendPending) { result |= USER_SUSPEND_REQUESTED; } if (pThread->IsThreadPoolThread()) { result |= USER_THREADPOOL; } return (CorDebugUserState)result; } //--------------------------------------------------------------------------------------- // // Look up the EnC version number of a particular jitted instance of a managed method. // // Arguments: // pModule - the module containing the managed method // vmMethodDesc - the MethodDesc of the managed method // mdMethod - the MethodDef metadata token of the managed method // pNativeStartAddress - the native start address of the jitted code // pJittedInstanceEnCVersion - out parameter; the version number of the version // corresponding to the specified native start address // pLatestEnCVersion - out parameter; the version number of the latest version // // Assumptions: // vmMethodDesc and mdMethod must match (see below). // // Notes: // mdMethod is not strictly necessary, since we can always get that from vmMethodDesc. // It is just a perf optimization since the caller has the metadata token around already. // // Today, there is no way to retrieve the EnC version number from the RS data structures. // This primitive uses DAC to retrieve it from the LS data structures. This function may // very well be ripped out in the future if we DACize this information, but the current // thinking is that some of the RS data structures will remain, most likely in a reduced form. // void DacDbiInterfaceImpl::LookupEnCVersions(Module* pModule, VMPTR_MethodDesc vmMethodDesc, mdMethodDef mdMethod, CORDB_ADDRESS pNativeStartAddress, SIZE_T * pLatestEnCVersion, SIZE_T * pJittedInstanceEnCVersion /* = NULL */) { MethodDesc * pMD = vmMethodDesc.GetDacPtr(); // make sure the vmMethodDesc and mdMethod match _ASSERTE(pMD->GetMemberDef() == mdMethod); _ASSERTE(pLatestEnCVersion != NULL); // @dbgtodo inspection - once we do EnC, stop using DMIs. // If the method wasn't EnCed, DMIs may not exist. And since this is DAC, we can't create them. // We may not have the memory for the DebuggerMethodInfos in a minidump. // When dump debugging EnC information isn't very useful so just fallback // to default version. DebuggerMethodInfo * pDMI = NULL; DebuggerJitInfo * pDJI = NULL; EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY { pDMI = g_pDebugger->GetOrCreateMethodInfo(pModule, mdMethod); if (pDMI != NULL) { pDJI = pDMI->FindJitInfo(pMD, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress)); } } EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY; if (pDJI != NULL) { if (pJittedInstanceEnCVersion != NULL) { *pJittedInstanceEnCVersion = pDJI->m_encVersion; } *pLatestEnCVersion = pDMI->GetCurrentEnCVersion(); } else { // If we have no DMI/DJI, then we must never have EnCed. So we can use default EnC info // Several cases where we don't have a DMI/DJI: // - LCG methods // - method was never "touched" by debugger. (DJIs are created lazily). if (pJittedInstanceEnCVersion != NULL) { *pJittedInstanceEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; } *pLatestEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; } } // Get the address of the Debugger control block on the helper thread // Arguments: none // Return Value: The remote address of the Debugger control block allocated on the helper thread // if it has been successfully allocated or NULL otherwise. CORDB_ADDRESS DacDbiInterfaceImpl::GetDebuggerControlBlockAddress() { DD_ENTER_MAY_THROW; if ((g_pDebugger != NULL) && (g_pDebugger->m_pRCThread != NULL)) { return CORDB_ADDRESS(dac_cast(g_pDebugger->m_pRCThread->GetDCB())); } return NULL; } // DacDbi API: Get the context for a particular thread of the target process void DacDbiInterfaceImpl::GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContextBuffer) { DD_ENTER_MAY_THROW _ASSERTE(pContextBuffer != NULL); Thread * pThread = vmThread.GetDacPtr(); // @dbgtodo Once the filter context is removed, then we should always // start with the leaf CONTEXT. DT_CONTEXT * pFilterContext = reinterpret_cast(pThread->GetFilterContext()); if (pFilterContext == NULL) { // If the filter context is NULL, then we use the true context of the thread. pContextBuffer->ContextFlags = CONTEXT_ALL; HRESULT hr = m_pTarget->GetThreadContext(pThread->GetOSThreadId(), pContextBuffer->ContextFlags, sizeof(*pContextBuffer), reinterpret_cast(pContextBuffer)); if (hr == E_NOTIMPL) { // GetThreadContext is not implemented on this data target. // That's why we have to make do with context we can obtain from Frames explicitly stored in Thread object. // It suffices for managed debugging stackwalk. REGDISPLAY tmpRd = {}; T_CONTEXT tmpContext = {}; FillRegDisplay(&tmpRd, &tmpContext); // Going through thread Frames and looking for first (deepest one) one that // that has context available for stackwalking (SP and PC) // For example: RedirectedThreadFrame, InlinedCallFrame, HelperMethodFrame, ComPlusMethodFrame Frame *frame = pThread->GetFrame(); while (frame != NULL && frame != FRAME_TOP) { frame->UpdateRegDisplay(&tmpRd); if (GetRegdisplaySP(&tmpRd) != 0 && GetControlPC(&tmpRd) != 0) { UpdateContextFromRegDisp(&tmpRd, &tmpContext); CopyMemory(pContextBuffer, &tmpContext, sizeof(*pContextBuffer)); pContextBuffer->ContextFlags = DT_CONTEXT_CONTROL; return; } frame = frame->Next(); } // It looks like this thread is not running managed code. ZeroMemory(pContextBuffer, sizeof(*pContextBuffer)); } else { IfFailThrow(hr); } } else { *pContextBuffer = *pFilterContext; } } // DacDbiInterfaceImpl::GetContext // Create a VMPTR_Object from a target object address // @dbgtodo validate the VMPTR_Object is in fact a object, possibly by DACizing // Object::Validate VMPTR_Object DacDbiInterfaceImpl::GetObject(CORDB_ADDRESS ptr) { DD_ENTER_MAY_THROW; VMPTR_Object vmObj = VMPTR_Object::NullPtr(); vmObj.SetDacTargetPtr(CORDB_ADDRESS_TO_TADDR(ptr)); return vmObj; } HRESULT DacDbiInterfaceImpl::EnableNGENPolicy(CorDebugNGENPolicy ePolicy) { #ifndef FEATURE_CORECLR DD_ENTER_MAY_THROW; // translate from our publicly exposed enum to the appropriate internal value AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS asmFlag = AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS_NONE; switch (ePolicy) { case DISABLE_LOCAL_NIC: asmFlag = AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS_APPLOCALNGENDISABLED; break; default: return E_INVALIDARG; } // we should have made some selection _ASSERTE(asmFlag != AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS_NONE); return AssemblyUsageLogManager::SetUsageLogFlag(asmFlag, TRUE); #else return E_NOTIMPL; #endif // FEATURE_CORECLR } HRESULT DacDbiInterfaceImpl::SetNGENCompilerFlags(DWORD dwFlags) { DD_ENTER_MAY_THROW; #ifndef FEATURE_PREJIT return CORDBG_E_NGEN_NOT_SUPPORTED; #else // verify that we are still early enough in runtime lifecycle to mutate these // flags. Typically this is done in the CreateProcess event though it is possible // to do it even earlier if(!Debugger::s_fCanChangeNgenFlags) return CORDBG_E_MUST_BE_IN_CREATE_PROCESS; BOOL fAllowOpt = ((dwFlags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION); PEFile::SetNGENDebugFlags(fAllowOpt); return S_OK; #endif } HRESULT DacDbiInterfaceImpl::GetNGENCompilerFlags(DWORD *pdwFlags) { DD_ENTER_MAY_THROW; #ifndef FEATURE_PREJIT return CORDBG_E_NGEN_NOT_SUPPORTED; #else BOOL fAllowOpt = TRUE; PEFile::GetNGENDebugFlags(&fAllowOpt); if(!fAllowOpt) { *pdwFlags = CORDEBUG_JIT_DISABLE_OPTIMIZATION; } else { *pdwFlags = CORDEBUG_JIT_DEFAULT; } return S_OK; #endif } typedef DPTR(OBJECTREF) PTR_ObjectRef; // Create a VMPTR_Object from an address which points to a reference to an object // @dbgtodo validate the VMPTR_Object is in fact a object, possibly by DACizing // Object::Validate VMPTR_Object DacDbiInterfaceImpl::GetObjectFromRefPtr(CORDB_ADDRESS ptr) { DD_ENTER_MAY_THROW; VMPTR_Object vmObj = VMPTR_Object::NullPtr(); PTR_ObjectRef objRef = PTR_ObjectRef(CORDB_ADDRESS_TO_TADDR(ptr)); vmObj.SetDacTargetPtr(PTR_TO_TADDR(*objRef)); return vmObj; } // Create a VMPTR_OBJECTHANDLE from a handle VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetVmObjectHandle(CORDB_ADDRESS handleAddress) { DD_ENTER_MAY_THROW; VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr(); vmObjHandle.SetDacTargetPtr(CORDB_ADDRESS_TO_TADDR(handleAddress)); return vmObjHandle; } // Validate that the VMPTR_OBJECTHANDLE refers to a legitimate managed object BOOL DacDbiInterfaceImpl::IsVmObjectHandleValid(VMPTR_OBJECTHANDLE vmHandle) { DD_ENTER_MAY_THROW; BOOL ret = FALSE; // this may cause unallocated debuggee memory to be read // SEH exceptions will be caught EX_TRY { OBJECTREF objRef = ObjectFromHandle((OBJECTHANDLE)vmHandle.GetDacPtr()); // NULL is certinally valid... if (objRef != NULL) { if (objRef->ValidateObjectWithPossibleAV()) { ret = TRUE; } } } EX_CATCH { } EX_END_CATCH(SwallowAllExceptions); return ret; } // determines if the specified module is a WinRT module HRESULT DacDbiInterfaceImpl::IsWinRTModule(VMPTR_Module vmModule, BOOL& isWinRT) { DD_ENTER_MAY_THROW; HRESULT hr = S_OK; isWinRT = FALSE; EX_TRY { Module* pModule = vmModule.GetDacPtr(); isWinRT = pModule->GetFile()->GetAssembly()->IsWindowsRuntime(); } EX_CATCH_HRESULT(hr); return hr; } // Determines the app domain id for the object refered to by a given VMPTR_OBJECTHANDLE ULONG DacDbiInterfaceImpl::GetAppDomainIdFromVmObjectHandle(VMPTR_OBJECTHANDLE vmHandle) { DD_ENTER_MAY_THROW; OBJECTHANDLE handle = (OBJECTHANDLE) vmHandle.GetDacPtr(); return HndGetHandleADIndex(handle).m_dwIndex; } // Get the target address from a VMPTR_OBJECTHANDLE, i.e., the handle address CORDB_ADDRESS DacDbiInterfaceImpl::GetHandleAddressFromVmHandle(VMPTR_OBJECTHANDLE vmHandle) { DD_ENTER_MAY_THROW; CORDB_ADDRESS handle = vmHandle.GetDacPtr(); return handle; } // Create a TargetBuffer which describes the location of the object TargetBuffer DacDbiInterfaceImpl::GetObjectContents(VMPTR_Object vmObj) { DD_ENTER_MAY_THROW; PTR_Object objPtr = vmObj.GetDacPtr(); _ASSERTE(objPtr->GetSize() <= 0xffffffff); return TargetBuffer(PTR_TO_TADDR(objPtr), (ULONG)objPtr->GetSize()); } // ============================================================================ // functions to get information about objects referenced via an instance of CordbReferenceValue or // CordbHandleValue // ============================================================================ // DacDbiInterfaceImpl::FastSanityCheckObject // Helper function for CheckRef. Sanity check an object. // We use a fast and easy check to improve confidence that objPtr points to a valid object. // We can't tell cheaply if this is really a valid object (that would require walking the GC heap), but at // least we can check if we get an EEClass from the supposed method table and then get the method table from // the class. If we can, we have improved the probability that the object is valid. // Arguments: // input: objPtr - address of the object we are checking // Return Value: E_INVALIDARG or S_OK. HRESULT DacDbiInterfaceImpl::FastSanityCheckObject(PTR_Object objPtr) { CONTRACTL { SO_NOT_MAINLINE; NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; HRESULT hr = S_OK; EX_TRY { // NULL is certainly valid... if (objPtr != NULL) { if (!objPtr->ValidateObjectWithPossibleAV()) { LOG((LF_CORDB, LL_INFO10000, "GOI: object methodtable-class invariant doesn't hold.\n")); hr = E_INVALIDARG; } } } EX_CATCH { LOG((LF_CORDB, LL_INFO10000, "GOI: exception indicated ref is bad.\n")); hr = E_INVALIDARG; } EX_END_CATCH(SwallowAllExceptions); return hr; } // DacDbiInterfaceImpl::FastSanityCheckObject // Perform a sanity check on an object address to determine if this _could be_ a valid object. // We can't tell this for certain without walking the GC heap, but we do some fast tests to rule // out clearly invalid object addresses. See code:DacDbiInterfaceImpl::FastSanityCheckObject for more // details. // Arguments: // input: objPtr - address of the object we are checking // Return Value: // objRefBad - true iff we have determined the address cannot be pointing to a valid object. // Note that a value of false doesn't necessarily guarantee the object is really // valid bool DacDbiInterfaceImpl::CheckRef(PTR_Object objPtr) { bool objRefBad = false; // Shortcut null references now... if (objPtr == NULL) { LOG((LF_CORDB, LL_INFO10000, "D::GOI: ref is NULL.\n")); objRefBad = true; } else { // Try to verify the integrity of the object. This is not fool proof. // @todo - this whole idea of expecting AVs is broken, but it does rule // out a fair bit of rubbish. Find another // way to test if the object is valid? if (FAILED(FastSanityCheckObject(objPtr))) { LOG((LF_CORDB, LL_INFO10000, "D::GOI: address is not a valid object.\n")); objRefBad = true; } } return objRefBad; } // DacDbiInterfaceImpl::CheckRef // DacDbiInterfaceImpl::InitObjectData // Initialize basic object information: type handle, object size, offset to fields and expanded type // information. // Arguments: // input: objPtr - address of object of interest // vmAppDomain - AppDomain for the type f the object // output: pObjectData - object information // Note: It is assumed that pObjectData is non-null. void DacDbiInterfaceImpl::InitObjectData(PTR_Object objPtr, VMPTR_AppDomain vmAppDomain, DebuggerIPCE_ObjectData * pObjectData) { _ASSERTE(pObjectData != NULL); // @todo - this is still dangerous because the object may still be invalid. VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr(); vmTypeHandle.SetDacTargetPtr(objPtr->GetGCSafeTypeHandle().AsTAddr()); // Save basic object info. pObjectData->objSize = objPtr->GetSize(); pObjectData->objOffsetToVars = dac_cast((objPtr)->GetData()) - dac_cast(objPtr); TypeHandleToExpandedTypeInfo(AllBoxed, vmAppDomain, vmTypeHandle, &(pObjectData->objTypeData)); // If this is a string object, set the type to ELEMENT_TYPE_STRING. if (objPtr->GetGCSafeMethodTable() == g_pStringClass) { pObjectData->objTypeData.elementType = ELEMENT_TYPE_STRING; if(pObjectData->objSize < MIN_OBJECT_SIZE) { pObjectData->objSize = PtrAlign(pObjectData->objSize); } } } // DacDbiInterfaceImpl::InitObjectData // DAC/DBI API // Get object information for a TypedByRef object (System.TypedReference). // These are objects that contain a managed pointer to a location and the type of the value at that location. // They are most commonly used for varargs but also may be used for parameters and locals. They are // stack-allocated. They provide a means for adding dynamic type information to a value type, whereas boxing // provides only static type information. This means they can be passed as reference parameters to // polymorphic methods that don't statically restrict the type of arguments they can receive. // Although they are represented simply as an address, unlike other object references, they don't point // directly to the object. Instead, there is an extra level of indirection. The reference points to a struct // that contains the address of the object, so we need to treat them differently. They have their own // CorElementType (ELEMENT_TYPE_TYPEDBYREF) which makes it possible to identify this special case. // Example: // static int AddABunchOfInts (__arglist) // { // int result = 0; // // System.ArgIterator iter = new System.ArgIterator (__arglist); // int argCount = iter.GetRemainingCount(); // // for (int i = 0; i < argCount; i++) // { // System.TypedReference typedRef = iter.GetNextArg(); // result += (int)TypedReference.ToObject(typedRef); // } // // return result; // } // // static int Main (string[] args) // { // int result = AddABunchOfInts (__arglist (2, 3, 4)); // Console.WriteLine ("Answer: {0}", result); // // if (result != 9) // return 1; // // return 0; // } // Initializes the objRef and typedByRefType fields of pObjectData (type info for the referent). void DacDbiInterfaceImpl::GetTypedByRefInfo(CORDB_ADDRESS pTypedByRef, VMPTR_AppDomain vmAppDomain, DebuggerIPCE_ObjectData * pObjectData) { DD_ENTER_MAY_THROW; // pTypedByRef is really the address of a TypedByRef struct rather than of a normal object. // The data field of the TypedByRef struct is the actual object ref. PTR_TypedByRef refAddr = PTR_TypedByRef(TADDR(pTypedByRef)); _ASSERTE(refAddr != NULL); _ASSERTE(pObjectData != NULL); // The type of the referent is in the type field of the TypedByRef. We need to initialize the object // data type information. TypeHandleToBasicTypeInfo(refAddr->type, &(pObjectData->typedByrefInfo.typedByrefType), vmAppDomain.GetDacPtr()); // The reference to the object is in the data field of the TypedByRef. CORDB_ADDRESS tempRef = dac_cast(refAddr->data); pObjectData->objRef = CORDB_ADDRESS_TO_PTR(tempRef); LOG((LF_CORDB, LL_INFO10000, "D::GASOI: sending REFANY result: " "ref=0x%08x, cls=0x%08x, mod=0x%p\n", pObjectData->objRef, pObjectData->typedByrefType.metadataToken, pObjectData->typedByrefType.vmDomainFile.GetDacPtr())); } // DacDbiInterfaceImpl::GetTypedByRefInfo // Get the string data associated withn obj and put it into the pointers // DAC/DBI API // Get the string length and offset to string base for a string object void DacDbiInterfaceImpl::GetStringData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData) { DD_ENTER_MAY_THROW; PTR_Object objPtr = PTR_Object(TADDR(objectAddress)); LOG((LF_CORDB, LL_INFO10000, "D::GOI: The referent is a string.\n")); if (objPtr->GetGCSafeMethodTable() != g_pStringClass) { ThrowHR(CORDBG_E_TARGET_INCONSISTENT); } PTR_StringObject pStrObj = dac_cast(objPtr); _ASSERTE(pStrObj != NULL); pObjectData->stringInfo.length = pStrObj->GetStringLength(); pObjectData->stringInfo.offsetToStringBase = (UINT_PTR) pStrObj->GetBufferOffset(); } // DacDbiInterfaceImpl::GetStringData // DAC/DBI API // Get information for an array type referent of an objRef, including rank, upper and lower // bounds, element size and type, and the number of elements. void DacDbiInterfaceImpl::GetArrayData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData) { DD_ENTER_MAY_THROW; PTR_Object objPtr = PTR_Object(TADDR(objectAddress)); PTR_MethodTable pMT = objPtr->GetGCSafeMethodTable(); if (!objPtr->GetGCSafeTypeHandle().IsArray()) { LOG((LF_CORDB, LL_INFO10000, "D::GASOI: object should be an array.\n")); pObjectData->objRefBad = true; } else { PTR_ArrayBase arrPtr = dac_cast(objPtr); // this is also returned in the type information for the array - we return both for sanity checking... pObjectData->arrayInfo.rank = arrPtr->GetRank(); pObjectData->arrayInfo.componentCount = arrPtr->GetNumComponents(); pObjectData->arrayInfo.offsetToArrayBase = arrPtr->GetDataPtrOffset(pMT); if (arrPtr->IsMultiDimArray()) { pObjectData->arrayInfo.offsetToUpperBounds = SIZE_T(arrPtr->GetBoundsOffset(pMT)); pObjectData->arrayInfo.offsetToLowerBounds = SIZE_T(arrPtr->GetLowerBoundsOffset(pMT)); } else { pObjectData->arrayInfo.offsetToUpperBounds = 0; pObjectData->arrayInfo.offsetToLowerBounds = 0; } pObjectData->arrayInfo.elementSize = arrPtr->GetComponentSize(); LOG((LF_CORDB, LL_INFO10000, "D::GOI: array info: " "baseOff=%d, lowerOff=%d, upperOff=%d, cnt=%d, rank=%d, rank (2) = %d," "eleSize=%d, eleType=0x%02x\n", pObjectData->arrayInfo.offsetToArrayBase, pObjectData->arrayInfo.offsetToLowerBounds, pObjectData->arrayInfo.offsetToUpperBounds, pObjectData->arrayInfo.componentCount, pObjectData->arrayInfo.rank, pObjectData->objTypeData.ArrayTypeData.arrayRank, pObjectData->arrayInfo.elementSize, pObjectData->objTypeData.ArrayTypeData.arrayTypeArg.elementType)); } } // DacDbiInterfaceImpl::GetArrayData // DAC/DBI API: Get information about an object for which we have a reference, including the object size and // type information. void DacDbiInterfaceImpl::GetBasicObjectInfo(CORDB_ADDRESS objectAddress, CorElementType type, VMPTR_AppDomain vmAppDomain, DebuggerIPCE_ObjectData * pObjectData) { DD_ENTER_MAY_THROW; PTR_Object objPtr = PTR_Object(TADDR(objectAddress)); pObjectData->objRefBad = CheckRef(objPtr); if (pObjectData->objRefBad != true) { // initialize object type, size, offset information. Note: We may have a different element type // after this. For example, we may start with E_T_CLASS but return with something more specific. InitObjectData (objPtr, vmAppDomain, pObjectData); } } // DacDbiInterfaceImpl::GetBasicObjectInfo // This is the data passed to EnumerateBlockingObjectsCallback below struct BlockingObjectUserDataWrapper { CALLBACK_DATA pUserData; IDacDbiInterface::FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback; }; // The callback helper used by EnumerateBlockingObjects below, this // callback in turn invokes the user's callback with the right arguments void EnumerateBlockingObjectsCallback(PTR_DebugBlockingItem obj, VOID* pUserData) { BlockingObjectUserDataWrapper* wrapper = (BlockingObjectUserDataWrapper*)pUserData; DacBlockingObject dacObj; // init to an arbitrary value to avoid mac compiler error about unintialized use // it will be correctly set in the switch and is never used with only this init here dacObj.blockingReason = DacBlockReason_MonitorCriticalSection; dacObj.vmBlockingObject.SetDacTargetPtr(dac_cast(OBJECTREFToObject(obj->pMonitor->GetOwningObject()))); dacObj.dwTimeout = obj->dwTimeout; dacObj.vmAppDomain.SetDacTargetPtr(dac_cast(obj->pAppDomain)); switch(obj->type) { case DebugBlock_MonitorCriticalSection: dacObj.blockingReason = DacBlockReason_MonitorCriticalSection; break; case DebugBlock_MonitorEvent: dacObj.blockingReason = DacBlockReason_MonitorEvent; break; default: _ASSERTE(!"obj->type has an invalid value"); return; } wrapper->fpCallback(dacObj, wrapper->pUserData); } // DAC/DBI API: // Enumerate all monitors blocking a thread void DacDbiInterfaceImpl::EnumerateBlockingObjects(VMPTR_Thread vmThread, FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback, CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; Thread * pThread = vmThread.GetDacPtr(); _ASSERTE(pThread != NULL); BlockingObjectUserDataWrapper wrapper; wrapper.fpCallback = fpCallback; wrapper.pUserData = pUserData; pThread->DebugBlockingInfo.VisitBlockingItems((DebugBlockingItemVisitor)EnumerateBlockingObjectsCallback, (VOID*)&wrapper); } // DAC/DBI API: // Returns the thread which owns the monitor lock on an object and the acquisition count MonitorLockInfo DacDbiInterfaceImpl::GetThreadOwningMonitorLock(VMPTR_Object vmObject) { DD_ENTER_MAY_THROW; MonitorLockInfo info; info.lockOwner = VMPTR_Thread::NullPtr(); info.acquisitionCount = 0; Object* pObj = vmObject.GetDacPtr(); DWORD threadId; DWORD acquisitionCount; if(!pObj->GetHeader()->GetThreadOwningMonitorLock(&threadId, &acquisitionCount)) { return info; } Thread *pThread = ThreadStore::GetThreadList(NULL); while (pThread != NULL) { if(pThread->GetThreadId() == threadId) { info.lockOwner.SetDacTargetPtr(PTR_HOST_TO_TADDR(pThread)); info.acquisitionCount = acquisitionCount; return info; } pThread = ThreadStore::GetThreadList(pThread); } _ASSERTE(!"A thread should have been found"); return info; } // The data passed to EnumerateThreadsCallback below struct ThreadUserDataWrapper { CALLBACK_DATA pUserData; IDacDbiInterface::FP_THREAD_ENUMERATION_CALLBACK fpCallback; }; // The callback helper used for EnumerateMonitorEventWaitList below. This callback // invokes the user's callback with the correct arguments. void EnumerateThreadsCallback(PTR_Thread pThread, VOID* pUserData) { ThreadUserDataWrapper* wrapper = (ThreadUserDataWrapper*)pUserData; VMPTR_Thread vmThread = VMPTR_Thread::NullPtr(); vmThread.SetDacTargetPtr(dac_cast(pThread)); wrapper->fpCallback(vmThread, wrapper->pUserData); } // DAC/DBI API: // Enumerate all threads waiting on the monitor event for an object void DacDbiInterfaceImpl::EnumerateMonitorEventWaitList(VMPTR_Object vmObject, FP_THREAD_ENUMERATION_CALLBACK fpCallback, CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; Object* pObj = vmObject.GetDacPtr(); SyncBlock* psb = pObj->PassiveGetSyncBlock(); // no sync block means no wait list if(psb == NULL) return; ThreadUserDataWrapper wrapper; wrapper.fpCallback = fpCallback; wrapper.pUserData = pUserData; ThreadQueue::EnumerateThreads(psb, (FP_TQ_THREAD_ENUMERATION_CALLBACK)EnumerateThreadsCallback, (VOID*) &wrapper); } bool DacDbiInterfaceImpl::AreGCStructuresValid() { return true; } HeapData::HeapData() : YoungestGenPtr(0), YoungestGenLimit(0), Gen0Start(0), Gen0End(0), SegmentCount(0), Segments(0) { } HeapData::~HeapData() { if (Segments) delete [] Segments; } LinearReadCache::LinearReadCache() : mCurrPageStart(0), mPageSize(0), mCurrPageSize(0), mPage(0) { SYSTEM_INFO si; GetSystemInfo(&si); mPageSize = si.dwPageSize; mPage = new (nothrow) BYTE[mPageSize]; } LinearReadCache::~LinearReadCache() { if (mPage) delete [] mPage; } bool LinearReadCache::MoveToPage(CORDB_ADDRESS addr) { mCurrPageStart = addr - (addr % mPageSize); HRESULT hr = g_dacImpl->m_pTarget->ReadVirtual(mCurrPageStart, mPage, mPageSize, &mCurrPageSize); if (hr != S_OK) { mCurrPageStart = 0; mCurrPageSize = 0; return false; } return true; } CORDB_ADDRESS DacHeapWalker::HeapStart = 0; CORDB_ADDRESS DacHeapWalker::HeapEnd = ~0; DacHeapWalker::DacHeapWalker() : mThreadCount(0), mAllocInfo(0), mHeapCount(0), mHeaps(0), mCurrObj(0), mCurrSize(0), mCurrMT(0), mCurrHeap(0), mCurrSeg(0), mStart((TADDR)HeapStart), mEnd((TADDR)HeapEnd) { } DacHeapWalker::~DacHeapWalker() { if (mAllocInfo) delete [] mAllocInfo; if (mHeaps) delete [] mHeaps; } SegmentData *DacHeapWalker::FindSegment(CORDB_ADDRESS obj) { for (size_t i = 0; i < mHeapCount; ++i) for (size_t j = 0; j < mHeaps[i].SegmentCount; ++j) if (mHeaps[i].Segments[j].Start <= obj && obj <= mHeaps[i].Segments[j].End) return &mHeaps[i].Segments[j]; return NULL; } HRESULT DacHeapWalker::Next(CORDB_ADDRESS *pValue, CORDB_ADDRESS *pMT, ULONG64 *pSize) { if (!HasMoreObjects()) return E_FAIL; if (pValue) *pValue = mCurrObj; if (pMT) *pMT = (CORDB_ADDRESS)mCurrMT; if (pSize) *pSize = (ULONG64)mCurrSize; HRESULT hr = MoveToNextObject(); return FAILED(hr) ? hr : S_OK; } HRESULT DacHeapWalker::MoveToNextObject() { do { // Move to the next object mCurrObj += mCurrSize; // Check to see if we are in the correct bounds. if (mHeaps[mCurrHeap].Gen0Start <= mCurrObj && mHeaps[mCurrHeap].Gen0End > mCurrObj) CheckAllocAndSegmentRange(); // Check to see if we've moved off the end of a segment if (mCurrObj >= mHeaps[mCurrHeap].Segments[mCurrSeg].End || mCurrObj > mEnd) { HRESULT hr = NextSegment(); if (FAILED(hr) || hr == S_FALSE) return hr; } // Get the method table pointer if (!mCache.ReadMT(mCurrObj, &mCurrMT)) return E_FAIL; if (!GetSize(mCurrMT, mCurrSize)) return E_FAIL; } while (mCurrObj < mStart); _ASSERTE(mStart <= mCurrObj && mCurrObj <= mEnd); return S_OK; } bool DacHeapWalker::GetSize(TADDR tMT, size_t &size) { // With heap corruption, it's entierly possible that the MethodTable // we get is bad. This could cause exceptions, which we will catch // and return false. This causes the heapwalker to move to the next // segment. bool ret = true; EX_TRY { MethodTable *mt = PTR_MethodTable(tMT); size_t cs = mt->GetComponentSize(); if (cs) { DWORD tmp = 0; if (mCache.Read(mCurrObj+sizeof(TADDR), &tmp)) cs *= tmp; else ret = false; } size = mt->GetBaseSize() + cs; // The size is not guaranteed to be aligned, we have to // do that ourself. if (mHeaps[mCurrHeap].Segments[mCurrSeg].Generation == 3) size = AlignLarge(size); else size = Align(size); } EX_CATCH { ret = false; } EX_END_CATCH(SwallowAllExceptions) return ret; } HRESULT DacHeapWalker::NextSegment() { mCurrObj = 0; mCurrMT = 0; mCurrSize = 0; do { mCurrSeg++; while (mCurrSeg >= mHeaps[mCurrHeap].SegmentCount) { mCurrSeg = 0; mCurrHeap++; if (mCurrHeap >= mHeapCount) { return S_FALSE; } } mCurrObj = mHeaps[mCurrHeap].Segments[mCurrSeg].Start; if (mHeaps[mCurrHeap].Gen0Start <= mCurrObj && mHeaps[mCurrHeap].Gen0End > mCurrObj) CheckAllocAndSegmentRange(); if (!mCache.ReadMT(mCurrObj, &mCurrMT)) { return E_FAIL; } if (!GetSize(mCurrMT, mCurrSize)) { return E_FAIL; } } while((mHeaps[mCurrHeap].Segments[mCurrSeg].Start > mEnd) || (mHeaps[mCurrHeap].Segments[mCurrSeg].End < mStart)); return S_OK; } void DacHeapWalker::CheckAllocAndSegmentRange() { const size_t MinObjSize = sizeof(TADDR)*3; for (int i = 0; i < mThreadCount; ++i) if (mCurrObj == mAllocInfo[i].Ptr) { mCurrObj = mAllocInfo[i].Limit + Align(MinObjSize); break; } if (mCurrObj == mHeaps[mCurrHeap].YoungestGenPtr) { mCurrObj = mHeaps[mCurrHeap].YoungestGenLimit + Align(MinObjSize); } } HRESULT DacHeapWalker::Init(CORDB_ADDRESS start, CORDB_ADDRESS end) { // Collect information about the allocation contexts in the process. ThreadStore* threadStore = ThreadStore::s_pThreadStore; if (threadStore != NULL) { int count = (int)threadStore->ThreadCountInEE(); mAllocInfo = new (nothrow) AllocInfo[count]; if (mAllocInfo == NULL) return E_OUTOFMEMORY; Thread *thread = NULL; int j = 0; for (int i = 0; i < count; ++i) { // The thread or allocation context being null is troubling, but not fatal. // We may have stopped the process where the thread list or thread's alloc // context was in an inconsistent state. We will simply skip over affected // segments during the heap walk if we encounter problems due to this. thread = ThreadStore::GetThreadList(thread); if (thread == NULL) continue; gc_alloc_context *ctx = thread->GetAllocContext(); if (ctx == NULL) continue; if ((CORDB_ADDRESS)ctx->alloc_ptr != NULL) { mAllocInfo[j].Ptr = (CORDB_ADDRESS)ctx->alloc_ptr; mAllocInfo[j].Limit = (CORDB_ADDRESS)ctx->alloc_limit; j++; } } mThreadCount = j; } #ifdef FEATURE_SVR_GC HRESULT hr = GCHeapUtilities::IsServerHeap() ? InitHeapDataSvr(mHeaps, mHeapCount) : InitHeapDataWks(mHeaps, mHeapCount); #else HRESULT hr = InitHeapDataWks(mHeaps, mHeapCount); #endif // Set up mCurrObj/mCurrMT. if (SUCCEEDED(hr)) hr = Reset(start, end); // Collect information about GC heaps return hr; } HRESULT DacHeapWalker::Reset(CORDB_ADDRESS start, CORDB_ADDRESS end) { _ASSERTE(mHeaps); _ASSERTE(mHeapCount > 0); _ASSERTE(mHeaps[0].Segments); _ASSERTE(mHeaps[0].SegmentCount > 0); mStart = start; mEnd = end; // Set up first object mCurrObj = mHeaps[0].Segments[0].Start; mCurrMT = 0; mCurrSize = 0; mCurrHeap = 0; mCurrSeg = 0; if (!mCache.ReadMT(mCurrObj, &mCurrMT)) return E_FAIL; if (!GetSize(mCurrMT, mCurrSize)) return E_FAIL; if (mCurrObj < mStart || mCurrObj > mEnd) MoveToNextObject(); return S_OK; } HRESULT DacHeapWalker::ListNearObjects(CORDB_ADDRESS obj, CORDB_ADDRESS *pPrev, CORDB_ADDRESS *pContaining, CORDB_ADDRESS *pNext) { SegmentData *seg = FindSegment(obj); if (seg == NULL) return E_FAIL; HRESULT hr = Reset(seg->Start, seg->End); if (SUCCEEDED(hr)) { CORDB_ADDRESS prev = 0; CORDB_ADDRESS curr = 0; ULONG64 size = 0; bool found = false; while (!found && HasMoreObjects()) { prev = curr; hr = Next(&curr, NULL, &size); if (FAILED(hr)) break; if (obj >= curr && obj < curr + size) found = true; } if (found) { if (pPrev) *pPrev = prev; if (pContaining) *pContaining = curr; if (pNext) { if (HasMoreObjects()) { hr = Next(&curr, NULL, NULL); if (SUCCEEDED(hr)) *pNext = curr; } else { *pNext = 0; } } hr = S_OK; } else if (SUCCEEDED(hr)) { hr = E_FAIL; } } return hr; } #include "gceewks.cpp" HRESULT DacHeapWalker::InitHeapDataWks(HeapData *&pHeaps, size_t &pCount) { // Scrape basic heap details pCount = 1; pHeaps = new (nothrow) HeapData[1]; if (pHeaps == NULL) return E_OUTOFMEMORY; pHeaps[0].YoungestGenPtr = (CORDB_ADDRESS)WKS::generation_table[0].allocation_context.alloc_ptr; pHeaps[0].YoungestGenLimit = (CORDB_ADDRESS)WKS::generation_table[0].allocation_context.alloc_limit; pHeaps[0].Gen0Start = (CORDB_ADDRESS)WKS::generation_table[0].allocation_start; pHeaps[0].Gen0End = (CORDB_ADDRESS)WKS::gc_heap::alloc_allocated.GetAddr(); pHeaps[0].Gen1Start = (CORDB_ADDRESS)WKS::generation_table[1].allocation_start; // Segments int count = GetSegmentCount(WKS::generation_table[NUMBERGENERATIONS-1].start_segment); count += GetSegmentCount(WKS::generation_table[NUMBERGENERATIONS-2].start_segment); pHeaps[0].SegmentCount = count; pHeaps[0].Segments = new (nothrow) SegmentData[count]; if (pHeaps[0].Segments == NULL) return E_OUTOFMEMORY; // Small object heap segments WKS::heap_segment *seg = WKS::generation_table[NUMBERGENERATIONS-2].start_segment; int i = 0; for (; seg && (i < count); ++i) { pHeaps[0].Segments[i].Start = (CORDB_ADDRESS)seg->mem; if (seg == WKS::gc_heap::ephemeral_heap_segment) { pHeaps[0].Segments[i].End = (CORDB_ADDRESS)WKS::gc_heap::alloc_allocated.GetAddr(); pHeaps[0].Segments[i].Generation = 1; pHeaps[0].EphemeralSegment = i; } else { pHeaps[0].Segments[i].End = (CORDB_ADDRESS)seg->allocated; pHeaps[0].Segments[i].Generation = 2; } seg = seg->next; } // Large object heap segments seg = WKS::generation_table[NUMBERGENERATIONS-1].start_segment; for (; seg && (i < count); ++i) { pHeaps[0].Segments[i].Generation = 3; pHeaps[0].Segments[i].Start = (CORDB_ADDRESS)seg->mem; pHeaps[0].Segments[i].End = (CORDB_ADDRESS)seg->allocated; seg = seg->next; } return S_OK; } HRESULT DacDbiInterfaceImpl::CreateHeapWalk(IDacDbiInterface::HeapWalkHandle *pHandle) { DD_ENTER_MAY_THROW; DacHeapWalker *data = new (nothrow) DacHeapWalker; if (data == NULL) return E_OUTOFMEMORY; HRESULT hr = data->Init(); if (SUCCEEDED(hr)) *pHandle = reinterpret_cast(data); else delete data; return hr; } void DacDbiInterfaceImpl::DeleteHeapWalk(HeapWalkHandle handle) { DD_ENTER_MAY_THROW; DacHeapWalker *data = reinterpret_cast(handle); if (data) delete data; } HRESULT DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle, ULONG count, OUT COR_HEAPOBJECT * objects, OUT ULONG *fetched) { DD_ENTER_MAY_THROW; if (fetched == NULL) return E_INVALIDARG; DacHeapWalker *walk = reinterpret_cast(handle); *fetched = 0; if (!walk->HasMoreObjects()) return S_FALSE; CORDB_ADDRESS freeMT = (CORDB_ADDRESS)g_pFreeObjectMethodTable.GetAddr(); HRESULT hr = S_OK; CORDB_ADDRESS addr, mt; ULONG64 size; ULONG i = 0; while (i < count && walk->HasMoreObjects()) { hr = walk->Next(&addr, &mt, &size); if (FAILED(hr)) break; if (mt != freeMT) { objects[i].address = addr; objects[i].type.token1 = mt; objects[i].type.token2 = NULL; objects[i].size = size; i++; } } if (SUCCEEDED(hr)) hr = (i < count) ? S_FALSE : S_OK; *fetched = i; return hr; } HRESULT DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayList *pSegments) { DD_ENTER_MAY_THROW; size_t heapCount = 0; HeapData *heaps = 0; #ifdef FEATURE_SVR_GC HRESULT hr = GCHeapUtilities::IsServerHeap() ? DacHeapWalker::InitHeapDataSvr(heaps, heapCount) : DacHeapWalker::InitHeapDataWks(heaps, heapCount); #else HRESULT hr = DacHeapWalker::InitHeapDataWks(heaps, heapCount); #endif NewArrayHolder _heapHolder = heaps; // Count the number of segments to know how much to allocate. int total = 0; for (size_t i = 0; i < heapCount; ++i) { // SegmentCount is +1 due to the ephemeral segment containing more than one // generation (Gen1 + Gen0, and sometimes part of Gen2). total += (int)heaps[i].SegmentCount + 1; // It's possible that part of Gen2 lives on the ephemeral segment. If so, // we need to add one more to the output. const size_t eph = heaps[i].EphemeralSegment; _ASSERTE(eph < heaps[i].SegmentCount); if (heaps[i].Segments[eph].Start != heaps[i].Gen1Start) total++; } pSegments->Alloc(total); // Now walk all segments and write them to the array. int curr = 0; for (size_t i = 0; i < heapCount; ++i) { // Generation 0 is not in the segment list. _ASSERTE(curr < total); { COR_SEGMENT &seg = (*pSegments)[curr++]; seg.start = heaps[i].Gen0Start; seg.end = heaps[i].Gen0End; seg.type = CorDebug_Gen0; seg.heap = (ULONG)i; } for (size_t j = 0; j < heaps[i].SegmentCount; ++j) { if (heaps[i].Segments[j].Generation == 1) { // This is the ephemeral segment. We have already written Gen0, // now write Gen1. _ASSERTE(heaps[i].Segments[j].Start <= heaps[i].Gen1Start); _ASSERTE(heaps[i].Segments[j].End > heaps[i].Gen1Start); { _ASSERTE(curr < total); COR_SEGMENT &seg = (*pSegments)[curr++]; seg.start = heaps[i].Gen1Start; seg.end = heaps[i].Gen0Start; seg.type = CorDebug_Gen1; seg.heap = (ULONG)i; } // It's possible for Gen2 to take up a portion of the ephemeral segment. // We test for that here. if (heaps[i].Segments[j].Start != heaps[i].Gen1Start) { _ASSERTE(curr < total); COR_SEGMENT &seg = (*pSegments)[curr++]; seg.start = heaps[i].Segments[j].Start; seg.end = heaps[i].Gen1Start; seg.type = CorDebug_Gen2; seg.heap = (ULONG)i; } } else { // Otherwise, we have a gen2 or gen3 (LOH) segment _ASSERTE(curr < total); COR_SEGMENT &seg = (*pSegments)[curr++]; seg.start = heaps[i].Segments[j].Start; seg.end = heaps[i].Segments[j].End; _ASSERTE(heaps[i].Segments[j].Generation <= CorDebug_LOH); seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation; seg.heap = (ULONG)i; } } } _ASSERTE(total == curr); return hr; } bool DacDbiInterfaceImpl::IsValidObject(CORDB_ADDRESS addr) { DD_ENTER_MAY_THROW; bool isValid = false; EX_TRY { PTR_Object obj(TO_TADDR(addr)); PTR_MethodTable mt = obj->GetMethodTable(); PTR_EEClass cls = mt->GetClass(); if (mt == cls->GetMethodTable()) isValid = true; else if (!mt->IsCanonicalMethodTable()) isValid = cls->GetMethodTable()->GetClass() == cls; } EX_CATCH { isValid = false; } EX_END_CATCH(SwallowAllExceptions) return isValid; } bool DacDbiInterfaceImpl::GetAppDomainForObject(CORDB_ADDRESS addr, OUT VMPTR_AppDomain * pAppDomain, OUT VMPTR_Module *pModule, OUT VMPTR_DomainFile *pDomainFile) { DD_ENTER_MAY_THROW; PTR_Object obj(TO_TADDR(addr)); MethodTable *mt = obj->GetMethodTable(); PTR_Module module = mt->GetModule(); PTR_Assembly assembly = module->GetAssembly(); BaseDomain *baseDomain = assembly->GetDomain(); if (baseDomain->IsSharedDomain()) { pModule->SetDacTargetPtr(PTR_HOST_TO_TADDR(module)); *pAppDomain = VMPTR_AppDomain::NullPtr(); *pDomainFile = VMPTR_DomainFile::NullPtr(); } else if (baseDomain->IsAppDomain()) { pAppDomain->SetDacTargetPtr(PTR_HOST_TO_TADDR(baseDomain->AsAppDomain())); pModule->SetDacTargetPtr(PTR_HOST_TO_TADDR(module)); pDomainFile->SetDacTargetPtr(PTR_HOST_TO_TADDR(module->GetDomainFile(baseDomain->AsAppDomain()))); } else { return false; } return true; } HRESULT DacDbiInterfaceImpl::CreateRefWalk(OUT RefWalkHandle * pHandle, BOOL walkStacks, BOOL walkFQ, UINT32 handleWalkMask) { DD_ENTER_MAY_THROW; DacRefWalker *walker = new (nothrow) DacRefWalker(this, walkStacks, walkFQ, handleWalkMask); if (walker == NULL) return E_OUTOFMEMORY; HRESULT hr = walker->Init(); if (FAILED(hr)) { delete walker; } else { *pHandle = reinterpret_cast(walker); } return hr; } void DacDbiInterfaceImpl::DeleteRefWalk(IN RefWalkHandle handle) { DD_ENTER_MAY_THROW; DacRefWalker *walker = reinterpret_cast(handle); if (walker) delete walker; } HRESULT DacDbiInterfaceImpl::WalkRefs(RefWalkHandle handle, ULONG count, OUT DacGcReference * objects, OUT ULONG *pFetched) { if (objects == NULL || pFetched == NULL) return E_POINTER; DD_ENTER_MAY_THROW; DacRefWalker *walker = reinterpret_cast(handle); if (!walker) return E_INVALIDARG; return walker->Next(count, objects, pFetched); } HRESULT DacDbiInterfaceImpl::GetTypeID(CORDB_ADDRESS dbgObj, COR_TYPEID *pID) { DD_ENTER_MAY_THROW; TADDR obj[3]; ULONG32 read = 0; HRESULT hr = g_dacImpl->m_pTarget->ReadVirtual(dbgObj, (BYTE*)obj, sizeof(obj), &read); if (FAILED(hr)) return hr; pID->token1 = (UINT64)(obj[0] & ~1); pID->token2 = 0; return hr; } HRESULT DacDbiInterfaceImpl::GetTypeIDForType(VMPTR_TypeHandle vmTypeHandle, COR_TYPEID *pID) { DD_ENTER_MAY_THROW; _ASSERTE(pID != NULL); _ASSERTE(!vmTypeHandle.IsNull()); TypeHandle th = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr()); PTR_MethodTable pMT = th.GetMethodTable(); pID->token1 = pMT.GetAddr(); _ASSERTE(pID->token1 != 0); pID->token2 = 0; return S_OK; } HRESULT DacDbiInterfaceImpl::GetObjectFields(COR_TYPEID id, ULONG32 celt, COR_FIELD *layout, ULONG32 *pceltFetched) { if (layout == NULL || pceltFetched == NULL) return E_POINTER; if (id.token1 == 0) return CORDBG_E_CLASS_NOT_LOADED; DD_ENTER_MAY_THROW; HRESULT hr = S_OK; TypeHandle typeHandle = TypeHandle::FromPtr(TO_TADDR(id.token1)); if (typeHandle.IsTypeDesc()) return E_INVALIDARG; ApproxFieldDescIterator fieldDescIterator(typeHandle.AsMethodTable(), ApproxFieldDescIterator::INSTANCE_FIELDS); ULONG32 cFields = fieldDescIterator.Count(); // Handle case where user only wanted to know the number of fields. if (layout == NULL) { *pceltFetched = cFields; return S_FALSE; } if (celt < cFields) { cFields = celt; // we are returning less than the total hr = S_FALSE; } // This must be non-null due to check at beginning of function. *pceltFetched = celt; CorElementType componentType = typeHandle.AsMethodTable()->GetInternalCorElementType(); BOOL fReferenceType = CorTypeInfo::IsObjRef_NoThrow(componentType); for (ULONG32 i = 0; i < cFields; ++i) { FieldDesc *pField = fieldDescIterator.Next(); layout[i].token = pField->GetMemberDef(); layout[i].offset = (ULONG32)pField->GetOffset() + (fReferenceType ? Object::GetOffsetOfFirstField() : 0); TypeHandle fieldHandle = pField->LookupFieldTypeHandle(); if (fieldHandle.IsNull()) { layout[i].id.token1 = 0; layout[i].id.token2 = 0; layout[i].fieldType = (CorElementType)0; } else { PTR_MethodTable mt = fieldHandle.GetMethodTable(); layout[i].fieldType = mt->GetInternalCorElementType(); layout[i].id.token1 = (ULONG64)mt.GetAddr(); if (!mt->IsArray()) { layout[i].id.token2 = 0; } else { TypeHandle hnd = mt->GetApproxArrayElementTypeHandle(); PTR_MethodTable cmt = hnd.GetMethodTable(); layout[i].id.token2 = (ULONG64)cmt.GetAddr(); } } } return hr; } HRESULT DacDbiInterfaceImpl::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout) { if (pLayout == NULL) return E_POINTER; if (id.token1 == 0) return CORDBG_E_CLASS_NOT_LOADED; DD_ENTER_MAY_THROW; PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id.token1)); PTR_MethodTable parentMT = mt->GetParentMethodTable(); COR_TYPEID parent = {parentMT.GetAddr(), 0}; pLayout->parentID = parent; DWORD size = mt->GetBaseSize(); ApproxFieldDescIterator fieldDescIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS); pLayout->objectSize = size; pLayout->numFields = fieldDescIterator.Count(); // Get type CorElementType componentType = mt->IsString() ? ELEMENT_TYPE_STRING : mt->GetInternalCorElementType(); pLayout->type = componentType; pLayout->boxOffset = CorTypeInfo::IsObjRef_NoThrow(componentType) ? 0 : sizeof(TADDR); return S_OK; } HRESULT DacDbiInterfaceImpl::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout) { if (pLayout == NULL) return E_POINTER; if (id.token1 == 0) return CORDBG_E_CLASS_NOT_LOADED; DD_ENTER_MAY_THROW; PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id.token1)); if (!mt->IsStringOrArray()) return E_INVALIDARG; if (mt->IsString()) { COR_TYPEID token; token.token1 = MscorlibBinder::GetElementType(ELEMENT_TYPE_CHAR).GetAddr(); token.token2 = 0; pLayout->componentID = token; pLayout->rankSize = 4; pLayout->numRanks = 1; pLayout->rankOffset = sizeof(TADDR); pLayout->firstElementOffset = sizeof(TADDR) + 4; pLayout->countOffset = sizeof(TADDR); pLayout->componentType = ELEMENT_TYPE_CHAR; pLayout->elementSize = 2; } else { DWORD ranks = mt->GetRank(); pLayout->rankSize = 4; pLayout->numRanks = ranks; bool multiDim = (ranks > 1); pLayout->rankOffset = multiDim ? sizeof(TADDR)*2 : sizeof(TADDR); pLayout->countOffset = sizeof(TADDR); pLayout->firstElementOffset = ArrayBase::GetDataPtrOffset(mt); TypeHandle hnd = mt->GetApproxArrayElementTypeHandle(); PTR_MethodTable cmt = hnd.GetMethodTable(); CorElementType componentType = cmt->GetInternalCorElementType(); if ((UINT64)cmt.GetAddr() == (UINT64)g_pStringClass.GetAddr()) componentType = ELEMENT_TYPE_STRING; COR_TYPEID token; token.token1 = cmt.GetAddr(); // This could be type handle token.token2 = 0; pLayout->componentID = token; pLayout->componentType = componentType; if (CorTypeInfo::IsObjRef_NoThrow(componentType)) pLayout->elementSize = sizeof(TADDR); else if (CorIsPrimitiveType(componentType)) pLayout->elementSize = gElementTypeInfo[componentType].m_cbSize; else pLayout->elementSize = cmt->GetNumInstanceFieldBytes(); } return S_OK; } void DacDbiInterfaceImpl::GetGCHeapInformation(COR_HEAPINFO * pHeapInfo) { DD_ENTER_MAY_THROW; size_t heapCount = 0; pHeapInfo->areGCStructuresValid = GCScan::GetGcRuntimeStructuresValid(); #ifdef FEATURE_SVR_GC if (GCHeapUtilities::IsServerHeap()) { pHeapInfo->gcType = CorDebugServerGC; pHeapInfo->numHeaps = DacGetNumHeaps(); } else #endif { pHeapInfo->gcType = CorDebugWorkstationGC; pHeapInfo->numHeaps = 1; } pHeapInfo->pointerSize = sizeof(TADDR); pHeapInfo->concurrent = g_pConfig->GetGCconcurrent() ? TRUE : FALSE; } HRESULT DacDbiInterfaceImpl::GetPEFileMDInternalRW(VMPTR_PEFile vmPEFile, OUT TADDR* pAddrMDInternalRW) { DD_ENTER_MAY_THROW; if (pAddrMDInternalRW == NULL) return E_INVALIDARG; PEFile * pPEFile = vmPEFile.GetDacPtr(); *pAddrMDInternalRW = pPEFile->GetMDInternalRWAddress(); return S_OK; } HRESULT DacDbiInterfaceImpl::GetReJitInfo(VMPTR_Module vmModule, mdMethodDef methodTk, OUT VMPTR_ReJitInfo* pvmReJitInfo) { DD_ENTER_MAY_THROW; if (pvmReJitInfo == NULL) return E_INVALIDARG; #ifdef FEATURE_REJIT PTR_Module pModule = vmModule.GetDacPtr(); ReJitManager * pReJitMgr = pModule->GetReJitManager(); PTR_ReJitInfo pReJitInfoCurrent = pReJitMgr->FindNonRevertedReJitInfo(pModule, methodTk); // if the token lookup failed, we need to search again by method desc // The rejit manager will index by token if the method isn't loaded when RequestReJIT runs // and by methoddesc if it was loaded if (pReJitInfoCurrent == NULL) { MethodDesc* pMD = pModule->LookupMethodDef(methodTk); if (pMD != NULL) { pReJitInfoCurrent = pReJitMgr->FindNonRevertedReJitInfo(dac_cast(pMD)); } } pvmReJitInfo->SetDacTargetPtr(PTR_TO_TADDR(pReJitInfoCurrent)); #else pvmReJitInfo->SetDacTargetPtr(0); #endif return S_OK; } HRESULT DacDbiInterfaceImpl::GetReJitInfo(VMPTR_MethodDesc vmMethod, CORDB_ADDRESS codeStartAddress, OUT VMPTR_ReJitInfo* pvmReJitInfo) { DD_ENTER_MAY_THROW; if (pvmReJitInfo == NULL) return E_INVALIDARG; #ifdef FEATURE_REJIT PTR_MethodDesc pMD = vmMethod.GetDacPtr(); ReJitManager * pReJitMgr = pMD->GetReJitManager(); PTR_ReJitInfo pReJitInfoCurrent = pReJitMgr->FindReJitInfo(pMD, (PCODE)codeStartAddress, 0); pvmReJitInfo->SetDacTargetPtr(PTR_TO_TADDR(pReJitInfoCurrent)); #else pvmReJitInfo->SetDacTargetPtr(0); #endif return S_OK; } HRESULT DacDbiInterfaceImpl::GetSharedReJitInfo(VMPTR_ReJitInfo vmReJitInfo, OUT VMPTR_SharedReJitInfo* pvmSharedReJitInfo) { DD_ENTER_MAY_THROW; if (pvmSharedReJitInfo == NULL) return E_INVALIDARG; #ifdef FEATURE_REJIT ReJitInfo* pReJitInfo = vmReJitInfo.GetDacPtr(); pvmSharedReJitInfo->SetDacTargetPtr(PTR_TO_TADDR(pReJitInfo->m_pShared)); #else _ASSERTE(!"You shouldn't be calling this - how did you get a ReJitInfo?"); pvmSharedReJitInfo->SetDacTargetPtr(0); #endif return S_OK; } HRESULT DacDbiInterfaceImpl::GetSharedReJitInfoData(VMPTR_SharedReJitInfo vmSharedReJitInfo, DacSharedReJitInfo* pData) { DD_ENTER_MAY_THROW; #ifdef FEATURE_REJIT SharedReJitInfo* pSharedReJitInfo = vmSharedReJitInfo.GetDacPtr(); pData->m_state = pSharedReJitInfo->GetState(); pData->m_pbIL = PTR_TO_CORDB_ADDRESS(pSharedReJitInfo->m_pbIL); pData->m_dwCodegenFlags = pSharedReJitInfo->m_dwCodegenFlags; pData->m_cInstrumentedMapEntries = (ULONG)pSharedReJitInfo->m_instrumentedILMap.GetCount(); pData->m_rgInstrumentedMapEntries = PTR_TO_CORDB_ADDRESS(dac_cast(pSharedReJitInfo->m_instrumentedILMap.GetOffsets())); #else _ASSERTE(!"You shouldn't be calling this - how did you get a SharedReJitInfo?"); #endif return S_OK; } HRESULT DacDbiInterfaceImpl::GetDefinesBitField(ULONG32 *pDefines) { DD_ENTER_MAY_THROW; if (pDefines == NULL) return E_INVALIDARG; *pDefines = g_pDebugger->m_defines; return S_OK; } HRESULT DacDbiInterfaceImpl::GetMDStructuresVersion(ULONG32* pMDStructuresVersion) { DD_ENTER_MAY_THROW; if (pMDStructuresVersion == NULL) return E_INVALIDARG; *pMDStructuresVersion = g_pDebugger->m_mdDataStructureVersion; return S_OK; } DacRefWalker::DacRefWalker(ClrDataAccess *dac, BOOL walkStacks, BOOL walkFQ, UINT32 handleMask) : mDac(dac), mWalkStacks(walkStacks), mWalkFQ(walkFQ), mHandleMask(handleMask), mStackWalker(NULL), mHandleWalker(NULL), mFQStart(PTR_NULL), mFQEnd(PTR_NULL), mFQCurr(PTR_NULL) { } DacRefWalker::~DacRefWalker() { Clear(); } HRESULT DacRefWalker::Init() { HRESULT hr = S_OK; if (mHandleMask) { // Will throw on OOM, which is fine. mHandleWalker = new DacHandleWalker(); hr = mHandleWalker->Init(GetHandleWalkerMask()); } if (mWalkStacks && SUCCEEDED(hr)) { hr = NextThread(); } return hr; } void DacRefWalker::Clear() { if (mHandleWalker) { delete mHandleWalker; mHandleWalker = NULL; } if (mStackWalker) { delete mStackWalker; mStackWalker = NULL; } } UINT32 DacRefWalker::GetHandleWalkerMask() { UINT32 result = 0; if (mHandleMask & CorHandleStrong) result |= (1 << HNDTYPE_STRONG); if (mHandleMask & CorHandleStrongPinning) result |= (1 << HNDTYPE_PINNED); if (mHandleMask & CorHandleWeakShort) result |= (1 << HNDTYPE_WEAK_SHORT); if (mHandleMask & CorHandleWeakLong) result |= (1 << HNDTYPE_WEAK_LONG); #ifdef FEATURE_COMINTEROP if ((mHandleMask & CorHandleWeakRefCount) || (mHandleMask & CorHandleStrongRefCount)) result |= (1 << HNDTYPE_REFCOUNTED); if (mHandleMask & CorHandleWeakWinRT) result |= (1 << HNDTYPE_WEAK_WINRT); #endif // FEATURE_COMINTEROP if (mHandleMask & CorHandleStrongDependent) result |= (1 << HNDTYPE_DEPENDENT); if (mHandleMask & CorHandleStrongAsyncPinned) result |= (1 << HNDTYPE_ASYNCPINNED); if (mHandleMask & CorHandleStrongSizedByref) result |= (1 << HNDTYPE_SIZEDREF); return result; } HRESULT DacRefWalker::Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched) { if (roots == NULL || pceltFetched == NULL) return E_POINTER; ULONG total = 0; HRESULT hr = S_OK; if (mHandleWalker) { hr = mHandleWalker->Next(celt, roots, &total); if (hr == S_FALSE || FAILED(hr)) { delete mHandleWalker; mHandleWalker = NULL; if (FAILED(hr)) return hr; } } if (total < celt) { while (total < celt && mFQCurr < mFQEnd) { DacGcReference &ref = roots[total++]; ref.vmDomain = VMPTR_AppDomain::NullPtr(); ref.objHnd.SetDacTargetPtr(mFQCurr.GetAddr()); ref.dwType = (DWORD)CorReferenceFinalizer; ref.i64ExtraData = 0; mFQCurr++; } } while (total < celt && mStackWalker) { ULONG fetched = 0; hr = mStackWalker->Next(celt-total, roots+total, &fetched); if (FAILED(hr)) return hr; if (hr == S_FALSE) { hr = NextThread(); if (FAILED(hr)) return hr; } total += fetched; } *pceltFetched = total; return total < celt ? S_FALSE : S_OK; } HRESULT DacRefWalker::NextThread() { Thread *pThread = NULL; if (mStackWalker) { pThread = mStackWalker->GetThread(); delete mStackWalker; mStackWalker = NULL; } pThread = ThreadStore::GetThreadList(pThread); if (!pThread) return S_FALSE; mStackWalker = new DacStackReferenceWalker(mDac, pThread->GetOSThreadId()); return mStackWalker->Init(); } HRESULT DacHandleWalker::Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched) { SUPPORTS_DAC; if (roots == NULL || pceltFetched == NULL) return E_POINTER; return DoHandleWalk(celt, roots, pceltFetched); } void CALLBACK DacHandleWalker::EnumCallbackDac(PTR_UNCHECKED_OBJECTREF handle, uintptr_t *pExtraInfo, uintptr_t param1, uintptr_t param2) { SUPPORTS_DAC; DacHandleWalkerParam *param = (DacHandleWalkerParam *)param1; HandleChunkHead *curr = param->Curr; // If we failed on a previous call (OOM) don't keep trying to allocate, it's not going to work. if (FAILED(param->Result)) return; // We've moved past the size of the current chunk. We'll allocate a new chunk // and stuff the handles there. These are cleaned up by the destructor if (curr->Count >= (curr->Size/sizeof(DacGcReference))) { if (curr->Next == NULL) { HandleChunk *next = new (nothrow) HandleChunk; if (next != NULL) { curr->Next = next; } else { param->Result = E_OUTOFMEMORY; return; } } curr = param->Curr = param->Curr->Next; } // Fill the current handle. DacGcReference *dataArray = (DacGcReference*)curr->pData; DacGcReference &data = dataArray[curr->Count++]; data.objHnd.SetDacTargetPtr(handle.GetAddr()); data.vmDomain.SetDacTargetPtr(TO_TADDR(param->AppDomain)); data.i64ExtraData = 0; unsigned int refCnt = 0; switch (param->Type) { case HNDTYPE_STRONG: data.dwType = (DWORD)CorHandleStrong; break; case HNDTYPE_PINNED: data.dwType = (DWORD)CorHandleStrongPinning; break; case HNDTYPE_WEAK_SHORT: data.dwType = (DWORD)CorHandleWeakShort; break; case HNDTYPE_WEAK_LONG: data.dwType = (DWORD)CorHandleWeakLong; break; #ifdef FEATURE_COMINTEROP case HNDTYPE_REFCOUNTED: data.dwType = (DWORD)(data.i64ExtraData ? CorHandleStrongRefCount : CorHandleWeakRefCount); GetRefCountedHandleInfo((OBJECTREF)*handle, param->Type, &refCnt, NULL, NULL, NULL); data.i64ExtraData = refCnt; break; case HNDTYPE_WEAK_WINRT: data.dwType = (DWORD)CorHandleWeakWinRT; break; #endif case HNDTYPE_DEPENDENT: data.dwType = (DWORD)CorHandleStrongDependent; data.i64ExtraData = GetDependentHandleSecondary(handle.GetAddr()).GetAddr(); break; case HNDTYPE_ASYNCPINNED: data.dwType = (DWORD)CorHandleStrongAsyncPinned; break; case HNDTYPE_SIZEDREF: data.dwType = (DWORD)CorHandleStrongSizedByref; break; } } void DacStackReferenceWalker::GCEnumCallbackDac(LPVOID hCallback, OBJECTREF *pObject, uint32_t flags, DacSlotLocation loc) { GCCONTEXT *gcctx = (GCCONTEXT *)hCallback; DacScanContext *dsc = (DacScanContext*)gcctx->sc; CORDB_ADDRESS obj = 0; if (flags & GC_CALL_INTERIOR) { if (loc.targetPtr) obj = (CORDB_ADDRESS)(*PTR_PTR_Object((TADDR)pObject)).GetAddr(); else obj = (CORDB_ADDRESS)TO_TADDR(pObject); HRESULT hr = dsc->pWalker->mHeap.ListNearObjects(obj, NULL, &obj, NULL); // If we failed don't add this instance to the list. ICorDebug doesn't handle invalid pointers // very well, and the only way the heap walker's ListNearObjects will fail is if we have heap // corruption...which ICorDebug doesn't deal with anyway. if (FAILED(hr)) return; } DacGcReference *data = dsc->pWalker->GetNextObject(dsc); if (data != NULL) { data->vmDomain.SetDacTargetPtr(dac_cast(dsc->pCurrentDomain).GetAddr()); if (obj) data->pObject = obj | 1; else if (loc.targetPtr) data->objHnd.SetDacTargetPtr(TO_TADDR(pObject)); else data->pObject = pObject->GetAddr() | 1; data->dwType = CorReferenceStack; data->i64ExtraData = 0; } } void DacStackReferenceWalker::GCReportCallbackDac(PTR_PTR_Object ppObj, ScanContext *sc, uint32_t flags) { DacScanContext *dsc = (DacScanContext*)sc; TADDR obj = ppObj.GetAddr(); if (flags & GC_CALL_INTERIOR) { CORDB_ADDRESS fixed_addr = 0; HRESULT hr = dsc->pWalker->mHeap.ListNearObjects((CORDB_ADDRESS)obj, NULL, &fixed_addr, NULL); // If we failed don't add this instance to the list. ICorDebug doesn't handle invalid pointers // very well, and the only way the heap walker's ListNearObjects will fail is if we have heap // corruption...which ICorDebug doesn't deal with anyway. if (FAILED(hr)) return; obj = TO_TADDR(fixed_addr); } DacGcReference *data = dsc->pWalker->GetNextObject(dsc); if (data != NULL) { data->vmDomain.SetDacTargetPtr(dac_cast(dsc->pCurrentDomain).GetAddr()); data->objHnd.SetDacTargetPtr(obj); data->dwType = CorReferenceStack; data->i64ExtraData = 0; } } HRESULT DacStackReferenceWalker::Next(ULONG count, DacGcReference stackRefs[], ULONG *pFetched) { if (stackRefs == NULL || pFetched == NULL) return E_POINTER; HRESULT hr = DoStackWalk (count, stackRefs, pFetched); return hr; }