diff options
Diffstat (limited to 'src/vm/debugdebugger.cpp')
-rw-r--r-- | src/vm/debugdebugger.cpp | 1771 |
1 files changed, 1771 insertions, 0 deletions
diff --git a/src/vm/debugdebugger.cpp b/src/vm/debugdebugger.cpp new file mode 100644 index 0000000000..9ea5427dfe --- /dev/null +++ b/src/vm/debugdebugger.cpp @@ -0,0 +1,1771 @@ +// 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: DebugDebugger.cpp +** +** Purpose: Native methods on System.Debug.Debugger +** +** + +===========================================================*/ + +#include "common.h" + +#include <object.h> +#include "ceeload.h" +#include "corpermp.h" + +#include "excep.h" +#include "frames.h" +#include "vars.hpp" +#include "field.h" +#include "gc.h" +#include "jitinterface.h" +#include "debugdebugger.h" +#include "dbginterface.h" +#include "cordebug.h" +#include "corsym.h" +#include "generics.h" +#include "eemessagebox.h" +#include "stackwalk.h" + +LogHashTable g_sLogHashTable; + +#ifndef DACCESS_COMPILE +//---------------------------------------------------------------------------- +// +// FindMostRecentUserCodeOnStack - find out the most recent user managed code on stack +// +// +// Arguments: +// pContext - [optional] pointer to the context to be restored the user code's context if found +// +// Return Value: +// The most recent user managed code or NULL if not found. +// +// Note: +// It is a heuristic approach to get the address of the user managed code that calls into +// BCL like System.Diagnostics.Debugger.Break assuming that we can find the original user +// code caller with stack walking. +// +// DoWatsonForUserBreak has the address returned from the helper frame that points to an +// internal BCL helpful function doing permission check. From bucketing perspetive it is +// more preferable to report the user managed code that invokes Debugger.Break instead. +// +// User managed code is managed code in non-system assembly. Currently, only mscorlib.dll +// is marked as system assembly. +// +//---------------------------------------------------------------------------- +UINT_PTR FindMostRecentUserCodeOnStack(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + Thread * pThread = GetThread(); + _ASSERTE(pThread != NULL); + + UINT_PTR address = NULL; + + CONTEXT ctx; + REGDISPLAY rd; + SetUpRegdisplayForStackWalk(pThread, &ctx, &rd); + + StackFrameIterator frameIter; + frameIter.Init(pThread, pThread->GetFrame(), &rd, FUNCTIONSONLY | LIGHTUNWIND); + + while (frameIter.IsValid()) + { + MethodDesc * pMD = frameIter.m_crawl.GetFunction(); + + // Is it not a system assembly? User manged user will not be in system assembly. + if ((pMD != NULL) && (!pMD->GetAssembly()->IsSystem())) + { + CrawlFrame * pCF = &(frameIter.m_crawl); + address = (UINT_PTR)GetControlPC(pCF->GetRegisterSet()); + break; + } + + if (frameIter.Next() != SWA_CONTINUE) + { + break; + } + } + + return address; +} + +#ifndef FEATURE_CORECLR +// Call into the unhandled-exception processing code to launch Watson. +// +// Arguments: +// address - address to distinguish callsite of break. +// +// Notes: +// Invokes a watson dialog in response to a user break (Debug.Break). +// Assumes that caller has already enforced any policy it cares about related to whether a debugger is attached. +void DoWatsonForUserBreak(UINT_PTR address) +{ + CONTRACTL + { + MODE_ANY; + GC_TRIGGERS; + THROWS; + PRECONDITION(address != NULL); + } + CONTRACTL_END; + + CONTEXT context; + EXCEPTION_RECORD exceptionRecord; + EXCEPTION_POINTERS exceptionPointers; + + ZeroMemory(&context, sizeof(context)); + ZeroMemory(&exceptionRecord, sizeof(exceptionRecord)); + ZeroMemory(&exceptionPointers, sizeof(exceptionPointers)); + + // Try to locate the user managed code invoking System.Diagnostics.Debugger.Break + UINT_PTR userCodeAddress = FindMostRecentUserCodeOnStack(); + if (userCodeAddress != NULL) + { + address = userCodeAddress; + } + + LOG((LF_EH, LL_INFO10, "DoDebugBreak: break at %0p\n", address)); + + exceptionRecord.ExceptionAddress = reinterpret_cast< PVOID >(address); + exceptionPointers.ExceptionRecord = &exceptionRecord; + exceptionPointers.ContextRecord = &context; + + Thread *pThread = GetThread(); + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker(); + _ASSERTE(pUEWatsonBucketTracker != NULL); + pUEWatsonBucketTracker->SaveIpForWatsonBucket(address); + pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UserBreakpoint, pThread, NULL); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + + WatsonLastChance(GetThread(), &exceptionPointers, TypeOfReportedError::UserBreakpoint); + +} // void DoDebugBreak() +#endif // !FEATURE_CORECLR + +// This does a user break, triggered by System.Diagnostics.Debugger.Break, or the IL opcode for break. +// +// Notes: +// If a managed debugger is attached, this should send the managed UserBreak event. +// Else if a native debugger is attached, this should send a native break event (kernel32!DebugBreak) +// Else, this should invoke Watson. +// +// Historical trivia: +// - In whidbey, this would still invoke Watson if a native-only debugger is attached. +// - In arrowhead, the managed debugging pipeline switched to be built on the native pipeline. +FCIMPL0(void, DebugDebugger::Break) +{ + FCALL_CONTRACT; + +#ifdef DEBUGGING_SUPPORTED + HELPER_METHOD_FRAME_BEGIN_0(); + +#ifdef _DEBUG + { + static int fBreakOnDebugBreak = -1; + if (fBreakOnDebugBreak == -1) + fBreakOnDebugBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnDebugBreak); + _ASSERTE(fBreakOnDebugBreak == 0 && "BreakOnDebugBreak"); + } + + static BOOL fDbgInjectFEE = -1; + if (fDbgInjectFEE == -1) + fDbgInjectFEE = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgInjectFEE); +#endif + + // WatsonLastChance has its own complex (and changing) policy of how to behave if a debugger is attached. + // So caller should explicitly enforce any debugger-related policy before handing off to watson. + // Check managed-only first, since managed debugging may be built on native-debugging. + if (CORDebuggerAttached() INDEBUG(|| fDbgInjectFEE)) + { + // A managed debugger is already attached -- let it handle the event. + g_pDebugInterface->SendUserBreakpoint(GetThread()); + } + else if (IsDebuggerPresent()) + { + // No managed debugger, but a native debug is attached. Explicitly fire a native user breakpoint. + // Don't rely on Watson support since that may have a different policy. + + // Toggle to preemptive before firing the debug event. This allows the debugger to suspend this + // thread at the debug event. + GCX_PREEMP(); + + // This becomes an unmanaged breakpoint, such as int 3. + DebugBreak(); + } + else + { +#ifndef FEATURE_CORECLR + // No debugger attached -- Watson up. + + // The HelperMethodFrame knows how to get the return address. + DoWatsonForUserBreak(HELPER_METHOD_FRAME_GET_RETURN_ADDRESS()); +#endif //FEATURE_CORECLR + } + + HELPER_METHOD_FRAME_END(); +#endif // DEBUGGING_SUPPORTED +} +FCIMPLEND + +FCIMPL0(FC_BOOL_RET, DebugDebugger::Launch) +{ + FCALL_CONTRACT; + +#ifdef DEBUGGING_SUPPORTED + if (CORDebuggerAttached()) + { + FC_RETURN_BOOL(TRUE); + } + else if (g_pDebugInterface != NULL) + { + HRESULT hr = S_OK; + + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + hr = g_pDebugInterface->LaunchDebuggerForUser(GetThread(), NULL, TRUE, TRUE); + + HELPER_METHOD_FRAME_END(); + + if (SUCCEEDED (hr)) + { + FC_RETURN_BOOL(TRUE); + } + } +#endif // DEBUGGING_SUPPORTED + + FC_RETURN_BOOL(FALSE); +} +FCIMPLEND + + +FCIMPL0(FC_BOOL_RET, DebugDebugger::IsDebuggerAttached) +{ + FCALL_CONTRACT; + + FC_GC_POLL_RET(); + +#ifdef DEBUGGING_SUPPORTED + FC_RETURN_BOOL(CORDebuggerAttached()); +#else // DEBUGGING_SUPPORTED + FC_RETURN_BOOL(FALSE); +#endif +} +FCIMPLEND + + +/*static*/ BOOL DebugDebugger::IsLoggingHelper() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + +#ifdef DEBUGGING_SUPPORTED + if (CORDebuggerAttached()) + { + return (g_pDebugInterface->IsLoggingEnabled()); + } +#endif // DEBUGGING_SUPPORTED + return FALSE; +} + + +// Log to managed debugger. +// It will send a managed log event, which will faithfully send the two string parameters here without +// appending a newline to anything. +// It will also call OutputDebugString() which will send a native debug event. The message +// string there will be a composite of the two managed string parameters and may include a newline. +FCIMPL3(void, DebugDebugger::Log, + INT32 Level, + StringObject* strModuleUNSAFE, + StringObject* strMessageUNSAFE + ) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(strModuleUNSAFE, NULL_OK)); + PRECONDITION(CheckPointer(strMessageUNSAFE, NULL_OK)); + } + CONTRACTL_END; + + STRINGREF strModule = (STRINGREF)ObjectToOBJECTREF(strModuleUNSAFE); + STRINGREF strMessage = (STRINGREF)ObjectToOBJECTREF(strMessageUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_2(strModule, strMessage); + + // OutputDebugString will log to native/interop debugger. + if (strModule != NULL) + { + WszOutputDebugString(strModule->GetBuffer()); + WszOutputDebugString(W(" : ")); + } + + if (strMessage != NULL) + { + WszOutputDebugString(strMessage->GetBuffer()); + } + + // If we're not logging a module prefix, then don't log the newline either. + // Thus if somebody is just logging messages, there won't be any extra newlines in there. + // If somebody is also logging category / module information, then this call to OutputDebugString is + // already prepending that to the message, so we append a newline for readability. + if (strModule != NULL) + { + WszOutputDebugString(W("\n")); + } + + +#ifdef DEBUGGING_SUPPORTED + + // Send message for logging only if the + // debugger is attached and logging is enabled + // for the given category + if (CORDebuggerAttached()) + { + if (IsLoggingHelper() ) + { + // Copy log message and category into our own SString to protect against GC + // Strings may contain embedded nulls, but we need to handle null-terminated + // strings, so use truncate now. + StackSString switchName; + if( strModule != NULL ) + { + // truncate if necessary + COUNT_T iLen = (COUNT_T) wcslen(strModule->GetBuffer()); + if (iLen > MAX_LOG_SWITCH_NAME_LEN) + { + iLen = MAX_LOG_SWITCH_NAME_LEN; + } + switchName.Set(strModule->GetBuffer(), iLen); + } + + SString message; + if( strMessage != NULL ) + { + message.Set(strMessage->GetBuffer(), (COUNT_T) wcslen(strMessage->GetBuffer())); + } + + g_pDebugInterface->SendLogMessage (Level, &switchName, &message); + } + } + +#endif // DEBUGGING_SUPPORTED + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +FCIMPL0(FC_BOOL_RET, DebugDebugger::IsLogging) +{ + FCALL_CONTRACT; + + FC_GC_POLL_RET(); + + FC_RETURN_BOOL(IsLoggingHelper()); +} +FCIMPLEND + + +FCIMPL4(void, DebugStackTrace::GetStackFramesInternal, + StackFrameHelper* pStackFrameHelperUNSAFE, + INT32 iSkip, + CLR_BOOL fNeedFileInfo, + Object* pExceptionUNSAFE + ) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(pStackFrameHelperUNSAFE)); + PRECONDITION(CheckPointer(pExceptionUNSAFE, NULL_OK)); + } + CONTRACTL_END; + + STACKFRAMEHELPERREF pStackFrameHelper = (STACKFRAMEHELPERREF)ObjectToOBJECTREF(pStackFrameHelperUNSAFE); + OBJECTREF pException = ObjectToOBJECTREF(pExceptionUNSAFE); + PTRARRAYREF dynamicMethodArrayOrig = NULL; + + HELPER_METHOD_FRAME_BEGIN_2(pStackFrameHelper, pException); + + GCPROTECT_BEGIN(dynamicMethodArrayOrig); + + ASSERT(iSkip >= 0); + + GetStackFramesData data; + + data.pDomain = GetAppDomain(); + + data.skip = iSkip; + + data.NumFramesRequested = pStackFrameHelper->iFrameCount; + + if (pException == NULL) + { + // Thread is NULL if it's the current thread. + data.TargetThread = pStackFrameHelper->targetThread; + GetStackFrames(NULL, (void*)-1, &data); + } + else + { + // We also fetch the dynamic method array in a GC protected artifact to ensure + // that the resolver objects, if any, are kept alive incase the exception object + // is thrown again (resetting the dynamic method array reference in the object) + // that may result in resolver objects getting collected before they can be reachable again + // (from the code below). + GetStackFramesFromException(&pException, &data, &dynamicMethodArrayOrig); + } + + if (data.cElements != 0) + { +#if defined(FEATURE_ISYM_READER) && defined(FEATURE_COMINTEROP) + if (fNeedFileInfo) + { + // Calls to COM up ahead. + EnsureComStarted(); + } +#endif // FEATURE_ISYM_READER && FEATURE_COMINTEROP + + // Allocate memory for the MethodInfo objects + BASEARRAYREF methodInfoArray = (BASEARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgMethodHandle), (OBJECTREF)methodInfoArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the Offsets + OBJECTREF offsets = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiOffset), (OBJECTREF)offsets, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the ILOffsets + OBJECTREF ilOffsets = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiILOffset), (OBJECTREF)ilOffsets, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the array of assembly file names + PTRARRAYREF assemblyPathArray = (PTRARRAYREF) AllocateObjectArray(data.cElements, g_pStringClass); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgAssemblyPath), (OBJECTREF)assemblyPathArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the LoadedPeAddress + BASEARRAYREF loadedPeAddressArray = (BASEARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgLoadedPeAddress), (OBJECTREF)loadedPeAddressArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the LoadedPeSize + OBJECTREF loadedPeSizeArray = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLoadedPeSize), (OBJECTREF)loadedPeSizeArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the InMemoryPdbAddress + BASEARRAYREF inMemoryPdbAddressArray = (BASEARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgInMemoryPdbAddress), (OBJECTREF)inMemoryPdbAddressArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the InMemoryPdbSize + OBJECTREF inMemoryPdbSizeArray = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiInMemoryPdbSize), (OBJECTREF)inMemoryPdbSizeArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the MethodTokens + OBJECTREF methodTokens = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiMethodToken), (OBJECTREF)methodTokens, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the Filename string objects + PTRARRAYREF filenameArray = (PTRARRAYREF) AllocateObjectArray(data.cElements, g_pStringClass); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgFilename), (OBJECTREF)filenameArray, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the LineNumbers + OBJECTREF lineNumbers = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLineNumber), (OBJECTREF)lineNumbers, + pStackFrameHelper->GetAppDomain()); + + // Allocate memory for the ColumnNumbers + OBJECTREF columnNumbers = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiColumnNumber), (OBJECTREF)columnNumbers, + pStackFrameHelper->GetAppDomain()); + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // Allocate memory for the flag indicating if this frame represents the last one from a foreign + // exception stack trace provided we have any such frames. Otherwise, set it to null. + // When StackFrameHelper.IsLastFrameFromForeignExceptionStackTrace is invoked in managed code, + // it will return false for the null case. + // + // This is an optimization for us to not allocate the BOOL array if we do not have any frames + // from a foreign stack trace. + OBJECTREF IsLastFrameFromForeignStackTraceFlags = NULL; + if (data.fDoWeHaveAnyFramesFromForeignStackTrace) + { + IsLastFrameFromForeignStackTraceFlags = AllocatePrimitiveArray(ELEMENT_TYPE_BOOLEAN, data.cElements); + + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace), (OBJECTREF)IsLastFrameFromForeignStackTraceFlags, + pStackFrameHelper->GetAppDomain()); + } + else + { + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace), NULL, + pStackFrameHelper->GetAppDomain()); + } +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + // Determine if there are any dynamic methods in the stack trace. If there are, + // allocate an ObjectArray large enough to hold an ObjRef to each one. + unsigned iNumDynamics = 0; + unsigned iCurDynamic = 0; + for (int iElement=0; iElement < data.cElements; iElement++) + { + MethodDesc *pMethod = data.pElements[iElement].pFunc; + if (pMethod->IsLCGMethod()) + { + iNumDynamics++; + } + else + if (pMethod->GetMethodTable()->Collectible()) + { + iNumDynamics++; + } + } + + if (iNumDynamics) + { + PTRARRAYREF dynamicDataArray = (PTRARRAYREF) AllocateObjectArray(iNumDynamics, g_pObjectClass); + SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->dynamicMethods), (OBJECTREF)dynamicDataArray, + pStackFrameHelper->GetAppDomain()); + } + + int iNumValidFrames = 0; + for (int i = 0; i < data.cElements; i++) + { + // The managed stacktrace classes always returns typical method definition, so we don't need to bother providing exact instantiation. + // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(data.pElements[i].pFunc, data.pElements[i].pExactGenericArgsToken, &pExactMethod, &thExactType); + MethodDesc* pFunc = data.pElements[i].pFunc; + + // Strip the instantiation to make sure that the reflection never gets a bad method desc back. + if (pFunc->HasMethodInstantiation()) + pFunc = pFunc->StripMethodInstantiation(); + _ASSERTE(pFunc->IsRuntimeMethodHandle()); + + // Method handle + size_t *pElem = (size_t*)pStackFrameHelper->rgMethodHandle->GetDataPtr(); + pElem[iNumValidFrames] = (size_t)pFunc; + + // Native offset + I4 *pI4 = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiOffset)->GetDirectPointerToNonObjectElements(); + pI4[iNumValidFrames] = data.pElements[i].dwOffset; + + // IL offset + I4 *pILI4 = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiILOffset)->GetDirectPointerToNonObjectElements(); + pILI4[iNumValidFrames] = data.pElements[i].dwILOffset; + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + if (data.fDoWeHaveAnyFramesFromForeignStackTrace) + { + // Set the BOOL indicating if the frame represents the last frame from a foreign exception stack trace. + U1 *pIsLastFrameFromForeignExceptionStackTraceU1 = (U1 *)((BOOLARRAYREF)pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace) + ->GetDirectPointerToNonObjectElements(); + pIsLastFrameFromForeignExceptionStackTraceU1 [iNumValidFrames] = (U1) data.pElements[i].fIsLastFrameFromForeignStackTrace; + } +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + MethodDesc *pMethod = data.pElements[i].pFunc; + + // If there are any dynamic methods, and this one is one of them, store + // a reference to it's managed resolver to keep it alive. + if (iNumDynamics) + { + if (pMethod->IsLCGMethod()) + { + DynamicMethodDesc *pDMD = pMethod->AsDynamicMethodDesc(); + OBJECTREF pResolver = pDMD->GetLCGMethodResolver()->GetManagedResolver(); + _ASSERTE(pResolver != NULL); + + ((PTRARRAYREF)pStackFrameHelper->dynamicMethods)->SetAt(iCurDynamic++, pResolver); + } + else if (pMethod->GetMethodTable()->Collectible()) + { + OBJECTREF pLoaderAllocator = pMethod->GetMethodTable()->GetLoaderAllocator()->GetExposedObject(); + _ASSERTE(pLoaderAllocator != NULL); + ((PTRARRAYREF)pStackFrameHelper->dynamicMethods)->SetAt(iCurDynamic++, pLoaderAllocator); + } + } + + Module *pModule = pMethod->GetModule(); + + // If it's an EnC method, then don't give back any line info, b/c the PDB is out of date. + // (We're using the stale PDB, not one w/ Edits applied). + // Since the MethodDesc is always the most recent, v1 instances of EnC methods on the stack + // will appeared to be Enc. This means we err on the side of not showing line numbers for EnC methods. + // If any method in the file was changed, then our line numbers could be wrong. Since we don't + // have udpated PDBs from EnC, we can at best look at the module's version number as a rough guess + // to if this file has been updated. + bool fIsEnc = false; +#ifdef EnC_SUPPORTED + if (pModule->IsEditAndContinueEnabled()) + { + EditAndContinueModule *eacm = (EditAndContinueModule *)pModule; + if (eacm->GetApplyChangesCount() != 1) + { + fIsEnc = true; + } + } +#endif + BOOL fPortablePDB = TRUE; + + // Check if the user wants the filenumber, linenumber info and that it is possible. + if (!fIsEnc && fNeedFileInfo) + { +#ifdef FEATURE_ISYM_READER + BOOL fFileInfoSet = FALSE; + ULONG32 sourceLine = 0; + ULONG32 sourceColumn = 0; + WCHAR wszFileName[MAX_LONGPATH]; + ULONG32 fileNameLength = 0; + { + // Note: we need to enable preemptive GC when accessing the unmanages symbol store. + GCX_PREEMP(); + + // Note: we use the NoThrow version of GetISymUnmanagedReader. If getting the unmanaged + // reader fails, then just leave the pointer NULL and leave any symbol info off of the + // stack trace. + ReleaseHolder<ISymUnmanagedReader> pISymUnmanagedReader( + pModule->GetISymUnmanagedReaderNoThrow()); + + if (pISymUnmanagedReader != NULL) + { + // Found a ISymUnmanagedReader for the regular PDB so don't attempt to + // read it as a portable PDB in mscorlib's StackFrameHelper. + fPortablePDB = FALSE; + + ReleaseHolder<ISymUnmanagedMethod> pISymUnmanagedMethod; + HRESULT hr = pISymUnmanagedReader->GetMethod(pMethod->GetMemberDef(), + &pISymUnmanagedMethod); + + if (SUCCEEDED(hr)) + { + // get all the sequence points and the documents + // associated with those sequence points. + // from the doument get the filename using GetURL() + ULONG32 SeqPointCount = 0; + ULONG32 RealSeqPointCount = 0; + + hr = pISymUnmanagedMethod->GetSequencePointCount(&SeqPointCount); + _ASSERTE (SUCCEEDED(hr) || (hr == E_OUTOFMEMORY) ); + + if (SUCCEEDED(hr) && SeqPointCount > 0) + { + // allocate memory for the objects to be fetched + NewArrayHolder<ULONG32> offsets (new (nothrow) ULONG32 [SeqPointCount]); + NewArrayHolder<ULONG32> lines (new (nothrow) ULONG32 [SeqPointCount]); + NewArrayHolder<ULONG32> columns (new (nothrow) ULONG32 [SeqPointCount]); + NewArrayHolder<ULONG32> endlines (new (nothrow) ULONG32 [SeqPointCount]); + NewArrayHolder<ULONG32> endcolumns (new (nothrow) ULONG32 [SeqPointCount]); + + // we free the array automatically, but we have to manually call release + // on each element in the array when we're done with it. + NewArrayHolder<ISymUnmanagedDocument*> documents ( + (ISymUnmanagedDocument **)new PVOID [SeqPointCount]); + + if ((offsets && lines && columns && documents && endlines && endcolumns)) + { + hr = pISymUnmanagedMethod->GetSequencePoints ( + SeqPointCount, + &RealSeqPointCount, + offsets, + (ISymUnmanagedDocument **)documents, + lines, + columns, + endlines, + endcolumns); + + _ASSERTE(SUCCEEDED(hr) || (hr == E_OUTOFMEMORY) ); + + if (SUCCEEDED(hr)) + { + _ASSERTE(RealSeqPointCount == SeqPointCount); + +#ifdef _DEBUG + { + // This is just some debugging code to help ensure that the array + // returned contains valid interface pointers. + for (ULONG32 i = 0; i < RealSeqPointCount; i++) + { + _ASSERTE(documents[i] != NULL); + documents[i]->AddRef(); + documents[i]->Release(); + } + } +#endif + + // This is the IL offset of the current frame + DWORD dwCurILOffset = data.pElements[i].dwILOffset; + + // search for the correct IL offset + DWORD j; + for (j=0; j<RealSeqPointCount; j++) + { + // look for the entry matching the one we're looking for + if (offsets[j] >= dwCurILOffset) + { + // if this offset is > what we're looking for, ajdust the index + if (offsets[j] > dwCurILOffset && j > 0) + { + j--; + } + + break; + } + } + + // If we didn't find a match, default to the last sequence point + if (j == RealSeqPointCount) + { + j--; + } + + while (lines[j] == 0x00feefee && j > 0) + { + j--; + } + +#ifdef DEBUGGING_SUPPORTED + if (lines[j] != 0x00feefee) + { + sourceLine = lines [j]; + sourceColumn = columns [j]; + } + else +#endif // DEBUGGING_SUPPORTED + { + sourceLine = 0; + sourceColumn = 0; + } + + // Also get the filename from the document... + _ASSERTE (documents [j] != NULL); + + hr = documents [j]->GetURL (MAX_LONGPATH, &fileNameLength, wszFileName); + _ASSERTE ( SUCCEEDED(hr) || (hr == E_OUTOFMEMORY) || (hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)) ); + + // indicate that the requisite information has been set! + fFileInfoSet = TRUE; + + // release the documents set by GetSequencePoints + for (DWORD x=0; x<RealSeqPointCount; x++) + { + documents [x]->Release(); + } + } // if got sequence points + + } // if all memory allocations succeeded + + // holders will now delete the arrays. + } + } + // Holder will release pISymUnmanagedMethod + } + + } // GCX_PREEMP() + + if (fFileInfoSet) + { + // Set the line and column numbers + I4 *pI4Line = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiLineNumber)->GetDirectPointerToNonObjectElements(); + pI4Line[iNumValidFrames] = sourceLine; + + I4 *pI4Column = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiColumnNumber)->GetDirectPointerToNonObjectElements(); + pI4Column[iNumValidFrames] = sourceColumn; + + // Set the file name + OBJECTREF obj = (OBJECTREF) StringObject::NewString(wszFileName); + pStackFrameHelper->rgFilename->SetAt(iNumValidFrames, obj); + } +#endif // FEATURE_ISYM_READER + + // If the above isym reader code did NOT set the source info either because it is ifdef'ed out (on xplat) + // or because the pdb is the new portable format on Windows then set the information needed to call the + // portable pdb reader in the StackTraceHelper. + if (fPortablePDB) + { + // Save MethodToken for the function + I4 *pMethodToken = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiMethodToken)->GetDirectPointerToNonObjectElements(); + pMethodToken[iNumValidFrames] = pMethod->GetMemberDef(); + + PEFile *pPEFile = pModule->GetFile(); + + // Get the address and size of the loaded PE image + COUNT_T peSize; + PTR_CVOID peAddress = pPEFile->GetLoadedImageContents(&peSize); + + // Save the PE address and size + PTR_CVOID *pLoadedPeAddress = (PTR_CVOID *)pStackFrameHelper->rgLoadedPeAddress->GetDataPtr(); + pLoadedPeAddress[iNumValidFrames] = peAddress; + + I4 *pLoadedPeSize = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiLoadedPeSize)->GetDirectPointerToNonObjectElements(); + pLoadedPeSize[iNumValidFrames] = (I4)peSize; + + // If there is a in memory symbol stream + CGrowableStream* stream = pModule->GetInMemorySymbolStream(); + if (stream != NULL) + { + MemoryRange range = stream->GetRawBuffer(); + + // Save the in-memory PDB address and size + PTR_VOID *pInMemoryPdbAddress = (PTR_VOID *)pStackFrameHelper->rgInMemoryPdbAddress->GetDataPtr(); + pInMemoryPdbAddress[iNumValidFrames] = range.StartAddress(); + + I4 *pInMemoryPdbSize = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiInMemoryPdbSize)->GetDirectPointerToNonObjectElements(); + pInMemoryPdbSize[iNumValidFrames] = (I4)range.Size(); + } + else + { + // Set the pdb path (assembly file name) + const SString& assemblyPath = pPEFile->GetPath(); + if (!assemblyPath.IsEmpty()) + { + OBJECTREF obj = (OBJECTREF)StringObject::NewString(assemblyPath); + pStackFrameHelper->rgAssemblyPath->SetAt(iNumValidFrames, obj); + } + } + } + } + + iNumValidFrames++; + } + + pStackFrameHelper->iFrameCount = iNumValidFrames; + } + else + { + pStackFrameHelper->iFrameCount = 0; + } + + GCPROTECT_END(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +FORCEINLINE void HolderDestroyStrongHandle(OBJECTHANDLE h) { if (h != NULL) DestroyStrongHandle(h); } +typedef Wrapper<OBJECTHANDLE, DoNothing<OBJECTHANDLE>, HolderDestroyStrongHandle, NULL> StrongHandleHolder; + +// receives a custom notification object from the target and sends it to the RS via +// code:Debugger::SendCustomDebuggerNotification +// Argument: dataUNSAFE - a pointer the the custom notification object being sent +FCIMPL1(void, DebugDebugger::CustomNotification, Object * dataUNSAFE) +{ + CONTRACTL + { + FCALL_CHECK; + } + CONTRACTL_END; + + OBJECTREF pData = ObjectToOBJECTREF(dataUNSAFE); + +#ifdef DEBUGGING_SUPPORTED + // Send notification only if the debugger is attached + if (CORDebuggerAttached() ) + { + HELPER_METHOD_FRAME_BEGIN_PROTECT(pData); + + Thread * pThread = GetThread(); + AppDomain * pAppDomain = pThread->GetDomain(); + + StrongHandleHolder objHandle = pAppDomain->CreateStrongHandle(pData); + MethodTable * pMT = pData->GetGCSafeMethodTable(); + Module * pModule = pMT->GetModule(); + DomainFile * pDomainFile = pModule->GetDomainFile(pAppDomain); + mdTypeDef classToken = pMT->GetCl(); + + pThread->SetThreadCurrNotification(objHandle); + g_pDebugInterface->SendCustomDebuggerNotification(pThread, pDomainFile, classToken); + pThread->ClearThreadCurrNotification(); + + TESTHOOKCALL(AppDomainCanBeUnloaded(pThread->GetDomain()->GetId().m_dwId, FALSE)); + if (pThread->IsAbortRequested()) + { + pThread->HandleThreadAbort(); + } + + HELPER_METHOD_FRAME_END(); + } + +#endif // DEBUGGING_SUPPORTED + +} +FCIMPLEND + + +void DebugStackTrace::GetStackFramesHelper(Frame *pStartFrame, + void* pStopStack, + GetStackFramesData *pData + ) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + } + CONTRACTL_END; + + ASSERT (pData != NULL); + + pData->cElements = 0; + + // if the caller specified (< 20) frames are required, then allocate + // only that many + if ((pData->NumFramesRequested > 0) && (pData->NumFramesRequested < 20)) + { + pData->cElementsAllocated = pData->NumFramesRequested; + } + else + { + pData->cElementsAllocated = 20; + } + + // Allocate memory for the initial 'n' frames + pData->pElements = new DebugStackTraceElement[pData->cElementsAllocated]; + + if (pData->TargetThread == NULL || + pData->TargetThread->GetInternal() == GetThread()) + { + // Null target thread specifies current thread. + GetThread()->StackWalkFrames(GetStackFramesCallback, pData, FUNCTIONSONLY, pStartFrame); + } + else + { + Thread *pThread = pData->TargetThread->GetInternal(); + _ASSERTE (pThread != NULL); + + // Here's the timeline for the TS_UserSuspendPending and TS_SyncSuspended bits. + // 0) Neither TS_UserSuspendPending nor TS_SyncSuspended set. + // 1) The suspending thread grabs the thread store lock + // then sets TS_UserSuspendPending + // then puts in place trip wires for the suspendee (if it is in managed code) + // and releases the thread store lock. + // 2) The suspending thread waits for the "SafeEvent". + // 3) The suspendee continues execution until it tries to enter preemptive mode. + // If it trips over the wires put in place by the suspending thread, + // it will try to enter preemptive mode. + // 4) The suspendee sets TS_SyncSuspended and the "SafeEvent". + // Then it waits for m_UserSuspendEvent. + // 5) AT THIS POINT, IT IS SAFE TO WALK THE SUSPENDEE'S STACK. + // 6) Now, some thread wants to resume the suspendee. + // The resuming thread takes the thread store lock + // then clears the TS_UserSuspendPending flag + // then sets m_UserSuspendEvent + // and releases the thread store lock. + // 7) The suspendee clears the TS_SyncSuspended flag. + // + // In other words, it is safe to trace the thread's stack IF we're holding the + // thread store lock AND TS_UserSuspendPending is set AND TS_SyncSuspended is set. + // + // This is because: + // - If we were not holding the thread store lock, the thread could be resumed + // underneath us. + // - As long as only TS_UserSuspendPending is set (and the thread is in cooperative + // mode), the thread can still be executing managed code until it trips. + // - When only TS_SyncSuspended is set, we race against it resuming execution. + + ThreadStoreLockHolder tsl; + + // We erect a barrier so that if the thread tries to disable preemptive GC, + // it will look at the TS_UserSuspendPending flag. Otherwise, it could resume + // execution of managed code during our stack walk. + TSSuspendHolder shTrap; + + Thread::ThreadState state = pThread->GetSnapshotState(); + if (state & (Thread::TS_Unstarted|Thread::TS_Dead|Thread::TS_Detached)) + { + goto LSafeToTrace; + } + + if (state & Thread::TS_UserSuspendPending) + { + if (state & Thread::TS_SyncSuspended) + { + goto LSafeToTrace; + } + +#ifndef DISABLE_THREADSUSPEND + // On Mac don't perform the optimization below, but rather wait for + // the suspendee to set the TS_SyncSuspended flag + + // The target thread is not actually suspended yet, but if it is + // in preemptive mode, then it is still safe to trace. Before we + // can look at another thread's GC mode, we have to suspend it: + // The target thread updates its GC mode flag with non-interlocked + // operations, and Thread::SuspendThread drains the CPU's store + // buffer (by virtue of calling GetThreadContext). + switch (pThread->SuspendThread()) + { + case Thread::STR_Success: + if (!pThread->PreemptiveGCDisabledOther()) + { + pThread->ResumeThread(); + goto LSafeToTrace; + } + + // Refuse to trace the stack. + // + // Note that there is a pretty large window in-between when the + // target thread sets the GC mode to cooperative, and when it + // actually sets the TS_SyncSuspended bit. In this window, we + // will refuse to take a stack trace even though it would be + // safe to do so. + pThread->ResumeThread(); + break; + case Thread::STR_Failure: + case Thread::STR_NoStressLog: + break; + case Thread::STR_UnstartedOrDead: + // We know the thread is not unstarted, because we checked for + // TS_Unstarted above. + _ASSERTE(!(state & Thread::TS_Unstarted)); + + // Since the thread is dead, it is safe to trace. + goto LSafeToTrace; + case Thread::STR_SwitchedOut: + if (!pThread->PreemptiveGCDisabledOther()) + { + goto LSafeToTrace; + } + break; + default: + UNREACHABLE(); + } +#endif // DISABLE_THREADSUSPEND + } + + COMPlusThrow(kThreadStateException, IDS_EE_THREAD_BAD_STATE); + + LSafeToTrace: + pThread->StackWalkFrames(GetStackFramesCallback, + pData, + FUNCTIONSONLY|ALLOW_ASYNC_STACK_WALK, + pStartFrame); + } + + // Do a 2nd pass outside of any locks. + // This will compute IL offsets. + for(INT32 i = 0; i < pData->cElements; i++) + { + pData->pElements[i].InitPass2(); + } + +} + + +void DebugStackTrace::GetStackFrames(Frame *pStartFrame, + void* pStopStack, + GetStackFramesData *pData + ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + GetStackFramesHelper(pStartFrame, pStopStack, pData); +} + + +StackWalkAction DebugStackTrace::GetStackFramesCallback(CrawlFrame* pCf, VOID* data) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + GetStackFramesData* pData = (GetStackFramesData*)data; + + if (pData->pDomain != pCf->GetAppDomain()) + { + return SWA_CONTINUE; + } + + if (pData->skip > 0) + { + pData->skip--; + return SWA_CONTINUE; + } + + // <REVISIT_TODO>@todo: How do we know what kind of frame we have?</REVISIT_TODO> + // Can we always assume FramedMethodFrame? + // NOT AT ALL!!!, but we can assume it's a function + // because we asked the stackwalker for it! + MethodDesc* pFunc = pCf->GetFunction(); + + if (pData->cElements >= pData->cElementsAllocated) + { + + DebugStackTraceElement* pTemp = new (nothrow) DebugStackTraceElement[2*pData->cElementsAllocated]; + + if (!pTemp) + { + return SWA_ABORT; + } + + memcpy(pTemp, pData->pElements, pData->cElementsAllocated * sizeof(DebugStackTraceElement)); + + delete [] pData->pElements; + + pData->pElements = pTemp; + pData->cElementsAllocated *= 2; + } + + PCODE ip; + DWORD dwNativeOffset; + + if (pCf->IsFrameless()) + { + // Real method with jitted code. + dwNativeOffset = pCf->GetRelOffset(); + ip = GetControlPC(pCf->GetRegisterSet()); + } + else + { + ip = NULL; + dwNativeOffset = 0; + } + + pData->pElements[pData->cElements].InitPass1( + dwNativeOffset, + pFunc, + ip); + + // We'll init the IL offsets outside the TSL lock. + + + ++pData->cElements; + + // Since we may be asynchronously walking another thread's stack, + // check (frequently) for stack-buffer-overrun corruptions after + // any long operation + pCf->CheckGSCookies(); + + // check if we already have the number of frames that the user had asked for + if ((pData->NumFramesRequested != 0) && (pData->NumFramesRequested <= pData->cElements)) + { + return SWA_ABORT; + } + + return SWA_CONTINUE; +} +#endif // !DACCESS_COMPILE + +void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, + GetStackFramesData *pData, + PTRARRAYREF * pDynamicMethodArray /*= NULL*/ + ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION (IsProtectedByGCFrame (e)); + PRECONDITION ((pDynamicMethodArray == NULL) || IsProtectedByGCFrame (pDynamicMethodArray)); + SUPPORTS_DAC; + } + CONTRACTL_END; + + ASSERT (pData != NULL); + + // Reasonable default, will indicate error on failure + pData->cElements = 0; + +#ifndef DACCESS_COMPILE + // for DAC builds this has already been validated + // Get the class for the exception + MethodTable *pExcepClass = (*e)->GetMethodTable(); + + _ASSERTE(IsException(pExcepClass)); // what is the pathway for this? + if (!IsException(pExcepClass)) + { + return; + } +#endif // DACCESS_COMPILE + + // Now get the _stackTrace reference + StackTraceArray traceData; + EXCEPTIONREF(*e)->GetStackTrace(traceData, pDynamicMethodArray); + + GCPROTECT_BEGIN(traceData); + // The number of frame info elements in the stack trace info + pData->cElements = static_cast<int>(traceData.Size()); + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // By default, assume that we have no frames from foreign exception stack trace. + pData->fDoWeHaveAnyFramesFromForeignStackTrace = FALSE; +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + // Now we know the size, allocate the information for the data struct + if (pData->cElements != 0) + { + // Allocate the memory to contain the data + pData->pElements = new DebugStackTraceElement[pData->cElements]; + + // Fill in the data + for (unsigned i = 0; i < (unsigned)pData->cElements; i++) + { + StackTraceElement const & cur = traceData[i]; + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // If we come across any frame representing foreign exception stack trace, + // then set the flag indicating so. This will be used to allocate the + // corresponding array in StackFrameHelper. + if (cur.fIsLastFrameFromForeignStackTrace) + { + pData->fDoWeHaveAnyFramesFromForeignStackTrace = TRUE; + } +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + + // Fill out the MethodDesc* + MethodDesc *pMD = cur.pFunc; + _ASSERTE(pMD); + + // Calculate the native offset + // This doesn't work for framed methods, since internal calls won't + // push frames and the method body is therefore non-contiguous. + // Currently such methods always return an IP of 0, so they're easy + // to spot. + DWORD dwNativeOffset; + + if (cur.ip) + { + dwNativeOffset = (DWORD)(cur.ip - (UINT_PTR)pMD->GetNativeCode()); + } + else + { + dwNativeOffset = 0; + } + + pData->pElements[i].InitPass1(dwNativeOffset, pMD, (PCODE) cur.ip +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + , cur.fIsLastFrameFromForeignStackTrace +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + ); +#ifndef DACCESS_COMPILE + pData->pElements[i].InitPass2(); +#endif + } + } + else + { + pData->pElements = NULL; + } + GCPROTECT_END(); + + return; +} + +// Init a stack-trace element. +// Initialization done potentially under the TSL. +void DebugStackTrace::DebugStackTraceElement::InitPass1( + DWORD dwNativeOffset, + MethodDesc *pFunc, + PCODE ip +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + , BOOL fIsLastFrameFromForeignStackTrace /*= FALSE*/ +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) +) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(pFunc != NULL); + + // May have a null IP for ecall frames. If IP is null, then dwNativeOffset should be 0 too. + _ASSERTE ( (ip != NULL) || (dwNativeOffset == 0) ); + + this->pFunc = pFunc; + this->dwOffset = dwNativeOffset; + this->ip = ip; +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + this->fIsLastFrameFromForeignStackTrace = fIsLastFrameFromForeignStackTrace; +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) +} + +#ifndef DACCESS_COMPILE + +// Initialization done outside the TSL. +// This may need to call locking operations that aren't safe under the TSL. +void DebugStackTrace::DebugStackTraceElement::InitPass2() +{ + CONTRACTL + { + MODE_ANY; + GC_TRIGGERS; + THROWS; + } + CONTRACTL_END; + + _ASSERTE(!ThreadStore::HoldingThreadStore()); + + bool bRes = false; + +#ifdef DEBUGGING_SUPPORTED + // Calculate the IL offset using the debugging services + if ((this->ip != NULL) && g_pDebugInterface) + { + bRes = g_pDebugInterface->GetILOffsetFromNative( + pFunc, (LPCBYTE) this->ip, this->dwOffset, &this->dwILOffset); + } + +#endif // !DEBUGGING_SUPPORTED + + // If there was no mapping information, then set to an invalid value + if (!bRes) + { + this->dwILOffset = (DWORD)-1; + } +} + +FCIMPL4(INT32, DebuggerAssert::ShowDefaultAssertDialog, + StringObject* strConditionUNSAFE, + StringObject* strMessageUNSAFE, + StringObject* strStackTraceUNSAFE, + StringObject* strWindowTitleUNSAFE + ) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(strConditionUNSAFE, NULL_OK)); + PRECONDITION(CheckPointer(strMessageUNSAFE, NULL_OK)); + PRECONDITION(CheckPointer(strStackTraceUNSAFE, NULL_OK)); + PRECONDITION(CheckPointer(strWindowTitleUNSAFE, NULL_OK)); + } + CONTRACTL_END; + + int result = IDRETRY; + + struct _gc { + STRINGREF strCondition; + STRINGREF strMessage; + STRINGREF strStackTrace; + STRINGREF strWindowTitle; + } gc; + + gc.strCondition = (STRINGREF) ObjectToOBJECTREF(strConditionUNSAFE); + gc.strMessage = (STRINGREF) ObjectToOBJECTREF(strMessageUNSAFE); + gc.strStackTrace = (STRINGREF) ObjectToOBJECTREF(strStackTraceUNSAFE); + gc.strWindowTitle = (STRINGREF) ObjectToOBJECTREF(strWindowTitleUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + StackSString condition; + StackSString message; + StackSString stackTrace; + StackSString windowTitle; + + if (gc.strCondition != NULL) + gc.strCondition->GetSString(condition); + if (gc.strMessage != NULL) + gc.strMessage->GetSString(message); + if (gc.strStackTrace != NULL) + gc.strStackTrace->GetSString(stackTrace); + if (gc.strWindowTitle != NULL) + gc.strWindowTitle->GetSString(windowTitle); + + StackSString msgText; + if (gc.strCondition != NULL) { + msgText.Append(W("Expression: ")); + msgText.Append(condition); + msgText.Append(W("\n")); + } + msgText.Append(W("Description: ")); + msgText.Append(message); + + StackSString stackTraceText; + if (gc.strStackTrace != NULL) { + stackTraceText.Append(W("Stack Trace:\n")); + stackTraceText.Append(stackTrace); + } + + if (gc.strWindowTitle == NULL) { + windowTitle.Set(W("Assert Failure")); + } + + // We're taking a string from managed code, and we can't be sure it doesn't have stuff like %s or \n in it. + // So, pass a format string of %s and pass the text as a vararg to our message box method. + // Also, varargs and StackSString don't mix. Convert to string first. + const WCHAR* msgTextAsUnicode = msgText.GetUnicode(); + result = EEMessageBoxNonLocalizedNonFatal(W("%s"), windowTitle, stackTraceText, MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION, msgTextAsUnicode); + + // map the user's choice to the values recognized by + // the System.Diagnostics.Assert package + if (result == IDRETRY) + { + result = FailDebug; + } + else if (result == IDIGNORE) + { + result = FailIgnore; + } + else + { + result = FailTerminate; + } + + HELPER_METHOD_FRAME_END(); + return result; +} +FCIMPLEND + + +FCIMPL1( void, Log::AddLogSwitch, + LogSwitchObject* logSwitchUNSAFE + ) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(logSwitchUNSAFE)); + } + CONTRACTL_END; + + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + HRESULT hresult = S_OK; + + struct _gc { + LOGSWITCHREF m_LogSwitch; + STRINGREF Name; + OBJECTREF tempObj; + STRINGREF strrefParentName; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + gc.m_LogSwitch = (LOGSWITCHREF)ObjectToOBJECTREF(logSwitchUNSAFE); + + // From the given args, extract the LogSwitch name + gc.Name = ((LogSwitchObject*) OBJECTREFToObject(gc.m_LogSwitch))->GetName(); + + _ASSERTE( gc.Name != NULL ); + WCHAR *pstrCategoryName = NULL; + int iCategoryLength = 0; + WCHAR wszParentName [MAX_LOG_SWITCH_NAME_LEN+1]; + WCHAR wszSwitchName [MAX_LOG_SWITCH_NAME_LEN+1]; + wszParentName [0] = W('\0'); + wszSwitchName [0] = W('\0'); + + // extract the (WCHAR) name from the STRINGREF object + gc.Name->RefInterpretGetStringValuesDangerousForGC(&pstrCategoryName, &iCategoryLength); + + _ASSERTE (iCategoryLength > 0); + wcsncpy_s(wszSwitchName, COUNTOF(wszSwitchName), pstrCategoryName, _TRUNCATE); + + // check if an entry with this name already exists in the hash table. + // Duplicates are not allowed. + // <REVISIT_TODO>: access to the hashtable is not synchronized!</REVISIT_TODO> + if(g_sLogHashTable.GetEntryFromHashTable(pstrCategoryName) != NULL) + { + hresult = TYPE_E_DUPLICATEID; + } + else + { + // Create a strong reference handle to the LogSwitch object + OBJECTHANDLE ObjHandle = pThread->GetDomain()->CreateStrongHandle(NULL); + StoreObjectInHandle(ObjHandle, ObjectToOBJECTREF(gc.m_LogSwitch)); + // Use ObjectFromHandle(ObjHandle) to get back the object. + + hresult = g_sLogHashTable.AddEntryToHashTable(pstrCategoryName, ObjHandle); + + // If we failed to insert this into the hash table, destroy the handle so + // that we don't leak it. + if (FAILED(hresult)) + { + ::DestroyStrongHandle(ObjHandle); + } + +#ifdef DEBUGGING_SUPPORTED + if (hresult == S_OK) + { + // tell the attached debugger about this switch + if (CORDebuggerAttached()) + { + int iLevel = gc.m_LogSwitch->GetLevel(); + WCHAR *pstrParentName = NULL; + int iParentNameLength = 0; + + gc.tempObj = gc.m_LogSwitch->GetParent(); + + LogSwitchObject* pParent = (LogSwitchObject*) OBJECTREFToObject( gc.tempObj ); + + if (pParent != NULL) + { + // From the given args, extract the ParentLogSwitch's name + gc.strrefParentName = pParent->GetName(); + + // extract the (WCHAR) name from the STRINGREF object + gc.strrefParentName->RefInterpretGetStringValuesDangerousForGC(&pstrParentName, &iParentNameLength ); + + if (iParentNameLength > MAX_LOG_SWITCH_NAME_LEN) + { + wcsncpy_s (wszParentName, COUNTOF(wszParentName), pstrParentName, _TRUNCATE); + } + else + { + wcscpy_s (wszParentName, COUNTOF(wszParentName), pstrParentName); + } + } + + g_pDebugInterface->SendLogSwitchSetting (iLevel, SWITCH_CREATE, wszSwitchName, wszParentName ); + } + } +#endif // DEBUGGING_SUPPORTED + } + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +FCIMPL3(void, Log::ModifyLogSwitch, + INT32 Level, + StringObject* strLogSwitchNameUNSAFE, + StringObject* strParentNameUNSAFE + ) +{ + CONTRACTL + { + FCALL_CHECK; + PRECONDITION(CheckPointer(strLogSwitchNameUNSAFE)); + PRECONDITION(CheckPointer(strParentNameUNSAFE)); + } + CONTRACTL_END; + + STRINGREF strLogSwitchName = (STRINGREF) ObjectToOBJECTREF(strLogSwitchNameUNSAFE); + STRINGREF strParentName = (STRINGREF) ObjectToOBJECTREF(strParentNameUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_2(strLogSwitchName, strParentName); + + _ASSERTE (strLogSwitchName != NULL); + + WCHAR *pstrLogSwitchName = NULL; + WCHAR *pstrParentName = NULL; + int iSwitchNameLength = 0; + int iParentNameLength = 0; + WCHAR wszParentName [MAX_LOG_SWITCH_NAME_LEN+1]; + WCHAR wszSwitchName [MAX_LOG_SWITCH_NAME_LEN+1]; + wszParentName [0] = W('\0'); + wszSwitchName [0] = W('\0'); + + // extract the (WCHAR) name from the STRINGREF object + strLogSwitchName->RefInterpretGetStringValuesDangerousForGC ( + &pstrLogSwitchName, + &iSwitchNameLength); + + if (iSwitchNameLength > MAX_LOG_SWITCH_NAME_LEN) + { + wcsncpy_s (wszSwitchName, COUNTOF(wszSwitchName), pstrLogSwitchName, _TRUNCATE); + } + else + { + wcscpy_s (wszSwitchName, COUNTOF(wszSwitchName), pstrLogSwitchName); + } + + // extract the (WCHAR) name from the STRINGREF object + strParentName->RefInterpretGetStringValuesDangerousForGC ( + &pstrParentName, + &iParentNameLength); + + if (iParentNameLength > MAX_LOG_SWITCH_NAME_LEN) + { + wcsncpy_s (wszParentName, COUNTOF(wszParentName), pstrParentName, _TRUNCATE); + } + else + { + wcscpy_s (wszParentName, COUNTOF(wszParentName), pstrParentName); + } + +#ifdef DEBUGGING_SUPPORTED + if (g_pDebugInterface) + { + g_pDebugInterface->SendLogSwitchSetting (Level, + SWITCH_MODIFY, + wszSwitchName, + wszParentName + ); + } +#endif // DEBUGGING_SUPPORTED + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +void Log::DebuggerModifyingLogSwitch (int iNewLevel, + const WCHAR *pLogSwitchName + ) +{ + CONTRACTL + { + GC_NOTRIGGER; + } + CONTRACTL_END; + + // check if an entry with this name exists in the hash table. + OBJECTHANDLE ObjHandle = g_sLogHashTable.GetEntryFromHashTable (pLogSwitchName); + if ( ObjHandle != NULL) + { + OBJECTREF obj = ObjectFromHandle (ObjHandle); + LogSwitchObject *pLogSwitch = (LogSwitchObject *)(OBJECTREFToObject (obj)); + + pLogSwitch->SetLevel (iNewLevel); + } +} + + +// Note: Caller should ensure that it's not adding a duplicate +// entry by calling GetEntryFromHashTable before calling this +// function. +HRESULT LogHashTable::AddEntryToHashTable (const WCHAR *pKey, + OBJECTHANDLE pData + ) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HashElement *pElement; + + // check that the length is non-zero + if (pKey == NULL) + { + return (E_INVALIDARG); + } + + int iHashKey = 0; + int iLength = (int)wcslen (pKey); + + for (int i= 0; i<iLength; i++) + { + iHashKey += pKey [i]; + } + + iHashKey = iHashKey % MAX_HASH_BUCKETS; + + // Create a new HashElement. This throws on oom, nothing to cleanup. + pElement = new HashElement; + + pElement->SetData (pData, pKey); + + if (m_Buckets [iHashKey] == NULL) + { + m_Buckets [iHashKey] = pElement; + } + else + { + pElement->SetNext (m_Buckets [iHashKey]); + m_Buckets [iHashKey] = pElement; + } + + return S_OK; +} + + +OBJECTHANDLE LogHashTable::GetEntryFromHashTable (const WCHAR *pKey) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (pKey == NULL) + { + return NULL; + } + + int iHashKey = 0; + int iLength = (int)wcslen (pKey); + + // Calculate the hash value of the given key + for (int i= 0; i<iLength; i++) + { + iHashKey += pKey [i]; + } + + iHashKey = iHashKey % MAX_HASH_BUCKETS; + + HashElement *pElement = m_Buckets [iHashKey]; + + // Find and return the data + while (pElement != NULL) + { + if (wcscmp(pElement->GetKey(), pKey) == 0) + { + return (pElement->GetData()); + } + + pElement = pElement->GetNext(); + } + + return NULL; +} + +// +// Returns a textual representation of the current stack trace. The format of the stack +// trace is the same as returned by StackTrace.ToString. +// +void GetManagedStackTraceString(BOOL fNeedFileInfo, SString &result) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Switch to cooperative GC mode before we call into managed code. + GCX_COOP(); + + MethodDescCallSite managedHelper(METHOD__STACK_TRACE__GET_MANAGED_STACK_TRACE_HELPER); + ARG_SLOT args[] = + { + BoolToArgSlot(fNeedFileInfo) + }; + + STRINGREF resultStringRef = (STRINGREF) managedHelper.Call_RetOBJECTREF(args); + resultStringRef->GetSString(result); +} + +#endif // !DACCESS_COMPILE |