diff options
Diffstat (limited to 'src/debug/di/divalue.cpp')
-rw-r--r-- | src/debug/di/divalue.cpp | 4564 |
1 files changed, 4564 insertions, 0 deletions
diff --git a/src/debug/di/divalue.cpp b/src/debug/di/divalue.cpp new file mode 100644 index 0000000000..50ecd68aa9 --- /dev/null +++ b/src/debug/di/divalue.cpp @@ -0,0 +1,4564 @@ +// 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: DIValue.cpp +// + +// +//***************************************************************************** +#include "stdafx.h" +#include "primitives.h" + +// copy from a MemoryRange to dest +// Arguments: +// input: source - MemoryRange describing the start address and size of the source buffer +// output: dest - address of the buffer to which the source buffer is copied +// Note: the buffer for dest must be allocated by the caller and must be large enough to hold the +// bytes from the source buffer. +void localCopy(void * dest, MemoryRange source) +{ + _ASSERTE(dest != NULL); + _ASSERTE(source.StartAddress() != NULL); + + memcpy(dest, source.StartAddress(), source.Size()); +} + +// for an inheritance graph of the ICDValue types, // See file:./ICorDebugValueTypes.vsd for a diagram of the types. + +/* ------------------------------------------------------------------------- * + * CordbValue class + * ------------------------------------------------------------------------- */ + +CordbValue::CordbValue(CordbAppDomain * appdomain, + CordbType * type, + CORDB_ADDRESS id, + bool isLiteral, + NeuterList * pList) + : CordbBase( + ((appdomain != NULL) ? (appdomain->GetProcess()) : (type->GetProcess())), + (UINT_PTR)id, enumCordbValue), + m_appdomain(appdomain), + m_type(type), // implicit InternalAddRef + //m_sigCopied(false), + m_size(0), + m_isLiteral(isLiteral) +{ + HRESULT hr = S_OK; + + _ASSERTE(GetProcess() != NULL); + + // Add to a neuter list. If none is provided, use the ExitProcess list as a default. + // The main neuter lists of interest here are: + // - CordbProcess::GetContinueNeuterList() - Shortest. Neuter when the process continues. + // - CordbAppDomain::GetExitNeuterList() - Middle. Neuter when the AD exits. Since most Values (except globals) are in + // a specific AD, this almost catches all; and keeps us safe in AD-unload scenarios. + // - CordbProcess::GetExitNeuterList() - Worst. Doesn't neuter until the process exits (or we detach). + // This could be a long time. + if (pList == NULL) + { + pList = GetProcess()->GetExitNeuterList(); + } + + + EX_TRY + { + pList->Add(GetProcess(), this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); +} // CordbValue::CordbValue + +CordbValue::~CordbValue() +{ + DTOR_ENTRY(this); + + _ASSERTE(this->IsNeutered()); + + _ASSERTE(m_type == NULL); +} // CordbValue::~CordbValue + +void CordbValue::Neuter() +{ + m_appdomain = NULL; + m_type.Clear(); + + ValueHome * pValueHome = GetValueHome(); + if (pValueHome != NULL) + { + pValueHome->Clear(); + } + CordbBase::Neuter(); +} // CordbValue::Neuter + +// Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbGenericValue +// Arguments: +// input: pAppdomain - appdomain to which the value belongs +// pType - type of the value +// remoteValue - remote address and size of the value +// localValue - local address and size of the value +// ppRemoteRegAddr - register address of the value +// output: ppValue - the newly created instance of an ICDValue +// Notes: +// - only one of the three locations will be non-NULL +// - Throws +/* static */ +void CordbValue::CreateGenericValue(CordbAppDomain * pAppdomain, + CordbType * pType, + TargetBuffer remoteValue, + MemoryRange localValue, + EnregisteredValueHomeHolder * ppRemoteRegAddr, + ICorDebugValue** ppValue) +{ + LOG((LF_CORDB,LL_INFO100000,"CV::CreateValueByType CreateGenericValue\n")); + RSSmartPtr<CordbGenericValue> pGenValue; + // A generic value + // By using a RSSmartPtr we ensure that in both success and failure cases, + // this object is cleaned up properly (deleted or not depending on ref counts). + // Specifically, the object has probably been placed on a neuter list so we + // can't delete it (but this is a detail we shouldn't rely on) + pGenValue.Assign(new CordbGenericValue(pAppdomain, + pType, + remoteValue, + ppRemoteRegAddr)); + + pGenValue->Init(localValue); // throws + + pGenValue->AddRef(); + *ppValue = (ICorDebugValue *)(ICorDebugGenericValue *)pGenValue; +} // CordbValue::CreateGenericValue + +// create a new instance of CordbVCObjectValue or CordbReferenceValue +// Arguments: +// input: pAppdomain - appdomain to which the value belongs +// pType - type of the value +// boxed - indicates whether the value is boxed +// remoteValue - remote address and size of the value +// localValue - local address and size of the value +// ppRemoteRegAddr - register address of the value +// output: ppValue - the newly created instance of an ICDValue +// Notes: +// - only one of the three locations will be non-NULL +// - Throws error codes from reading process memory +/* static */ +void CordbValue::CreateVCObjOrRefValue(CordbAppDomain * pAppdomain, + CordbType * pType, + bool boxed, + TargetBuffer remoteValue, + MemoryRange localValue, + EnregisteredValueHomeHolder * ppRemoteRegAddr, + ICorDebugValue** ppValue) + +{ + HRESULT hr = S_OK; + LOG((LF_CORDB,LL_INFO1000000,"CV::CreateValueByType Creating ReferenceValue\n")); + + // We either have a boxed or unboxed value type, or we have a value that's not a value type. + // For an unboxed value type, we'll create an instance of CordbVCObjectValue. Otherwise, we'll + // create an instance of CordbReferenceValue. + + // do we have a value type? + bool isVCObject = pType->IsValueType(); // throws + + if (!boxed && isVCObject) + { + RSSmartPtr<CordbVCObjectValue> pVCValue(new CordbVCObjectValue(pAppdomain, + pType, + remoteValue, + ppRemoteRegAddr)); + + IfFailThrow(pVCValue->Init(localValue)); + + pVCValue->AddRef(); + *ppValue = (ICorDebugValue*)(ICorDebugObjectValue*)pVCValue; + } + else + { + // either the value is boxed or it's not a value type + RSSmartPtr<CordbReferenceValue> pRef; + hr = CordbReferenceValue::Build(pAppdomain, + pType, + remoteValue, + localValue, + VMPTR_OBJECTHANDLE::NullPtr(), + ppRemoteRegAddr, // Home + &pRef); + IfFailThrow(hr); + hr = pRef->QueryInterface(__uuidof(ICorDebugValue), (void**)ppValue); + _ASSERTE(SUCCEEDED(hr)); + } +} // CordbValue::CreateVCObjOrRefValue + +// +// Create the proper ICDValue instance based on the given element type. +// Arguments: +// input: pAppdomain - appdomain to which the value belongs +// pType - type of the value +// boxed - indicates whether the value is boxed +// remoteValue - remote address and size of the value +// localValue - local address and size of the value +// ppRemoteRegAddr - register address of the value +// output: ppValue - the newly created instance of an ICDValue +// Notes: +// - Only one of the three locations, remoteValue, localValue or ppRemoteRegAddr, will be non-NULL. +// - Throws. +/*static*/ void CordbValue::CreateValueByType(CordbAppDomain * pAppdomain, + CordbType * pType, + bool boxed, + TargetBuffer remoteValue, + MemoryRange localValue, + EnregisteredValueHomeHolder * ppRemoteRegAddr, + ICorDebugValue** ppValue) +{ + INTERNAL_SYNC_API_ENTRY(pAppdomain->GetProcess()); // + + // We'd really hope that our callers give us a valid appdomain, but in case + // they don't, we'll fail gracefully. + if ((pAppdomain != NULL) && pAppdomain->IsNeutered()) + { + STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "CVBT using neutered AP, %p\n", pAppdomain); + ThrowHR(E_INVALIDARG); + } + + LOG((LF_CORDB,LL_INFO100000,"CV::CreateValueByType\n")); + + *ppValue = NULL; + + switch(pType->m_elementType) + { + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R8: + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + { + CreateGenericValue(pAppdomain, pType, remoteValue, localValue, ppRemoteRegAddr, ppValue); // throws + break; + } + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_TYPEDBYREF: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_FNPTR: + { + CreateVCObjOrRefValue(pAppdomain, pType, boxed, remoteValue, localValue, ppRemoteRegAddr, ppValue); // throws + break; + } + + default: + _ASSERTE(!"Bad value type!"); + ThrowHR(E_FAIL); + } +} // CordbValue::CreateValueByType + +// Create the proper ICDValue instance based on the given remote heap object +// Arguments: +// pAppDomain - the app domain the remote object is in +// vmObj - the remote object to get an ICDValue for +ICorDebugValue* CordbValue::CreateHeapValue(CordbAppDomain* pAppDomain, VMPTR_Object vmObj) +{ + IDacDbiInterface* pDac = pAppDomain->GetProcess()->GetDAC(); + + TargetBuffer objBuffer = pDac->GetObjectContents(vmObj); + VOID* pRemoteAddr = CORDB_ADDRESS_TO_PTR(objBuffer.pAddress); + // This creates a local reference that has a remote address in it. Ie &pRemoteAddr is an address + // in the host address space and pRemoteAddr is an address in the target. + MemoryRange localReferenceDescription(&pRemoteAddr, sizeof(pRemoteAddr)); + RSSmartPtr<CordbReferenceValue> pRefValue; + IfFailThrow(CordbReferenceValue::Build(pAppDomain, + NULL, + EMPTY_BUFFER, + localReferenceDescription, + VMPTR_OBJECTHANDLE::NullPtr(), + NULL, + &pRefValue)); + + // Dereference our temporary reference value to construct the heap value we want + ICorDebugValue* pExtValue; + IfFailThrow(pRefValue->Dereference(&pExtValue)); + return pExtValue; +} + +// Gets the size om bytes of a value from its type. If the value is complex, we assume it is represented as +// a reference, since this is called for values that have been found on the stack, as an element of an +// array (represented as CordbArrayValue) or field of an object (CordbObjectValue) or the result of a +// func eval. For unboxed value types, we get the size of the entire value (it is not represented as a +// reference). +// Examples: +// - int on the stack +// => sizeof(int) +// - int as a field in an object on the heap +// =>sizeof(int) +// - Boxed int on the heap +// => size of a pointer +// - Class Point { int x; int y}; // class will have a method table / object header which may increase size. +// => size of a pointer +// - Struct Point {int x; int y; }; // unboxed struct may not necessarily have the object header. +// => 2 * sizeof(int) +// - List<int> +// => size of a pointer +// Arguments: pType - the type of the value +// boxing - indicates whether the value is boxed or not +// Return Value: the size of the value +// Notes: Throws +// In general, this returns the unboxed size of the value, but if we have a type +// that represents a non-generic and it's not an unboxed value type, we know that +// it will be represented as a reference, so we return the size of a pointer instead. +/* static */ +ULONG32 CordbValue::GetSizeForType(CordbType * pType, BoxedValue boxing) +{ + ULONG32 size = 0; + + switch(pType->m_elementType) + { + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R8: + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: pType->GetUnboxedObjectSize(&size); break; + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_TYPEDBYREF: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_FNPTR: { + bool isUnboxedVCObject = false; + + if (boxing == kUnboxed) + { + isUnboxedVCObject = pType->IsValueType(); // throws + } + if (!isUnboxedVCObject) + { + // if it's not an unboxed value type (we're in the case + // for compound types), then it's a reference + // and we just want to return the size of a pointer + size = sizeof(void *); + } + else + { + pType->GetUnboxedObjectSize(&size); + } + } break; + + default: + _ASSERTE(!"Bad value type!"); +} + return size; +} // CordbValue::GetSizeForType + + +HRESULT CordbValue::CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint) +{ + VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **); + + return E_NOTIMPL; +} // CordbValue::CreateBreakpoint + +// gets the exact type of a value +// Arguments: +// input: none (uses m_type field) +// output: ppType - an instance of ICDType representing the exact type of the value +// Return Value: +HRESULT CordbValue::GetExactType(ICorDebugType **ppType) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + *ppType = static_cast<ICorDebugType*> (m_type); + + if (*ppType != NULL) + (*ppType)->AddRef(); + + return S_OK; +} // CordbValue::GetExactType + +// CreateHandle for a heap object. +// @todo: How to prevent this being called by non-heap object? +// Arguments: +// input: handleType - type of the handle to be created +// output: ppHandle - on success, the newly created handle +// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK +HRESULT CordbValue::InternalCreateHandle(CorDebugHandleType handleType, + ICorDebugHandleValue ** ppHandle) +{ + INTERNAL_SYNC_API_ENTRY(GetProcess()); + LOG((LF_CORDB,LL_INFO1000,"CV::CreateHandle\n")); + + DebuggerIPCEvent event; + CordbProcess *process; + BOOL fStrong = FALSE; + + // @dbgtodo- , as part of inspection, convert this path to throwing. + if (ppHandle == NULL) + { + return E_INVALIDARG; + } + + *ppHandle = NULL; + + if (handleType == HANDLE_STRONG) + { + fStrong = TRUE; + } + else + { + _ASSERTE(handleType == HANDLE_WEAK_TRACK_RESURRECTION); + } + + + // Create the ICorDebugHandleValue object + RSInitHolder<CordbHandleValue> pHandle(new (nothrow) CordbHandleValue(m_appdomain, m_type, handleType) ); + + if (pHandle == NULL) + { + return E_OUTOFMEMORY; + } + + // Send the event to create the handle. + process = m_appdomain->GetProcess(); + _ASSERTE(process != NULL); + + process->InitIPCEvent(&event, + DB_IPCE_CREATE_HANDLE, + true, + m_appdomain->GetADToken()); + + CORDB_ADDRESS addr = GetValueHome() != NULL ? GetValueHome()->GetAddress() : NULL; + event.CreateHandle.objectToken = CORDB_ADDRESS_TO_PTR(addr); + event.CreateHandle.fStrong = fStrong; + + // Note: two-way event here... + HRESULT hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)); + hr = WORST_HR(hr, event.hr); + + if (SUCCEEDED(hr)) + { + _ASSERTE(event.type == DB_IPCE_CREATE_HANDLE_RESULT); + + // Initialize the handle value object. + hr = pHandle->Init(event.CreateHandleResult.vmObjectHandle); + } + + if (!SUCCEEDED(hr)) + { + // Free the handle from the left-side. + pHandle->Dispose(); + + // The RSInitHolder will neuter and delete it. + return hr; + } + + // Pass out the new handle value object. + pHandle.TransferOwnershipExternal(ppHandle); + + return S_OK; +} // CordbValue::InternalCreateHandle + +/* ------------------------------------------------------------------------- * + * Generic Value class + * ------------------------------------------------------------------------- */ + +// +// CordbGenericValue constructor that builds a generic value from +// a remote address or register. +// Arguments: +// input: pAppdomain - the app domain to which the value belongs +// pType - the type of the value +// remoteValue - buffer (and size) of the remote location where +// the value resides. This may be NULL if the value +// is enregistered. +// ppRemoteRegAddr - information describing the register in which the +// value resides. This may be NULL--only one of +// ppRemoteRegAddr and remoteValue will be non-NULL, +// depending on whether the value is in a register or +// memory. +CordbGenericValue::CordbGenericValue(CordbAppDomain * pAppdomain, + CordbType * pType, + TargetBuffer remoteValue, + EnregisteredValueHomeHolder * ppRemoteRegAddr) + : CordbValue(pAppdomain, pType, remoteValue.pAddress, false), + m_pValueHome(NULL) +{ + _ASSERTE(pType->m_elementType != ELEMENT_TYPE_END); + _ASSERTE(pType->m_elementType != ELEMENT_TYPE_VOID); + _ASSERTE(pType->m_elementType < ELEMENT_TYPE_MAX); + + // We can fill in the size now for generic values. + ULONG32 size; + HRESULT hr; + hr = pType->GetUnboxedObjectSize(&size); + _ASSERTE (!FAILED(hr)); + m_size = size; + + // now instantiate the value home + NewHolder<ValueHome> pHome(NULL); + if (remoteValue.IsEmpty()) + { + pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr)); + } + else + { + pHome = (new RemoteValueHome(pAppdomain->GetProcess(), remoteValue)); + } + m_pValueHome = pHome.GetValue(); // throws + pHome.SuppressRelease(); +} // CordbGenericValue::CordbGenericValue + +// +// CordbGenericValue constructor that builds an empty generic value +// from just an element type. Used for literal values for func evals +// only. +// Arguments: +// input: pType - the type of the value +CordbGenericValue::CordbGenericValue(CordbType * pType) + : CordbValue(NULL, pType, NULL, true), + m_pValueHome(NULL) +{ + // The only purpose of a literal value is to hold a RS literal value. + ULONG32 size; + HRESULT hr; + hr = pType->GetUnboxedObjectSize(&size); + _ASSERTE (!FAILED(hr)); + m_size = size; + + memset(m_pCopyOfData, 0, m_size); + + // there is no value home for a literal so we leave it as NULL +} // CordbGenericValue::CordbGenericValue + +// destructor +CordbGenericValue::~CordbGenericValue() +{ + if (m_pValueHome != NULL) + { + delete m_pValueHome; + m_pValueHome = NULL; +} +} // CordbGenericValue::~CordbGenericValue + +HRESULT CordbGenericValue::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugGenericValue*>(this)); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugGenericValue) + { + *pInterface = static_cast<ICorDebugGenericValue*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugGenericValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbGenericValue::QueryInterface + +// +// initialize a generic value by copying the necessary data, either +// from the remote process or from another value in this process. +// Argument: +// input: localValue - RS location of value to be copied. This could be NULL or it +// could be a field from the cached copy of a CordbVCObjectValue or CordbObjectValue +// instance or an element from the cached copy of a CordbArrayValue instance +// Note: Throws error codes from reading process memory +void CordbGenericValue::Init(MemoryRange localValue) +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); + + if(!m_isLiteral) + { + // If neither localValue.StartAddress nor m_remoteValue.pAddress are set, then all that means + // is that we've got a pre-initialized 64-bit value. + if (localValue.StartAddress() != NULL) + { + // Copy the data out of the local address space. + localCopy(m_pCopyOfData, localValue); + } + else + { + m_pValueHome->GetValue(MemoryRange(m_pCopyOfData, m_size)); // throws + } + } +} // CordbGenericValue::Init + +// gets the value (i.e., number, boolean or pointer value) for this instance of CordbGenericValue +// Arguments: +// output: pTo - the starting address of a buffer in which the value will be written. This buffer must +// be guaranteed by the caller to be large enough to hold the value. There is no way for +// us to check here if it is. This must be non-NULL. +// Return Value: S_OK on success or E_INVALIDARG if the pTo is NULL +HRESULT CordbGenericValue::GetValue(void *pTo) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true); + + _ASSERTE(m_pCopyOfData != NULL); + // Copy out the value + memcpy(pTo, m_pCopyOfData, m_size); + + return S_OK; +} // CordbGenericValue::GetValue + +// Sets the value of this instance of CordbGenericValue +// Arguments: +// input: pFrom - pointer to a buffer holding the new value. We assume this is the same size as the +// original value; we have no way to check. This must be non-NULL. +// Return Value: S_OK on success or E_INVALIDARG if the pFrom is NULL +HRESULT CordbGenericValue::SetValue(void *pFrom) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(pFrom, BYTE, m_size, true, false); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // We only need to send to the left side to update values that are + // object references. For generic values, we can simply do a write + // memory. + + EX_TRY + { + if(!m_isLiteral) + { + m_pValueHome->SetValue(MemoryRange(pFrom, m_size), m_type); // throws + } + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + // That worked, so update the copy of the value we have in + // m_copyOfData. + memcpy(m_pCopyOfData, pFrom, m_size); + + return hr; +} // CordbGenericValue::SetValue + +// copies the value from this instance of CordbGenericValue iff the value represents a literal +// Arguments: +// output: pBuffer - pointer to the beginning of a caller-allocated buffer.This buffer must +// be guaranteed by the caller to be large enough to hol +// d the value. There is no way for +// us to check here if it is. This must be non-NULL. +// Return Value: true iff this is a literal value and pBuffer is a valid writeable address +bool CordbGenericValue::CopyLiteralData(BYTE *pBuffer) +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); + _ASSERTE(pBuffer != NULL); + + // If this is a RS fabrication, copy the literal data into the + // given buffer and return true. + if (m_isLiteral) + { + _ASSERTE(m_size <= 8); + memcpy(pBuffer, m_pCopyOfData, m_size); + return true; + } + else + return false; +} // CordbGenericValue::CopyLiteralData + +/* ------------------------------------------------------------------------- * + * Reference Value class + * ------------------------------------------------------------------------- */ + +// constructor +// Arguments: +// input: pAppdomain - appdomain to which the value belongs +// pType - the type of the referent (the object pointed to) +// localValue - the RS address and size of the buffer from which the reference +// will be copied. This will be NULL if either remoteValue, +// ppRemoteRegAddr or vmObjectHandle is non-NULL. Otherwise, it will +// point into the local cached copy of another instance of ICDValue +// remoteValue - the LS address and size of the buffer from which the reference +// will be copied. This will be NULL if either localValue, +// ppRemoteRegAddr, or vmObjectHandle is non-NULL. +// ppRemoteRegAddr - information about the register location of the buffer from which +// the reference will be copied. This will be NULL if either localValue, +// remoteValue, or vmObjectHandle is non-NULL. +// vmObjectHandle - a LS object handle holding the reference. This will be NULL if either +// localValue, remoteValue, or ppRemoteRegAddr is non-NULL. +// Note: this may throw OOM +CordbReferenceValue::CordbReferenceValue(CordbAppDomain * pAppdomain, + CordbType * pType, + MemoryRange localValue, + TargetBuffer remoteValue, + EnregisteredValueHomeHolder * ppRemoteRegAddr, + VMPTR_OBJECTHANDLE vmObjectHandle) + : CordbValue(pAppdomain, pType, remoteValue.pAddress, false, + // We'd like to change this to be a ContinueList so it gets neutered earlier, + // but it may be a breaking change + pAppdomain->GetSweepableExitNeuterList()), + + m_realTypeOfTypedByref(NULL) +{ + memset(&m_info, 0, sizeof(m_info)); + + LOG((LF_CORDB,LL_EVERYTHING,"CRV::CRV: this:0x%x\n",this)); + m_size = sizeof(void *); + + // now instantiate the value home + NewHolder<ValueHome> pHome(NULL); + + if (!vmObjectHandle.IsNull()) + { + pHome = (new HandleValueHome(pAppdomain->GetProcess(), vmObjectHandle)); + m_valueHome.SetObjHandleFlag(false); + } + + else if (remoteValue.IsEmpty()) + { + pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr)); + m_valueHome.SetObjHandleFlag(true); + + } + else + { + pHome = (new RefRemoteValueHome(pAppdomain->GetProcess(), remoteValue)); +} + m_valueHome.m_pHome = pHome.GetValue(); // throws + pHome.SuppressRelease(); +} // CordbReferenceValue::CordbReferenceValue + +// CordbReferenceValue constructor that builds an empty reference value +// from just an element type. Used for literal values for func evals +// only. +// Arguments: +// input: pType - the type of the value +CordbReferenceValue::CordbReferenceValue(CordbType * pType) + : CordbValue(NULL, pType, NULL, true, pType->GetAppDomain()->GetSweepableExitNeuterList()) +{ + memset(&m_info, 0, sizeof(m_info)); + + // The only purpose of a literal value is to hold a RS literal value. + m_size = sizeof(void*); + + // there is no value home for a literal + m_valueHome.m_pHome = NULL; +} // CordbReferenceValue::CordbReferenceValue + +// copies the value from this instance of CordbReferenceValue iff the value represents a literal +// Arguments: +// output: pBuffer - pointer to the beginning of a caller-allocated buffer.This buffer must +// be guaranteed by the caller to be large enough to hold the value. +// There is no way for us to check here if it is. This must be non-NULL. +// Return Value: true iff this is a literal value and pBuffer is a valid writeable address +bool CordbReferenceValue::CopyLiteralData(BYTE *pBuffer) +{ + _ASSERTE(pBuffer != NULL); + + // If this is a RS fabrication, then its a null reference. + if (m_isLiteral) + { + void *n = NULL; + memcpy(pBuffer, &n, sizeof(n)); + return true; + } + else + return false; +} // CordbReferenceValue::CopyLiteralData + +// destructor +CordbReferenceValue::~CordbReferenceValue() +{ + DTOR_ENTRY(this); + + LOG((LF_CORDB,LL_EVERYTHING,"CRV::~CRV: this:0x%x\n",this)); + + _ASSERTE(IsNeutered()); +} // CordbReferenceValue::~CordbReferenceValue + +void CordbReferenceValue::Neuter() +{ + if (m_valueHome.m_pHome != NULL) + { + m_valueHome.m_pHome->Clear(); + delete m_valueHome.m_pHome; + m_valueHome.m_pHome = NULL; + } + + m_realTypeOfTypedByref = NULL; + CordbValue::Neuter(); +} // CordbReferenceValue::Neuter + + +HRESULT CordbReferenceValue::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugReferenceValue*>(this)); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugReferenceValue) + { + *pInterface = static_cast<ICorDebugReferenceValue*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugReferenceValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbReferenceValue::QueryInterface + +// gets the type of the referent of the object ref +// Arguments: +// output: pType - the type of the value. The caller must guarantee that pType is non-null. +// Return Value: S_OK on success, E_INVALIDARG on failure +HRESULT CordbReferenceValue::GetType(CorElementType *pType) +{ + LIMITED_METHOD_CONTRACT; + + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *); + + if( m_type == NULL ) + { + // We may not have a CordbType if we were created from a GC handle to NULL + _ASSERTE( m_info.objTypeData.elementType == ELEMENT_TYPE_CLASS ); + _ASSERTE(!m_valueHome.ObjHandleIsNull()); + _ASSERTE( m_info.objRef == NULL ); + *pType = m_info.objTypeData.elementType; + } + else + { + // The element type stored in both places should match + _ASSERTE( m_info.objTypeData.elementType == m_type->m_elementType ); + *pType = m_type->m_elementType; + } + + return S_OK; +} // CordbReferenceValue::GetType + +// gets the remote (LS) address of the reference. This may return NULL if the +// reference is a literal or resides in a register. +// Arguments: +// output: pAddress - the LS location of the reference. The caller must guarantee pAddress is non-null, +// but the contents may be null after the call if the reference is enregistered or is +// the value of a field or element of some other Cordb*Value instance. +// Return Value: S_OK on success or E_INVALIDARG if pAddress is null +HRESULT CordbReferenceValue::GetAddress(CORDB_ADDRESS *pAddress) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *); + + *pAddress = m_valueHome.m_pHome ? m_valueHome.m_pHome->GetAddress() : NULL; + return (S_OK); +} + +// Determines whether the reference is null +// Arguments: +// output - pfIsNull - pointer to a BOOL that will be set to true iff this represents a +// null reference +// Return Value: S_OK on success or E_INVALIDARG if pfIsNull is null +HRESULT CordbReferenceValue::IsNull(BOOL * pfIsNull) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pfIsNull, BOOL *); + + if (m_isLiteral || (m_info.objRef == NULL)) + *pfIsNull = TRUE; + else + *pfIsNull = FALSE; + + return S_OK; +} + +// gets the value (object address) of this CordbReferenceValue +// Arguments: +// output: pTo - reference value +// Return Value: S_OK on success or E_INVALIDARG if pAddress is null +HRESULT CordbReferenceValue::GetValue(CORDB_ADDRESS *pAddress) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *); + FAIL_IF_NEUTERED(this); + + // Copy out the value, which is simply the value the object reference. + if (m_isLiteral) + *pAddress = NULL; + else + *pAddress = PTR_TO_CORDB_ADDRESS(m_info.objRef); + + return S_OK; +} + +// sets the value of the reference +// Arguments: +// input: address - the new reference--this must be a LS address +// Return Value: S_OK on success or E_INVALIDARG or write process memory errors +// Note: We make no effort to ensure that the new reference is of the same type as the old one. +// We simply assume it is. As long as this assumption is correct, we only need to update information about +// the referent if it's a string (its length can change). + +// @dbgtodo Microsoft inspection: consider whether it's worthwhile to verify that the type of the new referent is +// the same as the type of the existing one. We'd have to do most of the work for a call to InitRef to do +// this, since we need to know the type of the new referent. +HRESULT CordbReferenceValue::SetValue(CORDB_ADDRESS address) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + HRESULT hr = S_OK; + + // If this is a heap object, ideally we'd prevent violations of AppDomain isolation + // here. However, we have no reliable way of determining what AppDomain the address is in. + + // Can't change literal refs. + if (m_isLiteral) + { + return E_INVALIDARG; + } + + // Either we know the type, or it's a handle to a null value + _ASSERTE((m_type != NULL) || + (!m_valueHome.ObjHandleIsNull() && (m_info.objRef == NULL))); + + EX_TRY + { + m_valueHome.m_pHome->SetValue(MemoryRange(&address, sizeof(void *)), m_type); // throws + } + EX_CATCH_HRESULT(hr); + + if (SUCCEEDED(hr)) + { + // That worked, so update the copy of the value we have in + // our local cache. + m_info.objRef = CORDB_ADDRESS_TO_PTR(address); + + + if (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING) + { + // update information about the string + InitRef(MemoryRange(&m_info.objRef, sizeof (void *))); + } + + // All other data in m_info is no longer valid, and we may have invalidated other + // ICDRVs at this address. We have to invalidate all cached debuggee data. + m_appdomain->GetProcess()->m_continueCounter++; + } + + return hr; +} // CordbReferenceValue::SetValue + +HRESULT CordbReferenceValue::DereferenceStrong(ICorDebugValue **ppValue) +{ + return E_NOTIMPL; +} + +// Get a new ICDValue instance to represent the referent of this object ref. +// Arguments: +// output: ppValue - the new ICDValue instance +// Return Value: S_OK on success or E_INVALIDARG +HRESULT CordbReferenceValue::Dereference(ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + // Can't dereference literal refs. + if (m_isLiteral) + return E_INVALIDARG; + + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + + if (m_continueCounterLastSync != m_appdomain->GetProcess()->m_continueCounter) + { + IfFailRet(InitRef(MemoryRange(NULL, 0))); + } + + EX_TRY + { + // We may know ahead of time (depending on the reference type) if + // the reference is bad. + if ((m_info.objRefBad) || (m_info.objRef == NULL)) + { + ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE); + } + + hr = DereferenceCommon(m_appdomain, m_type, m_realTypeOfTypedByref, &m_info, ppValue); + } + EX_CATCH_HRESULT(hr); + return hr; + +} + +//----------------------------------------------------------------------------- +// Common helper to dereferefence. +// Parameters: +// pAppDomain, pType, pInfo - necessary paramters to create the value +// pRealTypeOfTypedByref - type for a potential TypedByRef. Can be NULL if we know +// that we're not a typed-byref (this is true if we're definitely an object handle) +// ppValue - outparameter for newly created value. This will get an Ext AddRef. +//----------------------------------------------------------------------------- +HRESULT CordbReferenceValue::DereferenceCommon( + CordbAppDomain * pAppDomain, + CordbType * pType, + CordbType * pRealTypeOfTypedByref, + DebuggerIPCE_ObjectData * pInfo, + ICorDebugValue **ppValue +) +{ + INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); + + // pCachedObject may be NULL if we're not caching. + _ASSERTE(pType != NULL); + _ASSERTE(pAppDomain != NULL); + _ASSERTE(pInfo != NULL); + _ASSERTE(ppValue != NULL); + + HRESULT hr = S_OK; + *ppValue = NULL; // just to be safe. + + switch(pType->m_elementType) + { + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_STRING: + { + LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type class/object/string\n")); + // An object value (possibly a string value, too.) If the class of this object is a value class, + // then we have a reference to a boxed object. So we create a box instead of an object value. + bool isBoxedVCObject = false; + + if ((pType->m_pClass != NULL) && (pType->m_elementType != ELEMENT_TYPE_STRING)) + { + EX_TRY + { + isBoxedVCObject = pType->m_pClass->IsValueClass(); + } + EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + { + return hr; + } + } + + if (isBoxedVCObject) + { + TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize); + EX_TRY + { + RSSmartPtr<CordbBoxValue> pBoxValue(new CordbBoxValue( + pAppDomain, + pType, + remoteValue, + (ULONG32)pInfo->objSize, + pInfo->objOffsetToVars)); + pBoxValue->ExternalAddRef(); + *ppValue = (ICorDebugValue*)(ICorDebugBoxValue*)pBoxValue; + } + EX_CATCH_HRESULT(hr); + } + else + { + RSSmartPtr<CordbObjectValue> pObj; + TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize); + // Note: we call Init() by default when we create (or refresh) a reference value, so we + // never have to do it again. + EX_TRY + { + pObj.Assign(new CordbObjectValue(pAppDomain, pType, remoteValue, pInfo)); + IfFailThrow(pObj->Init()); + + pObj->ExternalAddRef(); + *ppValue = static_cast<ICorDebugValue*>( static_cast<ICorDebugObjectValue*>(pObj) ); + } + EX_CATCH_HRESULT(hr); + } // boxed? + + break; + } + + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + { + LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type array/szarray\n")); + TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize); // sizeof(void *)? + EX_TRY + { + RSSmartPtr<CordbArrayValue> pArrayValue(new CordbArrayValue( + pAppDomain, + pType, + pInfo, + remoteValue)); + + IfFailThrow(pArrayValue->Init()); + + pArrayValue->ExternalAddRef(); + *ppValue = (ICorDebugValue*)(ICorDebugArrayValue*)pArrayValue; + } + EX_CATCH_HRESULT(hr); + + break; + } + + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_PTR: + { + //_ASSERTE(pInfo->objToken.IsNull()); // can't get this type w/ an object handle + + LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type byref/ptr\n")); + CordbType *ptrType; + pType->DestUnaryType(&ptrType); + + CorElementType et = ptrType->m_elementType; + + if (et == ELEMENT_TYPE_VOID) + { + *ppValue = NULL; + return CORDBG_S_VALUE_POINTS_TO_VOID; + } + + TargetBuffer remoteValue(pInfo->objRef, GetSizeForType(ptrType, kUnboxed)); + // Create a value for what this reference points to. Note: + // this could be almost any type of value. + EX_TRY + { + CordbValue::CreateValueByType( + pAppDomain, + ptrType, + false, + remoteValue, + MemoryRange(NULL, 0), // local value + NULL, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + + break; + } + + case ELEMENT_TYPE_TYPEDBYREF: + { + //_ASSERTE(pInfo->objToken.IsNull()); // can't get this type w/ an object handle + _ASSERTE(pRealTypeOfTypedByref != NULL); + + LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type typedbyref\n")); + + TargetBuffer remoteValue(pInfo->objRef, sizeof(void *)); + // Create the value for what this reference points + // to. + EX_TRY + { + CordbValue::CreateValueByType( + pAppDomain, + pRealTypeOfTypedByref, + false, + remoteValue, + MemoryRange(NULL, 0), // local value + NULL, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + + break; + } + + case ELEMENT_TYPE_FNPTR: + // Function pointers cannot be dereferenced; only the pointer value itself + // may be inspected--not what it points to. + *ppValue = NULL; + return CORDBG_E_VALUE_POINTS_TO_FUNCTION; + + default: + LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: Fail!\n")); + _ASSERTE(!"Bad reference type!"); + hr = E_FAIL; + break; + } + + return hr; +} + +// static helper to build a CordbReferenceValue from a general variable home. +// We can find the CordbType from the object instance. +HRESULT CordbReferenceValue::Build(CordbAppDomain * appdomain, + CordbType * type, + TargetBuffer remoteValue, + MemoryRange localValue, + VMPTR_OBJECTHANDLE vmObjectHandle, + EnregisteredValueHomeHolder * ppRemoteRegAddr, + CordbReferenceValue** ppValue) +{ + HRESULT hr = S_OK; + + // We can find the AD from an object handle (but not a normal object), so the AppDomain may + // be NULL if if it's an OH. + //_ASSERTE((appdomain != NULL) || objectRefsInHandles); + + // A reference, possibly to an object or value class + // Weak by default + EX_TRY + { + RSSmartPtr<CordbReferenceValue> pRefValue(new CordbReferenceValue(appdomain, + type, + localValue, + remoteValue, + ppRemoteRegAddr, + vmObjectHandle)); + IfFailThrow(pRefValue->InitRef(localValue)); + + pRefValue->InternalAddRef(); + *ppValue = pRefValue; + } + EX_CATCH_HRESULT(hr) + return hr; +} + +//----------------------------------------------------------------------------- +// Static helper to build a CordbReferenceValue from a GCHandle +// The LS can actually determine an AppDomain from an OBJECTHandles, however, the RS +// should already have this infromation too, so we pass it in. +// We also supply the AppDomain here because it provides the CordbValue with +// process affinity. +// Note that the GC handle may point to a NULL reference, in which case we should still create +// an appropriate ICorDebugReferenceValue for which IsNull returns TRUE. +//----------------------------------------------------------------------------- +HRESULT CordbReferenceValue::BuildFromGCHandle( + CordbAppDomain *pAppDomain, + VMPTR_OBJECTHANDLE gcHandle, + ICorDebugReferenceValue ** pOutRef +) +{ + _ASSERTE(pAppDomain != NULL); + _ASSERTE(pOutRef != NULL); + + CordbProcess * pProc; + pProc = pAppDomain->GetProcess(); + INTERNAL_SYNC_API_ENTRY(pProc); + + HRESULT hr = S_OK; + + *pOutRef = NULL; + + // Make sure we even have a GC handle. + // Also, We may have a handle, but its contents may be null. + if (gcHandle.IsNull()) + { + // We've seen this assert fire in the wild, but have never gotten a repro. + // so we'll include a runtime check to avoid the AV. + _ASSERTE(false || !"We got a bad reference value."); + return CORDBG_E_BAD_REFERENCE_VALUE; + } + + // Now that we've got an AppDomain, we can go ahead and create the reference value normally. + + RSSmartPtr<CordbReferenceValue> pRefValue; + TargetBuffer remoteValue; + EX_TRY + { + remoteValue.Init(pProc->GetDAC()->GetHandleAddressFromVmHandle(gcHandle), sizeof(void *)); + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + hr = CordbReferenceValue::Build( + pAppDomain, + NULL, // unknown type + remoteValue, // CORDB_ADDRESS remoteAddress, + MemoryRange(NULL, 0), + gcHandle, // objectRefsInHandles, + NULL, // EnregisteredValueHome * pRemoteRegAddr, + &pRefValue); + + if (SUCCEEDED(hr)) + { + pRefValue->QueryInterface(__uuidof(ICorDebugReferenceValue), (void**)pOutRef); + } + + return hr; +} + +// Helper function for SanityCheckPointer. Make an attempt to read memory at the address which is the value +// of the reference. +// Arguments: none +// Notes: +// - Throws +// - m_info.objRefBad must be set to true before calling this function. If we throw, we'll +// never end up setting m_info.objRefBad, but throwing indicates that the reference is +// indeed bad. Only if we exit normally will we end up setting m_info.objRefBad to false. +void CordbReferenceValue::TryDereferencingTarget() +{ + _ASSERTE(!!m_info.objRefBad == true); + // First get the referent type + CordbType * pReferentType; + m_type->DestUnaryType(&pReferentType); + + // Next get the size + ULONG32 dataSize, sizeToRead; + IfFailThrow(pReferentType->GetUnboxedObjectSize(&dataSize)); + if (dataSize <= 0) + sizeToRead = 1; // Read at least one byte. + else if (dataSize >= 8) + sizeToRead = 8; // Read at most eight bytes--this is just a perf improvement. Even if we read + // all the bytes, we are only able to determine that we can read those bytes, + // we can't really tell if the data we are reading is actually the data we + // want. + else sizeToRead = dataSize; + + // Now see if we can read from the address where the object is supposed to be + BYTE dummy[8]; + + // Get a target buffer with the remote address and size of the object--since we don't know if the + // address if valid, this could throw or return a size that's complete garbage + TargetBuffer object(m_info.objRef, sizeToRead); + + // now read target memory. This may throw ... + GetProcess()->SafeReadBuffer(object, dummy); + +} // CordbReferenceValue::TryDereferencingTarget + +// Do a sanity check on the pointer which is the value of the object reference. We can't efficiently ensure that +// the pointer is really good, so we settle for a quick check just to make sure the memory at the address is +// readable. We're actually just checking that we can dereference the pointer. +// Arguments: +// input: type - the type of the pointer to which the object reference points. +// output: none, but fills in m_info.objRefBad +// Note: Throws +void CordbReferenceValue::SanityCheckPointer (CorElementType type) +{ + m_info.objRefBad = TRUE; + if (type != ELEMENT_TYPE_FNPTR) + { + // We should never dereference a function pointer, so all references + // are considered "bad." + if (m_info.objRef != NULL) + { + if (type == ELEMENT_TYPE_PTR) + { + // The only way to tell if the reference in PTR is bad or + // not is to try to deref the thing. + TryDereferencingTarget(); + } + } // !m_info.m_basicData.m_vmObject.IsNull() + // else Null refs are considered "bad". + } // type != ELEMENT_TYPE_FNPTR + + // we made it without throwing, so we'll assume (perhaps wrongly) that the ref is good + m_info.objRefBad = FALSE; + +} // CordbReferenceValue::SanityCheckPointer + +// get information about the reference when it's not an object address but another kind of pointer type: +// ELEMENT_TYPE_BYREF, ELEMENT_TYPE_PTR or ELEMENT_TYPE_FNPTR +// Arguments: +// input: type - type of the referent +// localValue - starting address and length of a local buffer containing the object ref +// Notes: +// - fills in the m_info field of "this" +// - Throws (errors from reading process memory) +void CordbReferenceValue::GetPointerData(CorElementType type, MemoryRange localValue) +{ + HRESULT hr = S_OK; + // Fill in the type since we will not be getting it from the DAC + m_info.objTypeData.elementType = type; + + // First get the objRef + if (localValue.StartAddress() != NULL) + { + // localValue represents a buffer containing a copy of the objectRef that exists locally. It could be a + // component of a container type residing within a local cached copy belonging to some other + // Cordb*Value instance representing the container type. In this case it will be a field, array + // element, or referent of a different object reference for that other Cordb*Value instance. It + // could also be a pointer to the value of a local register display of the frame from which this object + // ref comes. + + // For example, if we have a value class (represented by a CordbVCObject instance) with a field + // that is an object pointer, localValue will contain a pointer to that field in the local + // cache of the CordbVCObjectValue instance (CordbVCObjectValue::m_pObjectCopy). + + // Note, though, that pLocalValue holds the address of a target object. We will cache + // the contents of pLocalValue (the object ref) here for efficiency of read access, but if we + // want to set the reference later (e.g., we want the object ref to point to NULL instead of an + // object), we'll have to set the object ref in the target, not our local copy. + // Host memory Target memory + // --------------- | + // CordbVCObjectValue::m_copyOfObject ----> | | + // | ... | | + // | | + // |---------------| | Object + // localAddress ---> | object addr |-------------> -------------- + // |---------------| | ---> | | + // | ... | | | | + // --------------- | | -------------- + // | + // CordbReferenceValue::m_info.objRef ---> --------------- | | + // | object addr |--------- + // --------------- | + + _ASSERTE(localValue.Size() == sizeof(void *)); + localCopy(&(m_info.objRef), localValue); + } + else + { + // we have a non-local location, so we'll get the value of the ref from its home + + // do some preinitialization in case we get an exception + EX_TRY + { + m_valueHome.m_pHome->GetValue(MemoryRange(&(m_info.objRef), sizeof(void*))); // throws + } + EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + { + m_info.objRef = NULL; + m_info.objRefBad = TRUE; + ThrowHR(hr); + } + } + + EX_TRY + { + // If we made it this far, we need to sanity check the pointer--we'll just see if we can + // read at that address + SanityCheckPointer(type); + } + EX_CATCH_HRESULT(hr); // we don't need to do anything here, m_info.objRefBad will have been set to true + +} // CordbReferenceValue::GetPointerData + +// Helper function for CordbReferenceValue::GetObjectData: Sets default values for the fields in pObjectData +// before processing begins. Not all will necessarily be initialized during processing. +// Arguments: +// input: objectType - type of the referent of the objRef being examined +// output: pObjectData - information about the reference to be initialized +void PreInitObjectData(DebuggerIPCE_ObjectData * pObjectData, void * objAddress, CorElementType objectType) +{ + _ASSERTE(pObjectData != NULL); + + memset(pObjectData, 0, sizeof(DebuggerIPCE_ObjectData)); + pObjectData->objRef = objAddress; + pObjectData->objTypeData.elementType = objectType; + +} // PreInitObjectData + +// get basic object specific data when a reference points to an object, plus extra data if the object is an +// array or string +// Arguments: +// input: pProcess - process to which the object belongs +// objectAddress - pointer to the TypedByRef object (this is the value of the object reference +// or handle. +// type - the type of the object referenced +// vmAppDomain - appdomain to which the object belongs +// output: pInfo - filled with information about the object to which the TypedByRef refers. +// Note: Throws +/* static */ +void CordbReferenceValue::GetObjectData(CordbProcess * pProcess, + void * objectAddress, + CorElementType type, + VMPTR_AppDomain vmAppdomain, + DebuggerIPCE_ObjectData * pInfo) +{ + IDacDbiInterface *pInterface = pProcess->GetDAC(); + CORDB_ADDRESS objTargetAddr = PTR_TO_CORDB_ADDRESS(objectAddress); + + // make sure we don't end up with old garbage values in case the reference is bad + PreInitObjectData(pInfo, objectAddress, type); + + pInterface->GetBasicObjectInfo(objTargetAddr, type, vmAppdomain, pInfo); + + if (!pInfo->objRefBad) + { + // for certain referent types, we need a bit more information: + if (pInfo->objTypeData.elementType == ELEMENT_TYPE_STRING) + { + pInterface->GetStringData(objTargetAddr, pInfo); + } + else if ((pInfo->objTypeData.elementType == ELEMENT_TYPE_ARRAY) || + (pInfo->objTypeData.elementType == ELEMENT_TYPE_SZARRAY)) + { + pInterface->GetArrayData(objTargetAddr, pInfo); + } + } + +} // CordbReferenceValue::GetObjectData + +// get information about a TypedByRef object when the reference is the address of a TypedByRef structure. +// Arguments: +// input: pProcess - process to which the object belongs +// pTypedByRef - pointer to the TypedByRef object (this is the value of the object reference or +// handle. +// type - the type of the object referenced +// vmAppDomain - appdomain to which the object belongs +// output: pInfo - filled with information about the object to which the TypedByRef refers. +// Note: Throws +/* static */ +void CordbReferenceValue::GetTypedByRefData(CordbProcess * pProcess, + CORDB_ADDRESS pTypedByRef, + CorElementType type, + VMPTR_AppDomain vmAppDomain, + DebuggerIPCE_ObjectData * pInfo) +{ + + // make sure we don't end up with old garbage values since we don't set all the values for TypedByRef objects + PreInitObjectData(pInfo, CORDB_ADDRESS_TO_PTR(pTypedByRef), type); + + // Though pTypedByRef is the value of the object ref represented by an instance of CordbReferenceValue, + // it is not the address of an object, as we would ordinarily expect. Instead, in the special case of + // TypedByref objects, it is actually the address of the TypedByRef struct which contains the + // type and the object address. + + pProcess->GetDAC()->GetTypedByRefInfo(pTypedByRef, vmAppDomain, pInfo); +} // CordbReferenceValue::GetTypedByRefData + +// get the address of the object referenced +// Arguments: none +// Return Value: the address of the object referenced (i.e., the value of the object ref) +// Note: Throws +void * CordbReferenceValue::GetObjectAddress(MemoryRange localValue) +{ + void * objectAddress; + if (localValue.StartAddress() != NULL) + { + // the object ref comes from a local cached copy + _ASSERTE(localValue.Size() == sizeof(void *)); + memcpy(&objectAddress, localValue.StartAddress(), localValue.Size()); + } + else + { + _ASSERTE(m_valueHome.m_pHome != NULL); + m_valueHome.m_pHome->GetValue(MemoryRange(&objectAddress, sizeof(void *))); // throws + } + return objectAddress; +} // CordbReferenceValue::GetObjectAddress + +// update type information after initializing -- when we initialize, we may get more exact type information +// than we previously had +// Arguments: none--uses and updates data members +// Note: Throws +void CordbReferenceValue::UpdateTypeInfo() +{ + // If the object type that we got back is different than the one we sent, then it means that we + // orignally had a CLASS and now have something more specific, like a SDARRAY, MDARRAY, or STRING or + // a constructed type. + // Update our signature accordingly, which is okay since we always have a copy of our sig. This + // ensures that the reference's signature accurately reflects what the Runtime knows it's pointing + // to. + // + // GENERICS: do this for all types: for example, an array might have been discovered to be a more + // specific kind of array (String[] where an Object[] was expected). + CordbType *newtype; + + IfFailThrow(CordbType::TypeDataToType(m_appdomain, &m_info.objTypeData, &newtype)); + + _ASSERTE(newtype->m_elementType != ELEMENT_TYPE_VALUETYPE); + m_type.Assign(newtype); // implicit Release + AddRef + + // For typed-byref's the act of dereferencing the object also reveals to us + // what the "real" type of the object is... + if (m_info.objTypeData.elementType == ELEMENT_TYPE_TYPEDBYREF) +{ + IfFailThrow(CordbType::TypeDataToType(m_appdomain, + &m_info.typedByrefInfo.typedByrefType, + &m_realTypeOfTypedByref)); + } +} // CordbReferenceValue::UpdateTypeInfo + +// Initialize this CordbReferenceValue. This may involve inspecting the LS to get information about the +// referent. +// Arguments: +// input: localValue - buffer address and size of the RS location of the reference. (This may be NULL +// if the reference didn't come from a local cached copy. See +// code:CordbReferenceValue::GetPointerData for further explanation of local locations.) +// Return Value: S_OK on success or E_INVALIDARG or write process memory errors on failure + +HRESULT CordbReferenceValue::InitRef(MemoryRange localValue) +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); + + HRESULT hr = S_OK; + CordbProcess * pProcess = GetProcess(); + + // Simple init needed for literal refs. Literals may have a null process / appdomain ptr. + if (m_isLiteral) + { + _ASSERTE(m_type != NULL); + m_info.objTypeData.elementType = m_type->m_elementType; + return hr; + } + + _ASSERTE((pProcess->GetShim() == NULL) || pProcess->GetSynchronized()); + + // If the helper thread is dead, then pretend this is a bad reference. + if (GetProcess()->m_helperThreadDead) + { + m_info.objRef = NULL; + m_info.objRefBad = TRUE; + return hr; + } + + m_continueCounterLastSync = pProcess->m_continueCounter; + + // If no type provided, then it's b/c we're a class and we'll get the type when we get Created. + CorElementType type = (m_type != NULL) ? (m_type->m_elementType) : ELEMENT_TYPE_CLASS; + _ASSERTE (type != ELEMENT_TYPE_GENERICINST); + _ASSERTE (type != ELEMENT_TYPE_VAR); + _ASSERTE (type != ELEMENT_TYPE_MVAR); + + EX_TRY + { + if ((type == ELEMENT_TYPE_BYREF) || + (type == ELEMENT_TYPE_PTR) || + (type == ELEMENT_TYPE_FNPTR)) + { + // we know the size is just the size of a pointer, so we can just read process memory to get the + // information we need + GetPointerData(type, localValue); + } + else // we have to get more information about the object from the DAC + { + if (type == ELEMENT_TYPE_TYPEDBYREF) + { + _ASSERTE(m_valueHome.m_pHome != NULL); + GetTypedByRefData(pProcess, + m_valueHome.m_pHome->GetAddress(), + type, + m_appdomain->GetADToken(), + &m_info); + } + else + { + GetObjectData(pProcess, GetObjectAddress(localValue), type, m_appdomain->GetADToken(), &m_info); + } + + // if we got (what we believe is probably) a good reference, we should update the type info + if (!m_info.objRefBad) + { + // we may have gotten back a more specific type than we had previously + UpdateTypeInfo(); + } + } + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbReferenceValue::InitRef + +/* ------------------------------------------------------------------------- * + * Object Value class + * ------------------------------------------------------------------------- */ + + +// validate a CordbObjectValue to ensure it hasn't been neutered +#define COV_VALIDATE_OBJECT() do { \ + BOOL bValid; \ + HRESULT hr; \ + if (FAILED(hr = IsValid(&bValid))) \ + return hr; \ + \ + if (!bValid) \ + { \ + return CORDBG_E_INVALID_OBJECT; \ + } \ + }while(0) + +// constructor +// Arguments: +// input: pAppDomain - the appdomain to which the object belongs +// pType - the type of the object +// remoteValue - the LS address and size of the object +// pObjectData - other information about the object, most importantly, the offset to the +// fields of the object +CordbObjectValue::CordbObjectValue(CordbAppDomain * pAppdomain, + CordbType * pType, + TargetBuffer remoteValue, + DebuggerIPCE_ObjectData *pObjectData ) + : CordbValue(pAppdomain, pType, remoteValue.pAddress, + false, pAppdomain->GetProcess()->GetContinueNeuterList()), + m_info(*pObjectData), + m_pObjectCopy(NULL), m_objectLocalVars(NULL), m_stringBuffer(NULL), + m_valueHome(pAppdomain->GetProcess(), remoteValue), + m_fIsExceptionObject(FALSE), m_fIsRcw(FALSE) +{ + _ASSERTE(pAppdomain != NULL); + + m_size = m_info.objSize; + + HRESULT hr = S_FALSE; + + ALLOW_DATATARGET_MISSING_MEMORY + ( + hr = IsExceptionObject(); + ); + + if (hr == S_OK) + m_fIsExceptionObject = TRUE; + + hr = S_FALSE; + ALLOW_DATATARGET_MISSING_MEMORY + ( + hr = IsRcw(); + ); + + if (hr == S_OK) + m_fIsRcw = TRUE; +} // CordbObjectValue::CordbObjectValue + +// destructor +CordbObjectValue::~CordbObjectValue() +{ + DTOR_ENTRY(this); + + _ASSERTE(IsNeutered()); +} // CordbObjectValue::~CordbObjectValue + +void CordbObjectValue::Neuter() +{ + // Destroy the copy of the object. + if (m_pObjectCopy != NULL) + { + delete [] m_pObjectCopy; + m_pObjectCopy = NULL; + } + + CordbValue::Neuter(); +} // CordbObjectValue::Neuter + +HRESULT CordbObjectValue::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugObjectValue*>(this)); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugObjectValue) + { + *pInterface = static_cast<ICorDebugObjectValue*>(this); + } + else if (id == IID_ICorDebugObjectValue2) + { + *pInterface = static_cast<ICorDebugObjectValue2*>(this); + } + else if (id == IID_ICorDebugGenericValue) + { + *pInterface = static_cast<ICorDebugGenericValue*>(this); + } + else if (id == IID_ICorDebugHeapValue) + { + *pInterface = static_cast<ICorDebugHeapValue*>(this); + } + else if (id == IID_ICorDebugHeapValue2) + { + *pInterface = static_cast<ICorDebugHeapValue2*>(this); + } + else if (id == IID_ICorDebugHeapValue3) + { + *pInterface = static_cast<ICorDebugHeapValue3*>(this); + } + else if ((id == IID_ICorDebugStringValue) && + (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING)) + { + *pInterface = static_cast<ICorDebugStringValue*>(this); + } + else if (id == IID_ICorDebugExceptionObjectValue && m_fIsExceptionObject) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugExceptionObjectValue*>(this)); + } + else if (id == IID_ICorDebugComObjectValue && m_fIsRcw) + { + *pInterface = static_cast<ICorDebugComObjectValue*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugObjectValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbObjectValue::QueryInterface + +// gets the type of the object +// Arguments: +// output: pType - the type of the value. The caller must guarantee that pType is non-null. +// Return Value: S_OK on success, E_INVALIDARG on failure +HRESULT CordbObjectValue::GetType(CorElementType *pType) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + return (CordbValue::GetType(pType)); +} // CordbObjectValue::GetType + +// gets the size of the object +// Arguments: +// output: pSize - the size of the value. The caller must guarantee that pSize is non-null. +// Return Value: S_OK on success, E_INVALIDARG on failure +HRESULT CordbObjectValue::GetSize(ULONG32 *pSize) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + return (CordbValue::GetSize(pSize)); +} // CordbObjectValue::GetSize + +// gets the size of the object +// Arguments: +// output: pSize - the size of the value. The caller must guarantee that pSize is non-null. +// Return Value: S_OK on success, E_INVALIDARG on failure +HRESULT CordbObjectValue::GetSize64(ULONG64 *pSize) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + return (CordbValue::GetSize64(pSize)); +} // CordbObjectValue::GetSize64 + + +// gets the remote (LS) address of the object. This may return NULL if the +// object is a literal or resides in a register. +// Arguments: +// output: pAddress - the LS address (the contents should not be null since objects +// aren't enregistered nor are they fields or elements of other +// types). The caller must ensure that pAddress is not null. +// Return Value: S_OK on success or E_INVALIDARG if pAddress is null +HRESULT CordbObjectValue::GetAddress(CORDB_ADDRESS *pAddress) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + COV_VALIDATE_OBJECT(); + VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *); + + *pAddress = m_valueHome.GetAddress(); + return (S_OK); +} // CordbObjectValue::GetAddress + +HRESULT CordbObjectValue::CreateBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + COV_VALIDATE_OBJECT(); + + return (CordbValue::CreateBreakpoint(ppBreakpoint)); +} + +// determine if "this" is still valid (i.e., not neutered) +// Arguments: +// output: pfIsValid - true iff "this" is still not neutered +// Return Value: S_OK or E_INVALIDARG +HRESULT CordbObjectValue::IsValid(BOOL * pfIsValid) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pfIsValid, BOOL *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // We're neutered on continue, so we're valid up until the time we're neutered + (*pfIsValid) = TRUE; + return S_OK; +} + +HRESULT CordbObjectValue::CreateRelocBreakpoint( + ICorDebugValueBreakpoint **ppBreakpoint) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **); + + COV_VALIDATE_OBJECT(); + + return E_NOTIMPL; +} + +/* +* Creates a handle of the given type for this heap value. +* +* Not Implemented In-Proc. +*/ +HRESULT CordbObjectValue::CreateHandle( + CorDebugHandleType handleType, + ICorDebugHandleValue ** ppHandle) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbValue::InternalCreateHandle(handleType, ppHandle); +} // CreateHandle + +// Get class information for this object +// Arguments: +// output: ppClass - ICDClass instance for this object +// Return Value: S_OK if success, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM on failure +HRESULT CordbObjectValue::GetClass(ICorDebugClass **ppClass) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + if (m_type->m_pClass == NULL) + { + if (FAILED(hr = m_type->Init(FALSE))) + return hr; + } + + _ASSERTE(m_type->m_pClass); + *ppClass = (ICorDebugClass*) m_type->m_pClass; + + if (*ppClass != NULL) + (*ppClass)->AddRef(); + + return hr; +} // CordbObjectValue::GetClass + + + + + +//----------------------------------------------------------------------------- +// +// Public API to get instance field of the given type in the object and returns an ICDValue for it. +// +// Arguments: +// pType - The type containing the field token. +// fieldDef - The field's metadata def. +// ppValue - OUT: the ICDValue for the field. +// +// Returns: +// S_OK on success. E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, CORDBG_E_FIELD_NOT_INSTANCE or OOM on +// failure +// +// Notes: +// This is for instance fields only. +// Lookup on code:CordbType::GetStaticFieldValue to get static fields. +// This is generics aware. +HRESULT CordbObjectValue::GetFieldValueForType(ICorDebugType * pType, + mdFieldDef fieldDef, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pType, ICorDebugType *); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + COV_VALIDATE_OBJECT(); + + CordbType * pCordbType = NULL; + HRESULT hr = S_OK; + + EX_TRY + { + BOOL fSyncBlockField = FALSE; + SIZE_T fldOffset; + + // + // <TODO>@todo: need to ensure that pType is really on the class + // hierarchy of m_class!!!</TODO> + // + if (pType == NULL) + { + pCordbType = m_type; + } + else + { + pCordbType = static_cast<CordbType *>(pType); + } + + // Validate the token. + if (pCordbType->m_pClass == NULL) + { + ThrowHR(E_INVALIDARG); + } + IMetaDataImport * pImport = pCordbType->m_pClass->GetModule()->GetMetaDataImporter(); + + if (!pImport->IsValidToken(fieldDef)) + { + ThrowHR(E_INVALIDARG); + } + + FieldData * pFieldData; + + #ifdef _DEBUG + pFieldData = NULL; + #endif + + hr = pCordbType->GetFieldInfo(fieldDef, &pFieldData); + + // If we couldn't get field info because the field was added with EnC + if (hr == CORDBG_E_ENC_HANGING_FIELD) + { + // The instance field hangs off the syncblock, get its address + hr = pCordbType->m_pClass->GetEnCHangingField(fieldDef, &pFieldData, this); + + if (SUCCEEDED(hr)) + { + fSyncBlockField = TRUE; + } + } + + if (SUCCEEDED(hr)) + { + _ASSERTE(pFieldData != NULL); + + if (pFieldData->m_fFldIsStatic) + { + ThrowHR(CORDBG_E_FIELD_NOT_INSTANCE); + } + + // Compute the remote address, too, so that SetValue will work. + // Note that if pFieldData is a syncBlock field, fldOffset will have been cooked + // to produce the correct result here. + _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset()); + fldOffset = pFieldData->GetInstanceOffset(); + + CordbModule * pModule = pCordbType->m_pClass->GetModule(); + + SigParser sigParser; + IfFailThrow(pFieldData->GetFieldSignature(pModule, &sigParser)); + + CordbType * pFieldType; + IfFailThrow(CordbType::SigToType(pModule, &sigParser, &(pCordbType->m_inst), &pFieldType)); + + ULONG32 size = GetSizeForType(pFieldType, kUnboxed); + + void * localAddr = NULL; + if (!fSyncBlockField) + { + // verify that the field starts and ends before the end of m_pObjectCopy + _ASSERTE(m_info.objOffsetToVars + fldOffset < m_size); + _ASSERTE(m_info.objOffsetToVars + fldOffset + size <= m_size); + localAddr = m_objectLocalVars + fldOffset; + } + + // pass the computed local field address, but don't claim we have a local addr if the fldOffset + // has been cooked to point us to a sync block field. + m_valueHome.CreateInternalValue(pFieldType, + m_info.objOffsetToVars + fldOffset, + localAddr, + size, + ppValue); // throws + } + + // If we can't get it b/c it's a constant, then say so. + hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef); + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbObjectValue::GetFieldValueForType + +// Public implementation of ICorDebugObjectValue::GetFieldValue +// Arguments: +// input: pClass - class information for this object +// fieldDef - the field token for the requested field +// output: ppValue - instance of ICDValue created to represent the field +// Return Value: S_OK on success, E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, CORDBG_E_FIELD_NOT_INSTANCE +// or OOM on failure +HRESULT CordbObjectValue::GetFieldValue(ICorDebugClass *pClass, + mdFieldDef fieldDef, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + + COV_VALIDATE_OBJECT(); + + HRESULT hr; + _ASSERTE(m_type); + + if (m_type->m_elementType != ELEMENT_TYPE_CLASS && + m_type->m_elementType != ELEMENT_TYPE_VALUETYPE) + { + return E_INVALIDARG; + } + + // mdFieldDef may specify a field within a base class. mdFieldDef tokens are unique throughout a module. + // So we still need a metadata scope to resolve the mdFieldDef. We can infer the scope from pClass. + // Beware that this Type may be derived from a type in another module, and so the incoming + // fieldDef has to be resolved in the metadata scope of pClass. + + RSExtSmartPtr<CordbType> relevantType; + + // This object has an ICorDebugType which has the type-parameters for generics. + // ICorDebugClass provided by the caller does not have type-parameters. So we resolve that + // by using the provided ICDClass with the type parameters from this object's ICDType. + if (FAILED (hr= m_type->GetParentType((CordbClass *) pClass, &relevantType))) + { + return hr; + } + // Upon exit relevantType will either be the appropriate type for the + // class we're looking for. + + hr = GetFieldValueForType(relevantType, fieldDef, ppValue); + // GetParentType adds one reference to relevantType., Holder dtor releases + return hr; + +} // CordbObjectValue::GetFieldValue + +HRESULT CordbObjectValue::GetVirtualMethod(mdMemberRef memberRef, + ICorDebugFunction **ppFunction) +{ + VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **); + FAIL_IF_NEUTERED(this); + COV_VALIDATE_OBJECT(); + + return E_NOTIMPL; +} // CordbObjectValue::GetVirtualMethod + +HRESULT CordbObjectValue::GetVirtualMethodAndType(mdMemberRef memberRef, + ICorDebugFunction **ppFunction, + ICorDebugType **ppType) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **); + VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugType **); + + COV_VALIDATE_OBJECT(); + + return E_NOTIMPL; +} // CordbObjectValue::GetVirtualMethodAndType + +HRESULT CordbObjectValue::GetContext(ICorDebugContext **ppContext) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppContext, ICorDebugContext **); + + COV_VALIDATE_OBJECT(); + + return E_NOTIMPL; +} // CordbObjectValue::GetContext + +// determines whether this represents a value class-- always returns false +// Arguments: +// output: pfIsValueClass - always false; CordbVCObjectValue is used to represent +// value classes, so by definition, a CordbObjectValue instance +// does not represent a value class +// Return Value: S_OK +// +HRESULT CordbObjectValue::IsValueClass(BOOL * pfIsValueClass) +{ + FAIL_IF_NEUTERED(this); + COV_VALIDATE_OBJECT(); + + if (pfIsValueClass) // don't assign to a null pointer! + *pfIsValueClass = FALSE; + + return S_OK; +} // CordbObjectValue::IsValueClass + +HRESULT CordbObjectValue::GetManagedCopy(IUnknown **ppObject) +{ + // GetManagedCopy() is deprecated. In the case where the version of + // the debugger doesn't match the version of the debuggee, the two processes + // might have dangerously different notions of the layout of an object. + + // This function is deprecated + return E_NOTIMPL; +} // CordbObjectValue::GetManagedCopy + +HRESULT CordbObjectValue::SetFromManagedCopy(IUnknown *pObject) +{ + // Deprecated for the same reason as GetManagedCopy() + return E_NOTIMPL; +} // CordbObjectValue::SetFromManagedCopy + +// gets a copy of the value +// Arguments: +// output: pTo - buffer to hold the object copy. The caller must guarantee that this +// is non-null and the buffer is large enough to hold the object +// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure +// +HRESULT CordbObjectValue::GetValue(void *pTo) +{ + FAIL_IF_NEUTERED(this); + COV_VALIDATE_OBJECT(); + + VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true); + + // Copy out the value, which is the whole object. + memcpy(pTo, m_pObjectCopy, m_size); + + return S_OK; +} // CordbObjectValue::GetValue + +HRESULT CordbObjectValue::SetValue(void *pFrom) +{ + // You're not allowed to set a whole object at once. + return E_INVALIDARG; +} // CordbObjectValue::SetValue + +// If this instance of CordbObjectValue is actually a string, get its length +// Arguments: +// output: pcchString - the count of characters in the string +// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure +// Note: if the object is not really a string, the value in pcchString will be garbage on exit +HRESULT CordbObjectValue::GetLength(ULONG32 *pcchString) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pcchString, SIZE_T *); + FAIL_IF_NEUTERED(this); + + _ASSERTE(m_info.objTypeData.elementType == ELEMENT_TYPE_STRING); + + COV_VALIDATE_OBJECT(); + + *pcchString = (ULONG32)m_info.stringInfo.length; + return S_OK; +} // CordbObjectValue::GetLength + +// If this instance of CordbObjectValue represents a string, extract the string and its length. +// If cchString is less than the length of the string, we'll return only the first cchString characters +// but pcchString will still hold the full length. If cchString is more than the string length, we'll +// return only string length characters. +// Arguments: +// input: cchString - the maximum number of characters to return, including NULL terminator +// output: pcchString - the actual length of the string, excluding NULL terminator (this may be greater than cchString) +// szString - a buffer holding the string. The memory for this must be allocated and +// managed by the caller and must have space for at least cchString characters +// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure +HRESULT CordbObjectValue::GetString(ULONG32 cchString, + ULONG32 *pcchString, + __out_ecount_opt(cchString) WCHAR szString[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(szString, WCHAR, cchString, true, true); + VALIDATE_POINTER_TO_OBJECT(pcchString, SIZE_T *); + + _ASSERTE(m_info.objTypeData.elementType == ELEMENT_TYPE_STRING); + + COV_VALIDATE_OBJECT(); + + if ((szString == NULL) || (cchString == 0)) + return E_INVALIDARG; + + // Add 1 to include null terminator + SIZE_T len = m_info.stringInfo.length + 1; + + // adjust length to the size of the buffer + if (cchString < len) + len = cchString; + + memcpy(szString, m_stringBuffer, len * 2); + *pcchString = (ULONG32)m_info.stringInfo.length; + + return S_OK; +} // CordbObjectValue::GetString + +// Initialize an instance of CordbObjectValue, filling in the m_pObjectCopy field and, if appropriate, +// string information. +// Arguments: none +// ReturnValue: S_OK on success or E_OUTOFMEMORY or read process memory errors on failure +HRESULT CordbObjectValue::Init() +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); // + LOG((LF_CORDB,LL_INFO1000,"Invoking COV::Init\n")); + + HRESULT hr = S_OK; + + _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_GENERICINST); + _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_VAR); + _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_MVAR); + + // Copy the entire object over to this process. + m_pObjectCopy = new (nothrow) BYTE[m_size]; + + if (m_pObjectCopy == NULL) + return E_OUTOFMEMORY; + + EX_TRY + { + m_valueHome.GetValue(MemoryRange(m_pObjectCopy, m_size)); // throws + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + // Compute offsets in bytes to the locals and to a string if this is a + // string object. + m_objectLocalVars = m_pObjectCopy + m_info.objOffsetToVars; + + if (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING) + m_stringBuffer = m_pObjectCopy + m_info.stringInfo.offsetToStringBase; + + return hr; +} // CordbObjectValue::Init + +// CordbObjectValue::GetThreadOwningMonitorLock +// If a managed thread owns the monitor lock on this object then *ppThread +// will point to that thread and S_OK will be returned. The thread object is valid +// until the thread exits. *pAcquisitionCount will indicate the number of times +// this thread would need to release the lock before it returns to being +// unowned. +// If no managed thread owns the monitor lock on this object then *ppThread +// and pAcquisitionCount will be unchanged and S_FALSE returned. +// If ppThread or pAcquisitionCount is not a valid pointer the result is +// undefined. +// If any error occurs such that it cannot be determined which, if any, thread +// owns the monitor lock on this object then a failing HRESULT will be returned +HRESULT CordbObjectValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(), + GetValueHome()->GetAddress(), + ppThread, + pAcquisitionCount); +} + +// CordbObjectValue::GetMonitorEventWaitList +// Provides an ordered list of threads which are queued on the event associated +// with a monitor lock. The first thread in the list is the first thread which +// will be released by the next call to Monitor.Pulse, the next thread in the list +// will be released on the following call, and so on. +// If this list is non-empty S_OK will be returned, if it is empty S_FALSE +// will be returned (the enumeration is still valid, just empty). +// In either case the enumeration interface is only usable for the duration +// of the current synchronized state, however the threads interfaces dispensed +// from it are valid until the thread exits. +// If ppThread is not a valid pointer the result is undefined. +// If any error occurs such that it cannot be determined which, if any, threads +// are waiting for the monitor then a failing HRESULT will be returned +HRESULT CordbObjectValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(), + GetValueHome()->GetAddress(), + ppThreadEnum); +} + +HRESULT CordbObjectValue::EnumerateExceptionCallStack(ICorDebugExceptionObjectCallStackEnum** ppCallStackEnum) +{ + if (!ppCallStackEnum) + return E_INVALIDARG; + + *ppCallStackEnum = NULL; + + HRESULT hr = S_OK; + CorDebugExceptionObjectStackFrame* pStackFrames = NULL; + + PUBLIC_API_BEGIN(this); + + CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); + + IDacDbiInterface* pDAC = GetProcess()->GetDAC(); + VMPTR_Object vmObj = pDAC->GetObject(objAddr); + + DacDbiArrayList<DacExceptionCallStackData> dacStackFrames; + + pDAC->GetStackFramesFromException(vmObj, dacStackFrames); + int stackFramesLength = dacStackFrames.Count(); + + if (stackFramesLength > 0) + { + pStackFrames = new CorDebugExceptionObjectStackFrame[stackFramesLength]; + for (int index = 0; index < stackFramesLength; ++index) + { + DacExceptionCallStackData& currentDacFrame = dacStackFrames[index]; + CorDebugExceptionObjectStackFrame& currentStackFrame = pStackFrames[index]; + + CordbAppDomain* pAppDomain = GetProcess()->LookupOrCreateAppDomain(currentDacFrame.vmAppDomain); + CordbModule* pModule = pAppDomain->LookupOrCreateModule(currentDacFrame.vmDomainFile); + + hr = pModule->QueryInterface(IID_ICorDebugModule, reinterpret_cast<void**>(¤tStackFrame.pModule)); + _ASSERTE(SUCCEEDED(hr)); + + currentStackFrame.ip = currentDacFrame.ip; + currentStackFrame.methodDef = currentDacFrame.methodDef; + currentStackFrame.isLastForeignExceptionFrame = currentDacFrame.isLastForeignExceptionFrame; + } + } + + CordbExceptionObjectCallStackEnumerator* callStackEnum = new CordbExceptionObjectCallStackEnumerator(GetProcess(), pStackFrames, stackFramesLength); + GetProcess()->GetContinueNeuterList()->Add(GetProcess(), callStackEnum); + + hr = callStackEnum->QueryInterface(IID_ICorDebugExceptionObjectCallStackEnum, reinterpret_cast<void**>(ppCallStackEnum)); + _ASSERTE(SUCCEEDED(hr)); + + PUBLIC_API_END(hr); + + if (pStackFrames) + delete[] pStackFrames; + + return hr; +} + +HRESULT CordbObjectValue::IsExceptionObject() +{ + HRESULT hr = S_OK; + + if (m_info.objTypeData.elementType != ELEMENT_TYPE_CLASS) + { + hr = S_FALSE; + } + else + { + CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); + + if (objAddr == NULL) + { + // object is a literal + hr = S_FALSE; + } + else + { + IDacDbiInterface* pDAC = GetProcess()->GetDAC(); + + VMPTR_Object vmObj = pDAC->GetObject(objAddr); + BOOL fIsException = pDAC->IsExceptionObject(vmObj); + + if (!fIsException) + hr = S_FALSE; + } + } + + return hr; +} + +HRESULT CordbObjectValue::IsRcw() +{ + HRESULT hr = S_OK; + + if (m_info.objTypeData.elementType != ELEMENT_TYPE_CLASS) + { + hr = S_FALSE; + } + else + { + CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); + + if (objAddr == NULL) + { + // object is a literal + hr = S_FALSE; + } + else + { + IDacDbiInterface* pDAC = GetProcess()->GetDAC(); + + VMPTR_Object vmObj = pDAC->GetObject(objAddr); + BOOL fIsRcw = pDAC->IsRcw(vmObj); + + if (!fIsRcw) + hr = S_FALSE; + } + } + + return hr; +} + +HRESULT CordbObjectValue::GetCachedInterfaceTypes( + BOOL bIInspectableOnly, + ICorDebugTypeEnum * * ppInterfacesEnum) +{ +#if !defined(FEATURE_COMINTEROP) + + return E_NOTIMPL; + +#else + + HRESULT hr = S_OK; + + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + VALIDATE_POINTER_TO_OBJECT(ppInterfacesEnum, ICorDebugTypeEnum **); + + _ASSERTE(m_fIsRcw); + + EX_TRY + { + *ppInterfacesEnum = NULL; + + NewArrayHolder<CordbType*> pItfs(NULL); + + // retrieve interface types + DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> dacInterfaces; + + IDacDbiInterface* pDAC = GetProcess()->GetDAC(); + + CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); + VMPTR_Object vmObj = pDAC->GetObject(objAddr); + + // retrieve type info from LS + pDAC->GetRcwCachedInterfaceTypes(vmObj, m_appdomain->GetADToken(), + bIInspectableOnly, &dacInterfaces); + + // synthesize CordbType instances + int cItfs = dacInterfaces.Count(); + if (cItfs > 0) + { + pItfs = new CordbType*[cItfs]; + for (int n = 0; n < cItfs; ++n) + { + hr = CordbType::TypeDataToType(m_appdomain, + &(dacInterfaces[n]), + &pItfs[n]); + } + } + + // build a type enumerator + CordbTypeEnum* pTypeEnum = CordbTypeEnum::Build(m_appdomain, GetProcess()->GetContinueNeuterList(), cItfs, pItfs); + if ( pTypeEnum == NULL ) + { + IfFailThrow(E_OUTOFMEMORY); + } + + (*ppInterfacesEnum) = static_cast<ICorDebugTypeEnum*> (pTypeEnum); + pTypeEnum->ExternalAddRef(); + + } + EX_CATCH_HRESULT(hr); + + return hr; + +#endif +} + +HRESULT CordbObjectValue::GetCachedInterfacePointers( + BOOL bIInspectableOnly, + ULONG32 celt, + ULONG32 *pcEltFetched, + CORDB_ADDRESS * ptrs) +{ +#if !defined(FEATURE_COMINTEROP) + + return E_NOTIMPL; + +#else + + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_fIsRcw); + + if (pcEltFetched == NULL && (ptrs == NULL || celt == 0)) + return E_INVALIDARG; + + HRESULT hr = S_OK; + ULONG32 cItfs = 0; + + // retrieve interface types + + CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); + + DacDbiArrayList<CORDB_ADDRESS> dacItfPtrs; + EX_TRY + { + IDacDbiInterface* pDAC = GetProcess()->GetDAC(); + VMPTR_Object vmObj = pDAC->GetObject(objAddr); + + // retrieve type info from LS + pDAC->GetRcwCachedInterfacePointers(vmObj, bIInspectableOnly, &dacItfPtrs); + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + // synthesize CordbType instances + cItfs = (ULONG32)dacItfPtrs.Count(); + + if (pcEltFetched != NULL && ptrs == NULL) + { + *pcEltFetched = cItfs; + return S_OK; + } + + if (pcEltFetched != NULL) + { + *pcEltFetched = (cItfs <= celt ? cItfs : celt); + } + + if (ptrs != NULL && *pcEltFetched > 0) + { + for (ULONG32 i = 0; i < *pcEltFetched; ++i) + ptrs[i] = dacItfPtrs[i]; + } + + return (*pcEltFetched == celt ? S_OK : S_FALSE); + +#endif +} + + +/* ------------------------------------------------------------------------- * + * Value Class Object + * ------------------------------------------------------------------------- */ + +// constructor +// Arguments: +// input: pAppdomain - app domain to which the value belongs +// pType - type information for the value +// remoteValue - buffer describing the target location of the value +// ppRemoteRegAddr - describes the register information if the value resides in a register +// Note: May throw E_OUTOFMEMORY +CordbVCObjectValue::CordbVCObjectValue(CordbAppDomain * pAppdomain, + CordbType * pType, + TargetBuffer remoteValue, + EnregisteredValueHomeHolder * ppRemoteRegAddr) + + // We'd like to neuter this on Continue (not just exit), but it may be a breaking change, + // especially for ValueTypes that don't have any GC refs in them. + : CordbValue(pAppdomain, + pType, + remoteValue.pAddress, + false, + pAppdomain->GetSweepableExitNeuterList()), + m_pObjectCopy(NULL), + m_pValueHome(NULL) +{ + // instantiate the value home + NewHolder<ValueHome> pHome(NULL); + + if (remoteValue.IsEmpty()) + { + pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr)); + } + else + { + pHome = (new VCRemoteValueHome(pAppdomain->GetProcess(), remoteValue)); + } + m_pValueHome = pHome.GetValue(); // throws + pHome.SuppressRelease(); +} // CordbVCObjectValue::CordbVCObjectValue + +// destructor +CordbVCObjectValue::~CordbVCObjectValue() +{ + DTOR_ENTRY(this); + + _ASSERTE(IsNeutered()); + + // Destroy the copy of the object. + if (m_pObjectCopy != NULL) + { + delete [] m_pObjectCopy; + m_pObjectCopy = NULL; + } + + // destroy the value home + if (m_pValueHome != NULL) + { + delete m_pValueHome; + m_pValueHome = NULL; +} +} // CordbVCObjectValue::~CordbVCObjectValue + +HRESULT CordbVCObjectValue::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugObjectValue*>(this)); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugObjectValue) + { + *pInterface = static_cast<ICorDebugObjectValue*>(this); + } + else if (id == IID_ICorDebugObjectValue2) + + { + *pInterface = static_cast<ICorDebugObjectValue2*>(this); + } + else if (id == IID_ICorDebugGenericValue) + { + *pInterface = static_cast<ICorDebugGenericValue*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugObjectValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbVCObjectValue::QueryInterface + +// returns the basic type of the ICDValue +// Arguments: +// output: pType - the type of the ICDValue (always E_T_VALUETYPE) +// ReturnValue: S_OK on success or E_INVALIDARG if pType is NULL +HRESULT CordbVCObjectValue::GetType(CorElementType *pType) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *); + + *pType = ELEMENT_TYPE_VALUETYPE; + return S_OK; +} // CordbVCObjectValue::GetType + +// public API to get the CordbClass field +// Arguments: +// output: ppClass - holds a pointer to the ICDClass instance belonging to this +// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED or synchronization errors on failure +HRESULT CordbVCObjectValue::GetClass(ICorDebugClass **ppClass) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + *ppClass = (ICorDebugClass*) GetClass(); + + if (*ppClass != NULL) + (*ppClass)->AddRef(); + + return S_OK; +} // CordbVCObjectValue::GetClass + +// internal method to get the CordbClass field +// Arguments: none +// ReturnValue: the instance of CordbClass belonging to this VC object +CordbClass *CordbVCObjectValue::GetClass() +{ + CordbClass *tycon; + Instantiation inst; + m_type->DestConstructedType(&tycon, &inst); + return tycon; +} // CordbVCObjectValue::GetClass + +//----------------------------------------------------------------------------- +// +// Finds the given field of the given type in the object and returns an ICDValue for it. +// +// Arguments: +// pType - The type of the field +// fieldDef - The field's metadata def. +// ppValue - OUT: the ICDValue for the field. +// +// Returns: +// S_OK on success, CORDBG_E_OBJECT_NEUTERED, E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, or various other +// failure codes +HRESULT CordbVCObjectValue::GetFieldValueForType(ICorDebugType * pType, + mdFieldDef fieldDef, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + // Validate the token. + if ((m_type->m_pClass == NULL) || !m_type->m_pClass->GetModule()->GetMetaDataImporter()->IsValidToken(fieldDef)) + { + ThrowHR(E_INVALIDARG); + } + + + CordbType * pCordbType; + + // + // <TODO>@todo: need to ensure that pClass is really on the class + // hierarchy of m_class!!!</TODO> + // + if (pType == NULL) + { + pCordbType = m_type; + } + else + { + pCordbType = static_cast<CordbType *> (pType); + } + + FieldData * pFieldData; + + #ifdef _DEBUG + pFieldData = NULL; + #endif + + hr = pCordbType->GetFieldInfo(fieldDef, &pFieldData); + _ASSERTE(hr != CORDBG_E_ENC_HANGING_FIELD); + + // If we get back CORDBG_E_ENC_HANGING_FIELD we'll just fail - + // value classes should not be able to add fields once they're loaded, + // since the new fields _can't_ be contiguous with the old fields, + // and having all the fields contiguous is kinda the point of a V.C. + IfFailThrow(hr); + + _ASSERTE(pFieldData != NULL); + + CordbModule * pModule = pCordbType->m_pClass->GetModule(); + + SigParser sigParser; + IfFailThrow(pFieldData->GetFieldSignature(pModule, &sigParser)); + + // <TODO> + // How can I assert that I have exactly one field? + // </TODO> + CordbType * pFieldType; + + IfFailThrow(CordbType::SigToType(pModule, &sigParser, &(pCordbType->m_inst), &pFieldType)); + + _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset()); + // Compute the address of the field contents in our local object cache + SIZE_T fieldOffset = pFieldData->GetInstanceOffset(); + ULONG32 size = GetSizeForType(pFieldType, kUnboxed); + + // verify that the field starts before the end of m_pObjectCopy + _ASSERTE(fieldOffset < m_size); + _ASSERTE(fieldOffset + size <= m_size); + + m_pValueHome->CreateInternalValue(pFieldType, + fieldOffset, + m_pObjectCopy + fieldOffset, + size, + ppValue); // throws + + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbVCObjectValue::GetFieldValueForType + +// gets an ICDValue to represent a field of the VC object +// Arguments: +// input: pClass - the class information for this object (needed to get the parent class information) +// fieldDef - field token for the desired field +// output: ppValue - on success, the ICDValue representing the desired field +// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM, +// CORDBG_E_ENC_HANGING_FIELD, or various other failure codes +HRESULT CordbVCObjectValue::GetFieldValue(ICorDebugClass *pClass, + mdFieldDef fieldDef, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + + HRESULT hr; + _ASSERTE(m_type); + + if (m_type->m_elementType != ELEMENT_TYPE_CLASS && + m_type->m_elementType != ELEMENT_TYPE_VALUETYPE) + { + return E_INVALIDARG; + } + + RSExtSmartPtr<CordbType> relevantType; + + if (FAILED (hr= m_type->GetParentType((CordbClass *) pClass, &relevantType))) + { + return hr; + } + // Upon exit relevantType will either be the appropriate type for the + // class we're looking for. + + hr = GetFieldValueForType(relevantType, fieldDef, ppValue); + // GetParentType ands one reference to relevantType, holder dtor releases that. + return hr; + +} // CordbVCObjectValue::GetFieldValue + +// get a copy of the VC object +// Arguments: +// output: pTo - a caller-allocated buffer to hold the copy +// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED on failure +// Note: The caller must ensure the buffer is large enough to hold the value (by a previous call to GetSize) +// and is responsible for allocation and deallocation. +HRESULT CordbVCObjectValue::GetValue(void *pTo) +{ + VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true); + FAIL_IF_NEUTERED(this); + + // Copy out the value, which is the whole object. + memcpy(pTo, m_pObjectCopy, m_size); + + return S_OK; +} // CordbVCObjectValue::GetValue + +// set the value of a VC object +// Arguments: +// input: pSrc - buffer containing the new value. Allocated and managed by the caller. +// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED, synchronization errors, E_INVALIDARG, write +// process memory errors, CORDBG_E_CLASS_NOT_LOADED or OOM on failure +HRESULT CordbVCObjectValue::SetValue(void * pSrc) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + + VALIDATE_POINTER_TO_OBJECT_ARRAY(pSrc, BYTE, m_size, true, false); + + // Can't change literals... + if (m_isLiteral) + return E_INVALIDARG; + + if (m_type) + { + IfFailRet(m_type->Init(FALSE)); + } + + EX_TRY + { + m_pValueHome->SetValue(MemoryRange(pSrc, m_size), m_type); // throws + } + EX_CATCH_HRESULT(hr); + if (SUCCEEDED(hr)) + { + // That worked, so update the copy of the value we have over here. + memcpy(m_pObjectCopy, pSrc, m_size); + } + + return hr; +} // CordbVCObjectValue::SetValue + +HRESULT CordbVCObjectValue::GetVirtualMethod(mdMemberRef memberRef, + ICorDebugFunction **ppFunction) +{ + return E_NOTIMPL; +} + +HRESULT CordbVCObjectValue::GetVirtualMethodAndType(mdMemberRef memberRef, + ICorDebugFunction **ppFunction, + ICorDebugType **ppType) +{ + return E_NOTIMPL; +} + +HRESULT CordbVCObjectValue::GetContext(ICorDebugContext **ppContext) +{ + return E_NOTIMPL; +} + +// self-identifier--always returns true as long as pbIsValueClass is non-Null +HRESULT CordbVCObjectValue::IsValueClass(BOOL *pbIsValueClass) +{ + if (pbIsValueClass) + *pbIsValueClass = TRUE; + + return S_OK; +} // CordbVCObjectValue::IsValueClass + +HRESULT CordbVCObjectValue::GetManagedCopy(IUnknown **ppObject) +{ + // This function is deprecated + return E_NOTIMPL; +} + +HRESULT CordbVCObjectValue::SetFromManagedCopy(IUnknown *pObject) +{ + // This function is deprecated + return E_NOTIMPL; +} + + // +// CordbVCObjectValue::Init +// +// Description +// Initializes the Right-Side's representation of a Value Class object. +// Parameters +// input: localValue - buffer containing the value if this instance of CordbObjectValue +// was a field or array element of an existing value, otherwise this +// will have a start address equal to NULL +// Returns +// HRESULT +// S_OK if the function completed normally +// failing HR otherwise +// Exceptions +// None +// +HRESULT CordbVCObjectValue::Init(MemoryRange localValue) +{ + HRESULT hr = S_OK; + + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); // + + // Get the object size from the class + ULONG32 size; + IfFailRet( m_type->GetUnboxedObjectSize(&size) ); + m_size = size; + + // Copy the entire object over to this process. + m_pObjectCopy = new (nothrow) BYTE[m_size]; + + if (m_pObjectCopy == NULL) + { + return E_OUTOFMEMORY; + } + + if (localValue.StartAddress() != NULL) + { + // The data is already in the local address space. Go ahead and copy it + // from there. + // localValue.StartAddress points to: + // 1. A field from the local cached copy belonging to an instance of CordbVCObjectValue (different + // instance from "this") or CordbObjectValue + // 2. An element in the locally cached subrange of an array belonging to an instance of CordbArrayValue + // 3. The address of a particular register in the register display of an instance of CordbNativeFrame + // for an enregistered value type. In this case, it's possible that the size of the value is + // smaller than the size of a full register. For that reason, we can't just use localValue.Size() + // as the number of bytes to copy, because only enough space for the value has been allocated. + _ASSERTE(localValue.Size() >= m_size); + localCopy(m_pObjectCopy, MemoryRange(localValue.StartAddress(), m_size)); + return S_OK; + } + + EX_TRY + { + m_pValueHome->GetValue(MemoryRange(m_pObjectCopy, m_size)); // throws + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbVCObjectValue::Init + +/* ------------------------------------------------------------------------- * + * Box Value class + * ------------------------------------------------------------------------- */ + +// constructor +// Arguments: +// input: appdomain - app domain to which the value belongs +// type - type information for the boxed value +// remoteValue - buffer describing the remote location of the value +// size - size of the value +// offsetToVars - offset from the beginning of the value to the first field of the value +CordbBoxValue::CordbBoxValue(CordbAppDomain *appdomain, + CordbType *type, + TargetBuffer remoteValue, + ULONG32 size, + SIZE_T offsetToVars) + : CordbValue(appdomain, type, remoteValue.pAddress, false, appdomain->GetProcess()->GetContinueNeuterList()), + m_offsetToVars(offsetToVars), + m_valueHome(appdomain->GetProcess(), remoteValue) +{ + m_size = size; +} // CordbBoxValue::CordbBoxValue + +// destructor +CordbBoxValue::~CordbBoxValue() +{ + DTOR_ENTRY(this); + _ASSERTE(IsNeutered()); +} // CordbBoxValue::~CordbBoxValue + +HRESULT CordbBoxValue::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugBoxValue*>(this)); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugBoxValue) + { + *pInterface = static_cast<ICorDebugBoxValue*>(this); + } + else if (id == IID_ICorDebugGenericValue) + { + *pInterface = static_cast<ICorDebugGenericValue*>(this); + } + else if (id == IID_ICorDebugHeapValue) + { + *pInterface = static_cast<ICorDebugHeapValue*>(this); + } + else if (id == IID_ICorDebugHeapValue2) + { + *pInterface = static_cast<ICorDebugHeapValue2*>(this); + } + else if (id == IID_ICorDebugHeapValue3) + { + *pInterface = static_cast<ICorDebugHeapValue3*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugBoxValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbBoxValue::QueryInterface + +// returns the basic type of the ICDValue +// Arguments: +// output: pType - the type of the ICDValue (always E_T_CLASS) +// ReturnValue: S_OK on success or E_INVALIDARG if pType is NULL +HRESULT CordbBoxValue::GetType(CorElementType *pType) +{ + VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *); + + *pType = ELEMENT_TYPE_CLASS; + + return (S_OK); +} // CordbBoxValue::GetType + +HRESULT CordbBoxValue::IsValid(BOOL *pbValid) +{ + VALIDATE_POINTER_TO_OBJECT(pbValid, BOOL *); + + // <TODO>@todo: implement tracking of objects across collections.</TODO> + + return E_NOTIMPL; +} + +HRESULT CordbBoxValue::CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint) +{ + VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **); + + return E_NOTIMPL; +} + +// Creates a handle of the given type for this heap value. +// Not Implemented In-Proc. +// Create a handle for a heap object. +// @todo: How to prevent this being called by non-heap object? +// Arguments: +// input: handleType - type of the handle to be created +// output: ppHandle - on success, the newly created handle +// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK +HRESULT CordbBoxValue::CreateHandle( + CorDebugHandleType handleType, + ICorDebugHandleValue ** ppHandle) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbValue::InternalCreateHandle(handleType, ppHandle); +} // CordbBoxValue::CreateHandle + +HRESULT CordbBoxValue::GetValue(void *pTo) +{ + // Can't get a whole copy of a box. + return E_INVALIDARG; +} + +HRESULT CordbBoxValue::SetValue(void *pFrom) +{ + // You're not allowed to set a box value. + return E_INVALIDARG; +} + +// gets the unboxed value from this boxed value +// Arguments: +// output: ppObject - pointer to an instance of ICDValue representing the unboxed value, unless ppObject +// is NULL +// Return Value: S_OK on success or a variety of possible failures: OOM, E_FAIL, errors from +// ReadProcessMemory. +HRESULT CordbBoxValue::GetObject(ICorDebugObjectValue **ppObject) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppObject, ICorDebugObjectValue **); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + ULONG32 size; + m_type->GetUnboxedObjectSize(&size); + + HRESULT hr = S_OK; + EX_TRY + { + m_valueHome.CreateInternalValue(m_type, + m_offsetToVars, + NULL, + size, + reinterpret_cast<ICorDebugValue **>(ppObject)); // throws + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbBoxValue::GetObject + +// If a managed thread owns the monitor lock on this object then *ppThread +// will point to that thread and S_OK will be returned. The thread object is valid +// until the thread exits. *pAcquisitionCount will indicate the number of times +// this thread would need to release the lock before it returns to being +// unowned. +// If no managed thread owns the monitor lock on this object then *ppThread +// and pAcquisitionCount will be unchanged and S_FALSE returned. +// If ppThread or pAcquisitionCount is not a valid pointer the result is +// undefined. +// If any error occurs such that it cannot be determined which, if any, thread +// owns the monitor lock on this object then a failing HRESULT will be returned +HRESULT CordbBoxValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(), + GetValueHome()->GetAddress(), + ppThread, + pAcquisitionCount); +} + +// Provides an ordered list of threads which are queued on the event associated +// with a monitor lock. The first thread in the list is the first thread which +// will be released by the next call to Monitor.Pulse, the next thread in the list +// will be released on the following call, and so on. +// If this list is non-empty S_OK will be returned, if it is empty S_FALSE +// will be returned (the enumeration is still valid, just empty). +// In either case the enumeration interface is only usable for the duration +// of the current synchronized state, however the threads interfaces dispensed +// from it are valid until the thread exits. +// If ppThread is not a valid pointer the result is undefined. +// If any error occurs such that it cannot be determined which, if any, threads +// are waiting for the monitor then a failing HRESULT will be returned +HRESULT CordbBoxValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(), + GetValueHome()->GetAddress(), + ppThreadEnum); +} + + +/* ------------------------------------------------------------------------- * + * Array Value class + * ------------------------------------------------------------------------- */ + +// The size of the buffer we allocate to hold array elements. +// Note that since we must be able to hold at least one element, we may +// allocate larger than the cache size here. +// Also, this cache doesn't include a small header used to store the rank vectors +#ifdef _DEBUG +// For debug, use a small size to cause more churn + #define ARRAY_CACHE_SIZE (1000) +#else +// For release, guess 4 pages should be enough. Subtract some bytes to store +// the header so that that doesn't push us onto another page. (We guess a reasonable +// header size, but it's ok if it's larger). + #define ARRAY_CACHE_SIZE (4 * 4096 - 24) +#endif + +// constructor +// Arguments: +// input: +// pAppDomain - app domain to which the value belongs +// pType - type information for the value +// pObjectInfo - array specific type information +// remoteValue - buffer describing the remote location of the value +CordbArrayValue::CordbArrayValue(CordbAppDomain * pAppdomain, + CordbType * pType, + DebuggerIPCE_ObjectData * pObjectInfo, + TargetBuffer remoteValue) + : CordbValue(pAppdomain, + pType, + remoteValue.pAddress, + false, + pAppdomain->GetProcess()->GetContinueNeuterList()), + m_info(*pObjectInfo), + m_pObjectCopy(NULL), + m_valueHome(pAppdomain->GetProcess(), remoteValue) +{ + m_size = m_info.objSize; + pType->DestUnaryType(&m_elemtype); + +// Set range to illegal values to force a load on first access + m_idxLower = m_idxUpper = (SIZE_T) -1; +} // CordbArrayValue::CordbArrayValue + +// destructor +CordbArrayValue::~CordbArrayValue() +{ + DTOR_ENTRY(this); + _ASSERTE(IsNeutered()); + + // Destroy the copy of the object. + if (m_pObjectCopy != NULL) + delete [] m_pObjectCopy; +} // CordbArrayValue::~CordbArrayValue + +HRESULT CordbArrayValue::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugArrayValue*>(this)); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugArrayValue) + { + *pInterface = static_cast<ICorDebugArrayValue*>(this); + } + else if (id == IID_ICorDebugGenericValue) + { + *pInterface = static_cast<ICorDebugGenericValue*>(this); + } + else if (id == IID_ICorDebugHeapValue) + { + *pInterface = static_cast<ICorDebugHeapValue*>(this); + } + else if (id == IID_ICorDebugHeapValue2) + { + *pInterface = static_cast<ICorDebugHeapValue2*>(this); + } + else if (id == IID_ICorDebugHeapValue3) + { + *pInterface = static_cast<ICorDebugHeapValue3*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugArrayValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbArrayValue::QueryInterface + +// gets the type of the array elements +// Arguments: +// output: pType - the element type unless pType is NULL +// Return Value: S_OK on success or E_INVALIDARG if pType is null +HRESULT CordbArrayValue::GetElementType(CorElementType *pType) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *); + + *pType = m_elemtype->m_elementType; + return S_OK; +} // CordbArrayValue::GetElementType + + +// gets the rank of the array +// Arguments: +// output: pnRank - the rank of the array unless pnRank is null +// Return Value: S_OK on success or E_INVALIDARG if pnRank is null +HRESULT CordbArrayValue::GetRank(ULONG32 *pnRank) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pnRank, SIZE_T *); + + // Rank info is duplicated for sanity checking - double check it here. + _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank); + *pnRank = m_type->m_rank; + return S_OK; +} // CordbArrayValue::GetRank + +// gets the number of elements in the array +// Arguments: +// output: pnCount - the number of dimensions for the array unless pnCount is null +// Return Value: S_OK on success or E_INVALIDARG if pnCount is null +HRESULT CordbArrayValue::GetCount(ULONG32 *pnCount) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pnCount, ULONG32 *); + + *pnCount = (ULONG32)m_info.arrayInfo.componentCount; + return S_OK; +} // CordbArrayValue::GetCount + +// get the size of each dimension of the array +// Arguments: +// input: cdim - the number of dimensions about which to get dimensions--this must be the same as the rank +// output: dims - an array to hold the sizes of the dimensions of the array--this is allocated and +// managed by the caller +// Return Value: S_OK on success or E_INVALIDARG +HRESULT CordbArrayValue::GetDimensions(ULONG32 cdim, ULONG32 dims[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(dims, SIZE_T, cdim, true, true); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // Rank info is duplicated for sanity checking - double check it here. + _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank); + if (cdim != m_type->m_rank) + return E_INVALIDARG; + + // SDArrays don't have bounds info, so return the component count. + if (cdim == 1) + dims[0] = (ULONG32)m_info.arrayInfo.componentCount; + else + { + _ASSERTE(m_info.arrayInfo.offsetToUpperBounds != 0); + _ASSERTE(m_arrayUpperBase != NULL); + + // The upper bounds info in the array is the true size of each + // dimension. + for (unsigned int i = 0; i < cdim; i++) + dims[i] = m_arrayUpperBase[i]; + } + + return S_OK; +} // CordbArrayValue::GetDimensions + +// +// indicates whether the array has base indices +// Arguments: +// output: pbHasBaseIndices - true iff the array has more than one dimension and pbHasBaseIndices is not null +// Return Value: S_OK on success or E_INVALIDARG if pbHasBaseIndices is null +HRESULT CordbArrayValue::HasBaseIndicies(BOOL *pbHasBaseIndices) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pbHasBaseIndices, BOOL *); + + *pbHasBaseIndices = m_info.arrayInfo.offsetToLowerBounds != 0; + return S_OK; +} // CordbArrayValue::HasBaseIndicies + +// gets the base indices for a multidimensional array +// Arguments: +// input: cdim - the number of dimensions (this must be the same as the actual rank of the array) +// indices - an array to hold the base indices for the array dimensions (allocated and managed +// by the caller, it must have space for cdim elements) +// Return Value: S_OK on success or E_INVALIDARG if cdim is not equal to the array rank or indices is null +HRESULT CordbArrayValue::GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(indices, SIZE_T, cdim, true, true); + + // Rank info is duplicated for sanity checking - double check it here. + _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank); + if ((cdim != m_type->m_rank) || + (m_info.arrayInfo.offsetToLowerBounds == 0)) + return E_INVALIDARG; + + _ASSERTE(m_arrayLowerBase != NULL); + + for (unsigned int i = 0; i < cdim; i++) + indices[i] = m_arrayLowerBase[i]; + + return S_OK; +} // CordbArrayValue::GetBaseIndicies + +// Get an element at the position indicated by the values in indices (one index for each dimension) +// Arguments: +// input: cdim - the number of dimensions and thus the number of elements in indices. This must match +// the actual rank of the array value. +// indices - an array of indices to specify the position of the element. For example, to get a[2][1][0], +// indices would contain 2, 1, and 0 in that order. +// output: ppValue - an ICDValue representing the element, unless an error occurs +// Return Value: S_OK on success or E_INVALIDARG if cdim != rank, indices is NULL or ppValue is NULL +// or a variety of possible failures: OOM, E_FAIL, errors from +// ReadProcessMemory. +HRESULT CordbArrayValue::GetElement(ULONG32 cdim, + ULONG32 indices[], + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(indices, SIZE_T, cdim, true, true); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + *ppValue = NULL; + + // Rank info is duplicated for sanity checking - double check it here. + _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank); + if ((cdim != m_type->m_rank) || (indices == NULL)) + return E_INVALIDARG; + + // If the array has lower bounds, adjust the indices. + if (m_info.arrayInfo.offsetToLowerBounds != 0) + { + _ASSERTE(m_arrayLowerBase != NULL); + + for (unsigned int i = 0; i < cdim; i++) + indices[i] -= m_arrayLowerBase[i]; + } + + SIZE_T offset = 0; + + // SDArrays don't have upper bounds + if (cdim == 1) + { + offset = indices[0]; + + // Bounds check + if (offset >= m_info.arrayInfo.componentCount) + return E_INVALIDARG; + } + else + { + _ASSERTE(m_info.arrayInfo.offsetToUpperBounds != 0); + _ASSERTE(m_arrayUpperBase != NULL); + + // Calculate the offset in bytes for all dimensions. + SIZE_T multiplier = 1; + + for (int i = cdim - 1; i >= 0; i--) + { + // Bounds check + if (indices[i] >= m_arrayUpperBase[i]) + return E_INVALIDARG; + + offset += indices[i] * multiplier; + multiplier *= m_arrayUpperBase[i]; + } + + _ASSERTE(offset < m_info.arrayInfo.componentCount); + } + + return GetElementAtPosition((ULONG32)offset, ppValue); +} // CordbArrayValue::GetElement + +// get an ICDValue to represent the element at a given position +// Arguments: +// input: nPosition - the offset from the beginning of the array to the element +// output: ppValue - the ICDValue representing the array element on success +// Return Value: S_OK on success, E_INVALIDARG or a variety of possible failures: OOM, E_FAIL, errors from +// ReadProcessMemory. +HRESULT CordbArrayValue::GetElementAtPosition(ULONG32 nPosition, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (nPosition >= m_info.arrayInfo.componentCount) + { + *ppValue = NULL; + return E_INVALIDARG; + } + + // Rank info is duplicated for sanity checking - double check it here. + _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank); + + // The header consists of two DWORDs for each dimension, representing the upper and lower bound for that dimension. A + // vector of lower bounds comes first, followed by a vector of upper bounds. We want to copy a range of + // elements into m_pObjectCopy following these vectors, so we need to compute the address where the + // vectors end and the elements begin. + const int cbHeader = 2 * m_type->m_rank * sizeof(DWORD); + HRESULT hr = S_OK; + + // Ensure that the proper subset is in the cache. m_idxLower and m_idxUpper are initialized to -1, so the + // first time we hit this condition check, it will evaluate to true. We will set these inside the + // consequent to the range starting at nPosition and ending at the last available cache position. Thus, + // after the first time we hit this, we are asking if nPosition lies outside the range we've cached. + if (nPosition < m_idxLower || nPosition >= m_idxUpper) + { + const SIZE_T cbElemSize = m_info.arrayInfo.elementSize; + SIZE_T len = 1; + + if (cbElemSize != 0) + { + // the element size could be bigger than the cache, but we want len to be at least 1. + len = max(ARRAY_CACHE_SIZE / cbElemSize, len); + } + else _ASSERTE(cbElemSize != 0); + + m_idxLower = nPosition; + m_idxUpper = min(m_idxLower + len, m_info.arrayInfo.componentCount); + _ASSERTE(m_idxLower < m_idxUpper); + + SIZE_T cbOffsetFrom = m_info.arrayInfo.offsetToArrayBase + m_idxLower * cbElemSize; + + SIZE_T cbSize = (m_idxUpper - m_idxLower) * cbElemSize; // we'll copy the largest range of ellements possible + + _ASSERTE(cbSize <= m_info.objSize); + // Copy the proper subrange of the array over + EX_TRY + { + m_valueHome.GetInternalValue(MemoryRange(m_pObjectCopy + cbHeader, cbSize), cbOffsetFrom); // throws + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + } + + SIZE_T size = m_info.arrayInfo.elementSize; + _ASSERTE(size <= m_info.objSize); + + SIZE_T offset = m_info.arrayInfo.offsetToArrayBase + (nPosition * size); + void * localAddress = m_pObjectCopy + cbHeader + ((nPosition - m_idxLower) * size); + + EX_TRY + { + m_valueHome.CreateInternalValue(m_elemtype, + offset, + localAddress, + (ULONG32)size, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + return hr; + +} // CordbArrayValue::GetElementAtPosition + +HRESULT CordbArrayValue::IsValid(BOOL *pbValid) +{ + VALIDATE_POINTER_TO_OBJECT(pbValid, BOOL *); + + // <TODO>@todo: implement tracking of objects across collections.</TODO> + + return E_NOTIMPL; +} + +HRESULT CordbArrayValue::CreateRelocBreakpoint( + ICorDebugValueBreakpoint **ppBreakpoint) +{ + VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **); + + return E_NOTIMPL; +} + +// Creates a handle of the given type for this heap value. +// Not Implemented In-Proc. +// Arguments: +// input: handleType - type of the handle to be created +// output: ppHandle - on success, the newly created handle +// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK +HRESULT CordbArrayValue::CreateHandle( + CorDebugHandleType handleType, + ICorDebugHandleValue ** ppHandle) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbValue::InternalCreateHandle(handleType, ppHandle); +} // CordbArrayValue::CreateHandle + +// get a copy of the array +// Arguments +// output: pTo - pointer to a caller-allocated and managed buffer to hold the copy. The caller must guarantee +// that this is large enough to hold the entire array +// Return Value: S_OK on success, E_INVALIDARG or read process memory errors on failure +HRESULT CordbArrayValue::GetValue(void *pTo) +{ + VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, void *, 1, false, true); + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + EX_TRY + { + // Copy out the value, which is the whole array. + // There's no lazy-evaluation here, so this could be rather large + m_valueHome.GetValue(MemoryRange(pTo, m_size)); // throws + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbArrayValue::GetValue + +HRESULT CordbArrayValue::SetValue(void *pFrom) +{ + // You're not allowed to set a whole array at once. + return E_INVALIDARG; +} + +// initialize a new instance of CordbArrayValue +// Arguments: none +// Return Value: S_OK on success or E_OUTOFMEMORY or read process memory errors on failure +// Note: we are only initializing information about the array (rank, sizes, dimensions, etc) here. We will not +// attempt to read array contents until we receive a request to do so. +HRESULT CordbArrayValue::Init() +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); // + HRESULT hr = S_OK; + + SIZE_T cbVector = m_info.arrayInfo.rank * sizeof(DWORD); + _ASSERTE(cbVector <= m_info.objSize); + + int cbHeader = 2 * (int)cbVector; + + // Find largest data size that will fit in cache + SIZE_T cbData = m_info.arrayInfo.componentCount * m_info.arrayInfo.elementSize; + if (cbData > ARRAY_CACHE_SIZE) + { + cbData = (ARRAY_CACHE_SIZE / m_info.arrayInfo.elementSize) + * m_info.arrayInfo.elementSize; + } + + if (cbData < m_info.arrayInfo.elementSize) + { + cbData = m_info.arrayInfo.elementSize; + } + + // Allocate memory + m_pObjectCopy = new (nothrow) BYTE[cbHeader + cbData]; + if (m_pObjectCopy == NULL) + return E_OUTOFMEMORY; + + + m_arrayLowerBase = NULL; + m_arrayUpperBase = NULL; + + // Copy base vectors into header. (Offsets are 0 if the vectors aren't used) + if (m_info.arrayInfo.offsetToLowerBounds != 0) + { + m_arrayLowerBase = (DWORD*)(m_pObjectCopy); + EX_TRY + { + m_valueHome.GetInternalValue(MemoryRange(m_arrayLowerBase, cbVector), + m_info.arrayInfo.offsetToLowerBounds); // throws + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + } + + + if (m_info.arrayInfo.offsetToUpperBounds != 0) + { + m_arrayUpperBase = (DWORD*)(m_pObjectCopy + cbVector); + EX_TRY + { + m_valueHome.GetInternalValue(MemoryRange(m_arrayUpperBase, cbVector), + m_info.arrayInfo.offsetToUpperBounds); // throws + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + } + + // That's all for now. We'll do lazy-evaluation for the array contents. + + return hr; +} // CordbArrayValue::Init + +// CordbArrayValue::GetThreadOwningMonitorLock +// If a managed thread owns the monitor lock on this object then *ppThread +// will point to that thread and S_OK will be returned. The thread object is valid +// until the thread exits. *pAcquisitionCount will indicate the number of times +// this thread would need to release the lock before it returns to being +// unowned. +// If no managed thread owns the monitor lock on this object then *ppThread +// and pAcquisitionCount will be unchanged and S_FALSE returned. +// If ppThread or pAcquisitionCount is not a valid pointer the result is +// undefined. +// If any error occurs such that it cannot be determined which, if any, thread +// owns the monitor lock on this object then a failing HRESULT will be returned +HRESULT CordbArrayValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(), + GetValueHome()->GetAddress(), ppThread, pAcquisitionCount); +} + +// CordbArrayValue::GetMonitorEventWaitList +// Provides an ordered list of threads which are queued on the event associated +// with a monitor lock. The first thread in the list is the first thread which +// will be released by the next call to Monitor.Pulse, the next thread in the list +// will be released on the following call, and so on. +// If this list is non-empty S_OK will be returned, if it is empty S_FALSE +// will be returned (the enumeration is still valid, just empty). +// In either case the enumeration interface is only usable for the duration +// of the current synchronized state, however the threads interfaces dispensed +// from it are valid until the thread exits. +// If ppThread is not a valid pointer the result is undefined. +// If any error occurs such that it cannot be determined which, if any, threads +// are waiting for the monitor then a failing HRESULT will be returned +HRESULT CordbArrayValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(), + GetValueHome()->GetAddress(), + ppThreadEnum); +} + +/* ------------------------------------------------------------------------- * + * Handle Value + * ------------------------------------------------------------------------- */ +// constructor +// Arguments: +// input: +// pAppDomain - app domain to which the value belongs +// pType - type information for the value +// handleType - indicates whether we are constructing a strong or weak handle +CordbHandleValue::CordbHandleValue( + CordbAppDomain * pAppdomain, + CordbType * pType, // The type of object that we create handle on + CorDebugHandleType handleType) // strong or weak handle + : CordbValue(pAppdomain, pType, NULL, false, + pAppdomain->GetSweepableExitNeuterList() + ) +{ + m_vmHandle = VMPTR_OBJECTHANDLE::NullPtr(); + m_fCanBeValid = TRUE; + + m_handleType = handleType; + m_size = sizeof(void*); +} // CordbHandleValue::CordbHandleValue + +//----------------------------------------------------------------------------- +// Assign internal handle to the given value, and update pertinent counters +// +// Arguments: +// handle - non-null CLR ObjectHandle that this CordbHandleValue will represent +// +// Notes: +// Call code:CordbHandleValue::ClearHandle to clear the handle value. +void CordbHandleValue::AssignHandle(VMPTR_OBJECTHANDLE handle) +{ + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + _ASSERTE(m_vmHandle.IsNull()); + + // Use code:CordbHandleValue::ClearHandle to clear the handle value. + _ASSERTE(!handle.IsNull()); + + m_vmHandle = handle; + GetProcess()->IncrementOutstandingHandles(); +} + +//----------------------------------------------------------------------------- +// Clear the handle value +// +// Assumptions: +// Caller only clears if not already cleared. +// +// Notes: +// This is the inverse of code:CordbHandleValue::AssignHandle +void CordbHandleValue::ClearHandle() +{ + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + _ASSERTE(!m_vmHandle.IsNull()); + + m_vmHandle = VMPTR_OBJECTHANDLE::NullPtr(); + GetProcess()->DecrementOutstandingHandles(); +} + +// initialize a new instance of CordbHandleValue +// Arguments: +// input: pHandle - non-null CLR ObjectHandle that this CordbHandleValue will represent +// Return Value: S_OK on success or CORDBG_E_TARGET_INCONSISTENT, E_INVALIDARG, read process memory errors. +HRESULT CordbHandleValue::Init(VMPTR_OBJECTHANDLE pHandle) +{ + INTERNAL_SYNC_API_ENTRY(GetProcess()); + HRESULT hr = S_OK; + + { + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + // If it is a strong handle, m_pHandle will not be NULL unless Dispose method is called. + // If it is a weak handle, m_pHandle can be NULL when Dispose is called. + AssignHandle(pHandle); + } + + // This will init m_info. + IfFailRet(RefreshHandleValue()); + + // objRefBad is currently overloaded to mean that 1) the object ref is invalid, or 2) the object ref is NULL. + // NULL is clearly not a bad object reference, but in either case we have no more type data to work with, + // so don't attempt to assign more specific type information to the reference. + if (!m_info.objRefBad) + { + // We need to get the type info from the left side. + CordbType *newtype; + + IfFailRet(CordbType::TypeDataToType(m_appdomain, &m_info.objTypeData, &newtype)); + + m_type.Assign(newtype); + } + + return hr; +} // CordbHandleValue::Init + +// destructor +CordbHandleValue::~CordbHandleValue() +{ + DTOR_ENTRY(this); + + _ASSERTE(IsNeutered()); +} // CordbHandleValue::~CordbHandleValue + +// Free left-side resources, mainly the GC handle keeping the object alive. +void CordbHandleValue::NeuterLeftSideResources() +{ + Dispose(); + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + Neuter(); +} // CordbHandleValue::NeuterLeftSideResources + +// Neuter +// Notes: +// CordbHandleValue may hold Left-Side resources via the GC handle. +// By the time we neuter it, those resources must have been freed, +// either explicitly by calling code:CordbHandleValue::Dispose, or +// implicitly by the left-side process exiting. +void CordbHandleValue::Neuter() +{ + // CordbHandleValue is on the AppDomainExit neuter list. + + // We should have cleaned up our Left-side resource by now (m_vmHandle + // should be null). If AppDomain / Process has already exited, then the LS + // already cleaned them up for us, and so we don't worry about them. + bool fAppDomainIsAlive = (m_appdomain != NULL && !m_appdomain->IsNeutered()); + if (fAppDomainIsAlive) + { + BOOL fTargetIsDead = !GetProcess()->IsSafeToSendEvents() || GetProcess()->m_exiting; + if (!fTargetIsDead) + { + _ASSERTE(m_vmHandle.IsNull()); + } + } + + CordbValue::Neuter(); +} // CordbHandleValue::Neuter + +// Helper: Refresh the handle value object. +// Gets information about the object to which the handle points. +// Arguments: none +// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED, CORDBG_E_BAD_REFERENCE_VALUE, +// errors from read process memory. +HRESULT CordbHandleValue::RefreshHandleValue() +{ + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); // + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + // If Dispose has been called, don't bother to refresh handle value. + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + // If weak handle and the object was dead, no point to refresh the handle value + if (m_fCanBeValid == FALSE) + { + return CORDBG_E_BAD_REFERENCE_VALUE; + } + + HRESULT hr = S_OK; + CorElementType type = m_type->m_elementType; + + _ASSERTE((m_pProcess != NULL)); + + _ASSERTE (type != ELEMENT_TYPE_GENERICINST); + _ASSERTE (type != ELEMENT_TYPE_VAR); + _ASSERTE (type != ELEMENT_TYPE_MVAR); + + CordbProcess * pProcess = GetProcess(); + void * objectAddress = NULL; + CORDB_ADDRESS objectHandle = 0; + + EX_TRY + { + objectHandle = pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmHandle); + if (type != ELEMENT_TYPE_TYPEDBYREF) + { + pProcess->SafeReadBuffer(TargetBuffer(objectHandle, sizeof(void *)), (BYTE *)&objectAddress); + } + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + EX_TRY + { + if (type == ELEMENT_TYPE_TYPEDBYREF) + { + CordbReferenceValue::GetTypedByRefData(pProcess, + objectHandle, + type, + m_appdomain->GetADToken(), + &m_info); + } + else + { + CordbReferenceValue::GetObjectData(pProcess, + objectAddress, + type, + m_appdomain->GetADToken(), + &m_info); + } + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + // If reference is already gone bad or reference is NULL, + // don't bother to refetch in the future. + // + if ((m_info.objRefBad) || (m_info.objRef == NULL)) + { + m_fCanBeValid = FALSE; + } + + return hr; +} + // CordbHandleValue::RefreshHandleValue + +HRESULT CordbHandleValue::QueryInterface(REFIID id, void **pInterface) +{ + VALIDATE_POINTER_TO_OBJECT(pInterface, void **); + + if (id == IID_ICorDebugValue) + { + *pInterface = static_cast<ICorDebugValue*>(this); + } + else if (id == IID_ICorDebugValue2) + { + *pInterface = static_cast<ICorDebugValue2*>(this); + } + else if (id == IID_ICorDebugValue3) + { + *pInterface = static_cast<ICorDebugValue3*>(this); + } + else if (id == IID_ICorDebugReferenceValue) + { + *pInterface = static_cast<ICorDebugReferenceValue*>(this); + } + else if (id == IID_ICorDebugHandleValue) + { + *pInterface = static_cast<ICorDebugHandleValue*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugHandleValue*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} // CordbHandleValue::QueryInterface + + +// return handle type. Currently we have strong and weak. +// Arguments: +// output: pType - the handle type unless pType is null +// Return Value: S_OK on success or E_INVALIDARG or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure +HRESULT CordbHandleValue::GetHandleType(CorDebugHandleType *pType) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pType, CorDebugHandleType *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + if (m_vmHandle.IsNull()) + { + // handle has been disposed! + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + *pType = m_handleType; + return S_OK; +} // CordbHandleValue::GetHandleType + +// Dispose will cause handle to be recycled. +// Arguments: none +// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or errors from the +// DB_IPCE_DISPOSE_HANDLE event + +// @dbgtodo Microsoft inspection: remove the dispose handle hresults when the IPC events are eliminated +HRESULT CordbHandleValue::Dispose() +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + HRESULT hr = S_OK; + DebuggerIPCEvent event; + CordbProcess *process; + + process = GetProcess(); + + // Process should still be alive because it would have neutered us if it became invalid. + _ASSERTE(process != NULL); + + VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr(); + { + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + if (m_vmHandle.IsNull()) + { + // handle has been disposed! + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + vmObjHandle = m_vmHandle; + ClearHandle(); // set m_pHandle to null. + + if (process->m_exiting) + { + // process is exiting. Don't do anything + return S_OK; + } + } + + // recycle the handle to EE + process->InitIPCEvent(&event, + DB_IPCE_DISPOSE_HANDLE, + false, + m_appdomain->GetADToken()); + + event.DisposeHandle.vmObjectHandle = vmObjHandle; + if (m_handleType == HANDLE_STRONG) + { + event.DisposeHandle.fStrong = TRUE; + } + else + { + event.DisposeHandle.fStrong = FALSE; + } + + // Note: one-way event here... + hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)); + + hr = WORST_HR(hr, event.hr); + + return hr; +} // CordbHandleValue::Dispose + +// get the type of the object to which the handle points +// Arguments: +// output: pType - the object type on success +// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED, CORDBG_E_CLASS_NOT_LOADED or synchronization errors on +// failure +HRESULT CordbHandleValue::GetType(CorElementType *pType) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + HRESULT hr = S_OK; + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + bool isBoxedVCObject = false; + if ((m_type->m_pClass != NULL) && (m_type->m_elementType != ELEMENT_TYPE_STRING)) + { + EX_TRY + { + isBoxedVCObject = m_type->m_pClass->IsValueClass(); + } + EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + return hr; + } + + if (isBoxedVCObject) + { + // if we create the handle to a boxed value type, then the type is + // E_T_CLASS. m_type is the underlying value type. That is incorrect to + // return. + // + *pType = ELEMENT_TYPE_CLASS; + return S_OK; + } + + return m_type->GetType(pType); +} // CordbHandleValue::GetType + +// get the size of the handle-- this will always return the size of the handle itself (just pointer size), so +// it's not particularly interesting. +// Arguments: +// output: pSize - the size of the handle (on success). This must be non-null. Memory management belongs +// to the caller. +// Return Value: S_OK on success, E_INVALIDARG (if pSize is null), or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure +HRESULT CordbHandleValue::GetSize(ULONG32 *pSize) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pSize, ULONG32 *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + if (m_size > ULONG_MAX) + { + *pSize = ULONG_MAX; + return (COR_E_OVERFLOW); + } + + //return the size of reference + *pSize = (ULONG)m_size; + return S_OK; +} // CordbHandleValue::GetSize + +// get the size of the handle-- this will always return the size of the handle itself (just pointer size), so +// it's not particularly interesting. +// Arguments: +// output: pSize - the size of the handle (on success). This must be non-null. Memory management belongs +// to the caller. +// Return Value: S_OK on success, E_INVALIDARG (if pSize is null), or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure +HRESULT CordbHandleValue::GetSize64(ULONG64 *pSize) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pSize, ULONG64 *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + //return the size of reference + *pSize = m_size; + return S_OK; +} // CordbHandleValue::GetSize + +// Get the target address of the handle +// Arguments: +// output: pAddress - handle address on success. This must be non-null and memory is managed by the caller +// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG on failure +HRESULT CordbHandleValue::GetAddress(CORDB_ADDRESS *pAddress) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + HRESULT hr = S_OK; + EX_TRY + { + *pAddress = GetProcess()->GetDAC()->GetHandleAddressFromVmHandle(m_vmHandle); + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbHandleValue::GetAddress + +HRESULT CordbHandleValue::CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint) +{ + return E_NOTIMPL; +} // CreateBreakpoint + +// indicates whether a handle is null +// Arguments: +// output: pbNull - true iff the handle is null and pbNull is non-null.Memory is managed by the caller +// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE, +// errors from read process memory. +HRESULT CordbHandleValue::IsNull(BOOL *pbNull) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pbNull, BOOL *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + HRESULT hr = S_OK; + + *pbNull = FALSE; + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + + // Only return true if handle is long weak handle and is disposed. + if (m_handleType == HANDLE_WEAK_TRACK_RESURRECTION) + { + hr = RefreshHandleValue(); + if (FAILED(hr)) + { + return hr; + } + + if (m_info.objRef == NULL) + { + *pbNull = TRUE; + } + } + else if (m_info.objRef == NULL) + { + *pbNull = TRUE; + } + + // strong handle always return false for IsNull + + return S_OK; +} // CordbHandleValue::IsNull + +// gets a copy of the value of the handle +// Arguments: +// output: pValue - handle { on success. This must be non-null and memory is managed by the caller +// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE, +// errors from read process memory. +HRESULT CordbHandleValue::GetValue(CORDB_ADDRESS *pValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pValue, CORDB_ADDRESS *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + RefreshHandleValue(); + *pValue = PTR_TO_CORDB_ADDRESS(m_info.objRef); + return S_OK; +} // CordbHandleValue::GetValue + +HRESULT CordbHandleValue::SetValue(CORDB_ADDRESS value) +{ + // do not support SetValue on Handle + return E_FAIL; +} // CordbHandleValue::GetValue + +// get an ICDValue to represent the object to which the handle refers +// Arguments: +// output: ppValue - pointer to the ICDValue for the handle referent as long as ppValue is non-null +// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE, +// errors from read process memory. +HRESULT CordbHandleValue::Dereference(ICorDebugValue **ppValue) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + _ASSERTE(m_appdomain != NULL); + _ASSERTE(!m_appdomain->IsNeutered()); + + *ppValue = NULL; + + if (m_vmHandle.IsNull()) + { + return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED; + } + + hr = RefreshHandleValue(); + if (FAILED(hr)) + { + return hr; + } + + if ((m_info.objRefBad) || (m_info.objRef == NULL)) + { + return CORDBG_E_BAD_REFERENCE_VALUE; + } + + EX_TRY + { + hr = CordbReferenceValue::DereferenceCommon(m_appdomain, + m_type, + NULL, // don't support typed-by-refs + &m_info, + ppValue); + } + EX_CATCH_HRESULT(hr); + return hr; +} // CordbHandleValue::Dereference + +HRESULT CordbHandleValue::DereferenceStrong(ICorDebugValue **ppValue) +{ + return E_NOTIMPL; +} + +// CordbHeapValue3Impl::GetThreadOwningMonitorLock +// If a managed thread owns the monitor lock on this object then *ppThread +// will point to that thread and S_OK will be returned. The thread object is valid +// until the thread exits. *pAcquisitionCount will indicate the number of times +// this thread would need to release the lock before it returns to being +// unowned. +// If no managed thread owns the monitor lock on this object then *ppThread +// and pAcquisitionCount will be unchanged and S_FALSE returned. +// If ppThread or pAcquisitionCount is not a valid pointer the result is +// undefined. +// If any error occurs such that it cannot be determined which, if any, thread +// owns the monitor lock on this object then a failing HRESULT will be returned +HRESULT CordbHeapValue3Impl::GetThreadOwningMonitorLock(CordbProcess* pProcess, + CORDB_ADDRESS remoteObjAddress, + ICorDebugThread **ppThread, + DWORD *pAcquisitionCount) +{ + HRESULT hr = S_OK; + EX_TRY + { + IDacDbiInterface *pDac = pProcess->GetDAC(); + VMPTR_Object vmObj = pDac->GetObject(remoteObjAddress); + MonitorLockInfo info = pDac->GetThreadOwningMonitorLock(vmObj); + if(info.acquisitionCount == 0) + { + // unowned + *ppThread = NULL; + *pAcquisitionCount = 0; + hr = S_FALSE; + } + else + { + RSLockHolder lockHolder(pProcess->GetProcessLock()); + CordbThread* pThread = pProcess->LookupOrCreateThread(info.lockOwner); + pThread->QueryInterface(__uuidof(ICorDebugThread), (VOID**) ppThread); + *pAcquisitionCount = info.acquisitionCount; + hr = S_OK; + } + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// A small helper for CordbHeapValue3Impl::GetMonitorEventWaitList that adds each enumerated thread to an array +// Arguments: +// vmThread - The thread to add +// puserData - the array to add it to +VOID ThreadEnumerationCallback(VMPTR_Thread vmThread, VOID* pUserData) +{ + CQuickArrayList<VMPTR_Thread>* pThreadList = (CQuickArrayList<VMPTR_Thread>*) pUserData; + pThreadList->Push(vmThread); +} + +// CordbHeapValue3Impl::GetMonitorEventWaitList +// Provides an ordered list of threads which are queued on the event associated +// with a monitor lock. The first thread in the list is the first thread which +// will be released by the next call to Monitor.Pulse, the next thread in the list +// will be released on the following call, and so on. +// If this list is non-empty S_OK will be returned, if it is empty S_FALSE +// will be returned (the enumeration is still valid, just empty). +// In either case the enumeration interface is only usable for the duration +// of the current synchronized state, however the threads interfaces dispensed +// from it are valid until the thread exits. +// If ppThread is not a valid pointer the result is undefined. +// If any error occurs such that it cannot be determined which, if any, threads +// are waiting for the monitor then a failing HRESULT will be returned +HRESULT CordbHeapValue3Impl::GetMonitorEventWaitList(CordbProcess* pProcess, + CORDB_ADDRESS remoteObjAddress, + ICorDebugThreadEnum **ppThreadEnum) +{ + HRESULT hr = S_OK; + RSSmartPtr<CordbThread> *rsThreads = NULL; + EX_TRY + { + IDacDbiInterface *pDac = pProcess->GetDAC(); + VMPTR_Object vmObj = pDac->GetObject(remoteObjAddress); + CQuickArrayList<VMPTR_Thread> threads; + pDac->EnumerateMonitorEventWaitList(vmObj, + (IDacDbiInterface::FP_THREAD_ENUMERATION_CALLBACK)ThreadEnumerationCallback, (VOID*)&threads); + + rsThreads = new RSSmartPtr<CordbThread>[threads.Size()]; + { + RSLockHolder lockHolder(pProcess->GetProcessLock()); + for(DWORD i = 0; i < threads.Size(); i++) + { + rsThreads[i].Assign(pProcess->LookupOrCreateThread(threads[i])); + } + } + + CordbThreadEnumerator* threadEnum = + new CordbThreadEnumerator(pProcess, rsThreads, (DWORD)threads.Size()); + pProcess->GetContinueNeuterList()->Add(pProcess, threadEnum); + threadEnum->QueryInterface(__uuidof(ICorDebugThreadEnum), (VOID**)ppThreadEnum); + if(threads.Size() == 0) + { + hr = S_FALSE; + } + } + EX_CATCH_HRESULT(hr); + delete [] rsThreads; + return hr; +} |