// // Callback for walking a thread's stack. Sends required frame data to the // DI as send buffers fill up. // StackWalkAction DebuggerThread::TraceAndSendStackCallback(FrameInfo *pInfo, VOID* data) { _RefreshStackFramesData *rsfd = (_RefreshStackFramesData*) data; Thread *t = rsfd->thread; DebuggerIPCEvent *pEvent = NULL; if (rsfd->iWhich == IPC_TARGET_INPROC) { pEvent = rsfd->pEvent; } else { _ASSERTE( rsfd->iWhich == IPC_TARGET_OUTOFPROC ); pEvent = rsfd->rcThread->GetIPCEventSendBuffer(rsfd->iWhich); } // Record registers for the start of the next chain, if appropriate. if (rsfd->needChainRegisters) { rsfd->chainRegisters = pInfo->registers; rsfd->needChainRegisters = false; } // Only report frames which are chain boundaries, or are not marked internal. LOG((LF_CORDB, LL_INFO1000, "DT::TASSC:chainReason:0x%x internal:0x%x " "md:0x%x **************************\n", pInfo->chainReason, pInfo->internal, pInfo->md)); if (pInfo->chainReason == 0 && (pInfo->internal || pInfo->md == NULL)) return SWA_CONTINUE; #ifdef LOGGING if( pInfo->quickUnwind == true ) LOG((LF_CORDB, LL_INFO10000, "DT::TASSC: rsfd => Doing quick unwind\n")); #endif // // If we've filled this event, send it off to the Right Side // before continuing the walk. // if ((rsfd->eventSize + sizeof(DebuggerIPCE_STRData)) >= rsfd->eventMaxSize) { // // pEvent->StackTraceResultData.threadUserState = g_pEEInterface->GetUserState(t); if (rsfd->iWhich == IPC_TARGET_OUTOFPROC) { rsfd->rcThread->SendIPCEvent(rsfd->iWhich); } else { DebuggerIPCEvent *peT; peT = rsfd->rcThread->GetIPCEventSendBufferContinuation(pEvent); if (peT == NULL) { pEvent->hr = E_OUTOFMEMORY; return SWA_ABORT; } CopyEventInfo(pEvent, peT); pEvent = peT; rsfd->pEvent = peT; rsfd->eventSize = 0; } // // Reset for the next set of frames. // pEvent->StackTraceResultData.traceCount = 0; rsfd->currentSTRData = &(pEvent->StackTraceResultData.traceData); rsfd->eventSize = (UINT_PTR)(rsfd->currentSTRData) - (UINT_PTR)(pEvent); } MethodDesc* fd = pInfo->md; if (fd != NULL && !pInfo->internal) { // // Send a frame // rsfd->currentSTRData->isChain = false; rsfd->currentSTRData->fp = pInfo->fp; rsfd->currentSTRData->quicklyUnwound = pInfo->quickUnwind; // Pass the appdomain that this thread was in when it was executing this frame to the Right Side. rsfd->currentSTRData->currentAppDomainToken = (void*)pInfo->currentAppDomain; REGDISPLAY* rd = &pInfo->registers; DebuggerREGDISPLAY* drd = &(rsfd->currentSTRData->rd); LPVOID FPAddress = GetRegdisplayFPAddress(rd); // Set some common elements drd->SP = rd->SP; drd->PC = (SIZE_T)*(rd->pPC); drd->FP = (FPAddress == NULL ? 0 : *((SIZE_T *)FPAddress)); // // PUSHED_REG_ADDR gives us NULL if the register still lives in the thread's context, or it gives us the address // of where the register was pushed for this frame. // #define PUSHED_REG_ADDR(_a) (((UINT_PTR)(_a) >= (UINT_PTR)rd->pContext) && ((UINT_PTR)(_a) <= ((UINT_PTR)rd->pContext + sizeof(CONTEXT)))) ? NULL : (_a) // Frame pointer drd->pFP = PUSHED_REG_ADDR(FPAddress); #ifdef _X86_ drd->pEdi = PUSHED_REG_ADDR(rd->pEdi); drd->Edi = (rd->pEdi == NULL ? 0 : *(rd->pEdi)); drd->pEsi = PUSHED_REG_ADDR(rd->pEsi); drd->Esi = (rd->pEsi == NULL ? 0 : *(rd->pEsi)); drd->pEbx = PUSHED_REG_ADDR(rd->pEbx); drd->Ebx = (rd->pEbx == NULL ? 0 : *(rd->pEbx)); drd->pEdx = PUSHED_REG_ADDR(rd->pEdx); drd->Edx = (rd->pEdx == NULL ? 0 : *(rd->pEdx)); drd->pEcx = PUSHED_REG_ADDR(rd->pEcx); drd->Ecx = (rd->pEcx == NULL ? 0 : *(rd->pEcx)); drd->pEax = PUSHED_REG_ADDR(rd->pEax); drd->Eax = (rd->pEax == NULL ? 0 : *(rd->pEax)); // Please leave EBP, ESP, EIP at the front so I don't have to scroll // left to see the most important registers. Thanks! LOG( (LF_CORDB, LL_INFO1000, "DT::TASSC:Registers:" "Ebp = %x Esp = %x Eip = %x Edi:%d " "Esi = %x Ebx = %x Edx = %x Ecx = %x Eax = %x\n", drd->FP, drd->SP, drd->PC, drd->Edi, drd->Esi, drd->Ebx, drd->Edx, drd->Ecx, drd->Eax ) ); #elif defined(_PPC_) || defined (_SPARC_) // All that's needed for now #else PORTABILITY_ASSERT("TraceAndSendStackCallback needs some munging for this platform."); #endif DebuggerIPCE_FuncData* currentFuncData = &rsfd->currentSTRData->v.funcData; _ASSERTE(fd != NULL); GetVAInfo(&(currentFuncData->fVarArgs), &(currentFuncData->rpSig), &(currentFuncData->cbSig), &(currentFuncData->rpFirstArg), fd, rd, pInfo->relOffset); LOG((LF_CORDB, LL_INFO10000, "DT::TASSC: good frame for %s::%s\n", fd->m_pszDebugClassName, fd->m_pszDebugMethodName)); // // Fill in information about the function that goes with this // frame. // currentFuncData->funcRVA = g_pEEInterface->MethodDescGetRVA(fd); _ASSERTE (t != NULL); Module *pRuntimeModule = g_pEEInterface->MethodDescGetModule(fd); AppDomain *pAppDomain = pInfo->currentAppDomain; currentFuncData->funcDebuggerModuleToken = (void*) g_pDebugger->LookupModule(pRuntimeModule, pAppDomain); if (currentFuncData->funcDebuggerModuleToken == NULL && rsfd->iWhich == IPC_TARGET_INPROC) { currentFuncData->funcDebuggerModuleToken = (void*)g_pDebugger->AddDebuggerModule(pRuntimeModule, pAppDomain); LOG((LF_CORDB, LL_INFO100, "DT::TASSC: load module Mod:%#08x AD:%#08x isDynamic:%#x runtimeMod:%#08x\n", currentFuncData->funcDebuggerModuleToken, pAppDomain, pRuntimeModule->IsReflection(), pRuntimeModule)); } _ASSERTE(currentFuncData->funcDebuggerModuleToken != 0); currentFuncData->funcDebuggerAssemblyToken = (g_pEEInterface->MethodDescGetModule(fd))->GetClassLoader()->GetAssembly(); currentFuncData->funcMetadataToken = fd->GetMemberDef(); currentFuncData->classMetadataToken = fd->GetClass()->GetCl(); // Pass back the local var signature token. COR_ILMETHOD *CorILM = g_pEEInterface->MethodDescGetILHeader(fd); if (CorILM == NULL ) { currentFuncData->localVarSigToken = mdSignatureNil; currentFuncData->ilStartAddress = NULL; currentFuncData->ilSize = 0; rsfd->currentSTRData->v.ILIP = NULL; currentFuncData->nativeStartAddressPtr = NULL; currentFuncData->nativeSize = 0; currentFuncData->nativenVersion = DebuggerJitInfo::DJI_VERSION_FIRST_VALID; } else { COR_ILMETHOD_DECODER ILHeader(CorILM); if (ILHeader.GetLocalVarSigTok() != 0) currentFuncData->localVarSigToken = ILHeader.GetLocalVarSigTok(); else currentFuncData->localVarSigToken = mdSignatureNil; // // currentFuncData->ilStartAddress = const_cast<BYTE*>(ILHeader.Code); currentFuncData->ilSize = ILHeader.GetCodeSize(); currentFuncData->ilnVersion = g_pDebugger->GetVersionNumber(fd); LOG((LF_CORDB,LL_INFO10000,"Sending il Ver:0x%x in stack trace!\n", currentFuncData->ilnVersion)); DebuggerJitInfo *jitInfo = g_pDebugger->GetJitInfo(fd, (const BYTE*)*pInfo->registers.pPC); if (jitInfo == NULL) { //EnC: Couldn't find the code; rsfd->currentSTRData->v.ILIP = NULL; // Note: always send back the size of the method. This // allows us to get the code, even when we haven't // been tracking. (Handling of the GetCode message // knows how to find the start address of the code, or // how to respond if is been pitched.) currentFuncData->nativeSize = g_pEEInterface->GetFunctionSize(fd); currentFuncData->nativeStartAddressPtr = NULL; currentFuncData->nativenVersion = DebuggerJitInfo::DJI_VERSION_FIRST_VALID; currentFuncData->CodeVersionToken = NULL; currentFuncData->ilToNativeMapAddr = NULL; currentFuncData->ilToNativeMapSize = 0; currentFuncData->nVersionMostRecentEnC = currentFuncData->ilnVersion; } else { LOG((LF_CORDB,LL_INFO10000,"DeTh::TASSC: Code: 0x%x Got DJI " "0x%x, from 0x%x to 0x%x\n",(const BYTE*)drd->PC,jitInfo, jitInfo->m_addrOfCode, jitInfo->m_addrOfCode + jitInfo->m_sizeOfCode)); DWORD whichIrrelevant; rsfd->currentSTRData->v.ILIP = const_cast<BYTE*>(ILHeader.Code) + jitInfo->MapNativeOffsetToIL((SIZE_T)pInfo->relOffset, &rsfd->currentSTRData->v.mapping, &whichIrrelevant); // Pass back the pointers to the sequence point map so // that the RIght Side can copy it out if needed. _ASSERTE(jitInfo->m_sequenceMapSorted); currentFuncData->ilToNativeMapAddr = jitInfo->m_sequenceMap; currentFuncData->ilToNativeMapSize = jitInfo->m_sequenceMapCount; if (!jitInfo->m_codePitched) { // It's there & life is groovy currentFuncData->nativeStartAddressPtr = &(jitInfo->m_addrOfCode); currentFuncData->nativeSize = g_pEEInterface->GetFunctionSize(fd); currentFuncData->nativenVersion = jitInfo->m_nVersion; currentFuncData->CodeVersionToken = (void *)jitInfo; } else { // It's been pitched currentFuncData->nativeStartAddressPtr = NULL; currentFuncData->nativeSize = 0; } LOG((LF_CORDB,LL_INFO10000,"Sending native Ver:0x%x Tok:0x%x in stack trace!\n", currentFuncData->nativenVersion,currentFuncData->CodeVersionToken)); } } currentFuncData->nativeOffset = (SIZE_T)pInfo->relOffset; // // Bump our pointers to the next space for the next frame. // pEvent->StackTraceResultData.traceCount++; rsfd->currentSTRData++; rsfd->eventSize += sizeof(DebuggerIPCE_STRData); } if (pInfo->chainReason != 0) { // // If we've filled this event, send it off to the Right Side // before continuing the walk. // if ((rsfd->eventSize + sizeof(DebuggerIPCE_STRData)) >= rsfd->eventMaxSize) { // // pEvent->StackTraceResultData.threadUserState = g_pEEInterface->GetUserState(t); if (rsfd->iWhich == IPC_TARGET_OUTOFPROC) { rsfd->rcThread->SendIPCEvent(rsfd->iWhich); } else { DebuggerIPCEvent *peT; peT = rsfd->rcThread->GetIPCEventSendBufferContinuation(pEvent); if (peT == NULL) { pEvent->hr = E_OUTOFMEMORY; return SWA_ABORT; } CopyEventInfo(pEvent, peT); pEvent = peT; rsfd->pEvent = peT; rsfd->eventSize = 0; } // // Reset for the next set of frames. // pEvent->StackTraceResultData.traceCount = 0; rsfd->currentSTRData = &(pEvent->StackTraceResultData.traceData); rsfd->eventSize = (UINT_PTR)(rsfd->currentSTRData) - (UINT_PTR)(pEvent); } // // Send a chain boundary // rsfd->currentSTRData->isChain = true; rsfd->currentSTRData->u.chainReason = pInfo->chainReason; rsfd->currentSTRData->u.managed = pInfo->managed; rsfd->currentSTRData->u.context = pInfo->context; rsfd->currentSTRData->fp = pInfo->fp; rsfd->currentSTRData->quicklyUnwound = pInfo->quickUnwind; #ifdef _DEBUG REGDISPLAY* rd = &rsfd->chainRegisters; #endif // _DEBUG #ifdef _X86_ LOG( (LF_CORDB, LL_INFO1000, "DT::TASSC:Registers: Edi:%d \ Esi = %x Ebx = %x Edx = %x Ecx = %x Eax = %x Ebp = %x\n", (rd->pEdi == NULL ? 0 : *(rd->pEdi)), (rd->pEsi == NULL ? 0 : *(rd->pEsi)), (rd->pEbx == NULL ? 0 : *(rd->pEbx)), (rd->pEdx == NULL ? 0 : *(rd->pEdx)), (rd->pEcx == NULL ? 0 : *(rd->pEcx)), (rd->pEax == NULL ? 0 : *(rd->pEax)), (rd->pEbp == NULL ? 0 : *(rd->pEbp))) ); #endif LOG( (LF_CORDB, LL_INFO1000, "DT::TASSC:PC: PC:%x SP:%x\n", (SIZE_T)*(rd->pPC), rd->SP)); rsfd->needChainRegisters = true; // // Bump our pointers to the next space for the next frame. // pEvent->StackTraceResultData.traceCount++; rsfd->currentSTRData++; rsfd->eventSize += sizeof(DebuggerIPCE_STRData); } return SWA_CONTINUE; }
//+---------------------------------------------------------------------------- // // 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, const BYTE *pTarget, MetaSig* sig, VASigCookie *pCookie, BOOL fIsStatic, PTRARRAYREF& pArgArray, OBJECTREF *pVarRet, PTRARRAYREF *ppVarOutParams) { THROWSCOMPLUSEXCEPTION(); TRIGGERSGC(); // the debugger, profiler code triggers a GC LOG((LF_REMOTING, LL_INFO10, "CallDescrWithObjectArray IN\n")); ByRefInfo *pByRefs = NULL; INT64 retval = 0; UINT nActualStackBytes = 0; LPBYTE pAlloc = 0; LPBYTE pFrameBase = 0; UINT32 numByRef = 0; DWORD attr = pRM->dwFlags; #ifdef _DEBUG MethodDesc *pMD = pRM->pMethod; #endif // 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); } #ifdef DEBUGGING_SUPPORTED // debugger goo What does this do? can someone put a comment here? if (CORDebuggerTraceCall()) g_pDebugInterface->TraceCall(pTarget); #endif // DEBUGGING_SUPPORTED #ifdef PROFILING_SUPPORTED // If we're profiling, notify the profiler that we're about to invoke the remoting target if (CORProfilerTrackRemoting()) g_profControlBlock.pProfInterface->RemotingServerInvocationStarted( reinterpret_cast<ThreadID>(GetThread())); #endif // PROFILING_SUPPORTED // Create a fake FramedMethodFrame on the stack. nActualStackBytes = sig->SizeOfActualFixedArgStack(fIsStatic); pAlloc = (LPBYTE)_alloca(FramedMethodFrame::GetNegSpaceSize() + sizeof(FramedMethodFrame) + nActualStackBytes); pFrameBase = pAlloc + FramedMethodFrame::GetNegSpaceSize(); // cycle through the parameters and see if there are byrefs BYTE typ = 0; BOOL fHasByRefs = FALSE; if (attr & RM_ATTR_BYREF_FLAG_SET) fHasByRefs = attr & RM_ATTR_HAS_BYREF_ARG; else { sig->Reset(); 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(); } int nFixedArgs = sig->NumFixedArgs(); // 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()); } ArgIterator argit(pFrameBase, sig, fIsStatic); // set the this pointer OBJECTREF *ppThis = (OBJECTREF*)argit.GetThisAddr(); *ppThis = NULL; // if there is a return buffer, allocate it if (sig->HasRetBuffArg()) { EEClass *pEECValue = sig->GetRetEEClass(); _ASSERTE(pEECValue->IsValueClass()); MethodTable * mt = pEECValue->GetMethodTable(); *pVarRet = AllocateObject(mt); *(argit.GetRetBuffArgAddr()) = (*pVarRet)->UnBox(); #if defined(_PPC_) || defined(_SPARC_) // retbuf // the CallDescrWorker callsite for methods with return buffer is // different for RISC CPUs - we pass this information along by setting // the lowest bit in pTarget pTarget = (const BYTE *)((UINT_PTR)pTarget | 0x1); #endif } // gather data about the parameters by iterating over the sig: UINT32 arg = 0; UINT32 structSize = 0; int ofs = 0; // REVIEW: need to use actual arg count if VarArgs are supported ArgInfo* pArgInfo = (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 *)pArgInfo, 0, sizeof(ArgInfo)*nFixedArgs); #endif for( ; 0 != (ofs = argit.GetNextOffset(&typ, &structSize)); arg++, pArgInfo++ ) { if (typ == ELEMENT_TYPE_BYREF) { EEClass *pClass = NULL; CorElementType brType = sig->GetByRefType(&pClass); if (CorIsPrimitiveType(brType)) { pArgInfo->dataSize = gElementTypeInfo[brType].m_cbSize; } else if (pClass->IsValueClass()) { pArgInfo->dataSize = pClass->GetAlignedNumInstanceFieldBytes(); numByRef ++; } else { pArgInfo->dataSize = sizeof(Object *); numByRef ++; } ByRefInfo *brInfo = (ByRefInfo *) _alloca(offsetof(ByRefInfo,data) + pArgInfo->dataSize); brInfo->argIndex = arg; brInfo->typ = brType; brInfo->pClass = pClass; brInfo->pNext = pByRefs; pByRefs = brInfo; pArgInfo->dataLocation = (BYTE*)brInfo->data; *((void**)(pFrameBase + ofs)) = (void*)pArgInfo->dataLocation; pArgInfo->dataClass = pClass; pArgInfo->dataType = brType; pArgInfo->byref = TRUE; } else { pArgInfo->dataLocation = pFrameBase + ofs; pArgInfo->dataSize = StackElemSize(structSize); pArgInfo->dataClass = sig->GetTypeHandle().GetClass(); // this may cause GC! pArgInfo->dataType = typ; pArgInfo->byref = FALSE; } } if (!fIsStatic) { // If this isn't a value class, verify the objectref #ifdef _DEBUG if (pMD->GetClass()->IsValueClass() == FALSE) { VALIDATEOBJECTREF(pServer); } #endif //_DEBUG *ppThis = pServer; } // 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. BEGINFORBIDGC(); // reset pArgInfo to point to the start of the block we _alloca-ed pArgInfo = pArgInfo-nFixedArgs; PBYTE dataLocation; INT32 dataSize; EEClass *dataClass; BYTE dataType; OBJECTREF* pArguments = pArgArray->m_Array; UINT32 i, j = arg; for (i=0; i<j; i++) { dataSize = pArgInfo->dataSize; dataLocation = pArgInfo->dataLocation; dataClass = pArgInfo->dataClass; dataType = pArgInfo->dataType; 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: 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()); } break; case 8: *((INT64*)dataLocation) = *((INT64*)pArguments[i]->GetData()); break; default: { memcpy(dataLocation, pArguments[i]->UnBox(), dataSize); } } pArgInfo++; } #ifdef _DEBUG // Should not be using this any more pArgInfo = pArgInfo - nFixedArgs; memset((void *)pArgInfo, 0, sizeof(ArgInfo)*nFixedArgs); #endif // if there were byrefs, push a protection frame ProtectByRefsFrame *pProtectionFrame = NULL; if (pByRefs && numByRef > 0) { char *pBuffer = (char*)_alloca (sizeof (ProtectByRefsFrame)); pProtectionFrame = new (pBuffer) ProtectByRefsFrame(GetThread(), pByRefs); } // call the correct worker function depending of if the method // is varargs or not ENDFORBIDGC(); #ifdef _PPC_ FramedMethodFrame::Enregister(pFrameBase, sig, fIsStatic, nActualStackBytes); #endif INSTALL_COMPLUS_EXCEPTION_HANDLER(); retval = CallDescrWorker( pFrameBase + sizeof(FramedMethodFrame) + nActualStackBytes, nActualStackBytes / STACK_ELEM_SIZE, #if defined(_X86_) || defined(_PPC_) // argregs (ArgumentRegisters*)(pFrameBase + FramedMethodFrame::GetOffsetOfArgumentRegisters()), #endif (LPVOID)pTarget); UNINSTALL_COMPLUS_EXCEPTION_HANDLER(); // set floating point return values getFPReturn(sig->GetFPReturnSize(), &retval); // need to build a object based on the return type. if (!sig->HasRetBuffArg()) { BYTE *pRetVal = (BYTE*)&retval; #ifdef BIGENDIAN switch (sig->GetReturnTypeSize()) { case 1: pRetVal += sizeof(void*)-1; break; case 2: pRetVal += sizeof(void*)-2; break; default: // nothing to do break; } #endif GetObjectFromStack(pVarRet, pRetVal, sig->GetReturnType(), sig->GetRetEEClass()); } // 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; GetObjectFromStack(&pTmp, pByRefs->data, pByRefs->typ, pByRefs->pClass); (*ppVarOutParams)->SetAt(pByRefs->argIndex, pTmp); pByRefs = pByRefs->pNext; } while (pByRefs); if (pProtectionFrame) pProtectionFrame->Pop(); } #ifdef PROFILING_SUPPORTED // If we're profiling, notify the profiler that we're about to invoke the remoting target if (CORProfilerTrackRemoting()) g_profControlBlock.pProfInterface->RemotingServerInvocationReturned( reinterpret_cast<ThreadID>(GetThread())); #endif // PROFILING_SUPPORTED LOG((LF_REMOTING, LL_INFO10, "CallDescrWithObjectArray OUT\n")); }