// 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. #include "common.h" /*******************************************************************/ /* The following routines used to exist in all builds so they could called from the * debugger before we had strike. * Now most of them are only included in debug builds for diagnostics purposes. */ /*******************************************************************/ #include "stdlib.h" BOOL isMemoryReadable(const TADDR start, unsigned len) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; } CONTRACTL_END; #if !defined(DACCESS_COMPILE) && defined(FEATURE_PAL) return PAL_ProbeMemory((PVOID)start, len, FALSE); #else // !DACCESS_COMPILE && FEATURE_PAL // // To accomplish this in a no-throw way, we have to touch each and every page // and see if it is in memory or not. // // // Touch the first and last bytes. // char buff; #ifdef DACCESS_COMPILE if (DacReadAll(start, &buff, 1, false) != S_OK) { return 0; } #else if (ReadProcessMemory(GetCurrentProcess(), (PVOID)start, &buff, 1, 0) == 0) { return 0; } #endif TADDR location; location = start + (len - 1); #ifdef DACCESS_COMPILE if (DacReadAll(location, &buff, 1, false) != S_OK) { return 0; } #else if (ReadProcessMemory(GetCurrentProcess(), (PVOID)location, &buff, 1, 0) == 0) { return 0; } #endif // // Now we have to loop thru each and every page in between and touch them. // location = start; while (len > GetOsPageSize()) { location += GetOsPageSize(); len -= GetOsPageSize(); #ifdef DACCESS_COMPILE if (DacReadAll(location, &buff, 1, false) != S_OK) { return 0; } #else if (ReadProcessMemory(GetCurrentProcess(), (PVOID)location, &buff, 1, 0) == 0) { return 0; } #endif } return 1; #endif // !DACCESS_COMPILE && FEATURE_PAL } /*******************************************************************/ /* check to see if 'retAddr' is a valid return address (it points to someplace that has a 'call' right before it), If possible it is it returns the address that was called in whereCalled */ bool isRetAddr(TADDR retAddr, TADDR* whereCalled) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_NOT_MAINLINE; } CONTRACTL_END; // don't waste time values clearly out of range if (retAddr < (TADDR)BOT_MEMORY || retAddr > (TADDR)TOP_MEMORY) { return false; } PTR_BYTE spot = PTR_BYTE(retAddr); if (!isMemoryReadable(dac_cast(spot) - 7, 7)) { return(false); } // Note this is possible to be spoofed, but pretty unlikely *whereCalled = 0; // call XXXXXXXX if (spot[-5] == 0xE8) { *whereCalled = *(PTR_DWORD(retAddr - 4)) + retAddr; return(true); } // call [XXXXXXXX] if (spot[-6] == 0xFF && (spot[-5] == 025)) { if (isMemoryReadable(*(PTR_TADDR(retAddr - 4)), 4)) { *whereCalled = *(PTR_TADDR(*(PTR_TADDR(retAddr - 4)))); return(true); } } // call [REG+XX] if (spot[-3] == 0xFF && (spot[-2] & ~7) == 0120 && (spot[-2] & 7) != 4) { return(true); } if (spot[-4] == 0xFF && spot[-3] == 0124) // call [ESP+XX] { return(true); } // call [REG+XXXX] if (spot[-6] == 0xFF && (spot[-5] & ~7) == 0220 && (spot[-5] & 7) != 4) { return(true); } if (spot[-7] == 0xFF && spot[-6] == 0224) // call [ESP+XXXX] { return(true); } // call [REG] if (spot[-2] == 0xFF && (spot[-1] & ~7) == 0020 && (spot[-1] & 7) != 4 && (spot[-1] & 7) != 5) { return(true); } // call REG if (spot[-2] == 0xFF && (spot[-1] & ~7) == 0320 && (spot[-1] & 7) != 4) { return(true); } // There are other cases, but I don't believe they are used. return(false); } /* * The remaining methods are included in debug builds only */ #ifdef _DEBUG #ifndef DACCESS_COMPILE void *DumpEnvironmentBlock(void) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; LPTSTR lpszVariable; lpszVariable = (LPTSTR)WszGetEnvironmentStrings(); while (*lpszVariable) { fprintf(stderr, "%c", *lpszVariable++); } fprintf(stderr, "\n"); return WszGetEnvironmentStrings(); } #if defined(_TARGET_X86_) && !defined(FEATURE_PAL) /*******************************************************************/ // Dump the SEH chain to stderr void PrintSEHChain(void) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; EXCEPTION_REGISTRATION_RECORD* pEHR = GetCurrentSEHRecord(); while (pEHR != NULL && pEHR != EXCEPTION_CHAIN_END) { fprintf(stderr, "pEHR:0x%x Handler:0x%x\n", (size_t)pEHR, (size_t)pEHR->Handler); pEHR = pEHR->Next; } } #endif // _TARGET_X86_ /*******************************************************************/ MethodDesc* IP2MD(ULONG_PTR IP) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return ExecutionManager::GetCodeMethodDesc((PCODE)IP); } /*******************************************************************/ /* if addr is a valid method table, return a pointer to it */ MethodTable* AsMethodTable(size_t addr) { CONTRACTL { NOTHROW; GC_NOTRIGGER; DEBUG_ONLY; } CONTRACTL_END; MethodTable* pValidMT = NULL; EX_TRY { MethodTable* pMT = (MethodTable*) addr; if (isMemoryReadable((TADDR)pMT, sizeof(MethodTable))) { EEClass* cls = pMT->GetClass_NoLogging(); if (isMemoryReadable((TADDR)cls, sizeof(EEClass)) && (cls->GetMethodTable() == pMT)) { pValidMT = pMT; } } } EX_CATCH { } EX_END_CATCH(SwallowAllExceptions) return(pValidMT); } /*******************************************************************/ /* if addr is a valid method table, return a pointer to it */ MethodDesc* AsMethodDesc(size_t addr) { CONTRACTL { NOTHROW; GC_NOTRIGGER; DEBUG_ONLY; } CONTRACTL_END; if (!IS_ALIGNED(addr, sizeof(void*))) return(0); MethodDesc* pValidMD = NULL; // We try to avoid the most AVs by explicitit calls to isMemoryReadable below, but rare cases can still get through // if we are unlucky. AVInRuntimeImplOkayHolder AVOkay; EX_TRY { MethodDesc* pMD = (MethodDesc*) addr; if (isMemoryReadable((TADDR)pMD, sizeof(MethodDesc))) { MethodDescChunk *chunk = pMD->GetMethodDescChunk(); if (isMemoryReadable((TADDR)chunk, sizeof(MethodDescChunk))) { RelativeFixupPointer * ppMT = chunk->GetMethodTablePtr(); // The MethodTable is stored as a RelativeFixupPointer which does an // extra indirection if the address is tagged (the low bit is set). // That could AV if we don't check it first. if (!ppMT->IsTagged((TADDR)ppMT) || isMemoryReadable((TADDR)ppMT->GetValuePtr(), sizeof(MethodTable*))) { if (AsMethodTable((size_t)RelativeFixupPointer::GetValueAtPtr((TADDR)ppMT)) != 0) { pValidMD = pMD; } } } } } EX_CATCH { } EX_END_CATCH(SwallowAllExceptions) return(pValidMD); } // This function will return NULL if the buffer is not large enough. /*******************************************************************/ wchar_t* formatMethodTable(MethodTable* pMT, __out_z __inout_ecount(bufSize) wchar_t* buff, DWORD bufSize) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; if(bufSize == 0) { return NULL; } buff[ bufSize - 1] = W('\0'); DefineFullyQualifiedNameForClass(); LPCUTF8 clsName = GetFullyQualifiedNameForClass(pMT); if (clsName != 0) { if(_snwprintf_s(buff, bufSize - 1, _TRUNCATE, W("%S"), clsName) < 0) { return NULL; } buff[ bufSize - 1] = W('\0'); } return(buff); } /*******************************************************************/ // This function will return NULL if the buffer is not large enough, otherwise it will // return the buffer position for next write. /*******************************************************************/ wchar_t* formatMethodDesc(MethodDesc* pMD, __out_z __inout_ecount(bufSize) wchar_t* buff, DWORD bufSize) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; if(bufSize == 0) { return NULL; } buff = formatMethodTable(pMD->GetMethodTable(), buff, bufSize); if(buff == NULL) { return NULL; } buff[bufSize - 1] = W('\0'); // this will guarantee the buffer is also NULL-terminated if(_snwprintf_s( &buff[lstrlenW(buff)] , bufSize -lstrlenW(buff) - 1, _TRUNCATE, W("::%S"), pMD->GetName()) < 0) { return NULL; } #ifdef _DEBUG if (pMD->m_pszDebugMethodSignature) { if(_snwprintf_s(&buff[lstrlenW(buff)], bufSize -lstrlenW(buff) - 1, _TRUNCATE, W(" %S"), pMD->m_pszDebugMethodSignature) < 0) { return NULL; } } #endif if(_snwprintf_s(&buff[lstrlenW(buff)], bufSize -lstrlenW(buff) - 1, _TRUNCATE, W("(%x)"), (size_t)pMD) < 0) { return NULL; } return(buff); } /*******************************************************************/ /* dump the stack, pretty printing IL methods if possible. This routine is very robust. It will never cause an access violation and it always find return addresses if they are on the stack (it may find some spurious ones however). */ int dumpStack(BYTE* topOfStack, unsigned len) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; size_t* top = (size_t*) topOfStack; size_t* end = (size_t*) &topOfStack[len]; size_t* ptr = (size_t*) (((size_t) top) & ~3); // make certain dword aligned. TADDR whereCalled; WszOutputDebugString(W("***************************************************\n")); CQuickBytes qb; int nLen = MAX_CLASSNAME_LENGTH * 4 + 400; // this should be enough wchar_t *buff = (wchar_t *) qb.AllocThrows(nLen * sizeof(wchar_t)); while (ptr < end) { buff[nLen - 1] = W('\0'); wchar_t* buffPtr = buff; // stop if we hit unmapped pages if (!isMemoryReadable((TADDR)ptr, sizeof(TADDR))) { break; } if (isRetAddr((TADDR)*ptr, &whereCalled)) { if (_snwprintf_s(buffPtr, buff+NumItems(buff)-buffPtr-1, _TRUNCATE, W("STK[%08X] = %08X "), (size_t)ptr, *ptr) <0) { return(0); } buffPtr += lstrlenW(buffPtr); const wchar_t* kind = W("RETADDR "); // Is this a stub (is the return address a MethodDesc? MethodDesc* ftn = AsMethodDesc(*ptr); if (ftn != 0) { kind = W(" MD PARAM"); // If another true return address is not directly before it, it is just // a methodDesc param. TADDR prevRetAddr = ptr[1]; if (isRetAddr(prevRetAddr, &whereCalled) && AsMethodDesc(prevRetAddr) == 0) { kind = W("STUBCALL"); } else { // Is it the magic sequence used by CallDescr? if (isMemoryReadable(prevRetAddr - sizeof(short), sizeof(short)) && ((short*) prevRetAddr)[-1] == 0x5A59) // Pop ECX POP EDX { kind = W("STUBCALL"); } } } else // Is it some other code the EE knows about? { ftn = ExecutionManager::GetCodeMethodDesc((PCODE)(*ptr)); } if(_snwprintf_s(buffPtr, buff+ nLen -buffPtr-1, _TRUNCATE, W("%s "), kind) < 0) { return(0); } buffPtr += lstrlenW(buffPtr); if (ftn != 0) { // buffer is not large enough if( formatMethodDesc(ftn, buffPtr, static_cast(buff+ nLen -buffPtr-1)) == NULL) { return(0); } buffPtr += lstrlenW(buffPtr); } else { wcsncpy_s(buffPtr, nLen - (buffPtr - buff), W(""), _TRUNCATE); buffPtr += lstrlenW(buffPtr); } if (whereCalled != 0) { if(_snwprintf_s(buffPtr, buff+ nLen -buffPtr-1, _TRUNCATE, W(" Caller called Entry %X"), whereCalled) <0) { return(0); } buffPtr += lstrlenW(buffPtr); } wcsncpy_s(buffPtr, nLen - (buffPtr - buff), W("\n"), _TRUNCATE); buffPtr += lstrlenW(buffPtr); WszOutputDebugString(buff); } MethodTable* pMT = AsMethodTable(*ptr); if (pMT != 0) { buffPtr = buff; if( _snwprintf_s(buffPtr, buff+ nLen -buffPtr-1, _TRUNCATE, W("STK[%08X] = %08X MT PARAM "), (size_t)ptr, *ptr ) <0) { return(0); } buffPtr += lstrlenW(buffPtr); if( formatMethodTable(pMT, buffPtr, static_cast(buff+ nLen -buffPtr-1)) == NULL) { return(0); } buffPtr += lstrlenW(buffPtr); wcsncpy_s(buffPtr, nLen - (buffPtr - buff), W("\n"), _TRUNCATE); WszOutputDebugString(buff); } ptr++; } // while return(0); } /*******************************************************************/ /* dump the stack from the current ESP. Stop when we reach a 64K boundary */ int DumpCurrentStack() { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; #ifdef _TARGET_X86_ BYTE* top = (BYTE *)GetCurrentSP(); // go back at most 64K, it will stop if we go off the // top to unmapped memory return(dumpStack(top, 0xFFFF)); #else _ASSERTE(!"@NYI - DumpCurrentStack(DebugHelp.cpp)"); return 0; #endif // _TARGET_X86_ } /*******************************************************************/ WCHAR* StringVal(STRINGREF objref) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; return(objref->GetBuffer()); } LPCUTF8 NameForMethodTable(UINT_PTR pMT) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; DefineFullyQualifiedNameForClass(); LPCUTF8 clsName = GetFullyQualifiedNameForClass(((MethodTable*)pMT)); // Note we're returning local stack space - this should be OK for using in the debugger though return clsName; } LPCUTF8 ClassNameForObject(UINT_PTR obj) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return(NameForMethodTable((UINT_PTR)(((Object*)obj)->GetMethodTable()))); } LPCUTF8 ClassNameForOBJECTREF(OBJECTREF obj) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return(ClassNameForObject((UINT_PTR)(OBJECTREFToObject(obj)))); } LPCUTF8 NameForMethodDesc(UINT_PTR pMD) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return(((MethodDesc*)pMD)->GetName()); } LPCUTF8 ClassNameForMethodDesc(UINT_PTR pMD) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; DefineFullyQualifiedNameForClass (); return GetFullyQualifiedNameForClass(((MethodDesc*)pMD)->GetMethodTable()); } PCCOR_SIGNATURE RawSigForMethodDesc(MethodDesc* pMD) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return(pMD->GetSig()); } Thread * CurrentThreadInfo () { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return GetThread (); } AppDomain *GetAppDomainForObject(UINT_PTR obj) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return ((Object*)obj)->GetAppDomain(); } ADIndex GetAppDomainIndexForObject(UINT_PTR obj) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return ((Object*)obj)->GetHeader()->GetAppDomainIndex(); } AppDomain *GetAppDomainForObjectHeader(UINT_PTR hdr) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; ADIndex indx = ((ObjHeader*)hdr)->GetAppDomainIndex(); if (!indx.m_dwIndex) { return NULL; } return SystemDomain::GetAppDomainAtIndex(indx); } ADIndex GetAppDomainIndexForObjectHeader(UINT_PTR hdr) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return ((ObjHeader*)hdr)->GetAppDomainIndex(); } SyncBlock *GetSyncBlockForObject(UINT_PTR obj) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return ((Object*)obj)->GetHeader()->PassiveGetSyncBlock(); } /*******************************************************************/ void PrintMethodTable(UINT_PTR pMT) { CONTRACTL { NOTHROW; GC_TRIGGERS; } CONTRACTL_END; MethodTable * p = (MethodTable *)pMT; DefineFullyQualifiedNameForClass(); LPCUTF8 name = GetFullyQualifiedNameForClass(p); p->DebugDumpVtable(name, true); p->DebugDumpFieldLayout(name, true); p->DebugDumpGCDesc(name, true); } void PrintTableForMethodDesc(UINT_PTR pMD) { CONTRACTL { NOTHROW; GC_TRIGGERS; } CONTRACTL_END; PrintMethodTable((UINT_PTR) ((MethodDesc *)pMD)->GetMethodTable() ); } void PrintException(OBJECTREF pObjectRef) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; if(pObjectRef == NULL) { return; } GCPROTECT_BEGIN(pObjectRef); if (!IsException(pObjectRef->GetMethodTable())) { printf("Specified object is not an exception object.\n"); } else { MethodDescCallSite internalToString(METHOD__EXCEPTION__INTERNAL_TO_STRING, &pObjectRef); ARG_SLOT arg[1] = { ObjToArgSlot(pObjectRef) }; STRINGREF str = internalToString.Call_RetSTRINGREF(arg); if(str->GetBuffer() != NULL) { WszOutputDebugString(str->GetBuffer()); } } GCPROTECT_END(); } void PrintException(UINT_PTR pObject) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; OBJECTREF pObjectRef = NULL; GCPROTECT_BEGIN(pObjectRef); GCPROTECT_END(); } /*******************************************************************/ /* sends a current stack trace to the debug window */ const char* FormatSig(MethodDesc* pMD, AppDomain *pDomain, AllocMemTracker *pamTracker); struct PrintCallbackData { BOOL toStdout; BOOL withAppDomain; #ifdef _DEBUG BOOL toLOG; #endif }; StackWalkAction PrintStackTraceCallback(CrawlFrame* pCF, VOID* pData) { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; CONTRACT_VIOLATION(ThrowsViolation); MethodDesc* pMD = pCF->GetFunction(); const int nLen = 2048 - 1; // keep one character for "\n" wchar_t *buff = (wchar_t*)alloca((nLen + 1) * sizeof(wchar_t)); buff[0] = 0; buff[nLen-1] = W('\0'); // make sure the buffer is always NULL-terminated PrintCallbackData *pCBD = (PrintCallbackData *)pData; if (pMD != 0) { MethodTable * pMT = pMD->GetMethodTable(); if (pCBD->withAppDomain) { if(_snwprintf_s(&buff[lstrlenW(buff)], nLen -lstrlenW(buff) - 1, _TRUNCATE, W("{[%3.3x] %s} "), pCF->GetAppDomain()->GetId().m_dwId, pCF->GetAppDomain()->GetFriendlyName(FALSE)) < 0) { return SWA_CONTINUE; } } DefineFullyQualifiedNameForClass(); LPCUTF8 clsName = GetFullyQualifiedNameForClass(pMT); if (clsName != 0) { if(_snwprintf_s(&buff[lstrlenW(buff)], nLen -lstrlenW(buff) - 1, _TRUNCATE, W("%S::"), clsName) < 0) { return SWA_CONTINUE; } } // This prematurely suppressrelease'd AmTracker will leak any memory allocated by FormatSig. // But this routine is diagnostic aid, not customer-reachable so we won't bother to plug. AllocMemTracker dummyAmTracker; int buffLen = _snwprintf_s(&buff[lstrlenW(buff)], nLen -lstrlenW(buff) - 1, _TRUNCATE, W("%S %S "), pMD->GetName(), FormatSig(pMD, pCF->GetAppDomain(), &dummyAmTracker)); dummyAmTracker.SuppressRelease(); if (buffLen < 0 ) { return SWA_CONTINUE; } if (pCF->IsFrameless() && pCF->GetJitManager() != 0) { PREGDISPLAY regs = pCF->GetRegisterSet(); DWORD offset = pCF->GetRelOffset(); TADDR start = pCF->GetCodeInfo()->GetStartAddress(); if(_snwprintf_s(&buff[lstrlenW(buff)], nLen -lstrlenW(buff) - 1, _TRUNCATE, W("JIT ESP:%X MethStart:%X EIP:%X(rel %X)"), (size_t)GetRegdisplaySP(regs), (size_t)start, (size_t)GetControlPC(regs), offset) < 0) { return SWA_CONTINUE; } } else { if(_snwprintf_s(&buff[lstrlenW(buff)], nLen -lstrlenW(buff) - 1, _TRUNCATE, W("EE implemented")) < 0) { return SWA_CONTINUE; } } } else { Frame* frame = pCF->GetFrame(); if(_snwprintf_s(&buff[lstrlenW(buff)], nLen -lstrlenW(buff) - 1, _TRUNCATE, W("EE Frame is") LFMT_ADDR, (size_t)DBG_ADDR(frame)) < 0) { return SWA_CONTINUE; } } if (pCBD->toStdout) { wcscat_s(buff, nLen + 1, W("\n")); PrintToStdOutW(buff); } #ifdef _DEBUG else if (pCBD->toLOG) { MAKE_ANSIPTR_FROMWIDE(sbuff, buff); // For LogSpewAlways to work rightr the "\n" (newline) // must be in the fmt string not part of the args LogSpewAlways(" %s\n", sbuff); } #endif else { wcscat_s(buff, nLen + 1, W("\n")); WszOutputDebugString(buff); } return SWA_CONTINUE; } void PrintStackTrace() { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; WszOutputDebugString(W("***************************************************\n")); PrintCallbackData cbd = {0, 0}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } void PrintStackTraceToStdout() { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; PrintCallbackData cbd = {1, 0}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } #ifdef _DEBUG void PrintStackTraceToLog() { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; PrintCallbackData cbd = {0, 0, 1}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } #endif void PrintStackTraceWithAD() { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; WszOutputDebugString(W("***************************************************\n")); PrintCallbackData cbd = {0, 1}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } void PrintStackTraceWithADToStdout() { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; PrintCallbackData cbd = {1, 1}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } #ifdef _DEBUG void PrintStackTraceWithADToLog() { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; PrintCallbackData cbd = {0, 1, 1}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } void PrintStackTraceWithADToLog(Thread *pThread) { CONTRACTL { DISABLED(NOTHROW); DISABLED(GC_TRIGGERS); } CONTRACTL_END; PrintCallbackData cbd = {0, 1, 1}; pThread->StackWalkFrames(PrintStackTraceCallback, &cbd, ALLOW_ASYNC_STACK_WALK, 0); } #endif /*******************************************************************/ // Get the system or current domain from the thread. BaseDomain* GetSystemDomain() { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return SystemDomain::System(); } AppDomain* GetCurrentDomain() { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; return SystemDomain::GetCurrentDomain(); } void PrintDomainName(size_t ob) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; AppDomain* dm = (AppDomain*) ob; LPCWSTR st = dm->GetFriendlyName(FALSE); if(st != NULL) { WszOutputDebugString(st); } else { WszOutputDebugString(W("")); } } #if defined(_TARGET_X86_) #include "gcdump.h" #include "../gcdump/i386/gcdumpx86.cpp" #include "../gcdump/gcdump.cpp" /*********************************************************************/ void printfToDbgOut(const char* fmt, ...) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; va_list args; va_start(args, fmt); char buffer[4096]; _vsnprintf_s(buffer, COUNTOF(buffer), _TRUNCATE, fmt, args); va_end(args); OutputDebugStringA( buffer ); } void DumpGCInfo(MethodDesc* method) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; PCODE methodStart = method->GetNativeCode(); if (methodStart == 0) { return; } EECodeInfo codeInfo(methodStart); _ASSERTE(codeInfo.GetRelOffset() == 0); ICodeManager* codeMan = codeInfo.GetCodeManager(); GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); unsigned methodSize = (unsigned)codeMan->GetFunctionSize(gcInfoToken); GCDump gcDump(gcInfoToken.Version); PTR_CBYTE gcInfo = PTR_CBYTE(gcInfoToken.Info); gcDump.gcPrintf = printfToDbgOut; InfoHdr header; printfToDbgOut ("Method info block:\n"); gcInfo += gcDump.DumpInfoHdr(gcInfo, &header, &methodSize, 0); printfToDbgOut ("\n"); printfToDbgOut ("Pointer table:\n"); gcInfo += gcDump.DumpGCTable(gcInfo, header, methodSize, 0); } void DumpGCInfoMD(size_t method) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; DumpGCInfo((MethodDesc*) method); } #endif #ifdef LOGGING void LogStackTrace() { WRAPPER_NO_CONTRACT; PrintCallbackData cbd = {0, 0, 1}; GetThread()->StackWalkFrames(PrintStackTraceCallback, &cbd,ALLOW_ASYNC_STACK_WALK, 0); } #endif #endif // #ifndef DACCESS_COMPILE #endif //_DEBUG