summaryrefslogtreecommitdiff
path: root/src/debug/di/module.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/module.cpp')
-rw-r--r--src/debug/di/module.cpp4925
1 files changed, 4925 insertions, 0 deletions
diff --git a/src/debug/di/module.cpp b/src/debug/di/module.cpp
new file mode 100644
index 0000000000..6717e8575f
--- /dev/null
+++ b/src/debug/di/module.cpp
@@ -0,0 +1,4925 @@
+// 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: module.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "winbase.h"
+
+#include "metadataexports.h"
+
+#include "winbase.h"
+#include "corpriv.h"
+#include "corsym.h"
+#include "ildbsymlib.h"
+
+#include "pedecoder.h"
+
+//---------------------------------------------------------------------------------------
+// Update an existing metadata importer with a buffer
+//
+// Arguments:
+// pUnk - IUnknoown of importer to update.
+// pData - local buffer containing new metadata
+// cbData - size of buffer in bytes.
+// dwReOpenFlags - metadata flags to pass for reopening.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+// Notes:
+// This will call code:MDReOpenMetaDataWithMemoryEx from the metadata engine.
+STDAPI ReOpenMetaDataWithMemoryEx(
+ void *pUnk,
+ LPCVOID pData,
+ ULONG cbData,
+ DWORD dwReOpenFlags)
+{
+ HRESULT hr = MDReOpenMetaDataWithMemoryEx(pUnk,pData, cbData, dwReOpenFlags);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize a new CordbModule around a Module in the target.
+//
+// Arguments:
+// pProcess - process that this module lives in
+// vmDomainFile - CLR cookie for module.
+CordbModule::CordbModule(
+ CordbProcess * pProcess,
+ VMPTR_Module vmModule,
+ VMPTR_DomainFile vmDomainFile)
+: CordbBase(pProcess, vmDomainFile.IsNull() ? VmPtrToCookie(vmModule) : VmPtrToCookie(vmDomainFile), enumCordbModule),
+ m_pAssembly(0),
+ m_pAppDomain(0),
+ m_classes(11),
+ m_functions(101),
+ m_vmDomainFile(vmDomainFile),
+ m_vmModule(vmModule),
+ m_EnCCount(0),
+ m_isIlWinMD(Uninitialized),
+ m_fForceMetaDataSerialize(FALSE),
+ m_nativeCodeTable(101)
+{
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ _ASSERTE(!vmModule.IsNull());
+
+ m_nLoadEventContinueCounter = 0;
+#ifdef _DEBUG
+ m_classes.DebugSetRSLock(pProcess->GetProcessLock());
+ m_functions.DebugSetRSLock(pProcess->GetProcessLock());
+#endif
+
+ // Fill out properties via DAC.
+ ModuleInfo modInfo;
+ pProcess->GetDAC()->GetModuleData(vmModule, &modInfo); // throws
+
+ m_PEBuffer.Init(modInfo.pPEBaseAddress, modInfo.nPESize);
+
+ m_fDynamic = modInfo.fIsDynamic;
+ m_fInMemory = modInfo.fInMemory;
+ m_vmPEFile = modInfo.vmPEFile;
+
+ if (!vmDomainFile.IsNull())
+ {
+ DomainFileInfo dfInfo;
+
+ pProcess->GetDAC()->GetDomainFileData(vmDomainFile, &dfInfo); // throws
+
+ m_pAppDomain = pProcess->LookupOrCreateAppDomain(dfInfo.vmAppDomain);
+ m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(dfInfo.vmDomainAssembly);
+ }
+ else
+ {
+ // Not yet implemented
+ m_pAppDomain = pProcess->GetSharedAppDomain();
+ m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(modInfo.vmAssembly);
+ }
+#ifdef _DEBUG
+ m_nativeCodeTable.DebugSetRSLock(GetProcess()->GetProcessLock());
+#endif
+
+ // MetaData is initialized lazily (via code:CordbModule::GetMetaDataImporter).
+ // Getting the metadata may be very expensive (especially if we go through the metadata locator, which
+ // invokes back to the data-target), so don't do it until asked.
+ // m_pIMImport, m_pInternalMetaDataImport are smart pointers that already initialize to NULL.
+}
+
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Callback helper for code:CordbModule::DbgAssertModuleDeleted
+//
+// Arguments
+// vmDomainFile - domain file in the enumeration
+// pUserData - pointer to the CordbModule that we just got an exit event for.
+//
+void DbgAssertModuleDeletedCallback(VMPTR_DomainFile vmDomainFile, void * pUserData)
+{
+ CordbModule * pThis = reinterpret_cast<CordbModule *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ if (!pThis->m_vmDomainFile.IsNull())
+ {
+ VMPTR_DomainFile vmDomainFileDeleted = pThis->m_vmDomainFile;
+
+ CONSISTENCY_CHECK_MSGF((vmDomainFileDeleted != vmDomainFile),
+ ("A Module Unload event was sent for a module, but it still shows up in the enumeration.\n vmDomainFileDeleted=%p\n",
+ VmPtrToCookie(vmDomainFileDeleted)));
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Assert that a module is no longer discoverable via enumeration.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// This is a debug only method. It's conceptually similar to
+// code:CordbProcess::DbgAssertAppDomainDeleted.
+//
+void CordbModule::DbgAssertModuleDeleted()
+{
+ GetProcess()->GetDAC()->EnumerateModulesInAssembly(
+ m_pAssembly->GetDomainAssemblyPtr(),
+ DbgAssertModuleDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+CordbModule::~CordbModule()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+
+ _ASSERTE(m_pIMImport == NULL);
+}
+
+// Neutered by CordbAppDomain
+void CordbModule::Neuter()
+{
+ // m_pAppDomain, m_pAssembly assigned w/o AddRef()
+ m_classes.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_functions.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ m_nativeCodeTable.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_pClass.Clear();
+
+ // This is very important because it also releases the metadata's potential file locks.
+ m_pInternalMetaDataImport.Clear();
+ m_pIMImport.Clear();
+
+ CordbBase::Neuter();
+}
+
+//
+// Creates an IStream based off the memory described by the TargetBuffer.
+//
+// Arguments:
+// pProcess - process that buffer is valid in.
+// buffer - memory range in target
+// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
+// caller owns the new object and must call Release.
+//
+// Returns:
+// Throws on error.
+// Common errors include if memory is missing in the target.
+//
+// Notes:
+// This will copy the memory over from the TargetBuffer, and then create a new IStream
+// object around it.
+//
+void GetStreamFromTargetBuffer(CordbProcess * pProcess, TargetBuffer buffer, IStream ** ppStream)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ppStream != NULL);
+ _ASSERTE(*ppStream == NULL);
+
+ int cbSize = buffer.cbSize;
+ NewArrayHolder<BYTE> localBuffer(new BYTE[cbSize]);
+
+ pProcess->SafeReadBuffer(buffer, localBuffer);
+
+ HRESULT hr = E_FAIL;
+ hr = CInMemoryStream::CreateStreamOnMemoryCopy(localBuffer, cbSize, ppStream);
+ IfFailThrow(hr);
+ _ASSERTE(*ppStream != NULL);
+}
+
+//
+// Helper API to get in-memory symbols from the target into a host stream object.
+//
+// Arguments:
+// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
+// caller owns the new object and must call Release.
+//
+// Returns:
+// kSymbolFormatNone if no PDB stream is present. This is a common case for
+// file-based modules, and also for dynamic modules that just aren't tracking
+// debug information.
+// The format of the symbols stored into ppStream. This is common:
+// - Ref.Emit modules if the debuggee generated debug symbols,
+// - in-memory modules (such as Load(Byte[], Byte[])
+// - hosted modules.
+// Throws on error
+//
+IDacDbiInterface::SymbolFormat CordbModule::GetInMemorySymbolStream(IStream ** ppStream)
+{
+ // @dbgtodo : add a PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM contract
+ // This function is mainly called internally in dbi, and also by the shim to emulate the
+ // UpdateModuleSymbols callback on attach.
+
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ppStream != NULL);
+ _ASSERTE(*ppStream == NULL);
+ *ppStream = NULL;
+
+ TargetBuffer bufferPdb;
+ IDacDbiInterface::SymbolFormat symFormat;
+ GetProcess()->GetDAC()->GetSymbolsBuffer(m_vmModule, &bufferPdb, &symFormat);
+ if (bufferPdb.IsEmpty())
+ {
+ // No in-memory PDB. Common case.
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
+ return IDacDbiInterface::kSymbolFormatNone;
+ }
+ else
+ {
+ _ASSERTE(symFormat != IDacDbiInterface::kSymbolFormatNone);
+ GetStreamFromTargetBuffer(GetProcess(), bufferPdb, ppStream);
+ return symFormat;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Accessor for PE file.
+//
+// Returns:
+// VMPTR_PEFile for this module. Should always be non-null
+//
+// Notes:
+// A main usage of this is to find the proper internal MetaData importer.
+// DACized code needs to map from PEFile --> IMDInternalImport.
+//
+VMPTR_PEFile CordbModule::GetPEFile()
+{
+ return m_vmPEFile;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Top-level getter for the public metadata importer for this module
+//
+// Returns:
+// metadata importer.
+// Never returns NULL. Will throw some hr (likely CORDBG_E_MISSING_METADATA) instead.
+//
+// Notes:
+// This will lazily create the metadata, possibly invoking back into the data-target.
+IMetaDataImport * CordbModule::GetMetaDataImporter()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+
+ // If we already have it, then we're done.
+ // This is critical to do at the top of this function to avoid potential recursion.
+ if (m_pIMImport != NULL)
+ {
+ return m_pIMImport;
+ }
+
+ // Lazily initialize
+
+
+ // Fetch metadata from target
+ LOG((LF_CORDB,LL_INFO1000, "CM::GMI Lazy init refreshing metadata\n"));
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ RefreshMetaData();
+ );
+
+ // If lookup failed from the Module & target memory, try the metadata locator interface
+ // from debugger, if we have one.
+ if (m_pIMImport == NULL)
+ {
+ bool isILMetaDataForNGENImage; // Not currently used for anything.
+
+ // The process's LookupMetaData will ping the debugger's ICorDebugMetaDataLocator iface.
+ CordbProcess * pProcess = GetProcess();
+ RSLockHolder processLockHolder(pProcess->GetProcessLock());
+ m_pInternalMetaDataImport.Clear();
+
+ // Do not call code:CordbProcess::LookupMetaData from this function. It will try to load
+ // through the CordbModule again which will end up back here, and on failure you'll fill the stack.
+ // Since we've already done everything possible from the Module anyhow, just call the
+ // stuff that talks to the debugger.
+ // Don't do anything with the ptr returned here, since it's really m_pInternalMetaDataImport.
+ pProcess->LookupMetaDataFromDebugger(m_vmPEFile, isILMetaDataForNGENImage, this);
+ }
+
+ // If we still can't get it, throw.
+ if (m_pIMImport == NULL)
+ {
+ ThrowHR(CORDBG_E_MISSING_METADATA);
+ }
+
+ return m_pIMImport;
+}
+
+// Refresh the metadata cache if a profiler added new rows.
+//
+// Arguments:
+// token - token that we want to ensure is in the metadata cache.
+//
+// Notes:
+// In profiler case, this may be referred to new rows and we may need to update the metadata
+// This only supports StandAloneSigs.
+//
+void CordbModule::UpdateMetaDataCacheIfNeeded(mdToken token)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token=0x%x\n", token));
+
+ // If we aren't trying to keep parity with our legacy profiler metadata update behavior
+ // then we should avoid this temporary update mechanism entirely
+ if(GetProcess()->GetWriteableMetadataUpdateMode() != LegacyCompatPolicy)
+ {
+ return;
+ }
+
+ // the metadata in WinMD is currently static since there's no
+ // support for profilers or EnC so we can simply exit early.
+ if (IsWinMD())
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token is in WinMD, exiting\n"));
+ return;
+ }
+
+ //
+ // 1) Check if in-range? Compare against tables, etc.
+ //
+ if(CheckIfTokenInMetaData(token))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was present\n"));
+ return;
+ }
+
+ //
+ // 2) Copy over new MetaData. From now on we assume that the profiler is
+ // modifying module metadata and that we need to serialize in process
+ // at each refresh
+ //
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was not present, refreshing\n"));
+ m_fForceMetaDataSerialize = TRUE;
+ RefreshMetaData();
+
+ // If we are dump debugging, we may still not have it. Nothing to be done.
+}
+
+// Returns TRUE if the token is present, FALSE if not.
+BOOL CordbModule::CheckIfTokenInMetaData(mdToken token)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ LOG((LF_CORDB,LL_INFO10000, "CM::CITIM token=0x%x\n", token));
+ _ASSERTE(TypeFromToken(token) == mdtSignature);
+ // we shouldn't be doing this on WinMD modules since they don't implement IID_IMetaDataTables
+ _ASSERTE(!IsWinMD());
+ RSExtSmartPtr<IMetaDataTables> pTable;
+
+ HRESULT hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataTables, (void**) &pTable);
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ ULONG cbRowsAvailable; // number of rows in the table
+
+ hr = pTable->GetTableInfo(
+ mdtSignature >> 24, // [IN] Which table.
+ NULL, // [OUT] Size of a row, bytes.
+ &cbRowsAvailable, // [OUT] Number of rows.
+ NULL, // [OUT] Number of columns in each row.
+ NULL, // [OUT] Key column, or -1 if none.
+ NULL); // [OUT] Name of the table.
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+
+ // Rows start counting with number 1.
+ ULONG rowRequested = RidFromToken(token);
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN requested=0x%x available=0x%x\n", rowRequested, cbRowsAvailable));
+ return (rowRequested <= cbRowsAvailable);
+}
+
+// This helper class ensures the remote serailzied buffer gets deleted in the RefreshMetaData
+// function below
+class CleanupRemoteBuffer
+{
+public:
+ CordbProcess* pProcess;
+ CordbModule* pModule;
+ TargetBuffer bufferMetaData;
+ BOOL fDoCleanup;
+
+ CleanupRemoteBuffer() :
+ fDoCleanup(FALSE) { }
+
+ ~CleanupRemoteBuffer()
+ {
+ if(fDoCleanup)
+ {
+ //
+ // Send 2nd event to free buffer.
+ //
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_RESOLVE_UPDATE_METADATA_2,
+ true,
+ pModule->GetAppDomain()->GetADToken());
+
+ event.MetadataUpdateRequest.pMetadataStart = CORDB_ADDRESS_TO_PTR(bufferMetaData.pAddress);
+
+ // Note: two-way event here...
+ IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+ _ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT);
+ }
+ }
+
+};
+
+// Called to refetch metadata. This occurs when a dynamic module grows or the profiler
+// has edited the metadata
+void CordbModule::RefreshMetaData()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "CM::RM\n"));
+
+ // There are several different ways we can get the metadata
+ // 1) [Most common] Module is loaded into VM and never changed. The importer
+ // will be constructed refering to the file on disk. This is a significant
+ // working set win because the VM and debugger share the image. If there is
+ // an error reading by file we can fall back to case #2 for these modules
+ // 2) Most modules have a buffer in target memory that represents their
+ // metadata. We copy that data over the RS and construct an in-memory
+ // importer on top of it.
+ // 3) The only modules that don't have a suitable buffer (case #2) are those
+ // modified in memory via the profiling API (or ENC). A message can be sent from
+ // the debugger to the debuggee instructing it to allocate a buffer and
+ // serialize the metadata into it. Then we copy that data to the RS and
+ // construct an in-memory importer on top of it.
+ // We don't need to send this message in the ENC case because the debugger
+ // has the same changes applied as the debuggee.
+ // 4) Case #3 won't work when dump debugging because we can't send IPC events.
+ // Instead we can locate chunks of the metadata pointed to in the implementation
+ // details of a remote MDInternalRW object, marshal that memory over to the
+ // debugger process, and then put a metadata reader on top of it.
+ // In time this DAC'ized metadata could be used in almost any scenario,
+ // although its probably worth keeping the file mapping technique in case
+ // #1 around for its performance wins.
+
+ CordbProcess * pProcess = GetProcess();
+ TargetBuffer bufferMetaData;
+ CleanupRemoteBuffer cleanup; // this local has a destructor to do some finally work
+
+
+ // check for scenarios we might want to handle with case #4
+ if (GetProcess()->GetShim() == NULL &&
+ GetProcess()->GetWriteableMetadataUpdateMode() == AlwaysShowUpdates &&
+ !m_fDynamic)
+ {
+ //None of the above requirements are particularly hard to change in the future as needed...
+ // a) dump-debugging mode - If we do this on a process that can move forward we need a mechanism to determine
+ // when to refetch the metadata.
+ // b) AlwaysShowUpdates - this is purely a risk mitigation choice, there aren't any known back-compat issues
+ // using DAC'ized metadata. If you want back-compat with the in-proc debugging behavior
+ // you need to figure out how to ReOpen the same public MD interface with new data.
+ // c) !m_fDynamic - A risk mitigation choice. Initial testing suggests it would work fine.
+
+
+ // So far we've only got a reader for in-memory-writable metadata (MDInternalRW implementation)
+ // We could make a reader for MDInternalRO, but no need yet. This also ensures we don't encroach into common
+ // scenario where we can map a file on disk.
+ TADDR remoteMDInternalRWAddr = NULL;
+ GetProcess()->GetDAC()->GetPEFileMDInternalRW(m_vmPEFile, &remoteMDInternalRWAddr);
+ if (remoteMDInternalRWAddr != NULL)
+ {
+ // we should only be doing this once to initialize, we don't support reopen with this technique
+ _ASSERTE(m_pIMImport == NULL);
+ ULONG32 mdStructuresVersion;
+ HRESULT hr = GetProcess()->GetDAC()->GetMDStructuresVersion(&mdStructuresVersion);
+ IfFailThrow(hr);
+ ULONG32 mdStructuresDefines;
+ hr = GetProcess()->GetDAC()->GetDefinesBitField(&mdStructuresDefines);
+ IfFailThrow(hr);
+ IMetaDataDispenserCustom* pDispCustom = NULL;
+ hr = GetProcess()->GetDispenser()->QueryInterface(IID_IMetaDataDispenserCustom, (void**)&pDispCustom);
+ IfFailThrow(hr);
+ IMDCustomDataSource* pDataSource = NULL;
+ hr = CreateRemoteMDInternalRWSource(remoteMDInternalRWAddr, GetProcess()->GetDataTarget(), mdStructuresDefines, mdStructuresVersion, &pDataSource);
+ IfFailThrow(hr);
+ IMetaDataImport* pImport = NULL;
+ hr = pDispCustom->OpenScopeOnCustomDataSource(pDataSource, 0, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
+ IfFailThrow(hr);
+ UpdateInternalMetaData();
+ return;
+ }
+ }
+
+ if(!m_fForceMetaDataSerialize) // case 1 and 2
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::RM !m_fForceMetaDataSerialize case\n"));
+ GetProcess()->GetDAC()->GetMetadata(m_vmModule, &bufferMetaData); // throws
+ }
+ else if (GetProcess()->GetShim() == NULL) // case 3 won't work on a dump so don't try
+ {
+ return;
+ }
+ else // case 3 on a live process
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::RM m_fForceMetaDataSerialize case\n"));
+ //
+ // Send 1 event to get metadata. This allocates a buffer
+ //
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_RESOLVE_UPDATE_METADATA_1,
+ true,
+ GetAppDomain()->GetADToken());
+
+ event.MetadataUpdateRequest.vmModule = m_vmModule;
+
+ // Note: two-way event here...
+ IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT);
+
+ //
+ // Update it on the RS
+ //
+ bufferMetaData.Init(PTR_TO_CORDB_ADDRESS(event.MetadataUpdateRequest.pMetadataStart), (ULONG) event.MetadataUpdateRequest.nMetadataSize);
+
+ // init the cleanup object to ensure the buffer gets destroyed later
+ cleanup.bufferMetaData = bufferMetaData;
+ cleanup.pProcess = pProcess;
+ cleanup.pModule = this;
+ cleanup.fDoCleanup = TRUE;
+ }
+
+ InitMetaData(bufferMetaData, IsFileMetaDataValid()); // throws
+}
+
+// Determines whether the on-disk metadata for this module is usable as the
+// current metadata
+BOOL CordbModule::IsFileMetaDataValid()
+{
+ bool fOpenFromFile = true;
+
+ // Dynamic, In-memory, modules must be OpenScopeOnMemory.
+ // For modules that require the metadata to be serialized in memory, we must also OpenScopeOnMemory
+ // For Enc, we'll can use OpenScope(onFile) and it will get converted to Memory when we get an emitter.
+ // We're called from before the ModuleLoad callback, so EnC status hasn't been set yet, so
+ // EnC will be false.
+ if (m_fDynamic || m_fInMemory || m_fForceMetaDataSerialize)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: m_fDynamic=0x%x m_fInMemory=0x%x m_fForceMetaDataSerialize=0x%x\n",
+ m_fDynamic, m_fInMemory, m_fForceMetaDataSerialize));
+ fOpenFromFile = false;
+ }
+
+#ifdef _DEBUG
+ // Reg key override to force us to use Open-by-memory. This can let us run perf tests to
+ // compare the Open-by-mem vs. Open-by-file.
+ static DWORD openFromFile = 99;
+ if (openFromFile == 99)
+ openFromFile = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoOpenMDByFile);
+
+ if (openFromFile)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: INTERNAL_DbgNoOpenMDByFile is set\n"));
+ fOpenFromFile = false;
+ }
+#endif
+
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: returns 0x%x\n", fOpenFromFile));
+ return fOpenFromFile;
+}
+
+//---------------------------------------------------------------------------------------
+// Accessor for Internal MetaData importer. This is lazily initialized.
+//
+// Returns:
+// Internal MetaDataImporter, which can be handed off to DAC. Not AddRef().
+// Should be non-null. Throws on error.
+//
+// Notes:
+// An internal metadata importer is used extensively by DAC-ized code (And Edit-and-continue).
+// This should not be handed out through ICorDebug.
+IMDInternalImport * CordbModule::GetInternalMD()
+{
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ UpdateInternalMetaData(); // throws
+ }
+ return m_pInternalMetaDataImport;
+}
+
+//---------------------------------------------------------------------------------------
+// The one-stop top-level initialization function the metadata (both public and private) for this module.
+//
+// Arguments:
+// buffer - valid buffer into target containing the metadata.
+// useFileMappingOptimization - if true this allows us to attempt just opening the importer
+// by using the metadata in the module on disk. if false or
+// if the attempt fails we open the metadata import on memory in
+// target buffer
+//
+// Notes:
+// This will initialize both the internal and public metadata from the buffer in the target.
+// Only called as a helper from RefreshMetaData()
+//
+// This may throw (eg, target buffer is missing).
+//
+void CordbModule::InitMetaData(TargetBuffer buffer, BOOL allowFileMappingOptimization)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO100000, "CM::IM: initing with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
+
+ // clear all the metadata
+ m_pInternalMetaDataImport.Clear();
+
+ if (m_pIMImport == NULL)
+ {
+ // The optimization we're going for here is that the OS will use the same physical memory to
+ // back multiple ReadOnly opens of the same file. Thus since we expect the target process in
+ // live debugging, or the debugger in dump debugging, has already opened the file we would
+ // like to not create a local buffer and spend time copying in metadata from the target when
+ // the OS will happily do address lookup magic against the same physical memory for everyone.
+
+
+ // Try getting the data from the file if allowed, and fall back to using the buffer
+ // if required
+ HRESULT hr = S_OK;
+ if (allowFileMappingOptimization)
+ {
+ hr = InitPublicMetaDataFromFile();
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB,LL_INFO1000000, "CM::IPM: File mapping failed with hr=0x%x\n", hr));
+ }
+ }
+
+ if(!allowFileMappingOptimization || FAILED(hr))
+ {
+ // This is where the expensive copy of all metadata content from target memory
+ // that we would like to try and avoid happens.
+ InitPublicMetaData(buffer);
+ }
+ }
+ else
+ {
+ // We've already handed out an Import object, and so we can't create a new pointer instance.
+ // Instead, we update the existing instance with new data.
+ UpdatePublicMetaDataFromRemote(buffer);
+ }
+
+ // if we haven't set it by this point UpdateInternalMetaData below is going to get us
+ // in an infinite loop of refreshing public metadata
+ _ASSERTE(m_pIMImport != NULL);
+
+ // Now that public metadata has changed, force internal metadata to update too.
+ // Public and internal metadata expose different access interfaces to the same underlying storage.
+ UpdateInternalMetaData();
+}
+
+//---------------------------------------------------------------------------------------
+// Updates the Internal MetaData object from the public importer. Lazily fetch public importer if needed.
+//
+// Assumptions:
+// Caller has cleared Internal metadata before even updating public metadata.
+// This way, if the caller fails halfway through updating the public metadata, we don't have
+// stale internal MetaData.
+void CordbModule::UpdateInternalMetaData()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Caller should have already cleared it.
+ _ASSERTE(m_pInternalMetaDataImport == NULL);
+
+ // Get the importer. If it's currently null, this will go fetch it.
+ IMetaDataImport * pImport = GetMetaDataImporter(); // throws
+
+ // If both the public and the private interfaces are NULL on entry to this function, the call above will
+ // recursively call this function. This can happen if the caller calls GetInternalMD() directly
+ // instead of InitMetaData(). In this case, the above function call will have initialized the internal
+ // interface as well, so we need to check for it here.
+
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ HRESULT hr = GetMDInternalInterfaceFromPublic(
+ pImport,
+ IID_IMDInternalImport,
+ reinterpret_cast<void**> (&m_pInternalMetaDataImport));
+
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ ThrowHR(hr);
+ }
+ }
+
+ _ASSERTE(m_pInternalMetaDataImport != NULL);
+}
+
+// Initialize the public metadata.
+//
+// The debuggee already has a copy of the metadata in its process.
+// If we OpenScope on file as read-only, the OS file-system will share our metadata with the
+// copy in the debuggee. This can be a major perf win. FX metadata can be over 8 MB+.
+// OpenScopeOnMemory can't be shared b/c we allocate a buffer.
+HRESULT CordbModule::InitPublicMetaDataFromFile()
+{
+ INTERNAL_API_ENTRY(this->GetProcess());
+
+ // @dbgtodo metadata - In v3, we can't assume we have the same path namespace as the target (i.e. it could be
+ // a dump or remote), so we can't just try and open the file. Instead we have to rely on interfaces
+ // on the datatarget to map the metadata here. Note that this must also work for minidumps where the
+ // metadata isn't necessarily in the dump image.
+
+ // Get filename. There are 2 filenames to choose from:
+ // - ngen (if applicable).
+ // - non-ngen (aka "normal").
+ // By loading metadata out of the same OS file as loaded into the debuggee space, the OS can share those pages.
+ const WCHAR * szFullPathName = NULL;
+ bool fDebuggerLoadingNgen = false;
+ bool fDebuggeeLoadedNgen = false;
+ szFullPathName = GetNGenImagePath();
+
+ if(szFullPathName != NULL)
+ {
+ fDebuggeeLoadedNgen = true;
+ fDebuggerLoadingNgen = true;
+
+#ifndef FEATURE_PAL
+ // NGEN images are large and we shouldn't load them if they won't be shared, therefore fail the NGEN mapping and
+ // fallback to IL image if the debugger doesn't have the image loaded already.
+ // Its possible that the debugger would still load the NGEN image sometime in the future and we will miss a sharing
+ // opportunity. Its an acceptable loss from an imperfect heuristic.
+ if (NULL == WszGetModuleHandle(szFullPathName))
+#endif
+ {
+ szFullPathName = NULL;
+ fDebuggerLoadingNgen = false;
+ }
+
+ }
+
+ // If we don't have or decided not to load the NGEN image, check to see if IL image is available
+ if (!fDebuggerLoadingNgen)
+ {
+ szFullPathName = GetModulePath();
+ }
+
+ // If we are doing live debugging we shouldn't use metadata from an IL image because it doesn't match closely enough.
+ // In particular the RVAs for IL code headers are different between the two images which will cause all IL code and
+ // local var signature lookups to fail. With further work we could compensate for the RVAs by computing
+ // the image layout differences and adjusting the returned RVAs, but there may be other differences that need to be accounted
+ // for as well. If we did go that route we should do a binary diff across a variety of NGEN/IL image metadata blobs to
+ // get a concrete understanding of the format differences.
+ //
+ // This check should really be 'Are we OK with only getting the functionality level of mini-dump debugging?' but since we
+ // don't know the debugger's intent we guess whether or not we are doing dump debugging by checking if we are shimmed. Once
+ // the shim supports live debugging we should probably just stop automatically falling back to IL image and let the debugger
+ // decide via the ICorDebugMetadataLocator interface.
+ if(fDebuggeeLoadedNgen && !fDebuggerLoadingNgen && GetProcess()->GetShim()!=NULL)
+ {
+ // The IL image might be there, but we shouldn't use it for live debugging
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+
+ // @dbgtodo metadata - This is really a CreateFile() call which we can't do. We must offload this to
+ // the data target for the dump-debugging scenarios.
+ //
+ // We're opening it as "read". If we QI for an IEmit interface (which we need for EnC),
+ // then the metadata engine will convert it to a "write" underneath us.
+ // We want "read" so that we can let the OS share the pages.
+ DWORD dwOpenFlags = 0;
+
+ // If we know we're never going to need to write (i.e. never do EnC), then we should indicate
+ // that to metadata by telling it this interface will always be read-only. By passing read-only,
+ // the metadata library will then also share the VM space for the image when the same image is
+ // opened multiple times for multiple AppDomains.
+ // We don't currently have a way to tell absolutely whether this module will support EnC, but we
+ // know that NGen modules NEVER support EnC, and NGen is the common case that eats up a lot of VM.
+ // So we'll use the heuristic of opening the metadata for all ngen images as read-only. Ideally
+ // we'd go even further here (perhaps even changing metadata to map only the region of the file it
+ // needs).
+ if (fDebuggerLoadingNgen)
+ {
+ dwOpenFlags = ofReadOnly | ofTrustedImage;
+ }
+
+ // This is the only place we ever validate that the file matches, because we're potentially
+ // loading the file from disk ourselves. We're doing this without giving the debugger a chance
+ // to do anything. We should never load a file that isn't an exact match.
+ return InitPublicMetaDataFromFile(szFullPathName, dwOpenFlags, true);
+}
+
+// We should only ever validate we have the correct file if it's a file we found ourselves.
+// We allow the debugger to choose their own policy with regard to using metadata from the IL image
+// when debugging an NI, or even intentionally using mismatched metadata if they like.
+HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName,
+ DWORD dwOpenFlags,
+ bool validateFileInfo)
+{
+#ifdef FEATURE_PAL
+ // UNIXTODO: Some intricate details of file mapping don't work on Linux as on Windows.
+ // We have to revisit this and try to fix it for POSIX system.
+ return E_FAIL;
+#else
+ if (validateFileInfo)
+ {
+ // Check that we've got the right file to target.
+ // There's nothing to prevent some other file being copied in for live, and with
+ // dump debugging there's nothing to say that we're not on another machine where a different
+ // file is at the same path.
+ // If we can't validate we have a hold of the correct file, we should not open it.
+ // We will fall back on asking the debugger to get us the correct file, or copying
+ // target memory back to the debugger.
+ DWORD dwImageTimeStamp = 0;
+ DWORD dwImageSize = 0;
+ bool isNGEN = false; // unused
+ StringCopyHolder filePath;
+
+
+ _ASSERTE(!m_vmPEFile.IsNull());
+ // MetaData lookup favors the NGEN image, which is what we want here.
+ if (!this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ isNGEN,
+ &filePath))
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't get metadata info for file \"%s\"\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // If the timestamp and size don't match, then this is the wrong file!
+ // Map the file and check them.
+ HandleHolder hMDFile = WszCreateFile(pszFullPathName,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL, // default security descriptor
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hMDFile == INVALID_HANDLE_VALUE)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ DWORD dwFileHigh = 0;
+ DWORD dwFileLow = GetFileSize(hMDFile, &dwFileHigh);
+ if (dwFileLow == INVALID_FILE_SIZE)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: File \"%s\" had invalid size.\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ _ASSERTE(dwFileHigh == 0);
+
+ HandleHolder hMap = WszCreateFileMapping(hMDFile, NULL, PAGE_READONLY, dwFileHigh, dwFileLow, NULL);
+ if (hMap == NULL)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't create mapping of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ MapViewHolder hMapView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+ if (hMapView == NULL)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't map view of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // Mapped as flat file, have PEDecoder go find what we want.
+ PEDecoder pedecoder(hMapView, (COUNT_T)dwFileLow);
+
+ if (!pedecoder.HasNTHeaders())
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: \"%s\" did not have PE headers!\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ if ((dwImageSize != pedecoder.GetVirtualSize()) ||
+ (dwImageTimeStamp != pedecoder.GetTimeDateStamp()))
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Validation of \"%s\" failed. "
+ "Expected size=%x, Expected timestamp=%x, Actual size=%x, Actual timestamp=%x\n",
+ pszFullPathName,
+ pedecoder.GetVirtualSize(),
+ pedecoder.GetTimeDateStamp(),
+ dwImageSize,
+ dwImageTimeStamp));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // All checks passed, go ahead and load this file for real.
+ }
+
+ // Get metadata Dispenser.
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+
+ HRESULT hr = pDisp->OpenScope(pszFullPathName, dwOpenFlags, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
+ _ASSERTE(SUCCEEDED(hr) == (m_pIMImport != NULL));
+
+ if (FAILED(hr))
+ {
+ // This should never happen in normal scenarios. It could happen if someone has renamed
+ // the assembly after it was opened by the debugee process, but this should be rare enough
+ // that we don't mind taking the perf. hit and loading from memory.
+ // @dbgtodo metadata - would this happen in the shadow-copy scenario?
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open metadata in file \"%s\" (hr=%x)\n", pszFullPathName, hr));
+ }
+
+ return hr;
+#endif // FEATURE_PAL
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize the public metadata.
+//
+// Arguments:
+// buffer - valid buffer into target containing the metadata.
+//
+// Assumptions:
+// This is an internal function which should only be called once to initialize the
+// metadata. Future attempts to re-initialize (in dynamic cases) should call code:CordbModule::UpdatePublicMetaDataFromRemote
+// After the public metadata is initialized, initialize private metadata via code:CordbModule::UpdateInternalMetaData
+//
+void CordbModule::InitPublicMetaData(TargetBuffer buffer)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this->GetProcess());
+ LOG((LF_CORDB,LL_INFO100000, "CM::IPM: initing with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
+ ULONG nMetaDataSize = buffer.cbSize;
+
+ if (nMetaDataSize == 0)
+ {
+ // We should always have metadata, and if we don't, we want to know.
+ // @dbgtodo metadata - we know metadata from dynamic modules doesn't work in V3
+ // (non-shim) cases yet.
+ // But our caller should already have handled that case.
+ SIMPLIFYING_ASSUMPTION(!"Error: missing the metadata");
+ return;
+ }
+
+ HRESULT hr = S_OK;
+
+ // Get metadata Dispenser.
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+
+ // copy it over from the remote process
+
+ CoTaskMemHolder<VOID> pMetaDataCopy;
+ CopyRemoteMetaData(buffer, pMetaDataCopy.GetAddr());
+
+
+ //
+ // Setup our metadata import object, m_pIMImport
+ //
+
+ // Save the old mode for restoration
+ VARIANT valueOld;
+ hr = pDisp->GetOption(MetaDataSetUpdate, &valueOld);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hr));
+
+ // Set R/W mode so that we can update the metadata when
+ // we do EnC operations.
+ VARIANT valueRW;
+ V_VT(&valueRW) = VT_UI4;
+ V_I4(&valueRW) = MDUpdateFull;
+ hr = pDisp->SetOption(MetaDataSetUpdate, &valueRW);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hr));
+
+ hr = pDisp->OpenScopeOnMemory(pMetaDataCopy,
+ nMetaDataSize,
+ ofTakeOwnership,
+ IID_IMetaDataImport,
+ reinterpret_cast<IUnknown**>( &m_pIMImport ));
+
+ // MetaData has taken ownership -don't free the memory
+ pMetaDataCopy.SuppressRelease();
+
+ // Immediately restore the old setting.
+ HRESULT hrRestore = pDisp->SetOption(MetaDataSetUpdate, &valueOld);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hrRestore));
+
+ // Throw on errors.
+ IfFailThrow(hr);
+ IfFailThrow(hrRestore);
+
+ // Done!
+}
+
+//---------------------------------------------------------------------------------------
+// Update public MetaData by copying it from the target and updating our IMetaDataImport object.
+//
+// Arguments:
+// buffer - buffer into target space containing metadata blob
+//
+// Notes:
+// Useful for additional class-loads into a dynamic module. A new class means new metadata
+// and so we need to update the RS metadata to stay in sync with the left-side.
+//
+// This will call code:CordbModule::CopyRemoteMetaData to copy the remote buffer locally, and then
+// it can OpenScopeOnMemory().
+//
+void CordbModule::UpdatePublicMetaDataFromRemote(TargetBuffer bufferRemoteMetaData)
+{
+ CONTRACTL
+ {
+ // @dbgtodo metadata - think about the error semantics here. These fails during dispatching an event; so
+ // address this during event pipeline.
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ if (bufferRemoteMetaData.IsEmpty())
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ INTERNAL_API_ENTRY(this->GetProcess()); //
+ LOG((LF_CORDB,LL_INFO100000, "CM::UPMFR: updating with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(bufferRemoteMetaData.pAddress), bufferRemoteMetaData.cbSize));
+ // We're re-initializing existing metadata.
+ _ASSERTE(m_pIMImport != NULL);
+
+
+ HRESULT hr = S_OK;
+
+ ULONG dwMetaDataSize = bufferRemoteMetaData.cbSize;
+
+ // First copy it from the remote process
+ CoTaskMemHolder<VOID> pLocalMetaDataPtr;
+ CopyRemoteMetaData(bufferRemoteMetaData, pLocalMetaDataPtr.GetAddr());
+
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+ _ASSERTE(pDisp != NULL); // throws on error.
+
+ LOG((LF_CORDB,LL_INFO100000, "CM::RI: converting to new metadata\n"));
+
+ // now verify that the metadata is valid by opening a temporary scope on the memory
+ {
+ ReleaseHolder<IMetaDataImport> pIMImport;
+ hr = pDisp->OpenScopeOnMemory(pLocalMetaDataPtr,
+ dwMetaDataSize,
+ 0,
+ IID_IMetaDataImport,
+ (IUnknown**)&pIMImport);
+ IfFailThrow(hr);
+ }
+
+ // We reopen on an existing instance, not create a new instance.
+ _ASSERTE(m_pIMImport != NULL); //
+
+ // Now tell our current IMetaDataImport object to re-initialize by swapping in the new memory block.
+ // This allows us to keep manipulating metadata objects on other threads without crashing.
+ // This will also invalidate an existing associated Internal MetaData.
+ hr = ReOpenMetaDataWithMemoryEx(m_pIMImport, pLocalMetaDataPtr, dwMetaDataSize, ofTakeOwnership );
+ IfFailThrow(hr);
+
+ // Success. MetaData now owns the metadata memory
+ pLocalMetaDataPtr.SuppressRelease();
+}
+
+//---------------------------------------------------------------------------------------
+// Copy metadata memory from the remote process into a newly allocated local buffer.
+//
+// Arguments:
+// pRemoteMetaDataPtr - pointer to remote buffer
+// dwMetaDataSize - size of buffer.
+// pLocalBuffer - holder to get local buffer.
+//
+// Returns:
+// pLocalBuffer may be allocated.
+// Throws on error (pLocalBuffer may contain garbage).
+// Else if successful, pLocalBuffer contains local copy of metadata.
+//
+// Notes:
+// This can copy metadata out for the dynamic case or the normal case.
+// Uses an allocator (CoTaskMemHolder) that lets us hand off the memory to the metadata.
+void CordbModule::CopyRemoteMetaData(
+ TargetBuffer buffer,
+ CoTaskMemHolder<VOID> * pLocalBuffer)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pLocalBuffer != NULL);
+ _ASSERTE(!buffer.IsEmpty());
+
+ // Allocate space for the local copy of the metadata
+ // No need to zero out the memory since we'll fill it all here.
+ LPVOID pRawBuffer = CoTaskMemAlloc(buffer.cbSize);
+ if (pRawBuffer == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+
+ pLocalBuffer->Assign(pRawBuffer);
+
+
+
+ // Copy the metadata from the left side
+ GetProcess()->SafeReadBuffer(buffer, (BYTE *)pRawBuffer);
+
+ return;
+}
+
+HRESULT CordbModule::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugModule)
+ {
+ *pInterface = static_cast<ICorDebugModule*>(this);
+ }
+ else if (id == IID_ICorDebugModule2)
+ {
+ *pInterface = static_cast<ICorDebugModule2*>(this);
+ }
+ else if (id == IID_ICorDebugModule3)
+ {
+ *pInterface = static_cast<ICorDebugModule3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugModule*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbModule::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ *ppProcess = static_cast<ICorDebugProcess*> (GetProcess());
+ GetProcess()->ExternalAddRef();
+
+ return S_OK;
+}
+
+HRESULT CordbModule::GetBaseAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_PEBuffer.pAddress;
+ return S_OK;
+}
+
+HRESULT CordbModule::GetAssembly(ICorDebugAssembly **ppAssembly)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppAssembly, ICorDebugAssembly **);
+
+ *ppAssembly = static_cast<ICorDebugAssembly *> (m_pAssembly);
+ if (m_pAssembly != NULL)
+ {
+ m_pAssembly->ExternalAddRef();
+ }
+
+ return S_OK;
+}
+
+// Public implementation of ICorDebugModule::GetName,
+// wrapper around code:GetNameWorker (which throws).
+HRESULT CordbModule::GetName(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ EX_TRY
+ {
+ hr = GetNameWorker(cchName, pcchName, szName);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // GetNameWorker can use metadata. If it fails due to missing metadata, or if we fail to find expected
+ // target memory (dump debugging) then we should fall back to getting the file name without metadata.
+ if ((hr == CORDBG_E_MISSING_METADATA) ||
+ (hr == CORDBG_E_READVIRTUAL_FAILURE) ||
+ (hr == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)))
+ {
+ DWORD dwImageTimeStamp = 0; // unused
+ DWORD dwImageSize = 0; // unused
+ bool isNGEN = false;
+ StringCopyHolder filePath;
+
+ _ASSERTE(!m_vmPEFile.IsNull());
+ if (this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ isNGEN,
+ &filePath))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ // Unfortunately, metadata lookup preferentially takes the ngen image - so in this case,
+ // we need to go back and get the IL image's name instead.
+ if ((isNGEN) &&
+ (this->GetProcess()->GetDAC()->GetILImageInfoFromNgenPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ &filePath)))
+ {
+ _ASSERTE(filePath.IsSet());
+ }
+
+ hr = CopyOutString(filePath, cchName, pcchName, szName);
+ }
+ }
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Gets the module pretty name (may be filename or faked up name)
+//
+// Arguments:
+// cchName - count of characters in the szName buffer on input.
+// *pcchName - Optional Out parameter, which gets set to the fully requested size
+// (not just how many characters are written).
+// szName - buffer to get name.
+//
+// Returns:
+// S_OK on success.
+// S_FALSE if we fabricate the name.
+// Return failing HR (on common errors) or Throw on exceptional errors.
+//
+// Note:
+// Filename isn't necessarily the same as the module name in the metadata.
+//
+HRESULT CordbModule::GetNameWorker(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ HRESULT hr = S_OK;
+ const WCHAR * szTempName = NULL;
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ szTempName = GetModulePath();
+ );
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // To support VS when debugging remotely we act like the Compact Framework and return the assembly name
+ // when asked for the name of an in-memory module.
+ if (szTempName == NULL)
+ {
+ IMetaDataAssemblyImport *pAssemblyImport = NULL;
+ if (SUCCEEDED(hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&pAssemblyImport)))
+ {
+ mdAssembly mda = TokenFromRid(1, mdtAssembly);
+ hr = pAssemblyImport->GetAssemblyProps(mda, // [IN] The Assembly for which to get the properties.
+ NULL, // [OUT] Pointer to the Originator blob.
+ NULL, // [OUT] Count of bytes in the Originator Blob.
+ NULL, // [OUT] Hash Algorithm.
+ szName, // [OUT] Buffer to fill with name.
+ cchName, // [IN] Size of buffer in wide chars.
+ (ULONG*)pcchName, // [OUT] Actual # of wide chars in name.
+ NULL, // [OUT] Assembly MetaData.
+ NULL); // [OUT] Flags.
+
+ pAssemblyImport->Release();
+
+ return hr;
+ }
+
+ // reset hr
+ hr = S_OK;
+ }
+
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ StringCopyHolder buffer;
+ // If the module has no file name, then we'll fabricate a fake name
+ if (!szTempName)
+ {
+ // On MiniDumpNormal, if the debugger can't find the module then there's no way we will
+ // find metadata.
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+
+ // Tempting to use the metadata-scope name, but that's a regression from Whidbey. For manifest modules,
+ // the metadata scope name is not initialized with the string the user supplied to create the
+ // dynamic assembly. So we call into the runtime to use CLR heuristics to get a more accurate name.
+ m_pProcess->GetDAC()->GetModuleSimpleName(m_vmModule, &buffer);
+ _ASSERTE(buffer.IsSet());
+ szTempName = buffer;
+ // Note that we considered returning S_FALSE for fabricated names like this, but that's a breaking
+ // change from Whidbey that is known to trigger bugs in vS. If a debugger wants to differentiate
+ // real path names from fake simple names, we'll just have to add a new API with the right semantics.
+ }
+
+ hr = CopyOutString(szTempName, cchName, pcchName, szName);
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Gets actual name of loaded module. (no faked names)
+//
+// Returns:
+// string for full path to module name. This is a file that can be opened.
+// NULL if name is not available (such as in some dynamic module cases)
+// Throws if failed accessing target
+//
+// Notes:
+// We avoid using the method name "GetModuleFileName" because winbase.h #defines that
+// token (along with many others) to have an A or W suffix.
+const WCHAR * CordbModule::GetModulePath()
+{
+ // Lazily initialize. Module filenames cannot change, and so once
+ // we've retrieved this successfully, it's stored for good.
+ if (!m_strModulePath.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ pDac->GetModulePath(m_vmModule, &m_strModulePath); // throws
+ _ASSERTE(m_strModulePath.IsSet());
+ }
+
+ if (m_strModulePath.IsEmpty())
+ {
+ return NULL; // module has no filename
+ }
+ return m_strModulePath;
+}
+
+//---------------------------------------------------------------------------------------
+// Get and caches ngen image path.
+//
+// Returns:
+// Null-terminated string to ngen image path.
+// NULL if there is no ngen filename (eg, file is not ngenned).
+// Throws on error (such as inability to read the path from the target).
+//
+// Notes:
+// This can be used to get the path to find metadata. For ngenned images,
+// the IL (and associated metadata) may not be loaded, so we may want to get the
+// metadata out of the ngen image.
+const WCHAR * CordbModule::GetNGenImagePath()
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Lazily initialize. Module filenames cannot change, and so once
+ // we've retrieved this successfully, it's stored for good.
+ if (!m_strNGenImagePath.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ BOOL fNonEmpty = pDac->GetModuleNGenPath(m_vmModule, &m_strNGenImagePath); // throws
+ (void)fNonEmpty; //prevent "unused variable" error from GCC
+ _ASSERTE(m_strNGenImagePath.IsSet() && (m_strNGenImagePath.IsEmpty() == !fNonEmpty));
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr) ||
+ m_strNGenImagePath == NULL ||
+ m_strNGenImagePath.IsEmpty())
+ {
+ return NULL; // module has no ngen filename
+ }
+ return m_strNGenImagePath;
+}
+
+// Implementation of ICorDebugModule::EnableJITDebugging
+// See also code:CordbModule::SetJITCompilerFlags
+HRESULT CordbModule::EnableJITDebugging(BOOL bTrackJITInfo, BOOL bAllowJitOpts)
+{
+ // Leftside will enforce that this is a valid time to change jit flags.
+ // V1.0 behavior allowed setting these in the middle of a module's lifetime, which meant
+ // that different methods throughout the module may have been jitted differently.
+ // Since V2, this has to be set when the module is first loaded, before anything is jitted.
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ DWORD dwFlags = CORDEBUG_JIT_DEFAULT;
+
+ // Since V2, bTrackJITInfo is the default and cannot be turned off.
+ if (!bAllowJitOpts)
+ {
+ dwFlags |= CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+ return SetJITCompilerFlags(dwFlags);
+}
+
+HRESULT CordbModule::EnableClassLoadCallbacks(BOOL bClassLoadCallbacks)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ // You must receive ClassLoad callbacks for dynamic modules so that we can keep the metadata up-to-date on the Right
+ // Side. Therefore, we refuse to turn them off for all dynamic modules (they were forced on when the module was
+ // loaded on the Left Side.)
+ if (m_fDynamic && !bClassLoadCallbacks)
+ return E_INVALIDARG;
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+ // Send a Set Class Load Flag event to the left side. There is no need to wait for a response, and this can be
+ // called whether or not the process is synchronized.
+ CordbProcess *pProcess = GetProcess();
+
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_SET_CLASS_LOAD_FLAG,
+ false,
+ (GetAppDomain()->GetADToken()));
+ event.SetClassLoad.vmDomainFile = this->m_vmDomainFile;
+ event.SetClassLoad.flag = (bClassLoadCallbacks == TRUE);
+
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event,
+ sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Public implementation of ICorDebugModule::GetFunctionFromToken
+// Get the CordbFunction matches this token / module pair.
+// Each time a function is Enc-ed, it gets its own CordbFunction object.
+// This will return the latest EnC version of the function for this Module,Token pair.
+HRESULT CordbModule::GetFunctionFromToken(mdMethodDef token,
+ ICorDebugFunction **ppFunction)
+{
+ // This is not reentrant. DBI should call code:CordbModule::LookupOrCreateFunctionLatestVersion instead.
+ PUBLIC_API_ENTRY(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess()); // @todo - can this be RequiredStop?
+
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Check token is valid.
+ if ((token == mdMethodDefNil) ||
+ (!GetMetaDataImporter()->IsValidToken(token)))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ CordbFunction * pFunction = LookupOrCreateFunctionLatestVersion(token);
+
+ *ppFunction = static_cast<ICorDebugFunction*> (pFunction);
+ pFunction->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::GetFunctionFromRVA(CORDB_ADDRESS rva,
+ ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbModule::LookupClassByToken(mdTypeDef token,
+ CordbClass **ppClass)
+{
+ INTERNAL_API_ENTRY(this->GetProcess()); //
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY // @dbgtodo exceptions - push this up
+ {
+ *ppClass = NULL;
+
+ if ((token == mdTypeDefNil) || (TypeFromToken(token) != mdtTypeDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo synchronization - Push this up
+
+ CordbClass *pClass = m_classes.GetBase(token);
+ if (pClass == NULL)
+ {
+ // Validate the token.
+ if (!GetMetaDataImporter()->IsValidToken(token))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ RSInitHolder<CordbClass> pClassInit(new CordbClass(this, token));
+ pClass = pClassInit.TransferOwnershipToHash(&m_classes);
+ }
+
+ *ppClass = pClass;
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::GetClassFromToken(mdTypeDef token,
+ ICorDebugClass **ppClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(this->GetProcess()); // @todo - could this be RequiredStopped?
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbClass *pClass = NULL;
+ *ppClass = NULL;
+
+ // Validate the token.
+ if (!GetMetaDataImporter()->IsValidToken(token))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ hr = LookupClassByToken(token, &pClass);
+ IfFailThrow(hr);
+
+ *ppClass = static_cast<ICorDebugClass*> (pClass);
+ pClass->ExternalAddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::CreateBreakpoint(ICorDebugModuleBreakpoint **ppBreakpoint)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugModuleBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+//
+// Return the token for the Module table entry for this object. The token
+// may then be passed to the meta data import api's.
+//
+HRESULT CordbModule::GetToken(mdModule *pToken)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pToken, mdModule *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = GetMetaDataImporter()->GetModuleFromScope(pToken);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+// public implementation for ICorDebugModule::GetMetaDataInterface
+// Return a meta data interface pointer that can be used to examine the
+// meta data for this module.
+HRESULT CordbModule::GetMetaDataInterface(REFIID riid, IUnknown **ppObj)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObj, IUnknown **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // QI the importer that we already have and return the result.
+ hr = GetMetaDataImporter()->QueryInterface(riid, (void**)ppObj);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// LookupFunctionLatestVersion finds the latest cached version of an existing CordbFunction
+// in the given module. If the function doesn't exist, it returns NULL.
+//
+// Arguments:
+// funcMetaDataToken - methoddef token for function to lookup
+//
+//
+// Notes:
+// If no CordbFunction instance was cached, then this returns NULL.
+// use code:CordbModule::LookupOrCreateFunctionLatestVersion to do a lookup that will
+// populate the cache if needed.
+CordbFunction* CordbModule::LookupFunctionLatestVersion(mdMethodDef funcMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ return m_functions.GetBase(funcMetaDataToken);
+}
+
+
+//-----------------------------------------------------------------------------
+// Lookup (or create) the CordbFunction for the latest EnC version.
+//
+// Arguments:
+// funcMetaDataToken - methoddef token for function to lookup
+//
+// Returns:
+// CordbFunction instance for that token. This will create an instance if needed, and so never returns null.
+// Throws on critical error.
+//
+// Notes:
+// This creates the latest EnC version. Use code:CordbModule::LookupOrCreateFunction to do an
+// enc-version aware function lookup.
+//
+CordbFunction* CordbModule::LookupOrCreateFunctionLatestVersion(mdMethodDef funcMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbFunction * pFunction = m_functions.GetBase(funcMetaDataToken);
+ if (pFunction != NULL)
+ {
+ return pFunction;
+ }
+
+ // EnC adds each version to the hash. So if the hash lookup fails, then it must not be an EnC case,
+ // and so we can use the default version number.
+ return CreateFunction(funcMetaDataToken, CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+}
+
+//-----------------------------------------------------------------------------
+// LookupOrCreateFunction finds an existing version of CordbFunction in the given module.
+// If the function doesn't exist, it creates it.
+//
+// The outgoing function is not yet fully inititalized. For eg, the Class field is not set.
+// However, ICorDebugFunction::GetClass() will check that and lazily initialize the field.
+//
+// Throws on error.
+//
+CordbFunction * CordbModule::LookupOrCreateFunction(mdMethodDef funcMetaDataToken, SIZE_T enCVersion)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbFunction * pFunction = m_functions.GetBase(funcMetaDataToken);
+
+ // special case non-existance as need to add to the hash table too
+ if (pFunction == NULL)
+ {
+ // EnC adds each version to the hash. So if the hash lookup fails,
+ // then it must not be an EnC case.
+ return CreateFunction(funcMetaDataToken, enCVersion);
+ }
+
+ // linked list sorted with most recent version at front. Version numbers correspond
+ // to actual edit count against the module, so version numbers not necessarily contiguous.
+ // Any valid EnC version must already exist as we would have created it on the ApplyChanges
+ for (CordbFunction *pf=pFunction; pf != NULL; pf = pf->GetPrevVersion())
+ {
+ if (pf->GetEnCVersionNumber() == enCVersion)
+ {
+ return pf;
+ }
+ }
+
+ _ASSERTE(!"Couldn't find EnC version of function\n");
+ ThrowHR(E_FAIL);
+}
+
+HRESULT CordbModule::IsDynamic(BOOL *pDynamic)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pDynamic, BOOL *);
+
+ (*pDynamic) = m_fDynamic;
+
+ return S_OK;
+}
+
+BOOL CordbModule::IsDynamic()
+{
+ return m_fDynamic;
+}
+
+
+HRESULT CordbModule::IsInMemory(BOOL *pInMemory)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pInMemory, BOOL *);
+
+ (*pInMemory) = m_fInMemory;
+
+ return S_OK;
+}
+
+HRESULT CordbModule::GetGlobalVariableValue(mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this->GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ if (m_pClass == NULL)
+ {
+ CordbClass * pGlobalClass = NULL;
+ hr = LookupClassByToken(COR_GLOBAL_PARENT_TOKEN, &pGlobalClass);
+ IfFailThrow(hr);
+
+ m_pClass.Assign(pGlobalClass);
+ _ASSERTE(m_pClass != NULL);
+ }
+
+ hr = m_pClass->GetStaticFieldValue(fieldDef, NULL, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+
+//
+// CreateFunction creates a new function from the given information and
+// adds it to the module.
+//
+CordbFunction * CordbModule::CreateFunction(mdMethodDef funcMetaDataToken, SIZE_T enCVersion)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // In EnC cases, the token may not yet be valid. We may be caching the CordbFunction
+ // for a token for an added method before the metadata is updated on the RS.
+ // We rely that our caller has done token validation.
+
+ // Create a new CordbFunction object or throw.
+ RSInitHolder<CordbFunction> pFunction(new CordbFunction(this, funcMetaDataToken, enCVersion)); // throws
+ CordbFunction * pCopy = pFunction.TransferOwnershipToHash(&m_functions);
+ return pCopy;
+}
+
+#ifdef EnC_SUPPORTED
+//---------------------------------------------------------------------------------------
+//
+// Creates a new CordbFunction object to represent this new version of a function and
+// updates the module's function collection to mark this as the latest version.
+//
+// Arguments:
+// funcMetaDataToken - the functions methodDef token in this module
+// enCVerison - The new version number of this function
+// ppFunction - Output param for the new instance - optional
+//
+// Assumptions:
+// Assumes the specified version of this function doesn't already exist (i.e. enCVersion
+// is newer than all existing versions).
+//
+HRESULT CordbModule::UpdateFunction(mdMethodDef funcMetaDataToken,
+ SIZE_T enCVersion,
+ CordbFunction** ppFunction)
+{
+ INTERNAL_API_ENTRY(this);
+ if (ppFunction)
+ *ppFunction = NULL;
+
+ _ASSERTE(funcMetaDataToken);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // pOldVersion is the 2nd newest version
+ CordbFunction* pOldVersion = LookupFunctionLatestVersion(funcMetaDataToken);
+
+ // if don't have an old version, then create a default versioned one as will most likely
+ // go looking for it later and easier to put it in now than have code to insert it later.
+ if (!pOldVersion)
+ {
+ LOG((LF_ENC, LL_INFO10000, "CM::UF: adding %8.8x with version %d\n", funcMetaDataToken, enCVersion));
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pOldVersion = CreateFunction(funcMetaDataToken, CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ // This method should not be called for versions that already exist
+ _ASSERTE( enCVersion > pOldVersion->GetEnCVersionNumber());
+
+ LOG((LF_ENC, LL_INFO10000, "CM::UF: updating %8.8x with version %d\n", funcMetaDataToken, enCVersion));
+ // Create a new function object.
+ CordbFunction * pNewVersion = new (nothrow) CordbFunction(this, funcMetaDataToken, enCVersion);
+
+ if (pNewVersion == NULL)
+ return E_OUTOFMEMORY;
+
+ // Chain the 2nd most recent version onto this instance (this will internal addref).
+ pNewVersion->SetPrevVersion(pOldVersion);
+
+ // Add the function to the Module's hash of all functions.
+ HRESULT hr = m_functions.SwapBase(pOldVersion, pNewVersion);
+
+ if (FAILED(hr))
+ {
+ delete pNewVersion;
+ return hr;
+ }
+
+ // Do cleanup for function which is no longer the latest version
+ pNewVersion->GetPrevVersion()->MakeOld();
+
+ if (ppFunction)
+ *ppFunction = pNewVersion;
+
+ return hr;
+}
+#endif // EnC_SUPPORTED
+
+
+HRESULT CordbModule::LookupOrCreateClass(mdTypeDef classMetaDataToken,CordbClass** ppClass)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo exceptions synchronization-
+ // Push this lock up, convert to exceptions.
+
+ HRESULT hr = S_OK;
+ *ppClass = LookupClass(classMetaDataToken);
+ if (*ppClass == NULL)
+ {
+ hr = CreateClass(classMetaDataToken,ppClass);
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ _ASSERTE(*ppClass != NULL);
+ }
+ return hr;
+}
+
+//
+// LookupClass finds an existing CordbClass in the given module.
+// If the class doesn't exist, it returns NULL.
+//
+CordbClass* CordbModule::LookupClass(mdTypeDef classMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ return m_classes.GetBase(classMetaDataToken);
+}
+
+//
+// CreateClass creates a new class from the given information and
+// adds it to the module.
+//
+HRESULT CordbModule::CreateClass(mdTypeDef classMetaDataToken,
+ CordbClass** ppClass)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbClass* pClass = new (nothrow) CordbClass(this, classMetaDataToken);
+
+ if (pClass == NULL)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = m_classes.AddBase(pClass);
+
+ if (SUCCEEDED(hr))
+ *ppClass = pClass;
+ else
+ delete pClass;
+
+ if (classMetaDataToken == COR_GLOBAL_PARENT_TOKEN)
+ {
+ _ASSERTE( m_pClass == NULL ); //redundant create
+ m_pClass.Assign(pClass);
+ }
+
+ return hr;
+}
+
+
+// Resolve a type-ref from this module to a CordbClass
+//
+// Arguments:
+// token - a Type Ref in this module's scope.
+// ppClass - out parameter to get the class we resolve to.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_CLASS_NOT_LOADED is the TypeRef is not yet resolved because the type it will refer
+// to is not yet loaded.
+//
+// Notes:
+// In general, a TypeRef refers to a type in another module. (Although as a corner case, it could
+// refer to this module too). This resolves a TypeRef within the current module's scope to a
+// (TypeDef, metadata scope), which is in turn encapsulated as a CordbClass.
+//
+// A TypeRef has a resolution scope (ModuleRef or AssemblyRef) and string name for the type
+// within that scope. Resolving means:
+// 1. Determining the actual metadata scope loaded for the resolution scope.
+// See also code:CordbModule::ResolveAssemblyInternal
+// If the resolved module hasn't been loaded yet, the resolution will fail.
+// 2. Doing a string lookup of the TypeRef's name within that resolved scope to find the TypeDef.
+// 3. Returning the (resolved scope, TypeDef) pair.
+//
+HRESULT CordbModule::ResolveTypeRef(mdTypeRef token, CordbClass **ppClass)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ CordbProcess * pProcess = GetProcess();
+
+ _ASSERTE((pProcess->GetShim() == NULL) || pProcess->GetSynchronized());
+
+
+ if ((token == mdTypeRefNil) || (TypeFromToken(token) != mdtTypeRef))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (m_vmDomainFile.IsNull() || m_pAppDomain == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+
+ HRESULT hr = S_OK;
+ *ppClass = NULL;
+ EX_TRY
+ {
+ TypeRefData inData = {m_vmDomainFile, token};
+ TypeRefData outData;
+
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->ResolveTypeReference(&inData, &outData);
+ }
+
+ CordbModule * pModule = m_pAppDomain->LookupOrCreateModule(outData.vmDomainFile);
+ IfFailThrow(pModule->LookupClassByToken(outData.typeToken, ppClass));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+} // CordbModule::ResolveTypeRef
+
+// Resolve a type ref or def to a CordbClass
+//
+// Arguments:
+// token - a mdTypeDef or mdTypeRef in this module's scope to be resolved
+// ppClass - out parameter to get the CordbClass for this type
+//
+// Notes:
+// See code:CordbModule::ResolveTypeRef for more details.
+HRESULT CordbModule::ResolveTypeRefOrDef(mdToken token, CordbClass **ppClass)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ if ((token == mdTypeRefNil) ||
+ (TypeFromToken(token) != mdtTypeRef && TypeFromToken(token) != mdtTypeDef))
+ return E_INVALIDARG;
+
+ if (TypeFromToken(token)==mdtTypeRef)
+ {
+ // It's a type-ref. That means the type is defined in another module.
+ // That other module is determined at runtime by Fusion / Loader policy. So we need to
+ // ultimately ask the runtime which module was actually loaded.
+ return ( ResolveTypeRef(token, ppClass) );
+ }
+ else
+ {
+ // It's a type-def. This is the easy case because the type is defined in this same module.
+ return ( LookupClassByToken(token, ppClass) );
+ }
+
+}
+
+//
+// GetSize returns the size of the module.
+//
+HRESULT CordbModule::GetSize(ULONG32 *pcBytes)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcBytes, ULONG32 *);
+
+ *pcBytes = m_PEBuffer.cbSize;
+
+ return S_OK;
+}
+
+CordbAssembly *CordbModule::GetCordbAssembly()
+{
+ INTERNAL_API_ENTRY(this);
+ return m_pAssembly;
+}
+
+
+// This is legacy from the aborted V1 EnC attempt - not used in V2 EnC support
+HRESULT CordbModule::GetEditAndContinueSnapshot(
+ ICorDebugEditAndContinueSnapshot **ppEditAndContinueSnapshot)
+{
+ return E_NOTIMPL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Requests that an edit be applied to the module for edit and continue and updates
+// the right-side state and metadata.
+//
+// Arguments:
+// cbMetaData - number of bytes in pbMetaData
+// pbMetaData - a delta metadata blob describing the metadata edits to be made
+// cbIL - number of bytes in pbIL
+// pbIL - a new method body stream containing all of the method body information
+// (IL, EH info, etc) for edited and added methods.
+//
+// Return Value:
+// S_OK on success, various errors on failure
+//
+// Notes:
+//
+//
+// This applies the same changes to the RS's copy of the metadata that the left-side will apply to
+// it's copy of the metadata. see code:EditAndContinueModule::ApplyEditAndContinue
+//
+HRESULT CordbModule::ApplyChanges(ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#ifdef EnC_SUPPORTED
+ // We enable EnC back in code:CordbModule::SetJITCompilerFlags.
+ // If EnC isn't enabled, then we'll fail in the LS when we try to ApplyChanges.
+ // We'd expect a well-behaved debugger to never actually land here.
+
+
+ LOG((LF_CORDB,LL_INFO10000, "CP::AC: applying changes"));
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pbMetaData,
+ BYTE,
+ cbMetaData,
+ true,
+ true);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pbIL,
+ BYTE,
+ cbIL,
+ true,
+ true);
+
+ HRESULT hr;
+ RSExtSmartPtr<IUnknown> pUnk;
+ RSExtSmartPtr<IMDInternalImport> pMDImport;
+ RSExtSmartPtr<IMDInternalImport> pMDImport2;
+
+ //
+ // Edit was successful - update the right-side state to reflect the edit
+ //
+
+ ++m_EnCCount;
+
+ // apply the changes to our copy of the metadata
+
+ _ASSERTE(m_pIMImport != NULL); // must have metadata at this point in EnC
+ IfFailGo(m_pIMImport->QueryInterface(IID_IUnknown, (void**)&pUnk));
+
+ IfFailGo(GetMDInternalInterfaceFromPublic(pUnk, IID_IMDInternalImport,
+ (void **)&pMDImport));
+
+ // The left-side will call this same method on its copy of the metadata.
+ hr = pMDImport->ApplyEditAndContinue(pbMetaData, cbMetaData, &pMDImport2);
+ if (pMDImport2 != NULL)
+ {
+ // ApplyEditAndContinue() expects IMDInternalImport**, but we give it RSExtSmartPtr<IMDInternalImport>
+ // Silent cast of RSExtSmartPtr to IMDInternalImport* leads to assignment of a raw pointer
+ // without calling AddRef(), thus we need to do it manually.
+
+ // @todo - ApplyEditAndContinue should probably AddRef the out parameter.
+ pMDImport2->AddRef();
+ }
+ IfFailGo(hr);
+
+
+ // We're about to get a new importer object, so release the old one.
+ m_pIMImport.Clear();
+ IfFailGo(GetMDPublicInterfaceFromInternal(pMDImport2, IID_IMetaDataImport, (void **)&m_pIMImport));
+ // set the new RVA value
+
+ // Send the delta over to the debugee and request that it apply the edit
+ IfFailGo( ApplyChangesInternal(cbMetaData, pbMetaData, cbIL, pbIL) );
+
+ EX_TRY
+ {
+
+ m_pInternalMetaDataImport.Clear();
+ UpdateInternalMetaData();
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERTE(SUCCEEDED(hr));
+
+ErrExit:
+ // MetaData interface pointers will be automatically released via SmartPtr dtors.
+
+ // @todo : prevent further execution of program
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Requests that an edit be applied to the module for edit and continue and updates
+// some right-side state, but does not update our copy of the metadata.
+//
+// Arguments:
+// cbMetaData - number of bytes in pbMetaData
+// pbMetaData - a delta metadata blob describing the metadata edits to be made
+// cbIL - number of bytes in pbIL
+// pbIL - a new method body stream containing all of the method body information
+// (IL, EH info, etc) for edited and added methods.
+//
+// Return Value:
+// S_OK on success, various errors on failure
+//
+HRESULT CordbModule::ApplyChangesInternal(ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[])
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal\n"));
+
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+#ifdef EnC_SUPPORTED
+ HRESULT hr;
+
+ void * pRemoteBuf = NULL;
+
+ EX_TRY
+ {
+
+ // Create and initialize the event as synchronous
+ // We'll be sending a NULL appdomain pointer since the individual modules
+ // will contains pointers to their respective A.D.s
+ DebuggerIPCEvent event;
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_APPLY_CHANGES, false, VMPTR_AppDomain::NullPtr());
+
+ event.ApplyChanges.vmDomainFile = this->m_vmDomainFile;
+
+ // Have the left-side create a buffer for us to store the delta into
+ ULONG cbSize = cbMetaData+cbIL;
+ TargetBuffer tbFull = GetProcess()->GetRemoteBuffer(cbSize);
+ pRemoteBuf = CORDB_ADDRESS_TO_PTR(tbFull.pAddress);
+
+ TargetBuffer tbMetaData = tbFull.SubBuffer(0, cbMetaData); // 1st half
+ TargetBuffer tbIL = tbFull.SubBuffer(cbMetaData); // 2nd half
+
+ // Copy the delta metadata over to the debugee
+
+ GetProcess()->SafeWriteBuffer(tbMetaData, pbMetaData); // throws
+ GetProcess()->SafeWriteBuffer(tbIL, pbIL); // throws
+
+ // Send a synchronous event requesting the debugee apply the edit
+ event.ApplyChanges.pDeltaMetadata = tbMetaData.pAddress;
+ event.ApplyChanges.cbDeltaMetadata = tbMetaData.cbSize;
+ event.ApplyChanges.pDeltaIL = tbIL.pAddress;
+ event.ApplyChanges.cbDeltaIL = tbIL.cbSize;
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal sending event\n"));
+ hr = GetProcess()->SendIPCEvent(&event, sizeof(event));
+ hr = WORST_HR(hr, event.hr);
+ IfFailThrow(hr);
+
+ // Allocate space for the return event.
+ // We always copy over the whole buffer size which is bigger than sizeof(DebuggerIPCEvent)
+ // This seems ugly, in this case we know the exact size of the event we want to read
+ // why copy over all the extra data?
+ DebuggerIPCEvent *retEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ {
+ //
+ // Wait for events to return from the RC. We expect zero or more add field,
+ // add function or update function events and one completion event.
+ //
+ while (TRUE)
+ {
+ hr = GetProcess()->m_cordb->WaitForIPCEventFromProcess(GetProcess(),
+ GetAppDomain(),
+ retEvent);
+ IfFailThrow(hr);
+
+ if (retEvent->type == DB_IPCE_APPLY_CHANGES_RESULT)
+ {
+ // Done receiving update events
+ hr = retEvent->ApplyChangesResult.hr;
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC apply changes result %8.8x.\n", hr));
+ break;
+ }
+
+ _ASSERTE(retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FIELD);
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC %s %8.8x to version %d.\n",
+ GetCurrentThreadId(),
+ retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ? "Update function" :
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ? "Add function" : "Add field",
+ retEvent->EnCUpdate.memberMetadataToken, retEvent->EnCUpdate.newVersionNumber));
+
+ CordbAppDomain *pAppDomain = GetAppDomain();
+ _ASSERTE(NULL != pAppDomain);
+ CordbModule* pModule = NULL;
+
+
+ pModule = pAppDomain->LookupOrCreateModule(retEvent->EnCUpdate.vmDomainFile); // throws
+ _ASSERTE(pModule != NULL);
+
+ // update to the newest version
+
+ if (retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION)
+ {
+ // Update the function collection to reflect this edit
+ hr = pModule->UpdateFunction(retEvent->EnCUpdate.memberMetadataToken, retEvent->EnCUpdate.newVersionNumber, NULL);
+
+ }
+ // mark the class and relevant type as old so we update it next time we try to query it
+ if (retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FIELD)
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo synchronization - push this up
+ CordbClass* pClass = pModule->LookupClass(retEvent->EnCUpdate.classMetadataToken);
+ // if don't find class, that is fine because it hasn't been loaded yet so doesn't
+ // need to be updated
+ if (pClass)
+ {
+ pClass->MakeOld();
+ }
+ }
+ }
+ }
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal complete.\n"));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // process may have gone away by the time we get here so don't assume is there.
+ CordbProcess *pProcess = GetProcess();
+ if (pProcess)
+ {
+ HRESULT hr2 = pProcess->ReleaseRemoteBuffer(&pRemoteBuf);
+ TESTANDRETURNHR(hr2);
+ }
+ return hr;
+#else // EnC_SUPPORTED
+ return E_NOTIMPL;
+#endif // EnC_SUPPORTED
+
+}
+
+// Set the JMC status for the entire module.
+// All methods specified in others[] will have jmc status !fIsUserCode
+// All other methods will have jmc status fIsUserCode.
+HRESULT CordbModule::SetJMCStatus(
+ BOOL fIsUserCode,
+ ULONG32 cOthers,
+ mdToken others[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+ // @todo -allow the other parameters. These are functions that have default status
+ // opposite of fIsUserCode.
+ if (cOthers != 0)
+ {
+ _ASSERTE(!"not yet impl for cOthers != 0");
+ return E_NOTIMPL;
+ }
+
+ // Send event to the LS.
+ CordbProcess* pProcess = this->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+
+ // Tell the LS that this module is/is not user code
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_SET_MODULE_JMC_STATUS, true, this->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_vmDomainFile;
+ event.SetJMCFunctionStatus.dwStatus = fIsUserCode;
+
+
+ // Note: two-way event here...
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "CordbModule::SetJMCStatus failed 0x%08x...\n", hr));
+
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_SET_MODULE_JMC_STATUS_RESULT);
+
+ LOG((LF_CORDB, LL_INFO10, "returning from CordbModule::SetJMCStatus 0x%08x...\n", hr));
+
+ return event.hr;
+}
+
+
+//
+// Resolve an assembly given an AssemblyRef token. Note that
+// this will not trigger the loading of assembly. If assembly is not yet loaded,
+// this will return an CORDBG_E_CANNOT_RESOLVE_ASSEMBLY error
+//
+HRESULT CordbModule::ResolveAssembly(mdToken tkAssemblyRef,
+ ICorDebugAssembly **ppAssembly)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this->GetProcess());
+
+ if(ppAssembly)
+ {
+ *ppAssembly = NULL;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbAssembly *pCordbAsm = ResolveAssemblyInternal(tkAssemblyRef);
+ if (pCordbAsm == NULL)
+ {
+ // Don't throw here. It's a common-case failure path and not exceptional.
+ hr = CORDBG_E_CANNOT_RESOLVE_ASSEMBLY;
+ }
+ else if(ppAssembly)
+ {
+ _ASSERTE(pCordbAsm != NULL);
+ *ppAssembly = pCordbAsm;
+ pCordbAsm->ExternalAddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Worker to resolve an assembly ref.
+//
+// Arguments:
+// tkAssemblyRef - token of assembly ref to resolve
+//
+// Returns:
+// Assembly that this token resolves to.
+// NULL if it's a valid token but the assembly has not yet been resolved.
+// (This is a non-exceptional error case).
+//
+// Notes:
+// MetaData has tokens to represent a reference to another assembly.
+// But Loader/Fusion policy ultimately decides which specific assembly is actually loaded
+// for that token.
+// This does the lookup of actual assembly and reports back to the debugger.
+
+CordbAssembly * CordbModule::ResolveAssemblyInternal(mdToken tkAssemblyRef)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ if (TypeFromToken(tkAssemblyRef) != mdtAssemblyRef || tkAssemblyRef == mdAssemblyRefNil)
+ {
+ // Not a valid token
+ ThrowHR(E_INVALIDARG);
+ }
+
+ CordbAssembly * pAssembly = NULL;
+
+ if (!m_vmDomainFile.IsNull())
+ {
+ // Get DAC to do the real work to resolve the assembly
+ VMPTR_DomainAssembly vmDomainAssembly = GetProcess()->GetDAC()->ResolveAssembly(m_vmDomainFile, tkAssemblyRef);
+
+ // now find the ICorDebugAssembly corresponding to it
+ if (!vmDomainAssembly.IsNull() && m_pAppDomain != NULL)
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ // Don't throw here because if the lookup fails, we want to throw CORDBG_E_CANNOT_RESOLVE_ASSEMBLY.
+ pAssembly = m_pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+ }
+ }
+
+ return pAssembly;
+}
+
+//
+// CreateReaderForInMemorySymbols - create an ISymUnmanagedReader object for symbols
+// which are loaded into memory in the CLR. See interface definition in cordebug.idl for
+// details.
+//
+HRESULT CordbModule::CreateReaderForInMemorySymbols(REFIID riid, void** ppObj)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Get the symbol memory in a stream to give to the reader.
+ ReleaseHolder<IStream> pStream;
+ IDacDbiInterface::SymbolFormat symFormat = GetInMemorySymbolStream(&pStream);
+
+ // First create the symbol binder corresponding to the format of the stream
+ ReleaseHolder<ISymUnmanagedBinder> pBinder;
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+#ifndef FEATURE_PAL
+ // PDB format - use diasymreader.dll with COM activation
+ InlineSString<_MAX_PATH> ssBuf;
+ IfFailThrow(GetHModuleDirectory(GetModuleInst(), ssBuf));
+ IfFailThrow(FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS,
+ ssBuf.GetUnicode(),
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder,
+ NULL));
+#else
+ IfFailThrow(FakeCoCreateInstance(CLSID_CorSymBinder_SxS,
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder));
+#endif
+ }
+ else if (symFormat == IDacDbiInterface::kSymbolFormatILDB)
+ {
+ // ILDB format - use statically linked-in ildbsymlib
+ IfFailThrow(IldbSymbolsCreateInstance(CLSID_CorSymBinder_SxS,
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder));
+ }
+ else
+ {
+ // No in-memory symbols, return the appropriate error
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
+ if (m_fDynamic || m_fInMemory)
+ {
+ // This is indeed an in-memory or dynamic module, we just don't have any symbols for it.
+ // This means the application didn't supply any, or they are not yet available. Symbols
+ // first become available at LoadClass time for dynamic modules and UpdateModuleSymbols
+ // time for non-dynamic in-memory modules.
+ ThrowHR(CORDBG_E_SYMBOLS_NOT_AVAILABLE);
+ }
+
+ // This module is on disk - the debugger should use it's normal symbol-loading logic.
+ ThrowHR(CORDBG_E_MODULE_LOADED_FROM_DISK);
+ }
+
+ // In the attach or dump case, if we attach or take the dump after we have defined a dynamic module, we may
+ // have already set the symbol format to "PDB" by the time we call CreateReaderForInMemorySymbols during initialization
+ // for loaded modules. (In the launch case, we do this initialization when the module is actually loaded, and before we
+ // set the symbol format.) When we call CreateReaderForInMemorySymbols, we can't assume the initialization was already
+ // performed or specifically, that we already have m_pIMImport initialized. We can't call into diasymreader with a NULL
+ // pointer as the value for m_pIMImport, so we need to check that here.
+ if (m_pIMImport == NULL)
+ {
+ ThrowHR(CORDBG_E_SYMBOLS_NOT_AVAILABLE);
+ }
+
+ // Now create the symbol reader from the data
+ ReleaseHolder<ISymUnmanagedReader> pReader;
+ IfFailThrow(pBinder->GetReaderFromStream(m_pIMImport, pStream, &pReader));
+
+ // Attempt to return the interface requested
+ // Note that this does an AddRef for our return value ppObj, so we don't suppress the release
+ // of the pReader holder.
+ IfFailThrow(pReader->QueryInterface(riid, ppObj));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Class class
+ * ------------------------------------------------------------------------- */
+
+//---------------------------------------------------------------------------------------
+// Set the continue counter that marks when the module is in its Load event
+//
+// Notes:
+// Jit flags can only be changed in the real module Load event. We may
+// have multiple module load events on different threads coming at the
+// same time. So each module load tracks its continue counter.
+//
+// This can be used by code:CordbModule::EnsureModuleIsInLoadCallback to
+// properly return CORDBG_E_MUST_BE_IN_LOAD_MODULE
+void CordbModule::SetLoadEventContinueMarker()
+{
+ // Well behaved targets should only set this once.
+ GetProcess()->TargetConsistencyCheck(m_nLoadEventContinueCounter == 0);
+
+ m_nLoadEventContinueCounter = GetProcess()->m_continueCounter;
+}
+
+//---------------------------------------------------------------------------------------
+// Return CORDBG_E_MUST_BE_IN_LOAD_MODULE if the module is not in the load module callback.
+//
+// Notes:
+// The comparison is done via continue counters. The counter of the load
+// event is cached via code:CordbModule::SetLoadEventContinueMarker.
+//
+// This state is currently stored on the RS. Alternatively, it could likely be retreived from the LS state as
+// well. One disadvantage of the current model is that if we detach during the load-module callback and
+// then reattach, the RS state is flushed and we lose the fact that we can toggle the jit flags.
+HRESULT CordbModule::EnsureModuleIsInLoadCallback()
+{
+ if (this->m_nLoadEventContinueCounter < GetProcess()->m_continueCounter)
+ {
+ return CORDBG_E_MUST_BE_IN_LOAD_MODULE;
+ }
+ else
+ {
+ return S_OK;
+ }
+}
+
+// Implementation of ICorDebugModule2::SetJITCompilerFlags
+// See also code:CordbModule::EnableJITDebugging
+HRESULT CordbModule::SetJITCompilerFlags(DWORD dwFlags)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ CordbProcess *pProcess = GetProcess();
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // can't have a subset of these, eg 0x101, so make sure we have an exact match
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) &&
+ (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION) &&
+ (dwFlags != CORDEBUG_JIT_ENABLE_ENC))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ BOOL fAllowJitOpts = ((dwFlags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION);
+ BOOL fEnableEnC = ((dwFlags & CORDEBUG_JIT_ENABLE_ENC) == CORDEBUG_JIT_ENABLE_ENC);
+
+ // Can only change jit flags when module is first loaded and before there's any jitted code.
+ // This ensures all code in the module is jitted the same way.
+ hr = EnsureModuleIsInLoadCallback();
+
+ if (SUCCEEDED(hr))
+ {
+ // DD interface will check if it's a valid time to change the flags.
+ hr = pProcess->GetDAC()->SetCompilerFlags(GetRuntimeDomainFile(), fAllowJitOpts, fEnableEnC);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // emulate v2 hresults
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // Emulate Whidbey error hresults
+ hr = GetProcess()->GetShim()->FilterSetJitFlagsHresult(hr);
+ }
+ return hr;
+
+}
+
+// Implementation of ICorDebugModule2::GetJitCompilerFlags
+HRESULT CordbModule::GetJITCompilerFlags(DWORD *pdwFlags )
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD*);
+ *pdwFlags = CORDEBUG_JIT_DEFAULT;;
+
+ CordbProcess *pProcess = GetProcess();
+
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ BOOL fAllowJitOpts;
+ BOOL fEnableEnC;
+
+ pProcess->GetDAC()->GetCompilerFlags (
+ GetRuntimeDomainFile(),
+ &fAllowJitOpts,
+ &fEnableEnC);
+
+ if (fEnableEnC)
+ {
+ *pdwFlags = CORDEBUG_JIT_ENABLE_ENC;
+ }
+ else if (! fAllowJitOpts)
+ {
+ *pdwFlags = CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+BOOL CordbModule::IsWinMD()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ if (m_isIlWinMD == Uninitialized)
+ {
+ BOOL isWinRT;
+ HRESULT hr = E_FAIL;
+
+ {
+ RSLockHolder processLockHolder(GetProcess()->GetProcessLock());
+ hr = GetProcess()->GetDAC()->IsWinRTModule(m_vmModule, isWinRT);
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ ThrowHR(hr);
+
+ if (isWinRT)
+ m_isIlWinMD = True;
+ else
+ m_isIlWinMD = False;
+ }
+
+ return m_isIlWinMD == True;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CordbCode class
+ * ------------------------------------------------------------------------- */
+//-----------------------------------------------------------------------------
+// CordbCode constructor
+// Arguments:
+// Input:
+// pFunction - CordbFunction instance for this function
+// encVersion - Edit and Continue version number for this code chunk
+// fIsIL - indicates whether the instance is a CordbILCode (as
+// opposed to a CordbNativeCode)
+// id - This is the hashtable key for CordbCode objects
+// - for native code, the code start address
+// - for IL code, 0
+// - for ReJit IL code, the remote pointer to the ReJitSharedInfo
+// Output:
+// fields of the CordbCode instance have been initialized
+//-----------------------------------------------------------------------------
+
+CordbCode::CordbCode(CordbFunction * pFunction, UINT_PTR id, SIZE_T encVersion, BOOL fIsIL)
+ : CordbBase(pFunction->GetProcess(), id, enumCordbCode),
+ m_fIsIL(fIsIL),
+ m_nVersion(encVersion),
+ m_rgbCode(NULL),
+ m_continueCounterLastSync(0),
+ m_pFunction(pFunction)
+{
+ _ASSERTE(pFunction != NULL);
+ _ASSERTE(m_nVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+} // CordbCode::CordbCode
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbCode object
+//-----------------------------------------------------------------------------
+CordbCode::~CordbCode()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbFunction
+// See CordbBase::Neuter for neuter semantics.
+//-----------------------------------------------------------------------------
+void CordbCode::Neuter()
+{
+ m_pFunction = NULL;
+
+ delete [] m_rgbCode;
+ m_rgbCode = NULL;
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugCode)
+ {
+ *pInterface = static_cast<ICorDebugCode*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugCode *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NOT IMPLEMENTED. Remap sequence points are entirely private to the LS,
+// and ICorDebug will dispatch a RemapOpportunity callback to notify the
+// debugger instead of letting the debugger query for the points.
+//
+// Returns: E_NOTIMPL
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetEnCRemapSequencePoints(ULONG32 cMap, ULONG32 * pcMap, ULONG32 offsets[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32*);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(offsets, ULONG32*, cMap, true, true);
+
+ //
+ // Old EnC interface - deprecated
+ //
+ return E_NOTIMPL;
+} // CordbCode::GetEnCRemapSequencePoints
+
+
+//-----------------------------------------------------------------------------
+// CordbCode::IsIL
+// Public method to determine if this Code object represents IL or native code.
+//
+// Parameters:
+// pbIL - OUT: on return, set to True if IL code, else False.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::IsIL(BOOL *pbIL)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbIL, BOOL *);
+
+ *pbIL = IsIL();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetFunction
+// Public method to get the Function object associated with this Code object.
+// Function:Code = 1:1 for IL, and 1:n for Native. So there is always a single
+// unique Function object to return.
+//
+// Parameters:
+// ppFunction - OUT: returns the Function object for this Code.
+//
+// Returns:
+// S_OK - on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetFunction(ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ *ppFunction = static_cast<ICorDebugFunction*> (m_pFunction);
+ m_pFunction->ExternalAddRef();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetSize
+// Get the size of the code in bytes. If this is IL code, it will be bytes of IL.
+// If this is native code, it will be bytes of native code.
+//
+// Parameters:
+// pcBytes - OUT: on return, set to the size of the code in bytes.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetSize(ULONG32 *pcBytes)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcBytes, ULONG32 *);
+
+ *pcBytes = GetSize();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::CreateBreakpoint
+// public method to create a breakpoint in the code.
+//
+// Parameters:
+// offset - offset in bytes to set the breakpoint at. If this is a Native
+// code object (IsIl == false), then units are bytes of native code. If
+// this is an IL code object, then units are bytes of IL code.
+// ppBreakpoint- out-parameter to hold newly created breakpoint object.
+//
+// Return value:
+// S_OK iff *ppBreakpoint is set. Else some error.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::CreateBreakpoint(ULONG32 offset,
+ ICorDebugFunctionBreakpoint **ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugFunctionBreakpoint **);
+
+ HRESULT hr;
+ ULONG32 size = GetSize();
+ LOG((LF_CORDB, LL_INFO10000, "CCode::CreateBreakpoint, offset=%d, size=%d, IsIl=%d, this=0x%p\n",
+ offset, size, m_fIsIL, this));
+
+ // Make sure the offset is within range of the method.
+ // If we're native code, then both offset & total code size are bytes of native code,
+ // else they're both bytes of IL.
+ if (offset >= size)
+ {
+ return CORDBG_E_UNABLE_TO_SET_BREAKPOINT;
+ }
+
+ CordbFunctionBreakpoint *bp = new (nothrow) CordbFunctionBreakpoint(this, offset);
+
+ if (bp == NULL)
+ return E_OUTOFMEMORY;
+
+ hr = bp->Activate(TRUE);
+ if (SUCCEEDED(hr))
+ {
+ *ppBreakpoint = static_cast<ICorDebugFunctionBreakpoint*> (bp);
+ bp->ExternalAddRef();
+ return S_OK;
+ }
+ else
+ {
+ delete bp;
+ return hr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetCode
+// Public method to get the code-bytes for this Code object. For an IL-code
+// object, this will be bytes of IL. For a native-code object, this will be
+// bytes of native opcodes.
+// The units of the offsets are the same as the units on the CordbCode object.
+// (eg, IL offsets for an IL code object, and native offsets for a native code object)
+// This will glue together hot + cold regions into a single blob.
+//
+// Units are also logical (aka linear) values, which
+// Parameters:
+// startOffset - linear offset in Code to start copying from.
+// endOffset - linear offset in Code to end copying from. Total bytes copied would be (endOffset - startOffset)
+// cBufferAlloc - number of bytes in the buffer supplied by the buffer[] parameter.
+// buffer - caller allocated storage to copy bytes into.
+// pcBufferSize - required out-parameter, holds number of bytes copied into buffer.
+//
+// Returns:
+// S_OK if copy successful. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetCode(ULONG32 startOffset,
+ ULONG32 endOffset,
+ ULONG32 cBufferAlloc,
+ BYTE buffer[],
+ ULONG32 *pcBufferSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, cBufferAlloc, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pcBufferSize, ULONG32 *);
+
+ LOG((LF_CORDB,LL_EVERYTHING, "CC::GC: for token:0x%x\n", m_pFunction->GetMetadataToken()));
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ *pcBufferSize = 0;
+
+ // Check ranges.
+ ULONG32 totalSize = GetSize();
+
+ if (cBufferAlloc < endOffset - startOffset)
+ endOffset = startOffset + cBufferAlloc;
+
+ if (endOffset > totalSize)
+ endOffset = totalSize;
+
+ if (startOffset > totalSize)
+ startOffset = totalSize;
+
+ // Check the continue counter since WriteMemory bumps it up.
+ if ((m_rgbCode == NULL) ||
+ (m_continueCounterLastSync < GetProcess()->m_continueCounter))
+ {
+ ReadCodeBytes();
+ m_continueCounterLastSync = GetProcess()->m_continueCounter;
+ }
+
+ // if we just got the code, we'll have to copy it over
+ if (*pcBufferSize == 0 && m_rgbCode != NULL)
+ {
+ memcpy(buffer,
+ m_rgbCode+startOffset,
+ endOffset - startOffset);
+ *pcBufferSize = endOffset - startOffset;
+ }
+ return hr;
+
+} // CordbCode::GetCode
+
+#include "dbgipcevents.h"
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetVersionNumber
+// Public method to get the EnC version number of the code.
+//
+// Parameters:
+// nVersion - OUT: on return, set to the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetVersionNumber( ULONG32 *nVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(nVersion, ULONG32 *);
+
+ LOG((LF_CORDB,LL_INFO10000,"R:CC:GVN:Returning 0x%x "
+ "as version\n",m_nVersion));
+
+ *nVersion = (ULONG32)m_nVersion;
+
+#ifndef EnC_SUPPORTED
+ _ASSERTE(*nVersion == 1);
+#endif // EnC_SUPPORTED
+
+ return S_OK;
+}
+
+// get the CordbFunction instance for this code object
+CordbFunction * CordbCode::GetFunction()
+{
+ _ASSERTE(m_pFunction != NULL);
+ return m_pFunction;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CordbILCode class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------------------------
+// CordbILCode ctor to make IL code.
+// Arguments:
+// Input:
+// pFunction - pointer to the CordbFunction instance for this function
+// codeRegionInfo - starting address and size in bytes of IL code blob
+// nVersion - EnC version number for this IL code blob
+// localVarSigToken - LocalVarSig for this IL blob
+// id - the key when using ILCode in a CordbHashTable
+// Output:
+// fields of this instance of CordbILCode have been initialized
+//-----------------------------------------------------------------------------
+CordbILCode::CordbILCode(CordbFunction * pFunction,
+ TargetBuffer codeRegionInfo,
+ SIZE_T nVersion,
+ mdSignature localVarSigToken,
+ UINT_PTR id)
+ : CordbCode(pFunction, id, nVersion, TRUE),
+#ifdef EnC_SUPPORTED
+ m_fIsOld(FALSE),
+#endif
+ m_codeRegionInfo(codeRegionInfo),
+ m_localVarSigToken(localVarSigToken)
+{
+} // CordbILCode::CordbILCode
+
+
+#ifdef EnC_SUPPORTED
+//-----------------------------------------------------------------------------
+// CordbILCode::MakeOld
+// Internal method to perform any cleanup necessary when a code blob is no longer
+// the most current.
+//-----------------------------------------------------------------------------
+void CordbILCode::MakeOld()
+{
+ m_fIsOld = TRUE;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetAddress
+// Public method to get the Entry address for the code. This is the address
+// where the method first starts executing.
+//
+// Parameters:
+// pStart - out-parameter to hold start address.
+//
+// Returns:
+// S_OK if *pStart is properly updated.
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::GetAddress(CORDB_ADDRESS * pStart)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pStart, CORDB_ADDRESS *);
+
+
+ _ASSERTE(this != NULL);
+ _ASSERTE(this->GetFunction() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule()->GetProcess() == GetProcess());
+
+ *pStart = (m_codeRegionInfo.pAddress);
+
+ return S_OK;
+} // CordbILCode::GetAddress
+
+//-----------------------------------------------------------------------------
+// CordbILCode::ReadCodeBytes
+// Reads the actual bytes of IL code into the data member m_rgbCode
+// Arguments:
+// none (uses data members)
+// Return value:
+// standard HRESULT values
+// also allocates and initializes m_rgbCode
+// Notes: assumes that the caller has checked to ensure that m_rgbCode doesn't
+// hold valid data
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::ReadCodeBytes()
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // We have an address & size, so we'll just call ReadMemory.
+ // This will conveniently strip out any patches too.
+ CORDB_ADDRESS pStart = m_codeRegionInfo.pAddress;
+ ULONG32 cbSize = (ULONG32) m_codeRegionInfo.cbSize;
+
+ delete [] m_rgbCode;
+ m_rgbCode = new BYTE[cbSize]; // throws
+
+ SIZE_T cbRead;
+ hr = GetProcess()->ReadMemory(pStart, cbSize, m_rgbCode, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbSize);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbILCode::ReadCodeBytes
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetILToNativeMapping
+// Public method (implements ICorDebugCode) to get the IL-->{ Native Start, Native End} mapping.
+// Since 1 CordbILCode can map to multiple CordbNativeCode due to generics, we cannot reliably return the
+// mapping information in all cases. So we always fail with CORDBG_E_NON_NATIVE_FRAME. The caller should
+// call code:CordbNativeCode::GetILToNativeMapping instead.
+//
+// Parameters:
+// cMap - size of incoming map[] array (in elements).
+// pcMap - OUT: full size of IL-->Native map (in elements).
+// map - caller allocated array to be filled in.
+//
+// Returns:
+// CORDBG_E_NON_NATIVE_FRAME in all cases
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_DEBUG_IL_TO_NATIVE_MAP *, cMap, true, true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CORDBG_E_NON_NATIVE_FRAME;
+} // CordbILCode::GetILToNativeMapping
+
+
+/*
+* CordbILCode::GetLocalVarSig
+*
+* Get the method's local variable metadata signature. This may be cached, but for dynamic modules we'll always
+* read it from the metadata. This function also returns the count of local variables in the method.
+*
+* Parameters:
+* pLocalSigParser - OUT: the local variable signature for the method.
+* pLocalCount - OUT: the number of locals the method has.
+*
+* Returns:
+* HRESULT for success or failure.
+*
+*/
+HRESULT CordbILCode::GetLocalVarSig(SigParser *pLocalSigParser,
+ ULONG *pLocalVarCount)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CONTRACTL // @dbgtodo exceptions - convert to throws...
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ FAIL_IF_NEUTERED(this);
+ HRESULT hr = S_OK;
+
+ // A function will not have a local var sig if it has no locals!
+ if (m_localVarSigToken != mdSignatureNil)
+ {
+ PCCOR_SIGNATURE localSignature;
+ ULONG size;
+ ULONG localCount;
+
+ EX_TRY // // @dbgtodo exceptions - push this up
+ {
+ GetFunction()->GetModule()->UpdateMetaDataCacheIfNeeded(m_localVarSigToken);
+ hr = GetFunction()->GetModule()->GetMetaDataImporter()->GetSigFromToken(m_localVarSigToken,
+ &localSignature,
+ &size);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_WARNING, "CICF::GLVS caught hr=0x%x\n", hr));
+ }
+ IfFailRet(hr);
+
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS creating sig parser sig=0x%x size=0x%x\n", localSignature, size));
+ SigParser sigParser = SigParser(localSignature, size);
+
+ ULONG data;
+
+ IfFailRet(sigParser.GetCallingConvInfo(&data));
+
+ _ASSERTE(data == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG);
+
+ // Snagg the count of locals in the sig.
+ IfFailRet(sigParser.GetData(&localCount));
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS localCount=0x%x\n", localCount));
+ if (pLocalSigParser != NULL)
+ {
+ *pLocalSigParser = sigParser;
+ }
+ if (pLocalVarCount != NULL)
+ {
+ *pLocalVarCount = localCount;
+ }
+ }
+ else
+ {
+ //
+ // Signature is Nil, so fill in everything with NULLs and zeros
+ //
+ if (pLocalSigParser != NULL)
+ {
+ *pLocalSigParser = SigParser(NULL, 0);
+ }
+
+ if (pLocalVarCount != NULL)
+ {
+ *pLocalVarCount = 0;
+ }
+ }
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS returning hr=0x%x\n", hr));
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetLocalVariableType
+// Internal method. Return the type of an IL local, specified by 0-based index.
+//
+// Parameters:
+// dwIndex - 0-based index for IL local number.
+// inst - instantiation information if this is a generic function. Eg,
+// if function is List<T>, inst describes T.
+// res - out parameter, yields to CordbType of the local.
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbILCode::GetLocalVariableType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT dwIndex=0x%x pInst=0x%p\n", dwIndex, pInst));
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Get the local variable signature.
+ SigParser sigParser;
+ ULONG cLocals;
+
+ IfFailThrow(GetLocalVarSig(&sigParser, &cLocals));
+
+ // Check the index.
+ if (dwIndex >= cLocals)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Run the signature and find the required argument.
+ for (unsigned int i = 0; i < dwIndex; i++)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT scanning index 0x%x\n", dwIndex));
+ IfFailThrow(sigParser.SkipExactlyOne());
+ }
+
+ hr = CordbType::SigToType(GetFunction()->GetModule(), &sigParser, pInst, ppResultType);
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT CT::SigToType returned hr=0x%x\n", hr));
+ IfFailThrow(hr);
+
+ } EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+mdSignature CordbILCode::GetLocalVarSigToken()
+{
+ return m_localVarSigToken;
+}
+
+CordbReJitILCode::CordbReJitILCode(CordbFunction *pFunction, SIZE_T encVersion, VMPTR_SharedReJitInfo vmSharedReJitInfo) :
+CordbILCode(pFunction, TargetBuffer(), encVersion, mdSignatureNil, VmPtrToCookie(vmSharedReJitInfo)),
+m_cClauses(0),
+m_cbLocalIL(0),
+m_cILMap(0)
+{
+ _ASSERTE(!vmSharedReJitInfo.IsNull());
+ DacSharedReJitInfo data = { 0 };
+ IfFailThrow(GetProcess()->GetDAC()->GetSharedReJitInfoData(vmSharedReJitInfo, &data));
+ IfFailThrow(Init(&data));
+}
+
+//-----------------------------------------------------------------------------
+// CordbReJitILCode::Init
+//
+// Returns:
+// S_OK if all fields are inited. Else error.
+HRESULT CordbReJitILCode::Init(DacSharedReJitInfo* pSharedReJitInfo)
+{
+ HRESULT hr = S_OK;
+
+ // Instrumented IL map
+ if (pSharedReJitInfo->m_cInstrumentedMapEntries)
+ {
+ if (pSharedReJitInfo->m_cInstrumentedMapEntries > 100000)
+ return CORDBG_E_TARGET_INCONSISTENT;
+ m_cILMap = pSharedReJitInfo->m_cInstrumentedMapEntries;
+ m_pILMap = new (nothrow)COR_IL_MAP[m_cILMap];
+ TargetBuffer mapBuffer(pSharedReJitInfo->m_rgInstrumentedMapEntries, m_cILMap*sizeof(COR_IL_MAP));
+ IfFailRet(GetProcess()->SafeReadBuffer(mapBuffer, (BYTE*)m_pILMap.GetValue(), FALSE /* bThrowOnError */));
+ }
+
+ // Read the method's IL header
+ CORDB_ADDRESS pIlHeader = pSharedReJitInfo->m_pbIL;
+ IMAGE_COR_ILMETHOD_FAT header = { 0 };
+ bool headerMustBeTiny = false;
+ ULONG32 headerSize = 0;
+ hr = GetProcess()->SafeReadStruct(pIlHeader, &header);
+ if (hr != S_OK)
+ {
+ // Its possible the header is tiny and there isn't enough memory to read a complete
+ // FAT header
+ headerMustBeTiny = true;
+ IfFailRet(GetProcess()->SafeReadStruct(pIlHeader, (IMAGE_COR_ILMETHOD_TINY *)&header));
+ }
+
+ // Read the ILCodeSize and LocalVarSigTok from header
+ ULONG32 ilCodeSize = 0;
+ IMAGE_COR_ILMETHOD_TINY *pMethodTinyHeader = (IMAGE_COR_ILMETHOD_TINY *)&header;
+ bool isTinyHeader = ((pMethodTinyHeader->Flags_CodeSize & (CorILMethod_FormatMask >> 1)) == CorILMethod_TinyFormat);
+ if (isTinyHeader)
+ {
+ ilCodeSize = (((unsigned)pMethodTinyHeader->Flags_CodeSize) >> (CorILMethod_FormatShift - 1));
+ headerSize = sizeof(IMAGE_COR_ILMETHOD_TINY);
+ m_localVarSigToken = mdSignatureNil;
+ }
+ else if (headerMustBeTiny)
+ {
+ // header was not CorILMethod_TinyFormat
+ // this is not possible, must be an error when reading from data target
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+ else
+ {
+ ilCodeSize = header.CodeSize;
+ headerSize = header.Size * 4;
+ m_localVarSigToken = header.LocalVarSigTok;
+ }
+ if (ilCodeSize == 0 || ilCodeSize > 100000)
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ m_codeRegionInfo.Init(pIlHeader + headerSize, ilCodeSize);
+ m_pLocalIL = new (nothrow) BYTE[ilCodeSize];
+ if (m_pLocalIL == NULL)
+ return E_OUTOFMEMORY;
+ m_cbLocalIL = ilCodeSize;
+ IfFailRet(GetProcess()->SafeReadBuffer(m_codeRegionInfo, m_pLocalIL, FALSE /*throwOnError*/));
+
+ // Check if this il code has exception clauses
+ if ((pMethodTinyHeader->Flags_CodeSize & CorILMethod_MoreSects) == 0)
+ {
+ return S_OK; // no EH, done initing
+ }
+
+ // EH section starts at the 4 byte aligned address after the code
+ CORDB_ADDRESS ehClauseHeader = ((pIlHeader + headerSize + ilCodeSize - 1) & ~3) + 4;
+ BYTE kind = 0;
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &kind));
+ if ((kind & CorILMethod_Sect_KindMask) != CorILMethod_Sect_EHTable)
+ {
+ return S_OK;
+ }
+ if (kind & CorILMethod_Sect_FatFormat)
+ {
+ // Read the section header to see how many clauses there are
+ IMAGE_COR_ILMETHOD_SECT_FAT sectionHeader = { 0 };
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &sectionHeader));
+ m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT);
+ if (m_cClauses > 10000) // sanity check the data before allocating
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ // Read in the clauses
+ TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_FAT), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT));
+ NewArrayHolder<IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT> pClauses = new (nothrow)IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT[m_cClauses];
+ if (pClauses == NULL)
+ return E_OUTOFMEMORY;
+ IfFailRet(GetProcess()->SafeReadBuffer(buffer, (BYTE*)pClauses.GetValue(), FALSE /*throwOnError*/));
+
+ // convert clauses
+ m_pClauses = new (nothrow)CorDebugEHClause[m_cClauses];
+ if (m_pClauses == NULL)
+ return E_OUTOFMEMORY;
+ for (ULONG32 i = 0; i < m_cClauses; i++)
+ {
+ BOOL isFilter = ((pClauses[i].Flags & COR_ILEXCEPTION_CLAUSE_FILTER) != 0);
+ m_pClauses[i].Flags = pClauses[i].Flags;
+ m_pClauses[i].TryOffset = pClauses[i].TryOffset;
+ m_pClauses[i].TryLength = pClauses[i].TryLength;
+ m_pClauses[i].HandlerOffset = pClauses[i].HandlerOffset;
+ m_pClauses[i].HandlerLength = pClauses[i].HandlerLength;
+ // these two fields are a union in the image, but are seperate in the struct ICorDebug returns
+ m_pClauses[i].ClassToken = isFilter ? 0 : pClauses[i].ClassToken;
+ m_pClauses[i].FilterOffset = isFilter ? pClauses[i].FilterOffset : 0;
+ }
+ }
+ else
+ {
+ // Read in the section header to see how many small clauses there are
+ IMAGE_COR_ILMETHOD_SECT_SMALL sectionHeader = { 0 };
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &sectionHeader));
+ ULONG32 m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL);
+ if (m_cClauses > 10000) // sanity check the data before allocating
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ // Read in the clauses
+ TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL));
+ NewArrayHolder<IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL> pClauses = new (nothrow)IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL[m_cClauses];
+ if (pClauses == NULL)
+ return E_OUTOFMEMORY;
+ IfFailRet(GetProcess()->SafeReadBuffer(buffer, (BYTE*)pClauses.GetValue(), FALSE /*throwOnError*/));
+
+ // convert clauses
+ m_pClauses = new (nothrow)CorDebugEHClause[m_cClauses];
+ if (m_pClauses == NULL)
+ return E_OUTOFMEMORY;
+ for (ULONG32 i = 0; i < m_cClauses; i++)
+ {
+ BOOL isFilter = ((pClauses[i].Flags & COR_ILEXCEPTION_CLAUSE_FILTER) != 0);
+ m_pClauses[i].Flags = pClauses[i].Flags;
+ m_pClauses[i].TryOffset = pClauses[i].TryOffset;
+ m_pClauses[i].TryLength = pClauses[i].TryLength;
+ m_pClauses[i].HandlerOffset = pClauses[i].HandlerOffset;
+ m_pClauses[i].HandlerLength = pClauses[i].HandlerLength;
+ // these two fields are a union in the image, but are seperate in the struct ICorDebug returns
+ m_pClauses[i].ClassToken = isFilter ? 0 : pClauses[i].ClassToken;
+ m_pClauses[i].FilterOffset = isFilter ? pClauses[i].FilterOffset : 0;
+ }
+ }
+ return S_OK;
+}
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbReJitILCode::GetEHClauses
+// Public method to get the EH clauses for IL code
+//
+// Parameters:
+// cClauses - size of incoming clauses array (in elements).
+// pcClauses - OUT param: cClauses>0 -> the number of elements written to in the clauses array.
+// cClauses=0 -> the number of EH clauses this IL code has
+// clauses - caller allocated storage to hold the EH clauses.
+//
+// Returns:
+// S_OK if successfully copied elements to clauses array.
+HRESULT CordbReJitILCode::GetEHClauses(ULONG32 cClauses, ULONG32 * pcClauses, CorDebugEHClause clauses[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcClauses, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(clauses, CorDebugEHClause *, cClauses, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cClauses != 0 && clauses == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pcClauses != NULL)
+ {
+ if (cClauses == 0)
+ {
+ *pcClauses = m_cClauses;
+ }
+ else
+ {
+ *pcClauses = MIN(cClauses, m_cClauses);
+ }
+ }
+
+ if (clauses != NULL)
+ {
+ memcpy_s(clauses, sizeof(CorDebugEHClause)*cClauses, m_pClauses, sizeof(CorDebugEHClause)*MIN(cClauses, m_cClauses));
+ }
+ return S_OK;
+}
+
+ULONG CordbReJitILCode::AddRef()
+{
+ return CordbCode::AddRef();
+}
+ULONG CordbReJitILCode::Release()
+{
+ return CordbCode::Release();
+}
+
+HRESULT CordbReJitILCode::QueryInterface(REFIID riid, void** ppInterface)
+{
+ if (riid == IID_ICorDebugILCode)
+ {
+ *ppInterface = static_cast<ICorDebugILCode*>(this);
+ }
+ else if (riid == IID_ICorDebugILCode2)
+ {
+ *ppInterface = static_cast<ICorDebugILCode2*>(this);
+ }
+ else
+ {
+ return CordbILCode::QueryInterface(riid, ppInterface);
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT CordbReJitILCode::GetLocalVarSigToken(mdSignature *pmdSig)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pmdSig, mdSignature *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pmdSig = m_localVarSigToken;
+ return S_OK;
+}
+
+HRESULT CordbReJitILCode::GetInstrumentedILMap(ULONG32 cMap, ULONG32 *pcMap, COR_IL_MAP map[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcClauses, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_IL_MAP *, cMap, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cMap != 0 && map == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pcMap != NULL)
+ {
+ if (cMap == 0)
+ {
+ *pcMap = m_cILMap;
+ }
+ else
+ {
+ *pcMap = MIN(cMap, m_cILMap);
+ }
+ }
+
+ if (map != NULL)
+ {
+ memcpy_s(map, sizeof(COR_IL_MAP)*cMap, m_pILMap, sizeof(COR_IL_MAP)*MIN(cMap, m_cILMap));
+ }
+ return S_OK;
+}
+
+// FindNativeInfoInILVariableArray
+// Linear search through an array of NativeVarInfos, to find the variable of index dwIndex, valid
+// at the given ip. Returns CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't valid at the given ip.
+// Arguments:
+// input: dwIndex - variable number
+// ip - IP
+// nativeInfoList - list of instances of NativeVarInfo
+// output: ppNativeInfo - the element of nativeInfoList that corresponds to the IP and variable number
+// if we find such an element or NULL otherwise
+// Return value: HRESULT: returns S_OK or CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't found
+//
+HRESULT FindNativeInfoInILVariableArray(DWORD dwIndex,
+ SIZE_T ip,
+ const DacDbiArrayList<ICorDebugInfo::NativeVarInfo> * nativeInfoList,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ _ASSERTE(ppNativeInfo != NULL);
+ *ppNativeInfo = NULL;
+
+ // A few words about this search: it must be linear, and the
+ // comparison of startOffset and endOffset to ip must be
+ // <=/>. startOffset points to the first instruction that will
+ // make the variable's home valid. endOffset points to the first
+ // instruction at which the variable's home invalid.
+ int lastGoodOne = -1;
+ for (unsigned int i = 0; i < (unsigned)nativeInfoList->Count(); i++)
+ {
+ if ((*nativeInfoList)[i].varNumber == dwIndex)
+ {
+ if ( (lastGoodOne == -1) ||
+ ((*nativeInfoList)[lastGoodOne].startOffset < (*nativeInfoList)[i].startOffset) )
+ {
+ lastGoodOne = i;
+ }
+
+ if (((*nativeInfoList)[i].startOffset <= ip) &&
+ ((*nativeInfoList)[i].endOffset > ip))
+ {
+ *ppNativeInfo = &((*nativeInfoList)[i]);
+
+ return S_OK;
+ }
+ }
+ }
+
+ // workaround:
+ //
+ // We didn't find the variable. Was the endOffset of the last range for this variable
+ // equal to the current IP? If so, go ahead and "lie" and report that as the
+ // variable's home for now.
+ //
+ // Rationale:
+ //
+ // * See TODO comment in code:Compiler::siUpdate (jit\scopeinfo.cpp). In optimized
+ // code, the JIT can report var lifetimes as being one instruction too short.
+ // This workaround makes up for that. Example code:
+ //
+ // static void foo(int x)
+ // {
+ // int b = x; // Value of "x" would not be reported in optimized code without the workaround
+ // bar(ref b);
+ // }
+ //
+ // * Since this is the first instruction after the last range a variable was alive,
+ // we're essentially assuming that since that instruction hasn't been executed
+ // yet, and since there isn't a new home for the variable, that the last home is
+ // still good. This actually turns out to be true 99.9% of the time, so we'll go
+ // with it for now.
+ // * We've been lying like this since 1999, so surely it's safe.
+ if ((lastGoodOne > -1) && ((*nativeInfoList)[lastGoodOne].endOffset == ip))
+ {
+ *ppNativeInfo = &((*nativeInfoList)[lastGoodOne]);
+ return S_OK;
+ }
+
+ return CORDBG_E_IL_VAR_NOT_AVAILABLE;
+} // FindNativeInfoInILVariableArray
+
+//* ------------------------------------------------------------------------- *
+// * Native Code class
+// * ------------------------------------------------------------------------- */
+
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode ctor to make Native code.
+// Arguments:
+// Input:
+// pFunction - the function for which this is the native code object
+// pJitData - the information about this code object retrieved from the DAC
+// fIsInstantiatedGeneric - indicates whether this code object is an instantiated
+// generic
+// Output:
+// fields of this instance of CordbNativeCode have been initialized
+//-----------------------------------------------------------------------------
+CordbNativeCode::CordbNativeCode(CordbFunction * pFunction,
+ const NativeCodeFunctionData * pJitData,
+ BOOL fIsInstantiatedGeneric)
+ : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, pJitData->encVersion, FALSE),
+ m_vmNativeCodeMethodDescToken(pJitData->vmNativeCodeMethodDescToken),
+ m_fCodeAvailable(TRUE),
+ m_fIsInstantiatedGeneric(fIsInstantiatedGeneric != FALSE)
+{
+ _ASSERTE(GetVersion() >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+
+ for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region)
+ {
+ m_rgCodeRegions[region] = pJitData->m_rgCodeRegions[region];
+ }
+} //CordbNativeCode::CordbNativeCode
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugCode)
+ {
+ *pInterface = static_cast<ICorDebugCode *>(this);
+ }
+ else if (id == IID_ICorDebugCode2)
+ {
+ *pInterface = static_cast<ICorDebugCode2 *>(this);
+ }
+ else if (id == IID_ICorDebugCode3)
+ {
+ *pInterface = static_cast<ICorDebugCode3 *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugCode *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetAddress
+// Public method to get the Entry address for the code. This is the address
+// where the method first starts executing.
+//
+// Parameters:
+// pStart - out-parameter to hold start address.
+//
+// Returns:
+// S_OK if *pStart is properly updated.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetAddress(CORDB_ADDRESS * pStart)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pStart, CORDB_ADDRESS *);
+
+
+ _ASSERTE(this != NULL);
+ _ASSERTE(this->GetFunction() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule()->GetProcess() == GetProcess());
+
+ // Since we don't do code-pitching, the address points directly to the code.
+ *pStart = (m_rgCodeRegions[kHot].pAddress);
+
+ if (*pStart == NULL)
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ return S_OK;
+} // CordbNativeCode::GetAddress
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::ReadCodeBytes
+// Reads the actual bytes of native code from both the hot and cold regions
+// into the data member m_rgbCode
+// Arguments:
+// none (uses data members)
+// Return value:
+// standard HRESULT values
+// also allocates and initializes m_rgbCode
+// Notes: assumes that the caller has checked to ensure that m_rgbCode doesn't
+// hold valid data
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::ReadCodeBytes()
+{
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // We have an address & size, so we'll just call ReadMemory.
+ // This will conveniently strip out any patches too.
+ CORDB_ADDRESS pHotStart = m_rgCodeRegions[kHot].pAddress;
+ CORDB_ADDRESS pColdStart = m_rgCodeRegions[kCold].pAddress;
+ ULONG32 cbHotSize = (ULONG32) m_rgCodeRegions[kHot].cbSize;
+ ULONG32 cbColdSize = GetColdSize();
+
+ delete [] m_rgbCode;
+ m_rgbCode = new BYTE[cbHotSize + cbColdSize];
+
+ SIZE_T cbRead;
+ hr = GetProcess()->ReadMemory(pHotStart, cbHotSize, m_rgbCode, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbHotSize);
+
+ if (HasColdRegion())
+ {
+ hr = GetProcess()->ReadMemory(pColdStart, cbColdSize, (BYTE *) m_rgbCode + cbHotSize, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbColdSize);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbNativeCode::ReadCodeBytes
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetColdSize
+// Get the size of the cold regions in bytes.
+//
+// Parameters:
+// none--uses data member m_rgCodeRegions to compute total size.
+//
+// Returns:
+// the size of the code in bytes.
+//-----------------------------------------------------------------------------
+ULONG32 CordbNativeCode::GetColdSize()
+{
+ ULONG32 pcBytes = 0;
+ for (CodeBlobRegion index = kCold; index < MAX_REGIONS; ++index)
+ {
+ pcBytes += m_rgCodeRegions[index].cbSize;
+ }
+ return pcBytes;
+} // CordbNativeCode::GetColdSize
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetSize
+// Get the size of the code in bytes.
+//
+// Parameters:
+// none--uses data member m_rgCodeRegions to compute total size.
+//
+// Returns:
+// the size of the code in bytes.
+//-----------------------------------------------------------------------------
+ULONG32 CordbNativeCode::GetSize()
+{
+ ULONG32 pcBytes = 0;
+ for (CodeBlobRegion index = kHot; index < MAX_REGIONS; ++index)
+ {
+ pcBytes += m_rgCodeRegions[index].cbSize;
+ }
+ return pcBytes;
+} // CordbNativeCode::GetSize
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetILToNativeMapping
+// Public method (implements ICorDebugCode) to get the IL-->{ Native Start, Native End} mapping.
+// This can only be retrieved for native code.
+// This will copy as much of the map as can fit in the incoming buffer.
+//
+// Parameters:
+// cMap - size of incoming map[] array (in elements).
+// pcMap - OUT: full size of IL-->Native map (in elements).
+// map - caller allocated array to be filled in.
+//
+// Returns:
+// S_OK on successful copying.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_DEBUG_IL_TO_NATIVE_MAP *,cMap,true,true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ LoadNativeInfo();
+
+ SequencePoints * pSeqPts = GetSequencePoints();
+ DebuggerILToNativeMap * rgMapInt = pSeqPts->GetMapAddr();
+ ULONG32 cMapIntCount = pSeqPts->GetEntryCount();
+
+ // If they gave us space to copy into...
+ if (map != NULL)
+ {
+ // Only copy as much as either they gave us or we have to copy.
+ ULONG32 cMapToCopy = min(cMap, cMapIntCount);
+
+ // Remember that we need to translate between our internal DebuggerILToNativeMap and the external
+ // COR_DEBUG_IL_TO_NATIVE_MAP!
+ ULONG32 size = GetSize();
+ ExportILToNativeMap(cMapToCopy, map, rgMapInt, size);
+ }
+
+ // return the full count of map entries
+ if (pcMap)
+ {
+ *pcMap = cMapIntCount;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbNativeCode::GetILToNativeMapping
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetCodeChunks
+// Public method to get the code regions of code. If the code
+// is broken into discontinuous regions (hot + cold), this lets a debugger
+// find the number of regions, and (start,size) of each.
+//
+// Parameters:
+// cbufSize - size of incoming chunks array (in elements).
+// pcnumChunks - OUT param: the number of elements written to in the chunk array.//
+// chunks - caller allocated storage to hold the code chunks.
+//
+// Returns:
+// S_OK if successfully copied elements to Chunk array.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetCodeChunks(
+ ULONG32 cbufSize,
+ ULONG32 * pcnumChunks,
+ CodeChunkInfo chunks[]
+)
+{
+ PUBLIC_API_ENTRY(this);
+
+ if (pcnumChunks == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if ((chunks == NULL) != (cbufSize == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Current V2.0 implementation has at most 2 possible chunks right now (1 hot, and 1 cold).
+ ULONG32 cActualChunks = HasColdRegion() ? 2 : 1;
+
+ // If no buf size, then we're querying the total number of chunks.
+ if (cbufSize == 0)
+ {
+ *pcnumChunks = cActualChunks;
+ return S_OK;
+ }
+
+ // Else give them as many as they asked for.
+ for (CodeBlobRegion index = kHot; (index < MAX_REGIONS) && ((int)cbufSize > index); ++index)
+ {
+ // Fill in the region information
+ chunks[index].startAddr = m_rgCodeRegions[index].pAddress;
+ chunks[index].length = (ULONG32) (m_rgCodeRegions[index].cbSize);
+ *pcnumChunks = cbufSize;
+ }
+
+ return S_OK;
+} // CordbNativeCode::GetCodeChunks
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetCompilerFlags
+// Public entry point to get code flags for this Code object.
+// Originally, ICDCode had this method implemented independently from the
+// ICDModule method GetJitCompilerFlags. This was because it was considered that
+// the flags would be per function, rather than per module.
+// In addition, GetCompilerFlags did two different things depending on whether
+// the code had a native image. It turned out that was the wrong thing to do
+// .
+//
+// Parameters:
+// pdwFlags - OUT: code gen flags (see CorDebugJITCompilerFlags)
+//
+// Return value:
+// S_OK if pdwFlags is set properly.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetCompilerFlags(DWORD * pdwFlags)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD *);
+ *pdwFlags = 0;
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return GetFunction()->GetModule()->GetJITCompilerFlags(pdwFlags);
+
+} // CordbNativeCode::GetCompilerFlags
+
+//-----------------------------------------------------------------------------
+// Given an IL local variable number and a native IP offset, return the
+// location of the variable in jitted code.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::ILVariableToNative(DWORD dwIndex,
+ SIZE_T ip,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ _ASSERTE(m_nativeVarData.IsInitialized());
+
+ return FindNativeInfoInILVariableArray(dwIndex,
+ ip,
+ m_nativeVarData.GetOffsetInfoList(),
+ ppNativeInfo);
+} // CordbNativeCode::ILVariableToNative
+
+
+HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(pFetched, ULONG32 *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ EX_TRY
+ {
+ hr = GetReturnValueLiveOffsetImpl(NULL, ILoffset, bufferSize, pFetched, pOffsets);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count)
+{
+#if defined(DBG_TARGET_ARM)
+ return MAX_INSTRUCTION_LENGTH;
+#elif defined(DBG_TARGET_ARM64)
+ return MAX_INSTRUCTION_LENGTH;
+#elif defined(DBG_TARGET_X86)
+ if (count < 2)
+ return -1;
+
+ // Skip instruction prefixes
+ do
+ {
+ switch (*ip)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf1:
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ ip++;
+ count--;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ BYTE opcode = *ip++;
+ if (opcode == 0xcc)
+ {
+ // todo: Can we actually get this result? Doesn't ICorDebug hand out un-patched assembly?
+ _ASSERTE(!"Hit break opcode!");
+ return -1;
+ }
+
+ // Analyze what we can of the opcode
+ switch (opcode)
+ {
+ case 0xff:
+ {
+ // Count may have been decremented by prefixes.
+ if (count < 2)
+ return -1;
+
+ BYTE modrm = *ip++;
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ int displace = -1;
+
+ if ((reg != 2) && (reg != 3) && (reg != 4) && (reg != 5))
+ {
+ //
+ // This is not a CALL or JMP instruction, return, unknown.
+ //
+ _ASSERTE(!"Unhandled opcode!");
+ return -1;
+ }
+
+
+ // Only try to decode registers if we actually have reg sets.
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+
+ if (rm == 4)
+ {
+ if (count < 3)
+ return -1;
+
+ //
+ // Get values from the SIB byte
+ //
+ BYTE ss = (*ip & 0xC0) >> 6;
+ BYTE index = (*ip & 0x38) >> 3;
+ BYTE base = (*ip & 0x7);
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if (base == 5)
+ displace = 7;
+ else
+ displace = 3;
+ }
+ else if (mod == 1)
+ {
+ displace = 4;
+ }
+ else
+ {
+ displace = 7;
+ }
+ }
+ else
+ {
+ if (mod == 0)
+ {
+ if (rm == 5)
+ displace = 6;
+ else
+ displace = 2;
+ }
+ else if (mod == 1)
+ {
+ displace = 3;
+ }
+ else
+ {
+ displace = 6;
+ }
+ }
+ break;
+
+ case 3:
+ default:
+ displace = 2;
+ break;
+ }
+
+ return displace;
+ } // end of 0xFF case
+
+ case 0xe8:
+ return 5;
+
+
+ default:
+ break;
+ }
+
+
+ _ASSERTE(!"Unhandled opcode!");
+ return -1;
+
+#elif defined(DBG_TARGET_AMD64)
+ BYTE rex = NULL;
+ BYTE prefix = *ip;
+ BOOL fContainsPrefix = FALSE;
+
+ // Should not happen.
+ if (prefix == 0xcc)
+ return -1;
+
+ // Skip instruction prefixes
+ //@TODO by euzem:
+ //This "loop" can't be really executed more than once so if CALL can really have more than one prefix we'll crash.
+ //Some of these prefixes are not allowed for CALL instruction and we should treat them as invalid code.
+ //It appears that this code was mostly copy/pasted from \NDP\clr\src\Debug\EE\amd64\amd64walker.cpp
+ //with very minimum fixes.
+ do
+ {
+ switch (prefix)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ ip++;
+ fContainsPrefix = TRUE;
+ continue;
+
+ // REX register extension prefixes
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4a:
+ case 0x4b:
+ case 0x4c:
+ case 0x4d:
+ case 0x4e:
+ case 0x4f:
+ // make sure to set rex to prefix, not *ip because *ip still represents the
+ // codestream which has a 0xcc in it.
+ rex = prefix;
+ ip++;
+ fContainsPrefix = TRUE;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ BYTE opcode = *ip++;
+
+ // Should not happen.
+ if (opcode == 0xcc)
+ return -1;
+
+
+ // Setup rex bits if needed
+ BYTE rex_b = 0;
+ BYTE rex_x = 0;
+ BYTE rex_r = 0;
+
+ if (rex != NULL)
+ {
+ rex_b = (rex & 0x1); // high bit to modrm r/m field or SIB base field or OPCODE reg field -- Hmm, when which?
+ rex_x = (rex & 0x2) >> 1; // high bit to sib index field
+ rex_r = (rex & 0x4) >> 2; // high bit to modrm reg field
+ }
+
+ // Analyze what we can of the opcode
+ switch (opcode)
+ {
+ case 0xff:
+ {
+ BYTE modrm = *ip++;
+
+ _ASSERT(modrm != NULL);
+
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ reg |= (rex_r << 3);
+ rm |= (rex_b << 3);
+
+ if ((reg < 2) || (reg > 5 && reg < 8) || (reg > 15)) {
+ // not a valid register for a CALL or BRANCH
+ _ASSERTE(!"Invalid opcode!");
+ return -1;
+ }
+
+ BYTE *result;
+ WORD displace = -1;
+
+ // See: Tables A-15,16,17 in AMD Dev Manual 3 for information
+ // about how the ModRM/SIB/REX bytes interact.
+
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+ if ((rm & 0x07) == 4) // we have an SIB byte following
+ {
+ //
+ // Get values from the SIB byte
+ //
+ BYTE sib = *ip;
+ _ASSERT(sib != NULL);
+
+ BYTE base = (sib & 0x07);
+ base |= (rex_b << 3);
+
+ ip++;
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if ((base & 0x07) == 5)
+ displace = 7;
+ else
+ displace = 3;
+ }
+ else if (mod == 1)
+ {
+ displace = 4;
+ }
+ else // mod == 2
+ {
+ displace = 7;
+ }
+ }
+ else
+ {
+ //
+ // Get the value we need from the register.
+ //
+
+ // Check for RIP-relative addressing mode.
+ if ((mod == 0) && ((rm & 0x07) == 5))
+ {
+ displace = 6; // 1 byte opcode + 1 byte modrm + 4 byte displacement (signed)
+ }
+ else
+ {
+ if (mod == 0)
+ displace = 2;
+ else if (mod == 1)
+ displace = 3;
+ else // mod == 2
+ displace = 6;
+ }
+ }
+
+ break;
+
+ case 3:
+ default:
+ displace = 2;
+ }
+
+ // Displace should be set by one of the cases above
+ if (displace == -1)
+ {
+ _ASSERTE(!"GetCallInstructionLength() encountered unexpected call instruction");
+ return -1;
+ }
+
+ // Account for the 1 byte prefix (REX or otherwise)
+ if (fContainsPrefix)
+ displace++;
+
+ // reg == 4 or 5 means that it is not a CALL, but JMP instruction
+ // so we will fall back to ASSERT after break
+ if ((reg != 4) && (reg != 5))
+ return displace;
+ break;
+ }
+ case 0xe8:
+ {
+ //Near call with the target specified by a 32-bit relative displacement.
+ //[maybe 1 byte prefix] + [1 byte opcode E8h] + [4 bytes offset]
+ return 5 + (fContainsPrefix ? 1 : 0);
+ }
+ default:
+ break;
+ }
+
+ _ASSERTE(!"Invalid opcode!");
+ return -1;
+#else
+#error Platform not implemented
+#endif
+}
+
+HRESULT CordbNativeCode::GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &parser, SigParser &methodGenerics)
+{
+ // mdFunction may be a MemberRef, a MethodDef, or a MethodSpec. We must handle all three cases.
+ HRESULT hr = S_OK;
+ IMetaDataImport* pImport = m_pFunction->GetModule()->GetMetaDataImporter();
+ RSExtSmartPtr<IMetaDataImport2> pImport2;
+ IfFailRet(pImport->QueryInterface(IID_IMetaDataImport2, (void**)&pImport2));
+
+ if (TypeFromToken(mdFunction) == mdtMemberRef)
+ {
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ IfFailRet(pImport->GetMemberRefProps(mdFunction, pClass, NULL, 0, 0, &sig, &sigSize));
+ parser = SigParser(sig, sigSize);
+ }
+ else if (TypeFromToken(mdFunction) == mdtMethodDef)
+ {
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ IfFailRet(pImport->GetMethodProps(mdFunction, pClass, NULL, 0, NULL, NULL, &sig, &sigSize, NULL, NULL));
+ parser = SigParser(sig, sigSize);
+ }
+ else if (TypeFromToken(mdFunction) == mdtMethodSpec)
+ {
+ // For a method spec, we use GetMethodSpecProps to get the generic singature and the parent token
+ // (which is a MethodDef token). We'll recurse to get the other properties from the parent token.
+
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ mdToken parentToken = 0;
+ IfFailRet(pImport2->GetMethodSpecProps(mdFunction, &parentToken, &sig, &sigSize));
+ methodGenerics = SigParser(sig, sigSize);
+
+ if (pClass)
+ *pClass = parentToken;
+
+ return GetSigParserFromFunction(parentToken, pClass, parser, methodGenerics);
+ }
+ else
+ {
+ // According to ECMA III.3.19, this can never happen.
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::EnsureReturnValueAllowed(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics)
+{
+ HRESULT hr = S_OK;
+ ULONG genCount = 0;
+ IfFailRet(SkipToReturn(parser, &genCount));
+
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, parser, methodGenerics, genCount);
+}
+
+HRESULT CordbNativeCode::EnsureReturnValueAllowedWorker(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics, ULONG genCount)
+{
+ // There are a few considerations here:
+ // 1. Generic instantiations. This is a "Foo<T>", and we need to check if that "Foo"
+ // fits one of the categories we disallow (such as a struct).
+ // 2. Void return.
+ // 3. ValueType - Unsupported this release.
+ // 4. MVAR - Method generics. We need to get the actual generic type and recursively
+ // check if we allow that.
+ // 5. VAR - Class generics. We need to get the actual generic type and recurse.
+
+ SigParser original(parser);
+ HRESULT hr = S_OK;
+ CorElementType returnType;
+ IfFailRet(parser.GetElemType(&returnType));
+ if (returnType == ELEMENT_TYPE_GENERICINST)
+ {
+ IfFailRet(parser.GetElemType(&returnType));
+
+ if (returnType == ELEMENT_TYPE_CLASS)
+ return S_OK;
+
+ if (returnType != ELEMENT_TYPE_VALUETYPE)
+ return META_E_BAD_SIGNATURE;
+
+ if (currentInstantiation == NULL)
+ return S_OK; // We will check again when we have the instantiation.
+
+ NewArrayHolder<CordbType*> types;
+ Instantiation inst;
+ IfFailRet(CordbJITILFrame::BuildInstantiationForCallsite(GetModule(), types, inst, currentInstantiation, targetClass, SigParser(methodGenerics)));
+
+ CordbType *pType = 0;
+ IfFailRet(CordbType::SigToType(GetModule(), &original, &inst, &pType));
+
+
+ IfFailRet(hr = pType->ReturnedByValue());
+ if (hr == S_OK) // not S_FALSE
+ return S_OK;
+
+ return CORDBG_E_UNSUPPORTED;
+ }
+
+ if (returnType == ELEMENT_TYPE_VALUETYPE)
+ {
+ Instantiation inst;
+ CordbType *pType = 0;
+ IfFailRet(CordbType::SigToType(GetModule(), &original, &inst, &pType));
+
+ IfFailRet(hr = pType->ReturnedByValue());
+ if (hr == S_OK) // not S_FALSE
+ return S_OK;
+
+ return CORDBG_E_UNSUPPORTED;
+ }
+
+ if (returnType == ELEMENT_TYPE_TYPEDBYREF)
+ return CORDBG_E_UNSUPPORTED;
+
+ if (returnType == ELEMENT_TYPE_VOID)
+ return E_UNEXPECTED;
+
+ if (returnType == ELEMENT_TYPE_MVAR)
+ {
+ // Get which generic parameter is referenced.
+ ULONG genParam = 0;
+ IfFailRet(parser.GetData(&genParam));
+
+ // Grab the calling convention of the method, ensure it's GENERICINST.
+ ULONG callingConv = 0;
+ IfFailRet(methodGenerics.GetCallingConvInfo(&callingConv));
+ if (callingConv != IMAGE_CEE_CS_CALLCONV_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ // Ensure sensible bounds.
+ SigParser generics(methodGenerics); // Make a copy since operations are destructive.
+ ULONG maxCount = 0;
+ IfFailRet(generics.GetData(&maxCount));
+ if (maxCount <= genParam || genParam > 1024)
+ return META_E_BAD_SIGNATURE;
+
+ // Walk to the parameter referenced.
+ while (genParam--)
+ IfFailRet(generics.SkipExactlyOne());
+
+ // Now recurse with "generics" at the location to continue parsing.
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, generics, methodGenerics, genCount);
+ }
+
+
+ if (returnType == ELEMENT_TYPE_VAR)
+ {
+ // Get which type parameter is reference.
+ ULONG typeParam = 0;
+ parser.GetData(&typeParam);
+
+ // Ensure something reasonable.
+ if (typeParam > 1024)
+ return META_E_BAD_SIGNATURE;
+
+ // Lookup the containing class's signature so we can get the referenced generic parameter.
+ IMetaDataImport *pImport = m_pFunction->GetModule()->GetMetaDataImporter();
+ PCCOR_SIGNATURE sig;
+ ULONG countSig;
+ IfFailRet(pImport->GetTypeSpecFromToken(targetClass, &sig, &countSig));
+
+ // Enusre the type's typespec is GENERICINST.
+ SigParser typeParser(sig, countSig);
+ CorElementType et;
+ IfFailRet(typeParser.GetElemType(&et));
+ if (et != ELEMENT_TYPE_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ // Move to the correct location.
+ IfFailRet(typeParser.GetElemType(&et));
+ if (et != ELEMENT_TYPE_VALUETYPE && et != ELEMENT_TYPE_CLASS)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeParser.GetToken(NULL));
+
+ ULONG totalTypeCount = 0;
+ IfFailRet(typeParser.GetData(&totalTypeCount));
+ if (totalTypeCount < typeParam)
+ return META_E_BAD_SIGNATURE;
+
+ while (typeParam--)
+ IfFailRet(typeParser.SkipExactlyOne());
+
+ // This is a temporary workaround for an infinite recursion here. ALL of this code will
+ // go away when we allow struct return values, but in the mean time this avoids a corner
+ // case in the type system we haven't solved yet.
+ IfFailRet(typeParser.PeekElemType(&et));
+ if (et == ELEMENT_TYPE_VAR)
+ return E_FAIL;
+
+ // Now that typeParser is at the location of the correct generic parameter, recurse.
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, typeParser, methodGenerics, genCount);
+ }
+
+ // Everything else supported
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::SkipToReturn(SigParser &parser, ULONG *genCount)
+{
+ // Takes a method signature parser (at the beginning of a signature) and skips to the
+ // return value.
+ HRESULT hr = S_OK;
+
+ // Skip calling convention
+ ULONG uCallConv;
+ IfFailRet(parser.GetCallingConvInfo(&uCallConv));
+ if ((uCallConv == IMAGE_CEE_CS_CALLCONV_FIELD) || (uCallConv == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG))
+ return META_E_BAD_SIGNATURE;
+
+ // Skip type parameter count if function is generic
+ if (uCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC)
+ IfFailRet(parser.GetData(genCount));
+
+ // Skip argument count
+ IfFailRet(parser.GetData(NULL));
+
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::GetCallSignature(ULONG32 ILoffset, mdToken *pClass, mdToken *pFunction, SigParser &parser, SigParser &generics)
+{
+ // check if specified IL offset is at a call instruction
+ CordbILCode *pCode = this->m_pFunction->GetILCode();
+ BYTE buffer[3];
+ ULONG32 fetched = 0;
+ HRESULT hr = pCode->GetCode(ILoffset, ILoffset+_countof(buffer), _countof(buffer), buffer, &fetched);
+
+ if (FAILED(hr))
+ return hr;
+ else if (fetched != _countof(buffer))
+ return CORDBG_E_INVALID_OPCODE;
+
+ // tail. - fe 14 (ECMA III.2.4)
+ BYTE instruction = buffer[0];
+ if (buffer[0] == 0xfe && buffer[1] == 0x14)
+ {
+ // tail call case. We don't allow managed return values for tailcalls.
+ return CORDBG_E_INVALID_OPCODE;
+ }
+
+ // call - 28 (ECMA III.3.19)
+ // callvirt - 6f (ECMA III.4.2)
+ if (instruction != 0x28 && instruction != 0x6f)
+ return CORDBG_E_INVALID_OPCODE;
+
+ // Now grab the MD token of the call
+ mdToken mdFunction = 0;
+ const ULONG32 offset = ILoffset + 1;
+ hr = pCode->GetCode(offset, offset+sizeof(mdToken), sizeof(mdToken), (BYTE*)&mdFunction, &fetched);
+ if (FAILED(hr) || fetched != sizeof(mdToken))
+ return CORDBG_E_INVALID_OPCODE;
+
+ if (pFunction)
+ *pFunction = mdFunction;
+
+ // Convert to a signature parser
+ return GetSigParserFromFunction(mdFunction, pClass, parser, generics);
+}
+
+HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets)
+{
+ if (pFetched == NULL)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ ULONG32 found = 0;
+
+ // verify that the call target actually returns something we allow
+ SigParser signature, generics;
+ mdToken mdClass = 0;
+ IfFailRet(GetCallSignature(ILoffset, &mdClass, NULL, signature, generics));
+ IfFailRet(EnsureReturnValueAllowed(currentInstantiation, mdClass, signature, generics));
+
+ // now find the native offset
+ SequencePoints *pSP = GetSequencePoints();
+ DebuggerILToNativeMap *pMap = pSP->GetCallsiteMapAddr();
+
+ for (ULONG32 i = 0; i < pSP->GetCallsiteEntryCount() && pMap; ++i, pMap++)
+ {
+ if (pMap->ilOffset == ILoffset && (pMap->source & ICorDebugInfo::CALL_INSTRUCTION) == ICorDebugInfo::CALL_INSTRUCTION)
+ {
+ // if we have a buffer, fill it in.
+ if (pOffsets && found < bufferSize)
+ {
+ // Fetch the actual assembly instructions
+ BYTE nativeBuffer[8];
+
+ ULONG32 fetched = 0;
+ IfFailRet(GetCode(pMap->nativeStartOffset, pMap->nativeStartOffset+_countof(nativeBuffer), _countof(nativeBuffer), nativeBuffer, &fetched));
+
+ int skipBytes = 0;
+
+#if defined(DBG_TARGET_X86) && defined(FEATURE_CORESYSTEM)
+ // Skip nop sleds on x86 coresystem. The JIT adds these instructions as a security measure,
+ // and incorrectly reports to us the wrong offset of the call instruction.
+ const BYTE nop_opcode = 0x90;
+ while (fetched && nativeBuffer[0] == nop_opcode)
+ {
+ skipBytes++;
+
+ for (int j = 1; j < _countof(nativeBuffer) && nativeBuffer[j] == nop_opcode; ++j)
+ skipBytes++;
+
+ // We must have at least one skip byte since the outer while ensures it. Thus we always need to reread
+ // the buffer at the end of this loop.
+ IfFailRet(GetCode(pMap->nativeStartOffset+skipBytes, pMap->nativeStartOffset+skipBytes+_countof(nativeBuffer), _countof(nativeBuffer), nativeBuffer, &fetched));
+ }
+#endif
+
+ // Get the length of the call instruction.
+ int offset = GetCallInstructionLength(nativeBuffer, fetched);
+ if (offset == -1)
+ return E_UNEXPECTED; // Could not decode instruction, this should never happen.
+
+ pOffsets[found] = pMap->nativeStartOffset + offset + skipBytes;
+ }
+
+ found++;
+ }
+ }
+
+ if (pOffsets)
+ *pFetched = found < bufferSize ? found : bufferSize;
+ else
+ *pFetched = found;
+
+ if (found == 0)
+ return E_FAIL;
+
+ if (pOffsets && found > bufferSize)
+ return S_FALSE;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a CordbNativeCode (if it's not already created) and adds it to the
+// hash table of CordbNativeCode instances belonging to this module.
+// Used by CordbFunction::InitNativeCodeInfo.
+//
+// Arguments:
+// Input:
+// methodToken - the methodDef token of the function this native code belongs to
+// methodDesc - the methodDesc for the jitted method
+// startAddress - the hot code startAddress for this method
+
+// Return value:
+// found or created CordbNativeCode pointer
+// Assumptions: methodToken is in the metadata for this module
+// methodDesc and startAddress should be consistent for
+// a jitted instance of methodToken's method
+//-----------------------------------------------------------------------------
+CordbNativeCode * CordbModule::LookupOrCreateNativeCode(mdMethodDef methodToken,
+ VMPTR_MethodDesc methodDesc,
+ CORDB_ADDRESS startAddress)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(startAddress != NULL);
+ _ASSERTE(methodDesc != VMPTR_MethodDesc::NullPtr());
+
+ CordbNativeCode * pNativeCode = NULL;
+ NativeCodeFunctionData codeInfo;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // see if we already have this--if not, we'll make an instance, otherwise we'll just return the one we have.
+ pNativeCode = m_nativeCodeTable.GetBase((UINT_PTR) startAddress);
+
+ if (pNativeCode == NULL)
+ {
+ GetProcess()->GetDAC()->GetNativeCodeInfoForAddr(methodDesc, startAddress, &codeInfo);
+
+ // We didn't have an instance, so we'll build one and add it to the hash table
+ LOG((LF_CORDB,
+ LL_INFO10000,
+ "R:CT::RSCreating code w/ ver:0x%x, md:0x%x, nativeStart=0x%08x, nativeSize=0x%08x\n",
+ codeInfo.encVersion,
+ VmPtrToCookie(codeInfo.vmNativeCodeMethodDescToken),
+ codeInfo.m_rgCodeRegions[kHot].pAddress,
+ codeInfo.m_rgCodeRegions[kHot].cbSize));
+
+ // Lookup the function object that this code should be bound to
+ CordbFunction* pFunction = CordbModule::LookupOrCreateFunction(methodToken, codeInfo.encVersion);
+ _ASSERTE(pFunction != NULL);
+
+ // There are bugs with the on-demand class load performed by CordbFunction in some cases. The old stack
+ // tracing code avoided them by eagerly loading the parent class so I am following suit
+ pFunction->InitParentClassOfFunction();
+
+ // First, create a new CordbNativeCode instance--we'll need this to make the CordbJITInfo instance
+ pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0);
+ _ASSERTE(pNativeCode != NULL);
+
+ m_nativeCodeTable.AddBaseOrThrow(pNativeCode);
+ }
+
+ return pNativeCode;
+} // CordbNativeCode::LookupOrCreateFromJITData
+
+// LoadNativeInfo loads from the left side any native variable info
+// from the JIT.
+//
+void CordbNativeCode::LoadNativeInfo()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_API_ENTRY(this->GetProcess());
+
+
+ // If we've either never done this before (no info), or we have, but the version number has increased, we
+ // should try and get a newer version of our JIT info.
+ if(m_nativeVarData.IsInitialized())
+ {
+ return;
+ }
+
+ // You can't do this if the function is implemented as part of the Runtime.
+ if (GetFunction()->IsNativeImpl() == CordbFunction::kNativeOnly)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+ CordbProcess *pProcess = GetProcess();
+ // Get everything via the DAC
+ if (m_fCodeAvailable)
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetNativeCodeSequencePointsAndVarInfo(GetVMNativeCodeMethodDescToken(),
+ GetAddress(),
+ m_fCodeAvailable,
+ &m_nativeVarData,
+ &m_sequencePoints);
+ }
+
+} // CordbNativeCode::LoadNativeInfo
+
+
+