// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // // // File: StackBuilderSink.cpp // // // Purpose: Native implementation for System.Runtime.Remoting.Messaging.StackBuilderSink // #include "common.h" #ifdef FEATURE_REMOTING #include "excep.h" #include "message.h" #include "stackbuildersink.h" #include "dbginterface.h" #include "remoting.h" #include "profilepriv.h" #include "class.h" struct ArgInfo { PBYTE dataLocation; INT32 dataSize; TypeHandle dataTypeHandle; BYTE dataType; BYTE byref; }; //+---------------------------------------------------------------------------- // // Method: CStackBuilderSink::PrivateProcessMessage, public // // Synopsis: Builds the stack and calls an object // // //+---------------------------------------------------------------------------- FCIMPL5(Object*, CStackBuilderSink::PrivateProcessMessage, Object* pSBSinkUNSAFE, MethodDesc* pMD, PTRArray* pArgsUNSAFE, Object* pServerUNSAFE, PTRARRAYREF* ppVarOutParams) { CONTRACTL { FCALL_CHECK; PRECONDITION(CheckPointer(pMD)); PRECONDITION(!pMD->IsGenericMethodDefinition()); PRECONDITION(pMD->IsRuntimeMethodHandle()); } CONTRACTL_END; struct _gc { PTRARRAYREF pArgs; OBJECTREF pServer; OBJECTREF pSBSink; OBJECTREF ret; } gc; gc.pArgs = (PTRARRAYREF) pArgsUNSAFE; gc.pServer = (OBJECTREF) pServerUNSAFE; gc.pSBSink = (OBJECTREF) pSBSinkUNSAFE; gc.ret = NULL; HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); // pMD->IsStatic() is SO_INTOLERANT. // Either pServer is non-null or the method is static (but not both) _ASSERTE( (pServerUNSAFE!=NULL) == !(pMD->IsStatic()) ); LOG((LF_REMOTING, LL_INFO10, "CStackBuilderSink::PrivateProcessMessage\n")); MethodDesc *pResolvedMD = pMD; // Check if this is an interface invoke, if yes, then we have to find the // real method descriptor on the class of the server object. if(pMD->GetMethodTable()->IsInterface()) { _ASSERTE(gc.pServer != NULL); // NOTE: This method can trigger GC // The last parameter (true) causes the method to take into account that // the object passed in is a TP and try to resolve the interface MD into // a server MD anyway (normally the call short circuits thunking objects // and just returns the interface MD unchanged). MethodTable *pRealMT = gc.pServer->GetTrueMethodTable(); #ifdef FEATURE_COMINTEROP if (pRealMT->IsComObjectType()) pResolvedMD = pRealMT->GetMethodDescForComInterfaceMethod(pMD, true); else #endif // FEATURE_COMINTEROP { if (pRealMT->ImplementsInterface(pMD->GetMethodTable())) { pResolvedMD = pRealMT->GetMethodDescForInterfaceMethod(TypeHandle(pMD->GetMethodTable()), pMD); // If the method is generic then we have more work to do -- // we'll get back the generic method descriptor and we'll have // to load the version with the right instantiation (get the // instantiation from the interface method). if (pResolvedMD->HasMethodInstantiation()) { _ASSERTE(pResolvedMD->IsGenericMethodDefinition()); _ASSERTE(pMD->GetNumGenericMethodArgs() == pResolvedMD->GetNumGenericMethodArgs()); pResolvedMD = MethodDesc::FindOrCreateAssociatedMethodDesc(pResolvedMD, pRealMT, FALSE, pMD->GetMethodInstantiation(), FALSE); _ASSERTE(!pResolvedMD->ContainsGenericVariables()); } } else pResolvedMD = NULL; } if(!pResolvedMD) { MAKE_WIDEPTR_FROMUTF8(wName, pMD->GetName()); COMPlusThrow(kMissingMethodException, IDS_EE_MISSING_METHOD, wName); } } // This looks a little dodgy for generics: pResolvedMD has been interface-resolved but not // virtual-resolved. So we seem to be taking the signature of a // half-resolved-virtual-call. But the MetaSig // is only used for GC purposes, and thus is probably OK: although the // metadata for the signature of a call may be different // as we move to base classes, the instantiated version // of the signature will still be the same // at both the callsite and the target). MetaSig mSig(pResolvedMD); // get the target depending on whether the method is virtual or non-virtual // like a constructor, private or final method PCODE pTarget = pResolvedMD->GetCallTarget(&(gc.pServer)); VASigCookie *pCookie = NULL; _ASSERTE(NULL != pTarget); // this function does the work ::CallDescrWithObjectArray( gc.pServer, pResolvedMD, //pRM, pTarget, &mSig, pCookie, gc.pArgs, &gc.ret, ppVarOutParams); LOG((LF_REMOTING, LL_INFO10, "CStackBuilderSink::PrivateProcessMessage OUT\n")); HELPER_METHOD_FRAME_END(); return OBJECTREFToObject(gc.ret); } FCIMPLEND class ProfilerServerCallbackHolder { public: ProfilerServerCallbackHolder(Thread* pThread) : m_pThread(pThread) { #ifdef PROFILING_SUPPORTED // If we're profiling, notify the profiler that we're about to invoke the remoting target { BEGIN_PIN_PROFILER(CORProfilerTrackRemoting()); GCX_PREEMP(); g_profControlBlock.pProfInterface->RemotingServerInvocationStarted(); END_PIN_PROFILER(); } #endif // PROFILING_SUPPORTED } ~ProfilerServerCallbackHolder() { #ifdef PROFILING_SUPPORTED // If profiling is active, tell profiler we've made the call, received the // return value, done any processing necessary, and now remoting is done. { BEGIN_PIN_PROFILER(CORProfilerTrackRemoting()); GCX_PREEMP(); g_profControlBlock.pProfInterface->RemotingServerInvocationReturned(); END_PIN_PROFILER(); } #endif // PROFILING_SUPPORTED } private: Thread* m_pThread; }; //+---------------------------------------------------------------------------- // // Function: CallDescrWithObjectArray, private // // Synopsis: Builds the stack from a object array and call the object // // // Note this function triggers GC and assumes that pServer, pArguments, pVarRet, and ppVarOutParams are // all already protected!! //+---------------------------------------------------------------------------- void CallDescrWithObjectArray(OBJECTREF& pServer, //ReflectMethod *pRM, MethodDesc *pMeth, PCODE pTarget, MetaSig* sig, VASigCookie *pCookie, PTRARRAYREF& pArgArray, OBJECTREF *pVarRet, PTRARRAYREF *ppVarOutParams) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; LOG((LF_REMOTING, LL_INFO10, "CallDescrWithObjectArray IN\n")); #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6263) // "Suppress PREFast warning about _alloca in a loop" // _alloca is called within a loop in a number of places within this method // (as an ultra fast means of acquiring temporary storage). This can be a // problem in some scenarios (swiftly drive us to stack overflow). But in // this case the allocations are tightly bounded (by the number of arguments // in the target method) and the allocations themselves are small (no worse // really than calling the method an extra time). #endif ByRefInfo *pByRefs = NULL; FrameWithCookie *pProtectValueClassFrame = NULL; FrameWithCookie *pProtectionFrame = NULL; UINT nStackBytes = 0; LPBYTE pAlloc = 0; LPBYTE pTransitionBlock = 0; UINT32 numByRef = 0; //DWORD attr = pRM->dwFlags; #ifdef _DEBUG //MethodDesc *pMD = pRM->pMethod; #endif ValueClassInfo *pValueClasses = NULL; // check the calling convention BYTE callingconvention = sig->GetCallingConvention(); if (!isCallConv(callingconvention, IMAGE_CEE_CS_CALLCONV_DEFAULT)) { _ASSERTE(!"This calling convention is not supported."); COMPlusThrow(kInvalidProgramException); } // Make sure we are properly loaded CONSISTENCY_CHECK(GetAppDomain()->CheckCanExecuteManagedCode(pMeth)); // Note this is redundant with the above but we do it anyway for safety pMeth->EnsureActive(); #ifdef DEBUGGING_SUPPORTED // debugger goo What does this do? can someone put a comment here? if (CORDebuggerTraceCall()) { g_pDebugInterface->TraceCall((const BYTE *)pTarget); } #endif // DEBUGGING_SUPPORTED Thread * pThread = GetThread(); { ProfilerServerCallbackHolder profilerHolder(pThread); ArgIterator argit(sig); // Create a fake FramedMethodFrame on the stack. nStackBytes = argit.SizeOfFrameArgumentArray(); UINT32 cbAlloc = 0; if (!ClrSafeInt::addition(TransitionBlock::GetNegSpaceSize(), sizeof(TransitionBlock), cbAlloc)) COMPlusThrow(kArgumentException); if (!ClrSafeInt::addition(cbAlloc, nStackBytes, cbAlloc)) COMPlusThrow(kArgumentException); pAlloc = (LPBYTE)_alloca(cbAlloc); pTransitionBlock = pAlloc + TransitionBlock::GetNegSpaceSize(); // cycle through the parameters and see if there are byrefs BOOL fHasByRefs = FALSE; //if (attr & RM_ATTR_BYREF_FLAG_SET) // fHasByRefs = attr & RM_ATTR_HAS_BYREF_ARG; //else { sig->Reset(); CorElementType typ; while ((typ = sig->NextArg()) != ELEMENT_TYPE_END) { if (typ == ELEMENT_TYPE_BYREF) { fHasByRefs = TRUE; //attr |= RM_ATTR_HAS_BYREF_ARG; break; } } //attr |= RM_ATTR_BYREF_FLAG_SET; //pRM->dwFlags = attr; sig->Reset(); } UINT32 nFixedArgs = sig->NumFixedArgs(); // To avoid any security problems with integer overflow/underflow we're // going to validate the number of args here (we're about to _alloca an // array of ArgInfo structures and maybe a managed object array as well // based on this count). _ASSERTE(sizeof(Object*) <= sizeof(ArgInfo)); UINT32 nMaxArgs = UINT32_MAX / sizeof(ArgInfo); if (nFixedArgs > nMaxArgs) COMPlusThrow(kArgumentException); // if there are byrefs allocate and array for the out parameters if (fHasByRefs) { *ppVarOutParams = PTRARRAYREF(AllocateObjectArray(sig->NumFixedArgs(), g_pObjectClass)); // Null out the array memset(&(*ppVarOutParams)->m_Array, 0, sizeof(OBJECTREF) * sig->NumFixedArgs()); } OBJECTREF *ppThis = NULL; if (sig->HasThis()) { ppThis = (OBJECTREF*)(pTransitionBlock + argit.GetThisOffset()); // *ppThis is not GC protected. It will be set to the right value // after all object allocations are made. *ppThis = NULL; } // if we have the Value Class return, we need to allocate that class and place a pointer to it on the stack. *pVarRet = NULL; TypeHandle retType = sig->GetRetTypeHandleThrowing(); // Note that we want the unnormalized (signature) type because GetStackObject // boxes as the element type, which if we normalized it would loose information. CorElementType retElemType = sig->GetReturnType(); // The MethodTable pointer of the return type, if that's a struct MethodTable* pStructRetTypeMT = NULL; // Allocate a boxed struct instance to hold the return value in any case. if (retElemType == ELEMENT_TYPE_VALUETYPE) { pStructRetTypeMT = retType.GetMethodTable(); *pVarRet = pStructRetTypeMT->Allocate(); } else { _ASSERTE(!argit.HasRetBuffArg()); } #ifdef CALLDESCR_REGTYPEMAP UINT64 dwRegTypeMap = 0; BYTE* pMap = (BYTE*)&dwRegTypeMap; #endif // CALLDESCR_REGTYPEMAP #ifdef CALLDESCR_FPARGREGS FloatArgumentRegisters *pFloatArgumentRegisters = NULL; #endif // CALLDESCR_FPARGREGS // gather data about the parameters by iterating over the sig: UINT32 arg = 0; int ofs = 0; // REVIEW: need to use actual arg count if VarArgs are supported ArgInfo* pArgInfoStart = (ArgInfo*) _alloca(nFixedArgs*sizeof(ArgInfo)); #ifdef _DEBUG // We expect to write useful data over every part of this so need // not do this in retail! memset((void *)pArgInfoStart, 0, sizeof(ArgInfo)*nFixedArgs); #endif for (; TransitionBlock::InvalidOffset != (ofs = argit.GetNextOffset()); arg++) { CONSISTENCY_CHECK(arg < nFixedArgs); ArgInfo* pArgInfo = pArgInfoStart + arg; #ifdef CALLDESCR_REGTYPEMAP FillInRegTypeMap(ofs, argit.GetArgType(), pMap); #endif #ifdef CALLDESCR_FPARGREGS // Under CALLDESCR_FPARGREGS we can have arguments in floating point registers. If we have at // least one such argument we point the call worker at the floating point area of the frame (we leave // it null otherwise since the worker can perform a useful optimization if it knows no floating point // registers need to be set up). if (TransitionBlock::HasFloatRegister(ofs, argit.GetArgLocDescForStructInRegs()) && (pFloatArgumentRegisters == NULL)) { pFloatArgumentRegisters = (FloatArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfFloatArgumentRegisters()); } #endif if (argit.GetArgType() == ELEMENT_TYPE_BYREF) { TypeHandle ty = TypeHandle(); CorElementType brType = sig->GetByRefType(&ty); if (CorIsPrimitiveType(brType)) { pArgInfo->dataSize = gElementTypeInfo[brType].m_cbSize; } else if (ty.IsValueType()) { pArgInfo->dataSize = ty.GetMethodTable()->GetNumInstanceFieldBytes(); numByRef ++; } else { pArgInfo->dataSize = sizeof(Object *); numByRef ++; } ByRefInfo *brInfo = (ByRefInfo *) _alloca(offsetof(ByRefInfo,data) + pArgInfo->dataSize); brInfo->argIndex = arg; brInfo->typ = brType; brInfo->typeHandle = ty; brInfo->pNext = pByRefs; pByRefs = brInfo; pArgInfo->dataLocation = (BYTE*)brInfo->data; *((void**)(pTransitionBlock + ofs)) = (void*)pArgInfo->dataLocation; pArgInfo->dataTypeHandle = ty; pArgInfo->dataType = static_cast(brType); pArgInfo->byref = TRUE; } else { pArgInfo->dataLocation = pTransitionBlock + ofs; pArgInfo->dataSize = argit.GetArgSize(); pArgInfo->dataTypeHandle = sig->GetLastTypeHandleThrowing(); // this may cause GC! pArgInfo->dataType = (BYTE)argit.GetArgType(); pArgInfo->byref = FALSE; } } if (ppThis) { // If this isn't a value class, verify the objectref #ifdef _DEBUG //if (pMD->GetMethodTable()->IsValueType() == FALSE) //{ // VALIDATEOBJECTREF(pServer); //} #endif //_DEBUG *ppThis = pServer; } PVOID pRetBufStackData = NULL; if (argit.HasRetBuffArg()) { // If the return buffer *must* be a stack-allocated object, allocate it. PVOID pRetBufData = NULL; if (pStructRetTypeMT->IsStructRequiringStackAllocRetBuf()) { SIZE_T sz = pStructRetTypeMT->GetNumInstanceFieldBytes(); pRetBufData = pRetBufStackData = _alloca(sz); memset(pRetBufData, 0, sz); pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pRetBufStackData, pStructRetTypeMT, pValueClasses); } else { pRetBufData = (*pVarRet)->GetData(); } *((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pRetBufData; } // There should be no GC when we fill up the stack with parameters, as we don't protect them // Assignment of "*ppThis" above triggers the point where we become unprotected. { GCX_FORBID(); PBYTE dataLocation; INT32 dataSize; TypeHandle dataTypeHandle; BYTE dataType; OBJECTREF* pArguments = pArgArray->m_Array; UINT32 i; for (i=0; idataSize; dataLocation = pArgInfo->dataLocation; dataTypeHandle = pArgInfo->dataTypeHandle; dataType = pArgInfo->dataType; // Nullable needs special treatment, even if it is 1, 2, 4, or 8 bytes if (dataType == ELEMENT_TYPE_VALUETYPE) goto DEFAULT_CASE; switch (dataSize) { case 1: // This "if" statement is necessary to make the assignement big-endian aware if (pArgInfo->byref) *((INT8*)dataLocation) = *((INT8*)pArguments[i]->GetData()); else *(StackElemType*)dataLocation = (StackElemType)*((INT8*)pArguments[i]->GetData()); break; case 2: // This "if" statement is necessary to make the assignement big-endian aware if (pArgInfo->byref) *((INT16*)dataLocation) = *((INT16*)pArguments[i]->GetData()); else *(StackElemType*)dataLocation = (StackElemType)*((INT16*)pArguments[i]->GetData()); break; case 4: #ifndef _WIN64 if ((dataType == ELEMENT_TYPE_STRING) || (dataType == ELEMENT_TYPE_OBJECT) || (dataType == ELEMENT_TYPE_CLASS) || (dataType == ELEMENT_TYPE_SZARRAY) || (dataType == ELEMENT_TYPE_ARRAY)) { *(OBJECTREF *)dataLocation = pArguments[i]; } else { *(StackElemType*)dataLocation = (StackElemType)*((INT32*)pArguments[i]->GetData()); } #else // !_WIN64 // This "if" statement is necessary to make the assignement big-endian aware if (pArgInfo->byref) *(INT32*)dataLocation = *((INT32*)pArguments[i]->GetData()); else *(StackElemType*)dataLocation = (StackElemType)*((INT32*)pArguments[i]->GetData()); #endif // !_WIN64 break; case 8: #ifdef _WIN64 if ((dataType == ELEMENT_TYPE_STRING) || (dataType == ELEMENT_TYPE_OBJECT) || (dataType == ELEMENT_TYPE_CLASS) || (dataType == ELEMENT_TYPE_SZARRAY) || (dataType == ELEMENT_TYPE_ARRAY)) { *(OBJECTREF *)dataLocation = pArguments[i]; } else { *((INT64*)dataLocation) = *((INT64*)pArguments[i]->GetData()); } #else // _WIN64 *((INT64*)dataLocation) = *((INT64*)pArguments[i]->GetData()); #endif // _WIN64 break; default: { DEFAULT_CASE: MethodTable * pMT = dataTypeHandle.GetMethodTable(); #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE // We do not need to allocate a buffer if the argument is already passed by reference. if (!pArgInfo->byref && ArgIterator::IsArgPassedByRef(dataTypeHandle)) { PVOID pvArg = _alloca(dataSize); pMT->UnBoxIntoUnchecked(pvArg, pArguments[i]); *(PVOID*)dataLocation = pvArg; pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pvArg, pMT, pValueClasses); } else #endif { pMT->UnBoxIntoUnchecked(dataLocation, pArguments[i]); } } } } #ifdef _DEBUG // Should not be using this any more memset((void *)pArgInfoStart, 0, sizeof(ArgInfo)*nFixedArgs); #endif // if there were byrefs, push a protection frame if (pByRefs && numByRef > 0) { char *pBuffer = (char*)_alloca (sizeof (FrameWithCookie)); pProtectionFrame = new (pBuffer) FrameWithCookie(pThread, pByRefs); } // If there were any value classes that must be protected by the // caller, push a ProtectValueClassFrame. if (pValueClasses) { char *pBuffer = (char*)_alloca (sizeof (FrameWithCookie)); pProtectValueClassFrame = new (pBuffer) FrameWithCookie(pThread, pValueClasses); } } // GCX_FORBID UINT fpReturnSize = argit.GetFPReturnSize(); CallDescrData callDescrData; callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); callDescrData.numStackSlots = nStackBytes / STACK_ELEM_SIZE; #ifdef CALLDESCR_ARGREGS callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); #endif #ifdef CALLDESCR_FPARGREGS callDescrData.pFloatArgumentRegisters = pFloatArgumentRegisters; #endif #ifdef CALLDESCR_REGTYPEMAP callDescrData.dwRegTypeMap = dwRegTypeMap; #endif callDescrData.fpReturnSize = fpReturnSize; callDescrData.pTarget = pTarget; CallDescrWorkerWithHandler(&callDescrData); // It is still illegal to do a GC here. The return type might have/contain GC pointers. if (retElemType == ELEMENT_TYPE_VALUETYPE) { _ASSERTE(*pVarRet != NULL); // we have already allocated a return object PVOID pVarRetData = (*pVarRet)->GetData(); // If the return result was passed back in registers, then copy it into the return object if (!argit.HasRetBuffArg()) { CopyValueClass(pVarRetData, &callDescrData.returnValue, (*pVarRet)->GetMethodTable(), (*pVarRet)->GetAppDomain()); } else if (pRetBufStackData != NULL) { // Copy the stack-allocated ret buff to the heap-allocated one. CopyValueClass(pVarRetData, pRetBufStackData, (*pVarRet)->GetMethodTable(), (*pVarRet)->GetAppDomain()); } // If the return is a Nullable, box it using Nullable conventions. // TODO: this double allocates on constructions which is wasteful if (!retType.IsNull()) *pVarRet = Nullable::NormalizeBox(*pVarRet); } else CMessage::GetObjectFromStack(pVarRet, &callDescrData.returnValue, retElemType, retType); // You can now do GCs if you want to if (pProtectValueClassFrame) pProtectValueClassFrame->Pop(pThread); // extract the out args from the byrefs if (pByRefs) { do { // Always extract the data ptr every time we enter this loop because // calls to GetObjectFromStack below can cause a GC. // Even this is not enough, because that we are passing a pointer to GC heap // to GetObjectFromStack . If GC happens, nobody is protecting the passed in pointer. OBJECTREF pTmp = NULL; void* dataLocation = pByRefs->data; CMessage::GetObjectFromStack(&pTmp, &dataLocation, pByRefs->typ, pByRefs->typeHandle, TRUE); (*ppVarOutParams)->SetAt(pByRefs->argIndex, pTmp); pByRefs = pByRefs->pNext; } while (pByRefs); if (pProtectionFrame) pProtectionFrame->Pop(pThread); } } // ProfilerServerCallbackHolder #ifdef _PREFAST_ #pragma warning(pop) #endif LOG((LF_REMOTING, LL_INFO10, "CallDescrWithObjectArray OUT\n")); } #endif // FEATURE_REMOTING