diff options
Diffstat (limited to 'src/ToolBox/SOS/Strike/util.cpp')
-rw-r--r-- | src/ToolBox/SOS/Strike/util.cpp | 6975 |
1 files changed, 6975 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp new file mode 100644 index 0000000000..9eec76e42c --- /dev/null +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -0,0 +1,6975 @@ +// 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 "sos.h" +#include "disasm.h" +#include <dbghelp.h> + +#include "corhdr.h" +#include "cor.h" +#include "dacprivate.h" +#include "sospriv.h" +#include "corerror.h" +#include "safemath.h" + +#include <psapi.h> +#include <cordebug.h> +#include <xcordebug.h> +#include <metahost.h> +#include <mscoree.h> +#include <tchar.h> +#include "debugshim.h" + +#ifdef FEATURE_PAL +#include "datatarget.h" +#endif // FEATURE_PAL +#include "gcinfo.h" + +#ifndef STRESS_LOG +#define STRESS_LOG +#endif // STRESS_LOG +#define STRESS_LOG_READONLY +#include "stresslog.h" + +#ifndef FEATURE_PAL +#define MAX_SYMBOL_LEN 4096 +#define SYM_BUFFER_SIZE (sizeof(IMAGEHLP_SYMBOL) + MAX_SYMBOL_LEN) +char symBuffer[SYM_BUFFER_SIZE]; +PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL) symBuffer; +#else +#include <sys/stat.h> +#include <coreruncommon.h> +#include <dlfcn.h> +#endif // !FEATURE_PAL + +#include <coreclrhost.h> +#include <set> + +LoadSymbolsForModuleDelegate SymbolReader::loadSymbolsForModuleDelegate; +DisposeDelegate SymbolReader::disposeDelegate; +ResolveSequencePointDelegate SymbolReader::resolveSequencePointDelegate; +GetLocalVariableName SymbolReader::getLocalVariableNameDelegate; +GetLineByILOffsetDelegate SymbolReader::getLineByILOffsetDelegate; + +const char * const CorElementTypeName[ELEMENT_TYPE_MAX]= +{ +#define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv) c, +#include "cortypeinfo.h" +#undef TYPEINFO +}; + +const char * const CorElementTypeNamespace[ELEMENT_TYPE_MAX]= +{ +#define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv) ns, +#include "cortypeinfo.h" +#undef TYPEINFO +}; + +IXCLRDataProcess *g_clrData = NULL; +ISOSDacInterface *g_sos = NULL; +ICorDebugProcess *g_pCorDebugProcess = NULL; + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) +#endif + +#ifndef IfFailGoto +#define IfFailGoto(EXPR, label) do { Status = (EXPR); if(FAILED(Status)) { goto label; } } while (0) +#endif // IfFailGoto + +#ifndef IfFailGo +#define IfFailGo(EXPR) IfFailGoto(EXPR, Error) +#endif // IfFailGo + +// Max number of reverted rejit versions that !dumpmd and !ip2md will print +const UINT kcMaxRevertedRejitData = 10; + +#ifndef FEATURE_PAL + +// ensure we always allocate on the process heap +void* __cdecl operator new(size_t size) throw() +{ return HeapAlloc(GetProcessHeap(), 0, size); } +void __cdecl operator delete(void* pObj) throw() +{ HeapFree(GetProcessHeap(), 0, pObj); } + +void* __cdecl operator new[](size_t size) throw() +{ return HeapAlloc(GetProcessHeap(), 0, size); } +void __cdecl operator delete[](void* pObj) throw() +{ HeapFree(GetProcessHeap(), 0, pObj); } + +/**********************************************************************\ +* Here we define types and functions that support custom COM * +* activation rules, as defined by the CIOptions enum. * +* * +\**********************************************************************/ + +typedef unsigned __int64 QWORD; + +namespace com_activation +{ + // + // Forward declarations for the implementation methods + // + + HRESULT CreateInstanceCustomImpl( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf); + HRESULT ClrCreateInstance( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf); + HRESULT CreateInstanceFromPath( + REFCLSID clsid, + REFIID iid, + LPCWSTR path, + void** ppItf); + BOOL GetPathFromModule( + HMODULE hModule, + __in_ecount(cFqPath) LPWSTR fqPath, + DWORD cFqPath); + HRESULT PickClrRuntimeInfo( + ICLRMetaHost *pMetaHost, + CIOptions cciOptions, + ICLRRuntimeInfo** ppClr); + QWORD VerString2Qword(LPCWSTR vStr); + void CleanupClsidHmodMap(); + + // Helper structures for defining the CLSID -> HMODULE hash table we + // use for caching already activated objects + class hash_compareGUID + { + public: + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + hash_compareGUID() + { } + + size_t operator( )(const GUID& _Key) const + { + DWORD *pdw = (DWORD*)&_Key; + return (size_t)(pdw[0] ^ pdw[1] ^ pdw[2] ^ pdw[3]); + } + + bool operator( )(const GUID& _Key1, const GUID& _Key2) const + { return memcmp(&_Key1, &_Key2, sizeof(GUID)) == -1; } + }; + + static std::unordered_map<GUID, HMODULE, hash_compareGUID> *g_pClsidHmodMap = NULL; + + + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceCustomImpl() provides a way to activate a COM object * +* w/o triggering the FeatureOnDemand dialog. In order to do this we * +* must avoid using the CoCreateInstance() API, which, on a machine * +* with v4+ installed and w/o v2, would trigger this. * +* CreateInstanceCustom() activates the requested COM object according * +* to the specified passed in CIOptions, in the following order * +* (skipping the steps not enabled in the CIOptions flags passed in): * +* 1. Attempt to activate the COM object using a framework install: * +* a. If the debugger machine has a V4+ shell shim use the shim * +* to activate the object * +* b. Otherwise simply call CoCreateInstance * +* 2. If unsuccessful attempt to activate looking for the dllName in * +* the same folder as the DAC was loaded from * +* 3. If unsuccessful attempt to activate the COM object looking in * +* every path specified in the debugger's .exepath and .sympath * +\**********************************************************************/ +HRESULT CreateInstanceCustomImpl( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf) +{ + _ASSERTE(ppItf != NULL); + + if (ppItf == NULL) + return E_POINTER; + + WCHAR wszClsid[64] = W("<CLSID>"); + + // Step 1: Attempt activation using an installed runtime + if ((cciOptions & cciFxMask) != 0) + { + CIOptions opt = cciOptions & cciFxMask; + if (SUCCEEDED(ClrCreateInstance(clsid, iid, dllName, opt, ppItf))) + return S_OK; + + ExtDbgOut("Failed to instantiate {%ls} from installed .NET framework locations.\n", wszClsid); + } + + if ((cciOptions & cciDbiColocated) != 0) + { + // if we institute a way to retrieve the module for the current DBI we + // can perform the same steps as for the DAC. + } + + // Step 2: attempt activation using the folder the DAC was loaded from + if ((cciOptions & cciDacColocated) != 0) + { + _ASSERTE(dllName != NULL); + HMODULE hDac = NULL; + WCHAR path[MAX_LONGPATH]; + + if (SUCCEEDED(g_sos->GetDacModuleHandle(&hDac)) + && GetPathFromModule(hDac, path, _countof(path))) + { + // build the fully qualified file name and attempt instantiation + if (wcscat_s(path, dllName) == 0 + && SUCCEEDED(CreateInstanceFromPath(clsid, iid, path, ppItf))) + { + return S_OK; + } + } + + ExtDbgOut("Failed to instantiate {%ls} from DAC location.\n", wszClsid); + } + + // Step 3: attempt activation using the debugger's .exepath and .sympath + if ((cciOptions & cciDbgPath) != 0) + { + _ASSERTE(dllName != NULL); + + ToRelease<IDebugSymbols3> spSym3(NULL); + HRESULT hr = g_ExtSymbols->QueryInterface(__uuidof(IDebugSymbols3), (void**)&spSym3); + if (FAILED(hr)) + { + ExtDbgOut("Unable to query IDebugSymbol3 HRESULT=0x%x.\n", hr); + goto ErrDbgPath; + } + + typedef HRESULT (__stdcall IDebugSymbols3::*GetPathFunc)(LPWSTR , ULONG, ULONG*); + + // Handle both the image path and the symbol path + GetPathFunc rgGetPathFuncs[] = + { &IDebugSymbols3::GetImagePathWide, &IDebugSymbols3::GetSymbolPathWide }; + + for (int i = 0; i < _countof(rgGetPathFuncs); ++i) + { + ULONG pathSize = 0; + + // get the path buffer size + if ((spSym3.GetPtr()->*rgGetPathFuncs[i])(NULL, 0, &pathSize) != S_OK) + { + continue; + } + + ArrayHolder<WCHAR> imgPath = new WCHAR[pathSize+MAX_LONGPATH+1]; + if (imgPath == NULL) + { + continue; + } + + // actually get the path + if ((spSym3.GetPtr()->*rgGetPathFuncs[i])(imgPath, pathSize, NULL) != S_OK) + { + continue; + } + + LPWSTR ctx; + LPCWSTR pathElem = wcstok_s(imgPath, W(";"), &ctx); + while (pathElem != NULL) + { + WCHAR fullName[MAX_LONGPATH]; + wcscpy_s(fullName, _countof(fullName), pathElem); + if (wcscat_s(fullName, W("\\")) == 0 && wcscat_s(fullName, dllName) == 0) + { + if (SUCCEEDED(CreateInstanceFromPath(clsid, iid, fullName, ppItf))) + { + return S_OK; + } + } + + pathElem = wcstok_s(NULL, W(";"), &ctx); + } + } + + ErrDbgPath: + ExtDbgOut("Failed to instantiate {%ls} from debugger's image path.\n", wszClsid); + } + + return REGDB_E_CLASSNOTREG; +} + + +#ifdef _MSC_VER +// SOS is essentially single-threaded. ignore "construction of local static object is not thread-safe" +#pragma warning(push) +#pragma warning(disable:4640) +#endif // _MSC_VER + + +/**********************************************************************\ +* Routine Description: * +* * +* ClrCreateInstance() attempts to activate a COM object using an * +* installed framework: * +* a. If the debugger machine has a V4+ shell shim use the shim to * +* activate the object * +* b. Otherwise simply call CoCreateInstance * +\**********************************************************************/ +HRESULT ClrCreateInstance( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf) +{ + _ASSERTE((cciOptions & ~cciFxMask) == 0 && (cciOptions & cciFxMask) != 0); + HRESULT Status = S_OK; + + static CIOptions prevOpt = 0; + static HRESULT prevHr = S_OK; + + // if we already tried to use NetFx install and failed don't try it again + if (prevOpt == cciOptions && FAILED(prevHr)) + { + return prevHr; + } + + prevOpt = cciOptions; + + // first try usig the metahost API: + HRESULT (__stdcall *pfnCLRCreateInstance)(REFCLSID clsid, REFIID riid, LPVOID * ppInterface) = NULL; + HMODULE hMscoree = NULL; + + // if there's a v4+ shim on the debugger machine + if (GetProcAddressT("CLRCreateInstance", W("mscoree.dll"), &pfnCLRCreateInstance, &hMscoree)) + { + // attempt to create an ICLRMetaHost instance + ToRelease<ICLRMetaHost> spMH; + Status = pfnCLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&spMH); + if (Status == E_NOTIMPL) + { + // E_NOTIMPL means we have a v4 aware mscoree but no v4+ framework + IfFailGo( CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, ppItf) ); + } + else + { + IfFailGo( Status ); + + // pick a runtime according to cciOptions + ToRelease<ICLRRuntimeInfo> spClr; + IfFailGo( PickClrRuntimeInfo(spMH, cciOptions, &spClr) ); + + // activate the COM object + Status = spClr->GetInterface(clsid, iid, ppItf); + + if (FAILED(Status) && dllName) + { + // if we have a v4+ runtime that does not have the fix to activate the requested CLSID + // try activating with the path + WCHAR clrDir[MAX_LONGPATH]; + DWORD cchClrDir = _countof(clrDir); + IfFailGo( spClr->GetRuntimeDirectory(clrDir, &cchClrDir) ); + IfFailGo( wcscat_s(clrDir, dllName) == 0 ? S_OK : E_FAIL ); + IfFailGo( CreateInstanceFromPath(clsid, iid, clrDir, ppItf) ); + } + } + } + else + { + // otherwise fallback to regular COM activation + IfFailGo( CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, ppItf) ); + } + +Error: + if (hMscoree != NULL) + { + FreeLibrary(hMscoree); + } + + // remember if we succeeded or failed + prevHr = Status; + + return Status; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceFromPath() instantiates a COM object using a passed in * +* fully-qualified path and a CLSID. * +* * +* Note: * +* * +* It uses a unordered_map to cache the mapping between a CLSID and the * +* HMODULE that is successfully used to activate the CLSID from. When * +* SOS is unloaded (in DebugExtensionUninitialize()) we call * +* FreeLibrary() for all cached HMODULEs. * +\**********************************************************************/ +HRESULT CreateInstanceFromPath( + REFCLSID clsid, + REFIID iid, + LPCWSTR path, + void** ppItf) +{ + HRESULT Status = S_OK; + HRESULT (__stdcall *pfnDllGetClassObject)(REFCLSID rclsid, REFIID riid, LPVOID *ppv) = NULL; + + HMODULE hmod = NULL; + + if (g_pClsidHmodMap == NULL) + { + g_pClsidHmodMap = new std::unordered_map<GUID, HMODULE, hash_compareGUID>(); + OnUnloadTask::Register(CleanupClsidHmodMap); + } + + auto it = g_pClsidHmodMap->find(clsid); + if (it != g_pClsidHmodMap->end()) + hmod = it->second; + + if (!GetProcAddressT("DllGetClassObject", path, &pfnDllGetClassObject, &hmod)) + return REGDB_E_CLASSNOTREG; + + ToRelease<IClassFactory> pFactory; + IfFailGo(pfnDllGetClassObject(clsid, IID_IClassFactory, (void**)&pFactory)); + + IfFailGo(pFactory->CreateInstance(NULL, iid, ppItf)); + + // only cache the HMODULE if we successfully created the COM object + (*g_pClsidHmodMap)[clsid] = hmod; + + return S_OK; + +Error: + if (hmod != NULL) + FreeLibrary(hmod); + + return Status; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* CleanupClsidHmodMap() cleans up the CLSID -> HMODULE map used to * +* cache successful activations from specific paths. This is registered * +* as an OnUnloadTask in CreateInstanceFromPath(), and executes when * +* SOS is unloaded, in DebugExtensionUninitialize(). * +\**********************************************************************/ +void CleanupClsidHmodMap() +{ + if (g_pClsidHmodMap != NULL) + { + for (auto it = g_pClsidHmodMap->begin(); it != g_pClsidHmodMap->end(); ++it) + { + _ASSERTE(it->second != NULL); + FreeLibrary(it->second); + } + + delete g_pClsidHmodMap; + g_pClsidHmodMap = NULL; + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* PickClrRuntimeInfo() selects on CLR runtime from the ones installed * +* on the debugger machine. If cciFxAny is specified in cciOptions it * +* simply returns the first runtime enumerated by the metahost * +* interface. If cciLatestFx is specified we pick the runtime with the * +* highest version (parsing the string returned from * +* ICLRRuntimeInfo::GetVersionString(). * +\**********************************************************************/ +HRESULT PickClrRuntimeInfo( + ICLRMetaHost *pMetaHost, + CIOptions cciOptions, + ICLRRuntimeInfo** ppClr) +{ + if (ppClr == NULL) + return E_POINTER; + + // only support "Any framework" and "latest framework" + if (cciOptions != cciAnyFx && cciOptions != cciLatestFx) + return E_INVALIDARG; + + HRESULT Status = S_OK; + *ppClr = NULL; + + // get the CLRRuntime enumerator + ToRelease<IEnumUnknown> spClrsEnum; + IfFailRet(pMetaHost->EnumerateInstalledRuntimes(&spClrsEnum)); + + ToRelease<ICLRRuntimeInfo> spChosenClr; + QWORD verMax = 0; + + int cntClr = 0; + while (1) + { + // retrieve the next ICLRRuntimeInfo + ULONG cnt; + ToRelease<IUnknown> spClrUnk; + if (spClrsEnum->Next(1, &spClrUnk, &cnt) != S_OK || cnt != 1) + break; + + ToRelease<ICLRRuntimeInfo> spClr; + BOOL bLoadable = FALSE; + // ignore un-loadable runtimes + if (FAILED(spClrUnk->QueryInterface(IID_ICLRRuntimeInfo, (void**)&spClr)) + || FAILED(spClr->IsLoadable(&bLoadable)) + || !bLoadable) + { + continue; + } + + WCHAR vStr[128]; + DWORD cStr = _countof(vStr); + if (FAILED(spClr->GetVersionString(vStr, &cStr))) + continue; + + ++cntClr; + + if ((cciOptions & cciAnyFx) != 0) + { + spChosenClr = spClr.Detach(); + break; + } + + QWORD ver = VerString2Qword(vStr); + if ((cciOptions & cciLatestFx) != 0) + { + if (ver > verMax) + { + verMax = ver; + spChosenClr = spClr.Detach(); + } + } + } + + if (cntClr == 0 || spChosenClr == NULL) + { + *ppClr = NULL; + return E_NOINTERFACE; + } + else + { + *ppClr = spChosenClr.Detach(); + return S_OK; + } +} + + +/**********************************************************************\ +* Routine Description: * +* * +* VerString2Qword() parses a string as returned from * +* ICLRRuntimeInfo::GetVersionString() into a QWORD, assuming every * +* numeric element is a WORD portion in the QWORD. * +\**********************************************************************/ +QWORD VerString2Qword(LPCWSTR vStr) +{ + _ASSERTE(vStr[0] == L'v' || vStr[0] == L'V'); + QWORD result = 0; + + DWORD v1, v2, v3; + if (swscanf_s(vStr+1, W("%d.%d.%d"), &v1, &v2, &v3) == 3) + { + result = ((QWORD)v1 << 48) | ((QWORD)v2 << 32) | ((QWORD)v3 << 16); + } + else if (swscanf_s(vStr+1, W("%d.%d"), &v1, &v2) == 2) + { + result = ((QWORD)v1 << 48) | ((QWORD)v2 << 32); + } + else if (swscanf_s(vStr+1, W("%d"), &v1) == 1) + { + result = ((QWORD)v1 << 48); + } + + return result; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* GetPathFromModule() returns the name of the folder containing the * +* file associated with hModule. * + \**********************************************************************/ +BOOL GetPathFromModule( + HMODULE hModule, + __in_ecount(cFqPath) LPWSTR fqPath, + DWORD cFqPath) +{ + int len = GetModuleFileNameW(hModule, fqPath, cFqPath); + if (len == 0 || len == cFqPath) + return FALSE; + + WCHAR *pLastSep = _wcsrchr(fqPath, DIRECTORY_SEPARATOR_CHAR_W); + if (pLastSep == NULL || pLastSep+1 >= fqPath+cFqPath) + return FALSE; + + *(pLastSep+1) = L'\0'; + + return TRUE; +} + +} + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceCustom() provides a way to activate a COM object w/o * +* triggering the FeatureOnDemand dialog. In order to do this we * +* must avoid using the CoCreateInstance() API, which, on a machine * +* with v4+ installed and w/o v2, would trigger this. * +* CreateInstanceCustom() activates the requested COM object according * +* to the specified passed in CIOptions, in the following order * +* (skipping the steps not enabled in the CIOptions flags passed in): * +* 1. Attempt to activate the COM object using a framework install: * +* a. If the debugger machine has a V4+ shell shim use the shim * +* to activate the object * +* b. Otherwise simply call CoCreateInstance * +* 2. If unsuccessful attempt to activate looking for the dllName in * +* the same folder as the DAC was loaded from * +* 3. If unsuccessful attempt to activate the COM object looking in * +* every path specified in the debugger's .exepath and .sympath * +\**********************************************************************/ +HRESULT CreateInstanceCustom( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf) +{ + return com_activation::CreateInstanceCustomImpl(clsid, iid, dllName, cciOptions, ppItf); +} + + + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to get the memory address given a symbol * +* name. It handles difference in symbol name between ntsd and * +* windbg. * +* * +\**********************************************************************/ +DWORD_PTR GetValueFromExpression (___in __in_z const char *const instr) +{ + ULONG64 dwAddr; + const char *str = instr; + char name[256]; + + dwAddr = 0; + HRESULT hr = g_ExtSymbols->GetOffsetByName (str, &dwAddr); + if (SUCCEEDED(hr)) + return (DWORD_PTR)dwAddr; + else if (hr == S_FALSE && dwAddr) + return (DWORD_PTR)dwAddr; + + strcpy_s (name, _countof(name), str); + char *ptr; + if ((ptr = strstr (name, "__")) != NULL) + { + ptr[0] = ':'; + ptr[1] = ':'; + ptr += 2; + while ((ptr = strstr(ptr, "__")) != NULL) + { + ptr[0] = ':'; + ptr[1] = ':'; + ptr += 2; + } + dwAddr = 0; + hr = g_ExtSymbols->GetOffsetByName (name, &dwAddr); + if (SUCCEEDED(hr)) + return (DWORD_PTR)dwAddr; + else if (hr == S_FALSE && dwAddr) + return (DWORD_PTR)dwAddr; + } + else if ((ptr = strstr (name, "::")) != NULL) + { + ptr[0] = '_'; + ptr[1] = '_'; + ptr += 2; + while ((ptr = strstr(ptr, "::")) != NULL) + { + ptr[0] = '_'; + ptr[1] = '_'; + ptr += 2; + } + dwAddr = 0; + hr = g_ExtSymbols->GetOffsetByName (name, &dwAddr); + if (SUCCEEDED(hr)) + return (DWORD_PTR)dwAddr; + else if (hr == S_FALSE && dwAddr) + return (DWORD_PTR)dwAddr; + } + return 0; +} + +#endif // FEATURE_PAL + +ModuleInfo moduleInfo[MSCOREND] = {{0,FALSE,0},{0,FALSE,0},{0,FALSE,0}}; + +void ReportOOM() +{ + ExtOut("SOS Error: Out of memory\n"); +} + +HRESULT CheckEEDll() +{ +#ifndef FEATURE_PAL + VS_FIXEDFILEINFO ee = {}; + + static VS_FIXEDFILEINFO sos = {}; + static BOOL sosDataInit = FALSE; + + BOOL result = GetEEVersion(&ee); + if (result && !sosDataInit) + { + result = GetSOSVersion(&sos); + + if (result) + sosDataInit = TRUE; + } + + // We will ignore errors because it's possible sos is being loaded before CLR. + if (result) + { + if ((ee.dwFileVersionMS != sos.dwFileVersionMS) || (ee.dwFileVersionLS != sos.dwFileVersionLS)) + { + ExtOut("The version of SOS does not match the version of CLR you are debugging. Please\n"); + ExtOut("load the matching version of SOS for the version of CLR you are debugging.\n"); + ExtOut("CLR Version: %u.%u.%u.%u\n", + HIWORD(ee.dwFileVersionMS), + LOWORD(ee.dwFileVersionMS), + HIWORD(ee.dwFileVersionLS), + LOWORD(ee.dwFileVersionLS)); + + ExtOut("SOS Version: %u.%u.%u.%u\n", + HIWORD(sos.dwFileVersionMS), + LOWORD(sos.dwFileVersionMS), + HIWORD(sos.dwFileVersionLS), + LOWORD(sos.dwFileVersionLS)); + } + } + + DEBUG_MODULE_PARAMETERS Params; + + // Do we have clr.dll + if (moduleInfo[MSCORWKS].baseAddr == 0) + { + g_ExtSymbols->GetModuleByModuleName (MAIN_CLR_MODULE_NAME_A,0,NULL, + &moduleInfo[MSCORWKS].baseAddr); + if (moduleInfo[MSCORWKS].baseAddr != 0 && moduleInfo[MSCORWKS].hasPdb == FALSE) + { + g_ExtSymbols->GetModuleParameters (1, &moduleInfo[MSCORWKS].baseAddr, 0, &Params); + if (Params.SymbolType == SymDeferred) + { + g_ExtSymbols->Reload("/f " MAIN_CLR_DLL_NAME_A); + g_ExtSymbols->GetModuleParameters (1, &moduleInfo[MSCORWKS].baseAddr, 0, &Params); + } + + if (Params.SymbolType == SymPdb || Params.SymbolType == SymDia) + { + moduleInfo[MSCORWKS].hasPdb = TRUE; + } + + moduleInfo[MSCORWKS].size = Params.Size; + } + if (moduleInfo[MSCORWKS].baseAddr != 0 && moduleInfo[MSCORWKS].hasPdb == FALSE) + ExtOut("PDB symbol for clr.dll not loaded\n"); + } + + return (moduleInfo[MSCORWKS].baseAddr != 0) ? S_OK : E_FAIL; +#else + return S_OK; +#endif // FEATURE_PAL +} + +EEFLAVOR GetEEFlavor () +{ +#ifdef FEATURE_PAL + return MSCORWKS; +#else // FEATUER_PAL + EEFLAVOR flavor = UNKNOWNEE; + + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A,0,NULL,NULL))) { + flavor = MSCORWKS; + } + return flavor; +#endif // FEATURE_PAL else +} + +BOOL IsDumpFile () +{ + static int g_fDumpFile = -1; + if (g_fDumpFile == -1) { + ULONG Class; + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + if (Qualifier >= DEBUG_DUMP_SMALL) + g_fDumpFile = 1; + else + g_fDumpFile = 0; + } + return g_fDumpFile != 0; +} + +BOOL g_InMinidumpSafeMode = FALSE; + +BOOL IsMiniDumpFileNODAC () +{ +#ifndef FEATURE_PAL + ULONG Class; + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + if (Qualifier == DEBUG_DUMP_SMALL) + { + g_ExtControl->GetDumpFormatFlags(&Qualifier); + if ((Qualifier & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY) == 0) + { + return TRUE; + } + } + +#endif // FEATURE_PAL + return FALSE; +} + + +// We use this predicate to mean the smallest, most restrictive kind of +// minidump file. There is no heap dump, only that set of information +// gathered to make !clrstack, !threads, !help, !eeversion and !pe work. +BOOL IsMiniDumpFile () +{ +#ifndef FEATURE_PAL + // It is okay for this to be static, because although the debugger may debug multiple + // managed processes at once, I don't believe multiple dumpfiles of different + // types is a scenario to worry about. + if (IsMiniDumpFileNODAC()) + { + // Beyond recognizing the dump type above, all we can rely on for this + // is a flag set by the user indicating they want a safe mode minidump + // experience. This is primarily for testing. + return g_InMinidumpSafeMode; + } + +#endif // FEATURE_PAL + return FALSE; +} + +ULONG DebuggeeType() +{ + static ULONG Class = DEBUG_CLASS_UNINITIALIZED; + if (Class == DEBUG_CLASS_UNINITIALIZED) { + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + } + return Class; +} + +#ifndef FEATURE_PAL + +// Check if a file exist +BOOL FileExist (const char *filename) +{ + WIN32_FIND_DATA FindFileData; + HANDLE handle = FindFirstFile (filename, &FindFileData); + if (handle != INVALID_HANDLE_VALUE) { + FindClose (handle); + return TRUE; + } + else + return FALSE; +} + + +BOOL FileExist (const WCHAR *filename) +{ + WIN32_FIND_DATAW FindFileData; + HANDLE handle = FindFirstFileW (filename, &FindFileData); + if (handle != INVALID_HANDLE_VALUE) { + FindClose (handle); + return TRUE; + } + else + return FALSE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find out if a dll is bbt-ized * +* * +\**********************************************************************/ +BOOL IsRetailBuild (size_t base) +{ + IMAGE_DOS_HEADER DosHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(base), &DosHeader, sizeof(DosHeader), NULL) != S_OK) + return FALSE; + IMAGE_NT_HEADERS32 Header32; + if (g_ExtData->ReadVirtual(TO_CDADDR(base + DosHeader.e_lfanew), &Header32, sizeof(Header32), NULL) != S_OK) + return FALSE; + // If there is no COMHeader, this can not be managed code. + if (Header32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress == 0) + return FALSE; + + size_t debugDirAddr = base + Header32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; + size_t nSize = Header32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + IMAGE_DEBUG_DIRECTORY debugDir; + size_t nbytes = 0; + while (nbytes < nSize) { + if (g_ExtData->ReadVirtual(TO_CDADDR(debugDirAddr+nbytes), &debugDir, sizeof(debugDir), NULL) != S_OK) + return FALSE; + if (debugDir.Type == 0xA) { + return TRUE; + } + nbytes += sizeof(debugDir); + } + return FALSE; +} + +#endif // !FEATURE_PAL + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to read memory from the debugee's * +* address space. If the initial read fails, it attempts to read * +* only up to the edge of the page containing "offset". * +* * +\**********************************************************************/ +BOOL SafeReadMemory (TADDR offset, PVOID lpBuffer, ULONG cb, + PULONG lpcbBytesRead) +{ + BOOL bRet = FALSE; + + bRet = SUCCEEDED(g_ExtData->ReadVirtual(TO_CDADDR(offset), lpBuffer, cb, + lpcbBytesRead)); + + if (!bRet) + { + cb = (ULONG)(NextOSPageAddress(offset) - offset); + bRet = SUCCEEDED(g_ExtData->ReadVirtual(TO_CDADDR(offset), lpBuffer, cb, + lpcbBytesRead)); + } + return bRet; +} + +ULONG OSPageSize () +{ + static ULONG pageSize = 0; + if (pageSize == 0) + g_ExtControl->GetPageSize(&pageSize); + + return pageSize; +} + +size_t NextOSPageAddress (size_t addr) +{ + size_t pageSize = OSPageSize(); + return (addr+pageSize)&(~(pageSize-1)); +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to get the address of MethodDesc * +* given an ip address * +* * +\**********************************************************************/ +void IP2MethodDesc (DWORD_PTR IP, DWORD_PTR &methodDesc, JITTypes &jitType, + DWORD_PTR &gcinfoAddr) +{ + + CLRDATA_ADDRESS EIP = TO_CDADDR(IP); + DacpCodeHeaderData codeHeaderData; + + methodDesc = NULL; + gcinfoAddr = NULL; + + if (codeHeaderData.Request(g_sos, EIP) != S_OK) + { + return; + } + + methodDesc = (DWORD_PTR) codeHeaderData.MethodDescPtr; + jitType = (JITTypes) codeHeaderData.JITType; + gcinfoAddr = (DWORD_PTR) codeHeaderData.GCInfo; +} + +BOOL IsValueField (DacpFieldDescData *pFD) +{ + return (pFD->Type == ELEMENT_TYPE_VALUETYPE); +} + +void DisplayDataMember (DacpFieldDescData* pFD, DWORD_PTR dwAddr, BOOL fAlign=TRUE) +{ + if (dwAddr > 0) + { + // we must have called this function for a "real" (non-zero size) data type + PREFIX_ASSUME(gElementTypeInfo[pFD->Type] != 0); + + DWORD_PTR dwTmp = dwAddr; + bool bVTStatic = (pFD->bIsStatic && pFD->Type == ELEMENT_TYPE_VALUETYPE); + + if (gElementTypeInfo[pFD->Type] != NO_SIZE || bVTStatic) + { + union Value + { + char ch; + short Short; + DWORD_PTR ptr; + int Int; + unsigned int UInt; + __int64 Int64; + unsigned __int64 UInt64; + float Float; + double Double; + } value; + + ZeroMemory(&value, sizeof(value)); + if (bVTStatic) + { + // static VTypes are boxed + moveBlock (value, dwTmp, gElementTypeInfo[ELEMENT_TYPE_CLASS]); + } + else + { + moveBlock (value, dwTmp, gElementTypeInfo[pFD->Type]); + } + + switch (pFD->Type) + { + case ELEMENT_TYPE_I1: + // there's no ANSI conformant type specifier for + // signed char, so use the next best thing, + // signed short (sign extending) + if (fAlign) + ExtOut("%" POINTERSIZE "hd", (short)value.ch); + else + ExtOut("%d", value.ch); + break; + case ELEMENT_TYPE_I2: + if (fAlign) + ExtOut("%" POINTERSIZE "hd", value.Short); + else + ExtOut("%d", value.Short); + break; + case ELEMENT_TYPE_I4: + if (fAlign) + ExtOut("%" POINTERSIZE "d", value.Int); + else + ExtOut("%d", value.Int); + break; + case ELEMENT_TYPE_I8: + ExtOut("%I64d", value.Int64); + break; + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_BOOLEAN: + if (fAlign) + // there's no ANSI conformant type specifier for + // unsigned char, so use the next best thing, + // unsigned short, not extending the sign + ExtOut("%" POINTERSIZE "hu", (USHORT)value.Short); + else + ExtOut("%u", value.ch); + break; + case ELEMENT_TYPE_U2: + if (fAlign) + ExtOut("%" POINTERSIZE "hu", value.Short); + else + ExtOut("%u", value.Short); + break; + case ELEMENT_TYPE_U4: + if (fAlign) + ExtOut("%" POINTERSIZE "u", value.UInt); + else + ExtOut("%u", value.UInt); + break; + case ELEMENT_TYPE_U8: + ExtOut("%I64u", value.UInt64); + break; + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + if (fAlign) + ExtOut("%" POINTERSIZE "p", SOS_PTR(value.ptr)); + else + ExtOut("%p", SOS_PTR(value.ptr)); + break; + case ELEMENT_TYPE_R4: + ExtOut("%f", value.Float); + break; + case ELEMENT_TYPE_R8: + ExtOut("%f", value.Double); + break; + case ELEMENT_TYPE_CHAR: + if (fAlign) + ExtOut("%" POINTERSIZE "hx", value.Short); + else + ExtOut("%x", value.Short); + break; + case ELEMENT_TYPE_VALUETYPE: + if (value.ptr) + DMLOut(DMLValueClass(pFD->MTOfType, dwTmp)); + else + ExtOut("%p", SOS_PTR(0)); + break; + default: + if (value.ptr) + DMLOut(DMLObject(value.ptr)); + else + ExtOut("%p", SOS_PTR(0)); + break; + } + } + else + { + if (pFD->Type == ELEMENT_TYPE_VALUETYPE) + DMLOut(DMLValueClass(pFD->MTOfType, dwTmp)); + else + ExtOut("%p", SOS_PTR(0)); + } + } + else + { + ExtOut("%" POINTERSIZE "s", " "); + } +} + +void GetStaticFieldPTR(DWORD_PTR* pOutPtr, DacpDomainLocalModuleData* pDLMD, DacpMethodTableData* pMTD, DacpFieldDescData* pFDD, BYTE* pFlags = 0) +{ + DWORD_PTR dwTmp; + + if (pFDD->Type == ELEMENT_TYPE_VALUETYPE + || pFDD->Type == ELEMENT_TYPE_CLASS) + { + dwTmp = (DWORD_PTR) pDLMD->pGCStaticDataStart + pFDD->dwOffset; + } + else + { + dwTmp = (DWORD_PTR) pDLMD->pNonGCStaticDataStart + pFDD->dwOffset; + } + + *pOutPtr = 0; + + if (pMTD->bIsDynamic) + { + ExtOut("dynamic statics NYI"); + return; + } + else + { + if (pFlags && pMTD->bIsShared) + { + BYTE flags; + DWORD_PTR pTargetFlags = (DWORD_PTR) pDLMD->pClassData + RidFromToken(pMTD->cl) - 1; + move_xp (flags, pTargetFlags); + + *pFlags = flags; + } + + + *pOutPtr = dwTmp; + } + return; +} + +void GetDLMFlags(DacpDomainLocalModuleData* pDLMD, DacpMethodTableData* pMTD, BYTE* pFlags) +{ + if (pMTD->bIsDynamic) + { + ExtOut("dynamic statics NYI"); + return; + } + else + { + if (pFlags) + { + BYTE flags; + DWORD_PTR pTargetFlags = (DWORD_PTR) pDLMD->pClassData + RidFromToken(pMTD->cl) - 1; + move_xp (flags, pTargetFlags); + + *pFlags = flags; + } + } + return; +} + +void GetThreadStaticFieldPTR(DWORD_PTR* pOutPtr, DacpThreadLocalModuleData* pTLMD, DacpMethodTableData* pMTD, DacpFieldDescData* pFDD, BYTE* pFlags = 0) +{ + DWORD_PTR dwTmp; + + if (pFDD->Type == ELEMENT_TYPE_VALUETYPE + || pFDD->Type == ELEMENT_TYPE_CLASS) + { + dwTmp = (DWORD_PTR) pTLMD->pGCStaticDataStart + pFDD->dwOffset; + } + else + { + dwTmp = (DWORD_PTR) pTLMD->pNonGCStaticDataStart + pFDD->dwOffset; + } + + *pOutPtr = 0; + + if (pMTD->bIsDynamic) + { + ExtOut("dynamic thread statics NYI"); + return; + } + else + { + if (pFlags) + { + BYTE flags; + DWORD_PTR pTargetFlags = (DWORD_PTR) pTLMD->pClassData + RidFromToken(pMTD->cl) - 1; + move_xp (flags, pTargetFlags); + + *pFlags = flags; + } + + *pOutPtr = dwTmp; + } + return; +} + +void DisplaySharedStatic(ULONG64 dwModuleDomainID, DacpMethodTableData* pMT, DacpFieldDescData *pFD) +{ + DacpAppDomainStoreData adsData; + if (adsData.Request(g_sos)!=S_OK) + { + ExtOut("Unable to get AppDomain information\n"); + } + + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[adsData.DomainCount]; + if (pArray==NULL) + { + ReportOOM(); + return; + } + + if (g_sos->GetAppDomainList(adsData.DomainCount,pArray, NULL)!=S_OK) + { + ExtOut("Unable to get array of AppDomains\n"); + return; + } + +#if defined(_TARGET_WIN64_) + ExtOut(" >> Domain:Value "); +#else + ExtOut(" >> Domain:Value "); +#endif + // Skip the SystemDomain and SharedDomain + for (int i = 0; i < adsData.DomainCount ; i ++) + { + DacpAppDomainData appdomainData; + if (appdomainData.Request(g_sos,pArray[i])!=S_OK) + { + ExtOut("Unable to get AppDomain %lx\n",pArray[i]); + return; + } + + DacpDomainLocalModuleData vDomainLocalModule; + if (g_sos->GetDomainLocalModuleDataFromAppDomain(appdomainData.AppDomainPtr, (int)dwModuleDomainID, &vDomainLocalModule) != S_OK) + { + DMLOut(" %s:NotInit ", DMLDomain(pArray[i])); + continue; + } + + DWORD_PTR dwTmp; + BYTE Flags = 0; + GetStaticFieldPTR(&dwTmp, &vDomainLocalModule , pMT, pFD, &Flags); + + if ((Flags&1) == 0) { + // We have not initialized this yet. + DMLOut(" %s:NotInit ", DMLDomain(pArray[i])); + continue; + } + else if (Flags & 2) { + // We have not initialized this yet. + DMLOut(" %s:FailInit", DMLDomain(pArray[i])); + continue; + } + + DMLOut(" %s:", DMLDomain(appdomainData.AppDomainPtr)); + DisplayDataMember(pFD, dwTmp, FALSE); + } + ExtOut(" <<\n"); +} + +void DisplayThreadStatic (DacpModuleData* pModule, DacpMethodTableData* pMT, DacpFieldDescData *pFD, BOOL fIsShared) +{ + SIZE_T dwModuleIndex = (SIZE_T)pModule->dwModuleIndex; + SIZE_T dwModuleDomainID = (SIZE_T)pModule->dwModuleID; + + DacpThreadStoreData ThreadStore; + ThreadStore.Request(g_sos); + + ExtOut(" >> Thread:Value"); + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread) + { + DacpThreadData vThread; + if (vThread.Request(g_sos, CurThread) != S_OK) + { + ExtOut(" error getting thread %p, aborting this field\n", SOS_PTR(CurThread)); + return; + } + + if (vThread.osThreadId != 0) + { + CLRDATA_ADDRESS appDomainAddr = vThread.domain; + + // Get the DLM (we need this to check the ClassInit flags). + // It's annoying that we have to issue one request for + // domain-neutral modules and domain-specific modules. + DacpDomainLocalModuleData vDomainLocalModule; + if (fIsShared) + { + if (g_sos->GetDomainLocalModuleDataFromAppDomain(appDomainAddr, (int)dwModuleDomainID, &vDomainLocalModule) != S_OK) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + } + else + { + if (g_sos->GetDomainLocalModuleDataFromModule(pMT->Module, &vDomainLocalModule) != S_OK) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + } + + // Get the TLM + DacpThreadLocalModuleData vThreadLocalModule; + if (g_sos->GetThreadLocalModuleData(CurThread, (int)dwModuleIndex, &vThreadLocalModule) != S_OK) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + + DWORD_PTR dwTmp; + BYTE Flags = 0; + GetThreadStaticFieldPTR(&dwTmp, &vThreadLocalModule, pMT, pFD, &Flags); + + if ((Flags&4) == 0) + { + // Not allocated, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + + Flags = 0; + GetDLMFlags(&vDomainLocalModule, pMT, &Flags); + + if ((Flags&1) == 0) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + + ExtOut(" %x:", vThread.osThreadId); + DisplayDataMember(pFD, dwTmp, FALSE); + } + + // Go to next thread + CurThread = vThread.nextThread; + } + ExtOut(" <<\n"); +} + +void DisplayContextStatic (DacpFieldDescData *pFD, size_t offset, BOOL fIsShared) +{ + ExtOut("\nDisplay of context static variables is not implemented yet\n"); + /* + int numDomain; + DWORD_PTR *domainList = NULL; + GetDomainList (domainList, numDomain); + ToDestroy des0 ((void**)&domainList); + AppDomain vAppDomain; + Context vContext; + + ExtOut(" >> Domain:Value"); + for (int i = 0; i < numDomain; i ++) + { + DWORD_PTR dwAddr = domainList[i]; + if (dwAddr == 0) { + continue; + } + vAppDomain.Fill (dwAddr); + if (vAppDomain.m_pDefaultContext == 0) + continue; + dwAddr = (DWORD_PTR)vAppDomain.m_pDefaultContext; + vContext.Fill (dwAddr); + + if (fIsShared) + dwAddr = (DWORD_PTR)vContext.m_pSharedStaticData; + else + dwAddr = (DWORD_PTR)vContext.m_pUnsharedStaticData; + if (dwAddr == 0) + continue; + dwAddr += offsetof(STATIC_DATA, dataPtr); + dwAddr += offset; + if (safemove (dwAddr, dwAddr) == 0) + continue; + if (dwAddr == 0) + // We have not initialized this yet. + continue; + + dwAddr += pFD->dwOffset; + if (pFD->Type == ELEMENT_TYPE_CLASS + || pFD->Type == ELEMENT_TYPE_VALUETYPE) + { + if (safemove (dwAddr, dwAddr) == 0) + continue; + } + if (dwAddr == 0) + // We have not initialized this yet. + continue; + ExtOut(" %p:", (ULONG64)domainList[i]); + DisplayDataMember (pFD, dwAddr, FALSE); + } + ExtOut(" <<\n"); + */ +} + +const char * ElementTypeName(unsigned type) +{ + switch (type) { + case ELEMENT_TYPE_PTR: + return "PTR"; + break; + case ELEMENT_TYPE_BYREF: + return "BYREF"; + break; + case ELEMENT_TYPE_VALUETYPE: + return "VALUETYPE"; + break; + case ELEMENT_TYPE_CLASS: + return "CLASS"; + break; + case ELEMENT_TYPE_VAR: + return "VAR"; + break; + case ELEMENT_TYPE_ARRAY: + return "ARRAY"; + break; + case ELEMENT_TYPE_FNPTR: + return "FNPTR"; + break; + case ELEMENT_TYPE_SZARRAY: + return "SZARRAY"; + break; + case ELEMENT_TYPE_MVAR: + return "MVAR"; + break; + default: + if ((type >= _countof(CorElementTypeName)) || (CorElementTypeName[type] == NULL)) + { + return ""; + } + return CorElementTypeName[type]; + break; + } +} // ElementTypeName + +const char * ElementTypeNamespace(unsigned type) +{ + if ((type >= _countof(CorElementTypeName)) || (CorElementTypeNamespace[type] == NULL)) + { + return ""; + } + return CorElementTypeNamespace[type]; +} + +void ComposeName_s(CorElementType Type, __out_ecount(capacity_buffer) LPSTR buffer, size_t capacity_buffer) +{ + const char *p = ElementTypeNamespace(Type); + if ((p) && (*p != '\0')) + { + strcpy_s(buffer,capacity_buffer,p); + strcat_s(buffer,capacity_buffer,"."); + strcat_s(buffer,capacity_buffer,ElementTypeName(Type)); + } + else + { + strcpy_s(buffer,capacity_buffer,ElementTypeName(Type)); + } +} + +// NOTE: pszName is changed +// INPUT MAXCHARS RETURN +// HelloThere 5 ...re +// HelloThere 8 ...There +LPWSTR FormatTypeName (__out_ecount (maxChars) LPWSTR pszName, UINT maxChars) +{ + UINT iStart = 0; + UINT iLen = (int) _wcslen(pszName); + if (iLen > maxChars) + { + iStart = iLen - maxChars; + UINT numDots = (maxChars < 3) ? maxChars : 3; + for (UINT i=0; i < numDots; i++) + pszName[iStart+i] = '.'; + } + return pszName + iStart; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump all fields of a managed object. * +* dwStartAddr specifies the beginning memory address. * +* bFirst is used to avoid printing header everytime. * +* * +\**********************************************************************/ +void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodTableFieldData *pMTFD, DWORD_PTR dwStartAddr, BOOL bFirst, BOOL bValueClass) +{ + static DWORD numInstanceFields = 0; + if (bFirst) + { + ExtOutIndent(); + ExtOut("%" POINTERSIZE "s %8s %8s %20s %2s %8s %" POINTERSIZE "s %s\n", + "MT", "Field", "Offset", "Type", "VT", "Attr", "Value", "Name"); + numInstanceFields = 0; + } + + BOOL fIsShared = pMTD->bIsShared; + + if (pMTD->ParentMethodTable) + { + DacpMethodTableData vParentMethTable; + if (vParentMethTable.Request(g_sos,pMTD->ParentMethodTable) != S_OK) + { + ExtOut("Invalid parent MethodTable\n"); + return; + } + + DacpMethodTableFieldData vParentMethTableFields; + if (vParentMethTableFields.Request(g_sos,pMTD->ParentMethodTable) != S_OK) + { + ExtOut("Invalid parent EEClass\n"); + return; + } + + DisplayFields(pMTD->ParentMethodTable, &vParentMethTable, &vParentMethTableFields, dwStartAddr, FALSE, bValueClass); + } + + DWORD numStaticFields = 0; + CLRDATA_ADDRESS dwAddr = pMTFD->FirstField; + DacpFieldDescData vFieldDesc; + + // Get the module name + DacpModuleData module; + if (module.Request(g_sos, pMTD->Module)!=S_OK) + return; + + ToRelease<IMetaDataImport> pImport = MDImportForModule(&module); + + while (numInstanceFields < pMTFD->wNumInstanceFields + || numStaticFields < pMTFD->wNumStaticFields) + { + if (IsInterrupt()) + return; + + ExtOutIndent (); + + if ((vFieldDesc.Request(g_sos, dwAddr)!=S_OK) || + (vFieldDesc.Type >= ELEMENT_TYPE_MAX)) + { + ExtOut("Unable to display fields\n"); + return; + } + dwAddr = vFieldDesc.NextField; + + DWORD offset = vFieldDesc.dwOffset; + if(!((vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal || fIsShared) && vFieldDesc.bIsStatic)) + { + if (!bValueClass) + { + offset += sizeof(BaseObject); + } + } + + DMLOut("%s %8x %8x ", DMLMethodTable(vFieldDesc.MTOfType), + TokenFromRid(vFieldDesc.mb, mdtFieldDef), + offset); + + char ElementName[mdNameLen]; + if ((vFieldDesc.Type == ELEMENT_TYPE_VALUETYPE || + vFieldDesc.Type == ELEMENT_TYPE_CLASS) && vFieldDesc.MTOfType) + { + NameForMT_s((DWORD_PTR)vFieldDesc.MTOfType, g_mdName, mdNameLen); + ExtOut("%20.20S ", FormatTypeName(g_mdName, 20)); + } + else + { + if (vFieldDesc.Type == ELEMENT_TYPE_CLASS && vFieldDesc.TokenOfType != mdTypeDefNil) + { + // Get the name from Metadata!!! + NameForToken_s(TokenFromRid(vFieldDesc.TokenOfType, mdtTypeDef), pImport, g_mdName, mdNameLen, false); + ExtOut("%20.20S ", FormatTypeName(g_mdName, 20)); + } + else + { + // If ET type from signature is different from fielddesc, then the signature one is more descriptive. + // For example, E_T_STRING in field desc will be E_T_CLASS. In minidump's case, we won't have + // the method table for it. + ComposeName_s(vFieldDesc.Type != vFieldDesc.sigType ? vFieldDesc.sigType : vFieldDesc.Type, ElementName, sizeof(ElementName)/sizeof(ElementName[0])); + ExtOut("%20.20s ", ElementName); + } + } + + ExtOut("%2s ", (IsElementValueType(vFieldDesc.Type)) ? "1" : "0"); + + if (vFieldDesc.bIsStatic && (vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal)) + { + numStaticFields ++; + if (fIsShared) + ExtOut("%8s %" POINTERSIZE "s", "shared", vFieldDesc.bIsThreadLocal ? "TLstatic" : "CLstatic"); + else + ExtOut("%8s ", vFieldDesc.bIsThreadLocal ? "TLstatic" : "CLstatic"); + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + + if (IsMiniDumpFile()) + { + ExtOut(" <no information>\n"); + } + else + { + if (vFieldDesc.bIsThreadLocal) + { + DacpModuleData vModule; + if (vModule.Request(g_sos,pMTD->Module) == S_OK) + { + DisplayThreadStatic(&vModule, pMTD, &vFieldDesc, fIsShared); + } + } + else if (vFieldDesc.bIsContextLocal) + { + DisplayContextStatic(&vFieldDesc, + pMTFD->wContextStaticOffset, + fIsShared); + } + } + + } + else if (vFieldDesc.bIsStatic) + { + numStaticFields ++; + + if (fIsShared) + { + ExtOut("%8s %" POINTERSIZE "s", "shared", "static"); + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + + if (IsMiniDumpFile()) + { + ExtOut(" <no information>\n"); + } + else + { + DacpModuleData vModule; + if (vModule.Request(g_sos,pMTD->Module) == S_OK) + { + DisplaySharedStatic(vModule.dwModuleID, pMTD, &vFieldDesc); + } + } + } + else + { + ExtOut("%8s ", "static"); + + DacpDomainLocalModuleData vDomainLocalModule; + + // The MethodTable isn't shared, so the module must not be loaded domain neutral. We can + // get the specific DomainLocalModule instance without needing to know the AppDomain in advance. + if (g_sos->GetDomainLocalModuleDataFromModule(pMTD->Module, &vDomainLocalModule) != S_OK) + { + ExtOut(" <no information>\n"); + } + else + { + DWORD_PTR dwTmp; + GetStaticFieldPTR(&dwTmp, &vDomainLocalModule, pMTD, &vFieldDesc); + DisplayDataMember(&vFieldDesc, dwTmp); + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + } + } + } + else + { + numInstanceFields ++; + + ExtOut("%8s ", "instance"); + + if (dwStartAddr > 0) + { + DWORD_PTR dwTmp = dwStartAddr + vFieldDesc.dwOffset + (bValueClass ? 0 : sizeof(BaseObject)); + DisplayDataMember(&vFieldDesc, dwTmp); + } + else + { + ExtOut(" %8s", " "); + } + + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + } + + } + + return; +} + +// Return value: -1 = error, +// 0 = field not found, +// > 0 = offset to field from objAddr +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL bFirst) +{ + TADDR mt = NULL; + if FAILED(GetMTOfObject(TO_TADDR(cdaObj), &mt)) + return -1; + + return GetObjFieldOffset(cdaObj, TO_CDADDR(mt), wszFieldName, bFirst); +} + +// Return value: -1 = error, +// 0 = field not found, +// > 0 = offset to field from objAddr +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, + BOOL bFirst/*=TRUE*/) +{ + +#define EXITPOINT(EXPR) do { if(!(EXPR)) { return -1; } } while (0) + + DacpObjectData objData; + DacpMethodTableData dmtd; + DacpMethodTableFieldData vMethodTableFields; + DacpFieldDescData vFieldDesc; + DacpModuleData module; + static DWORD numInstanceFields = 0; // Static due to recursion visiting parents + + if (bFirst) + { + numInstanceFields = 0; + } + + EXITPOINT(objData.Request(g_sos, cdaObj) == S_OK); + EXITPOINT(dmtd.Request(g_sos, cdaMT) == S_OK); + + if (dmtd.ParentMethodTable) + { + DWORD retVal = GetObjFieldOffset (cdaObj, dmtd.ParentMethodTable, + wszFieldName, FALSE); + if (retVal != 0) + { + // return in case of error or success. + // Fall through for field-not-found. + return retVal; + } + } + + EXITPOINT (vMethodTableFields.Request(g_sos,cdaMT) == S_OK); + EXITPOINT (module.Request(g_sos,dmtd.Module) == S_OK); + + CLRDATA_ADDRESS dwAddr = vMethodTableFields.FirstField; + ToRelease<IMetaDataImport> pImport = MDImportForModule(&module); + + while (numInstanceFields < vMethodTableFields.wNumInstanceFields) + { + EXITPOINT (vFieldDesc.Request(g_sos, dwAddr) == S_OK); + + if (!vFieldDesc.bIsStatic) + { + DWORD offset = vFieldDesc.dwOffset + sizeof(BaseObject); + NameForToken_s (TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + if (_wcscmp (wszFieldName, g_mdName) == 0) + { + return offset; + } + numInstanceFields ++; + } + + dwAddr = vFieldDesc.NextField; + } + + // Field name not found... + return 0; + +#undef EXITPOINT +} + +// Returns an AppDomain address if AssemblyPtr is loaded into that domain only. Otherwise +// returns NULL +CLRDATA_ADDRESS IsInOneDomainOnly(CLRDATA_ADDRESS AssemblyPtr) +{ + CLRDATA_ADDRESS appDomain = NULL; + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + ExtOut("Unable to get appdomain store\n"); + return NULL; + } + + size_t AllocSize; + if (!ClrSafeInt<size_t>::multiply(sizeof(CLRDATA_ADDRESS), adstore.DomainCount, AllocSize)) + { + ReportOOM(); + return NULL; + } + + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[adstore.DomainCount]; + if (pArray==NULL) + { + ReportOOM(); + return NULL; + } + + if (g_sos->GetAppDomainList(adstore.DomainCount, pArray, NULL)!=S_OK) + { + ExtOut ("Failed to get appdomain list\n"); + return NULL; + } + + for (int i = 0; i < adstore.DomainCount; i++) + { + if (IsInterrupt()) + return NULL; + + DacpAppDomainData dadd; + if (dadd.Request(g_sos, pArray[i]) != S_OK) + { + ExtOut ("Unable to get AppDomain %p\n", SOS_PTR(pArray[i])); + return NULL; + } + + if (dadd.AssemblyCount) + { + size_t AssemblyAllocSize; + if (!ClrSafeInt<size_t>::multiply(sizeof(CLRDATA_ADDRESS), dadd.AssemblyCount, AssemblyAllocSize)) + { + ReportOOM(); + return NULL; + } + + ArrayHolder<CLRDATA_ADDRESS> pAsmArray = new CLRDATA_ADDRESS[dadd.AssemblyCount]; + if (pAsmArray==NULL) + { + ReportOOM(); + return NULL; + } + + if (g_sos->GetAssemblyList(dadd.AppDomainPtr,dadd.AssemblyCount,pAsmArray, NULL)!=S_OK) + { + ExtOut("Unable to get array of Assemblies\n"); + return NULL; + } + + for (LONG n = 0; n < dadd.AssemblyCount; n ++) + { + if (IsInterrupt()) + return NULL; + + if (AssemblyPtr == pAsmArray[n]) + { + if (appDomain != NULL) + { + // We have found more than one AppDomain that loaded this + // assembly, we must return NULL. + return NULL; + } + appDomain = dadd.AppDomainPtr; + } + } + } + } + + + return appDomain; +} + +CLRDATA_ADDRESS GetAppDomainForMT(CLRDATA_ADDRESS mtPtr) +{ + DacpMethodTableData mt; + if (mt.Request(g_sos, mtPtr) != S_OK) + { + return NULL; + } + + DacpModuleData module; + if (module.Request(g_sos, mt.Module) != S_OK) + { + return NULL; + } + + DacpAssemblyData assembly; + if (assembly.Request(g_sos, module.Assembly) != S_OK) + { + return NULL; + } + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + return NULL; + } + + return (assembly.ParentDomain == adstore.sharedDomain) ? + IsInOneDomainOnly(assembly.AssemblyPtr) : + assembly.ParentDomain; +} + +CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr) +{ + CLRDATA_ADDRESS appDomain = NULL; + + DacpObjectData objData; + if (objData.Request(g_sos,objPtr) != S_OK) + { + return NULL; + } + + // First check eeclass->module->assembly->domain. + // Then check the object flags word + // finally, search threads for a reference to the object, and look at the thread context. + + DacpMethodTableData mt; + if (mt.Request(g_sos,objData.MethodTable) != S_OK) + { + return NULL; + } + + DacpModuleData module; + if (module.Request(g_sos,mt.Module) != S_OK) + { + return NULL; + } + + DacpAssemblyData assembly; + if (assembly.Request(g_sos,module.Assembly) != S_OK) + { + return NULL; + } + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + return NULL; + } + + if (assembly.ParentDomain == adstore.sharedDomain) + { + sos::Object obj(TO_TADDR(objPtr)); + ULONG value = 0; + if (!obj.TryGetHeader(value)) + { + return NULL; + } + + DWORD adIndex = (value >> SBLK_APPDOMAIN_SHIFT) & SBLK_MASK_APPDOMAININDEX; + if ( ((value & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) != 0) || adIndex==0) + { + // No AppDomainID information. We'll make use of a heuristic. + // If the assembly is in the shared domain, we can report it as + // being in domain X if the only other domain that has the assembly + // loaded is domain X. + appDomain = IsInOneDomainOnly(assembly.AssemblyPtr); + if (appDomain == NULL && ((value & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) != 0)) + { + if ((value & BIT_SBLK_IS_HASHCODE) == 0) + { + UINT index = value & MASK_SYNCBLOCKINDEX; + // We have a syncblock, the appdomain ID may be in there. + DacpSyncBlockData syncBlockData; + if (syncBlockData.Request(g_sos,index) == S_OK) + { + appDomain = syncBlockData.appDomainPtr; + } + } + } + } + else if ((value & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) == 0) + { + size_t AllocSize; + if (!ClrSafeInt<size_t>::multiply(sizeof(CLRDATA_ADDRESS), adstore.DomainCount, AllocSize)) + { + return NULL; + } + // we know we have a non-zero adIndex. Find the appdomain. + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[adstore.DomainCount]; + if (pArray==NULL) + { + return NULL; + } + + if (g_sos->GetAppDomainList(adstore.DomainCount, pArray, NULL)!=S_OK) + { + return NULL; + } + + for (int i = 0; i < adstore.DomainCount; i++) + { + DacpAppDomainData dadd; + if (dadd.Request(g_sos, pArray[i]) != S_OK) + { + return NULL; + } + if (dadd.dwId == adIndex) + { + appDomain = pArray[i]; + break; + } + } + } + } + else + { + appDomain = assembly.ParentDomain; + } + + return appDomain; +} + +HRESULT FileNameForModule (DWORD_PTR pModuleAddr, __out_ecount (MAX_LONGPATH) WCHAR *fileName) +{ + DacpModuleData ModuleData; + fileName[0] = L'\0'; + + HRESULT hr = ModuleData.Request(g_sos, TO_CDADDR(pModuleAddr)); + if (SUCCEEDED(hr)) + { + hr = FileNameForModule(&ModuleData,fileName); + } + + return hr; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the file name given a Module. * +* * +\**********************************************************************/ +// fileName should be at least MAX_LONGPATH +HRESULT FileNameForModule (DacpModuleData *pModule, __out_ecount (MAX_LONGPATH) WCHAR *fileName) +{ + fileName[0] = L'\0'; + + HRESULT hr = S_OK; + CLRDATA_ADDRESS dwAddr = pModule->File; + if (dwAddr == 0) + { + // TODO: We have dynamic module + return E_NOTIMPL; + } + + CLRDATA_ADDRESS base = 0; + hr = g_sos->GetPEFileBase(dwAddr, &base); + if (SUCCEEDED(hr)) + { + hr = g_sos->GetPEFileName(dwAddr, MAX_LONGPATH, fileName, NULL); + if (SUCCEEDED(hr)) + { + if (fileName[0] != W('\0')) + return hr; // done + } +#ifndef FEATURE_PAL + // Try the base * + if (base) + { + hr = DllsName((ULONG_PTR) base, fileName); + } +#endif // !FEATURE_PAL + } + + // If we got here, either DllsName worked, or we couldn't find a name + return hr; +} + +void AssemblyInfo(DacpAssemblyData *pAssembly) +{ + ExtOut("ClassLoader: %p\n", SOS_PTR(pAssembly->ClassLoader)); + if ((ULONG64)pAssembly->AssemblySecDesc != NULL) + ExtOut("SecurityDescriptor: %p\n", SOS_PTR(pAssembly->AssemblySecDesc)); + ExtOut(" Module Name\n"); + + ArrayHolder<CLRDATA_ADDRESS> Modules = new CLRDATA_ADDRESS[pAssembly->ModuleCount]; + if (Modules == NULL + || g_sos->GetAssemblyModuleList(pAssembly->AssemblyPtr, pAssembly->ModuleCount, Modules, NULL) != S_OK) + { + ReportOOM(); + return; + } + + for (UINT n=0;n<pAssembly->ModuleCount;n++) + { + if (IsInterrupt()) + { + return; + } + + CLRDATA_ADDRESS ModuleAddr = Modules[n]; + DMLOut("%s " WIN86_8SPACES, DMLModule(ModuleAddr)); + DacpModuleData moduleData; + if (moduleData.Request(g_sos,ModuleAddr)==S_OK) + { + WCHAR fileName[MAX_LONGPATH]; + FileNameForModule (&moduleData, fileName); + if (fileName[0]) + { + ExtOut("%S\n", fileName); + } + else + { + ExtOut("%S\n", (moduleData.bIsReflection) ? W("Dynamic Module") : W("Unknown Module")); + } + } + } +} + +const char *GetStageText(DacpAppDomainDataStage stage) +{ + switch(stage) + { + case STAGE_CREATING: + return "CREATING"; + case STAGE_READYFORMANAGEDCODE: + return "READYFORMANAGEDCODE"; + case STAGE_ACTIVE: + return "ACTIVE"; + case STAGE_OPEN: + return "OPEN"; + case STAGE_UNLOAD_REQUESTED: + return "UNLOAD_REQUESTED"; + case STAGE_EXITING: + return "EXITING"; + case STAGE_EXITED: + return "EXITED"; + case STAGE_FINALIZING: + return "FINALIZING"; + case STAGE_FINALIZED: + return "FINALIZED"; + case STAGE_HANDLETABLE_NOACCESS: + return "HANDLETABLE_NOACCESS"; + case STAGE_CLEARED: + return "CLEARED"; + case STAGE_COLLECTED: + return "COLLECTED"; + case STAGE_CLOSED: + return "CLOSED"; + } + return "UNKNOWN"; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a domain. * +* * +\**********************************************************************/ +void DomainInfo (DacpAppDomainData *pDomain) +{ + ExtOut("LowFrequencyHeap: %p\n", SOS_PTR(pDomain->pLowFrequencyHeap)); + ExtOut("HighFrequencyHeap: %p\n", SOS_PTR(pDomain->pHighFrequencyHeap)); + ExtOut("StubHeap: %p\n", SOS_PTR(pDomain->pStubHeap)); + ExtOut("Stage: %s\n", GetStageText(pDomain->appDomainStage)); + if ((ULONG64)pDomain->AppSecDesc != NULL) + ExtOut("SecurityDescriptor: %p\n", SOS_PTR(pDomain->AppSecDesc)); + ExtOut("Name: "); + + if (g_sos->GetAppDomainName(pDomain->AppDomainPtr, mdNameLen, g_mdName, NULL)!=S_OK) + { + ExtOut("Error getting AppDomain friendly name\n"); + } + else + { + ExtOut("%S\n", (g_mdName[0] != L'\0') ? g_mdName : W("None")); + } + + if (pDomain->AssemblyCount == 0) + return; + + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[pDomain->AssemblyCount]; + if (pArray==NULL) + { + ReportOOM(); + return; + } + + if (g_sos->GetAssemblyList(pDomain->AppDomainPtr,pDomain->AssemblyCount,pArray, NULL)!=S_OK) + { + ExtOut("Unable to get array of Assemblies\n"); + return; + } + + LONG n; + // Assembly vAssembly; + for (n = 0; n < pDomain->AssemblyCount; n ++) + { + if (IsInterrupt()) + return; + + if (n != 0) + ExtOut("\n"); + + DMLOut("Assembly: %s", DMLAssembly(pArray[n])); + DacpAssemblyData assemblyData; + if (assemblyData.Request(g_sos, pArray[n], pDomain->AppDomainPtr) == S_OK) + { + if (assemblyData.isDynamic) + ExtOut(" (Dynamic)"); + + ExtOut(" ["); + if (g_sos->GetAssemblyName(pArray[n], mdNameLen, g_mdName, NULL) == S_OK) + ExtOut("%S", g_mdName); + ExtOut("]\n"); + + AssemblyInfo(&assemblyData); + } + } + + ExtOut("\n"); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a MethodDesc using * +* metadata API. * +* * +\**********************************************************************/ +BOOL NameForMD_s (DWORD_PTR pMD, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName) +{ + mdName[0] = L'\0'; + CLRDATA_ADDRESS StartAddr = TO_CDADDR(pMD); + DacpMethodDescData MethodDescData; + + // don't need to check for minidump file as all commands are seals + // We also do not have EEJitManager to validate anyway. + // + if (!IsMiniDumpFile() && MethodDescData.Request(g_sos,StartAddr) != S_OK) + { + ExtOut("%p is not a MethodDesc\n", SOS_PTR(StartAddr)); + return FALSE; + } + + if (g_sos->GetMethodDescName(StartAddr, mdNameLen, mdName, NULL) != S_OK) + { + wcscpy_s(mdName, capacity_mdName, W("UNKNOWN")); + return FALSE; + } + return TRUE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a MethodTable using * +* metadata API. * +* * +\**********************************************************************/ +BOOL NameForMT_s(DWORD_PTR MTAddr, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName) +{ + HRESULT hr = g_sos->GetMethodTableName(TO_CDADDR(MTAddr), (ULONG32)capacity_mdName, mdName, NULL); + return SUCCEEDED(hr); +} + +WCHAR *CreateMethodTableName(TADDR mt, TADDR cmt) +{ + bool array = false; + WCHAR *res = NULL; + + if (mt == sos::MethodTable::GetFreeMT()) + { + res = new WCHAR[5]; + wcscpy_s(res, 5, W("Free")); + return res; + } + + if (mt == sos::MethodTable::GetArrayMT() && cmt != NULL) + { + mt = cmt; + array = true; + } + + unsigned int needed = 0; + HRESULT hr = g_sos->GetMethodTableName(mt, 0, NULL, &needed); + + // If failed, we will return null. + if (SUCCEEDED(hr)) + { + // +2 for [], if we need it. + res = new WCHAR[needed+2]; + hr = g_sos->GetMethodTableName(mt, needed, res, NULL); + + if (FAILED(hr)) + { + delete [] res; + res = NULL; + } + else if (array) + { + res[needed-1] = '['; + res[needed] = ']'; + res[needed+1] = 0; + } + } + + return res; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if str2 is a substring of str1 and str1 and str2 * +* share the same file path. +* * +\**********************************************************************/ +BOOL IsSameModuleName (const char *str1, const char *str2) +{ + if (strlen (str1) < strlen (str2)) + return FALSE; + const char *ptr1 = str1 + strlen(str1)-1; + const char *ptr2 = str2 + strlen(str2)-1; + while (ptr2 >= str2) + { +#ifndef FEATURE_PAL + if (tolower(*ptr1) != tolower(*ptr2)) +#else + if (*ptr1 != *ptr2) +#endif + { + return FALSE; + } + ptr2--; + ptr1--; + } + if (ptr1 >= str1 && *ptr1 != DIRECTORY_SEPARATOR_CHAR_A && *ptr1 != ':') + { + return FALSE; + } + return TRUE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if moduleAddr is the address of a module. * +* * +\**********************************************************************/ +BOOL IsModule (DWORD_PTR moduleAddr) +{ + DacpModuleData module; + return (module.Request(g_sos, TO_CDADDR(moduleAddr))==S_OK); +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if value is the address of a MethodTable. * +* We verify that MethodTable and EEClass are right. +* * +\**********************************************************************/ +BOOL IsMethodTable (DWORD_PTR value) +{ + DacpMethodTableData mtabledata; + if (mtabledata.Request(g_sos, TO_CDADDR(value))!=S_OK) + { + return FALSE; + } + + return TRUE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if value is the address of a MethodDesc. * +* We verify that MethodTable and EEClass are right. +* * +\**********************************************************************/ +BOOL IsMethodDesc (DWORD_PTR value) +{ + // Just by retrieving one successfully from the DAC, we know we have a MethodDesc. + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(value)) != S_OK) + { + return FALSE; + } + + return TRUE; +} + +DacpUsefulGlobalsData g_special_usefulGlobals; + +BOOL IsObjectArray (DacpObjectData *pData) +{ + if (pData->ObjectType == OBJ_ARRAY) + return g_special_usefulGlobals.ArrayMethodTable == pData->MethodTable; + + return FALSE; +} + +BOOL IsObjectArray (DWORD_PTR obj) +{ + DWORD_PTR mtAddr = NULL; + if (SUCCEEDED(GetMTOfObject(obj, &mtAddr))) + return TO_TADDR(g_special_usefulGlobals.ArrayMethodTable) == mtAddr; + + return FALSE; +} + +BOOL IsStringObject (size_t obj) +{ + DWORD_PTR mtAddr = NULL; + + if (SUCCEEDED(GetMTOfObject(obj, &mtAddr))) + return TO_TADDR(g_special_usefulGlobals.StringMethodTable) == mtAddr; + + return FALSE; +} + +void DumpStackObjectsOutput(const char *location, DWORD_PTR objAddr, BOOL verifyFields) +{ + // rule out pointers that are outside of the gc heap. + if (g_snapshot.GetHeap(objAddr) == NULL) + return; + + DacpObjectData objectData; + if (objectData.Request(g_sos, TO_CDADDR(objAddr)) != S_OK) + return; + + if (sos::IsObject(objAddr, verifyFields != FALSE) + && !sos::MethodTable::IsFreeMT(TO_TADDR(objectData.MethodTable))) + { + DMLOut("%-" POINTERSIZE "s %s ", location, DMLObject(objAddr)); + if (g_sos->GetObjectClassName(TO_CDADDR(objAddr), mdNameLen, g_mdName, NULL)==S_OK) + { + ExtOut("%S", g_mdName); + + if (IsStringObject(objAddr)) + { + ExtOut(" "); + StringObjectContent(objAddr, FALSE, 40); + } + else if (IsObjectArray(objAddr) && + (g_sos->GetMethodTableName(objectData.ElementTypeHandle, mdNameLen, g_mdName, NULL) == S_OK)) + { + ExtOut(" "); + ExtOut("(%S[])", g_mdName); + } + } + else + { + ExtOut("<unknown type>"); + } + ExtOut("\n"); + } +} + +void DumpStackObjectsOutput(DWORD_PTR ptr, DWORD_PTR objAddr, BOOL verifyFields) +{ + char location[64]; + sprintf_s(location, 64, "%p", (DWORD_PTR *)ptr); + + DumpStackObjectsOutput(location, objAddr, verifyFields); +} + +void DumpStackObjectsInternal(size_t StackTop, size_t StackBottom, BOOL verifyFields) +{ + for (DWORD_PTR ptr = StackTop; ptr <= StackBottom; ptr += sizeof(DWORD_PTR)) + { + if (IsInterrupt()) + return; + + DWORD_PTR objAddr; + move_xp(objAddr, ptr); + + DumpStackObjectsOutput(ptr, objAddr, verifyFields); + } +} + +void DumpRegObjectHelper(const char *regName, BOOL verifyFields) +{ + DWORD_PTR reg; +#ifdef FEATURE_PAL + if (FAILED(g_ExtRegisters->GetValueByName(regName, ®))) + return; +#else + DEBUG_VALUE value; + ULONG IREG; + if (FAILED(g_ExtRegisters->GetIndexByName(regName, &IREG)) || + FAILED(g_ExtRegisters->GetValue(IREG, &value))) + return; + +#if defined(SOS_TARGET_X86) || defined(SOS_TARGET_ARM) + reg = (DWORD_PTR) value.I32; +#elif defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) + reg = (DWORD_PTR) value.I64; +#else +#error Unsupported target +#endif +#endif // FEATURE_PAL + + DumpStackObjectsOutput(regName, reg, verifyFields); +} + +void DumpStackObjectsHelper ( + TADDR StackTop, + TADDR StackBottom, + BOOL verifyFields) +{ + ExtOut(g_targetMachine->GetDumpStackObjectsHeading()); + + LPCSTR* regs; + unsigned int cnt; + g_targetMachine->GetGCRegisters(®s, &cnt); + + for (size_t i = 0; i < cnt; ++i) + DumpRegObjectHelper(regs[i], verifyFields); + + // Make certain StackTop is dword aligned: + DumpStackObjectsInternal(StackTop & ~ALIGNCONST, StackBottom, verifyFields); +} + +void AddToModuleList(DWORD_PTR * &moduleList, int &numModule, int &maxList, + DWORD_PTR dwModuleAddr) +{ + int i; + for (i = 0; i < numModule; i ++) + { + if (moduleList[i] == dwModuleAddr) + break; + } + if (i == numModule) + { + moduleList[numModule] = dwModuleAddr; + numModule ++; + if (numModule == maxList) + { + int listLength = 0; + if (!ClrSafeInt<int>::multiply(maxList, 2, listLength)) + { + ExtOut("<integer overflow>\n"); + numModule = 0; + ControlC = 1; + return; + } + DWORD_PTR *list = new DWORD_PTR [listLength]; + + if (list == NULL) + { + numModule = 0; + ControlC = 1; + return; + } + memcpy (list, moduleList, maxList * sizeof(PVOID)); + delete[] moduleList; + moduleList = list; + maxList *= 2; + } + } +} + +BOOL IsFusionLoadedModule (LPCSTR fusionName, LPCSTR mName) +{ + // The fusion name will be in this format: + // <module name>, Version=<version>, Culture=<culture>, PublicKeyToken=<token> + // If fusionName up to the comma matches mName (case insensitive), + // we consider that a match was found. + LPCSTR commaPos = strchr (fusionName, ','); + if (commaPos) + { + // verify that fusionName and mName match up to a comma. + while (*fusionName != ',') + { + if (*mName == '\0') + { + return FALSE; + } + +#ifndef FEATURE_PAL + if (tolower(*fusionName) != tolower(*mName)) +#else + if (*fusionName != *mName) +#endif + { + return FALSE; + } + fusionName++; + mName++; + } + return TRUE; + } + return FALSE; +} + +BOOL DebuggerModuleNamesMatch (CLRDATA_ADDRESS PEFileAddr, ___in __in_z LPSTR mName) +{ + // Another way to see if a module is the same is + // to accept that mName may be the debugger's name for + // a loaded module. We can get the debugger's name for + // the module we are looking at right now, and compare + // it with mName, if they match exactly, we can add + // the module to the list. + if (PEFileAddr) + { + CLRDATA_ADDRESS pebase = 0; + if (g_sos->GetPEFileBase(PEFileAddr, &pebase) == S_OK) + { + if (pebase) + { + ULONG Index; + ULONG64 base; + if (g_ExtSymbols->GetModuleByOffset(pebase, 0, &Index, &base) == S_OK) + { + CHAR ModuleName[MAX_LONGPATH+1]; + + if (g_ExtSymbols->GetModuleNames(Index, base, NULL, 0, NULL, ModuleName, + MAX_LONGPATH, NULL, NULL, 0, NULL) == S_OK) + { + if (_stricmp (ModuleName, mName) == 0) + { + return TRUE; + } + } + } + } + } + } + return FALSE; +} + +DWORD_PTR *ModuleFromName(__in_opt LPSTR mName, int *numModule) +{ + if (numModule == NULL) + return NULL; + + DWORD_PTR *moduleList = NULL; + *numModule = 0; + + DacpAppDomainStoreData adsData; + if (adsData.Request(g_sos)!=S_OK) + return NULL; + + ArrayHolder<CLRDATA_ADDRESS> pAssemblyArray = NULL; + ArrayHolder<CLRDATA_ADDRESS> pModules = NULL; + int arrayLength = 0; + if (!ClrSafeInt<int>::addition(adsData.DomainCount, 2, arrayLength)) + { + ExtOut("<integer overflow>\n"); + return NULL; + } + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[arrayLength]; + + if (pArray==NULL) + { + ReportOOM(); + return NULL; + } + + pArray[0] = adsData.systemDomain; + pArray[1] = adsData.sharedDomain; + if (g_sos->GetAppDomainList(adsData.DomainCount, pArray.GetPtr()+2, NULL)!=S_OK) + { + ExtOut("Unable to get array of AppDomains\n"); + return NULL; + } + + // List all domain + size_t AllocSize; + int maxList = arrayLength; // account for system and shared domains + if (maxList <= 0 || !ClrSafeInt<size_t>::multiply(maxList, sizeof(PVOID), AllocSize)) + { + ExtOut("Integer overflow error.\n"); + return NULL; + } + + moduleList = new DWORD_PTR[maxList]; + if (moduleList == NULL) + { + ReportOOM(); + return NULL; + } + + WCHAR StringData[MAX_LONGPATH]; + char fileName[sizeof(StringData)/2]; + + // Search all domains to find a module + for (int n = 0; n < adsData.DomainCount+2; n++) + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + goto Failure; + } + + DacpAppDomainData appDomain; + if (FAILED(appDomain.Request(g_sos,pArray[n]))) + { + // Don't print a failure message here, there is a very normal case when checking + // for modules after clr is loaded but before any AppDomains or assemblies are created + // for example: + // >sxe ld:clr + // >g + // ... + // ModLoad: clr.dll + // >!bpmd Foo.dll Foo.Bar + + // we will correctly give the answer that whatever module you were looking for, it isn't loaded yet + goto Failure; + } + + if (appDomain.AssemblyCount) + { + pAssemblyArray = new CLRDATA_ADDRESS[appDomain.AssemblyCount]; + if (pAssemblyArray==NULL) + { + ReportOOM(); + goto Failure; + } + + if (FAILED(g_sos->GetAssemblyList(appDomain.AppDomainPtr, appDomain.AssemblyCount, pAssemblyArray, NULL))) + { + ExtOut("Unable to get array of Assemblies for the given AppDomain..\n"); + goto Failure; + } + + for (int nAssem = 0; nAssem < appDomain.AssemblyCount; nAssem ++) + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + goto Failure; + } + + DacpAssemblyData assemblyData; + if (FAILED(assemblyData.Request(g_sos, pAssemblyArray[nAssem]))) + { + ExtOut("Failed to request assembly.\n"); + goto Failure; + } + + pModules = new CLRDATA_ADDRESS[assemblyData.ModuleCount]; + if (FAILED(g_sos->GetAssemblyModuleList(assemblyData.AssemblyPtr, assemblyData.ModuleCount, pModules, NULL))) + { + ExtOut("Failed to get the modules for the given assembly.\n"); + goto Failure; + } + + for (UINT nModule = 0; nModule < assemblyData.ModuleCount; nModule++) + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + goto Failure; + } + + CLRDATA_ADDRESS ModuleAddr = pModules[nModule]; + DacpModuleData ModuleData; + if (FAILED(ModuleData.Request(g_sos,ModuleAddr))) + { + ExtOut("Failed to request Module data from assembly.\n"); + goto Failure; + } + + FileNameForModule ((DWORD_PTR)ModuleAddr, StringData); + int m; + for (m = 0; StringData[m] != L'\0'; m++) + { + fileName[m] = (char)StringData[m]; + } + fileName[m] = '\0'; + + if ((mName == NULL) || + IsSameModuleName(fileName, mName) || + DebuggerModuleNamesMatch(ModuleData.File, mName) || + IsFusionLoadedModule(fileName, mName)) + { + AddToModuleList(moduleList, *numModule, maxList, (DWORD_PTR)ModuleAddr); + } + } + + pModules = NULL; + } + pAssemblyArray = NULL; + } + } + + return moduleList; + + // We do not want to return a half-constructed list. Instead, we return NULL on a failure. +Failure: + delete [] moduleList; + return NULL; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a name. * +* * +\**********************************************************************/ +void GetInfoFromName(DWORD_PTR ModulePtr, const char* name) +{ + ToRelease<IMetaDataImport> pImport = MDImportForModule (ModulePtr); + if (pImport == 0) + return; + + static WCHAR wszName[MAX_CLASSNAME_LENGTH]; + size_t n; + size_t length = strlen (name); + for (n = 0; n <= length; n ++) + wszName[n] = name[n]; + + // First enumerate methods. We're taking advantage of the DAC's + // CLRDataModule::EnumMethodDefinitionByName which can parse + // method names (whether in nested classes, or explicit interface + // method implementations). + ToRelease<IXCLRDataModule> ModuleDefinition; + if (g_sos->GetModule(ModulePtr, &ModuleDefinition) == S_OK) + { + CLRDATA_ENUM h; + if (ModuleDefinition->StartEnumMethodDefinitionsByName(wszName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + BOOL fStatus = FALSE; + while (ModuleDefinition->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + if (fStatus) + ExtOut("-----------------------\n"); + + mdTypeDef token; + if (pMeth->GetTokenAndScope(&token, NULL) == S_OK) + { + GetInfoFromModule(ModulePtr, token); + fStatus = TRUE; + } + pMeth->Release(); + } + ModuleDefinition->EndEnumMethodDefinitionsByName(h); + if (fStatus) + return; + } + } + + // Now look for types, type members and fields + mdTypeDef cl; + mdToken tkEnclose = mdTokenNil; + WCHAR *pName; + WCHAR *pHead = wszName; + while ( ((pName = _wcschr (pHead,L'+')) != NULL) || + ((pName = _wcschr (pHead,L'/')) != NULL)) { + pName[0] = L'\0'; + if (FAILED(pImport->FindTypeDefByName(pHead,tkEnclose,&tkEnclose))) + return; + pHead = pName+1; + } + + pName = pHead; + + // @todo: Handle Nested classes correctly. + if (SUCCEEDED (pImport->FindTypeDefByName (pName, tkEnclose, &cl))) + { + GetInfoFromModule(ModulePtr, cl); + return; + } + + // See if it is a method + WCHAR *pwzMethod; + if ((pwzMethod = _wcsrchr(pName, L'.')) == NULL) + return; + + if (pwzMethod[-1] == L'.') + pwzMethod --; + pwzMethod[0] = L'\0'; + pwzMethod ++; + + // @todo: Handle Nested classes correctly. + if (SUCCEEDED(pImport->FindTypeDefByName (pName, tkEnclose, &cl))) + { + mdMethodDef token; + ULONG cTokens; + HCORENUM henum = NULL; + + // is Member? + henum = NULL; + if (SUCCEEDED (pImport->EnumMembersWithName (&henum, cl, pwzMethod, + &token, 1, &cTokens)) + && cTokens == 1) + { + ExtOut("Member (mdToken token) of\n"); + GetInfoFromModule(ModulePtr, cl); + return; + } + + // is Field? + henum = NULL; + if (SUCCEEDED (pImport->EnumFieldsWithName (&henum, cl, pwzMethod, + &token, 1, &cTokens)) + && cTokens == 1) + { + ExtOut("Field (mdToken token) of\n"); + GetInfoFromModule(ModulePtr, cl); + return; + } + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a token. * +* * +\**********************************************************************/ +DWORD_PTR GetMethodDescFromModule(DWORD_PTR ModuleAddr, ULONG token) +{ + if (TypeFromToken(token) != mdtMethodDef) + return NULL; + + CLRDATA_ADDRESS md = 0; + if (FAILED(g_sos->GetMethodDescFromToken(ModuleAddr, token, &md))) + { + return NULL; + } + else if (0 == md) + { + // a NULL ReturnValue means the method desc is not loaded yet + return MD_NOT_YET_LOADED; + } + else if ( !IsMethodDesc((DWORD_PTR)md)) + { + return NULL; + } + + return (DWORD_PTR)md; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the MethodDefinitions given a name. * +* * +\**********************************************************************/ +HRESULT GetMethodDefinitionsFromName(TADDR ModulePtr, IXCLRDataModule* mod, const char *name, IXCLRDataMethodDefinition **ppOut, int numMethods, int *numMethodsNeeded) +{ + if (name == NULL) + return E_FAIL; + + size_t n; + size_t length = strlen (name); + for (n = 0; n <= length; n ++) + g_mdName[n] = name[n]; + + CLRDATA_ENUM h; + int methodCount = 0; + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + while (mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + methodCount++; + pMeth->Release(); + } + mod->EndEnumMethodDefinitionsByName(h); + } + + if(numMethodsNeeded != NULL) + *numMethodsNeeded = methodCount; + if(ppOut == NULL) + return S_OK; + if(numMethods > methodCount) + numMethods = methodCount; + + if (methodCount > 0) + { + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + for (int i = 0; i < numMethods && mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK; i++) + { + ppOut[i] = pMeth; + } + mod->EndEnumMethodDefinitionsByName(h); + } + } + + return S_OK; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a name. * +* * +\**********************************************************************/ +HRESULT GetMethodDescsFromName(TADDR ModulePtr, IXCLRDataModule* mod, const char *name, DWORD_PTR **pOut,int *numMethods) +{ + if (name == NULL || pOut == NULL || numMethods == NULL) + return E_FAIL; + + *pOut = NULL; + *numMethods = 0; + + size_t n; + size_t length = strlen (name); + for (n = 0; n <= length; n ++) + g_mdName[n] = name[n]; + + CLRDATA_ENUM h; + int methodCount = 0; + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + while (mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + methodCount++; + pMeth->Release(); + } + mod->EndEnumMethodDefinitionsByName(h); + } + + if (methodCount > 0) + { + *pOut = new TADDR[methodCount]; + if (*pOut==NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + *numMethods = methodCount; + + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + int i = 0; + IXCLRDataMethodDefinition *pMeth = NULL; + while (mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + mdTypeDef token; + if (pMeth->GetTokenAndScope(&token, NULL) != S_OK) + (*pOut)[i] = NULL; + (*pOut)[i] = GetMethodDescFromModule(ModulePtr, token); + if ((*pOut)[i] == NULL) + { + *numMethods = 0; + return E_FAIL; + } + i++; + pMeth->Release(); + } + mod->EndEnumMethodDefinitionsByName(h); + } + } + + return S_OK; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a token. * +* * +\**********************************************************************/ +void GetInfoFromModule (DWORD_PTR ModuleAddr, ULONG token, DWORD_PTR *ret) +{ + switch (TypeFromToken(token)) + { + case mdtMethodDef: + break; + case mdtTypeDef: + break; + case mdtTypeRef: + break; + case mdtFieldDef: + break; + default: + ExtOut("This token type is not supported\n"); + return; + break; + } + + CLRDATA_ADDRESS md = 0; + if (FAILED(g_sos->GetMethodDescFromToken(ModuleAddr, token, &md)) || !IsValidToken (ModuleAddr, token)) + { + ExtOut("<invalid module token>\n"); + return; + } + + if (ret != NULL) + { + *ret = (DWORD_PTR)md; + return; + } + + ExtOut("Token: %p\n", SOS_PTR(token)); + + switch (TypeFromToken(token)) + { + case mdtFieldDef: + { + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + ExtOut("Field name: %S\n", g_mdName); + break; + } + case mdtMethodDef: + { + if (md) + { + DMLOut("MethodDesc: %s\n", DMLMethodDesc(md)); + + // Easiest to get full parameterized method name from ..::GetMethodName + if (g_sos->GetMethodDescName(md, mdNameLen, g_mdName, NULL) != S_OK) + { + // Fall back to just method name without parameters.. + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + } + } + else + { + ExtOut("MethodDesc: <not loaded yet>\n"); + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + } + + ExtOut("Name: %S\n", g_mdName); + // Nice to have a little more data + if (md) + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, md) == S_OK) + { + if (MethodDescData.bHasNativeCode) + { + DMLOut("JITTED Code Address: %s\n", DMLIP(MethodDescData.NativeCodeAddr)); + } + else + { +#ifndef FEATURE_PAL + if (IsDMLEnabled()) + DMLOut("Not JITTED yet. Use <exec cmd=\"!bpmd -md %p\">!bpmd -md %p</exec> to break on run.\n", + SOS_PTR(md), SOS_PTR(md)); + else + ExtOut("Not JITTED yet. Use !bpmd -md %p to break on run.\n", SOS_PTR(md)); +#else + ExtOut("Not JITTED yet. Use 'bpmd -md %p' to break on run.\n", SOS_PTR(md)); +#endif + } + } + else + { + ExtOut ("<Error getting MethodDesc information>\n"); + } + } + else + { + ExtOut("Not JITTED yet.\n"); + } + break; + } + case mdtTypeDef: + case mdtTypeRef: + { + if (md) + { + DMLOut("MethodTable: %s\n", DMLMethodTable(md)); + DacpMethodTableData mtabledata; + if (mtabledata.Request(g_sos, md) == S_OK) + { + DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class)); + } + else + { + ExtOut("EEClass: <error getting EEClass>\n"); + } + } + else + { + ExtOut("MethodTable: <not loaded yet>\n"); + ExtOut("EEClass: <not loaded yet>\n"); + } + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + ExtOut("Name: %S\n", g_mdName); + break; + } + default: + break; + } + return; +} + +BOOL IsMTForFreeObj(DWORD_PTR pMT) +{ + return (pMT == g_special_usefulGlobals.FreeMethodTable); +} + +const char *EHTypeName(EHClauseType et) +{ + if (et == EHFault) + return "FAULT"; + else if (et == EHFinally) + return "FINALLY"; + else if (et == EHFilter) + return "FILTER"; + else if (et == EHTyped) + return "TYPED"; + else + return "UNKNOWN"; +} + +void DumpRejitData(DacpReJitData * pReJitData) +{ + ExtOut(" ReJITID %p: ", SOS_PTR(pReJitData->rejitID)); + DMLOut("CodeAddr = %s", DMLIP(pReJitData->NativeCodeAddr)); + + LPCSTR szFlags; + switch (pReJitData->flags) + { + default: + case DacpReJitData::kUnknown: + szFlags = ""; + break; + + case DacpReJitData::kRequested: + szFlags = " (READY to jit on next call)"; + break; + + case DacpReJitData::kActive: + szFlags = " (CURRENT)"; + break; + + case DacpReJitData::kReverted: + szFlags = " (reverted)"; + break; + } + ExtOut("%s\n", szFlags); +} + +// For !ip2md requests, this function helps us ensure that rejitted version corresponding +// to the specified IP always gets dumped. It may have already been dumped if it was the +// current rejit version (which is always dumped) or one of the reverted versions that we +// happened to dump before we clipped their number down to kcRejitDataRevertedMax. +BOOL ShouldDumpRejitDataRequested(DacpMethodDescData * pMethodDescData, DacpReJitData * pRevertedRejitData, UINT cRevertedRejitData) +{ + if (pMethodDescData->rejitDataRequested.rejitID == 0) + return FALSE; + + if (pMethodDescData->rejitDataRequested.rejitID == pMethodDescData->rejitDataCurrent.rejitID) + return FALSE; + + for (ULONG i=0; i < cRevertedRejitData; i++) + { + if (pMethodDescData->rejitDataRequested.rejitID == pRevertedRejitData[i].rejitID) + return FALSE; + } + + return TRUE; +} + + +void DumpAllRejitDataIfNecessary(DacpMethodDescData * pMethodDescData, DacpReJitData * pRevertedRejitData, UINT cRevertedRejitData) +{ + // If there's no rejit info to output, then skip + if ((pMethodDescData->rejitDataCurrent.rejitID == 0) && + (pMethodDescData->rejitDataRequested.rejitID == 0) && + (cRevertedRejitData == 0)) + { + return; + } + ExtOut("ReJITed versions:\n"); + + // Dump CURRENT rejit info + DumpRejitData(&pMethodDescData->rejitDataCurrent); + + // Dump reverted rejit infos + for (ULONG i=0; i < cRevertedRejitData; i++) + { + DumpRejitData(&pRevertedRejitData[i]); + } + + // For !ip2md, ensure we dump the rejit version corresponding to the specified IP + // (if not already dumped) + if (ShouldDumpRejitDataRequested(pMethodDescData, pRevertedRejitData, cRevertedRejitData)) + DumpRejitData(&pMethodDescData->rejitDataRequested); + + // If we maxed out the reverted versions we dumped, let user know there may be more + if (cRevertedRejitData == kcMaxRevertedRejitData) + ExtOut(" (... possibly more reverted versions ...)\n"); +} + +void DumpMDInfoFromMethodDescData(DacpMethodDescData * pMethodDescData, DacpReJitData * pRevertedRejitData, UINT cRevertedRejitData, BOOL fStackTraceFormat) +{ + static WCHAR wszNameBuffer[1024]; // should be large enough + BOOL bFailed = FALSE; + if (g_sos->GetMethodDescName(pMethodDescData->MethodDescPtr, 1024, wszNameBuffer, NULL) != S_OK) + { + wcscpy_s(wszNameBuffer, _countof(wszNameBuffer), W("UNKNOWN")); + bFailed = TRUE; + } + + if (!fStackTraceFormat) + { + ExtOut("Method Name: %S\n", wszNameBuffer); + + DacpMethodTableData mtdata; + if (SUCCEEDED(mtdata.Request(g_sos, pMethodDescData->MethodTablePtr))) + { + DMLOut("Class: %s\n", DMLClass(mtdata.Class)); + } + + DMLOut("MethodTable: %s\n", DMLMethodTable(pMethodDescData->MethodTablePtr)); + ExtOut("mdToken: %p\n", SOS_PTR(pMethodDescData->MDToken)); + DMLOut("Module: %s\n", DMLModule(pMethodDescData->ModulePtr)); + ExtOut("IsJitted: %s\n", pMethodDescData->bHasNativeCode ? "yes" : "no"); + DMLOut("CodeAddr: %s\n", DMLIP(pMethodDescData->NativeCodeAddr)); + + DacpMethodDescTransparencyData transparency; + if (SUCCEEDED(transparency.Request(g_sos, pMethodDescData->MethodDescPtr))) + { + ExtOut("Transparency: %s\n", GetTransparency(transparency)); + } + + DumpAllRejitDataIfNecessary(pMethodDescData, pRevertedRejitData, cRevertedRejitData); + } + else + { + if (!bFailed) + { + ExtOut("%S", wszNameBuffer); + } + else + { + // Only clutter the display with module/token for cases where we + // can't get the MethodDesc name for some reason. + DMLOut("Unknown MethodDesc (Module %s, mdToken %08x)", + DMLModule(pMethodDescData->ModulePtr), + pMethodDescData->MDToken); + } + } +} + +void DumpMDInfo(DWORD_PTR dwMethodDescAddr, CLRDATA_ADDRESS dwRequestedIP /* = 0 */, BOOL fStackTraceFormat /* = FALSE */) +{ + DacpMethodDescData MethodDescData; + DacpReJitData revertedRejitData[kcMaxRevertedRejitData]; + ULONG cNeededRevertedRejitData; + if (g_sos->GetMethodDescData( + TO_CDADDR(dwMethodDescAddr), + dwRequestedIP, + &MethodDescData, + _countof(revertedRejitData), + revertedRejitData, + &cNeededRevertedRejitData) != S_OK) + { + ExtOut("%p is not a MethodDesc\n", SOS_PTR(dwMethodDescAddr)); + return; + } + + DumpMDInfoFromMethodDescData(&MethodDescData, revertedRejitData, cNeededRevertedRejitData, fStackTraceFormat); +} + +void GetDomainList (DWORD_PTR *&domainList, int &numDomain) +{ + DacpAppDomainStoreData adsData; + + numDomain = 0; + + if (adsData.Request(g_sos)!=S_OK) + { + return; + } + + // Do prefast integer checks before the malloc. + size_t AllocSize; + LONG DomainAllocCount; + if (!ClrSafeInt<LONG>::addition(adsData.DomainCount, 2, DomainAllocCount) || + !ClrSafeInt<size_t>::multiply(DomainAllocCount, sizeof(PVOID), AllocSize) || + (domainList = new DWORD_PTR[DomainAllocCount]) == NULL) + { + return; + } + + domainList[numDomain++] = (DWORD_PTR) adsData.systemDomain; + domainList[numDomain++] = (DWORD_PTR) adsData.sharedDomain; + + CLRDATA_ADDRESS *pArray = new CLRDATA_ADDRESS[adsData.DomainCount]; + if (pArray==NULL) + { + return; + } + + if (g_sos->GetAppDomainList(adsData.DomainCount, pArray, NULL)!=S_OK) + { + delete [] pArray; + return; + } + + for (int n=0;n<adsData.DomainCount;n++) + { + if (IsInterrupt()) + break; + domainList[numDomain++] = (DWORD_PTR) pArray[n]; + } + + delete [] pArray; +} + + +HRESULT GetThreadList(DWORD_PTR **threadList, int *numThread) +{ + _ASSERTE(threadList != NULL); + _ASSERTE(numThread != NULL); + + if (threadList == NULL || numThread == NULL) + { + return E_FAIL; + } + + *numThread = 0; + + DacpThreadStoreData ThreadStore; + if ( ThreadStore.Request(g_sos) != S_OK) + { + ExtOut("Failed to request threads from the thread store."); + return E_FAIL; + } + + *threadList = new DWORD_PTR[ThreadStore.threadCount]; + if (*threadList == NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread != NULL) + { + if (IsInterrupt()) + return S_FALSE; + + DacpThreadData Thread; + if (Thread.Request(g_sos, CurThread) != S_OK) + { + ExtOut("Failed to request Thread at %p\n", SOS_PTR(CurThread)); + return E_FAIL; + } + + (*threadList)[(*numThread)++] = (DWORD_PTR)CurThread; + CurThread = Thread.nextThread; + } + + return S_OK; +} + +CLRDATA_ADDRESS GetCurrentManagedThread () +{ + DacpThreadStoreData ThreadStore; + ThreadStore.Request(g_sos); + + ULONG Tid; + g_ExtSystem->GetCurrentThreadSystemId(&Tid); + + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread) + { + DacpThreadData Thread; + if (Thread.Request(g_sos, CurThread) != S_OK) + { + return NULL; + } + + if (Thread.osThreadId == Tid) + { + return CurThread; + } + + CurThread = Thread.nextThread; + } + return NULL; +} + + +void ReloadSymbolWithLineInfo() +{ +#ifndef FEATURE_PAL + static BOOL bLoadSymbol = FALSE; + if (!bLoadSymbol) + { + ULONG Options; + g_ExtSymbols->GetSymbolOptions(&Options); + if (!(Options & SYMOPT_LOAD_LINES)) + { + g_ExtSymbols->AddSymbolOptions(SYMOPT_LOAD_LINES); + + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName(MSCOREE_SHIM_A, 0, NULL, NULL))) + g_ExtSymbols->Reload("/f " MSCOREE_SHIM_A); + + EEFLAVOR flavor = GetEEFlavor(); + if (flavor == MSCORWKS) + g_ExtSymbols->Reload("/f " MAIN_CLR_DLL_NAME_A); + } + + // reload mscoree.pdb and clrjit.pdb to get line info + bLoadSymbol = TRUE; + } +#endif +} + +// Return 1 if the function is our stub +// Return MethodDesc if the function is managed +// Otherwise return 0 +size_t FunctionType (size_t EIP) +{ + ULONG64 base = 0; + ULONG ulLoaded, ulUnloaded, ulIndex; + + // Get the number of loaded and unloaded modules + if (FAILED(g_ExtSymbols->GetNumberModules(&ulLoaded, &ulUnloaded))) + return 0; + + + if (SUCCEEDED(g_ExtSymbols->GetModuleByOffset(TO_CDADDR(EIP), 0, &ulIndex, &base)) && base != 0) + { + if (ulIndex < ulLoaded) + { + IMAGE_DOS_HEADER DosHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(base), &DosHeader, sizeof(DosHeader), NULL) != S_OK) + return 0; + IMAGE_NT_HEADERS Header; + if (g_ExtData->ReadVirtual(TO_CDADDR(base + DosHeader.e_lfanew), &Header, sizeof(Header), NULL) != S_OK) + return 0; + // If there is no COMHeader, this can not be managed code. + if (Header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COMHEADER].VirtualAddress == 0) + return 0; + + IMAGE_COR20_HEADER ComPlusHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(base + Header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COMHEADER].VirtualAddress), + &ComPlusHeader, sizeof(ComPlusHeader), NULL) != S_OK) + return 0; + + // If there is no Precompiled image info, it can not be prejit code + if (ComPlusHeader.ManagedNativeHeader.VirtualAddress == 0) { + return 0; + } + } + } + + CLRDATA_ADDRESS dwStartAddr = TO_CDADDR(EIP); + CLRDATA_ADDRESS pMD; + if (g_sos->GetMethodDescPtrFromIP(dwStartAddr, &pMD) != S_OK) + { + return 1; + } + + return (size_t) pMD; +} + +#ifndef FEATURE_PAL + +// +// Gets version info for the CLR in the debuggee process. +// +BOOL GetEEVersion(VS_FIXEDFILEINFO *pFileInfo) +{ + _ASSERTE(g_ExtSymbols2); + _ASSERTE(pFileInfo); + // Grab the version info directly from the module. + return g_ExtSymbols2->GetModuleVersionInformation(DEBUG_ANY_ID, + moduleInfo[GetEEFlavor()].baseAddr, + "\\", pFileInfo, sizeof(VS_FIXEDFILEINFO), NULL) == S_OK; +} + +extern HMODULE g_hInstance; +BOOL GetSOSVersion(VS_FIXEDFILEINFO *pFileInfo) +{ + _ASSERTE(pFileInfo); + + WCHAR wszFullPath[MAX_LONGPATH]; + DWORD cchFullPath = GetModuleFileNameW(g_hInstance, wszFullPath, _countof(wszFullPath)); + + DWORD dwHandle = 0; + DWORD infoSize = GetFileVersionInfoSizeW(wszFullPath, &dwHandle); + if (infoSize) + { + ArrayHolder<BYTE> pVersionInfo = new BYTE[infoSize]; + if (pVersionInfo) + { + if (GetFileVersionInfoW(wszFullPath, NULL, infoSize, pVersionInfo)) + { + VS_FIXEDFILEINFO *pTmpFileInfo = NULL; + UINT uLen = 0; + if (VerQueryValue(pVersionInfo, "\\", (LPVOID *) &pTmpFileInfo, &uLen)) + { + *pFileInfo = *pTmpFileInfo; // Copy the info + return TRUE; + } + } + } + } + + return FALSE; +} + +#endif // !FEATURE_PAL + +size_t ObjectSize(DWORD_PTR obj,BOOL fIsLargeObject) +{ + DWORD_PTR dwMT; + MOVE(dwMT, obj); + return ObjectSize(obj, dwMT, FALSE, fIsLargeObject); +} + +size_t ObjectSize(DWORD_PTR obj, DWORD_PTR mt, BOOL fIsValueClass, BOOL fIsLargeObject) +{ + BOOL bContainsPointers; + size_t size = 0; + if (!GetSizeEfficient(obj, mt, fIsLargeObject, size, bContainsPointers)) + { + return 0; + } + return size; +} + +// This takes an array of values and sets every non-printable character +// to be a period. +void Flatten(__out_ecount(len) char *data, unsigned int len) +{ + for (unsigned int i = 0; i < len; ++i) + if (data[i] < 32 || data[i] > 126) + data[i] = '.'; + data[len] = 0; +} + +void CharArrayContent(TADDR pos, ULONG num, bool widechar) +{ + if (!pos || num <= 0) + return; + + if (widechar) + { + ArrayHolder<WCHAR> data = new WCHAR[num+1]; + if (!data) + { + ReportOOM(); + return; + } + + ULONG readLen = 0; + if (!SafeReadMemory(pos, data, num<<1, &readLen)) + return; + + Flatten(data.GetPtr(), readLen >> 1); + ExtOut("%S", data.GetPtr()); + } + else + { + ArrayHolder<char> data = new char[num+1]; + if (!data) + { + ReportOOM(); + return; + } + + ULONG readLen = 0; + if (!SafeReadMemory(pos, data, num, &readLen)) + return; + + _ASSERTE(readLen <= num); + Flatten(data, readLen); + + ExtOut("%s", data.GetPtr()); + } +} + +void StringObjectContent(size_t obj, BOOL fLiteral, const int length) +{ + DacpObjectData objData; + if (objData.Request(g_sos, TO_CDADDR(obj))!=S_OK) + { + ExtOut("<Invalid Object>"); + return; + } + + strobjInfo stInfo; + + if (MOVE(stInfo,obj) != S_OK) + { + ExtOut ("Error getting string data\n"); + return; + } + + if (objData.Size > 0x200000 || + stInfo.m_StringLength > 0x200000) + { + ExtOut ("<String is invalid or too large to print>\n"); + return; + } + + ArrayHolder<WCHAR> pwszBuf = new WCHAR[stInfo.m_StringLength+1]; + if (pwszBuf == NULL) + { + return; + } + + DWORD_PTR dwAddr = (DWORD_PTR)pwszBuf.GetPtr(); + if (g_sos->GetObjectStringData(TO_CDADDR(obj), stInfo.m_StringLength+1, pwszBuf, NULL)!=S_OK) + { + ExtOut("Error getting string data\n"); + return; + } + + if (!fLiteral) + { + pwszBuf[stInfo.m_StringLength] = L'\0'; + ExtOut ("%S", pwszBuf.GetPtr()); + } + else + { + ULONG32 count = stInfo.m_StringLength; + WCHAR buffer[256]; + WCHAR out[512]; + while (count) + { + DWORD toRead = 255; + if (count < toRead) + toRead = count; + + ULONG bytesRead; + wcsncpy_s(buffer,_countof(buffer),(LPWSTR) dwAddr, toRead); + bytesRead = toRead*sizeof(WCHAR); + DWORD wcharsRead = bytesRead/2; + buffer[wcharsRead] = L'\0'; + + ULONG j,k=0; + for (j = 0; j < wcharsRead; j ++) + { + if (_iswprint (buffer[j])) { + out[k] = buffer[j]; + k ++; + } + else + { + out[k++] = L'\\'; + switch (buffer[j]) { + case L'\n': + out[k++] = L'n'; + break; + case L'\0': + out[k++] = L'0'; + break; + case L'\t': + out[k++] = L't'; + break; + case L'\v': + out[k++] = L'v'; + break; + case L'\b': + out[k++] = L'b'; + break; + case L'\r': + out[k++] = L'r'; + break; + case L'\f': + out[k++] = L'f'; + break; + case L'\a': + out[k++] = L'a'; + break; + case L'\\': + break; + case L'\?': + out[k++] = L'?'; + break; + default: + out[k++] = L'?'; + break; + } + } + } + + out[k] = L'\0'; + ExtOut ("%S", out); + + count -= wcharsRead; + dwAddr += bytesRead; + } + } +} + +#ifdef _TARGET_WIN64_ + +#include <limits.h> + +__int64 str64hex(const char *ptr) +{ + __int64 value = 0; + unsigned char nCount = 0; + + if(ptr==NULL) + return 0; + + // Ignore leading 0x if present + if (*ptr=='0' && toupper(*(ptr+1))=='X') { + ptr = ptr + 2; + } + + while (1) { + + char digit; + + if (isdigit(*ptr)) { + digit = *ptr - '0'; + } else if (isalpha(*ptr)) { + digit = (((char)toupper(*ptr)) - 'A') + 10; + if (digit >= 16) { + break; // terminate + } + } else { + break; + } + + if (nCount>15) { + return _UI64_MAX; // would be an overflow + } + + value = value << 4; + value |= digit; + + ptr++; + nCount++; + } + + return value; +} + +#endif // _TARGET_WIN64_ + +BOOL GetValueForCMD (const char *ptr, const char *end, ARGTYPE type, size_t *value) +{ + if (type == COSTRING) { + // Allocate memory for the length of the string. Whitespace terminates + // User must free the string data. + char *pszValue = NULL; + size_t dwSize = (end - ptr); + pszValue= new char[dwSize+1]; + if (pszValue == NULL) + { + return FALSE; + } + strncpy_s(pszValue,dwSize+1,ptr,dwSize); // _TRUNCATE + *value = (size_t) pszValue; + } else { + char *last; + if (type == COHEX) { +#ifdef _TARGET_WIN64_ + *value = str64hex(ptr); +#else + *value = strtoul(ptr,&last,16); +#endif + } + else { +#ifdef _TARGET_WIN64_ + *value = _atoi64(ptr); +#else + *value = strtoul(ptr,&last,10); +#endif + } + +#ifdef _TARGET_WIN64_ + last = (char *) ptr; + // Ignore leading 0x if present + if (*last=='0' && toupper(*(last+1))=='X') { + last = last + 2; + } + + while (isdigit(*last) || (toupper(*last)>='A' && toupper(*last)<='F')) { + last++; + } +#endif + + if (last != end) { + return FALSE; + } + } + + return TRUE; +} + +void SetValueForCMD (void *vptr, ARGTYPE type, size_t value) +{ + switch (type) { + case COBOOL: + *(BOOL*)vptr = (BOOL) value; + break; + case COSIZE_T: + case COSTRING: + case COHEX: + *(SIZE_T*)vptr = value; + break; + } +} + +BOOL GetCMDOption(const char *string, CMDOption *option, size_t nOption, + CMDValue *arg, size_t maxArg, size_t *nArg) +{ + const char *end; + const char *ptr = string; + BOOL endofOption = FALSE; + + for (size_t n = 0; n < nOption; n ++) + { + if (IsInterrupt()) + return FALSE; + + option[n].hasSeen = FALSE; + } + + if (nArg) { + *nArg = 0; + } + + while (ptr[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + // skip any space + if (isspace (ptr[0])) { + while (isspace (ptr[0])) + { + if (IsInterrupt()) + return FALSE; + + ptr ++; + } + + continue; + } + + end = ptr; + + // Arguments can be quoted with ". We'll remove the quotes and + // allow spaces to exist in the string. + BOOL bQuotedArg = FALSE; + if (ptr[0] == '\'' && ptr[1] != '-') + { + bQuotedArg = TRUE; + + // skip quote + ptr++; + end++; + + while (end[0] != '\'' && end[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + end ++; + } + if (end[0] != '\'') + { + // Error, th ere was a start quote but no end quote + ExtOut ("Missing quote in %s\n", ptr); + return FALSE; + } + } + else // whitespace terminates + { + while (!isspace(end[0]) && end[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + end ++; + } + } + +#ifndef FEATURE_PAL + if (ptr[0] != '-' && ptr[0] != '/') { +#else + if (ptr[0] != '-') { +#endif + if (maxArg == 0) { + ExtOut ("Incorrect argument: %s\n", ptr); + return FALSE; + } + endofOption = TRUE; + if (*nArg >= maxArg) { + ExtOut ("Incorrect argument: %s\n", ptr); + return FALSE; + } + + size_t value; + if (!GetValueForCMD (ptr,end,arg[*nArg].type,&value)) { + + char oldChar = *end; + *(char *)end = '\0'; + value = (size_t)GetExpression (ptr); + *(char *)end = oldChar; + + /* + + It is silly to do this, what if 0 is a valid expression for + the command? + + if (value == 0) { + ExtOut ("Invalid argument: %s\n", ptr); + return FALSE; + } + */ + } + + SetValueForCMD (arg[*nArg].vptr, arg[*nArg].type, value); + + (*nArg) ++; + } + else if (endofOption) { + ExtOut ("Wrong option: %s\n", ptr); + return FALSE; + } + else { + char buffer[80]; + if (end-ptr > 79) { + ExtOut ("Invalid option %s\n", ptr); + return FALSE; + } + strncpy_s (buffer,_countof(buffer), ptr, end-ptr); + + size_t n; + for (n = 0; n < nOption; n ++) + { + if (IsInterrupt()) + return FALSE; + + if (_stricmp (buffer, option[n].name) == 0) { + if (option[n].hasSeen) { + ExtOut ("Invalid option: option specified multiple times: %s\n", buffer); + return FALSE; + } + option[n].hasSeen = TRUE; + if (option[n].hasValue) { + // skip any space + ptr = end; + if (isspace (ptr[0])) { + while (isspace (ptr[0])) + { + if (IsInterrupt()) + return FALSE; + + ptr ++; + } + } + if (ptr[0] == '\0') { + ExtOut ("Missing value for option %s\n", buffer); + return FALSE; + } + end = ptr; + while (!isspace(end[0]) && end[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + end ++; + } + + size_t value; + if (!GetValueForCMD (ptr,end,option[n].type,&value)) { + + char oldChar = *end; + *(char *)end = '\0'; + value = (size_t)GetExpression (ptr); + *(char *)end = oldChar; + } + + SetValueForCMD (option[n].vptr,option[n].type,value); + } + else { + SetValueForCMD (option[n].vptr,option[n].type,TRUE); + } + break; + } + } + if (n == nOption) { + ExtOut ("Unknown option: %s\n", buffer); + return FALSE; + } + } + + ptr = end; + if (bQuotedArg) + { + ptr++; + } + } + return TRUE; +} + +ReadVirtualCache g_special_rvCacheSpace; +ReadVirtualCache *rvCache = &g_special_rvCacheSpace; + +void ResetGlobals(void) +{ + // There are some globals used in SOS that exist for efficiency in one command, + // but should be reset because the next execution of an SOS command could be on + // another managed process. Reset them to a default state here, as this command + // is called on every SOS entry point. + g_sos->GetUsefulGlobals(&g_special_usefulGlobals); + g_special_mtCache.Clear(); + g_special_rvCacheSpace.Clear(); + Output::ResetIndent(); +} + +//--------------------------------------------------------------------------------------- +// +// Loads private DAC interface, and points g_clrData to it. +// +// Return Value: +// HRESULT indicating success or failure +// +HRESULT LoadClrDebugDll(void) +{ + HRESULT hr = S_OK; +#ifdef FEATURE_PAL + static IXCLRDataProcess* s_clrDataProcess = NULL; + if (s_clrDataProcess == NULL) + { + int err = PAL_InitializeDLL(); + if(err != 0) + { + return CORDBG_E_UNSUPPORTED; + } + char dacModulePath[MAX_LONGPATH]; + strcpy_s(dacModulePath, _countof(dacModulePath), g_ExtServices->GetCoreClrDirectory()); + strcat_s(dacModulePath, _countof(dacModulePath), MAKEDLLNAME_A("mscordaccore")); + + HMODULE hdac = LoadLibraryA(dacModulePath); + if (hdac == NULL) + { + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + PFN_CLRDataCreateInstance pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)GetProcAddress(hdac, "CLRDataCreateInstance"); + if (pfnCLRDataCreateInstance == NULL) + { + FreeLibrary(hdac); + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + ICLRDataTarget *target = new DataTarget(); + hr = pfnCLRDataCreateInstance(__uuidof(IXCLRDataProcess), target, (void**)&s_clrDataProcess); + if (FAILED(hr)) + { + s_clrDataProcess = NULL; + return hr; + } + ULONG32 flags = 0; + s_clrDataProcess->GetOtherNotificationFlags(&flags); + flags |= (CLRDATA_NOTIFY_ON_MODULE_LOAD | CLRDATA_NOTIFY_ON_MODULE_UNLOAD | CLRDATA_NOTIFY_ON_EXCEPTION); + s_clrDataProcess->SetOtherNotificationFlags(flags); + } + g_clrData = s_clrDataProcess; + g_clrData->AddRef(); + g_clrData->Flush(); +#else + WDBGEXTS_CLR_DATA_INTERFACE Query; + + Query.Iid = &__uuidof(IXCLRDataProcess); + if (!Ioctl(IG_GET_CLR_DATA_INTERFACE, &Query, sizeof(Query))) + { + return E_FAIL; + } + + g_clrData = (IXCLRDataProcess*)Query.Iface; +#endif + hr = g_clrData->QueryInterface(__uuidof(ISOSDacInterface), (void**)&g_sos); + if (FAILED(hr)) + { + g_sos = NULL; + return hr; + } + return S_OK; +} + +#ifndef FEATURE_PAL + +// This structure carries some input/output data to the FindFileInPathCallback below +typedef struct _FindFileCallbackData +{ + DWORD timestamp; + DWORD filesize; + HMODULE hModule; +} FindFileCallbackData; + + +// A callback used by SymFindFileInPath - called once for each file that matches +// the initial search criteria and allows the user to do arbitrary processing +// This implementation checks that filesize and timestamp are correct, then +// saves the loaded module handle +// Parameters +// filename - the full path the file which was found +// context - a user specified pointer to arbitrary data, in this case a FindFileCallbackData +// Return Value +// TRUE if the search should continue (the file is no good) +// FALSE if the search should stop (the file is good) +BOOL +FindFileInPathCallback( + ___in PCWSTR filename, + ___in PVOID context + ) +{ + HRESULT hr; + FindFileCallbackData* pCallbackData; + pCallbackData = (FindFileCallbackData*)context; + if (!pCallbackData) + return TRUE; + + pCallbackData->hModule = LoadLibraryExW( + filename, + NULL, // __reserved + LOAD_WITH_ALTERED_SEARCH_PATH); // Ensure we check the dir in wszFullPath first + if (pCallbackData->hModule == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + ExtOut("Unable to load '%S'. HRESULT = 0x%x.\n", filename, hr); + return TRUE; + } + + // Did we load the right one? + MODULEINFO modInfo = {0}; + if (!GetModuleInformation( + GetCurrentProcess(), + pCallbackData->hModule, + &modInfo, + sizeof(modInfo))) + { + ExtOut("Failed to read module information for '%S'. HRESULT = 0x%x.\n", filename, HRESULT_FROM_WIN32(GetLastError())); + FreeLibrary(pCallbackData->hModule); + return TRUE; + } + + IMAGE_DOS_HEADER * pDOSHeader = (IMAGE_DOS_HEADER *) modInfo.lpBaseOfDll; + IMAGE_NT_HEADERS * pNTHeaders = (IMAGE_NT_HEADERS *) (((LPBYTE) modInfo.lpBaseOfDll) + pDOSHeader->e_lfanew); + DWORD dwSizeActual = pNTHeaders->OptionalHeader.SizeOfImage; + DWORD dwTimeStampActual = pNTHeaders->FileHeader.TimeDateStamp; + if ((dwSizeActual != pCallbackData->filesize) || (dwTimeStampActual != pCallbackData->timestamp)) + { + ExtOut("Found '%S', but it does not match the CLR being debugged.\n", filename); + ExtOut("Size: Expected '0x%x', Actual '0x%x'\n", pCallbackData->filesize, dwSizeActual); + ExtOut("Time stamp: Expected '0x%x', Actual '0x%x'\n", pCallbackData->timestamp, dwTimeStampActual); + FreeLibrary(pCallbackData->hModule); + return TRUE; + } + + ExtOut("Loaded %S\n", filename); + return FALSE; +} + +#endif // FEATURE_PAL + +//--------------------------------------------------------------------------------------- +// Provides a way for the public CLR debugging interface to find the appropriate +// mscordbi.dll, DAC, etc. +class SOSLibraryProvider : public ICLRDebuggingLibraryProvider +{ +public: + SOSLibraryProvider() : m_ref(0) + { + } + + virtual ~SOSLibraryProvider() {} + + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID InterfaceId, + PVOID* pInterface) + { + if (InterfaceId == IID_IUnknown) + { + *pInterface = static_cast<IUnknown *>(this); + } + else if (InterfaceId == IID_ICLRDebuggingLibraryProvider) + { + *pInterface = static_cast<ICLRDebuggingLibraryProvider *>(this); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; + } + + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&m_ref); + } + + virtual ULONG STDMETHODCALLTYPE Release() + { + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; + } + + + + // Called by the shim to locate and load mscordacwks and mscordbi + // Parameters: + // pwszFileName - the name of the file to load + // dwTimestamp - the expected timestamp of the file + // dwSizeOfImage - the expected SizeOfImage (a PE header data value) + // phModule - a handle to loaded module + // + // Return Value + // S_OK if the file was loaded, or any error if not + virtual HRESULT STDMETHODCALLTYPE ProvideLibrary( + const WCHAR * pwszFileName, + DWORD dwTimestamp, + DWORD dwSizeOfImage, + HMODULE * phModule) + { +#ifndef FEATURE_PAL + HRESULT hr; + FindFileCallbackData callbackData = {0}; + callbackData.timestamp = dwTimestamp; + callbackData.filesize = dwSizeOfImage; + + if ((phModule == NULL) || (pwszFileName == NULL)) + { + return E_INVALIDARG; + } + + HMODULE dacModule; + if(g_sos == NULL) + { + // we ensure that windbg loads DAC first so that we can be sure to use the same one + return E_UNEXPECTED; + } + if (FAILED(hr = g_sos->GetDacModuleHandle(&dacModule))) + { + ExtOut("Failed to get the dac module handle. hr=0x%x.\n", hr); + return hr; + } + + WCHAR dacPath[MAX_LONGPATH]; + DWORD len = GetModuleFileNameW(dacModule, dacPath, MAX_LONGPATH); + if(len == 0 || len == MAX_LONGPATH) + { + ExtOut("GetModuleFileName(dacModuleHandle) failed. Last error = 0x%x\n", GetLastError()); + return E_FAIL; + } + + // if we are looking for the DAC, just load the one windbg already found + if(_wcsncmp(pwszFileName, W("mscordac"), _wcslen(W("mscordac")))==0) + { + FindFileInPathCallback(dacPath, &callbackData); + *phModule = callbackData.hModule; + return hr; + } + + ULONG64 hProcess; + hr = g_ExtSystem->GetCurrentProcessHandle(&hProcess); + if (FAILED(hr)) + { + ExtOut("IDebugSystemObjects::GetCurrentProcessHandle HRESULT=0x%x.\n", hr); + return hr; + } + + ToRelease<IDebugSymbols3> spSym3(NULL); + hr = g_ExtSymbols->QueryInterface(__uuidof(IDebugSymbols3), (void**)&spSym3); + if (FAILED(hr)) + { + ExtOut("Unable to query IDebugSymbol3 HRESULT=0x%x.\n", hr); + return hr; + } + + ULONG pathSize = 0; + hr = spSym3->GetSymbolPathWide(NULL, 0, &pathSize); + if(FAILED(hr)) //S_FALSE if the path doesn't fit, but if the path was size 0 perhaps we would get S_OK? + { + ExtOut("Unable to get symbol path length. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", hr); + return hr; + } + + ArrayHolder<WCHAR> symbolPath = new WCHAR[pathSize+MAX_LONGPATH+1]; + + + + hr = spSym3->GetSymbolPathWide(symbolPath, pathSize, NULL); + if(S_OK != hr) + { + ExtOut("Unable to get symbol path. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", hr); + return hr; + } + + WCHAR foundPath[MAX_LONGPATH]; + BOOL rc = SymFindFileInPathW((HANDLE)hProcess, + symbolPath, + pwszFileName, + (PVOID)(ULONG_PTR) dwTimestamp, + dwSizeOfImage, + 0, + SSRVOPT_DWORD, + foundPath, + (PFINDFILEINPATHCALLBACKW) &FindFileInPathCallback, + (PVOID) &callbackData + ); + if(!rc) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + ExtOut("SymFindFileInPath failed for %S. HRESULT=0x%x.\nPlease ensure that %S is on your symbol path.", pwszFileName, hr, pwszFileName); + } + + *phModule = callbackData.hModule; + return hr; +#else + WCHAR modulePath[MAX_LONGPATH]; + int length = MultiByteToWideChar(CP_ACP, 0, g_ExtServices->GetCoreClrDirectory(), -1, modulePath, _countof(modulePath)); + if (0 >= length) + { + ExtOut("MultiByteToWideChar(coreclrDirectory) failed. Last error = 0x%x\n", GetLastError()); + return E_FAIL; + } + wcscat_s(modulePath, _countof(modulePath), pwszFileName); + + *phModule = LoadLibraryW(modulePath); + if (*phModule == NULL) + { + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + ExtOut("Unable to load '%S'. HRESULT = 0x%x.\n", pwszFileName, hr); + return hr; + } + return S_OK; +#endif // FEATURE_PAL + } + +protected: + LONG m_ref; +}; + +//--------------------------------------------------------------------------------------- +// Data target for the debugged process. Provided to OpenVirtualProcess in order to +// get an ICorDebugProcess back +// +class SOSDataTarget : public ICorDebugMutableDataTarget +#ifdef FEATURE_PAL +, public ICorDebugDataTarget4 +#endif +{ +public: + SOSDataTarget() : m_ref(0) + { + } + + virtual ~SOSDataTarget() {} + + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID InterfaceId, + PVOID* pInterface) + { + if (InterfaceId == IID_IUnknown) + { + *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this)); + } + else if (InterfaceId == IID_ICorDebugDataTarget) + { + *pInterface = static_cast<ICorDebugDataTarget *>(this); + } + else if (InterfaceId == IID_ICorDebugMutableDataTarget) + { + *pInterface = static_cast<ICorDebugMutableDataTarget *>(this); + } +#ifdef FEATURE_PAL + else if (InterfaceId == IID_ICorDebugDataTarget4) + { + *pInterface = static_cast<ICorDebugDataTarget4 *>(this); + } +#endif + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; + } + + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&m_ref); + } + + virtual ULONG STDMETHODCALLTYPE Release() + { + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; + } + + // + // ICorDebugDataTarget. + // + + virtual HRESULT STDMETHODCALLTYPE GetPlatform(CorDebugPlatform * pPlatform) + { + ULONG platformKind = g_targetMachine->GetPlatform(); +#ifdef FEATURE_PAL + if(platformKind == IMAGE_FILE_MACHINE_I386) + *pPlatform = CORDB_PLATFORM_POSIX_X86; + else if(platformKind == IMAGE_FILE_MACHINE_AMD64) + *pPlatform = CORDB_PLATFORM_POSIX_AMD64; + else if(platformKind == IMAGE_FILE_MACHINE_ARMNT) + *pPlatform = CORDB_PLATFORM_POSIX_ARM; + else + return E_FAIL; +#else + if(platformKind == IMAGE_FILE_MACHINE_I386) + *pPlatform = CORDB_PLATFORM_WINDOWS_X86; + else if(platformKind == IMAGE_FILE_MACHINE_AMD64) + *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64; + else if(platformKind == IMAGE_FILE_MACHINE_ARMNT) + *pPlatform = CORDB_PLATFORM_WINDOWS_ARM; + else if(platformKind == IMAGE_FILE_MACHINE_ARM64) + *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64; + else + return E_FAIL; +#endif + + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE ReadVirtual( + CORDB_ADDRESS address, + BYTE * pBuffer, + ULONG32 request, + ULONG32 * pcbRead) + { + if (g_ExtData == NULL) + { + return E_UNEXPECTED; + } + return g_ExtData->ReadVirtual(address, pBuffer, request, (PULONG) pcbRead); + } + + virtual HRESULT STDMETHODCALLTYPE GetThreadContext( + DWORD dwThreadOSID, + ULONG32 contextFlags, + ULONG32 contextSize, + BYTE * context) + { +#ifdef FEATURE_PAL + if (g_ExtSystem == NULL) + { + return E_UNEXPECTED; + } + return g_ExtSystem->GetThreadContextById(dwThreadOSID, contextFlags, contextSize, context); +#else + ULONG ulThreadIDOrig; + ULONG ulThreadIDRequested; + HRESULT hr; + HRESULT hrRet; + + hr = g_ExtSystem->GetCurrentThreadId(&ulThreadIDOrig); + if (FAILED(hr)) + { + return hr; + } + + hr = g_ExtSystem->GetThreadIdBySystemId(dwThreadOSID, &ulThreadIDRequested); + if (FAILED(hr)) + { + return hr; + } + + hr = g_ExtSystem->SetCurrentThreadId(ulThreadIDRequested); + if (FAILED(hr)) + { + return hr; + } + + // Prepare context structure + ZeroMemory(context, contextSize); + ((CONTEXT*) context)->ContextFlags = contextFlags; + + // Ok, do it! + hrRet = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize); + + // This is cleanup; failure here doesn't mean GetThreadContext should fail + // (that's determined by hrRet). + g_ExtSystem->SetCurrentThreadId(ulThreadIDOrig); + + return hrRet; +#endif // FEATURE_PAL + } + + // + // ICorDebugMutableDataTarget. + // + virtual HRESULT STDMETHODCALLTYPE WriteVirtual(CORDB_ADDRESS address, + const BYTE * pBuffer, + ULONG32 bytesRequested) + { + if (g_ExtData == NULL) + { + return E_UNEXPECTED; + } + return g_ExtData->WriteVirtual(address, (PVOID)pBuffer, bytesRequested, NULL); + } + + virtual HRESULT STDMETHODCALLTYPE SetThreadContext(DWORD dwThreadID, + ULONG32 contextSize, + const BYTE * pContext) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(DWORD dwThreadId, + CORDB_CONTINUE_STATUS continueStatus) + { + return E_NOTIMPL; + } + +#ifdef FEATURE_PAL + // + // ICorDebugDataTarget4 + // + virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context) + { + if (g_ExtServices == NULL) + { + return E_UNEXPECTED; + } + return g_ExtServices->VirtualUnwind(threadId, contextSize, context); + + } +#endif // FEATURE_PAL + +protected: + LONG m_ref; +}; + +HRESULT InitCorDebugInterfaceFromModule(ULONG64 ulBase, ICLRDebugging * pClrDebugging) +{ + HRESULT hr; + + ToRelease<ICorDebugMutableDataTarget> pSOSDataTarget = new SOSDataTarget; + pSOSDataTarget->AddRef(); + + ToRelease<ICLRDebuggingLibraryProvider> pSOSLibraryProvider = new SOSLibraryProvider; + pSOSLibraryProvider->AddRef(); + + CLR_DEBUGGING_VERSION clrDebuggingVersionRequested = {0}; + clrDebuggingVersionRequested.wMajor = 4; + + CLR_DEBUGGING_VERSION clrDebuggingVersionActual = {0}; + + CLR_DEBUGGING_PROCESS_FLAGS clrDebuggingFlags = (CLR_DEBUGGING_PROCESS_FLAGS)0; + + ToRelease<IUnknown> pUnkProcess; + + hr = pClrDebugging->OpenVirtualProcess( + ulBase, + pSOSDataTarget, + pSOSLibraryProvider, + &clrDebuggingVersionRequested, + IID_ICorDebugProcess, + &pUnkProcess, + &clrDebuggingVersionActual, + &clrDebuggingFlags); + if (FAILED(hr)) + { + return hr; + } + + ICorDebugProcess * pCorDebugProcess = NULL; + hr = pUnkProcess->QueryInterface(IID_ICorDebugProcess, (PVOID*) &pCorDebugProcess); + if (FAILED(hr)) + { + return hr; + } + + // Transfer memory ownership of refcount to global + g_pCorDebugProcess = pCorDebugProcess; + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Unloads public ICorDebug interfaces, and clears g_pCorDebugProcess +// This is only needed once after CLR unloads, not after every InitCorDebugInterface call +// +VOID UninitCorDebugInterface() +{ + if(g_pCorDebugProcess != NULL) + { + g_pCorDebugProcess->Detach(); + g_pCorDebugProcess->Release(); + g_pCorDebugProcess = NULL; + } +} + +//--------------------------------------------------------------------------------------- +// +// Loads public ICorDebug interfaces, and points g_pCorDebugProcess to them +// This should be called at least once per windbg stop state to ensure that +// the interface is available and that it doesn't hold stale data. Calling it +// more than once isn't an error, but does have perf overhead from needlessly +// flushing memory caches. +// +// Return Value: +// HRESULT indicating success or failure +// + +HRESULT InitCorDebugInterface() +{ + HMODULE hModule = NULL; + HRESULT hr; + ToRelease<ICLRDebugging> pClrDebugging; + + // we may already have an ICorDebug instance we can use + if(g_pCorDebugProcess != NULL) + { + // ICorDebugProcess4 is currently considered a private experimental interface on ICorDebug, it might go away so + // we need to be sure to handle its absense gracefully + ToRelease<ICorDebugProcess4> pProcess4 = NULL; + if(SUCCEEDED(g_pCorDebugProcess->QueryInterface(__uuidof(ICorDebugProcess4), (void**)&pProcess4))) + { + // FLUSH_ALL is more expensive than PROCESS_RUNNING, but this allows us to be safe even if things + // like IDNA are in use where we might be looking at non-sequential snapshots of process state + if(SUCCEEDED(pProcess4->ProcessStateChanged(FLUSH_ALL))) + { + // we already have an ICorDebug instance loaded and flushed, nothing more to do + return S_OK; + } + } + + // this is a very heavy handed way of reseting + UninitCorDebugInterface(); + } + + // SOS now has a statically linked version of the loader code that is normally found in mscoree/mscoreei.dll + // Its not much code and takes a big step towards 0 install dependencies + // Need to pick the appropriate SKU of CLR to detect +#if defined(FEATURE_CORESYSTEM) + GUID skuId = CLR_ID_ONECORE_CLR; +#elif defined(FEATURE_CORECLR) + GUID skuId = CLR_ID_CORECLR; +#else + GUID skuId = CLR_ID_V4_DESKTOP; +#endif + CLRDebuggingImpl* pDebuggingImpl = new CLRDebuggingImpl(skuId); + hr = pDebuggingImpl->QueryInterface(IID_ICLRDebugging, (LPVOID *)&pClrDebugging); + if (FAILED(hr)) + { + delete pDebuggingImpl; + return hr; + } + +#ifndef FEATURE_PAL + ULONG cLoadedModules; + ULONG cUnloadedModules; + hr = g_ExtSymbols->GetNumberModules(&cLoadedModules, &cUnloadedModules); + if (FAILED(hr)) + { + return hr; + } + + ULONG64 ulBase; + for (ULONG i = 0; i < cLoadedModules; i++) + { + hr = g_ExtSymbols->GetModuleByIndex(i, &ulBase); + if (FAILED(hr)) + { + return hr; + } + + // Dunno if this is a CLR module or not (or even if it's the particular one the + // user cares about during inproc SxS scenarios). For now, just try to use it + // to grab an ICorDebugProcess. If it works, great. Else, continue the loop + // until we find the first one that works. + hr = InitCorDebugInterfaceFromModule(ulBase, pClrDebugging); + if (SUCCEEDED(hr)) + { + return hr; + } + + // On failure, just iterate to the next module and try again... + } + + // Still here? Didn't find the right module. + // TODO: Anything useful to return or log here? + return E_FAIL; +#else + ULONG64 ulBase; + hr = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_DLL_NAME_A, 0, NULL, &ulBase); + if (SUCCEEDED(hr)) + { + hr = InitCorDebugInterfaceFromModule(ulBase, pClrDebugging); + } + return hr; +#endif // FEATURE_PAL +} + + +typedef enum +{ + GC_HEAP_INVALID = 0, + GC_HEAP_WKS = 1, + GC_HEAP_SVR = 2 +} GC_HEAP_TYPE; + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find out if runtime is server build * +* * +\**********************************************************************/ + +DacpGcHeapData *g_pHeapData = NULL; +DacpGcHeapData g_HeapData; + +BOOL InitializeHeapData() +{ + if (g_pHeapData == NULL) + { + if (g_HeapData.Request(g_sos) != S_OK) + { + return FALSE; + } + g_pHeapData = &g_HeapData; + } + return TRUE; +} + +BOOL IsServerBuild() +{ + return InitializeHeapData() ? g_pHeapData->bServerMode : FALSE; +} + +UINT GetMaxGeneration() +{ + return InitializeHeapData() ? g_pHeapData->g_max_generation : 0; +} + +UINT GetGcHeapCount() +{ + return InitializeHeapData() ? g_pHeapData->HeapCount : 0; +} + +BOOL GetGcStructuresValid() +{ + // We don't want to use the cached HeapData, because this can change + // each time the program runs for a while. + DacpGcHeapData heapData; + if (heapData.Request(g_sos) != S_OK) + { + return FALSE; + } + + return heapData.bGcStructuresValid; +} + +void GetAllocContextPtrs(AllocInfo *pallocInfo) +{ + // gets the allocation contexts for all threads. This provides information about how much of + // the current allocation quantum has been allocated and the heap to which the quantum belongs. + // The allocation quantum is a fixed size chunk of zeroed memory from which allocations will come + // until it's filled. Each managed thread has its own allocation context. + + pallocInfo->num = 0; + pallocInfo->array = NULL; + + // get the thread store (See code:ClrDataAccess::RequestThreadStoreData for details) + DacpThreadStoreData ThreadStore; + if ( ThreadStore.Request(g_sos) != S_OK) + { + return; + } + + int numThread = ThreadStore.threadCount; + if (numThread) + { + pallocInfo->array = new needed_alloc_context[numThread]; + if (pallocInfo->array == NULL) + { + return; + } + } + + // get details for each thread in the thread store + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread != NULL) + { + if (IsInterrupt()) + return; + + DacpThreadData Thread; + // Get information about the thread (we're getting the values of several of the + // fields of the Thread instance from the target) See code:ClrDataAccess::RequestThreadData for + // details + if (Thread.Request(g_sos, CurThread) != S_OK) + { + return; + } + + if (Thread.allocContextPtr != 0) + { + // get a list of all the allocation contexts + int j; + for (j = 0; j < pallocInfo->num; j ++) + { + if (pallocInfo->array[j].alloc_ptr == (BYTE *) Thread.allocContextPtr) + break; + } + if (j == pallocInfo->num) + { + pallocInfo->num ++; + pallocInfo->array[j].alloc_ptr = (BYTE *) Thread.allocContextPtr; + pallocInfo->array[j].alloc_limit = (BYTE *) Thread.allocContextLimit; + } + } + + CurThread = Thread.nextThread; + } +} + +HRESULT ReadVirtualCache::Read(TADDR taOffset, PVOID Buffer, ULONG BufferSize, PULONG lpcbBytesRead) +{ + // sign extend the passed in Offset so we can use it in when calling + // IDebugDataSpaces::ReadVirtual() + + CLRDATA_ADDRESS Offset = TO_CDADDR(taOffset); + // Offset can be any random ULONG64, as it can come from VerifyObjectMember(), and this + // can pass random pointer values in case of GC heap corruption + HRESULT ret; + ULONG cbBytesRead = 0; + + if (BufferSize == 0) + return S_OK; + + if (BufferSize > CACHE_SIZE) + { + // Don't even try with the cache + return g_ExtData->ReadVirtual(Offset, Buffer, BufferSize, lpcbBytesRead); + } + + if ((m_cacheValid) + && (taOffset >= m_startCache) + && (taOffset <= m_startCache + m_cacheSize - BufferSize)) + + { + // It is within the cache + memcpy(Buffer,(LPVOID) ((ULONG64)m_cache + (taOffset - m_startCache)), BufferSize); + + if (lpcbBytesRead != NULL) + { + *lpcbBytesRead = BufferSize; + } + + return S_OK; + } + + m_cacheValid = FALSE; + m_startCache = taOffset; + + // avoid an int overflow + if (m_startCache + CACHE_SIZE < m_startCache) + m_startCache = (TADDR)(-CACHE_SIZE); + + ret = g_ExtData->ReadVirtual(TO_CDADDR(m_startCache), m_cache, CACHE_SIZE, &cbBytesRead); + if (ret != S_OK) + { + return ret; + } + + m_cacheSize = cbBytesRead; + m_cacheValid = TRUE; + memcpy(Buffer, (LPVOID) ((ULONG64)m_cache + (taOffset - m_startCache)), BufferSize); + + if (lpcbBytesRead != NULL) + { + *lpcbBytesRead = cbBytesRead; + } + + return S_OK; +} + +HRESULT GetMTOfObject(TADDR obj, TADDR *mt) +{ + if (!mt) + return E_POINTER; + + // Read the MethodTable and if we succeed, get rid of the mark bits. + HRESULT hr = rvCache->Read(obj, mt, sizeof(TADDR), NULL); + if (SUCCEEDED(hr)) + *mt &= ~3; + + return hr; +} + +#ifndef FEATURE_PAL + +StressLogMem::~StressLogMem () +{ + MemRange * range = list; + + while (range) + { + MemRange * temp = range->next; + delete range; + range = temp; + } +} + +bool StressLogMem::Init (ULONG64 stressLogAddr, IDebugDataSpaces* memCallBack) +{ + size_t ThreadStressLogAddr = NULL; + HRESULT hr = memCallBack->ReadVirtual(UL64_TO_CDA(stressLogAddr + offsetof (StressLog, logs)), + &ThreadStressLogAddr, sizeof (ThreadStressLogAddr), 0); + if (hr != S_OK) + { + return false; + } + + while(ThreadStressLogAddr != NULL) + { + size_t ChunkListHeadAddr = NULL; + hr = memCallBack->ReadVirtual(TO_CDADDR(ThreadStressLogAddr + ThreadStressLog::OffsetOfListHead ()), + &ChunkListHeadAddr, sizeof (ChunkListHeadAddr), 0); + if (hr != S_OK || ChunkListHeadAddr == NULL) + { + return false; + } + + size_t StressLogChunkAddr = ChunkListHeadAddr; + + do + { + AddRange (StressLogChunkAddr, sizeof (StressLogChunk)); + hr = memCallBack->ReadVirtual(TO_CDADDR(StressLogChunkAddr + offsetof (StressLogChunk, next)), + &StressLogChunkAddr, sizeof (StressLogChunkAddr), 0); + if (hr != S_OK) + { + return false; + } + if (StressLogChunkAddr == NULL) + { + return true; + } + } while (StressLogChunkAddr != ChunkListHeadAddr); + + hr = memCallBack->ReadVirtual(TO_CDADDR(ThreadStressLogAddr + ThreadStressLog::OffsetOfNext ()), + &ThreadStressLogAddr, sizeof (ThreadStressLogAddr), 0); + if (hr != S_OK) + { + return false; + } + } + + return true; +} + +bool StressLogMem::IsInStressLog (ULONG64 addr) +{ + MemRange * range = list; + while (range) + { + if (range->InRange (addr)) + return true; + range = range->next; + } + + return false; +} + +#endif // !FEATURE_PAL + +unsigned int Output::g_bSuppressOutput = 0; +unsigned int Output::g_Indent = 0; +bool Output::g_bDbgOutput = false; +bool Output::g_bDMLExposed = false; +unsigned int Output::g_DMLEnable = 0; + +template <class T, int count, int size> const int StaticData<T, count, size>::Count = count; +template <class T, int count, int size> const int StaticData<T, count, size>::Size = size; + +StaticData<char, 4, 1024> CachedString::cache; + +CachedString::CachedString() +: mPtr(0), mRefCount(0), mIndex(~0), mSize(cache.Size) +{ + Create(); +} + +CachedString::CachedString(const CachedString &rhs) +: mPtr(0), mRefCount(0), mIndex(~0), mSize(cache.Size) +{ + Copy(rhs); +} + +CachedString::~CachedString() +{ + Clear(); +} + +const CachedString &CachedString::operator=(const CachedString &rhs) +{ + Clear(); + Copy(rhs); + return *this; +} + +void CachedString::Copy(const CachedString &rhs) +{ + if (rhs.IsOOM()) + { + SetOOM(); + } + else + { + mPtr = rhs.mPtr; + mIndex = rhs.mIndex; + mSize = rhs.mSize; + + if (rhs.mRefCount) + { + mRefCount = rhs.mRefCount; + (*mRefCount)++; + } + else + { + // We only create count the first time we copy it, so + // we initialize it to 2. + mRefCount = rhs.mRefCount = new unsigned int(2); + if (!mRefCount) + SetOOM(); + } + } +} + +void CachedString::Clear() +{ + if (!mRefCount || --*mRefCount == 0) + { + if (mIndex == -1) + { + if (mPtr) + delete [] mPtr; + } + else if (mIndex >= 0 && mIndex < cache.Count) + { + cache.InUse[mIndex] = false; + } + + if (mRefCount) + delete mRefCount; + } + + mPtr = 0; + mIndex = ~0; + mRefCount = 0; + mSize = cache.Size; +} + + +void CachedString::Create() +{ + mIndex = -1; + mRefCount = 0; + + // First try to find a string in the cache to use. + for (int i = 0; i < cache.Count; ++i) + if (!cache.InUse[i]) + { + cache.InUse[i] = true; + mPtr = cache.Data[i]; + mIndex = i; + break; + } + + // We did not find a string to use, so we'll create a new one. + if (mIndex == -1) + { + mPtr = new char[cache.Size]; + if (!mPtr) + SetOOM(); + } +} + + +void CachedString::SetOOM() +{ + Clear(); + mIndex = -2; +} + +void CachedString::Allocate(int size) +{ + Clear(); + mPtr = new char[size]; + + if (mPtr) + { + mSize = size; + mIndex = -1; + } + else + { + SetOOM(); + } +} + +size_t CountHexCharacters(CLRDATA_ADDRESS val) +{ + size_t ret = 0; + + while (val) + { + val >>= 4; + ret++; + } + + return ret; +} + +void WhitespaceOut(int count) +{ + static const int FixedIndentWidth = 0x40; + static const char FixedIndentString[FixedIndentWidth+1] = + " "; + + if (count <= 0) + return; + + int mod = count & 0x3F; + count &= ~0x3F; + + if (mod > 0) + g_ExtControl->Output(DEBUG_OUTPUT_NORMAL, "%.*s", mod, FixedIndentString); + + for ( ; count > 0; count -= FixedIndentWidth) + g_ExtControl->Output(DEBUG_OUTPUT_NORMAL, FixedIndentString); +} + +void DMLOut(PCSTR format, ...) +{ + if (Output::IsOutputSuppressed()) + return; + + va_list args; + va_start(args, format); + ExtOutIndent(); + +#ifndef FEATURE_PAL + if (IsDMLEnabled() && !Output::IsDMLExposed()) + { + g_ExtControl->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, format, args); + } + else +#endif + { + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, format, args); + } + + va_end(args); +} + +void IfDMLOut(PCSTR format, ...) +{ +#ifndef FEATURE_PAL + if (Output::IsOutputSuppressed() || !IsDMLEnabled()) + return; + + va_list args; + + va_start(args, format); + ExtOutIndent(); + g_ExtControl->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, format, args); + va_end(args); +#endif +} + +void ExtOut(PCSTR Format, ...) +{ + if (Output::IsOutputSuppressed()) + return; + + va_list Args; + + va_start(Args, Format); + ExtOutIndent(); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args); + va_end(Args); +} + +void ExtWarn(PCSTR Format, ...) +{ + if (Output::IsOutputSuppressed()) + return; + + va_list Args; + + va_start(Args, Format); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_WARNING, Format, Args); + va_end(Args); +} + +void ExtErr(PCSTR Format, ...) +{ + va_list Args; + + va_start(Args, Format); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_ERROR, Format, Args); + va_end(Args); +} + + +void ExtDbgOut(PCSTR Format, ...) +{ +#ifdef _DEBUG + if (Output::g_bDbgOutput) + { + va_list Args; + + va_start(Args, Format); + ExtOutIndent(); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args); + va_end(Args); + } +#endif +} + +const char * const DMLFormats[] = +{ + NULL, // DML_None (do not use) + "<exec cmd=\"!DumpMT /d %s\">%s</exec>", // DML_MethodTable + "<exec cmd=\"!DumpMD /d %s\">%s</exec>", // DML_MethodDesc + "<exec cmd=\"!DumpClass /d %s\">%s</exec>", // DML_EEClass + "<exec cmd=\"!DumpModule /d %s\">%s</exec>", // DML_Module + "<exec cmd=\"!U /d %s\">%s</exec>", // DML_IP + "<exec cmd=\"!DumpObj /d %s\">%s</exec>", // DML_Object + "<exec cmd=\"!DumpDomain /d %s\">%s</exec>", // DML_Domain + "<exec cmd=\"!DumpAssembly /d %s\">%s</exec>", // DML_Assembly + "<exec cmd=\"~~[%s]s\">%s</exec>", // DML_ThreadID + "<exec cmd=\"!DumpVC /d %s %s\">%s</exec>", // DML_ValueClass + "<exec cmd=\"!DumpHeap /d -mt %s\">%s</exec>", // DML_DumpHeapMT + "<exec cmd=\"!ListNearObj /d %s\">%s</exec>", // DML_ListNearObj + "<exec cmd=\"!ThreadState %s\">%s</exec>", // DML_ThreadState + "<exec cmd=\"!PrintException /d %s\">%s</exec>",// DML_PrintException + "<exec cmd=\"!DumpRCW /d %s\">%s</exec>", // DML_RCWrapper + "<exec cmd=\"!DumpCCW /d %s\">%s</exec>", // DML_CCWrapper + "<exec cmd=\"!ClrStack -i %S %d\">%S</exec>", // DML_ManagedVar +}; + +void ConvertToLower(__out_ecount(len) char *buffer, size_t len) +{ + for (size_t i = 0; i < len && buffer[i]; ++i) + buffer[i] = (char)tolower(buffer[i]); +} + +/* Build a hex display of addr. + */ +int GetHex(CLRDATA_ADDRESS addr, __out_ecount(len) char *out, size_t len, bool fill) +{ + int count = sprintf_s(out, len, fill ? "%p" : "%x", (size_t)addr); + + ConvertToLower(out, len); + + return count; +} + +CachedString Output::BuildHexValue(CLRDATA_ADDRESS addr, FormatType type, bool fill) +{ + CachedString ret; + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + + if (IsDMLEnabled()) + { + char hex[POINTERSIZE_BYTES*2 + 1]; + GetHex(addr, hex, _countof(hex), fill); + sprintf_s(ret, ret.GetStrLen(), DMLFormats[type], hex, hex); + } + else + { + GetHex(addr, ret, ret.GetStrLen(), fill); + } + + return ret; +} + +CachedString Output::BuildVCValue(CLRDATA_ADDRESS mt, CLRDATA_ADDRESS addr, FormatType type, bool fill) +{ + _ASSERTE(type == DML_ValueClass); + CachedString ret; + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + + if (IsDMLEnabled()) + { + char hexaddr[POINTERSIZE_BYTES*2 + 1]; + char hexmt[POINTERSIZE_BYTES*2 + 1]; + + GetHex(addr, hexaddr, _countof(hexaddr), fill); + GetHex(mt, hexmt, _countof(hexmt), fill); + + sprintf_s(ret, ret.GetStrLen(), DMLFormats[type], hexmt, hexaddr, hexaddr); + } + else + { + GetHex(addr, ret, ret.GetStrLen(), fill); + } + + return ret; +} + +CachedString Output::BuildManagedVarValue(__in_z LPCWSTR expansionName, ULONG frame, __in_z LPCWSTR simpleName, FormatType type) +{ + _ASSERTE(type == DML_ManagedVar); + CachedString ret; + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + + // calculate the number of digits in frame (this assumes base-10 display of frames) + int numFrameDigits = 0; + if (frame > 0) + { + ULONG tempFrame = frame; + while (tempFrame > 0) + { + ++numFrameDigits; + tempFrame /= 10; + } + } + else + { + numFrameDigits = 1; + } + + size_t totalStringLength = strlen(DMLFormats[type]) + _wcslen(expansionName) + numFrameDigits + _wcslen(simpleName) + 1; + if (totalStringLength > ret.GetStrLen()) + { + ret.Allocate(static_cast<int>(totalStringLength)); + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + } + + if (IsDMLEnabled()) + { + sprintf_s(ret, ret.GetStrLen(), DMLFormats[type], expansionName, frame, simpleName); + } + else + { + sprintf_s(ret, ret.GetStrLen(), "%S", simpleName); + } + + return ret; +} + +CachedString Output::BuildManagedVarValue(__in_z LPCWSTR expansionName, ULONG frame, int indexInArray, FormatType type) +{ + WCHAR indexString[24]; + swprintf_s(indexString, _countof(indexString), W("[%d]"), indexInArray); + return BuildManagedVarValue(expansionName, frame, indexString, type); +} + +EnableDMLHolder::EnableDMLHolder(BOOL enable) + : mEnable(enable) +{ +#ifndef FEATURE_PAL + // If the user has not requested that we use DML, it's still possible that + // they have instead specified ".prefer_dml 1". If enable is false, + // we will check here for .prefer_dml. Since this class is only used once + // per command issued to SOS, this should only check the setting once per + // sos command issued. + if (!mEnable && Output::g_DMLEnable <= 0) + { + ULONG opts; + HRESULT hr = g_ExtControl->GetEngineOptions(&opts); + mEnable = SUCCEEDED(hr) && (opts & DEBUG_ENGOPT_PREFER_DML) == DEBUG_ENGOPT_PREFER_DML; + } + + if (mEnable) + { + Output::g_DMLEnable++; + } +#endif // FEATURE_PAL +} + +EnableDMLHolder::~EnableDMLHolder() +{ +#ifndef FEATURE_PAL + if (mEnable) + Output::g_DMLEnable--; +#endif +} + +bool IsDMLEnabled() +{ + return Output::g_DMLEnable > 0; +} + +NoOutputHolder::NoOutputHolder(BOOL bSuppress) + : mSuppress(bSuppress) +{ + if (mSuppress) + Output::g_bSuppressOutput++; +} + +NoOutputHolder::~NoOutputHolder() +{ + if (mSuppress) + Output::g_bSuppressOutput--; +} + +// +// Code to support mapping RVAs to managed code line numbers. +// + +// +// Retrieves the IXCLRDataMethodInstance* instance associated with the +// passed in native offset. +HRESULT +GetClrMethodInstance( + ___in ULONG64 NativeOffset, + ___out IXCLRDataMethodInstance** Method) +{ + HRESULT Status; + CLRDATA_ENUM MethEnum; + + Status = g_clrData->StartEnumMethodInstancesByAddress(NativeOffset, NULL, &MethEnum); + + if (Status == S_OK) + { + Status = g_clrData->EnumMethodInstanceByAddress(&MethEnum, Method); + g_clrData->EndEnumMethodInstancesByAddress(MethEnum); + } + + // Any alternate success is a true failure here. + return (Status == S_OK || FAILED(Status)) ? Status : E_NOINTERFACE; +} + +// +// Enumerates over the IL address map associated with the passed in +// managed method, and returns the highest non-epilog offset. +HRESULT +GetLastMethodIlOffset( + ___in IXCLRDataMethodInstance* Method, + ___out PULONG32 MethodOffs) +{ + HRESULT Status; + CLRDATA_IL_ADDRESS_MAP MapLocal[16]; + CLRDATA_IL_ADDRESS_MAP* Map = MapLocal; + ULONG32 MapCount = _countof(MapLocal); + ULONG32 MapNeeded; + ULONG32 HighestOffset; + + for (;;) + { + if ((Status = Method->GetILAddressMap(MapCount, &MapNeeded, Map)) != S_OK) + { + return Status; + } + + if (MapNeeded <= MapCount) + { + break; + } + + // Need more map entries. + if (Map != MapLocal) + { + // Already went around and the answer changed, + // which should not be possible. + delete[] Map; + return E_UNEXPECTED; + } + + Map = new CLRDATA_IL_ADDRESS_MAP[MapNeeded]; + if (!Map) + { + return E_OUTOFMEMORY; + } + + MapCount = MapNeeded; + } + + HighestOffset = 0; + for (size_t i = 0; i < MapNeeded; i++) + { + if (Map[i].ilOffset != (ULONG32)CLRDATA_IL_OFFSET_NO_MAPPING && + Map[i].ilOffset != (ULONG32)CLRDATA_IL_OFFSET_PROLOG && + Map[i].ilOffset != (ULONG32)CLRDATA_IL_OFFSET_EPILOG && + Map[i].ilOffset > HighestOffset) + { + HighestOffset = Map[i].ilOffset; + } + } + + if (Map != MapLocal) + { + delete[] Map; + } + + *MethodOffs = HighestOffset; + return S_OK; +} + +// +// Convert a native offset (possibly already associated with a managed +// method identified by the passed in IXCLRDataMethodInstance) to a +// triplet (ImageInfo, MethodToken, MethodOffset) that can be used to +// represent an "IL offset". +HRESULT +ConvertNativeToIlOffset( + ___in ULONG64 native, + ___out IXCLRDataModule** ppModule, + ___out mdMethodDef* methodToken, + ___out PULONG32 methodOffs) +{ + ToRelease<IXCLRDataMethodInstance> pMethodInst(NULL); + HRESULT Status; + + if ((Status = GetClrMethodInstance(native, &pMethodInst)) != S_OK) + { + return Status; + } + + if ((Status = pMethodInst->GetILOffsetsByAddress(native, 1, NULL, methodOffs)) != S_OK) + { + *methodOffs = 0; + } + else + { + switch((LONG)*methodOffs) + { + case CLRDATA_IL_OFFSET_NO_MAPPING: + return E_NOINTERFACE; + + case CLRDATA_IL_OFFSET_PROLOG: + // Treat all of the prologue as part of + // the first source line. + *methodOffs = 0; + break; + + case CLRDATA_IL_OFFSET_EPILOG: + // Back up until we find the last real + // IL offset. + if ((Status = GetLastMethodIlOffset(pMethodInst, methodOffs)) != S_OK) + { + return Status; + } + break; + } + } + + return pMethodInst->GetTokenAndScope(methodToken, ppModule); +} + +// Based on a native offset, passed in the first argument this function +// identifies the corresponding source file name and line number. +HRESULT +GetLineByOffset( + ___in ULONG64 offset, + ___out ULONG *pLinenum, + __out_ecount(cchFileName) WCHAR* pwszFileName, + ___in ULONG cchFileName) +{ + HRESULT Status = S_OK; + ULONG32 methodToken; + ULONG32 methodOffs; + + // Find the image, method token and IL offset that correspond to "offset" + ToRelease<IXCLRDataModule> pModule(NULL); + IfFailRet(ConvertNativeToIlOffset(offset, &pModule, &methodToken, &methodOffs)); + + ToRelease<IMetaDataImport> pMDImport(NULL); + IfFailRet(pModule->QueryInterface(IID_IMetaDataImport, (LPVOID *) &pMDImport)); + + SymbolReader symbolReader; + IfFailRet(symbolReader.LoadSymbols(pMDImport, pModule)); + + return symbolReader.GetLineByILOffset(methodToken, methodOffs, pLinenum, pwszFileName, cchFileName); +} + +void TableOutput::ReInit(int numColumns, int defaultColumnWidth, Alignment alignmentDefault, int indent, int padding) +{ + Clear(); + + mColumns = numColumns; + mDefaultWidth = defaultColumnWidth; + mIndent = indent; + mPadding = padding; + mCurrCol = 0; + mDefaultAlign = alignmentDefault; +} + +void TableOutput::SetWidths(int columns, ...) +{ + SOS_Assert(columns > 0); + SOS_Assert(columns <= mColumns); + + AllocWidths(); + + va_list list; + va_start(list, columns); + + for (int i = 0; i < columns; ++i) + mWidths[i] = va_arg(list, int); + + va_end(list); +} + +void TableOutput::SetColWidth(int col, int width) +{ + SOS_Assert(col >= 0 && col < mColumns); + SOS_Assert(width >= 0); + + AllocWidths(); + + mWidths[col] = width; +} + +void TableOutput::SetColAlignment(int col, Alignment align) +{ + SOS_Assert(col >= 0 && col < mColumns); + + if (!mAlignments) + { + mAlignments = new Alignment[mColumns]; + for (int i = 0; i < mColumns; ++i) + mAlignments[i] = mDefaultAlign; + } + + mAlignments[col] = align; +} + + + +void TableOutput::Clear() +{ + if (mAlignments) + { + delete [] mAlignments; + mAlignments = 0; + } + + if (mWidths) + { + delete [] mWidths; + mWidths = 0; + } +} + +void TableOutput::AllocWidths() +{ + if (!mWidths) + { + mWidths = new int[mColumns]; + for (int i = 0; i < mColumns; ++i) + mWidths[i] = mDefaultWidth; + } +} + +int TableOutput::GetColumnWidth(int col) +{ + SOS_Assert(col < mColumns); + + if (mWidths) + return mWidths[col]; + + return mDefaultWidth; +} + +Alignment TableOutput::GetColAlign(int col) +{ + SOS_Assert(col < mColumns); + if (mAlignments) + return mAlignments[col]; + + return mDefaultAlign; +} + +const char *TableOutput::GetWhitespace(int amount) +{ + static char WhiteSpace[256] = ""; + static int count = 0; + + if (count == 0) + { + count = _countof(WhiteSpace); + for (int i = 0; i < count-1; ++i) + WhiteSpace[i] = ' '; + WhiteSpace[count-1] = 0; + } + + SOS_Assert(amount < count); + return &WhiteSpace[count-amount-1]; +} + +void TableOutput::OutputBlankColumns(int col) +{ + if (col < mCurrCol) + { + ExtOut("\n"); + mCurrCol = 0; + } + + int whitespace = 0; + for (int i = mCurrCol; i < col; ++i) + whitespace += GetColumnWidth(i) + mPadding; + + ExtOut(GetWhitespace(whitespace)); +} + +void TableOutput::OutputIndent() +{ + if (mIndent) + ExtOut(GetWhitespace(mIndent)); +} + +#ifndef FEATURE_PAL + +PEOffsetMemoryReader::PEOffsetMemoryReader(TADDR moduleBaseAddress) : + m_moduleBaseAddress(moduleBaseAddress), + m_refCount(1) + {} + +HRESULT __stdcall PEOffsetMemoryReader::QueryInterface(REFIID riid, VOID** ppInterface) +{ + if(riid == __uuidof(IDiaReadExeAtOffsetCallback)) + { + *ppInterface = static_cast<IDiaReadExeAtOffsetCallback*>(this); + AddRef(); + return S_OK; + } + else if(riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG __stdcall PEOffsetMemoryReader::AddRef() +{ + return InterlockedIncrement((volatile LONG *) &m_refCount); +} + +ULONG __stdcall PEOffsetMemoryReader::Release() +{ + ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount); + if(count == 0) + { + delete this; + } + return count; +} + +// IDiaReadExeAtOffsetCallback implementation +HRESULT __stdcall PEOffsetMemoryReader::ReadExecutableAt(DWORDLONG fileOffset, DWORD cbData, DWORD* pcbData, BYTE data[]) +{ + return SafeReadMemory(m_moduleBaseAddress + fileOffset, data, cbData, pcbData) ? S_OK : E_FAIL; +} + +PERvaMemoryReader::PERvaMemoryReader(TADDR moduleBaseAddress) : + m_moduleBaseAddress(moduleBaseAddress), + m_refCount(1) + {} + +HRESULT __stdcall PERvaMemoryReader::QueryInterface(REFIID riid, VOID** ppInterface) +{ + if(riid == __uuidof(IDiaReadExeAtRVACallback)) + { + *ppInterface = static_cast<IDiaReadExeAtRVACallback*>(this); + AddRef(); + return S_OK; + } + else if(riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG __stdcall PERvaMemoryReader::AddRef() +{ + return InterlockedIncrement((volatile LONG *) &m_refCount); +} + +ULONG __stdcall PERvaMemoryReader::Release() +{ + ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount); + if(count == 0) + { + delete this; + } + return count; +} + +// IDiaReadExeAtOffsetCallback implementation +HRESULT __stdcall PERvaMemoryReader::ReadExecutableAtRVA(DWORD relativeVirtualAddress, DWORD cbData, DWORD* pcbData, BYTE data[]) +{ + return SafeReadMemory(m_moduleBaseAddress + relativeVirtualAddress, data, cbData, pcbData) ? S_OK : E_FAIL; +} + +#endif // FEATURE_PAL + +HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in ICorDebugModule* pModule) +{ + HRESULT Status = S_OK; + BOOL isDynamic = FALSE; + BOOL isInMemory = FALSE; + IfFailRet(pModule->IsDynamic(&isDynamic)); + IfFailRet(pModule->IsInMemory(&isInMemory)); + + if (isDynamic) + { + // Dynamic and in memory assemblies are a special case which we will ignore for now + ExtWarn("SOS Warning: Loading symbols for dynamic assemblies is not yet supported\n"); + return E_FAIL; + } + + ULONG64 peAddress = 0; + ULONG32 peSize = 0; + IfFailRet(pModule->GetBaseAddress(&peAddress)); + IfFailRet(pModule->GetSize(&peSize)); + + ULONG32 len = 0; + WCHAR moduleName[MAX_LONGPATH]; + IfFailRet(pModule->GetName(_countof(moduleName), &len, moduleName)); + +#ifndef FEATURE_PAL + if (SUCCEEDED(LoadSymbolsForWindowsPDB(pMD, peAddress, moduleName, isInMemory))) + { + return S_OK; + } +#endif // FEATURE_PAL + return LoadSymbolsForPortablePDB(moduleName, isInMemory, isInMemory, peAddress, peSize, 0, 0); +} + +HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in IXCLRDataModule* pModule) +{ + DacpGetModuleData moduleData; + HRESULT hr = moduleData.Request(pModule); + if (FAILED(hr)) + { + ExtOut("LoadSymbols moduleData.Request FAILED 0x%08x\n", hr); + return hr; + } + + if (moduleData.IsDynamic) + { + ExtWarn("SOS Warning: Loading symbols for dynamic assemblies is not yet supported\n"); + return E_FAIL; + } + + ArrayHolder<WCHAR> pModuleName = new WCHAR[MAX_LONGPATH + 1]; + ULONG32 nameLen = 0; + hr = pModule->GetFileName(MAX_LONGPATH, &nameLen, pModuleName); + if (FAILED(hr)) + { + ExtOut("LoadSymbols: IXCLRDataModule->GetFileName FAILED 0x%08x\n", hr); + return hr; + } + +#ifndef FEATURE_PAL + // TODO: in-memory windows PDB not supported + hr = LoadSymbolsForWindowsPDB(pMD, moduleData.LoadedPEAddress, pModuleName, moduleData.IsFileLayout); + if (SUCCEEDED(hr)) + { + return hr; + } +#endif // FEATURE_PAL + + return LoadSymbolsForPortablePDB( + pModuleName, + moduleData.IsInMemory, + moduleData.IsFileLayout, + moduleData.LoadedPEAddress, + moduleData.LoadedPESize, + moduleData.InMemoryPdbAddress, + moduleData.InMemoryPdbSize); +} + +#ifndef FEATURE_PAL + +HRESULT SymbolReader::LoadSymbolsForWindowsPDB(___in IMetaDataImport* pMD, ___in ULONG64 peAddress, __in_z WCHAR* pModuleName, ___in BOOL isFileLayout) +{ + HRESULT Status = S_OK; + + if (m_pSymReader != NULL) + return S_OK; + + IfFailRet(CoInitialize(NULL)); + + // We now need a binder object that will take the module and return a + // reader object + ToRelease<ISymUnmanagedBinder3> pSymBinder; + if (FAILED(Status = CreateInstanceCustom(CLSID_CorSymBinder_SxS, + IID_ISymUnmanagedBinder3, + W("diasymreader.dll"), + cciLatestFx|cciDacColocated|cciDbgPath, + (void**)&pSymBinder))) + { + ExtOut("SOS Error: Unable to CoCreateInstance class=CLSID_CorSymBinder_SxS, interface=IID_ISymUnmanagedBinder3, hr=0x%x\n", Status); + ExtOut("This usually means the installation of .Net Framework on your machine is missing or needs repair\n"); + return Status; + } + + ToRelease<IDebugSymbols3> spSym3(NULL); + Status = g_ExtSymbols->QueryInterface(__uuidof(IDebugSymbols3), (void**)&spSym3); + if (FAILED(Status)) + { + ExtOut("SOS Error: Unable to query IDebugSymbols3 HRESULT=0x%x.\n", Status); + return Status; + } + + ULONG pathSize = 0; + Status = spSym3->GetSymbolPathWide(NULL, 0, &pathSize); + if (FAILED(Status)) //S_FALSE if the path doesn't fit, but if the path was size 0 perhaps we would get S_OK? + { + ExtOut("SOS Error: Unable to get symbol path length. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", Status); + return Status; + } + + ArrayHolder<WCHAR> symbolPath = new WCHAR[pathSize]; + Status = spSym3->GetSymbolPathWide(symbolPath, pathSize, NULL); + if (S_OK != Status) + { + ExtOut("SOS Error: Unable to get symbol path. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", Status); + return Status; + } + + ToRelease<IUnknown> pCallback = NULL; + if (isFileLayout) + { + pCallback = (IUnknown*) new PEOffsetMemoryReader(TO_TADDR(peAddress)); + } + else + { + pCallback = (IUnknown*) new PERvaMemoryReader(TO_TADDR(peAddress)); + } + + // TODO: this should be better integrated with windbg's symbol lookup + Status = pSymBinder->GetReaderFromCallback(pMD, pModuleName, symbolPath, + AllowRegistryAccess | AllowSymbolServerAccess | AllowOriginalPathAccess | AllowReferencePathAccess, pCallback, &m_pSymReader); + + if (FAILED(Status) && m_pSymReader != NULL) + { + m_pSymReader->Release(); + m_pSymReader = NULL; + } + return Status; +} + +#endif // FEATURE_PAL + +// +// Pass to managed helper code to read in-memory PEs/PDBs +// Returns the number of bytes read. +// +int ReadMemoryForSymbols(ULONG64 address, char *buffer, int cb) +{ + ULONG read; + if (SafeReadMemory(address, (PVOID)buffer, cb, &read)) + { + return read; + } + return 0; +} + +HRESULT SymbolReader::LoadSymbolsForPortablePDB(__in_z WCHAR* pModuleName, ___in BOOL isInMemory, ___in BOOL isFileLayout, + ___in ULONG64 peAddress, ___in ULONG64 peSize, ___in ULONG64 inMemoryPdbAddress, ___in ULONG64 inMemoryPdbSize) +{ + HRESULT Status = S_OK; + + if (loadSymbolsForModuleDelegate == nullptr) + { + IfFailRet(PrepareSymbolReader()); + } + + // The module name needs to be null for in-memory PE's. + ArrayHolder<char> szModuleName = nullptr; + if (!isInMemory && pModuleName != nullptr) + { + szModuleName = new char[MAX_LONGPATH]; + if (WideCharToMultiByte(CP_ACP, 0, pModuleName, (int)(_wcslen(pModuleName) + 1), szModuleName, MAX_LONGPATH, NULL, NULL) == 0) + { + return E_FAIL; + } + } + + m_symbolReaderHandle = loadSymbolsForModuleDelegate(szModuleName, isFileLayout, peAddress, + (int)peSize, inMemoryPdbAddress, (int)inMemoryPdbSize, ReadMemoryForSymbols); + + if (m_symbolReaderHandle == 0) + { + return E_FAIL; + } + + return Status; +} + +#ifndef FEATURE_PAL + +void AddFilesFromDirectoryToTpaList(const char* directory, std::string& tpaList) +{ + const char * const tpaExtensions[] = { + "*.ni.dll", // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir + "*.dll", + "*.ni.exe", + "*.exe", + }; + + std::set<std::string> addedAssemblies; + + // Walk the directory for each extension separately so that we first get files with .ni.dll extension, + // then files with .dll extension, etc. + for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++) + { + const char* ext = tpaExtensions[extIndex]; + size_t extLength = strlen(ext); + + std::string assemblyPath(directory); + assemblyPath.append(DIRECTORY_SEPARATOR_STR_A); + assemblyPath.append(tpaExtensions[extIndex]); + + WIN32_FIND_DATAA data; + HANDLE findHandle = FindFirstFileA(assemblyPath.c_str(), &data); + + if (findHandle != INVALID_HANDLE_VALUE) + { + do + { + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + + std::string filename(data.cFileName); + size_t extPos = filename.length() - extLength; + std::string filenameWithoutExt(filename.substr(0, extPos)); + + // Make sure if we have an assembly with multiple extensions present, + // we insert only one version of it. + if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end()) + { + addedAssemblies.insert(filenameWithoutExt); + + tpaList.append(directory); + tpaList.append(DIRECTORY_SEPARATOR_STR_A); + tpaList.append(filename); + tpaList.append(";"); + } + } + } + while (0 != FindNextFileA(findHandle, &data)); + + FindClose(findHandle); + } + } +} + +bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable) +{ + ArrayHolder<char> hostPath = new char[MAX_LONGPATH+1]; + if (::GetModuleFileName(NULL, hostPath, MAX_LONGPATH) == 0) + { + return false; + } + + entrypointExecutable.clear(); + entrypointExecutable.append(hostPath); + + return true; +} + +#endif // FEATURE_PAL + +HRESULT SymbolReader::PrepareSymbolReader() +{ + static bool attemptedSymbolReaderPreparation = false; + if (attemptedSymbolReaderPreparation) + { + // If we already tried to set up the symbol reader, we won't try again. + return E_FAIL; + } + + attemptedSymbolReaderPreparation = true; + + std::string absolutePath; + std::string coreClrPath; + HRESULT Status; + +#ifdef FEATURE_PAL + coreClrPath = g_ExtServices->GetCoreClrDirectory(); + if (!GetAbsolutePath(coreClrPath.c_str(), absolutePath)) + { + ExtErr("Error: Failed to get coreclr absolute path\n"); + return E_FAIL; + } + coreClrPath.append(DIRECTORY_SEPARATOR_STR_A); + coreClrPath.append(MAIN_CLR_DLL_NAME_A); +#else + ULONG index; + Status = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, &index, NULL); + if (FAILED(Status)) + { + ExtErr("Error: Can't find coreclr module\n"); + return Status; + } + ArrayHolder<char> szModuleName = new char[MAX_LONGPATH + 1]; + Status = g_ExtSymbols->GetModuleNames(index, 0, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL, NULL, 0, NULL); + if (FAILED(Status)) + { + ExtErr("Error: Failed to get coreclr module name\n"); + return Status; + } + coreClrPath = szModuleName; + + // Parse off the module name to get just the path + size_t pos = coreClrPath.rfind(DIRECTORY_SEPARATOR_CHAR_A); + if (pos == std::string::npos) + { + ExtErr("Error: Failed to parse coreclr module name\n"); + return E_FAIL; + } + absolutePath.assign(coreClrPath, 0, pos); +#endif // FEATURE_PAL + + HMODULE coreclrLib = LoadLibraryA(coreClrPath.c_str()); + if (coreclrLib == nullptr) + { + ExtErr("Error: Failed to load %s\n", coreClrPath.c_str()); + return E_FAIL; + } + + void *hostHandle; + unsigned int domainId; + coreclr_initialize_ptr initializeCoreCLR = (coreclr_initialize_ptr)GetProcAddress(coreclrLib, "coreclr_initialize"); + if (initializeCoreCLR == nullptr) + { + ExtErr("Error: coreclr_initialize not found\n"); + return E_FAIL; + } + + std::string tpaList; + AddFilesFromDirectoryToTpaList(absolutePath.c_str(), tpaList); + + const char *propertyKeys[] = { + "TRUSTED_PLATFORM_ASSEMBLIES", "APP_PATHS", "APP_NI_PATHS", + "NATIVE_DLL_SEARCH_DIRECTORIES", "AppDomainCompatSwitch"}; + + const char *propertyValues[] = {// TRUSTED_PLATFORM_ASSEMBLIES + tpaList.c_str(), + // APP_PATHS + absolutePath.c_str(), + // APP_NI_PATHS + absolutePath.c_str(), + // NATIVE_DLL_SEARCH_DIRECTORIES + absolutePath.c_str(), + // AppDomainCompatSwitch + "UseLatestBehaviorWhenTFMNotSpecified"}; + + std::string entryPointExecutablePath; + if (!GetEntrypointExecutableAbsolutePath(entryPointExecutablePath)) + { + ExtErr("Could not get full path to current executable"); + return E_FAIL; + } + + Status = initializeCoreCLR(entryPointExecutablePath.c_str(), "sos", + sizeof(propertyKeys) / sizeof(propertyKeys[0]), propertyKeys, propertyValues, &hostHandle, &domainId); + + if (FAILED(Status)) + { + ExtErr("Error: Fail to initialize CoreCLR %08x\n", Status); + return Status; + } + + coreclr_create_delegate_ptr createDelegate = (coreclr_create_delegate_ptr)GetProcAddress(coreclrLib, "coreclr_create_delegate"); + if (createDelegate == nullptr) + { + ExtErr("Error: coreclr_create_delegate not found\n"); + return E_FAIL; + } + + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "LoadSymbolsForModule", (void **)&loadSymbolsForModuleDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "Dispose", (void **)&disposeDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "ResolveSequencePoint", (void **)&resolveSequencePointDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetLocalVariableName", (void **)&getLocalVariableNameDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetLineByILOffset", (void **)&getLineByILOffsetDelegate)); + + return Status; +} + +HRESULT SymbolReader::GetLineByILOffset(___in mdMethodDef methodToken, ___in ULONG64 ilOffset, + ___out ULONG *pLinenum, __out_ecount(cchFileName) WCHAR* pwszFileName, ___in ULONG cchFileName) +{ + HRESULT Status = S_OK; + + if (m_symbolReaderHandle != 0) + { + _ASSERTE(getLineByILOffsetDelegate != nullptr); + + BSTR bstrFileName = SysAllocStringLen(0, MAX_LONGPATH); + if (bstrFileName == nullptr) + { + return E_OUTOFMEMORY; + } + // Source lines with 0xFEEFEE markers are filtered out on the managed side. + if ((getLineByILOffsetDelegate(m_symbolReaderHandle, methodToken, ilOffset, pLinenum, &bstrFileName) == FALSE) || (*pLinenum == 0)) + { + SysFreeString(bstrFileName); + return E_FAIL; + } + wcscpy_s(pwszFileName, cchFileName, bstrFileName); + SysFreeString(bstrFileName); + return S_OK; + } + +#ifndef FEATURE_PAL + if (m_pSymReader == NULL) + return E_FAIL; + + ToRelease<ISymUnmanagedMethod> pSymMethod(NULL); + IfFailRet(m_pSymReader->GetMethod(methodToken, &pSymMethod)); + + ULONG32 seqPointCount = 0; + IfFailRet(pSymMethod->GetSequencePointCount(&seqPointCount)); + + if (seqPointCount == 0) + return E_FAIL; + + // allocate memory for the objects to be fetched + ArrayHolder<ULONG32> offsets(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> lines(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> columns(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> endlines(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> endcolumns(new ULONG32[seqPointCount]); + ArrayHolder<ToRelease<ISymUnmanagedDocument>> documents(new ToRelease<ISymUnmanagedDocument>[seqPointCount]); + + ULONG32 realSeqPointCount = 0; + IfFailRet(pSymMethod->GetSequencePoints(seqPointCount, &realSeqPointCount, offsets, &(documents[0]), lines, columns, endlines, endcolumns)); + + const ULONG32 HiddenLine = 0x00feefee; + int bestSoFar = -1; + + for (int i = 0; i < (int)realSeqPointCount; i++) + { + if (offsets[i] > ilOffset) + break; + + if (lines[i] != HiddenLine) + bestSoFar = i; + } + + if (bestSoFar != -1) + { + ULONG32 cchNeeded = 0; + IfFailRet(documents[bestSoFar]->GetURL(cchFileName, &cchNeeded, pwszFileName)); + + *pLinenum = lines[bestSoFar]; + return S_OK; + } +#endif // FEATURE_PAL + + return E_FAIL; +} + +HRESULT SymbolReader::GetNamedLocalVariable(___in ISymUnmanagedScope * pScope, ___in ICorDebugILFrame * pILFrame, ___in mdMethodDef methodToken, + ___in ULONG localIndex, __out_ecount(paramNameLen) WCHAR* paramName, ___in ULONG paramNameLen, ICorDebugValue** ppValue) +{ + HRESULT Status = S_OK; + + if (m_symbolReaderHandle != 0) + { + _ASSERTE(getLocalVariableNameDelegate != nullptr); + + BSTR wszParamName = SysAllocStringLen(0, mdNameLen); + if (wszParamName == NULL) + { + return E_OUTOFMEMORY; + } + + if (getLocalVariableNameDelegate(m_symbolReaderHandle, methodToken, localIndex, &wszParamName) == FALSE) + { + SysFreeString(wszParamName); + return E_FAIL; + } + + wcscpy_s(paramName, paramNameLen, wszParamName); + SysFreeString(wszParamName); + + if (FAILED(pILFrame->GetLocalVariable(localIndex, ppValue)) || (*ppValue == NULL)) + { + *ppValue = NULL; + return E_FAIL; + } + return S_OK; + } + +#ifndef FEATURE_PAL + if (m_pSymReader == NULL) + return E_FAIL; + + if (pScope == NULL) + { + ToRelease<ISymUnmanagedMethod> pSymMethod; + IfFailRet(m_pSymReader->GetMethod(methodToken, &pSymMethod)); + + ToRelease<ISymUnmanagedScope> pScope; + IfFailRet(pSymMethod->GetRootScope(&pScope)); + + return GetNamedLocalVariable(pScope, pILFrame, methodToken, localIndex, paramName, paramNameLen, ppValue); + } + else + { + ULONG32 numVars = 0; + IfFailRet(pScope->GetLocals(0, &numVars, NULL)); + + ArrayHolder<ISymUnmanagedVariable*> pLocals = new ISymUnmanagedVariable*[numVars]; + IfFailRet(pScope->GetLocals(numVars, &numVars, pLocals)); + + for (ULONG i = 0; i < numVars; i++) + { + ULONG32 varIndexInMethod = 0; + if (SUCCEEDED(pLocals[i]->GetAddressField1(&varIndexInMethod))) + { + if (varIndexInMethod != localIndex) + continue; + + ULONG32 nameLen = 0; + if (FAILED(pLocals[i]->GetName(paramNameLen, &nameLen, paramName))) + swprintf_s(paramName, paramNameLen, W("local_%d\0"), localIndex); + + if (SUCCEEDED(pILFrame->GetLocalVariable(varIndexInMethod, ppValue)) && (*ppValue != NULL)) + { + for(ULONG j = 0; j < numVars; j++) pLocals[j]->Release(); + return S_OK; + } + else + { + *ppValue = NULL; + for(ULONG j = 0; j < numVars; j++) pLocals[j]->Release(); + return E_FAIL; + } + } + } + + ULONG32 numChildren = 0; + IfFailRet(pScope->GetChildren(0, &numChildren, NULL)); + + ArrayHolder<ISymUnmanagedScope*> pChildren = new ISymUnmanagedScope*[numChildren]; + IfFailRet(pScope->GetChildren(numChildren, &numChildren, pChildren)); + + for (ULONG i = 0; i < numChildren; i++) + { + if (SUCCEEDED(GetNamedLocalVariable(pChildren[i], pILFrame, methodToken, localIndex, paramName, paramNameLen, ppValue))) + { + for (ULONG j = 0; j < numChildren; j++) pChildren[j]->Release(); + return S_OK; + } + } + + for (ULONG j = 0; j < numChildren; j++) pChildren[j]->Release(); + } +#endif // FEATURE_PAL + + return E_FAIL; +} + +HRESULT SymbolReader::GetNamedLocalVariable(___in ICorDebugFrame * pFrame, ___in ULONG localIndex, __out_ecount(paramNameLen) WCHAR* paramName, + ___in ULONG paramNameLen, ___out ICorDebugValue** ppValue) +{ + HRESULT Status = S_OK; + + *ppValue = NULL; + paramName[0] = L'\0'; + + ToRelease<ICorDebugILFrame> pILFrame; + IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame)); + + ToRelease<ICorDebugFunction> pFunction; + IfFailRet(pFrame->GetFunction(&pFunction)); + + mdMethodDef methodDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugModule> pModule; + IfFailRet(pFunction->GetClass(&pClass)); + IfFailRet(pFunction->GetModule(&pModule)); + IfFailRet(pFunction->GetToken(&methodDef)); + + return GetNamedLocalVariable(NULL, pILFrame, methodDef, localIndex, paramName, paramNameLen, ppValue); +} + +HRESULT SymbolReader::ResolveSequencePoint(__in_z WCHAR* pFilename, ___in ULONG32 lineNumber, ___in TADDR mod, ___out mdMethodDef* pToken, ___out ULONG32* pIlOffset) +{ + HRESULT Status = S_OK; + + if (m_symbolReaderHandle != 0) + { + _ASSERTE(resolveSequencePointDelegate != nullptr); + + char szName[mdNameLen]; + if (WideCharToMultiByte(CP_ACP, 0, pFilename, (int)(_wcslen(pFilename) + 1), szName, mdNameLen, NULL, NULL) == 0) + { + return E_FAIL; + } + if (resolveSequencePointDelegate(m_symbolReaderHandle, szName, lineNumber, pToken, pIlOffset) == FALSE) + { + return E_FAIL; + } + return S_OK; + } + +#ifndef FEATURE_PAL + if (m_pSymReader == NULL) + return E_FAIL; + + ULONG32 cDocs = 0; + ULONG32 cDocsNeeded = 0; + ArrayHolder<ToRelease<ISymUnmanagedDocument>> pDocs = NULL; + + IfFailRet(m_pSymReader->GetDocuments(cDocs, &cDocsNeeded, NULL)); + pDocs = new ToRelease<ISymUnmanagedDocument>[cDocsNeeded]; + cDocs = cDocsNeeded; + IfFailRet(m_pSymReader->GetDocuments(cDocs, &cDocsNeeded, &(pDocs[0]))); + + ULONG32 filenameLen = (ULONG32) _wcslen(pFilename); + + for (ULONG32 i = 0; i < cDocs; i++) + { + ULONG32 cchUrl = 0; + ULONG32 cchUrlNeeded = 0; + ArrayHolder<WCHAR> pUrl = NULL; + IfFailRet(pDocs[i]->GetURL(cchUrl, &cchUrlNeeded, pUrl)); + pUrl = new WCHAR[cchUrlNeeded]; + cchUrl = cchUrlNeeded; + IfFailRet(pDocs[i]->GetURL(cchUrl, &cchUrlNeeded, pUrl)); + + // If the URL is exactly as long as the filename then compare the two names directly + if (cchUrl-1 == filenameLen) + { + if (0!=_wcsicmp(pUrl, pFilename)) + continue; + } + // does the URL suffix match [back]slash + filename? + else if (cchUrl-1 > filenameLen) + { + WCHAR* slashLocation = pUrl + (cchUrl - filenameLen - 2); + if (*slashLocation != L'\\' && *slashLocation != L'/') + continue; + if (0 != _wcsicmp(slashLocation+1, pFilename)) + continue; + } + // URL is too short to match + else + continue; + + ULONG32 closestLine = 0; + if (FAILED(pDocs[i]->FindClosestLine(lineNumber, &closestLine))) + continue; + + ToRelease<ISymUnmanagedMethod> pSymUnmanagedMethod; + IfFailRet(m_pSymReader->GetMethodFromDocumentPosition(pDocs[i], closestLine, 0, &pSymUnmanagedMethod)); + IfFailRet(pSymUnmanagedMethod->GetToken(pToken)); + IfFailRet(pSymUnmanagedMethod->GetOffset(pDocs[i], closestLine, 0, pIlOffset)); + + // If this IL + if (*pIlOffset == -1) + { + return E_FAIL; + } + return S_OK; + } +#endif // FEATURE_PAL + + return E_FAIL; +} + +static void AddAssemblyName(WString& methodOutput, CLRDATA_ADDRESS mdesc) +{ + DacpMethodDescData mdescData; + if (SUCCEEDED(mdescData.Request(g_sos, mdesc))) + { + DacpModuleData dmd; + if (SUCCEEDED(dmd.Request(g_sos, mdescData.ModulePtr))) + { + ToRelease<IXCLRDataModule> pModule; + if (SUCCEEDED(g_sos->GetModule(mdescData.ModulePtr, &pModule))) + { + ArrayHolder<WCHAR> wszFileName = new WCHAR[MAX_LONGPATH + 1]; + ULONG32 nameLen = 0; + if (SUCCEEDED(pModule->GetFileName(MAX_LONGPATH, &nameLen, wszFileName))) + { + if (wszFileName[0] != W('\0')) + { + WCHAR *pJustName = _wcsrchr(wszFileName, DIRECTORY_SEPARATOR_CHAR_W); + if (pJustName == NULL) + pJustName = wszFileName - 1; + methodOutput += (pJustName + 1); + methodOutput += W("!"); + } + } + } + } + } +} + +WString GetFrameFromAddress(TADDR frameAddr, IXCLRDataStackWalk *pStackWalk, BOOL bAssemblyName) +{ + TADDR vtAddr; + MOVE(vtAddr, frameAddr); + + WString frameOutput; + frameOutput += W("["); + + if (SUCCEEDED(g_sos->GetFrameName(TO_CDADDR(vtAddr), mdNameLen, g_mdName, NULL))) + frameOutput += g_mdName; + else + frameOutput += W("Frame"); + + frameOutput += WString(W(": ")) + Pointer(frameAddr) + W("] "); + + // Print the frame's associated function info, if it has any. + CLRDATA_ADDRESS mdesc = 0; + if (SUCCEEDED(g_sos->GetMethodDescPtrFromFrame(frameAddr, &mdesc))) + { + if (SUCCEEDED(g_sos->GetMethodDescName(mdesc, mdNameLen, g_mdName, NULL))) + { + if (bAssemblyName) + { + AddAssemblyName(frameOutput, mdesc); + } + + frameOutput += g_mdName; + } + else + { + frameOutput += W("<unknown method>"); + } + } + else if (pStackWalk) + { + // The Frame did not have direct function info, so try to get the method instance + // (in this case a MethodDesc), and read the name from it. + ToRelease<IXCLRDataFrame> frame; + if (SUCCEEDED(pStackWalk->GetFrame(&frame))) + { + ToRelease<IXCLRDataMethodInstance> methodInstance; + if (SUCCEEDED(frame->GetMethodInstance(&methodInstance))) + { + // GetName can return S_FALSE if mdNameLen is not large enough. However we are already + // passing a pretty big buffer in. If this returns S_FALSE (meaning the buffer is too + // small) then we should not output it anyway. + if (methodInstance->GetName(0, mdNameLen, NULL, g_mdName) == S_OK) + frameOutput += g_mdName; + } + } + } + + return frameOutput; +} + +WString MethodNameFromIP(CLRDATA_ADDRESS ip, BOOL bSuppressLines, BOOL bAssemblyName, BOOL bDisplacement) +{ + ULONG linenum; + WString methodOutput; + CLRDATA_ADDRESS mdesc = 0; + + if (FAILED(g_sos->GetMethodDescPtrFromIP(ip, &mdesc))) + { + methodOutput = W("<unknown>"); + } + else + { + DacpMethodDescData mdescData; + if (SUCCEEDED(g_sos->GetMethodDescName(mdesc, mdNameLen, g_mdName, NULL))) + { + if (bAssemblyName) + { + AddAssemblyName(methodOutput, mdesc); + } + + methodOutput += g_mdName; + + if (bDisplacement) + { + if (SUCCEEDED(mdescData.Request(g_sos, mdesc))) + { + ULONG64 disp = (ip - mdescData.NativeCodeAddr); + if (disp) + { + methodOutput += W(" + "); + methodOutput += Decimal(disp); + } + } + } + } + else if (SUCCEEDED(mdescData.Request(g_sos, mdesc))) + { + DacpModuleData dmd; + BOOL bModuleNameWorked = FALSE; + ULONG64 addrInModule = ip; + if (SUCCEEDED(dmd.Request(g_sos, mdescData.ModulePtr))) + { + CLRDATA_ADDRESS peFileBase = 0; + if (SUCCEEDED(g_sos->GetPEFileBase(dmd.File, &peFileBase))) + { + if (peFileBase) + { + addrInModule = peFileBase; + } + } + } + ULONG Index; + ULONG64 moduleBase; + if (SUCCEEDED(g_ExtSymbols->GetModuleByOffset(UL64_TO_CDA(addrInModule), 0, &Index, &moduleBase))) + { + ArrayHolder<char> szModuleName = new char[MAX_LONGPATH+1]; + + if (SUCCEEDED(g_ExtSymbols->GetModuleNames(Index, moduleBase, NULL, 0, NULL, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL))) + { + MultiByteToWideChar (CP_ACP, 0, szModuleName, MAX_LONGPATH, g_mdName, _countof(g_mdName)); + methodOutput += g_mdName; + methodOutput += W("!"); + } + } + methodOutput += W("<unknown method>"); + } + else + { + methodOutput = W("<unknown>"); + } + + ArrayHolder<WCHAR> wszFileName = new WCHAR[MAX_LONGPATH]; + if (!bSuppressLines && + SUCCEEDED(GetLineByOffset(TO_CDADDR(ip), &linenum, wszFileName, MAX_LONGPATH))) + { + methodOutput += WString(W(" [")) + wszFileName + W(" @ ") + Decimal(linenum) + W("]"); + } + } + + return methodOutput; +} + +HRESULT GetGCRefs(ULONG osID, SOSStackRefData **ppRefs, unsigned int *pRefCnt, SOSStackRefError **ppErrors, unsigned int *pErrCount) +{ + if (ppRefs == NULL || pRefCnt == NULL) + return E_POINTER; + + if (pErrCount) + *pErrCount = 0; + + *pRefCnt = 0; + unsigned int count = 0; + ToRelease<ISOSStackRefEnum> pEnum; + if (FAILED(g_sos->GetStackReferences(osID, &pEnum)) || FAILED(pEnum->GetCount(&count))) + { + ExtOut("Failed to enumerate GC references.\n"); + return E_FAIL; + } + + *ppRefs = new SOSStackRefData[count]; + if (FAILED(pEnum->Next(count, *ppRefs, pRefCnt))) + { + ExtOut("Failed to enumerate GC references.\n"); + return E_FAIL; + } + + SOS_Assert(count == *pRefCnt); + + // Enumerate errors found. Any bad HRESULT recieved while enumerating errors is NOT a fatal error. + // Hence we return S_FALSE if we encounter one. + + if (ppErrors && pErrCount) + { + ToRelease<ISOSStackRefErrorEnum> pErrors; + if (FAILED(pEnum->EnumerateErrors(&pErrors))) + { + ExtOut("Failed to enumerate GC reference errors.\n"); + return S_FALSE; + } + + if (FAILED(pErrors->GetCount(&count))) + { + ExtOut("Failed to enumerate GC reference errors.\n"); + return S_FALSE; + } + + *ppErrors = new SOSStackRefError[count]; + if (FAILED(pErrors->Next(count, *ppErrors, pErrCount))) + { + ExtOut("Failed to enumerate GC reference errors.\n"); + *pErrCount = 0; + return S_FALSE; + } + + SOS_Assert(count == *pErrCount); + } + return S_OK; +} + + +InternalFrameManager::InternalFrameManager() : m_cInternalFramesActual(0), m_iInternalFrameCur(0) {} + +HRESULT InternalFrameManager::Init(ICorDebugThread3 * pThread3) +{ + _ASSERTE(pThread3 != NULL); + + return pThread3->GetActiveInternalFrames( + _countof(m_rgpInternalFrame2), + &m_cInternalFramesActual, + &(m_rgpInternalFrame2[0])); +} + +HRESULT InternalFrameManager::PrintPrecedingInternalFrames(ICorDebugFrame * pFrame) +{ + HRESULT Status; + + for (; m_iInternalFrameCur < m_cInternalFramesActual; m_iInternalFrameCur++) + { + BOOL bIsCloser = FALSE; + IfFailRet(m_rgpInternalFrame2[m_iInternalFrameCur]->IsCloserToLeaf(pFrame, &bIsCloser)); + + if (!bIsCloser) + { + // Current internal frame is now past pFrame, so we're done + return S_OK; + } + + IfFailRet(PrintCurrentInternalFrame()); + } + + // Exhausted list of internal frames. Done! + return S_OK; +} + +HRESULT InternalFrameManager::PrintCurrentInternalFrame() +{ + _ASSERTE(m_iInternalFrameCur < m_cInternalFramesActual); + + HRESULT Status; + + CORDB_ADDRESS address; + IfFailRet(m_rgpInternalFrame2[m_iInternalFrameCur]->GetAddress(&address)); + + ToRelease<ICorDebugInternalFrame> pInternalFrame; + IfFailRet(m_rgpInternalFrame2[m_iInternalFrameCur]->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID *) &pInternalFrame)); + + CorDebugInternalFrameType type; + IfFailRet(pInternalFrame->GetFrameType(&type)); + + LPCSTR szFrameType = NULL; + switch(type) + { + default: + szFrameType = "Unknown internal frame."; + break; + + case STUBFRAME_M2U: + szFrameType = "Managed to Unmanaged transition"; + break; + + case STUBFRAME_U2M: + szFrameType = "Unmanaged to Managed transition"; + break; + + case STUBFRAME_APPDOMAIN_TRANSITION: + szFrameType = "AppDomain transition"; + break; + + case STUBFRAME_LIGHTWEIGHT_FUNCTION: + szFrameType = "Lightweight function"; + break; + + case STUBFRAME_FUNC_EVAL: + szFrameType = "Function evaluation"; + break; + + case STUBFRAME_INTERNALCALL: + szFrameType = "Internal call"; + break; + + case STUBFRAME_CLASS_INIT: + szFrameType = "Class initialization"; + break; + + case STUBFRAME_EXCEPTION: + szFrameType = "Exception"; + break; + + case STUBFRAME_SECURITY: + szFrameType = "Security"; + break; + + case STUBFRAME_JIT_COMPILATION: + szFrameType = "JIT Compilation"; + break; + } + + DMLOut("%p %s ", SOS_PTR(address), SOS_PTR(0)); + ExtOut("[%s: %p]\n", szFrameType, SOS_PTR(address)); + + return S_OK; +} |