// 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: funceval.cpp // // // funceval.cpp - Debugger func-eval routines. // // **************************************************************************** // Putting code & #includes, #defines, etc, before the stdafx.h will // cause the code,etc, to be silently ignored #include "stdafx.h" #include "debugdebugger.h" #include "../inc/common.h" #include "perflog.h" #include "eeconfig.h" // This is here even for retail & free builds... #include "../../dlls/mscorrc/resource.h" #include "vars.hpp" #include "threads.h" #include "appdomain.inl" #include #include "ilformatter.h" #ifndef DACCESS_COMPILE // // This is the main file for processing func-evals. Nestle in // with a cup o' tea and read on. // // The most common case is handled in GCProtectArgsAndDoNormalFuncEval(), which follows // all the comments below. The two other corner cases are handled in // FuncEvalHijackWorker(), and are extremely straight-forward. // // There are several steps to successfully processing a func-eval. At a // very high level, the first step is to gather all the information necessary // to make the call (specifically, gather arg info and method info); the second // step is to actually make the call to managed code; finally, the third step // is to take all results and unpackage them. // // The first step (gathering arg and method info) has several critical passes that // must be made. // a) Protect all passed in args from a GC. // b) Transition into the appropriate AppDomain if necessary // c) Pre-allocate object for 'new' calls and, if necessary, box the 'this' argument. (May cause a GC) // d) Gather method info (May cause GC) // e) Gather info from runtime about args. (May cause a GC) // f) Box args that need to be, GC-protecting the newly boxed items. (May cause a GC) // g) Pre-allocate object for return values. (May cause a GC) // h) Copy to pBufferForArgsArray all the args. This array is used to hold values that // may need writable memory for ByRef args. // i) Create and load pArgumentArray to be passed as the stack for the managed call. // NOTE: From the time we load the first argument into the stack we cannot cause a GC // as the argument array cannot be GC-protected. // // The second step (Making the managed call), is relatively easy, and is a single call. // // The third step (unpacking all results), has a couple of passes as well. // a) Copy back all resulting values. // b) Free all temporary work memory. // // // The most difficult part of doing a func-eval is the first step, since once you // have everything set up, unpacking and calling are reverse, gc-safe, operations. Thus, // elaboration is needed on the first step. // // a) Protect all passed in args from a GC. This must be done in a gc-forbid region, // and the code path to this function must not trigger a gc either. In this function five // parallel arrays are used: pObjectRefArray, pMaybeInteriorPtrArray, pByRefMaybeInteriorPtrArray, // pBufferForArgsArray, and pArguments. // pObjectRefArray is used to gc-protect all arguments and results that are objects. // pMaybeInteriorPtrArray is used to gc-protect all arguments that might be pointers // to an interior of a managed object. // pByRefMaybeInteriorPtrArray is similar to pMaybeInteriorPtrArray, except that it protects the // address of the arguments instead of the arguments themselves. This is needed because we may have // by ref arguments whose address is an interior pointer into the GC heap. // pBufferForArgsArray is used strictly as a buffer for copying primitives // that need to be passed as ByRef, or may be enregistered. This array also holds // handles. // These first two arrays are mutually exclusive, that is, if there is an entry // in one array at index i, there should be no entry in either of the other arrays at // the same index. // pArguments is used as the complete array of arguments to pass to the managed function. // // Unfortunately the necessary information to complete pass (a) perfectly may cause a gc, so // instead, pass (a) is over-aggressive and protects the following: All object refs into // pObjectRefArray, and puts all values that could be raw pointers into pMaybeInteriorPtrArray. // // b) Discovers the method to be called, and if it is a 'new' allocate an object for the result. // // c) Gather information about the method that will be called. // // d) Here we gather information from the method signature which tells which args are // ByRef and various other flags. We will use this information in later passes. // // e) Using the information in pass (c), for each argument: box arguments, placing newly // boxed items into pObjectRefArray immediately after creating them. // // f) Pre-allocate any object for a returned value. // // g) Using the information is pass (c), all arguments are copied into a scratch buffer before // invoking the managed function. // // h) pArguments is loaded from the pre-allocated return object, the individual elements // of the other 3 arrays, and from any non-ByRef literals. This is the complete stack // to be passed to the managed function. For performance increase, it can remove any // overly aggressive items that were placed in pMaybeInteriorPtrArray. // // // IsElementTypeSpecial() // // This is a simple function used to check if a CorElementType needs special handling for func eval. // // parameters: type - the CorElementType which we need to check // // return value: true if the specified type needs special handling // inline static bool IsElementTypeSpecial(CorElementType type) { LIMITED_METHOD_CONTRACT; return ((type == ELEMENT_TYPE_CLASS) || (type == ELEMENT_TYPE_OBJECT) || (type == ELEMENT_TYPE_ARRAY) || (type == ELEMENT_TYPE_SZARRAY) || (type == ELEMENT_TYPE_STRING)); } // // GetAndSetLiteralValue() // // This helper function extracts the value out of the source pointer while taking into account alignment and size. // Then it stores the value into the destination pointer, again taking into account alignment and size. // // parameters: pDst - destination pointer // dstType - the CorElementType of the destination value // pSrc - source pointer // srcType - the CorElementType of the source value // // return value: none // inline static void GetAndSetLiteralValue(LPVOID pDst, CorElementType dstType, LPVOID pSrc, CorElementType srcType) { LIMITED_METHOD_CONTRACT; UINT64 srcValue; // Retrieve the value using the source CorElementType. switch (g_pEEInterface->GetSizeForCorElementType(srcType)) { case 1: srcValue = (UINT64)*((BYTE*)pSrc); break; case 2: srcValue = (UINT64)*((USHORT*)pSrc); break; case 4: srcValue = (UINT64)*((UINT32*)pSrc); break; case 8: srcValue = (UINT64)*((UINT64*)pSrc); break; default: UNREACHABLE(); } // Cast to the appropriate type using the destination CorElementType. switch (dstType) { case ELEMENT_TYPE_BOOLEAN: *(BYTE*)pDst = (BYTE)!!srcValue; break; case ELEMENT_TYPE_I1: *(INT8*)pDst = (INT8)srcValue; break; case ELEMENT_TYPE_U1: *(UINT8*)pDst = (UINT8)srcValue; break; case ELEMENT_TYPE_I2: *(INT16*)pDst = (INT16)srcValue; break; case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: *(UINT16*)pDst = (UINT16)srcValue; break; #if !defined(_WIN64) case ELEMENT_TYPE_I: #endif case ELEMENT_TYPE_I4: *(int*)pDst = (int)srcValue; break; #if !defined(_WIN64) case ELEMENT_TYPE_U: #endif case ELEMENT_TYPE_U4: case ELEMENT_TYPE_R4: *(unsigned*)pDst = (unsigned)srcValue; break; #if defined(_WIN64) case ELEMENT_TYPE_I: #endif case ELEMENT_TYPE_I8: case ELEMENT_TYPE_R8: *(INT64*)pDst = (INT64)srcValue; break; #if defined(_WIN64) case ELEMENT_TYPE_U: #endif case ELEMENT_TYPE_U8: *(UINT64*)pDst = (UINT64)srcValue; break; case ELEMENT_TYPE_FNPTR: case ELEMENT_TYPE_PTR: *(void **)pDst = (void *)(SIZE_T)srcValue; break; default: UNREACHABLE(); } } // // Throw on not supported func evals // static void ValidateFuncEvalReturnType(DebuggerIPCE_FuncEvalType evalType, MethodTable * pMT) { CONTRACTL { THROWS; GC_TRIGGERS; } CONTRACTL_END; if (pMT == g_pStringClass) { if (evalType == DB_IPCE_FET_NEW_OBJECT || evalType == DB_IPCE_FET_NEW_OBJECT_NC) { // Cannot call New object on String constructor. COMPlusThrow(kArgumentException,W("Argument_CannotCreateString")); } } else if (g_pEEInterface->IsTypedReference(pMT)) { // Cannot create typed references through funceval. if (evalType == DB_IPCE_FET_NEW_OBJECT || evalType == DB_IPCE_FET_NEW_OBJECT_NC || evalType == DB_IPCE_FET_NORMAL) { COMPlusThrow(kArgumentException, W("Argument_CannotCreateTypedReference")); } } } // // Given a register, return the value. // static SIZE_T GetRegisterValue(DebuggerEval *pDE, CorDebugRegister reg, void *regAddr, SIZE_T regValue) { LIMITED_METHOD_CONTRACT; SIZE_T ret = 0; // Check whether the register address is the marker value for a register in a non-leaf frame. // This is related to the funceval breaking change. // if (regAddr == CORDB_ADDRESS_TO_PTR(kNonLeafFrameRegAddr)) { ret = regValue; } else { switch (reg) { case REGISTER_STACK_POINTER: ret = (SIZE_T)GetSP(&pDE->m_context); break; case REGISTER_FRAME_POINTER: ret = (SIZE_T)GetFP(&pDE->m_context); break; #if defined(_TARGET_X86_) case REGISTER_X86_EAX: ret = pDE->m_context.Eax; break; case REGISTER_X86_ECX: ret = pDE->m_context.Ecx; break; case REGISTER_X86_EDX: ret = pDE->m_context.Edx; break; case REGISTER_X86_EBX: ret = pDE->m_context.Ebx; break; case REGISTER_X86_ESI: ret = pDE->m_context.Esi; break; case REGISTER_X86_EDI: ret = pDE->m_context.Edi; break; #elif defined(_TARGET_AMD64_) case REGISTER_AMD64_RAX: ret = pDE->m_context.Rax; break; case REGISTER_AMD64_RCX: ret = pDE->m_context.Rcx; break; case REGISTER_AMD64_RDX: ret = pDE->m_context.Rdx; break; case REGISTER_AMD64_RBX: ret = pDE->m_context.Rbx; break; case REGISTER_AMD64_RSI: ret = pDE->m_context.Rsi; break; case REGISTER_AMD64_RDI: ret = pDE->m_context.Rdi; break; case REGISTER_AMD64_R8: ret = pDE->m_context.R8; break; case REGISTER_AMD64_R9: ret = pDE->m_context.R9; break; case REGISTER_AMD64_R10: ret = pDE->m_context.R10; break; case REGISTER_AMD64_R11: ret = pDE->m_context.R11; break; case REGISTER_AMD64_R12: ret = pDE->m_context.R12; break; case REGISTER_AMD64_R13: ret = pDE->m_context.R13; break; case REGISTER_AMD64_R14: ret = pDE->m_context.R14; break; case REGISTER_AMD64_R15: ret = pDE->m_context.R15; break; // fall through case REGISTER_AMD64_XMM0: case REGISTER_AMD64_XMM1: case REGISTER_AMD64_XMM2: case REGISTER_AMD64_XMM3: case REGISTER_AMD64_XMM4: case REGISTER_AMD64_XMM5: case REGISTER_AMD64_XMM6: case REGISTER_AMD64_XMM7: case REGISTER_AMD64_XMM8: case REGISTER_AMD64_XMM9: case REGISTER_AMD64_XMM10: case REGISTER_AMD64_XMM11: case REGISTER_AMD64_XMM12: case REGISTER_AMD64_XMM13: case REGISTER_AMD64_XMM14: case REGISTER_AMD64_XMM15: ret = FPSpillToR8(&(pDE->m_context.Xmm0) + (reg - REGISTER_AMD64_XMM0)); break; #endif // !_TARGET_X86_ && !_TARGET_AMD64_ default: _ASSERT(!"Invalid register number!"); } } return ret; } // // Given a register, set its value. // static void SetRegisterValue(DebuggerEval *pDE, CorDebugRegister reg, void *regAddr, SIZE_T newValue) { CONTRACTL { THROWS; } CONTRACTL_END; // Check whether the register address is the marker value for a register in a non-leaf frame. // If so, then we can't update the register. Throw an exception to communicate this error. if (regAddr == CORDB_ADDRESS_TO_PTR(kNonLeafFrameRegAddr)) { COMPlusThrowHR(CORDBG_E_FUNC_EVAL_CANNOT_UPDATE_REGISTER_IN_NONLEAF_FRAME); return; } else { switch (reg) { case REGISTER_STACK_POINTER: SetSP(&pDE->m_context, newValue); break; case REGISTER_FRAME_POINTER: SetFP(&pDE->m_context, newValue); break; #ifdef _TARGET_X86_ case REGISTER_X86_EAX: pDE->m_context.Eax = newValue; break; case REGISTER_X86_ECX: pDE->m_context.Ecx = newValue; break; case REGISTER_X86_EDX: pDE->m_context.Edx = newValue; break; case REGISTER_X86_EBX: pDE->m_context.Ebx = newValue; break; case REGISTER_X86_ESI: pDE->m_context.Esi = newValue; break; case REGISTER_X86_EDI: pDE->m_context.Edi = newValue; break; #elif defined(_TARGET_AMD64_) case REGISTER_AMD64_RAX: pDE->m_context.Rax = newValue; break; case REGISTER_AMD64_RCX: pDE->m_context.Rcx = newValue; break; case REGISTER_AMD64_RDX: pDE->m_context.Rdx = newValue; break; case REGISTER_AMD64_RBX: pDE->m_context.Rbx = newValue; break; case REGISTER_AMD64_RSI: pDE->m_context.Rsi = newValue; break; case REGISTER_AMD64_RDI: pDE->m_context.Rdi = newValue; break; case REGISTER_AMD64_R8: pDE->m_context.R8= newValue; break; case REGISTER_AMD64_R9: pDE->m_context.R9= newValue; break; case REGISTER_AMD64_R10: pDE->m_context.R10= newValue; break; case REGISTER_AMD64_R11: pDE->m_context.R11 = newValue; break; case REGISTER_AMD64_R12: pDE->m_context.R12 = newValue; break; case REGISTER_AMD64_R13: pDE->m_context.R13 = newValue; break; case REGISTER_AMD64_R14: pDE->m_context.R14 = newValue; break; case REGISTER_AMD64_R15: pDE->m_context.R15 = newValue; break; // fall through case REGISTER_AMD64_XMM0: case REGISTER_AMD64_XMM1: case REGISTER_AMD64_XMM2: case REGISTER_AMD64_XMM3: case REGISTER_AMD64_XMM4: case REGISTER_AMD64_XMM5: case REGISTER_AMD64_XMM6: case REGISTER_AMD64_XMM7: case REGISTER_AMD64_XMM8: case REGISTER_AMD64_XMM9: case REGISTER_AMD64_XMM10: case REGISTER_AMD64_XMM11: case REGISTER_AMD64_XMM12: case REGISTER_AMD64_XMM13: case REGISTER_AMD64_XMM14: case REGISTER_AMD64_XMM15: R8ToFPSpill(&(pDE->m_context.Xmm0) + (reg - REGISTER_AMD64_XMM0), newValue); break; #endif // !_TARGET_X86_ && !_TARGET_AMD64_ default: _ASSERT(!"Invalid register number!"); } } } /* * GetRegsiterValueAndReturnAddress * * This routine takes out a value from a register, or set of registers, into one of * the given buffers (depending on size), and returns a pointer to the filled in * buffer, or NULL on error. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * pFEAD - Information about this particular argument. * pInt64Buf - pointer to a buffer of type INT64 * pSizeTBuf - pointer to a buffer of native size type. * * Returns: * pointer to the filled in buffer, else NULL on error. * */ static PVOID GetRegisterValueAndReturnAddress(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *pFEAD, INT64 *pInt64Buf, SIZE_T *pSizeTBuf ) { LIMITED_METHOD_CONTRACT; PVOID pAddr; #if !defined(_WIN64) pAddr = pInt64Buf; DWORD *pLow = (DWORD*)(pInt64Buf); DWORD *pHigh = pLow + 1; #endif // _WIN64 switch (pFEAD->argHome.kind) { #if !defined(_WIN64) case RAK_REGREG: *pLow = GetRegisterValue(pDE, pFEAD->argHome.u.reg2, pFEAD->argHome.u.reg2Addr, pFEAD->argHome.u.reg2Value); *pHigh = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); break; case RAK_MEMREG: *pLow = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); *pHigh = *((DWORD*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr)); break; case RAK_REGMEM: *pLow = *((DWORD*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr)); *pHigh = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); break; #endif // _WIN64 case RAK_REG: // Simply grab the value out of the proper register. *pSizeTBuf = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); pAddr = pSizeTBuf; break; default: pAddr = NULL; break; } return pAddr; } //--------------------------------------------------------------------------------------- // // Clean up any temporary value class variables we have allocated for the funceval. // // Arguments: // pStackStructArray - array whose elements track the location and type of the temporary variables // void CleanUpTemporaryVariables(ValueClassInfo ** ppProtectedValueClasses) { while (*ppProtectedValueClasses != NULL) { ValueClassInfo * pValueClassInfo = *ppProtectedValueClasses; *ppProtectedValueClasses = pValueClassInfo->pNext; DeleteInteropSafe(reinterpret_cast(pValueClassInfo)); } } #ifdef _DEBUG // // Create a parallel array that tracks that we have initialized information in // each array. // #define MAX_DATA_LOCATIONS_TRACKED 100 typedef DWORD DataLocation; #define DL_NonExistent 0x00 #define DL_ObjectRefArray 0x01 #define DL_MaybeInteriorPtrArray 0x02 #define DL_BufferForArgsArray 0x04 #define DL_All 0xFF #endif // _DEBUG /* * GetFuncEvalArgValue * * This routine is used to fill the pArgument array with the appropriate value. This function * uses the three parallel array entries given, and places the correct value, or reference to * the value in pArgument. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * pFEAD - Information about this particular argument. * isByRef - Is the argument being passed ByRef. * fNeedBoxOrUnbox - Did the argument need boxing or unboxing. * argTH - The type handle for the argument. * byrefArgSigType - The signature type of a parameter that isByRef == true. * pArgument - Location to place the reference or value. * pMaybeInteriorPtrArg - A pointer that contains a value that may be pointers to * the interior of a managed object. * pObjectRefArg - A pointer that contains an object ref. It was built previously. * pBufferArg - A pointer for holding stuff that did not need to be protected. * * Returns: * None. * */ static void GetFuncEvalArgValue(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *pFEAD, bool isByRef, bool fNeedBoxOrUnbox, TypeHandle argTH, CorElementType byrefArgSigType, TypeHandle byrefArgTH, ARG_SLOT *pArgument, void *pMaybeInteriorPtrArg, OBJECTREF *pObjectRefArg, INT64 *pBufferArg, ValueClassInfo ** ppProtectedValueClasses, CorElementType argSigType DEBUG_ARG(DataLocation dataLocation) ) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; _ASSERTE((dataLocation != DL_NonExistent) || (pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE)); switch (pFEAD->argElementType) { case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R8: { INT64 *pSource; #if defined(_WIN64) _ASSERTE(dataLocation & DL_MaybeInteriorPtrArray); pSource = (INT64 *)pMaybeInteriorPtrArg; #else // !_WIN64 _ASSERTE(dataLocation & DL_BufferForArgsArray); pSource = pBufferArg; #endif // !_WIN64 if (!isByRef) { *((INT64*)pArgument) = *pSource; } else { *pArgument = PtrToArgSlot(pSource); } } break; case ELEMENT_TYPE_VALUETYPE: { SIZE_T v = 0; LPVOID pAddr = NULL; INT64 bigVal = 0; if (pFEAD->argAddr != NULL) { pAddr = *((void **)pMaybeInteriorPtrArg); } else { pAddr = GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v); if (pAddr == NULL) { COMPlusThrow(kArgumentNullException); } } _ASSERTE(pAddr); if (!fNeedBoxOrUnbox && !isByRef) { _ASSERTE(argTH.GetMethodTable()); unsigned size = argTH.GetMethodTable()->GetNumInstanceFieldBytes(); if (size <= sizeof(ARG_SLOT) #if defined(_TARGET_AMD64_) // On AMD64 we pass value types of size which are not powers of 2 by ref. && ((size & (size-1)) == 0) #endif // _TARGET_AMD64_ ) { memcpyNoGCRefs(ArgSlotEndianessFixup(pArgument, sizeof(LPVOID)), pAddr, size); } else { _ASSERTE(pFEAD->argAddr != NULL); #if defined(ENREGISTERED_PARAMTYPE_MAXSIZE) if (ArgIterator::IsArgPassedByRef(argTH)) { // On X64, by-value value class arguments which are bigger than 8 bytes are passed by reference // according to the native calling convention. The same goes for value class arguments whose size // is smaller than 8 bytes but not a power of 2. To avoid side effets, we need to allocate a // temporary variable and pass that by reference instead. On ARM64, by-value value class // arguments which are bigger than 16 bytes are passed by reference. _ASSERTE(ppProtectedValueClasses != NULL); BYTE * pTemp = new (interopsafe) BYTE[ALIGN_UP(sizeof(ValueClassInfo), 8) + size]; ValueClassInfo * pValueClassInfo = (ValueClassInfo *)pTemp; LPVOID pData = pTemp + ALIGN_UP(sizeof(ValueClassInfo), 8); memcpyNoGCRefs(pData, pAddr, size); *pArgument = PtrToArgSlot(pData); pValueClassInfo->pData = pData; pValueClassInfo->pMT = argTH.GetMethodTable(); pValueClassInfo->pNext = *ppProtectedValueClasses; *ppProtectedValueClasses = pValueClassInfo; } else #endif // ENREGISTERED_PARAMTYPE_MAXSIZE *pArgument = PtrToArgSlot(pAddr); } } else { if (fNeedBoxOrUnbox) { *pArgument = ObjToArgSlot(*pObjectRefArg); } else { if (pFEAD->argAddr) { *pArgument = PtrToArgSlot(pAddr); } else { // The argument is the address of where we're holding the primitive in the PrimitiveArg array. We // stick the real value from the register into the PrimitiveArg array. It should be in a single // register since it is pointer-sized. _ASSERTE( pFEAD->argHome.kind == RAK_REG ); *pArgument = PtrToArgSlot(pBufferArg); *pBufferArg = (INT64)v; } } } } break; default: // literal values smaller than 8 bytes and "special types" (e.g. object, string, etc.) { INT64 *pSource; INDEBUG(DataLocation expectedLocation); #ifdef _TARGET_X86_ if ((pFEAD->argElementType == ELEMENT_TYPE_I4) || (pFEAD->argElementType == ELEMENT_TYPE_U4) || (pFEAD->argElementType == ELEMENT_TYPE_R4)) { INDEBUG(expectedLocation = DL_MaybeInteriorPtrArray); pSource = (INT64 *)pMaybeInteriorPtrArg; } else #endif if (IsElementTypeSpecial(pFEAD->argElementType)) { INDEBUG(expectedLocation = DL_ObjectRefArray); pSource = (INT64 *)pObjectRefArg; } else { INDEBUG(expectedLocation = DL_BufferForArgsArray); pSource = pBufferArg; } if (pFEAD->argAddr != NULL) { if (!isByRef) { if (pFEAD->argIsHandleValue) { _ASSERTE(dataLocation & DL_BufferForArgsArray); OBJECTHANDLE oh = *((OBJECTHANDLE*)(pBufferArg)); // Always comes from buffer *pArgument = PtrToArgSlot(g_pEEInterface->GetObjectFromHandle(oh)); } else { _ASSERTE(dataLocation & expectedLocation); if (pSource != NULL) { *pArgument = *pSource; // may come from either array. } else { *pArgument = NULL; } } } else { if (pFEAD->argIsHandleValue) { _ASSERTE(dataLocation & DL_BufferForArgsArray); *pArgument = *pBufferArg; // Buffer contains the object handle, in this case, so // just copy that across. } else { _ASSERTE(dataLocation & expectedLocation); *pArgument = PtrToArgSlot(pSource); // Load the argument with the address of our buffer. } } } else if (pFEAD->argIsLiteral) { _ASSERTE(dataLocation & expectedLocation); if (!isByRef) { if (pSource != NULL) { *pArgument = *pSource; // may come from either array. } else { *pArgument = NULL; } } else { *pArgument = PtrToArgSlot(pSource); // Load the argument with the address of our buffer. } } else { if (!isByRef) { if (pSource != NULL) { *pArgument = *pSource; // may come from either array. } else { *pArgument = NULL; } } else { *pArgument = PtrToArgSlot(pSource); // Load the argument with the address of our buffer. } } // If we need to unbox, then unbox the arg now. if (fNeedBoxOrUnbox) { if (!isByRef) { // function expects valuetype, argument received is class or object // Take the ObjectRef off the stack. ARG_SLOT oi1 = *pArgument; OBJECTREF o1 = ArgSlotToObj(oi1); // For Nullable types, we need a 'true' nullable to pass to the function, and we do this // by passing a boxed nullable that we unbox. We allocated this space earlier however we // did not know the data location until just now. Fill it in with the data and use that // to pass to the function. if (Nullable::IsNullableType(argTH)) { _ASSERTE(*pObjectRefArg != 0); _ASSERTE((*pObjectRefArg)->GetMethodTable() == argTH.GetMethodTable()); if (o1 != *pObjectRefArg) { Nullable::UnBoxNoCheck((*pObjectRefArg)->GetData(), o1, (*pObjectRefArg)->GetMethodTable()); o1 = *pObjectRefArg; } } if (o1 == NULL) { COMPlusThrow(kArgumentNullException); } if (!o1->GetMethodTable()->IsValueType()) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } // Unbox the little fella to get a pointer to the raw data. void *pData = o1->GetData(); // Get its size to make sure it fits in an ARG_SLOT unsigned size = o1->GetMethodTable()->GetNumInstanceFieldBytes(); if (size <= sizeof(ARG_SLOT)) { // Its not ByRef, so we need to copy the value class onto the ARG_SLOT. CopyValueClassUnchecked(ArgSlotEndianessFixup(pArgument, sizeof(LPVOID)), pData, o1->GetMethodTable()); } else { // Store pointer to the space in the ARG_SLOT *pArgument = PtrToArgSlot(pData); } } else { // Function expects byref valuetype, argument received is byref class. // Grab the ObjectRef off the stack via the pointer on the stack. Note: the stack has a pointer to the // ObjectRef since the arg was specified as byref. OBJECTREF* op1 = (OBJECTREF*)ArgSlotToPtr(*pArgument); if (op1 == NULL) { COMPlusThrow(kArgumentNullException); } OBJECTREF o1 = *op1; // For Nullable types, we need a 'true' nullable to pass to the function, and we do this // by passing a boxed nullable that we unbox. We allocated this space earlier however we // did not know the data location until just now. Fill it in with the data and use that // to pass to the function. if (Nullable::IsNullableType(byrefArgTH)) { _ASSERTE(*pObjectRefArg != 0 && (*pObjectRefArg)->GetMethodTable() == byrefArgTH.GetMethodTable()); if (o1 != *pObjectRefArg) { Nullable::UnBoxNoCheck((*pObjectRefArg)->GetData(), o1, (*pObjectRefArg)->GetMethodTable()); o1 = *pObjectRefArg; } } if (o1 == NULL) { COMPlusThrow(kArgumentNullException); } _ASSERTE(o1->GetMethodTable()->IsValueType()); // Unbox the little fella to get a pointer to the raw data. void *pData = o1->GetData(); // If it is ByRef, then we just replace the ObjectRef with a pointer to the data. *pArgument = PtrToArgSlot(pData); } } // Validate any objectrefs that are supposed to be on the stack. // @TODO: Move this to before the boxing/unboxing above if (!fNeedBoxOrUnbox) { Object *objPtr; if (!isByRef) { if (IsElementTypeSpecial(argSigType)) { // validate the integrity of the object objPtr = (Object*)ArgSlotToPtr(*pArgument); if (FAILED(ValidateObject(objPtr))) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } } } else { _ASSERTE(argSigType == ELEMENT_TYPE_BYREF); if (IsElementTypeSpecial(byrefArgSigType)) { objPtr = *(Object**)(ArgSlotToPtr(*pArgument)); if (FAILED(ValidateObject(objPtr))) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } } } } } } } static CorDebugRegister GetArgAddrFromReg( DebuggerIPCE_FuncEvalArgData *pFEAD) { CorDebugRegister retval = REGISTER_INSTRUCTION_POINTER; // good as default as any #if defined(_WIN64) retval = (pFEAD->argHome.kind == RAK_REG ? pFEAD->argHome.reg1 : (CorDebugRegister)((int)REGISTER_IA64_F0 + pFEAD->argHome.floatIndex)); #else // !_WIN64 retval = pFEAD->argHome.reg1; #endif // !_WIN64 return retval; } // // Given info about a byref argument, retrieve the current value from the pBufferForArgsArray, // the pMaybeInteriorPtrArray, the pByRefMaybeInteriorPtrArray, or the pObjectRefArray. Then // place it back into the proper register or address. // // Note that we should never use the argAddr of the DebuggerIPCE_FuncEvalArgData in this function // since the address may be an interior GC pointer and may have been moved by the GC. Instead, // use the pByRefMaybeInteriorPtrArray. // static void SetFuncEvalByRefArgValue(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *pFEAD, CorElementType byrefArgSigType, INT64 bufferByRefArg, void *maybeInteriorPtrArg, void *byRefMaybeInteriorPtrArg, OBJECTREF objectRefByRefArg) { WRAPPER_NO_CONTRACT; switch (pFEAD->argElementType) { case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R8: // 64bit values { INT64 source; #if defined(_WIN64) source = (INT64)maybeInteriorPtrArg; #else // !_WIN64 source = bufferByRefArg; #endif // !_WIN64 if (pFEAD->argIsLiteral) { // If this was a literal arg, then copy the updated primitive back into the literal. memcpy(pFEAD->argLiteralData, &source, sizeof(pFEAD->argLiteralData)); } else if (pFEAD->argAddr != NULL) { *((INT64 *)byRefMaybeInteriorPtrArg) = source; return; } else { #if !defined(_WIN64) // RAK_REG is the only 4 byte type, all others are 8 byte types. _ASSERTE(pFEAD->argHome.kind != RAK_REG); SIZE_T *pLow = (SIZE_T*)(&source); SIZE_T *pHigh = pLow + 1; switch (pFEAD->argHome.kind) { case RAK_REGREG: SetRegisterValue(pDE, pFEAD->argHome.u.reg2, pFEAD->argHome.u.reg2Addr, *pLow); SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, *pHigh); break; case RAK_MEMREG: SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, *pLow); *((SIZE_T*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr)) = *pHigh; break; case RAK_REGMEM: *((SIZE_T*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr)) = *pLow; SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, *pHigh); break; default: break; } #else // _WIN64 // The only types we use are RAK_REG and RAK_FLOAT, and both of them can be 4 or 8 bytes. _ASSERTE((pFEAD->argHome.kind == RAK_REG) || (pFEAD->argHome.kind == RAK_FLOAT)); SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, source); #endif // _WIN64 } } break; default: // literal values smaller than 8 bytes and "special types" (e.g. object, array, string, etc.) { SIZE_T source; #ifdef _TARGET_X86_ if ((pFEAD->argElementType == ELEMENT_TYPE_I4) || (pFEAD->argElementType == ELEMENT_TYPE_U4) || (pFEAD->argElementType == ELEMENT_TYPE_R4)) { source = (SIZE_T)maybeInteriorPtrArg; } else { #endif source = (SIZE_T)bufferByRefArg; #ifdef _TARGET_X86_ } #endif if (pFEAD->argIsLiteral) { // If this was a literal arg, then copy the updated primitive back into the literal. // The literall buffer is a fixed size (8 bytes), but our source may be 4 or 8 bytes // depending on the platform. To prevent reading past the end of the source, we // zero the destination buffer and copy only as many bytes as available. memset( pFEAD->argLiteralData, 0, sizeof(pFEAD->argLiteralData) ); if (IsElementTypeSpecial(pFEAD->argElementType)) { _ASSERTE( sizeof(pFEAD->argLiteralData) >= sizeof(objectRefByRefArg) ); memcpy(pFEAD->argLiteralData, &objectRefByRefArg, sizeof(objectRefByRefArg)); } else { _ASSERTE( sizeof(pFEAD->argLiteralData) >= sizeof(source) ); memcpy(pFEAD->argLiteralData, &source, sizeof(source)); } } else if (pFEAD->argAddr == NULL) { // If the 32bit value is enregistered, copy it back to the proper regs. // RAK_REG is the only valid 4 byte type on WIN32. On WIN64, both RAK_REG and RAK_FLOAT can be // 4 bytes or 8 bytes. _ASSERTE((pFEAD->argHome.kind == RAK_REG) WIN64_ONLY(|| (pFEAD->argHome.kind == RAK_FLOAT))); CorDebugRegister regNum = GetArgAddrFromReg(pFEAD); // Shove the result back into the proper register. if (IsElementTypeSpecial(pFEAD->argElementType)) { SetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, (SIZE_T)ObjToArgSlot(objectRefByRefArg)); } else { SetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, (SIZE_T)source); } } else { // If the result was an object by ref, then copy back the new location of the object (in GC case). if (pFEAD->argIsHandleValue) { // do nothing. The Handle was passed in the pArgument array directly } else if (IsElementTypeSpecial(pFEAD->argElementType)) { *((SIZE_T*)byRefMaybeInteriorPtrArg) = (SIZE_T)ObjToArgSlot(objectRefByRefArg); } else if (pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE) { // Do nothing, we passed in the pointer to the valuetype in the pArgument array directly. } else { GetAndSetLiteralValue(byRefMaybeInteriorPtrArg, pFEAD->argElementType, &source, ELEMENT_TYPE_PTR); } } } // end default } // end switch } /* * GCProtectAllPassedArgs * * This routine is the first step in doing a func-eval. For a complete overview, see * the comments at the top of this file. * * This routine over-aggressively protects all arguments that may be references to * managed objects. This function cannot crawl the function signature, since doing * so may trigger a GC, and thus, we must assume everything is ByRef. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * pObjectRefArray - An array that contains any object refs. It was built previously. * pMaybeInteriorPtrArray - An array that contains values that may be pointers to * the interior of a managed object. * pBufferForArgsArray - An array for holding stuff that does not need to be protected. * Any handle for the 'this' pointer is put in here for pulling it out later. * * Returns: * None. * */ static void GCProtectAllPassedArgs(DebuggerEval *pDE, OBJECTREF *pObjectRefArray, void **pMaybeInteriorPtrArray, void **pByRefMaybeInteriorPtrArray, INT64 *pBufferForArgsArray DEBUG_ARG(DataLocation pDataLocationArray[]) ) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; } CONTRACTL_END; DebuggerIPCE_FuncEvalArgData *argData = pDE->GetArgData(); unsigned currArgIndex = 0; // // Gather all the information for the parameters. // for ( ; currArgIndex < pDE->m_argCount; currArgIndex++) { DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex]; // In case any of the arguments is a by ref argument and points into the GC heap, // we need to GC protect their addresses as well. if (pFEAD->argAddr != NULL) { pByRefMaybeInteriorPtrArray[currArgIndex] = pFEAD->argAddr; } switch (pFEAD->argElementType) { case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R8: // 64bit values #if defined(_WIN64) // // Only need to worry about protecting if a pointer is a 64 bit quantity. // _ASSERTE(sizeof(void *) == sizeof(INT64)); if (pFEAD->argAddr != NULL) { pMaybeInteriorPtrArray[currArgIndex] = *((void **)(pFEAD->argAddr)); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray; } #endif } else if (pFEAD->argIsLiteral) { _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(void *)); // // If this is a byref literal arg, then it maybe an interior ptr. // void *v = NULL; memcpy(&v, pFEAD->argLiteralData, sizeof(v)); pMaybeInteriorPtrArray[currArgIndex] = v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray; } #endif } else { _ASSERTE((pFEAD->argHome.kind == RAK_REG) || (pFEAD->argHome.kind == RAK_FLOAT)); CorDebugRegister regNum = GetArgAddrFromReg(pFEAD); SIZE_T v = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); pMaybeInteriorPtrArray[currArgIndex] = (void *)(v); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray; } #endif } #endif // _WIN64 break; case ELEMENT_TYPE_VALUETYPE: // // If the value type address could be an interior pointer. // if (pFEAD->argAddr != NULL) { pMaybeInteriorPtrArray[currArgIndex] = ((void **)(pFEAD->argAddr)); } INDEBUG(pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray); break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_OBJECT: case ELEMENT_TYPE_STRING: case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: if (pFEAD->argAddr != NULL) { if (pFEAD->argIsHandleValue) { OBJECTHANDLE oh = (OBJECTHANDLE)(pFEAD->argAddr); pBufferForArgsArray[currArgIndex] = (INT64)(size_t)oh; INDEBUG(pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray); } else { pObjectRefArray[currArgIndex] = *((OBJECTREF *)(pFEAD->argAddr)); INDEBUG(pDataLocationArray[currArgIndex] |= DL_ObjectRefArray); } } else if (pFEAD->argIsLiteral) { _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(OBJECTREF)); OBJECTREF v = NULL; memcpy(&v, pFEAD->argLiteralData, sizeof(v)); pObjectRefArray[currArgIndex] = v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_ObjectRefArray; } #endif } else { // RAK_REG is the only valid pointer-sized type. _ASSERTE(pFEAD->argHome.kind == RAK_REG); // Simply grab the value out of the proper register. SIZE_T v = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); // The argument is the address. pObjectRefArray[currArgIndex] = (OBJECTREF)v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_ObjectRefArray; } #endif } break; case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_R4: // 32bit values #ifdef _TARGET_X86_ _ASSERTE(sizeof(void *) == sizeof(INT32)); if (pFEAD->argAddr != NULL) { if (pFEAD->argIsHandleValue) { // // Ignorable - no need to protect // } else { pMaybeInteriorPtrArray[currArgIndex] = *((void **)(pFEAD->argAddr)); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray; } #endif } } else if (pFEAD->argIsLiteral) { _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(INT32)); // // If this is a byref literal arg, then it maybe an interior ptr. // void *v = NULL; memcpy(&v, pFEAD->argLiteralData, sizeof(v)); pMaybeInteriorPtrArray[currArgIndex] = v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray; } #endif } else { // RAK_REG is the only valid 4 byte type on WIN32. _ASSERTE(pFEAD->argHome.kind == RAK_REG); // Simply grab the value out of the proper register. SIZE_T v = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); // The argument is the address. pMaybeInteriorPtrArray[currArgIndex] = (void *)v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray; } #endif } #endif // _TARGET_X86_ default: // // Ignorable - no need to protect // break; } } } /* * ResolveFuncEvalGenericArgInfo * * This function pulls out any generic args and makes sure the method is loaded for it. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * * Returns: * None. * */ void ResolveFuncEvalGenericArgInfo(DebuggerEval *pDE) { CONTRACTL { THROWS; GC_TRIGGERS; } CONTRACTL_END; DebuggerIPCE_TypeArgData *firstdata = pDE->GetTypeArgData(); unsigned int nGenericArgs = pDE->m_genericArgsCount; SIZE_T cbAllocSize; if ((!ClrSafeInt::multiply(nGenericArgs, sizeof(TypeHandle *), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } TypeHandle * pGenericArgs = (nGenericArgs == 0) ? NULL : (TypeHandle *) _alloca(cbAllocSize); // // Snag the type arguments from the input and get the // method desc that corresponds to the instantiated desc. // Debugger::TypeDataWalk walk(firstdata, pDE->m_genericArgsNodeCount); walk.ReadTypeHandles(nGenericArgs, pGenericArgs); // better error message if (!walk.Finished()) { COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg")); } // Find the proper MethodDesc that we need to call. // Since we're already in the target domain, it can't be unloaded so it's safe to // use domain specific structures like the Module*. _ASSERTE( GetAppDomain() == pDE->m_debuggerModule->GetAppDomain() ); pDE->m_md = g_pEEInterface->LoadMethodDef(pDE->m_debuggerModule->GetRuntimeModule(), pDE->m_methodToken, nGenericArgs, pGenericArgs, &(pDE->m_ownerTypeHandle)); // We better have a MethodDesc at this point. _ASSERTE(pDE->m_md != NULL); ValidateFuncEvalReturnType(pDE->m_evalType , pDE->m_md->GetMethodTable()); // If this is a new object operation, then we should have a .ctor. if ((pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) && !pDE->m_md->IsCtor()) { COMPlusThrow(kArgumentException, W("Argument_MissingDefaultConstructor")); } pDE->m_md->EnsureActive(); // Run the Class Init for this class, if necessary. MethodTable * pOwningMT = pDE->m_ownerTypeHandle.GetMethodTable(); pOwningMT->EnsureInstanceActive(); pOwningMT->CheckRunClassInitThrowing(); if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) { // Work out the exact type of the allocated object pDE->m_resultType = (nGenericArgs == 0) ? TypeHandle(pDE->m_md->GetMethodTable()) : g_pEEInterface->LoadInstantiation(pDE->m_md->GetModule(), pDE->m_md->GetMethodTable()->GetCl(), nGenericArgs, pGenericArgs); } } /* * BoxFuncEvalThisParameter * * This function is a helper for DoNormalFuncEval. It boxes the 'this' parameter if necessary. * For example, when a method Object.ToString is called on a value class like System.DateTime * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * argData - Array of information about the arguments. * pMaybeInteriorPtrArray - An array that contains values that may be pointers to * the interior of a managed object. * pObjectRef - A GC protected place to put a boxed value, if necessary. * * Returns: * None * */ void BoxFuncEvalThisParameter(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *argData, void **pMaybeInteriorPtrArray, OBJECTREF *pObjectRefArg // out DEBUG_ARG(DataLocation pDataLocationArray[]) ) { WRAPPER_NO_CONTRACT; // // See if we have a value type that is going to be passed as a 'this' pointer. // if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && !pDE->m_md->IsStatic() && (pDE->m_argCount > 0)) { // Allocate the space for box nullables. Nullable parameters need a unboxed // nullable value to point at, where our current representation does not have // an unboxed value inside them. Thus we need another buffer to hold it (and // gcprotects it. We used boxed values for this by converting them to 'true' // nullable form, calling the function, and in the case of byrefs, converting // them back afterward. MethodTable* pMT = pDE->m_md->GetMethodTable(); if (Nullable::IsNullableType(pMT)) { OBJECTREF obj = AllocateObject(pMT); if (*pObjectRefArg != NULL) { BOOL typesMatch = Nullable::UnBox(obj->GetData(), *pObjectRefArg, pMT); (void)typesMatch; //prevent "unused variable" error from GCC _ASSERTE(typesMatch); } *pObjectRefArg = obj; } if (argData[0].argElementType == ELEMENT_TYPE_VALUETYPE) { // // See if we need to box up the 'this' parameter. // if (!pDE->m_md->GetMethodTable()->IsValueType()) { DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[0]; SIZE_T v; LPVOID pAddr = NULL; INT64 bigVal; { GCX_FORBID(); //pAddr is unprotected from the time we initialize it if (pFEAD->argAddr != NULL) { _ASSERTE(pDataLocationArray[0] & DL_MaybeInteriorPtrArray); pAddr = pMaybeInteriorPtrArray[0]; INDEBUG(pDataLocationArray[0] &= ~DL_MaybeInteriorPtrArray); } else { pAddr = GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v); if (pAddr == NULL) { COMPlusThrow(kArgumentNullException); } } _ASSERTE(pAddr != NULL); } //GCX_FORBID GCPROTECT_BEGININTERIOR(pAddr); //ReadTypeHandle may trigger a GC and move the object that has the value type at pAddr as a field // // Grab the class of this value type. If the type is a parameterized // struct type then it may not have yet been loaded by the EE (generics // code sharing may have meant we have never bothered to create the exact // type yet). // // A buffer should have been allocated for the full struct type _ASSERTE(argData[0].fullArgType != NULL); Debugger::TypeDataWalk walk((DebuggerIPCE_TypeArgData *) argData[0].fullArgType, argData[0].fullArgTypeNodeCount); TypeHandle typeHandle = walk.ReadTypeHandle(); if (typeHandle.IsNull()) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } // // Box up this value type // *pObjectRefArg = typeHandle.GetMethodTable()->Box(pAddr); if (Nullable::IsNullableType(typeHandle.GetMethodTable()) && (*pObjectRefArg == NULL)) { COMPlusThrow(kArgumentNullException); } GCPROTECT_END(); INDEBUG(pDataLocationArray[0] |= DL_ObjectRefArray); } } } } // // This is used to store (temporarily) information about the arguments that func-eval // will pass. It is used only for the args of the function, not the return buffer nor // the 'this' pointer, if there is any of either. // struct FuncEvalArgInfo { CorElementType argSigType; CorElementType byrefArgSigType; TypeHandle byrefArgTypeHandle; bool fNeedBoxOrUnbox; TypeHandle sigTypeHandle; }; /* * GatherFuncEvalArgInfo * * This function is a helper for DoNormalFuncEval. It gathers together all the information * necessary to process the arguments. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * mSig - The metadata signature of the fuction to call. * argData - Array of information about the arguments. * pFEArgInfo - An array of structs to hold the argument information. * * Returns: * None. * */ void GatherFuncEvalArgInfo(DebuggerEval *pDE, MetaSig mSig, DebuggerIPCE_FuncEvalArgData *argData, FuncEvalArgInfo *pFEArgInfo // out ) { WRAPPER_NO_CONTRACT; unsigned currArgIndex = 0; if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic()) { // // Skip over the 'this' arg, since this function is not supposed to mess with it. // currArgIndex++; } // // Gather all the information for the parameters. // for ( ; currArgIndex < pDE->m_argCount; currArgIndex++) { DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex]; // // Move to the next arg in the signature. // CorElementType argSigType = mSig.NextArgNormalized(); _ASSERTE(argSigType != ELEMENT_TYPE_END); // // If this arg is a byref arg, then we'll need to know what type we're referencing for later... // TypeHandle byrefTypeHandle = TypeHandle(); CorElementType byrefArgSigType = ELEMENT_TYPE_END; if (argSigType == ELEMENT_TYPE_BYREF) { byrefArgSigType = mSig.GetByRefType(&byrefTypeHandle); } // // If the sig says class but we've got a value class parameter, then remember that we need to box it. If // the sig says value class, but we've got a boxed value class, then remember that we need to unbox it. // bool fNeedBoxOrUnbox = ((argSigType == ELEMENT_TYPE_CLASS) && (pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE)) || (((argSigType == ELEMENT_TYPE_VALUETYPE) && ((pFEAD->argElementType == ELEMENT_TYPE_CLASS) || (pFEAD->argElementType == ELEMENT_TYPE_OBJECT))) || // This is when method signature is expecting a BYREF ValueType, yet we receive the boxed valuetype's handle. (pFEAD->argElementType == ELEMENT_TYPE_CLASS && argSigType == ELEMENT_TYPE_BYREF && byrefArgSigType == ELEMENT_TYPE_VALUETYPE)); pFEArgInfo[currArgIndex].argSigType = argSigType; pFEArgInfo[currArgIndex].byrefArgSigType = byrefArgSigType; pFEArgInfo[currArgIndex].byrefArgTypeHandle = byrefTypeHandle; pFEArgInfo[currArgIndex].fNeedBoxOrUnbox = fNeedBoxOrUnbox; pFEArgInfo[currArgIndex].sigTypeHandle = mSig.GetLastTypeHandleThrowing(); } } /* * BoxFuncEvalArguments * * This function is a helper for DoNormalFuncEval. It boxes all the arguments that * need to be. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * argData - Array of information about the arguments. * pFEArgInfo - An array of structs to hold the argument information. * pMaybeInteriorPtrArray - An array that contains values that may be pointers to * the interior of a managed object. * pObjectRef - A GC protected place to put a boxed value, if necessary. * * Returns: * None * */ void BoxFuncEvalArguments(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *argData, FuncEvalArgInfo *pFEArgInfo, void **pMaybeInteriorPtrArray, OBJECTREF *pObjectRef // out DEBUG_ARG(DataLocation pDataLocationArray[]) ) { WRAPPER_NO_CONTRACT; unsigned currArgIndex = 0; if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic()) { // // Skip over the 'this' arg, since this function is not supposed to mess with it. // currArgIndex++; } // // Gather all the information for the parameters. // for ( ; currArgIndex < pDE->m_argCount; currArgIndex++) { DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex]; // Allocate the space for box nullables. Nullable parameters need a unboxed // nullable value to point at, where our current representation does not have // an unboxed value inside them. Thus we need another buffer to hold it (and // gcprotects it. We used boxed values for this by converting them to 'true' // nullable form, calling the function, and in the case of byrefs, converting // them back afterward. TypeHandle th = pFEArgInfo[currArgIndex].sigTypeHandle; if (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF) th = pFEArgInfo[currArgIndex].byrefArgTypeHandle; if (!th.IsNull() && Nullable::IsNullableType(th)) { OBJECTREF obj = AllocateObject(th.AsMethodTable()); if (pObjectRef[currArgIndex] != NULL) { BOOL typesMatch = Nullable::UnBox(obj->GetData(), pObjectRef[currArgIndex], th.AsMethodTable()); (void)typesMatch; //prevent "unused variable" error from GCC _ASSERTE(typesMatch); } pObjectRef[currArgIndex] = obj; } // // Check if we should box this value now // if ((pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE) && (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF) && pFEArgInfo[currArgIndex].fNeedBoxOrUnbox) { SIZE_T v; INT64 bigVal; LPVOID pAddr = NULL; if (pFEAD->argAddr != NULL) { _ASSERTE(pDataLocationArray[currArgIndex] & DL_MaybeInteriorPtrArray); pAddr = pMaybeInteriorPtrArray[currArgIndex]; INDEBUG(pDataLocationArray[currArgIndex] &= ~DL_MaybeInteriorPtrArray); } else { pAddr = GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v); if (pAddr == NULL) { COMPlusThrow(kArgumentNullException); } } _ASSERTE(pAddr != NULL); MethodTable * pMT = pFEArgInfo[currArgIndex].sigTypeHandle.GetMethodTable(); // // Stuff the newly boxed item into our GC-protected array. // pObjectRef[currArgIndex] = pMT->Box(pAddr); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_ObjectRefArray; } #endif } } } /* * GatherFuncEvalMethodInfo * * This function is a helper for DoNormalFuncEval. It gathers together all the information * necessary to process the method * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * mSig - The metadata signature of the fuction to call. * argData - Array of information about the arguments. * ppUnboxedMD - Returns a resolve method desc if the original is an unboxing stub. * pObjectRefArray - GC protected array of objects passed to this func-eval call. * used to resolve down to the method target for generics. * pBufferForArgsArray - Array of values not needing gc-protection. May hold the * handle for the method targer for generics. * pfHasRetBuffArg - TRUE if the function has a return buffer. * pRetValueType - The TypeHandle of the return value. * * * Returns: * None. * */ void GatherFuncEvalMethodInfo(DebuggerEval *pDE, MetaSig mSig, DebuggerIPCE_FuncEvalArgData *argData, MethodDesc **ppUnboxedMD, OBJECTREF *pObjectRefArray, INT64 *pBufferForArgsArray, BOOL *pfHasRetBuffArg, // out BOOL *pfHasNonStdByValReturn, // out TypeHandle *pRetValueType // out, only if fHasRetBuffArg == true DEBUG_ARG(DataLocation pDataLocationArray[]) ) { WRAPPER_NO_CONTRACT; // // If 'this' is a non-static function that points to an unboxing stub, we need to return the // unboxed method desc to really call. // if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && !pDE->m_md->IsStatic() && pDE->m_md->IsUnboxingStub()) { *ppUnboxedMD = pDE->m_md->GetMethodTable()->GetUnboxedEntryPointMD(pDE->m_md); } // // Resolve down to the method on the class of the 'this' parameter. // if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && pDE->m_md->IsVtableMethod()) { // // Assuming that a constructor can't be an interface method... // _ASSERTE(pDE->m_evalType == DB_IPCE_FET_NORMAL); // // We need to go grab the 'this' argument to figure out what class we're headed for... // if (pDE->m_argCount == 0) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } // // We should have a valid this pointer. // @todo: But the check should cover the register kind as well! // if ((argData[0].argHome.kind == RAK_NONE) && (argData[0].argAddr == NULL)) { COMPlusThrow(kArgumentNullException); } // // Assume we can only have this for real objects or boxed value types, not value classes... // _ASSERTE((argData[0].argElementType == ELEMENT_TYPE_OBJECT) || (argData[0].argElementType == ELEMENT_TYPE_STRING) || (argData[0].argElementType == ELEMENT_TYPE_CLASS) || (argData[0].argElementType == ELEMENT_TYPE_ARRAY) || (argData[0].argElementType == ELEMENT_TYPE_SZARRAY) || ((argData[0].argElementType == ELEMENT_TYPE_VALUETYPE) && (pObjectRefArray[0] != NULL))); // // Now get the object pointer to our first arg. // OBJECTREF objRef = NULL; GCPROTECT_BEGIN(objRef); if (argData[0].argElementType == ELEMENT_TYPE_VALUETYPE) { // // In this case, we know where it is. // objRef = pObjectRefArray[0]; _ASSERTE(pDataLocationArray[0] & DL_ObjectRefArray); } else { TypeHandle dummyTH; ARG_SLOT objSlot; // // Take out the first arg. We're gonna trick GetFuncEvalArgValue by passing in just our // object ref as the stack. // // Note that we are passing ELEMENT_TYPE_END in the last parameter because we want to // supress the the valid object ref check. // GetFuncEvalArgValue(pDE, &(argData[0]), false, false, dummyTH, ELEMENT_TYPE_CLASS, dummyTH, &objSlot, NULL, pObjectRefArray, pBufferForArgsArray, NULL, ELEMENT_TYPE_END DEBUG_ARG(pDataLocationArray[0]) ); objRef = ArgSlotToObj(objSlot); } // // Validate the object // if (FAILED(ValidateObject(OBJECTREFToObject(objRef)))) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } // // Null isn't valid in this case! // if (objRef == NULL) { COMPlusThrow(kArgumentNullException); } // // Make sure that the object supplied is of a type that can call the method supplied. // if (!g_pEEInterface->ObjIsInstanceOf(OBJECTREFToObject(objRef), pDE->m_ownerTypeHandle)) { COMPlusThrow(kArgumentException, W("Argument_CORDBBadMethod")); } // // Now, find the proper MethodDesc for this interface method based on the object we're invoking the // method on. // pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(&objRef, pDE->m_ownerTypeHandle); GCPROTECT_END(); } else { pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(NULL, pDE->m_ownerTypeHandle); } // // Get the resulting type now. Doing this may trigger a GC or throw. // if (pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) { pDE->m_resultType = mSig.GetRetTypeHandleThrowing(); } // // Check if there is an explicit return argument, or if the return type is really a VALUETYPE but our // calling convention is passing it in registers. We just need to remember the pretValueClass so // that we will box it properly on our way out. // { ArgIterator argit(&mSig); *pfHasRetBuffArg = argit.HasRetBuffArg(); *pfHasNonStdByValReturn = argit.HasNonStandardByvalReturn(); } CorElementType retType = mSig.GetReturnType(); CorElementType retTypeNormalized = mSig.GetReturnTypeNormalized(); if (*pfHasRetBuffArg || *pfHasNonStdByValReturn || ((retType == ELEMENT_TYPE_VALUETYPE) && (retType != retTypeNormalized))) { *pRetValueType = mSig.GetRetTypeHandleThrowing(); } else { // // Make sure the caller initialized this value // _ASSERTE((*pRetValueType).IsNull()); } } /* * CopyArgsToBuffer * * This routine copies all the arguments to a local buffer, so that any one that needs to be * passed can be. Note that this local buffer is NOT GC-protected, and so all the values * in the buffer may not be relied on. You *must* use GetFuncEvalArgValue() to load up the * Arguments for the call, because it has the logic to decide which of the parallel arrays to pull * from. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * argData - Array of information about the arguments. * pFEArgInfo - An array of structs to hold the argument information. Must have be previously filled in. * pBufferArray - An array to store values. * * Returns: * None. * */ void CopyArgsToBuffer(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *argData, FuncEvalArgInfo *pFEArgInfo, INT64 *pBufferArray DEBUG_ARG(DataLocation pDataLocationArray[]) ) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; unsigned currArgIndex = 0; if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic()) { // // Skip over the 'this' arg, since this function is not supposed to mess with it. // currArgIndex++; } // // Spin thru each argument now // for ( ; currArgIndex < pDE->m_argCount; currArgIndex++) { DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex]; BOOL isByRef = (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF); BOOL fNeedBoxOrUnbox; fNeedBoxOrUnbox = pFEArgInfo[currArgIndex].fNeedBoxOrUnbox; LOG((LF_CORDB, LL_EVERYTHING, "CATB: currArgIndex=%d\n", currArgIndex)); LOG((LF_CORDB, LL_EVERYTHING, "\t: argSigType=0x%x, byrefArgSigType=0x%0x, inType=0x%0x\n", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, pFEAD->argElementType)); INT64 *pDest = &(pBufferArray[currArgIndex]); switch (pFEAD->argElementType) { case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R8: if (pFEAD->argAddr != NULL) { *pDest = *(INT64*)(pFEAD->argAddr); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } else if (pFEAD->argIsLiteral) { _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(void *)); // If this is a literal arg, then we just copy the data. memcpy(pDest, pFEAD->argLiteralData, sizeof(INT64)); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } else { #if !defined(_WIN64) // RAK_REG is the only 4 byte type, all others are 8 byte types. _ASSERTE(pFEAD->argHome.kind != RAK_REG); INT64 bigVal = 0; SIZE_T v; INT64 *pAddr; pAddr = (INT64*)GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v); if (pAddr == NULL) { COMPlusThrow(kArgumentNullException); } *pDest = *pAddr; #else // _WIN64 // Both RAK_REG and RAK_FLOAT can be either 4 bytes or 8 bytes. _ASSERTE((pFEAD->argHome.kind == RAK_REG) || (pFEAD->argHome.kind == RAK_FLOAT)); CorDebugRegister regNum = GetArgAddrFromReg(pFEAD); *pDest = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); #endif // _WIN64 #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } break; case ELEMENT_TYPE_VALUETYPE: // // For value types, we dont do anything here, instead delay until GetFuncEvalArgInfo // break; case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_OBJECT: case ELEMENT_TYPE_STRING: case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: if (pFEAD->argAddr != NULL) { if (!isByRef) { if (pFEAD->argIsHandleValue) { OBJECTHANDLE oh = (OBJECTHANDLE)(pFEAD->argAddr); *pDest = (INT64)(size_t)oh; } else { *pDest = *((SIZE_T*)(pFEAD->argAddr)); } #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } else { if (pFEAD->argIsHandleValue) { *pDest = (INT64)(size_t)(pFEAD->argAddr); } else { *pDest = *(SIZE_T*)(pFEAD->argAddr); } #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } } else if (pFEAD->argIsLiteral) { _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(INT64)); // The called function may expect a larger/smaller value than the literal value. // So we convert the value to the right type. CONSISTENCY_CHECK_MSGF(((pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_CLASS) || (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_SZARRAY) || (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_ARRAY)) || (isByRef && ((pFEArgInfo[currArgIndex].byrefArgSigType == ELEMENT_TYPE_CLASS) || (pFEArgInfo[currArgIndex].byrefArgSigType == ELEMENT_TYPE_SZARRAY) || (pFEArgInfo[currArgIndex].byrefArgSigType == ELEMENT_TYPE_ARRAY))), ("argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, isByRef)); LOG((LF_CORDB, LL_EVERYTHING, "argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d\n", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, isByRef)); *(SIZE_T*)pDest = *(SIZE_T*)pFEAD->argLiteralData; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } else { // RAK_REG is the only valid 4 byte type on WIN32. On WIN64, RAK_REG and RAK_FLOAT // can both be either 4 bytes or 8 bytes; _ASSERTE((pFEAD->argHome.kind == RAK_REG) WIN64_ONLY(|| (pFEAD->argHome.kind == RAK_FLOAT))); CorDebugRegister regNum = GetArgAddrFromReg(pFEAD); // Simply grab the value out of the proper register. SIZE_T v = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); *pDest = v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } break; default: // 4-byte, 2-byte, or 1-byte values if (pFEAD->argAddr != NULL) { if (!isByRef) { if (pFEAD->argIsHandleValue) { OBJECTHANDLE oh = (OBJECTHANDLE)(pFEAD->argAddr); *pDest = (INT64)(size_t)oh; } else { GetAndSetLiteralValue(pDest, pFEArgInfo[currArgIndex].argSigType, pFEAD->argAddr, pFEAD->argElementType); } #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } else { if (pFEAD->argIsHandleValue) { *pDest = (INT64)(size_t)(pFEAD->argAddr); } else { // We have to make sure we only grab the correct size of memory from the source. On IA64, we // have to make sure we don't cause misaligned data exceptions as well. Then we put the value // into the pBufferArray. The reason is that we may be passing in some values by ref to a // function that's expecting something of a bigger size. Thus, if we don't do this, then we'll // be bashing memory right next to the source value as the function being called acts upon some // bigger value. GetAndSetLiteralValue(pDest, pFEArgInfo[currArgIndex].byrefArgSigType, pFEAD->argAddr, pFEAD->argElementType); } #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } } else if (pFEAD->argIsLiteral) { _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(INT32)); // The called function may expect a larger/smaller value than the literal value, // so we convert the value to the right type. CONSISTENCY_CHECK_MSGF( ((pFEArgInfo[currArgIndex].argSigType>=ELEMENT_TYPE_BOOLEAN) && (pFEArgInfo[currArgIndex].argSigType<=ELEMENT_TYPE_R8)) || (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_PTR) || (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_I) || (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_U) || (isByRef && ((pFEArgInfo[currArgIndex].byrefArgSigType>=ELEMENT_TYPE_BOOLEAN) && (pFEArgInfo[currArgIndex].byrefArgSigType<=ELEMENT_TYPE_R8))), ("argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, isByRef)); LOG((LF_CORDB, LL_EVERYTHING, "argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d\n", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, isByRef)); CorElementType relevantType = (isByRef ? pFEArgInfo[currArgIndex].byrefArgSigType : pFEArgInfo[currArgIndex].argSigType); GetAndSetLiteralValue(pDest, relevantType, pFEAD->argLiteralData, pFEAD->argElementType); #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } else { // RAK_REG is the only valid 4 byte type on WIN32. On WIN64, RAK_REG and RAK_FLOAT // can both be either 4 bytes or 8 bytes; _ASSERTE((pFEAD->argHome.kind == RAK_REG) WIN64_ONLY(|| (pFEAD->argHome.kind == RAK_FLOAT))); CorDebugRegister regNum = GetArgAddrFromReg(pFEAD); // Simply grab the value out of the proper register. SIZE_T v = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value); *pDest = v; #ifdef _DEBUG if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED) { pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray; } #endif } } } } /* * PackArgumentArray * * This routine fills a given array with the correct values for passing to a managed function. * It uses various component arrays that contain information to correctly create the argument array. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * argData - Array of information about the arguments. * pUnboxedMD - MethodDesc of the function to call, after unboxing. * RetValueType - Type Handle of the return value of the managed function we will call. * pFEArgInfo - An array of structs to hold the argument information. Must have be previously filled in. * pObjectRefArray - An array that contains any object refs. It was built previously. * pMaybeInteriorPtrArray - An array that contains values that may be pointers to * the interior of a managed object. * pBufferForArgsArray - An array that contains values that need writable memory space * for passing ByRef. * newObj - Pre-allocated object for a 'new' call. * pArguments - This array is packed from the above arrays. * ppRetValue - Return value buffer if fRetValueArg is TRUE * * Returns: * None. * */ void PackArgumentArray(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *argData, FuncEvalArgInfo *pFEArgInfo, MethodDesc *pUnboxedMD, TypeHandle RetValueType, OBJECTREF *pObjectRefArray, void **pMaybeInteriorPtrArray, INT64 *pBufferForArgsArray, ValueClassInfo ** ppProtectedValueClasses, OBJECTREF newObj, BOOL fRetValueArg, ARG_SLOT *pArguments, PVOID * ppRetValue DEBUG_ARG(DataLocation pDataLocationArray[]) ) { WRAPPER_NO_CONTRACT; GCX_FORBID(); unsigned currArgIndex = 0; unsigned currArgSlot = 0; // // THIS POINTER (if any) // For non-static methods, or when returning a new object, // the first arg in the array is 'this' or the new object. // if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) { // // If this is a new object op, then we need to fill in the 0'th // arg slot with the 'this' ptr. // pArguments[0] = ObjToArgSlot(newObj); // // If we are invoking a function on a value class, but we have a boxed value class for 'this', // then go ahead and unbox it and leave a ref to the value class on the stack as 'this'. // if (pDE->m_md->GetMethodTable()->IsValueType()) { _ASSERTE(newObj->GetMethodTable()->IsValueType()); // This is one of those places we use true boxed nullables _ASSERTE(!Nullable::IsNullableType(pDE->m_md->GetMethodTable()) || newObj->GetMethodTable() == pDE->m_md->GetMethodTable()); void *pData = newObj->GetData(); pArguments[0] = PtrToArgSlot(pData); } // // Bump up the arg slot // currArgSlot++; } else if (!pDE->m_md->IsStatic()) { // // Place 'this' first in the array for non-static methods. // TypeHandle dummyTH; bool isByRef = false; bool fNeedBoxOrUnbox = false; // We had better have an object for a 'this' argument! CorElementType et = argData[0].argElementType; if (!(IsElementTypeSpecial(et) || et == ELEMENT_TYPE_VALUETYPE)) { COMPlusThrow(kArgumentOutOfRangeException, W("ArgumentOutOfRange_Enum")); } LOG((LF_CORDB, LL_EVERYTHING, "this: currArgSlot=%d, currArgIndex=%d et=0x%x\n", currArgSlot, currArgIndex, et)); if (pDE->m_md->GetMethodTable()->IsValueType()) { // For value classes, the 'this' parameter is always passed by reference. // However do not unbox if we are calling an unboxing stub. if (pDE->m_md == pUnboxedMD) { // pDE->m_md is expecting an unboxed this pointer. Then we will unbox it. isByRef = true; // Remember if we need to unbox this parameter, though. if ((et == ELEMENT_TYPE_CLASS) || (et == ELEMENT_TYPE_OBJECT)) { fNeedBoxOrUnbox = true; } } } else if (et == ELEMENT_TYPE_VALUETYPE) { // When the method that we invoking is defined on non value type and we receive the ValueType as input, // we are calling methods on System.Object. In this case, we need to box the input ValueType. fNeedBoxOrUnbox = true; } GetFuncEvalArgValue(pDE, &argData[currArgIndex], isByRef, fNeedBoxOrUnbox, dummyTH, ELEMENT_TYPE_CLASS, pDE->m_md->GetMethodTable(), &(pArguments[currArgSlot]), &(pMaybeInteriorPtrArray[currArgIndex]), &(pObjectRefArray[currArgIndex]), &(pBufferForArgsArray[currArgIndex]), NULL, ELEMENT_TYPE_OBJECT DEBUG_ARG((currArgIndex < MAX_DATA_LOCATIONS_TRACKED) ? pDataLocationArray[currArgIndex] : DL_All) ); LOG((LF_CORDB, LL_EVERYTHING, "this = 0x%08x\n", ArgSlotToPtr(pArguments[currArgSlot]))); // We need to check 'this' for a null ref ourselves... NOTE: only do this if we put an object reference on // the stack. If we put a byref for a value type, then we don't need to do this! if (!isByRef) { // The this pointer is not a unboxed value type. ARG_SLOT oi1 = pArguments[currArgSlot]; OBJECTREF o1 = ArgSlotToObj(oi1); if (FAILED(ValidateObject(OBJECTREFToObject(o1)))) { COMPlusThrow(kArgumentException, W("Argument_BadObjRef")); } if (OBJECTREFToObject(o1) == NULL) { COMPlusThrow(kNullReferenceException, W("NullReference_This")); } // For interface method, we have already done the check early on. if (!pDE->m_md->IsInterface()) { // We also need to make sure that the method that we are invoking is either defined on this object or the direct/indirect // base objects. Object *objPtr = OBJECTREFToObject(o1); MethodTable *pMT = objPtr->GetMethodTable(); // Do this check in the following cases as well... if (!pMT->IsArray() && !pDE->m_md->IsSharedByGenericInstantiations()) { TypeHandle thFrom = TypeHandle(pMT); TypeHandle thTarget = TypeHandle(pDE->m_md->GetMethodTable()); // What about MaybeCast? if (thFrom.CanCastToNoGC(thTarget) == TypeHandle::CannotCast) { COMPlusThrow(kArgumentException, W("Argument_CORDBBadMethod")); } } } } // // Increment up both arrays. // currArgSlot++; currArgIndex++; } // Special handling for functions that return value classes. if (fRetValueArg) { LOG((LF_CORDB, LL_EVERYTHING, "retBuff: currArgSlot=%d, currArgIndex=%d\n", currArgSlot, currArgIndex)); // // Allocate buffer for return value and GC protect it in case it contains object references // unsigned size = RetValueType.GetMethodTable()->GetNumInstanceFieldBytes(); #ifdef FEATURE_HFA // The buffer for HFAs has to be always ENREGISTERED_RETURNTYPE_MAXSIZE size = max(size, ENREGISTERED_RETURNTYPE_MAXSIZE); #endif BYTE * pTemp = new (interopsafe) BYTE[ALIGN_UP(sizeof(ValueClassInfo), 8) + size]; ValueClassInfo * pValueClassInfo = (ValueClassInfo *)pTemp; LPVOID pData = pTemp + ALIGN_UP(sizeof(ValueClassInfo), 8); memset(pData, 0, size); pValueClassInfo->pData = pData; pValueClassInfo->pMT = RetValueType.GetMethodTable(); pValueClassInfo->pNext = *ppProtectedValueClasses; *ppProtectedValueClasses = pValueClassInfo; pArguments[currArgSlot++] = PtrToArgSlot(pData); *ppRetValue = pData; } // REAL ARGUMENTS (if any) // Now do the remaining args for ( ; currArgIndex < pDE->m_argCount; currArgSlot++, currArgIndex++) { DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex]; LOG((LF_CORDB, LL_EVERYTHING, "currArgSlot=%d, currArgIndex=%d\n", currArgSlot, currArgIndex)); LOG((LF_CORDB, LL_EVERYTHING, "\t: argSigType=0x%x, byrefArgSigType=0x%0x, inType=0x%0x\n", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, pFEAD->argElementType)); GetFuncEvalArgValue(pDE, pFEAD, pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF, pFEArgInfo[currArgIndex].fNeedBoxOrUnbox, pFEArgInfo[currArgIndex].sigTypeHandle, pFEArgInfo[currArgIndex].byrefArgSigType, pFEArgInfo[currArgIndex].byrefArgTypeHandle, &(pArguments[currArgSlot]), &(pMaybeInteriorPtrArray[currArgIndex]), &(pObjectRefArray[currArgIndex]), &(pBufferForArgsArray[currArgIndex]), ppProtectedValueClasses, pFEArgInfo[currArgIndex].argSigType DEBUG_ARG((currArgIndex < MAX_DATA_LOCATIONS_TRACKED) ? pDataLocationArray[currArgIndex] : DL_All) ); } } /* * UnpackFuncEvalResult * * This routine takes the resulting object of a func-eval, and does any copying, boxing, unboxing, necessary. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * newObj - Pre-allocated object for NEW_OBJ func-evals. * retObject - Pre-allocated object to be filled in with the info in pRetBuff. * RetValueType - The return type of the function called. * pRetBuff - The raw bytes returned by the func-eval call when there is a return buffer parameter. * * * Returns: * None. * */ void UnpackFuncEvalResult(DebuggerEval *pDE, OBJECTREF newObj, OBJECTREF retObject, TypeHandle RetValueType, void *pRetBuff ) { CONTRACTL { WRAPPER(THROWS); GC_NOTRIGGER; } CONTRACTL_END; // Ah, but if this was a new object op, then the result is really // the object we allocated above... if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) { // We purposely do not morph nullables to be boxed Ts here because debugger EE's otherwise // have no way of creating true nullables that they need for their own purposes. pDE->m_result[0] = ObjToArgSlot(newObj); pDE->m_retValueBoxing = Debugger::AllBoxed; } else if (!RetValueType.IsNull()) { LOG((LF_CORDB, LL_EVERYTHING, "FuncEval call is saving a boxed VC return value.\n")); // // We pre-created it above // _ASSERTE(retObject != NULL); // This is one of those places we use true boxed nullables _ASSERTE(!Nullable::IsNullableType(RetValueType)|| retObject->GetMethodTable() == RetValueType.GetMethodTable()); if (pRetBuff != NULL) { // box the object CopyValueClass(retObject->GetData(), pRetBuff, RetValueType.GetMethodTable(), retObject->GetAppDomain()); } else { // box the primitive returned, retObject is a true nullable for nullabes, It will be Normalized later CopyValueClass(retObject->GetData(), pDE->m_result, RetValueType.GetMethodTable(), retObject->GetAppDomain()); } pDE->m_result[0] = ObjToArgSlot(retObject); pDE->m_retValueBoxing = Debugger::AllBoxed; } else { // // Other FuncEvals return primitives as unboxed. // pDE->m_retValueBoxing = Debugger::OnlyPrimitivesUnboxed; } LOG((LF_CORDB, LL_INFO10000, "FuncEval call has saved the return value.\n")); // No exception, so it worked as far as we're concerned. pDE->m_successful = true; // If the result is an object, then place the object // reference into a strong handle and place the handle into the // pDE to protect the result from a collection. CorElementType retClassET = pDE->m_resultType.GetSignatureCorElementType(); if ((pDE->m_retValueBoxing == Debugger::AllBoxed) || !RetValueType.IsNull() || IsElementTypeSpecial(retClassET)) { LOG((LF_CORDB, LL_EVERYTHING, "Creating strong handle for boxed DoNormalFuncEval result.\n")); OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(ArgSlotToObj(pDE->m_result[0])); pDE->m_result[0] = (INT64)(LONG_PTR)oh; pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh); } } /* * UnpackFuncEvalArguments * * This routine takes the resulting object of a func-eval, and does any copying, boxing, unboxing, necessary. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * newObj - Pre-allocated object for NEW_OBJ func-evals. * retObject - Pre-allocated object to be filled in with the info in pSource. * RetValueType - The return type of the function called. * pSource - The raw bytes returned by the func-eval call when there is a hidden parameter. * * * Returns: * None. * */ void UnpackFuncEvalArguments(DebuggerEval *pDE, DebuggerIPCE_FuncEvalArgData *argData, MetaSig mSig, BOOL staticMethod, OBJECTREF *pObjectRefArray, void **pMaybeInteriorPtrArray, void **pByRefMaybeInteriorPtrArray, INT64 *pBufferForArgsArray ) { WRAPPER_NO_CONTRACT; // Update any enregistered byrefs with their new values from the // proper byref temporary array. if (pDE->m_argCount > 0) { mSig.Reset(); unsigned currArgIndex = 0; if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic()) { // // Skip over the 'this' arg, since this function is not supposed to mess with it. // currArgIndex++; } for (; currArgIndex < pDE->m_argCount; currArgIndex++) { CorElementType argSigType = mSig.NextArgNormalized(); LOG((LF_CORDB, LL_EVERYTHING, "currArgIndex=%d argSigType=0x%x\n", currArgIndex, argSigType)); _ASSERTE(argSigType != ELEMENT_TYPE_END); if (argSigType == ELEMENT_TYPE_BYREF) { TypeHandle byrefClass = TypeHandle(); CorElementType byrefArgSigType = mSig.GetByRefType(&byrefClass); // If these are the true boxed nullables we created in BoxFuncEvalArguments, convert them back pObjectRefArray[currArgIndex] = Nullable::NormalizeBox(pObjectRefArray[currArgIndex]); LOG((LF_CORDB, LL_EVERYTHING, "DoNormalFuncEval: Updating enregistered byref...\n")); SetFuncEvalByRefArgValue(pDE, &argData[currArgIndex], byrefArgSigType, pBufferForArgsArray[currArgIndex], pMaybeInteriorPtrArray[currArgIndex], pByRefMaybeInteriorPtrArray[currArgIndex], pObjectRefArray[currArgIndex] ); } } } } /* * FuncEvalWrapper * * Helper function for func-eval. We have to split it out so that we can put a __try / __finally in to * notify on a Catch-Handler found. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * pArguments - created stack to pass for the call. * pCatcherStackAddr - stack address to report as the Catch Handler Found location. * * Returns: * None. * */ void FuncEvalWrapper(MethodDescCallSite* pMDCS, DebuggerEval *pDE, const ARG_SLOT *pArguments, BYTE *pCatcherStackAddr) { struct Param : NotifyOfCHFFilterWrapperParam { MethodDescCallSite* pMDCS; DebuggerEval *pDE; const ARG_SLOT *pArguments; }; Param param; param.pFrame = pCatcherStackAddr; // Inherited from NotifyOfCHFFilterWrapperParam param.pMDCS = pMDCS; param.pDE = pDE; param.pArguments = pArguments; PAL_TRY(Param *, pParam, ¶m) { pParam->pMDCS->CallWithValueTypes_RetArgSlot(pParam->pArguments, pParam->pDE->m_result, sizeof(pParam->pDE->m_result)); } PAL_EXCEPT_FILTER(NotifyOfCHFFilterWrapper) { // Should never reach here b/c handler should always continue search. _ASSERTE(false); } PAL_ENDTRY } /* * RecordFuncEvalException * * Helper function records the details of an exception that occurred during a FuncEval * Note that this should be called from within the target domain of the FuncEval. * * Parameters: * pDE - pointer to the DebuggerEval object being processed * ppException - the Exception object that was thrown * * Returns: * None. */ static void RecordFuncEvalException(DebuggerEval *pDE, OBJECTREF ppException ) { CONTRACTL { THROWS; // CreateStrongHandle could throw OOM GC_NOTRIGGER; MODE_COOPERATIVE; } CONTRACTL_END; // We got an exception. Make the exception into our result. pDE->m_successful = false; LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - Exception during funceval.\n")); // // Special handling for thread abort exceptions. We need to explicitly reset the // abort request on the EE thread, then make sure to place this thread on a thunk // that will re-raise the exception when we continue the process. Note: we still // pass this thread abort exception up as the result of the eval. // if (IsExceptionOfType(kThreadAbortException, &ppException)) { if (pDE->m_aborting != DebuggerEval::FE_ABORT_NONE) { // // Reset the abort request. // pDE->m_thread->UserResetAbort(Thread::TAR_FuncEval); // // This is the abort we sent down. // memset(pDE->m_result, 0, sizeof(pDE->m_result)); pDE->m_resultType = TypeHandle(); pDE->m_aborted = true; pDE->m_retValueBoxing = Debugger::NoValueTypeBoxing; LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - funceval abort exception.\n")); } else { // // This must have come from somewhere else, remember that we need to // rethrow this. // pDE->m_rethrowAbortException = true; // // The result is the exception object. // pDE->m_result[0] = ObjToArgSlot(ppException); pDE->m_resultType = ppException->GetTypeHandle(); OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(ArgSlotToObj(pDE->m_result[0])); pDE->m_result[0] = (ARG_SLOT)PTR_TO_CORDB_ADDRESS(oh); pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh); pDE->m_retValueBoxing = Debugger::NoValueTypeBoxing; LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - Non-FE abort thread abort..\n")); } } else { // // The result is the exception object. // pDE->m_result[0] = ObjToArgSlot(ppException); pDE->m_resultType = ppException->GetTypeHandle(); OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(ArgSlotToObj(pDE->m_result[0])); pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh; pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh); pDE->m_retValueBoxing = Debugger::NoValueTypeBoxing; LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - Exception for the user.\n")); } } /* * DoNormalFuncEval * * Does the main body of work (steps 1c onward) for the normal func-eval algorithm detailed at the * top of this file. The args have already been GC protected and we've transitioned into the appropriate * domain (steps 1a & 1b). This has to be a seperate function from GCProtectArgsAndDoNormalFuncEval * because otherwise we can't reliably find the right GCFrames to pop when unwinding the stack due to * an exception on 64-bit platforms (we have some GCFrames outside of the TRY, and some inside, * and they won't necesarily be layed out sequentially on the stack if they are all in the same function). * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * pCatcherStackAddr - stack address to report as the Catch Handler Found location. * pObjectRefArray - An array to hold object ref args. This array is protected from GC's. * pMaybeInteriorPtrArray - An array to hold values that may be pointers into a managed object. * This array is protected from GCs. * pByRefMaybeInteriorPtrArray - An array to hold values that may be pointers into a managed * object. This array is protected from GCs. This array protects the address of the arguments * while the pMaybeInteriorPtrArray protects the value of the arguments. We need to do this * because of by ref arguments. * pBufferForArgsArray - a buffer of temporary scratch space for things that do not need to be * protected, or are protected for free (e.g. Handles). * pDataLocationArray - an array of tracking data for debug sanity checks * * Returns: * None. */ static void DoNormalFuncEval( DebuggerEval *pDE, BYTE *pCatcherStackAddr, OBJECTREF *pObjectRefArray, void **pMaybeInteriorPtrArray, void **pByRefMaybeInteriorPtrArray, INT64 *pBufferForArgsArray, ValueClassInfo ** ppProtectedValueClasses DEBUG_ARG(DataLocation pDataLocationArray[]) ) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; } CONTRACTL_END; // // Now that all the args are protected, we can go back and deal with generic args and resolving // all their information. // ResolveFuncEvalGenericArgInfo(pDE); // // Grab the signature of the method we're working on and do some error checking. // Note that if this instantiated generic code, then this will // correctly give as an instantiated view of the signature that we can iterate without // worrying about generic items in the signature. // MetaSig mSig(pDE->m_md); BYTE callingconvention = mSig.GetCallingConvention(); if (!isCallConv(callingconvention, IMAGE_CEE_CS_CALLCONV_DEFAULT)) { // We don't support calling vararg! COMPlusThrow(kArgumentException, W("Argument_CORDBBadVarArgCallConv")); } // // We'll need to know if this is a static method or not. // BOOL staticMethod = pDE->m_md->IsStatic(); _ASSERTE((pDE->m_evalType == DB_IPCE_FET_NORMAL) || !staticMethod); // // Do Step 1c - Pre-allocate space for new objects. // OBJECTREF newObj = NULL; GCPROTECT_BEGIN(newObj); SIZE_T allocArgCnt = 0; if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) { ValidateFuncEvalReturnType(DB_IPCE_FET_NEW_OBJECT, pDE->m_resultType.GetMethodTable()); pDE->m_resultType.GetMethodTable()->EnsureInstanceActive(); newObj = AllocateObject(pDE->m_resultType.GetMethodTable()); // // Note: we account for an extra argument in the count passed // in. We use this to increase the space allocated for args, // and we use it to control the number of args copied into // those arrays below. Note: m_argCount already includes space // for this. // allocArgCnt = pDE->m_argCount + 1; } else { allocArgCnt = pDE->m_argCount; } // // Validate the argument count with mSig. // if (allocArgCnt != (mSig.NumFixedArgs() + (staticMethod ? 0 : 1))) { COMPlusThrow(kTargetParameterCountException, W("Arg_ParmCnt")); } // // Do Step 1d - Gather information about the method that will be called. // // An array to hold information about the parameters to be passed. This is // all the information we need to gather before entering the GCX_FORBID area. // DebuggerIPCE_FuncEvalArgData *argData = pDE->GetArgData(); MethodDesc *pUnboxedMD = pDE->m_md; BOOL fHasRetBuffArg; BOOL fHasNonStdByValReturn; TypeHandle RetValueType; BoxFuncEvalThisParameter(pDE, argData, pMaybeInteriorPtrArray, pObjectRefArray DEBUG_ARG(pDataLocationArray) ); GatherFuncEvalMethodInfo(pDE, mSig, argData, &pUnboxedMD, pObjectRefArray, pBufferForArgsArray, &fHasRetBuffArg, &fHasNonStdByValReturn, &RetValueType DEBUG_ARG(pDataLocationArray) ); // // Do Step 1e - Gather info from runtime about args (may trigger a GC). // SIZE_T cbAllocSize; if (!(ClrSafeInt::multiply(pDE->m_argCount, sizeof(FuncEvalArgInfo), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } FuncEvalArgInfo * pFEArgInfo = (FuncEvalArgInfo *)_alloca(cbAllocSize); memset(pFEArgInfo, 0, cbAllocSize); GatherFuncEvalArgInfo(pDE, mSig, argData, pFEArgInfo); // // Do Step 1f - Box or unbox arguments one at a time, placing newly boxed items into // pObjectRefArray immediately after creating them. // BoxFuncEvalArguments(pDE, argData, pFEArgInfo, pMaybeInteriorPtrArray, pObjectRefArray DEBUG_ARG(pDataLocationArray) ); #ifdef _DEBUG if (!RetValueType.IsNull()) { _ASSERTE(RetValueType.IsValueType()); } #endif // // Do Step 1g - Pre-allocate any return value object. // OBJECTREF retObject = NULL; GCPROTECT_BEGIN(retObject); if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && !RetValueType.IsNull()) { ValidateFuncEvalReturnType(pDE->m_evalType, RetValueType.GetMethodTable()); RetValueType.GetMethodTable()->EnsureInstanceActive(); retObject = AllocateObject(RetValueType.GetMethodTable()); } // // Do Step 1h - Copy into scratch buffer all enregistered arguments, and // ByRef literals. // CopyArgsToBuffer(pDE, argData, pFEArgInfo, pBufferForArgsArray DEBUG_ARG(pDataLocationArray) ); // // We presume that the function has a return buffer. This assumption gets squeezed out // when we pack the argument array. // allocArgCnt++; LOG((LF_CORDB, LL_EVERYTHING, "Func eval for %s::%s: allocArgCnt=%d\n", pDE->m_md->m_pszDebugClassName, pDE->m_md->m_pszDebugMethodName, allocArgCnt)); MethodDescCallSite funcToEval(pDE->m_md, pDE->m_targetCodeAddr); // // Do Step 1i - Create and pack argument array for managed function call. // // Allocate space for argument stack // if ((!ClrSafeInt::multiply(allocArgCnt, sizeof(ARG_SLOT), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } ARG_SLOT * pArguments = (ARG_SLOT *)_alloca(cbAllocSize); memset(pArguments, 0, cbAllocSize); LPVOID pRetBuff = NULL; PackArgumentArray(pDE, argData, pFEArgInfo, pUnboxedMD, RetValueType, pObjectRefArray, pMaybeInteriorPtrArray, pBufferForArgsArray, ppProtectedValueClasses, newObj, #ifdef FEATURE_HFA fHasRetBuffArg || fHasNonStdByValReturn, #else fHasRetBuffArg, #endif pArguments, &pRetBuff DEBUG_ARG(pDataLocationArray) ); // // // Do Step 2 - Make the call! // // FuncEvalWrapper(&funcToEval, pDE, pArguments, pCatcherStackAddr); { // We have now entered the zone where taking a GC is fatal until we get the // return value all fixed up. // GCX_FORBID(); // // // Do Step 3 - Unpack results and update ByRef arguments. // // // LOG((LF_CORDB, LL_EVERYTHING, "FuncEval call has returned\n")); // GC still can't happen until we get our return value out half way through the unpack function UnpackFuncEvalResult(pDE, newObj, retObject, RetValueType, pRetBuff ); } UnpackFuncEvalArguments(pDE, argData, mSig, staticMethod, pObjectRefArray, pMaybeInteriorPtrArray, pByRefMaybeInteriorPtrArray, pBufferForArgsArray ); GCPROTECT_END(); // retObject GCPROTECT_END(); // newObj } /* * GCProtectArgsAndDoNormalFuncEval * * This routine is the primary entrypoint for normal func-evals. It implements the algorithm * described at the top of this file, doing steps 1a and 1b itself, then calling DoNormalFuncEval * to do the rest. * * Parameters: * pDE - pointer to the DebuggerEval object being processed. * pCatcherStackAddr - stack address to report as the Catch Handler Found location. * * Returns: * None. * */ static void GCProtectArgsAndDoNormalFuncEval(DebuggerEval *pDE, BYTE *pCatcherStackAddr ) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; } CONTRACTL_END; INDEBUG(DataLocation pDataLocationArray[MAX_DATA_LOCATIONS_TRACKED]); // // An array to hold object ref args. This array is protected from GC's. // SIZE_T cbAllocSize; if ((!ClrSafeInt::multiply(pDE->m_argCount, sizeof(OBJECTREF), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } OBJECTREF * pObjectRefArray = (OBJECTREF*)_alloca(cbAllocSize); memset(pObjectRefArray, 0, cbAllocSize); GCPROTECT_ARRAY_BEGIN(*pObjectRefArray, pDE->m_argCount); // // An array to hold values that may be pointers into a managed object. This array // is protected from GCs. // if ((!ClrSafeInt::multiply(pDE->m_argCount, sizeof(void**), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } void ** pMaybeInteriorPtrArray = (void **)_alloca(cbAllocSize); memset(pMaybeInteriorPtrArray, 0, cbAllocSize); GCPROTECT_BEGININTERIOR_ARRAY(*pMaybeInteriorPtrArray, (UINT)(cbAllocSize/sizeof(OBJECTREF))); // // An array to hold values that may be pointers into a managed object. This array // is protected from GCs. This array protects the address of the arguments while the // pMaybeInteriorPtrArray protects the value of the arguments. We need to do this because // of by ref arguments. // if ((!ClrSafeInt::multiply(pDE->m_argCount, sizeof(void**), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } void ** pByRefMaybeInteriorPtrArray = (void **)_alloca(cbAllocSize); memset(pByRefMaybeInteriorPtrArray, 0, cbAllocSize); GCPROTECT_BEGININTERIOR_ARRAY(*pByRefMaybeInteriorPtrArray, (UINT)(cbAllocSize/sizeof(OBJECTREF))); // // A buffer of temporary scratch space for things that do not need to be protected, or // are protected for free (e.g. Handles). // if ((!ClrSafeInt::multiply(pDE->m_argCount, sizeof(INT64), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } INT64 *pBufferForArgsArray = (INT64*)_alloca(cbAllocSize); memset(pBufferForArgsArray, 0, cbAllocSize); FrameWithCookie protectValueClassFrame; // // Initialize our tracking array // INDEBUG(memset(pDataLocationArray, 0, sizeof(DataLocation) * (MAX_DATA_LOCATIONS_TRACKED))); { GCX_FORBID(); // // Do step 1a // GCProtectAllPassedArgs(pDE, pObjectRefArray, pMaybeInteriorPtrArray, pByRefMaybeInteriorPtrArray, pBufferForArgsArray DEBUG_ARG(pDataLocationArray) ); } // // Do step 1b: we can switch domains since everything is now protected. // Note that before this point, it's unsafe to rely on pDE->m_module since it may be // invalid due to an AD unload. // All normal func evals should have an AppDomain specified. // _ASSERTE( pDE->m_appDomainId.m_dwId != 0 ); ENTER_DOMAIN_ID( pDE->m_appDomainId ); // Wrap everything in a EX_TRY so we catch any exceptions that could be thrown. // Note that we don't let any thrown exceptions cross the AppDomain boundary because we don't // want them to get marshalled. EX_TRY { DoNormalFuncEval( pDE, pCatcherStackAddr, pObjectRefArray, pMaybeInteriorPtrArray, pByRefMaybeInteriorPtrArray, pBufferForArgsArray, protectValueClassFrame.GetValueClassInfoList() DEBUG_ARG(pDataLocationArray) ); } EX_CATCH { // We got an exception. Make the exception into our result. OBJECTREF ppException = GET_THROWABLE(); GCX_FORBID(); RecordFuncEvalException( pDE, ppException); } // Note: we need to catch all exceptioins here because they all get reported as the result of // the funceval. If a ThreadAbort occurred other than for a funcEval abort, we'll re-throw it manually. EX_END_CATCH(SwallowAllExceptions); // Restore context END_DOMAIN_TRANSITION; protectValueClassFrame.Pop(); CleanUpTemporaryVariables(protectValueClassFrame.GetValueClassInfoList()); GCPROTECT_END(); // pByRefMaybeInteriorPtrArray GCPROTECT_END(); // pMaybeInteriorPtrArray GCPROTECT_END(); // pObjectRefArray LOG((LF_CORDB, LL_EVERYTHING, "DoNormalFuncEval: returning...\n")); } void FuncEvalHijackRealWorker(DebuggerEval *pDE, Thread* pThread, FuncEvalFrame* pFEFrame) { BYTE * pCatcherStackAddr = (BYTE*) pFEFrame; // Handle normal func evals in DoNormalFuncEval if ((pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) || (pDE->m_evalType == DB_IPCE_FET_NORMAL)) { GCProtectArgsAndDoNormalFuncEval(pDE, pCatcherStackAddr); LOG((LF_CORDB, LL_EVERYTHING, "DoNormalFuncEval has returned.\n")); return; } // The method may be in a different AD than the thread. // The RS already verified that all of the arguments are in the same appdomain as the function // (because we can't verify it here). // Note that this is exception safe, so we are guarenteed to be in the correct AppDomain when // we leave this method. // Before this, we can't safely use the DebuggerModule* since the domain may have been unloaded. ENTER_DOMAIN_ID( pDE->m_appDomainId ); OBJECTREF newObj = NULL; GCPROTECT_BEGIN(newObj); // Wrap everything in a EX_TRY so we catch any exceptions that could be thrown. // Note that we don't let any thrown exceptions cross the AppDomain boundary because we don't // want them to get marshalled. EX_TRY { DebuggerIPCE_TypeArgData *firstdata = pDE->GetTypeArgData(); DWORD nGenericArgs = pDE->m_genericArgsCount; SIZE_T cbAllocSize; if ((!ClrSafeInt::multiply(nGenericArgs, sizeof(TypeHandle *), cbAllocSize)) || (cbAllocSize != (size_t)(cbAllocSize))) { ThrowHR(COR_E_OVERFLOW); } TypeHandle *pGenericArgs = (nGenericArgs == 0) ? NULL : (TypeHandle *) _alloca(cbAllocSize); // // Snag the type arguments from the input and get the // method desc that corresponds to the instantiated desc. // Debugger::TypeDataWalk walk(firstdata, pDE->m_genericArgsNodeCount); walk.ReadTypeHandles(nGenericArgs, pGenericArgs); // better error message if (!walk.Finished()) COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg")); switch (pDE->m_evalType) { case DB_IPCE_FET_NEW_OBJECT_NC: { // Find the class. TypeHandle thClass = g_pEEInterface->LoadClass(pDE->m_debuggerModule->GetRuntimeModule(), pDE->m_classToken); if (thClass.IsNull()) COMPlusThrow(kArgumentNullException, W("ArgumentNull_Type")); // Apply any type arguments TypeHandle th = (nGenericArgs == 0) ? thClass : g_pEEInterface->LoadInstantiation(pDE->m_debuggerModule->GetRuntimeModule(), pDE->m_classToken, nGenericArgs, pGenericArgs); if (th.IsNull() || th.ContainsGenericVariables()) COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg")); // Run the Class Init for this type, if necessary. MethodTable * pOwningMT = th.GetMethodTable(); pOwningMT->EnsureInstanceActive(); pOwningMT->CheckRunClassInitThrowing(); // Create a new instance of the class ValidateFuncEvalReturnType(DB_IPCE_FET_NEW_OBJECT_NC, th.GetMethodTable()); newObj = AllocateObject(th.GetMethodTable()); // No exception, so it worked. pDE->m_successful = true; // So is the result type. pDE->m_resultType = th; // // Box up all returned objects // pDE->m_retValueBoxing = Debugger::AllBoxed; // Make a strong handle for the result. OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(newObj); pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh; pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh); break; } case DB_IPCE_FET_NEW_STRING: { // Create the string. m_argData is not necessarily null terminated... // The numeration parameter represents the string length, not the buffer size, but // we have passed the buffer size across to copy our data properly, so must divide back out. // NewString will return NULL if pass null, but want an empty string in that case, so // just create an EmptyString explicitly. if ((pDE->m_argData == NULL) || (pDE->m_stringSize == 0)) { newObj = StringObject::GetEmptyString(); } else { newObj = StringObject::NewString(pDE->GetNewStringArgData(), (int)(pDE->m_stringSize/sizeof(WCHAR))); } // No exception, so it worked. pDE->m_successful = true; // Result type is, of course, a string. pDE->m_resultType = newObj->GetTypeHandle(); // Place the result in a strong handle to protect it from a collection. OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(newObj); pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh; pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh); break; } case DB_IPCE_FET_NEW_ARRAY: { // @todo: We're only gonna handle SD arrays for right now. if (pDE->m_arrayRank > 1) COMPlusThrow(kRankException, W("Rank_MultiDimNotSupported")); // Grab the elementType from the arg/data area. _ASSERTE(nGenericArgs == 1); TypeHandle th = pGenericArgs[0]; CorElementType et = th.GetSignatureCorElementType(); // Gotta be a primitive, class, or System.Object. if (((et < ELEMENT_TYPE_BOOLEAN) || (et > ELEMENT_TYPE_R8)) && !IsElementTypeSpecial(et)) { COMPlusThrow(kArgumentOutOfRangeException, W("ArgumentOutOfRange_Enum")); } // Grab the dims from the arg/data area. These come after the type arguments. SIZE_T *dims; dims = (SIZE_T*) (firstdata + pDE->m_genericArgsNodeCount); if (IsElementTypeSpecial(et)) { newObj = AllocateObjectArray((DWORD)dims[0], th); } else { // Create a simple array. Note: we can only do this type of create here due to the checks above. newObj = AllocatePrimitiveArray(et, (DWORD)dims[0]); } // No exception, so it worked. pDE->m_successful = true; // Result type is, of course, the type of the array. pDE->m_resultType = newObj->GetTypeHandle(); // Place the result in a strong handle to protect it from a collection. OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(newObj); pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh; pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh); break; } default: _ASSERTE(!"Invalid eval type!"); } } EX_CATCH { // We got an exception. Make the exception into our result. OBJECTREF ppException = GET_THROWABLE(); GCX_FORBID(); RecordFuncEvalException( pDE, ppException); } // Note: we need to catch all exceptioins here because they all get reported as the result of // the funceval. If a ThreadAbort occurred other than for a funcEval abort, we'll re-throw it manually. EX_END_CATCH(SwallowAllExceptions); GCPROTECT_END(); // // Restore context // END_DOMAIN_TRANSITION; } // // FuncEvalHijackWorker is the function that managed threads start executing in order to perform a function // evaluation. Control is transfered here on the proper thread by hijacking that that's IP to this method in // Debugger::FuncEvalSetup. This function can also be called directly by a Runtime thread that is stopped sending a // first or second chance exception to the Right Side. // // The DebuggerEval object may get deleted by the helper thread doing a CleanupFuncEval while this thread is blocked // sending the eval complete. void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE) { CONTRACTL { MODE_COOPERATIVE; GC_TRIGGERS; THROWS; PRECONDITION(CheckPointer(pDE)); } CONTRACTL_END; Thread *pThread = NULL; CONTEXT *filterContext = NULL; { GCX_FORBID(); LOG((LF_CORDB, LL_INFO100000, "D:FEHW for pDE:%08x evalType:%d\n", pDE, pDE->m_evalType)); pThread = GetThread(); #ifndef DACCESS_COMPILE #ifdef _DEBUG // // Flush all debug tracking information for this thread on object refs as it // only approximates proper tracking and may have stale data, resulting in false // positives. We dont want that as func-eval runs a lot, so flush them now. // g_pEEInterface->ObjectRefFlush(pThread); #endif #endif if (!pDE->m_evalDuringException) { // // From this point forward we use FORBID regions to guard against GCs. // Refer to code:Debugger::FuncEvalSetup to see the increment was done. // g_pDebugger->DecThreadsAtUnsafePlaces(); } // Preemptive GC is disabled at the start of this method. _ASSERTE(g_pEEInterface->IsPreemptiveGCDisabled()); DebuggerController::DispatchFuncEvalEnter(pThread); // If we've got a filter context still installed, then remove it while we do the work... filterContext = g_pEEInterface->GetThreadFilterContext(pDE->m_thread); if (filterContext) { _ASSERTE(pDE->m_evalDuringException); g_pEEInterface->SetThreadFilterContext(pDE->m_thread, NULL); } } // // Special handling for a re-abort eval. We don't setup a EX_TRY or try to lookup a function to call. All we do // is have this thread abort itself. // if (pDE->m_evalType == DB_IPCE_FET_RE_ABORT) { // // Push our FuncEvalFrame. The return address is equal to the IP in the saved context in the DebuggerEval. The // m_Datum becomes the ptr to the DebuggerEval. The frame address also serves as the address of the catch-handler-found. // FrameWithCookie FEFrame(pDE, GetIP(&pDE->m_context), false); FEFrame.Push(); pDE->m_thread->UserAbort(pDE->m_requester, EEPolicy::TA_Safe, INFINITE, Thread::UAC_Normal); _ASSERTE(!"Should not return from UserAbort here!"); return NULL; } // // We cannot scope the following in a GCX_FORBID(), but we would like to. But we need the frames on the // stack here, so they must never go out of scope. // // // Push our FuncEvalFrame. The return address is equal to the IP in the saved context in the DebuggerEval. The // m_Datum becomes the ptr to the DebuggerEval. The frame address also serves as the address of the catch-handler-found. // FrameWithCookie FEFrame(pDE, GetIP(&pDE->m_context), true); FEFrame.Push(); // On ARM the single step flag is per-thread and not per context. We need to make sure that the SS flag is cleared // for the funceval, and that the state is back to what it should be after the funceval completes. #ifdef _TARGET_ARM_ bool ssEnabled = pDE->m_thread->IsSingleStepEnabled(); if (ssEnabled) pDE->m_thread->DisableSingleStep(); #endif FuncEvalHijackRealWorker(pDE, pThread, &FEFrame); #ifdef _TARGET_ARM_ if (ssEnabled) pDE->m_thread->EnableSingleStep(); #endif LOG((LF_CORDB, LL_EVERYTHING, "FuncEval has finished its primary work.\n")); // // The func-eval is now completed, successfully or with failure, aborted or run-to-completion. // pDE->m_completed = true; if (pDE->m_thread->IsAbortRequested()) { // // Check if an unmanaged thread tried to also abort this thread while we // were doing the func-eval, then that kind we want to rethrow. The check // versus m_aborted is for the case where the FE was aborted, we caught that, // then cleared the FEAbort request, but there is still an outstanding abort // - then it must be a user abort. // if ((pDE->m_aborting == DebuggerEval::FE_ABORT_NONE) || pDE->m_aborted) { pDE->m_rethrowAbortException = true; } // // Reset the abort request if a func-eval abort was submitted, but the func-eval completed // before the abort could take place, we want to make sure we do not throw an abort exception // in this case. // if (pDE->m_aborting != DebuggerEval::FE_ABORT_NONE) { pDE->m_thread->UserResetAbort(Thread::TAR_FuncEval); } } // Codepitching can hijack our frame's return address. That means that we'll need to update PC in our saved context // so that when its restored, its like we've returned to the codepitching hijack. At this point, the old value of // EIP is worthless anyway. if (!pDE->m_evalDuringException) { SetIP(&pDE->m_context, (SIZE_T)FEFrame.GetReturnAddress()); } // // Disable all steppers and breakpoints created during the func-eval // DebuggerController::DispatchFuncEvalExit(pThread); void *dest = NULL; if (!pDE->m_evalDuringException) { // Signal to the helper thread that we're done with our func eval. Start by creating a DebuggerFuncEvalComplete // object. Give it an address at which to create the patch, which is a chunk of memory specified by our // DebuggerEval big enough to hold a breakpoint instruction. #ifdef _TARGET_ARM_ dest = (BYTE*)((DWORD)&(pDE->m_bpInfoSegment->m_breakpointInstruction) | THUMB_CODE); #else dest = &(pDE->m_bpInfoSegment->m_breakpointInstruction); #endif // // The created object below sets up itself as a hijack and will destroy itself when the hijack and work // is done. // DebuggerFuncEvalComplete *comp; comp = new (interopsafe) DebuggerFuncEvalComplete(pThread, dest); _ASSERTE(comp != NULL); // would have thrown // Pop the FuncEvalFrame now that we're pretty much done. Make sure we // don't pop the frame too early. Because GC can be triggered in our grabbing of // Debugger lock. If we pop the FE frame without setting back thread filter context, // the frames left uncrawlable. // FEFrame.Pop(); } else { // We don't have to setup any special hijacks to return from here when we've been processing during an // exception. We just go ahead and send the FuncEvalComplete event over now. Don't forget to enable/disable PGC // around the call... _ASSERTE(g_pEEInterface->IsPreemptiveGCDisabled()); if (filterContext != NULL) { g_pEEInterface->SetThreadFilterContext(pDE->m_thread, filterContext); } // Pop the FuncEvalFrame now that we're pretty much done. FEFrame.Pop(); { // // This also grabs the debugger lock, so we can atomically check if a detach has // happened. // SENDIPCEVENT_BEGIN(g_pDebugger, pDE->m_thread); if ((pDE->m_thread->GetDomain() != NULL) && pDE->m_thread->GetDomain()->IsDebuggerAttached()) { if (CORDebuggerAttached()) { g_pDebugger->FuncEvalComplete(pDE->m_thread, pDE); g_pDebugger->SyncAllThreads(SENDIPCEVENT_PtrDbgLockHolder); } } SENDIPCEVENT_END; } } // pDE may now point to deleted memory if the helper thread did a CleanupFuncEval while we // were blocked waiting for a continue after the func-eval complete. // We return the address that we want to resume executing at. return dest; } #if defined(WIN64EXCEPTIONS) && !defined(FEATURE_PAL) EXTERN_C EXCEPTION_DISPOSITION FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord WIN64_ARG(IN ULONG64 MemoryStackFp) NOT_WIN64_ARG(IN ULONG32 MemoryStackFp), IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) { DebuggerEval* pDE = NULL; #if defined(_TARGET_AMD64_) pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame); #elif defined(_TARGET_ARM_) // on ARM the establisher frame is the SP of the caller of FuncEvalHijack, on other platforms it's FuncEvalHijack's SP. // in FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from // the establisher frame we can get the stack location where R0 was stored. pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 8); #elif defined(_TARGET_ARM64_) // on ARM64 the establisher frame is the SP of the caller of FuncEvalHijack. // in FuncEvalHijack we allocate 32 bytes of stack space and then store R0 at the current SP + 16, so if we subtract 16 from // the establisher frame we can get the stack location where R0 was stored. pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 16); #else _ASSERTE(!"NYI - FuncEvalHijackPersonalityRoutine()"); #endif FixupDispatcherContext(pDispatcherContext, &(pDE->m_context), pContextRecord); // Returning ExceptionCollidedUnwind will cause the OS to take our new context record and // dispatcher context and restart the exception dispatching on this call frame, which is // exactly the behavior we want. return ExceptionCollidedUnwind; } #endif // WIN64EXCEPTIONS && !FEATURE_PAL #endif // ifndef DACCESS_COMPILE