diff options
Diffstat (limited to 'src/debug/daccess/daccess.cpp')
-rw-r--r-- | src/debug/daccess/daccess.cpp | 8673 |
1 files changed, 8673 insertions, 0 deletions
diff --git a/src/debug/daccess/daccess.cpp b/src/debug/daccess/daccess.cpp new file mode 100644 index 0000000000..ba3995b1f7 --- /dev/null +++ b/src/debug/daccess/daccess.cpp @@ -0,0 +1,8673 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// File: daccess.cpp +// + +// +// ClrDataAccess implementation. +// +//***************************************************************************** + +#include "stdafx.h" +#include <clrdata.h> +#include "typestring.h" +#include "holder.h" +#include "debuginfostore.h" +#include "peimagelayout.inl" +#include "datatargetadapter.h" +#include "readonlydatatargetfacade.h" +#include "metadataexports.h" +#include "excep.h" +#include "debugger.h" +#include "dwreport.h" +#include "primitives.h" +#include "dbgutil.h" +#ifdef FEATURE_PAL +#include <dactablerva.h> +#endif + +#include "dwbucketmanager.hpp" + +// To include definiton of IsThrowableThreadAbortException +// #include <exstatecommon.h> + +CRITICAL_SECTION g_dacCritSec; +ClrDataAccess* g_dacImpl; +HINSTANCE g_thisModule; + +extern VOID STDMETHODCALLTYPE TLS_FreeMasterSlotIndex(); + +EXTERN_C BOOL WINAPI +DllMain(HANDLE instance, DWORD reason, LPVOID reserved) +{ + static bool g_procInitialized = false; + + switch(reason) + { + case DLL_PROCESS_ATTACH: + { + if (g_procInitialized) + { +#ifdef FEATURE_PAL + // Double initialization can happen on Unix + // in case of manual load of DAC shared lib and calling DllMain + // not a big deal, we just ignore it. + return TRUE; +#else + return FALSE; +#endif + } + +#ifdef FEATURE_PAL + int err = PAL_InitializeDLL(); + if(err != 0) + { + return FALSE; + } +#endif + InitializeCriticalSection(&g_dacCritSec); + + // Save the module handle. + g_thisModule = (HINSTANCE)instance; + + g_procInitialized = true; + break; + } + + case DLL_PROCESS_DETACH: + // It's possible for this to be called without ATTACH completing (eg. if it failed) + if (g_procInitialized) + { + DeleteCriticalSection(&g_dacCritSec); + } +#ifndef FEATURE_PAL + TLS_FreeMasterSlotIndex(); +#endif + g_procInitialized = false; + break; + } + + return TRUE; +} + +HINSTANCE +GetModuleInst(void) +{ + return g_thisModule; +} + +HRESULT +ConvertUtf8(__in LPCUTF8 utf8, + ULONG32 bufLen, + ULONG32* nameLen, + __out_ecount_part_opt(bufLen, *nameLen) PWSTR buffer) +{ + if (nameLen) + { + *nameLen = WszMultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + if (!*nameLen) + { + return HRESULT_FROM_GetLastError(); + } + } + + if (buffer && bufLen) + { + if (!WszMultiByteToWideChar(CP_UTF8, 0, utf8, -1, buffer, bufLen)) + { + return HRESULT_FROM_GetLastError(); + } + } + + return S_OK; +} + +HRESULT +AllocUtf8(__in_opt LPCWSTR wstr, + ULONG32 srcChars, + __deref_out LPUTF8* utf8) +{ + ULONG32 chars = WszWideCharToMultiByte(CP_UTF8, 0, wstr, srcChars, + NULL, 0, NULL, NULL); + if (!chars) + { + return HRESULT_FROM_GetLastError(); + } + + // Make sure the converted string is always terminated. + if (srcChars != (ULONG32)-1) + { + if (!ClrSafeInt<ULONG32>::addition(chars, 1, chars)) + { + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + } + + char* mem = new (nothrow) char[chars]; + if (!mem) + { + return E_OUTOFMEMORY; + } + + if (!WszWideCharToMultiByte(CP_UTF8, 0, wstr, srcChars, + mem, chars, NULL, NULL)) + { + HRESULT hr = HRESULT_FROM_GetLastError(); + delete [] mem; + return hr; + } + + if (srcChars != (ULONG32)-1) + { + mem[chars - 1] = 0; + } + + *utf8 = mem; + return S_OK; +} + +HRESULT +GetFullClassNameFromMetadata(IMDInternalImport* mdImport, + mdTypeDef classToken, + ULONG32 bufferChars, + __inout_ecount(bufferChars) LPUTF8 buffer) +{ + HRESULT hr; + LPCUTF8 baseName, namespaceName; + + IfFailRet(mdImport->GetNameOfTypeDef(classToken, &baseName, &namespaceName)); + return ns::MakePath(buffer, bufferChars, namespaceName, baseName) ? + S_OK : E_OUTOFMEMORY; +} + +HRESULT +GetFullMethodNameFromMetadata(IMDInternalImport* mdImport, + mdMethodDef methodToken, + ULONG32 bufferChars, + __inout_ecount(bufferChars) LPUTF8 buffer) +{ + HRESULT status; + HRESULT hr; + mdTypeDef classToken; + size_t len; + + if (mdImport->GetParentToken(methodToken, &classToken) == S_OK) + { + if ((status = + GetFullClassNameFromMetadata(mdImport, classToken, + bufferChars, buffer)) != S_OK) + { + return status; + } + + len = strlen(buffer); + buffer += len; + bufferChars -= static_cast<ULONG32>(len) + 1; + + if (!bufferChars) + { + return E_OUTOFMEMORY; + } + + *buffer++ = NAMESPACE_SEPARATOR_CHAR; + } + + LPCUTF8 methodName; + IfFailRet(mdImport->GetNameOfMethodDef(methodToken, &methodName)); +// Review conversion of size_t to ULONG32. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4267) +#endif + len = strlen(methodName); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + if (len >= bufferChars) + { + return E_OUTOFMEMORY; + } + + strcpy_s(buffer, bufferChars, methodName); + return S_OK; +} + +HRESULT +SplitFullName(__in_z __in PCWSTR fullName, + SplitSyntax syntax, + ULONG32 memberDots, + __deref_out_opt LPUTF8* namespaceName, + __deref_out_opt LPUTF8* typeName, + __deref_out_opt LPUTF8* memberName, + __deref_out_opt LPUTF8* params) +{ + HRESULT status; + PCWSTR paramsStart, memberStart, memberEnd, typeStart; + + if (!*fullName) + { + return E_INVALIDARG; + } + + // + // Split off parameters. + // + + paramsStart = wcschr(fullName, W('(')); + if (paramsStart) + { + if (syntax != SPLIT_METHOD || + paramsStart == fullName) + { + return E_INVALIDARG; + } + + if ((status = AllocUtf8(paramsStart, (ULONG32)-1, params)) != S_OK) + { + return status; + } + + memberEnd = paramsStart - 1; + } + else + { + *params = NULL; + memberEnd = fullName + (wcslen(fullName) - 1); + } + + if (syntax != SPLIT_TYPE) + { + // + // Split off member name. + // + + memberStart = memberEnd; + + for (;;) + { + while (memberStart >= fullName && + *memberStart != W('.')) + { + memberStart--; + } + + // Some member names (e.g. .ctor and .dtor) have + // dots, so go back to the first dot. + while (memberStart > fullName && + memberStart[-1] == W('.')) + { + memberStart--; + } + + if (memberStart <= fullName) + { + if (memberDots > 0) + { + // Caller expected dots in the + // member name and they weren't found. + status = E_INVALIDARG; + goto DelParams; + } + + break; + } + else if (memberDots == 0) + { + break; + } + + memberStart--; + memberDots--; + } + + memberStart++; + if (memberStart > memberEnd) + { + status = E_INVALIDARG; + goto DelParams; + } + + if ((status = AllocUtf8(memberStart, (ULONG32) + (memberEnd - memberStart) + 1, + memberName)) != S_OK) + { + goto DelParams; + } + } + else + { + *memberName = NULL; + memberStart = memberEnd + 2; + } + + // + // Split off type name. + // + + if (memberStart > fullName) + { + // Must have at least one character for the type + // name. If there was a member name, there must + // also be a separator. + if (memberStart < fullName + 2) + { + status = E_INVALIDARG; + goto DelMember; + } + + typeStart = memberStart - 2; + while (typeStart >= fullName && + *typeStart != W('.')) + { + typeStart--; + } + typeStart++; + + if ((status = AllocUtf8(typeStart, (ULONG32) + (memberStart - typeStart) - 1, + typeName)) != S_OK) + { + goto DelMember; + } + } + else + { + *typeName = NULL; + typeStart = fullName; + } + + // + // Namespace must be the rest. + // + + if (typeStart > fullName) + { + if ((status = AllocUtf8(fullName, (ULONG32) + (typeStart - fullName) - 1, + namespaceName)) != S_OK) + { + goto DelType; + } + } + else + { + *namespaceName = NULL; + } + + return S_OK; + + DelType: + delete [] (*typeName); + DelMember: + delete [] (*memberName); + DelParams: + delete [] (*params); + return status; +} + +int +CompareUtf8(__in LPCUTF8 str1, __in LPCUTF8 str2, __in ULONG32 nameFlags) +{ + if (nameFlags & CLRDATA_BYNAME_CASE_INSENSITIVE) + { + // XXX Microsoft - Convert to Unicode? + return SString::_stricmp(str1, str2); + } + + return strcmp(str1, str2); +} + +//---------------------------------------------------------------------------- +// +// MetaEnum. +// +//---------------------------------------------------------------------------- + +HRESULT +MetaEnum::Start(IMDInternalImport* mdImport, ULONG32 kind, + mdToken container) +{ + HRESULT status; + + switch(kind) + { + case mdtTypeDef: + status = mdImport->EnumTypeDefInit(&m_enum); + break; + case mdtMethodDef: + case mdtFieldDef: + status = mdImport->EnumInit(kind, container, &m_enum); + break; + default: + return E_INVALIDARG; + } + if (status != S_OK) + { + return status; + } + + m_mdImport = mdImport; + m_kind = kind; + + return S_OK; +} + +void +MetaEnum::End(void) +{ + if (!m_mdImport) + { + return; + } + + switch(m_kind) + { + case mdtTypeDef: + m_mdImport->EnumTypeDefClose(&m_enum); + break; + case mdtMethodDef: + case mdtFieldDef: + m_mdImport->EnumClose(&m_enum); + break; + } + + Clear(); +} + +HRESULT +MetaEnum::NextToken(mdToken* token, + __deref_opt_out_opt LPCUTF8* namespaceName, + __deref_opt_out_opt LPCUTF8* name) +{ + HRESULT hr; + if (!m_mdImport) + { + return E_INVALIDARG; + } + + switch(m_kind) + { + case mdtTypeDef: + if (!m_mdImport->EnumTypeDefNext(&m_enum, token)) + { + return S_FALSE; + } + m_lastToken = *token; + if (namespaceName || name) + { + LPCSTR _name, _namespaceName; + + IfFailRet(m_mdImport->GetNameOfTypeDef(*token, &_name, &_namespaceName)); + if (namespaceName) + { + *namespaceName = _namespaceName; + } + if (name) + { + *name = _name; + } + } + return S_OK; + + case mdtMethodDef: + if (!m_mdImport->EnumNext(&m_enum, token)) + { + return S_FALSE; + } + m_lastToken = *token; + if (namespaceName) + { + *namespaceName = NULL; + } + if (name != NULL) + { + IfFailRet(m_mdImport->GetNameOfMethodDef(*token, name)); + } + return S_OK; + + case mdtFieldDef: + if (!m_mdImport->EnumNext(&m_enum, token)) + { + return S_FALSE; + } + m_lastToken = *token; + if (namespaceName) + { + *namespaceName = NULL; + } + if (name != NULL) + { + IfFailRet(m_mdImport->GetNameOfFieldDef(*token, name)); + } + return S_OK; + + default: + return E_INVALIDARG; + } +} + +HRESULT +MetaEnum::NextDomainToken(AppDomain** appDomain, + mdToken* token) +{ + HRESULT status; + + if (m_appDomain) + { + // Use only the caller-provided app domain. + *appDomain = m_appDomain; + return NextToken(token, NULL, NULL); + } + + // + // Splay tokens across all app domains. + // + + for (;;) + { + if (m_lastToken == mdTokenNil) + { + // Need to fetch a token. + if ((status = NextToken(token, NULL, NULL)) != S_OK) + { + return status; + } + + m_domainIter.Init(); + } + + if (m_domainIter.Next()) + { + break; + } + + m_lastToken = mdTokenNil; + } + + *appDomain = m_domainIter.GetDomain(); + *token = m_lastToken; + + return S_OK; +} + +HRESULT +MetaEnum::NextTokenByName(__in_opt LPCUTF8 namespaceName, + __in_opt LPCUTF8 name, + ULONG32 nameFlags, + mdToken* token) +{ + HRESULT status; + LPCUTF8 tokNamespace, tokName; + + for (;;) + { + if ((status = NextToken(token, &tokNamespace, &tokName)) != S_OK) + { + return status; + } + + if (namespaceName && + (!tokNamespace || + CompareUtf8(namespaceName, tokNamespace, nameFlags) != 0)) + { + continue; + } + if (name && + (!tokName || + CompareUtf8(name, tokName, nameFlags) != 0)) + { + continue; + } + + return S_OK; + } +} + +HRESULT +MetaEnum::NextDomainTokenByName(__in_opt LPCUTF8 namespaceName, + __in_opt LPCUTF8 name, + ULONG32 nameFlags, + AppDomain** appDomain, mdToken* token) +{ + HRESULT status; + + if (m_appDomain) + { + // Use only the caller-provided app domain. + *appDomain = m_appDomain; + return NextTokenByName(namespaceName, name, nameFlags, token); + } + + // + // Splay tokens across all app domains. + // + + for (;;) + { + if (m_lastToken == mdTokenNil) + { + // Need to fetch a token. + if ((status = NextTokenByName(namespaceName, name, nameFlags, + token)) != S_OK) + { + return status; + } + + m_domainIter.Init(); + } + + if (m_domainIter.Next()) + { + break; + } + + m_lastToken = mdTokenNil; + } + + *appDomain = m_domainIter.GetDomain(); + *token = m_lastToken; + + return S_OK; +} + +HRESULT +MetaEnum::New(Module* mod, + ULONG32 kind, + mdToken container, + IXCLRDataAppDomain* pubAppDomain, + MetaEnum** metaEnumRet, + CLRDATA_ENUM* handle) +{ + HRESULT status; + MetaEnum* metaEnum; + + if (handle) + { + *handle = TO_CDENUM(NULL); + } + + if (!mod->GetFile()->HasMetadata()) + { + return S_FALSE; + } + + metaEnum = new (nothrow) MetaEnum; + if (!metaEnum) + { + return E_OUTOFMEMORY; + } + + if ((status = metaEnum-> + Start(mod->GetMDImport(), kind, container)) != S_OK) + { + delete metaEnum; + return status; + } + + if (pubAppDomain) + { + metaEnum->m_appDomain = + ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain(); + } + + if (metaEnumRet) + { + *metaEnumRet = metaEnum; + } + if (handle) + { + *handle = TO_CDENUM(metaEnum); + } + return S_OK; +} + +//---------------------------------------------------------------------------- +// +// SplitName +// +//---------------------------------------------------------------------------- + +SplitName::SplitName(SplitSyntax syntax, ULONG32 nameFlags, + ULONG32 memberDots) +{ + m_syntax = syntax; + m_nameFlags = nameFlags; + m_memberDots = memberDots; + + Clear(); +} + +void +SplitName::Delete(void) +{ + delete [] m_namespaceName; + m_namespaceName = NULL; + delete [] m_typeName; + m_typeName = NULL; + delete [] m_memberName; + m_memberName = NULL; + delete [] m_params; + m_params = NULL; +} + +void +SplitName::Clear(void) +{ + m_namespaceName = NULL; + m_typeName = NULL; + m_typeToken = mdTypeDefNil; + m_memberName = NULL; + m_memberToken = mdTokenNil; + m_params = NULL; + + m_tlsThread = NULL; + m_metaEnum.m_appDomain = NULL; + m_module = NULL; + m_lastField = NULL; +} + +HRESULT +SplitName::SplitString(__in_opt PCWSTR fullName) +{ + if (m_syntax == SPLIT_NO_NAME) + { + if (fullName) + { + return E_INVALIDARG; + } + + return S_OK; + } + else if (!fullName) + { + return E_INVALIDARG; + } + + return SplitFullName(fullName, + m_syntax, + m_memberDots, + &m_namespaceName, + &m_typeName, + &m_memberName, + &m_params); +} + +FORCEINLINE +WCHAR* wcrscan(LPCWSTR beg, LPCWSTR end, WCHAR ch) +{ + //_ASSERTE(beg <= end); + WCHAR *p; + for (p = (WCHAR*)end; p >= beg; --p) + { + if (*p == ch) + break; + } + return p; +} + +// This functions allocates a new UTF8 string that contains the classname +// lying between the current sepName and the previous sepName. E.g. for a +// class name of "Outer+middler+inner" when sepName points to the NULL +// terminator this function will return "inner" in pResult and will update +// sepName to point to the second '+' character in the string. When sepName +// points to the first '+' character this function will return "Outer" in +// pResult and sepName will point one WCHAR before fullName. +HRESULT NextEnclosingClasName(LPCWSTR fullName, __deref_inout LPWSTR& sepName, __deref_out LPUTF8 *pResult) +{ + if (sepName < fullName) + { + return E_FAIL; + } + //_ASSERTE(*sepName == W('\0') || *sepName == W('+') || *sepName == W('/')); + + LPWSTR origInnerName = sepName-1; + if ((sepName = wcrscan(fullName, origInnerName, W('+'))) < fullName) + { + sepName = wcrscan(fullName, origInnerName, W('/')); + } + + return AllocUtf8(sepName+1, static_cast<ULONG32>(origInnerName-sepName), pResult); +} + +bool +SplitName::FindType(IMDInternalImport* mdInternal) +{ + if (m_typeToken != mdTypeDefNil) + { + return true; + } + + if (!m_typeName) + { + return false; + } + + if ((m_namespaceName == NULL || m_namespaceName[0] == '\0') + && (CompareUtf8(COR_MODULE_CLASS, m_typeName, m_nameFlags)==0)) + { + m_typeToken = TokenFromRid(1, mdtTypeDef); // <Module> class always has a RID of 1. + return true; + } + + MetaEnum metaEnum; + + if (metaEnum.Start(mdInternal, mdtTypeDef, mdTypeDefNil) != S_OK) + { + return false; + } + + LPUTF8 curClassName; + + ULONG32 length; + WCHAR wszName[MAX_CLASS_NAME]; + ConvertUtf8(m_typeName, MAX_CLASS_NAME, &length, wszName); + + WCHAR *pHead; + +Retry: + + pHead = wszName + length; + + if (FAILED(NextEnclosingClasName(wszName, pHead, &curClassName))) + { + return false; + } + + // an inner class has an empty namespace associated with it + HRESULT hr = metaEnum.NextTokenByName((pHead < wszName) ? m_namespaceName : "", + curClassName, + m_nameFlags, + &m_typeToken); + delete[] curClassName; + + if (hr != S_OK) + { + // if we didn't find a token with the given name + return false; + } + else if (pHead < wszName) + { + // if we did find a token, *and* the class name given + // does not specify any enclosing class, that's it + return true; + } + else + { + // restart with innermost class + pHead = wszName + length; + mdTypeDef tkInner = m_typeToken; + mdTypeDef tkOuter; + BOOL bRetry = FALSE; + LPUTF8 utf8Name; + + while ( + !bRetry + && SUCCEEDED(NextEnclosingClasName(wszName, pHead, &utf8Name)) + ) + { + if (mdInternal->GetNestedClassProps(tkInner, &tkOuter) != S_OK) + tkOuter = mdTypeDefNil; + + LPCSTR szName, szNS; + if (FAILED(mdInternal->GetNameOfTypeDef(tkInner, &szName, &szNS))) + { + return false; + } + bRetry = (CompareUtf8(utf8Name, szName, m_nameFlags) != 0); + if (!bRetry) + { + // if this is outermost class we need to compare namespaces too + if (tkOuter == mdTypeDefNil) + { + // is this the outermost in the class name, too? + if (pHead < wszName + && CompareUtf8(m_namespaceName ? m_namespaceName : "", szNS, m_nameFlags) == 0) + { + delete[] utf8Name; + return true; + } + else + { + bRetry = TRUE; + } + } + } + delete[] utf8Name; + tkInner = tkOuter; + } + + goto Retry; + } + +} + +bool +SplitName::FindMethod(IMDInternalImport* mdInternal) +{ + if (m_memberToken != mdTokenNil) + { + return true; + } + + if (m_typeToken == mdTypeDefNil || + !m_memberName) + { + return false; + } + + ULONG32 EmptySig = 0; + + // XXX Microsoft - Compare using signature when available. + if (mdInternal->FindMethodDefUsingCompare(m_typeToken, + m_memberName, + (PCCOR_SIGNATURE)&EmptySig, + sizeof(EmptySig), + NULL, + NULL, + &m_memberToken) != S_OK) + { + m_memberToken = mdTokenNil; + return false; + } + + return true; +} + +bool +SplitName::FindField(IMDInternalImport* mdInternal) +{ + if (m_memberToken != mdTokenNil) + { + return true; + } + + if (m_typeToken == mdTypeDefNil || + !m_memberName || + m_params) + { + // Can't have params with a field. + return false; + } + + MetaEnum metaEnum; + + if (metaEnum.Start(mdInternal, mdtFieldDef, m_typeToken) != S_OK) + { + return false; + } + + return metaEnum.NextTokenByName(NULL, + m_memberName, + m_nameFlags, + &m_memberToken) == S_OK; +} + +HRESULT +SplitName::AllocAndSplitString(__in_opt PCWSTR fullName, + SplitSyntax syntax, + ULONG32 nameFlags, + ULONG32 memberDots, + SplitName** split) +{ + HRESULT status; + + if (nameFlags & ~(CLRDATA_BYNAME_CASE_SENSITIVE | + CLRDATA_BYNAME_CASE_INSENSITIVE)) + { + return E_INVALIDARG; + } + + *split = new (nothrow) SplitName(syntax, nameFlags, memberDots); + if (!*split) + { + return E_OUTOFMEMORY; + } + + if ((status = (*split)->SplitString(fullName)) != S_OK) + { + delete (*split); + return status; + } + + return S_OK; +} + +HRESULT +SplitName::CdStartMethod(__in_opt PCWSTR fullName, + ULONG32 nameFlags, + Module* mod, + mdTypeDef typeToken, + AppDomain* appDomain, + IXCLRDataAppDomain* pubAppDomain, + SplitName** splitRet, + CLRDATA_ENUM* handle) +{ + HRESULT status; + SplitName* split; + ULONG methDots = 0; + + *handle = TO_CDENUM(NULL); + + Retry: + if ((status = SplitName:: + AllocAndSplitString(fullName, SPLIT_METHOD, nameFlags, + methDots, &split)) != S_OK) + { + return status; + } + + if (typeToken == mdTypeDefNil) + { + if (!split->FindType(mod->GetMDImport())) + { + bool hasNamespace = split->m_namespaceName != NULL; + + delete split; + + // + // We may have a case where there's an + // explicitly implemented method which + // has dots in the name. If it's possible + // to move the method name dot split + // back, go ahead and retry that way. + // + + if (hasNamespace) + { + methDots++; + goto Retry; + } + + return E_INVALIDARG; + } + + typeToken = split->m_typeToken; + } + else + { + if (split->m_namespaceName || split->m_typeName) + { + delete split; + return E_INVALIDARG; + } + } + + if ((status = split->m_metaEnum. + Start(mod->GetMDImport(), mdtMethodDef, typeToken)) != S_OK) + { + delete split; + return status; + } + + split->m_metaEnum.m_appDomain = appDomain; + if (pubAppDomain) + { + split->m_metaEnum.m_appDomain = + ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain(); + } + split->m_module = mod; + + *handle = TO_CDENUM(split); + if (splitRet) + { + *splitRet = split; + } + return S_OK; +} + +HRESULT +SplitName::CdNextMethod(CLRDATA_ENUM* handle, + mdMethodDef* token) +{ + SplitName* split = FROM_CDENUM(SplitName, *handle); + if (!split) + { + return E_INVALIDARG; + } + + return split->m_metaEnum. + NextTokenByName(NULL, split->m_memberName, split->m_nameFlags, + token); +} + +HRESULT +SplitName::CdNextDomainMethod(CLRDATA_ENUM* handle, + AppDomain** appDomain, + mdMethodDef* token) +{ + SplitName* split = FROM_CDENUM(SplitName, *handle); + if (!split) + { + return E_INVALIDARG; + } + + return split->m_metaEnum. + NextDomainTokenByName(NULL, split->m_memberName, split->m_nameFlags, + appDomain, token); +} + +HRESULT +SplitName::CdStartField(__in_opt PCWSTR fullName, + ULONG32 nameFlags, + ULONG32 fieldFlags, + IXCLRDataTypeInstance* fromTypeInst, + TypeHandle typeHandle, + Module* mod, + mdTypeDef typeToken, + ULONG64 objBase, + Thread* tlsThread, + IXCLRDataTask* pubTlsThread, + AppDomain* appDomain, + IXCLRDataAppDomain* pubAppDomain, + SplitName** splitRet, + CLRDATA_ENUM* handle) +{ + HRESULT status; + SplitName* split; + + *handle = TO_CDENUM(NULL); + + if ((status = SplitName:: + AllocAndSplitString(fullName, + fullName ? SPLIT_FIELD : SPLIT_NO_NAME, + nameFlags, 0, + &split)) != S_OK) + { + return status; + } + + if (typeHandle.IsNull()) + { + if (typeToken == mdTypeDefNil) + { + if (!split->FindType(mod->GetMDImport())) + { + status = E_INVALIDARG; + goto Fail; + } + + typeToken = split->m_typeToken; + } + else + { + if (split->m_namespaceName || split->m_typeName) + { + status = E_INVALIDARG; + goto Fail; + } + } + + // With phased class loading, this may return a partially-loaded type + // @todo : does this matter? + typeHandle = mod->LookupTypeDef(split->m_typeToken); + if (typeHandle.IsNull()) + { + status = E_UNEXPECTED; + goto Fail; + } + } + + if ((status = InitFieldIter(&split->m_fieldEnum, + typeHandle, + true, + fieldFlags, + fromTypeInst)) != S_OK) + { + goto Fail; + } + + split->m_objBase = objBase; + split->m_tlsThread = tlsThread; + if (pubTlsThread) + { + split->m_tlsThread = ((ClrDataTask*)pubTlsThread)->GetThread(); + } + split->m_metaEnum.m_appDomain = appDomain; + if (pubAppDomain) + { + split->m_metaEnum.m_appDomain = + ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain(); + } + split->m_module = mod; + + *handle = TO_CDENUM(split); + if (splitRet) + { + *splitRet = split; + } + return S_OK; + + Fail: + delete split; + return status; +} + +HRESULT +SplitName::CdNextField(ClrDataAccess* dac, + CLRDATA_ENUM* handle, + IXCLRDataTypeDefinition** fieldType, + ULONG32* fieldFlags, + IXCLRDataValue** value, + ULONG32 nameBufRetLen, + ULONG32* nameLenRet, + __out_ecount_part_opt(nameBufRetLen, *nameLenRet) WCHAR nameBufRet[ ], + IXCLRDataModule** tokenScopeRet, + mdFieldDef* tokenRet) +{ + HRESULT status; + + SplitName* split = FROM_CDENUM(SplitName, *handle); + if (!split) + { + return E_INVALIDARG; + } + + FieldDesc* fieldDesc; + + while ((fieldDesc = split->m_fieldEnum.Next())) + { + if (split->m_syntax != SPLIT_NO_NAME) + { + LPCUTF8 fieldName; + if (FAILED(fieldDesc->GetName_NoThrow(&fieldName)) || + (split->Compare(split->m_memberName, fieldName) != 0)) + { + continue; + } + } + + split->m_lastField = fieldDesc; + + if (fieldFlags != NULL) + { + *fieldFlags = + GetTypeFieldValueFlags(fieldDesc->GetFieldTypeHandleThrowing(), + fieldDesc, + split->m_fieldEnum. + IsFieldFromParentClass() ? + CLRDATA_FIELD_IS_INHERITED : 0, + false); + } + + if ((nameBufRetLen != 0) || (nameLenRet != NULL)) + { + LPCUTF8 szFieldName; + status = fieldDesc->GetName_NoThrow(&szFieldName); + if (status != S_OK) + { + return status; + } + + status = ConvertUtf8( + szFieldName, + nameBufRetLen, + nameLenRet, + nameBufRet); + if (status != S_OK) + { + return status; + } + } + + if (tokenScopeRet && !value) + { + *tokenScopeRet = new (nothrow) + ClrDataModule(dac, fieldDesc->GetModule()); + if (!*tokenScopeRet) + { + return E_OUTOFMEMORY; + } + } + + if (tokenRet) + { + *tokenRet = fieldDesc->GetMemberDef(); + } + + if (fieldType) + { + TypeHandle fieldTypeHandle = fieldDesc->GetFieldTypeHandleThrowing(); + *fieldType = new (nothrow) + ClrDataTypeDefinition(dac, + fieldTypeHandle.GetModule(), + fieldTypeHandle.GetMethodTable()->GetCl(), + fieldTypeHandle); + if (!*fieldType && tokenScopeRet) + { + delete (ClrDataModule*)*tokenScopeRet; + } + return *fieldType ? S_OK : E_OUTOFMEMORY; + } + + if (value) + { + return ClrDataValue:: + NewFromFieldDesc(dac, + split->m_metaEnum.m_appDomain, + split->m_fieldEnum.IsFieldFromParentClass() ? + CLRDATA_VALUE_IS_INHERITED : 0, + fieldDesc, + split->m_objBase, + split->m_tlsThread, + NULL, + value, + nameBufRetLen, + nameLenRet, + nameBufRet, + tokenScopeRet, + tokenRet); + } + + return S_OK; + } + + return S_FALSE; +} + +HRESULT +SplitName::CdNextDomainField(ClrDataAccess* dac, + CLRDATA_ENUM* handle, + IXCLRDataValue** value) +{ + HRESULT status; + + SplitName* split = FROM_CDENUM(SplitName, *handle); + if (!split) + { + return E_INVALIDARG; + } + + if (split->m_metaEnum.m_appDomain) + { + // Use only the caller-provided app domain. + return CdNextField(dac, handle, NULL, NULL, value, + 0, NULL, NULL, NULL, NULL); + } + + // + // Splay fields across all app domains. + // + + for (;;) + { + if (!split->m_lastField) + { + // Need to fetch a field. + if ((status = CdNextField(dac, handle, NULL, NULL, NULL, + 0, NULL, NULL, NULL, NULL)) != S_OK) + { + return status; + } + + split->m_metaEnum.m_domainIter.Init(); + } + + if (split->m_metaEnum.m_domainIter.Next()) + { + break; + } + + split->m_lastField = NULL; + } + + return ClrDataValue:: + NewFromFieldDesc(dac, + split->m_metaEnum.m_domainIter.GetDomain(), + split->m_fieldEnum.IsFieldFromParentClass() ? + CLRDATA_VALUE_IS_INHERITED : 0, + split->m_lastField, + split->m_objBase, + split->m_tlsThread, + NULL, + value, + 0, + NULL, + NULL, + NULL, + NULL); +} + +HRESULT +SplitName::CdStartType(__in_opt PCWSTR fullName, + ULONG32 nameFlags, + Module* mod, + AppDomain* appDomain, + IXCLRDataAppDomain* pubAppDomain, + SplitName** splitRet, + CLRDATA_ENUM* handle) +{ + HRESULT status; + SplitName* split; + + *handle = TO_CDENUM(NULL); + + if ((status = SplitName:: + AllocAndSplitString(fullName, SPLIT_TYPE, nameFlags, 0, + &split)) != S_OK) + { + return status; + } + + if ((status = split->m_metaEnum. + Start(mod->GetMDImport(), mdtTypeDef, mdTokenNil)) != S_OK) + { + delete split; + return status; + } + + split->m_metaEnum.m_appDomain = appDomain; + if (pubAppDomain) + { + split->m_metaEnum.m_appDomain = + ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain(); + } + split->m_module = mod; + + *handle = TO_CDENUM(split); + if (splitRet) + { + *splitRet = split; + } + return S_OK; +} + +HRESULT +SplitName::CdNextType(CLRDATA_ENUM* handle, + mdTypeDef* token) +{ + SplitName* split = FROM_CDENUM(SplitName, *handle); + if (!split) + { + return E_INVALIDARG; + } + + return split->m_metaEnum. + NextTokenByName(split->m_namespaceName, split->m_typeName, + split->m_nameFlags, token); +} + +HRESULT +SplitName::CdNextDomainType(CLRDATA_ENUM* handle, + AppDomain** appDomain, + mdTypeDef* token) +{ + SplitName* split = FROM_CDENUM(SplitName, *handle); + if (!split) + { + return E_INVALIDARG; + } + + return split->m_metaEnum. + NextDomainTokenByName(split->m_namespaceName, split->m_typeName, + split->m_nameFlags, appDomain, token); +} + +//---------------------------------------------------------------------------- +// +// DacInstanceManager. +// +// Data retrieved from the target process is cached for two reasons: +// +// 1. It may be necessary to map from the host address back to the target +// address. For example, if any code uses a 'this' pointer or +// takes the address of a field the address has to be translated from +// host to target. This requires instances to be held as long as +// they may be referenced. +// +// 2. Data is often referenced multiple times so caching is an important +// performance advantage. +// +// Ideally we'd like to implement a simple page cache but this is +// complicated by the fact that user minidump memory can have +// arbitrary granularity and also that the member operator (->) +// needs to return a pointer to an object. That means that all of +// the data for an object must be sequential and cannot be split +// at page boundaries. +// +// Data can also be accessed with different sizes. For example, +// a base struct can be accessed, then cast to a derived struct and +// accessed again with the larger derived size. The cache must +// be able to replace data to maintain the largest amount of data +// touched. +// +// We keep track of each access and the recovered memory for it. +// A hash on target address allows quick access to instance data +// by target address. The data for each access has a header on it +// for bookkeeping purposes, so host address to target address translation +// is just a matter of backing up to the header and pulling the target +// address from it. Keeping each access separately allows easy +// replacement by larger accesses. +// +//---------------------------------------------------------------------------- + +DacInstanceManager::DacInstanceManager(void) + : m_unusedBlock(NULL) +{ + InitEmpty(); +} + +DacInstanceManager::~DacInstanceManager(void) +{ + // We are stopping debugging in this case, so don't save any block of memory. + // Otherwise, there will be a memory leak. + Flush(false); +} + +#if defined(DAC_HASHTABLE) +DAC_INSTANCE* +DacInstanceManager::Add(DAC_INSTANCE* inst) +{ + // Assert that we don't add NULL instances. This allows us to assert that found instances + // are not NULL in DacInstanceManager::Find + _ASSERTE(inst != NULL); + + DWORD nHash = DAC_INSTANCE_HASH(inst->addr); + HashInstanceKeyBlock* block = m_hash[nHash]; + + if (!block || block->firstElement == 0) + { + + HashInstanceKeyBlock* newBlock; + if (block) + { + newBlock = (HashInstanceKeyBlock*) new (nothrow) BYTE[HASH_INSTANCE_BLOCK_ALLOC_SIZE]; + } + else + { + // We allocate one big memory chunk that has a block for every index of the hash table to + // improve data locality and reduce the number of allocs. In most cases, a hash bucket will + // use only one block, so improving data locality across blocks (i.e. keeping the buckets of the + // hash table together) should help. + newBlock = (HashInstanceKeyBlock*) + ClrVirtualAlloc(NULL, HASH_INSTANCE_BLOCK_ALLOC_SIZE*NumItems(m_hash), MEM_COMMIT, PAGE_READWRITE); + } + if (!newBlock) + { + return NULL; + } + if (block) + { + // We add the newest block to the start of the list assuming that most accesses are for + // recently added elements. + newBlock->next = block; + m_hash[nHash] = newBlock; // The previously allocated block + newBlock->firstElement = HASH_INSTANCE_BLOCK_NUM_ELEMENTS; + block = newBlock; + } + else + { + for (DWORD j = 0; j < NumItems(m_hash); j++) + { + m_hash[j] = newBlock; + newBlock->next = NULL; // The previously allocated block + newBlock->firstElement = HASH_INSTANCE_BLOCK_NUM_ELEMENTS; + newBlock = (HashInstanceKeyBlock*) (((BYTE*) newBlock) + HASH_INSTANCE_BLOCK_ALLOC_SIZE); + } + block = m_hash[nHash]; + } + } + _ASSERTE(block->firstElement > 0); + block->firstElement--; + block->instanceKeys[block->firstElement].addr = inst->addr; + block->instanceKeys[block->firstElement].instance = inst; + + inst->next = NULL; + return inst; +} +#else //DAC_HASHTABLE +DAC_INSTANCE* +DacInstanceManager::Add(DAC_INSTANCE* inst) +{ + _ASSERTE(inst != NULL); +#ifdef _DEBUG + bool isInserted = (m_hash.find(inst->addr) == m_hash.end()); +#endif //_DEBUG + DAC_INSTANCE *(&target) = m_hash[inst->addr]; + _ASSERTE(!isInserted || target == NULL); + if( target != NULL ) + { + //This is necessary to preserve the semantics of Supersede, however, it + //is more or less dead code. + inst->next = target; + target = inst; + + //verify descending order + _ASSERTE(inst->size >= target->size); + } + else + { + target = inst; + } + + return inst; +} + +#endif // #if defined(DAC_HASHTABLE) + + +DAC_INSTANCE* +DacInstanceManager::Alloc(TADDR addr, ULONG32 size, DAC_USAGE_TYPE usage) +{ + SUPPORTS_DAC_HOST_ONLY; + DAC_INSTANCE_BLOCK* block; + DAC_INSTANCE* inst; + ULONG32 fullSize; + + static_assert_no_msg(sizeof(DAC_INSTANCE_BLOCK) <= DAC_INSTANCE_ALIGN); + static_assert_no_msg((sizeof(DAC_INSTANCE) & (DAC_INSTANCE_ALIGN - 1)) == 0); + + // + // All allocated instances must be kept alive as long + // as anybody may have a host pointer for one of them. + // This means that we cannot delete an arbitrary instance + // unless we are sure no pointers exist, which currently + // is not possible to determine, thus we just hold everything + // until a Flush. This greatly simplifies instance allocation + // as we can then just sweep through large blocks rather + // than having to use a real allocator. The only + // complication is that we need to keep all instance + // data aligned. We have guaranteed that the header will + // preserve alignment of the data following if the header + // is aligned, so as long as we round up all allocations + // to a multiple of the alignment size everything just works. + // + + fullSize = (size + DAC_INSTANCE_ALIGN - 1) & ~(DAC_INSTANCE_ALIGN - 1); + _ASSERTE(fullSize && fullSize <= 0xffffffff - 2 * sizeof(*inst)); + fullSize += sizeof(*inst); + + // + // Check for an existing block with space. + // + + for (block = m_blocks; block; block = block->next) + { + if (fullSize <= block->bytesFree) + { + break; + } + } + + if (!block) + { + // + // No existing block has enough space, so allocate a new + // one if necessary and link it in. We know we're allocating large + // blocks so directly VirtualAlloc. We save one block through a + // flush so that we spend less time allocating/deallocating. + // + + ULONG32 blockSize = fullSize + DAC_INSTANCE_ALIGN; + if (blockSize < DAC_INSTANCE_BLOCK_ALLOCATION) + { + blockSize = DAC_INSTANCE_BLOCK_ALLOCATION; + } + + // If we have a saved block and it's large enough, use it. + block = m_unusedBlock; + if ((block != NULL) && + ((block->bytesUsed + block->bytesFree) >= blockSize)) + { + m_unusedBlock = NULL; + + // Right now, we're locked to DAC_INSTANCE_BLOCK_ALLOCATION but + // that might change in the future if we decide to do something + // else with the size guarantee in code:DacInstanceManager::FreeAllBlocks + blockSize = block->bytesUsed + block->bytesFree; + } + else + { + block = (DAC_INSTANCE_BLOCK*) + ClrVirtualAlloc(NULL, blockSize, MEM_COMMIT, PAGE_READWRITE); + } + + if (!block) + { + return NULL; + } + + // Keep the first aligned unit for the block header. + block->bytesUsed = DAC_INSTANCE_ALIGN; + block->bytesFree = blockSize - DAC_INSTANCE_ALIGN; + + block->next = m_blocks; + m_blocks = block; + + m_blockMemUsage += blockSize; + } + + inst = (DAC_INSTANCE*)((PBYTE)block + block->bytesUsed); + block->bytesUsed += fullSize; + _ASSERTE(block->bytesFree >= fullSize); + block->bytesFree -= fullSize; + + inst->next = NULL; + inst->addr = addr; + inst->size = size; + inst->sig = DAC_INSTANCE_SIG; + inst->usage = usage; + inst->enumMem = 0; + inst->MDEnumed = 0; + + m_numInst++; + m_instMemUsage += fullSize; + return inst; +} + +void +DacInstanceManager::ReturnAlloc(DAC_INSTANCE* inst) +{ + SUPPORTS_DAC_HOST_ONLY; + DAC_INSTANCE_BLOCK* block; + DAC_INSTANCE_BLOCK * pPrevBlock; + ULONG32 fullSize; + + // + // This special routine handles cleanup in + // cases where an instances has been allocated + // but must be returned due to a following error. + // The given instance must be the last instance + // in an existing block. + // + + fullSize = + ((inst->size + DAC_INSTANCE_ALIGN - 1) & ~(DAC_INSTANCE_ALIGN - 1)) + + sizeof(*inst); + + pPrevBlock = NULL; + for (block = m_blocks; block; pPrevBlock = block, block = block->next) + { + if ((PBYTE)inst == (PBYTE)block + (block->bytesUsed - fullSize)) + { + break; + } + } + + if (!block) + { + return; + } + + block->bytesUsed -= fullSize; + block->bytesFree += fullSize; + m_numInst--; + m_instMemUsage -= fullSize; + + // If the block is empty after returning the specified instance, that means this block was newly created + // when this instance was allocated. We have seen cases where we are asked to allocate a + // large chunk of memory only to fail to read the memory from a dump later on, i.e. when both the target + // address and the size are invalid. If we keep the allocation, we'll grow the VM size unnecessarily. + // Thus, release a block if it's empty and if it's not the default size (to avoid thrashing memory). + // See Dev10 Dbug 812112 for more information. + if ((block->bytesUsed == DAC_INSTANCE_ALIGN) && + ((block->bytesFree + block->bytesUsed) != DAC_INSTANCE_BLOCK_ALLOCATION)) + { + // The empty block is at the beginning of the list. + if (pPrevBlock == NULL) + { + m_blocks = block->next; + } + else + { + _ASSERTE(pPrevBlock->next == block); + pPrevBlock->next = block->next; + } + ClrVirtualFree(block, 0, MEM_RELEASE); + } +} + + +#if defined(DAC_HASHTABLE) +DAC_INSTANCE* +DacInstanceManager::Find(TADDR addr) +{ + +#if defined(DAC_MEASURE_PERF) + unsigned _int64 nStart, nEnd; + g_nFindCalls++; + nStart = GetCycleCount(); +#endif // #if defined(DAC_MEASURE_PERF) + + HashInstanceKeyBlock* block = m_hash[DAC_INSTANCE_HASH(addr)]; + +#if defined(DAC_MEASURE_PERF) + nEnd = GetCycleCount(); + g_nFindHashTotalTime += nEnd - nStart; +#endif // #if defined(DAC_MEASURE_PERF) + + while (block) + { + DWORD nIndex = block->firstElement; + for (; nIndex < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; nIndex++) + { + if (block->instanceKeys[nIndex].addr == addr) + { + #if defined(DAC_MEASURE_PERF) + nEnd = GetCycleCount(); + g_nFindHits++; + g_nFindTotalTime += nEnd - nStart; + if (g_nStackWalk) g_nFindStackTotalTime += nEnd - nStart; +#endif // #if defined(DAC_MEASURE_PERF) + + DAC_INSTANCE* inst = block->instanceKeys[nIndex].instance; + + // inst should not be NULL even if the address was superseded. We search + // the entries in the reverse order they were added. So we should have + // found the superseding entry before this one. (Of course, if a NULL instance + // has been added, this assert is meaningless. DacInstanceManager::Add + // asserts that NULL instances aren't added.) + + _ASSERTE(inst != NULL); + + return inst; + } + } + block = block->next; + } + +#if defined(DAC_MEASURE_PERF) + nEnd = GetCycleCount(); + g_nFindFails++; + g_nFindTotalTime += nEnd - nStart; + if (g_nStackWalk) g_nFindStackTotalTime += nEnd - nStart; +#endif // #if defined(DAC_MEASURE_PERF) + + return NULL; +} +#else //DAC_HASHTABLE +DAC_INSTANCE* +DacInstanceManager::Find(TADDR addr) +{ + DacInstanceHashIterator iter = m_hash.find(addr); + if( iter == m_hash.end() ) + { + return NULL; + } + else + { + return iter->second; + } +} +#endif // if defined(DAC_HASHTABLE) + +HRESULT +DacInstanceManager::Write(DAC_INSTANCE* inst, bool throwEx) +{ + HRESULT status; + + if (inst->usage == DAC_VPTR) + { + // Skip over the host-side vtable pointer when + // writing back. + status = DacWriteAll(inst->addr + sizeof(TADDR), + (PBYTE)(inst + 1) + sizeof(PVOID), + inst->size - sizeof(TADDR), + throwEx); + } + else + { + // Write the whole instance back. + status = DacWriteAll(inst->addr, inst + 1, inst->size, throwEx); + } + + return status; +} + +#if defined(DAC_HASHTABLE) +void +DacInstanceManager::Supersede(DAC_INSTANCE* inst) +{ + _ASSERTE(inst != NULL); + + // + // This instance has been superseded by a larger + // one and so must be removed from the hash. However, + // code may be holding the instance pointer so it + // can't just be deleted. Put it on a list for + // later cleanup. + // + + HashInstanceKeyBlock* block = m_hash[DAC_INSTANCE_HASH(inst->addr)]; + while (block) + { + DWORD nIndex = block->firstElement; + for (; nIndex < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; nIndex++) + { + if (block->instanceKeys[nIndex].instance == inst) + { + block->instanceKeys[nIndex].instance = NULL; + break; + } + } + if (nIndex < HASH_INSTANCE_BLOCK_NUM_ELEMENTS) + { + break; + } + block = block->next; + } + + AddSuperseded(inst); +} +#else //DAC_HASHTABLE +void +DacInstanceManager::Supersede(DAC_INSTANCE* inst) +{ + _ASSERTE(inst != NULL); + + // + // This instance has been superseded by a larger + // one and so must be removed from the hash. However, + // code may be holding the instance pointer so it + // can't just be deleted. Put it on a list for + // later cleanup. + // + + DacInstanceHashIterator iter = m_hash.find(inst->addr); + if( iter == m_hash.end() ) + return; + + DAC_INSTANCE** bucket = &(iter->second); + DAC_INSTANCE* cur = *bucket; + DAC_INSTANCE* prev = NULL; + //walk through the chain looking for this particular instance + while (cur) + { + if (cur == inst) + { + if (!prev) + { + *bucket = inst->next; + } + else + { + prev->next = inst->next; + } + break; + } + + prev = cur; + cur = cur->next; + } + + AddSuperseded(inst); +} +#endif // if defined(DAC_HASHTABLE) + +// This is the default Flush() called when the DAC cache is invalidated, +// e.g. when we continue the debuggee process. In this case, we want to +// save one block of memory to avoid thrashing. See the usage of m_unusedBlock +// for more information. +void DacInstanceManager::Flush(void) +{ + Flush(true); +} + +void DacInstanceManager::Flush(bool fSaveBlock) +{ + SUPPORTS_DAC_HOST_ONLY; + + // + // All allocated memory is in the block + // list, so just free the blocks and + // forget all the internal pointers. + // + + for (;;) + { + FreeAllBlocks(fSaveBlock); + + DAC_INSTANCE_PUSH* push = m_instPushed; + if (!push) + { + break; + } + + m_instPushed = push->next; + m_blocks = push->blocks; + delete push; + } + + // If we are not saving any memory blocks, then clear the saved buffer block (if any) as well. + if (!fSaveBlock) + { + if (m_unusedBlock != NULL) + { + ClrVirtualFree(m_unusedBlock, 0, MEM_RELEASE); + m_unusedBlock = NULL; + } + } + +#if defined(DAC_HASHTABLE) + for (int i = NumItems(m_hash) - 1; i >= 0; i--) + { + HashInstanceKeyBlock* block = m_hash[i]; + HashInstanceKeyBlock* next; + while (block) + { + next = block->next; + if (next) + { + delete [] block; + } + else if (i == 0) + { + ClrVirtualFree(block, 0, MEM_RELEASE); + } + block = next; + } + } +#else //DAC_HASHTABLE + m_hash.clear(); +#endif //DAC_HASHTABLE + + InitEmpty(); +} + +#if defined(DAC_HASHTABLE) +void +DacInstanceManager::ClearEnumMemMarker(void) +{ + ULONG i; + DAC_INSTANCE* inst; + + for (i = 0; i < NumItems(m_hash); i++) + { + HashInstanceKeyBlock* block = m_hash[i]; + while (block) + { + DWORD j; + for (j = block->firstElement; j < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; j++) + { + inst = block->instanceKeys[j].instance; + if (inst != NULL) + { + inst->enumMem = 0; + } + } + block = block->next; + } + } + for (inst = m_superseded; inst; inst = inst->next) + { + inst->enumMem = 0; + } +} +#else //DAC_HASHTABLE +void +DacInstanceManager::ClearEnumMemMarker(void) +{ + ULONG i; + DAC_INSTANCE* inst; + + DacInstanceHashIterator end = m_hash.end(); + /* REVISIT_TODO Fri 10/20/2006 + * This might have an issue, since it might miss chained entries off of + * ->next. However, ->next is going away, and for all intents and + * purposes, this never happens. +*/ + for( DacInstanceHashIterator cur = m_hash.begin(); cur != end; ++cur ) + { + cur->second->enumMem = 0; + } + + for (inst = m_superseded; inst; inst = inst->next) + { + inst->enumMem = 0; + } +} +#endif // if defined(DAC_HASHTABLE) + + +#if defined(DAC_HASHTABLE) +// +// +// Iterating through all of the hash entry and report the memory +// instance to minidump +// +// This function returns the total number of bytes that it reported. +// +// +UINT +DacInstanceManager::DumpAllInstances( + ICLRDataEnumMemoryRegionsCallback *pCallBack) // memory report call back +{ + ULONG i; + DAC_INSTANCE* inst; + UINT cbTotal = 0; + +#if defined(DAC_MEASURE_PERF) + FILE* fp = fopen("c:\\dumpLog.txt", "a"); + int total = 0; +#endif // #if defined(DAC_MEASURE_PERF) + + for (i = 0; i < NumItems(m_hash); i++) + { + +#if defined(DAC_MEASURE_PERF) + int numInBucket = 0; +#endif // #if defined(DAC_MEASURE_PERF) + + HashInstanceKeyBlock* block = m_hash[i]; + while (block) + { + DWORD j; + for (j = block->firstElement; j < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; j++) + { + inst = block->instanceKeys[j].instance; + + // Only report those we intended to. + // So far, only metadata is excluded! + // + if (inst && inst->noReport == 0) + { + cbTotal += inst->size; + HRESULT hr = pCallBack->EnumMemoryRegion(TO_CDADDR(inst->addr), inst->size); + if (hr == COR_E_OPERATIONCANCELED) + { + ThrowHR(hr); + } + } + +#if defined(DAC_MEASURE_PERF) + if (inst) + { + numInBucket++; + } +#endif // #if defined(DAC_MEASURE_PERF) + } + block = block->next; + } + + #if defined(DAC_MEASURE_PERF) + fprintf(fp, "%4d: %4d%s", i, numInBucket, (i+1)%5? "; " : "\n"); + total += numInBucket; +#endif // #if defined(DAC_MEASURE_PERF) + + } + +#if defined(DAC_MEASURE_PERF) + fprintf(fp, "\n\nTotal entries: %d\n\n", total); + fclose(fp); +#endif // #if defined(DAC_MEASURE_PERF) + + return cbTotal; + +} +#else //DAC_HASHTABLE +// +// +// Iterating through all of the hash entry and report the memory +// instance to minidump +// +// This function returns the total number of bytes that it reported. +// +// +UINT +DacInstanceManager::DumpAllInstances( + ICLRDataEnumMemoryRegionsCallback *pCallBack) // memory report call back +{ + SUPPORTS_DAC_HOST_ONLY; + + DAC_INSTANCE* inst; + UINT cbTotal = 0; + +#if defined(DAC_MEASURE_PERF) + FILE* fp = fopen("c:\\dumpLog.txt", "a"); +#endif // #if defined(DAC_MEASURE_PERF) + +#if defined(DAC_MEASURE_PERF) + int numInBucket = 0; +#endif // #if defined(DAC_MEASURE_PERF) + + DacInstanceHashIterator end = m_hash.end(); + for (DacInstanceHashIterator cur = m_hash.begin(); end != cur; ++cur) + { + inst = cur->second; + + // Only report those we intended to. + // So far, only metadata is excluded! + // + if (inst->noReport == 0) + { + cbTotal += inst->size; + HRESULT hr = pCallBack->EnumMemoryRegion(TO_CDADDR(inst->addr), inst->size); + if (hr == COR_E_OPERATIONCANCELED) + { + ThrowHR(hr); + } + } + +#if defined(DAC_MEASURE_PERF) + numInBucket++; +#endif // #if defined(DAC_MEASURE_PERF) + } + +#if defined(DAC_MEASURE_PERF) + fprintf(fp, "\n\nTotal entries: %d\n\n", numInBucket); + fclose(fp); +#endif // #if defined(DAC_MEASURE_PERF) + + return cbTotal; + +} +#endif // if defined(DAC_HASHTABLE) + +DAC_INSTANCE_BLOCK* +DacInstanceManager::FindInstanceBlock(DAC_INSTANCE* inst) +{ + for (DAC_INSTANCE_BLOCK* block = m_blocks; block; block = block->next) + { + if ((PBYTE)inst >= (PBYTE)block && + (PBYTE)inst < (PBYTE)block + block->bytesUsed) + { + return block; + } + } + + return NULL; +} + +// If fSaveBlock is false, free all blocks of allocated memory. Otherwise, +// free all blocks except the one we save to avoid thrashing memory. +// Callers very frequently flush repeatedly with little memory needed in DAC +// so this avoids wasteful repeated allocations/deallocations. +// There is a very unlikely case that we'll have allocated an extremely large +// block; if this is the only block we will save none since this block will +// remain allocated. +void +DacInstanceManager::FreeAllBlocks(bool fSaveBlock) +{ + DAC_INSTANCE_BLOCK* block; + + while ((block = m_blocks)) + { + m_blocks = block->next; + + // If we haven't saved our single block yet and this block is the default size + // then we will save it instead of freeing it. This avoids saving an unnecessarily large + // memory block. + // Do *NOT* trash the byte counts. code:DacInstanceManager::Alloc + // depends on them being correct when checking to see if a block is large enough. + if (fSaveBlock && + (m_unusedBlock == NULL) && + ((block->bytesFree + block->bytesUsed) == DAC_INSTANCE_BLOCK_ALLOCATION)) + { + // Just to avoid confusion, since we're keeping it around. + block->next = NULL; + m_unusedBlock = block; + } + else + { + ClrVirtualFree(block, 0, MEM_RELEASE); + } + } +} + +//---------------------------------------------------------------------------- +// +// DacStreamManager. +// +//---------------------------------------------------------------------------- + +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + +namespace serialization { namespace bin { + + //======================================================================== + // Support functions for binary serialization of simple types to a buffer: + // - raw_size() returns the size in bytes of the binary representation + // of a value. + // - raw_serialize() copies the binary representation of a value into a + // buffer. + // - raw_deserialize() generates a value from its binary representation + // in a buffer. + // Beyond simple types the APIs below support SString instances. SStrings + // are stored as UTF8 strings. + //======================================================================== + + static const size_t ErrOverflow = (size_t)(-1); + +#ifndef FEATURE_PAL + + // Template class is_blittable + template <typename _Ty, typename Enable = void> + struct is_blittable + : std::false_type + { // determines whether _Ty is blittable + }; + + template <typename _Ty> + struct is_blittable<_Ty, typename std::enable_if<std::is_arithmetic<_Ty>::value>::type> + : std::true_type + { // determines whether _Ty is blittable + }; + + // allow types to declare themselves blittable by including a static bool + // member "is_blittable". + template <typename _Ty> + struct is_blittable<_Ty, typename std::enable_if<_Ty::is_blittable>::type> + : std::true_type + { // determines whether _Ty is blittable + }; + + + //======================================================================== + // serialization::bin::Traits<T> enables binary serialization and + // deserialization of instances of T. + //======================================================================== + + // + // General specialization for non-blittable types - must be overridden + // for each specific non-blittable type. + // + template <typename T, typename Enable = void> + class Traits + { + public: + static FORCEINLINE size_t + raw_size(const T & val) + { + static_assert(false, "Non-blittable types need explicit specializations"); + } + }; + + // + // General type trait supporting serialization/deserialization of blittable + // type arguments (as defined by the is_blittable<> type traits above). + // + template <typename T> + class Traits<T, typename std::enable_if<is_blittable<T>::value>::type> + { +#else // FEATURE_PAL + template <typename T> + class Traits + { +#endif // !FEATURE_PAL + public: + // + // raw_size() returns the size in bytes of the binary representation of a + // value. + // + static FORCEINLINE size_t + raw_size(const T & val) + { + return sizeof(T); + } + + // + // raw_serialize() copies the binary representation of a value into a + // "dest" buffer that has "destSize" bytes available. + // Returns raw_size(val), or ErrOverflow if the buffer does not have + // enough space to accommodate "val". + // + static FORCEINLINE size_t + raw_serialize(BYTE* dest, size_t destSize, const T & val) + { + size_t cnt = raw_size(val); + + if (destSize < cnt) + { + return ErrOverflow; + } + + memcpy_s(dest, destSize, &val, cnt); + + return cnt; + } + + // + // raw_deserialize() generates a value "val" from its binary + // representation in a buffer "src". + // Returns raw_size(val), or ErrOverflow if the buffer does not have + // enough space to accommodate "val". + // + static FORCEINLINE size_t + raw_deserialize(T & val, const BYTE* src, size_t srcSize) + { + size_t cnt = raw_size(*(T*)src); + + if (srcSize < cnt) + { + return ErrOverflow; + } + + memcpy_s(&val, cnt, src, cnt); + + return cnt; + } + + }; + + // + // Specialization for UTF8 strings + // + template<> + class Traits<LPCUTF8> + { + public: + static FORCEINLINE size_t + raw_size(const LPCUTF8 & val) + { + return strlen(val) + 1; + } + + static FORCEINLINE size_t + raw_serialize(BYTE* dest, size_t destSize, const LPCUTF8 & val) + { + size_t cnt = raw_size(val); + + if (destSize < cnt) + { + return ErrOverflow; + } + + memcpy_s(dest, destSize, &val, cnt); + + return cnt; + } + + static FORCEINLINE size_t + raw_deserialize(LPCUTF8 & val, const BYTE* src, size_t srcSize) + { + size_t cnt = strnlen((LPCUTF8)src, srcSize) + 1; + + // assert we found a NULL terminated string at "src" + if (srcSize < cnt) + { + return ErrOverflow; + } + + // we won't allocate another buffer for this string + val = (LPCUTF8)src; + + return cnt; + } + + }; + + // + // Specialization for SString. + // SString serialization/deserialization is performed to/from a UTF8 + // string. + // + template<> + class Traits<SString> + { + public: + static FORCEINLINE size_t + raw_size(const SString & val) + { + StackSString s; + val.ConvertToUTF8(s); + // make sure to include the NULL terminator + return s.GetCount() + 1; + } + + static FORCEINLINE size_t + raw_serialize(BYTE* dest, size_t destSize, const SString & val) + { + // instead of calling raw_size() we inline it here, so we can reuse + // the UTF8 string obtained below as an argument to memcpy. + + StackSString s; + val.ConvertToUTF8(s); + // make sure to include the NULL terminator + size_t cnt = s.GetCount() + 1; + + if (destSize < cnt) + { + return ErrOverflow; + } + + memcpy_s(dest, destSize, s.GetUTF8NoConvert(), cnt); + + return cnt; + } + + static FORCEINLINE size_t + raw_deserialize(SString & val, const BYTE* src, size_t srcSize) + { + size_t cnt = strnlen((LPCUTF8)src, srcSize) + 1; + + // assert we found a NULL terminated string at "src" + if (srcSize < cnt) + { + return ErrOverflow; + } + + // a literal SString avoids a new allocation + copy + SString sUtf8(SString::Utf8Literal, (LPCUTF8) src); + sUtf8.ConvertToUnicode(val); + + return cnt; + } + + }; + +#ifndef FEATURE_PAL + // + // Specialization for SString-derived classes (like SStrings) + // + template<typename T> + class Traits<T, typename std::enable_if<std::is_base_of<SString, T>::value>::type> + : public Traits<SString> + { + }; +#endif // !FEATURE_PAL + + // + // Convenience functions to allow argument type deduction + // + template <typename T> FORCEINLINE + size_t raw_size(const T & val) + { return Traits<T>::raw_size(val); } + + template <typename T> FORCEINLINE + size_t raw_serialize(BYTE* dest, size_t destSize, const T & val) + { return Traits<T>::raw_serialize(dest, destSize, val); } + + template <typename T> FORCEINLINE + size_t raw_deserialize(T & val, const BYTE* src, size_t srcSize) + { return Traits<T>::raw_deserialize(val, src, srcSize); } + + + enum StreamBuffState + { + sbsOK, + sbsUnrecoverable, + sbsOOM = sbsUnrecoverable, + }; + + // + // OStreamBuff - Manages writing to an output buffer + // + class OStreamBuff + { + public: + OStreamBuff(BYTE * _buff, size_t _buffsize) + : buffsize(_buffsize) + , buff(_buff) + , crt(0) + , sbs(sbsOK) + { } + + template <typename T> + OStreamBuff& operator << (const T & val) + { + if (sbs >= sbsUnrecoverable) + return *this; + + size_t cnt = raw_serialize(buff+crt, buffsize-crt, val); + if (cnt == ErrOverflow) + { + sbs = sbsOOM; + } + else + { + crt += cnt; + } + + return *this; + } + + inline size_t GetPos() const + { + return crt; + } + + inline BOOL operator!() const + { + return sbs >= sbsUnrecoverable; + } + + inline StreamBuffState State() const + { + return sbs; + } + + private: + size_t buffsize; // size of buffer + BYTE* buff; // buffer to stream to + size_t crt; // current offset in buffer + StreamBuffState sbs; // current state + }; + + + // + // OStreamBuff - Manages reading from an input buffer + // + class IStreamBuff + { + public: + IStreamBuff(const BYTE* _buff, size_t _buffsize) + : buffsize(_buffsize) + , buff(_buff) + , crt(0) + , sbs(sbsOK) + { } + + template <typename T> + IStreamBuff& operator >> (T & val) + { + if (sbs >= sbsUnrecoverable) + return *this; + + size_t cnt = raw_deserialize(val, buff+crt, buffsize-crt); + if (cnt == ErrOverflow) + { + sbs = sbsOOM; + } + else + { + crt += cnt; + } + + return *this; + } + + inline size_t GetPos() const + { + return crt; + } + + inline BOOL operator!() const + { + return sbs >= sbsUnrecoverable; + } + + inline StreamBuffState State() const + { + return sbs; + } + + private: + size_t buffsize; // size of buffer + const BYTE * buff; // buffer to read from + size_t crt; // current offset in buffer + StreamBuffState sbs; // current state + }; + +} } + +using serialization::bin::StreamBuffState; +using serialization::bin::IStreamBuff; +using serialization::bin::OStreamBuff; + + +// Callback function type used by DacStreamManager to coordinate +// amount of available memory between multiple streamable data +// structures (e.g. DacEENamesStreamable) +typedef bool (*Reserve_Fnptr)(DWORD size, void * writeState); + + +// +// DacEENamesStreamable +// Stores EE struct* -> Name mappings and streams them to a +// streambuf when asked +// +class DacEENamesStreamable +{ +private: + // the hash map storing the interesting mappings of EE* -> Names + MapSHash< TADDR, SString, + NoRemoveSHashTraits < + NonDacAwareSHashTraits< MapSHashTraits <TADDR, SString> > + > > m_hash; + + Reserve_Fnptr m_reserveFn; + void *m_writeState; + +private: + // signature value in the header in stream + static const DWORD sig = 0x614e4545; // "EENa" - EE Name + + // header in stream + struct StreamHeader + { + DWORD sig; // 0x614e4545 == "EENa" + DWORD cnt; // count of entries + + static const bool is_blittable = true; + }; + +public: + DacEENamesStreamable() + : m_reserveFn(NULL) + , m_writeState(NULL) + {} + + // Ensures the instance is ready for caching data and later writing + // its map entries to an OStreamBuff. + bool PrepareStreamForWriting(Reserve_Fnptr pfn, void * writeState) + { + _ASSERTE(pfn != NULL && writeState != NULL); + m_reserveFn = pfn; + m_writeState = writeState; + + DWORD size = (DWORD) sizeof(StreamHeader); + + // notify owner to reserve space for a StreamHeader + return m_reserveFn(size, m_writeState); + } + + // Adds a new mapping from an EE struct pointer (e.g. MethodDesc*) to + // its name + bool AddEEName(TADDR taEE, const SString & eeName) + { + _ASSERTE(m_reserveFn != NULL && m_writeState != NULL); + + // as a micro-optimization convert to Utf8 here as both raw_size and + // raw_serialize are optimized for Utf8... + StackSString seeName; + eeName.ConvertToUTF8(seeName); + + DWORD size = (DWORD)(serialization::bin::raw_size(taEE) + + serialization::bin::raw_size(seeName)); + + // notify owner of the amount of space needed in the buffer + if (m_reserveFn(size, m_writeState)) + { + // if there's still space cache the entry in m_hash + m_hash.AddOrReplace(KeyValuePair<TADDR, SString>(taEE, seeName)); + return true; + } + else + { + return false; + } + } + + // Finds an EE name from a target address of an EE struct (e.g. + // MethodDesc*) + bool FindEEName(TADDR taEE, SString & eeName) const + { + return m_hash.Lookup(taEE, &eeName) == TRUE; + } + + void Clear() + { + m_hash.RemoveAll(); + } + + // Writes a header and the hash entries to an OStreamBuff + HRESULT StreamTo(OStreamBuff &out) const + { + StreamHeader hdr; + hdr.sig = sig; + hdr.cnt = (DWORD) m_hash.GetCount(); + + out << hdr; + + auto end = m_hash.End(); + for (auto cur = m_hash.Begin(); end != cur; ++cur) + { + out << cur->Key() << cur->Value(); + if (!out) + return E_FAIL; + } + + return S_OK; + } + + // Reads a header and the hash entries from an IStreamBuff + HRESULT StreamFrom(IStreamBuff &in) + { + StreamHeader hdr; + + DWORD _sig; + in >> hdr; // in >> hdr.sig >> hdr.cnt; + + if (hdr.sig != sig) + return E_FAIL; + + for (size_t i = 0; i < hdr.cnt; ++i) + { + TADDR taEE; + SString eeName; + in >> taEE >> eeName; + + if (!in) + return E_FAIL; + + m_hash.AddOrReplace(KeyValuePair<TADDR, SString>(taEE, eeName)); + } + + return S_OK; + } + +}; + +//================================================================================ +// This class enables two scenarios: +// 1. When debugging a triage/mini-dump the class is initialized with a valid +// buffer in taMiniMetaDataBuff. Afterwards one can call MdCacheGetEEName to +// retrieve the name associated with a MethodDesc*. +// 2. When generating a dump one must follow this sequence: +// a. Initialize the DacStreamManager passing a valid (if the current +// debugging target is a triage/mini-dump) or empty buffer (if the +// current target is a live processa full or a heap dump) +// b. Call PrepareStreamsForWriting() before starting enumerating any memory +// c. Call MdCacheAddEEName() anytime we enumerate an EE structure of interest +// d. Call EnumStreams() as the last action in the memory enumeration method. +// +class DacStreamManager +{ +public: + enum eReadOrWrite + { + eNone, // the stream doesn't exist (target is a live process/full/heap dump) + eRO, // the stream exists and we've read it (target is triage/mini-dump) + eWO, // the stream doesn't exist but we're creating it + // (e.g. to save a minidump from the current debugging session) + eRW // the stream exists but we're generating another triage/mini-dump + }; + + static const DWORD sig = 0x6d727473; // 'strm' + + struct StreamsHeader + { + DWORD dwSig; // 0x6d727473 == "strm" + DWORD dwTotalSize; // total size in bytes + DWORD dwCntStreams; // number of streams (currently 1) + + static const bool is_blittable = true; + }; + + DacStreamManager(TADDR miniMetaDataBuffAddress, DWORD miniMetaDataBuffSizeMax) + : m_MiniMetaDataBuffAddress(miniMetaDataBuffAddress) + , m_MiniMetaDataBuffSizeMax(miniMetaDataBuffSizeMax) + , m_rawBuffer(NULL) + , m_cbAvailBuff(0) + , m_rw(eNone) + , m_bStreamsRead(FALSE) + , m_EENames() + { + Initialize(); + } + + ~DacStreamManager() + { + if (m_rawBuffer != NULL) + { + delete [] m_rawBuffer; + } + } + + bool PrepareStreamsForWriting() + { + if (m_rw == eNone) + m_rw = eWO; + else if (m_rw == eRO) + m_rw = eRW; + else if (m_rw == eRW) + /* nothing */; + else // m_rw == eWO + { + // this is a second invocation from a possibly live process + // clean up the map since the callstacks/exceptions may be different + m_EENames.Clear(); + } + + // update available count based on the header and footer sizes + if (m_MiniMetaDataBuffSizeMax < sizeof(StreamsHeader)) + return false; + + m_cbAvailBuff = m_MiniMetaDataBuffSizeMax - sizeof(StreamsHeader); + + // update available count based on each stream's initial needs + if (!m_EENames.PrepareStreamForWriting(&ReserveInBuffer, this)) + return false; + + return true; + } + + bool MdCacheAddEEName(TADDR taEEStruct, const SString& name) + { + // don't cache unless we enabled "W"riting from a target that does not + // already have a stream yet + if (m_rw != eWO) + return false; + + m_EENames.AddEEName(taEEStruct, name); + return true; + } + + HRESULT EnumStreams(IN CLRDataEnumMemoryFlags flags) + { + _ASSERTE(flags == CLRDATA_ENUM_MEM_MINI || flags == CLRDATA_ENUM_MEM_TRIAGE); + _ASSERTE(m_rw == eWO || m_rw == eRW); + + DWORD cbWritten = 0; + + if (m_rw == eWO) + { + // only dump the stream is it wasn't already present in the target + DumpAllStreams(&cbWritten); + } + else + { + cbWritten = m_MiniMetaDataBuffSizeMax; + } + + DacEnumMemoryRegion(m_MiniMetaDataBuffAddress, cbWritten, false); + DacUpdateMemoryRegion(m_MiniMetaDataBuffAddress, cbWritten, m_rawBuffer); + + return S_OK; + } + + bool MdCacheGetEEName(TADDR taEEStruct, SString & eeName) + { + if (!m_bStreamsRead) + { + ReadAllStreams(); + } + + if (m_rw == eNone || m_rw == eWO) + { + return false; + } + + return m_EENames.FindEEName(taEEStruct, eeName); + } + +private: + HRESULT Initialize() + { + _ASSERTE(m_rw == eNone); + _ASSERTE(m_rawBuffer == NULL); + + HRESULT hr = S_OK; + + StreamsHeader hdr; + DacReadAll(dac_cast<TADDR>(m_MiniMetaDataBuffAddress), + &hdr, sizeof(hdr), true); + + // when the DAC looks at a triage dump or minidump generated using + // a "minimetadata" enabled DAC, buff will point to a serialized + // representation of a methoddesc->method name hashmap. + if (hdr.dwSig == sig) + { + m_rw = eRO; + m_MiniMetaDataBuffSizeMax = hdr.dwTotalSize; + hr = S_OK; + } + else + // when the DAC initializes this for the case where the target is + // (a) a live process, or (b) a full dump, buff will point to a + // zero initialized memory region (allocated w/ VirtualAlloc) + if (hdr.dwSig == 0 && hdr.dwTotalSize == 0 && hdr.dwCntStreams == 0) + { + hr = S_OK; + } + // otherwise we may have some memory corruption. treat this as + // a liveprocess/full dump + else + { + hr = S_FALSE; + } + + BYTE * buff = new BYTE[m_MiniMetaDataBuffSizeMax]; + DacReadAll(dac_cast<TADDR>(m_MiniMetaDataBuffAddress), + buff, m_MiniMetaDataBuffSizeMax, true); + + m_rawBuffer = buff; + + return hr; + } + + HRESULT DumpAllStreams(DWORD * pcbWritten) + { + _ASSERTE(m_rw == eWO); + + HRESULT hr = S_OK; + + OStreamBuff out(m_rawBuffer, m_MiniMetaDataBuffSizeMax); + + // write header + StreamsHeader hdr; + hdr.dwSig = sig; + hdr.dwTotalSize = m_MiniMetaDataBuffSizeMax-m_cbAvailBuff; // will update + hdr.dwCntStreams = 1; + + out << hdr; + + // write MethodDesc->Method name map + hr = m_EENames.StreamTo(out); + + // wrap up the buffer whether we ecountered an error or not + size_t cbWritten = out.GetPos(); + cbWritten = ALIGN_UP(cbWritten, sizeof(size_t)); + + // patch the dwTotalSize field blitted at the beginning of the buffer + ((StreamsHeader*)m_rawBuffer)->dwTotalSize = (DWORD) cbWritten; + + if (pcbWritten) + *pcbWritten = (DWORD) cbWritten; + + return hr; + } + + HRESULT ReadAllStreams() + { + _ASSERTE(!m_bStreamsRead); + + if (m_rw == eNone || m_rw == eWO) + { + // no streams to read... + m_bStreamsRead = TRUE; + return S_FALSE; + } + + HRESULT hr = S_OK; + + IStreamBuff in(m_rawBuffer, m_MiniMetaDataBuffSizeMax); + + // read header + StreamsHeader hdr; + in >> hdr; + _ASSERTE(hdr.dwSig == sig); + _ASSERTE(hdr.dwCntStreams == 1); + + // read EE struct pointer -> EE name map + m_EENames.Clear(); + hr = m_EENames.StreamFrom(in); + + m_bStreamsRead = TRUE; + + return hr; + } + + static bool ReserveInBuffer(DWORD size, void * writeState) + { + DacStreamManager * pThis = reinterpret_cast<DacStreamManager*>(writeState); + if (size > pThis->m_cbAvailBuff) + { + return false; + } + else + { + pThis->m_cbAvailBuff -= size; + return true; + } + } + +private: + TADDR m_MiniMetaDataBuffAddress; // TADDR of the buffer + DWORD m_MiniMetaDataBuffSizeMax; // max size of buffer + BYTE * m_rawBuffer; // inproc copy of buffer + DWORD m_cbAvailBuff; // available bytes in buffer + eReadOrWrite m_rw; + BOOL m_bStreamsRead; + DacEENamesStreamable m_EENames; +}; + +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + +//---------------------------------------------------------------------------- +// +// ClrDataAccess. +// +//---------------------------------------------------------------------------- + +LONG ClrDataAccess::s_procInit; + +ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget/*=0*/) +{ + SUPPORTS_DAC_HOST_ONLY; // ctor does no marshalling - don't check with DacCop + + /* + * Stash the various forms of the new ICorDebugDataTarget interface + */ + m_pTarget = pTarget; + m_pTarget->AddRef(); + + HRESULT hr; + + hr = m_pTarget->QueryInterface(__uuidof(ICorDebugMutableDataTarget), + (void**)&m_pMutableTarget); + + if (hr != S_OK) + { + // Create a target which always fails the write requests with CORDBG_E_TARGET_READONLY + m_pMutableTarget = new ReadOnlyDataTargetFacade(); + m_pMutableTarget->AddRef(); + } + + /* + * If we have a legacy target, it means we're providing compatibility for code that used + * the old ICLRDataTarget interfaces. There are still a few things (like metadata location, + * GetImageBase, and VirtualAlloc) that the implementation may use which we haven't superseded + * in ICorDebugDataTarget, so we still need access to the old target interfaces. + * Any functionality that does exist in ICorDebugDataTarget is accessed from that interface + * using the DataTargetAdapter on top of the legacy interface (to unify the calling code). + * Eventually we may expose all functionality we need using ICorDebug (possibly a private + * interface for things like VirtualAlloc), at which point we can stop using the legacy interfaces + * completely (except in the DataTargetAdapter). + */ + m_pLegacyTarget = NULL; + m_pLegacyTarget2 = NULL; + m_pLegacyTarget3 = NULL; + m_legacyMetaDataLocator = NULL; + m_target3 = NULL; + if (pLegacyTarget != NULL) + { + m_pLegacyTarget = pLegacyTarget; + + m_pLegacyTarget->AddRef(); + + m_pLegacyTarget->QueryInterface(__uuidof(ICLRDataTarget2), (void**)&m_pLegacyTarget2); + + m_pLegacyTarget->QueryInterface(__uuidof(ICLRDataTarget3), (void**)&m_pLegacyTarget3); + + if (pLegacyTarget->QueryInterface(__uuidof(ICLRMetadataLocator), + (void**)&m_legacyMetaDataLocator) != S_OK) + { + // The debugger doesn't implement IMetadataLocator. Use + // IXCLRDataTarget3 if that exists. Otherwise we don't need it. + pLegacyTarget->QueryInterface(__uuidof(IXCLRDataTarget3), + (void**)&m_target3); + } + } + + m_globalBase = 0; + m_refs = 1; + m_instanceAge = 0; + m_debugMode = GetEnvironmentVariableA("MSCORDACWKS_DEBUG", NULL, 0) != 0; + + m_enumMemCb = NULL; + m_updateMemCb = NULL; + m_enumMemFlags = (CLRDataEnumMemoryFlags)-1; // invalid + m_jitNotificationTable = NULL; + m_gcNotificationTable = NULL; + +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + m_streams = NULL; +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + + // Target consistency checks are disabled by default. + // See code:ClrDataAccess::SetTargetConsistencyChecks for details. + m_fEnableTargetConsistencyAsserts = false; + +#ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDACEnableAssert)) + { + m_fEnableTargetConsistencyAsserts = true; + } + + // Verification asserts are disabled by default because some debuggers (cdb/windbg) probe likely locations + // for DAC and having this assert pop up all the time can be annoying. We let derived classes enable + // this if they want. It can also be overridden at run-time with COMPlus_DbgDACAssertOnMismatch, + // see ClrDataAccess::VerifyDlls for details. + m_fEnableDllVerificationAsserts = false; +#endif + +} + +ClrDataAccess::~ClrDataAccess(void) +{ + SUPPORTS_DAC_HOST_ONLY; + +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + if (m_streams) + { + delete m_streams; + } +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + + delete [] m_jitNotificationTable; + if (m_pLegacyTarget) + { + m_pLegacyTarget->Release(); + } + if (m_pLegacyTarget2) + { + m_pLegacyTarget2->Release(); + } + if (m_pLegacyTarget3) + { + m_pLegacyTarget3->Release(); + } + if (m_legacyMetaDataLocator) + { + m_legacyMetaDataLocator->Release(); + } + if (m_target3) + { + m_target3->Release(); + } + m_pTarget->Release(); + m_pMutableTarget->Release(); +} + +STDMETHODIMP +ClrDataAccess::QueryInterface(THIS_ + IN REFIID interfaceId, + OUT PVOID* iface) +{ + void* ifaceRet; + + if (IsEqualIID(interfaceId, IID_IUnknown) || + IsEqualIID(interfaceId, __uuidof(IXCLRDataProcess)) || + IsEqualIID(interfaceId, __uuidof(IXCLRDataProcess2))) + { + ifaceRet = static_cast<IXCLRDataProcess2*>(this); + } + else if (IsEqualIID(interfaceId, __uuidof(ICLRDataEnumMemoryRegions))) + { + ifaceRet = static_cast<ICLRDataEnumMemoryRegions*>(this); + } + else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface))) + { + ifaceRet = static_cast<ISOSDacInterface*>(this); + } + else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface2))) + { + ifaceRet = static_cast<ISOSDacInterface2*>(this); + } + else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface3))) + { + ifaceRet = static_cast<ISOSDacInterface3*>(this); + } + else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface4))) + { + ifaceRet = static_cast<ISOSDacInterface4*>(this); + } + else + { + *iface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + *iface = ifaceRet; + return S_OK; +} + +STDMETHODIMP_(ULONG) +ClrDataAccess::AddRef(THIS) +{ + return InterlockedIncrement(&m_refs); +} + +STDMETHODIMP_(ULONG) +ClrDataAccess::Release(THIS) +{ + SUPPORTS_DAC_HOST_ONLY; + LONG newRefs = InterlockedDecrement(&m_refs); + if (newRefs == 0) + { + delete this; + } + return newRefs; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::Flush(void) +{ + SUPPORTS_DAC_HOST_ONLY; + + // + // Free MD import objects. + // + m_mdImports.Flush(); + + // Free instance memory. + m_instances.Flush(); + + // When the host instance cache is flushed we + // update the instance age count so that + // all child objects automatically become + // invalid. This prevents them from using + // any pointers they've kept to host instances + // which are now gone. + m_instanceAge++; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::StartEnumTasks( + /* [out] */ CLRDATA_ENUM* handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + if (ThreadStore::s_pThreadStore) + { + Thread* thread = ThreadStore::GetAllThreadList(NULL, 0, 0); + *handle = TO_CDENUM(thread); + status = *handle ? S_OK : S_FALSE; + } + else + { + status = S_FALSE; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EnumTask( + /* [in, out] */ CLRDATA_ENUM* handle, + /* [out] */ IXCLRDataTask **task) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + if (*handle) + { + Thread* thread = FROM_CDENUM(Thread, *handle); + *task = new (nothrow) ClrDataTask(this, thread); + if (*task) + { + thread = ThreadStore::GetAllThreadList(thread, 0, 0); + *handle = TO_CDENUM(thread); + status = S_OK; + } + else + { + status = E_OUTOFMEMORY; + } + } + else + { + status = S_FALSE; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EndEnumTasks( + /* [in] */ CLRDATA_ENUM handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // Enumerator holds no resources. + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetTaskByOSThreadID( + /* [in] */ ULONG32 osThreadID, + /* [out] */ IXCLRDataTask **task) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = E_INVALIDARG; + Thread* thread = DacGetThread(osThreadID); + if (thread != NULL) + { + *task = new (nothrow) ClrDataTask(this, thread); + status = *task ? S_OK : E_OUTOFMEMORY; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetTaskByUniqueID( + /* [in] */ ULONG64 uniqueID, + /* [out] */ IXCLRDataTask **task) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + Thread* thread = FindClrThreadByTaskId(uniqueID); + if (thread) + { + *task = new (nothrow) ClrDataTask(this, thread); + status = *task ? S_OK : E_OUTOFMEMORY; + } + else + { + status = E_INVALIDARG; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetFlags( + /* [out] */ ULONG32 *flags) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft - GC check. + *flags = CLRDATA_PROCESS_DEFAULT; + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::IsSameObject( + /* [in] */ IXCLRDataProcess* process) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = m_pTarget == ((ClrDataAccess*)process)->m_pTarget ? + S_OK : S_FALSE; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetManagedObject( + /* [out] */ IXCLRDataValue **value) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetDesiredExecutionState( + /* [out] */ ULONG32 *state) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::SetDesiredExecutionState( + /* [in] */ ULONG32 state) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetAddressType( + /* [in] */ CLRDATA_ADDRESS address, + /* [out] */ CLRDataAddressType* type) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // The only thing that constitutes a failure is some + // dac failure while checking things. + status = S_OK; + TADDR taAddr = CLRDATA_ADDRESS_TO_TADDR(address); + if (IsPossibleCodeAddress(taAddr) == S_OK) + { + if (ExecutionManager::IsManagedCode(taAddr)) + { + *type = CLRDATA_ADDRESS_MANAGED_METHOD; + goto Exit; + } + + if (StubManager::IsStub(taAddr)) + { + *type = CLRDATA_ADDRESS_RUNTIME_UNMANAGED_STUB; + goto Exit; + } + } + + *type = CLRDATA_ADDRESS_UNRECOGNIZED; + + Exit: ; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetRuntimeNameByAddress( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ ULONG32 flags, + /* [in] */ ULONG32 bufLen, + /* [out] */ ULONG32 *symbolLen, + /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR symbolBuf[ ], + /* [out] */ CLRDATA_ADDRESS* displacement) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { +#ifdef _TARGET_ARM_ + address &= ~THUMB_CODE; //workaround for windbg passing in addresses with the THUMB mode bit set +#endif + status = RawGetMethodName(address, flags, bufLen, symbolLen, symbolBuf, + displacement); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::StartEnumAppDomains( + /* [out] */ CLRDATA_ENUM* handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + AppDomainIterator* iter = new (nothrow) AppDomainIterator(FALSE); + if (iter) + { + *handle = TO_CDENUM(iter); + status = S_OK; + } + else + { + status = E_OUTOFMEMORY; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EnumAppDomain( + /* [in, out] */ CLRDATA_ENUM* handle, + /* [out] */ IXCLRDataAppDomain **appDomain) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + AppDomainIterator* iter = FROM_CDENUM(AppDomainIterator, *handle); + if (iter->Next()) + { + *appDomain = new (nothrow) + ClrDataAppDomain(this, iter->GetDomain()); + status = *appDomain ? S_OK : E_OUTOFMEMORY; + } + else + { + status = S_FALSE; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EndEnumAppDomains( + /* [in] */ CLRDATA_ENUM handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + AppDomainIterator* iter = FROM_CDENUM(AppDomainIterator, handle); + delete iter; + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetAppDomainByUniqueID( + /* [in] */ ULONG64 uniqueID, + /* [out] */ IXCLRDataAppDomain **appDomain) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + AppDomainIterator iter(FALSE); + + status = E_INVALIDARG; + while (iter.Next()) + { + if (iter.GetDomain()->GetId().m_dwId == uniqueID) + { + *appDomain = new (nothrow) + ClrDataAppDomain(this, iter.GetDomain()); + status = *appDomain ? S_OK : E_OUTOFMEMORY; + break; + } + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::StartEnumAssemblies( + /* [out] */ CLRDATA_ENUM* handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter* iter = new (nothrow) ProcessModIter; + if (iter) + { + *handle = TO_CDENUM(iter); + status = S_OK; + } + else + { + status = E_OUTOFMEMORY; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EnumAssembly( + /* [in, out] */ CLRDATA_ENUM* handle, + /* [out] */ IXCLRDataAssembly **assembly) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter* iter = FROM_CDENUM(ProcessModIter, *handle); + Assembly* assem; + + if ((assem = iter->NextAssem())) + { + *assembly = new (nothrow) + ClrDataAssembly(this, assem); + status = *assembly ? S_OK : E_OUTOFMEMORY; + } + else + { + status = S_FALSE; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EndEnumAssemblies( + /* [in] */ CLRDATA_ENUM handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter* iter = FROM_CDENUM(ProcessModIter, handle); + delete iter; + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::StartEnumModules( + /* [out] */ CLRDATA_ENUM* handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter* iter = new (nothrow) ProcessModIter; + if (iter) + { + *handle = TO_CDENUM(iter); + status = S_OK; + } + else + { + status = E_OUTOFMEMORY; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EnumModule( + /* [in, out] */ CLRDATA_ENUM* handle, + /* [out] */ IXCLRDataModule **mod) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter* iter = FROM_CDENUM(ProcessModIter, *handle); + Module* curMod; + + if ((curMod = iter->NextModule())) + { + *mod = new (nothrow) + ClrDataModule(this, curMod); + status = *mod ? S_OK : E_OUTOFMEMORY; + } + else + { + status = S_FALSE; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EndEnumModules( + /* [in] */ CLRDATA_ENUM handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter* iter = FROM_CDENUM(ProcessModIter, handle); + delete iter; + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetModuleByAddress( + /* [in] */ CLRDATA_ADDRESS address, + /* [out] */ IXCLRDataModule** mod) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter modIter; + Module* modDef; + + while ((modDef = modIter.NextModule())) + { + TADDR base; + ULONG32 length; + PEFile* file = modDef->GetFile(); + + if ((base = PTR_TO_TADDR(file->GetLoadedImageContents(&length)))) + { + if (TO_CDADDR(base) <= address && + TO_CDADDR(base + length) > address) + { + break; + } + } + if (file->HasNativeImage()) + { + base = PTR_TO_TADDR(file->GetLoadedNative()->GetBase()); + length = file->GetLoadedNative()->GetVirtualSize(); + if (TO_CDADDR(base) <= address && + TO_CDADDR(base + length) > address) + { + break; + } + } + } + + if (modDef) + { + *mod = new (nothrow) + ClrDataModule(this, modDef); + status = *mod ? S_OK : E_OUTOFMEMORY; + } + else + { + status = S_FALSE; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::StartEnumMethodDefinitionsByAddress( + /* [in] */ CLRDATA_ADDRESS address, + /* [out] */ CLRDATA_ENUM *handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + ProcessModIter modIter; + Module* modDef; + + while ((modDef = modIter.NextModule())) + { + TADDR base; + ULONG32 length; + PEFile* file = modDef->GetFile(); + + if ((base = PTR_TO_TADDR(file->GetLoadedImageContents(&length)))) + { + if (TO_CDADDR(base) <= address && + TO_CDADDR(base + length) > address) + { + break; + } + } + } + + status = EnumMethodDefinitions:: + CdStart(modDef, true, address, handle); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EnumMethodDefinitionByAddress( + /* [out][in] */ CLRDATA_ENUM* handle, + /* [out] */ IXCLRDataMethodDefinition **method) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = EnumMethodDefinitions::CdNext(this, handle, method); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EndEnumMethodDefinitionsByAddress( + /* [in] */ CLRDATA_ENUM handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = EnumMethodDefinitions::CdEnd(handle); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::StartEnumMethodInstancesByAddress( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ IXCLRDataAppDomain* appDomain, + /* [out] */ CLRDATA_ENUM *handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + MethodDesc* methodDesc; + + *handle = 0; + status = S_FALSE; + TADDR taddr; + if( (status = TRY_CLRDATA_ADDRESS_TO_TADDR(address, &taddr)) != S_OK ) + { + goto Exit; + } + + if (IsPossibleCodeAddress(taddr) != S_OK) + { + goto Exit; + } + + methodDesc = ExecutionManager::GetCodeMethodDesc(taddr); + if (!methodDesc) + { + goto Exit; + } + + status = EnumMethodInstances::CdStart(methodDesc, appDomain, + handle); + + Exit: ; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EnumMethodInstanceByAddress( + /* [out][in] */ CLRDATA_ENUM* handle, + /* [out] */ IXCLRDataMethodInstance **method) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = EnumMethodInstances::CdNext(this, handle, method); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::EndEnumMethodInstancesByAddress( + /* [in] */ CLRDATA_ENUM handle) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = EnumMethodInstances::CdEnd(handle); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetDataByAddress( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ ULONG32 flags, + /* [in] */ IXCLRDataAppDomain* appDomain, + /* [in] */ IXCLRDataTask* tlsTask, + /* [in] */ ULONG32 bufLen, + /* [out] */ ULONG32 *nameLen, + /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ], + /* [out] */ IXCLRDataValue **value, + /* [out] */ CLRDATA_ADDRESS *displacement) +{ + HRESULT status; + + if (flags != 0) + { + return E_INVALIDARG; + } + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetExceptionStateByExceptionRecord( + /* [in] */ EXCEPTION_RECORD64 *record, + /* [out] */ IXCLRDataExceptionState **exception) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::TranslateExceptionRecordToNotification( + /* [in] */ EXCEPTION_RECORD64 *record, + /* [in] */ IXCLRDataExceptionNotification *notify) +{ + HRESULT status = E_FAIL; + ClrDataModule* pubModule = NULL; + ClrDataMethodInstance* pubMethodInst = NULL; + ClrDataExceptionState* pubExState = NULL; + GcEvtArgs pubGcEvtArgs; + ULONG32 notifyType = 0; + DWORD catcherNativeOffset = 0; + + DAC_ENTER(); + + EX_TRY + { + // + // We cannot hold the dac lock while calling + // out as the external code can do arbitrary things. + // Instead we make a pass over the exception + // information and create all necessary objects. + // We then leave the lock and make the callbac. + // + + TADDR exInfo[EXCEPTION_MAXIMUM_PARAMETERS]; + for (UINT i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++) + { + exInfo[i] = TO_TADDR(record->ExceptionInformation[i]); + } + + notifyType = DACNotify::GetType(exInfo); + switch(notifyType) + { + case DACNotify::MODULE_LOAD_NOTIFICATION: + { + TADDR modulePtr; + + if (DACNotify::ParseModuleLoadNotification(exInfo, modulePtr)) + { + Module* clrModule = PTR_Module(modulePtr); + pubModule = new (nothrow) ClrDataModule(this, clrModule); + if (pubModule == NULL) + { + status = E_OUTOFMEMORY; + } + else + { + status = S_OK; + } + } + break; + } + + case DACNotify::MODULE_UNLOAD_NOTIFICATION: + { + TADDR modulePtr; + + if (DACNotify::ParseModuleUnloadNotification(exInfo, modulePtr)) + { + Module* clrModule = PTR_Module(modulePtr); + pubModule = new (nothrow) ClrDataModule(this, clrModule); + if (pubModule == NULL) + { + status = E_OUTOFMEMORY; + } + else + { + status = S_OK; + } + } + break; + } + + case DACNotify::JIT_NOTIFICATION: + { + TADDR methodDescPtr; + + if (DACNotify::ParseJITNotification(exInfo, methodDescPtr)) + { + // Try and find the right appdomain + MethodDesc* methodDesc = PTR_MethodDesc(methodDescPtr); + BaseDomain* baseDomain = methodDesc->GetDomain(); + AppDomain* appDomain = NULL; + + if (baseDomain->IsAppDomain()) + { + appDomain = PTR_AppDomain(PTR_HOST_TO_TADDR(baseDomain)); + } + else + { + // Find a likely domain, because it's the shared domain. + AppDomainIterator adi(FALSE); + appDomain = adi.GetDomain(); + } + + pubMethodInst = + new (nothrow) ClrDataMethodInstance(this, + appDomain, + methodDesc); + if (pubMethodInst == NULL) + { + status = E_OUTOFMEMORY; + } + else + { + status = S_OK; + } + } + break; + } + + case DACNotify::EXCEPTION_NOTIFICATION: + { + TADDR threadPtr; + + if (DACNotify::ParseExceptionNotification(exInfo, threadPtr)) + { + // Translation can only occur at the time of + // receipt of the notify exception, so we assume + // that the Thread's current exception state + // is the state we want. + status = ClrDataExceptionState:: + NewFromThread(this, + PTR_Thread(threadPtr), + &pubExState, + NULL); + } + break; + } + + case DACNotify::GC_NOTIFICATION: + { + if (DACNotify::ParseGCNotification(exInfo, pubGcEvtArgs)) + { + status = S_OK; + } + break; + } + + case DACNotify::CATCH_ENTER_NOTIFICATION: + { + TADDR methodDescPtr; + if (DACNotify::ParseExceptionCatcherEnterNotification(exInfo, methodDescPtr, catcherNativeOffset)) + { + // Try and find the right appdomain + MethodDesc* methodDesc = PTR_MethodDesc(methodDescPtr); + BaseDomain* baseDomain = methodDesc->GetDomain(); + AppDomain* appDomain = NULL; + + if (baseDomain->IsAppDomain()) + { + appDomain = PTR_AppDomain(PTR_HOST_TO_TADDR(baseDomain)); + } + else + { + // Find a likely domain, because it's the shared domain. + AppDomainIterator adi(FALSE); + appDomain = adi.GetDomain(); + } + + pubMethodInst = + new (nothrow) ClrDataMethodInstance(this, + appDomain, + methodDesc); + if (pubMethodInst == NULL) + { + status = E_OUTOFMEMORY; + } + else + { + status = S_OK; + } + } + break; + } + + default: + status = E_INVALIDARG; + break; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + + if (status == S_OK) + { + IXCLRDataExceptionNotification2* notify2; + + if (notify->QueryInterface(__uuidof(IXCLRDataExceptionNotification2), + (void**)¬ify2) != S_OK) + { + notify2 = NULL; + } + + IXCLRDataExceptionNotification3* notify3; + if (notify->QueryInterface(__uuidof(IXCLRDataExceptionNotification3), + (void**)¬ify3) != S_OK) + { + notify3 = NULL; + } + + IXCLRDataExceptionNotification4* notify4; + if (notify->QueryInterface(__uuidof(IXCLRDataExceptionNotification4), + (void**)¬ify4) != S_OK) + { + notify4 = NULL; + } + + switch(notifyType) + { + case DACNotify::MODULE_LOAD_NOTIFICATION: + notify->OnModuleLoaded(pubModule); + break; + + case DACNotify::MODULE_UNLOAD_NOTIFICATION: + notify->OnModuleUnloaded(pubModule); + break; + + case DACNotify::JIT_NOTIFICATION: + notify->OnCodeGenerated(pubMethodInst); + break; + + case DACNotify::EXCEPTION_NOTIFICATION: + if (notify2) + { + notify2->OnException(pubExState); + } + else + { + status = E_INVALIDARG; + } + break; + + case DACNotify::GC_NOTIFICATION: + if (notify3) + { + notify3->OnGcEvent(pubGcEvtArgs); + } + break; + + case DACNotify::CATCH_ENTER_NOTIFICATION: + if (notify4) + { + notify4->ExceptionCatcherEnter(pubMethodInst, catcherNativeOffset); + } + break; + + default: + // notifyType has already been validated. + _ASSERTE(FALSE); + break; + } + + if (notify2) + { + notify2->Release(); + } + if (notify3) + { + notify3->Release(); + } + } + + if (pubModule) + { + pubModule->Release(); + } + if (pubMethodInst) + { + pubMethodInst->Release(); + } + if (pubExState) + { + pubExState->Release(); + } + + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::CreateMemoryValue( + /* [in] */ IXCLRDataAppDomain* appDomain, + /* [in] */ IXCLRDataTask* tlsTask, + /* [in] */ IXCLRDataTypeInstance* type, + /* [in] */ CLRDATA_ADDRESS addr, + /* [out] */ IXCLRDataValue** value) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + AppDomain* dacDomain; + Thread* dacThread; + TypeHandle dacType; + ULONG32 flags; + NativeVarLocation loc; + + dacDomain = ((ClrDataAppDomain*)appDomain)->GetAppDomain(); + if (tlsTask) + { + dacThread = ((ClrDataTask*)tlsTask)->GetThread(); + } + else + { + dacThread = NULL; + } + dacType = ((ClrDataTypeInstance*)type)->GetTypeHandle(); + + flags = GetTypeFieldValueFlags(dacType, NULL, 0, false); + + loc.addr = addr; + loc.size = dacType.GetSize(); + loc.contextReg = false; + + *value = new (nothrow) + ClrDataValue(this, dacDomain, dacThread, flags, + dacType, addr, 1, &loc); + status = *value ? S_OK : E_OUTOFMEMORY; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::SetAllTypeNotifications( + /* [in] */ IXCLRDataModule* mod, + /* [in] */ ULONG32 flags) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::SetAllCodeNotifications( + /* [in] */ IXCLRDataModule* mod, + /* [in] */ ULONG32 flags) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + status = E_FAIL; + + if (!IsValidMethodCodeNotification(flags)) + { + status = E_INVALIDARG; + } + else + { + JITNotifications jn(GetHostJitNotificationTable()); + if (!jn.IsActive()) + { + status = E_OUTOFMEMORY; + } + else + { + BOOL changedTable; + TADDR modulePtr = mod ? + PTR_HOST_TO_TADDR(((ClrDataModule*)mod)->GetModule()) : + NULL; + + if (jn.SetAllNotifications(modulePtr, flags, &changedTable)) + { + if (!changedTable || + (changedTable && jn.UpdateOutOfProcTable())) + { + status = S_OK; + } + } + } + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetTypeNotifications( + /* [in] */ ULONG32 numTokens, + /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[], + /* [in] */ IXCLRDataModule* singleMod, + /* [in, size_is(numTokens)] */ mdTypeDef tokens[], + /* [out, size_is(numTokens)] */ ULONG32 flags[]) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::SetTypeNotifications( + /* [in] */ ULONG32 numTokens, + /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[], + /* [in] */ IXCLRDataModule* singleMod, + /* [in, size_is(numTokens)] */ mdTypeDef tokens[], + /* [in, size_is(numTokens)] */ ULONG32 flags[], + /* [in] */ ULONG32 singleFlags) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + // XXX Microsoft. + status = E_NOTIMPL; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::GetCodeNotifications( + /* [in] */ ULONG32 numTokens, + /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[], + /* [in] */ IXCLRDataModule* singleMod, + /* [in, size_is(numTokens)] */ mdMethodDef tokens[], + /* [out, size_is(numTokens)] */ ULONG32 flags[]) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + if ((flags == NULL || tokens == NULL) || + (mods == NULL && singleMod == NULL) || + (mods != NULL && singleMod != NULL)) + { + status = E_INVALIDARG; + } + else + { + JITNotifications jn(GetHostJitNotificationTable()); + if (!jn.IsActive()) + { + status = E_OUTOFMEMORY; + } + else + { + TADDR modulePtr = NULL; + if (singleMod) + { + modulePtr = PTR_HOST_TO_TADDR(((ClrDataModule*)singleMod)-> + GetModule()); + } + + for (ULONG32 i = 0; i < numTokens; i++) + { + if (singleMod == NULL) + { + modulePtr = + PTR_HOST_TO_TADDR(((ClrDataModule*)mods[i])-> + GetModule()); + } + USHORT jt = jn.Requested(modulePtr, tokens[i]); + flags[i] = jt; + } + + status = S_OK; + } + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::SetCodeNotifications( + /* [in] */ ULONG32 numTokens, + /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[], + /* [in] */ IXCLRDataModule* singleMod, + /* [in, size_is(numTokens)] */ mdMethodDef tokens[], + /* [in, size_is(numTokens)] */ ULONG32 flags[], + /* [in] */ ULONG32 singleFlags) +{ + HRESULT status = E_UNEXPECTED; + + DAC_ENTER(); + + EX_TRY + { + if ((tokens == NULL) || + (mods == NULL && singleMod == NULL) || + (mods != NULL && singleMod != NULL)) + { + status = E_INVALIDARG; + } + else + { + JITNotifications jn(GetHostJitNotificationTable()); + if (!jn.IsActive() || numTokens > jn.GetTableSize()) + { + status = E_OUTOFMEMORY; + } + else + { + BOOL changedTable = FALSE; + + // Are flags valid? + if (flags) + { + for (ULONG32 check = 0; check < numTokens; check++) + { + if (!IsValidMethodCodeNotification(flags[check])) + { + status = E_INVALIDARG; + goto Exit; + } + } + } + else if (!IsValidMethodCodeNotification(singleFlags)) + { + status = E_INVALIDARG; + goto Exit; + } + + TADDR modulePtr = NULL; + if (singleMod) + { + modulePtr = + PTR_HOST_TO_TADDR(((ClrDataModule*)singleMod)-> + GetModule()); + } + + for (ULONG32 i = 0; i < numTokens; i++) + { + if (singleMod == NULL) + { + modulePtr = + PTR_HOST_TO_TADDR(((ClrDataModule*)mods[i])-> + GetModule()); + } + + USHORT curFlags = jn.Requested(modulePtr, tokens[i]); + USHORT setFlags = (USHORT)(flags ? flags[i] : singleFlags); + + if (curFlags != setFlags) + { + if (!jn.SetNotification(modulePtr, tokens[i], + setFlags)) + { + status = E_FAIL; + goto Exit; + } + + changedTable = TRUE; + } + } + + if (!changedTable || + (changedTable && jn.UpdateOutOfProcTable())) + { + status = S_OK; + } + } + } + +Exit: ; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT +ClrDataAccess::GetOtherNotificationFlags( + /* [out] */ ULONG32* flags) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + *flags = g_dacNotificationFlags; + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +HRESULT +ClrDataAccess::SetOtherNotificationFlags( + /* [in] */ ULONG32 flags) +{ + HRESULT status; + + if ((flags & ~(CLRDATA_NOTIFY_ON_MODULE_LOAD | + CLRDATA_NOTIFY_ON_MODULE_UNLOAD | + CLRDATA_NOTIFY_ON_EXCEPTION | + CLRDATA_NOTIFY_ON_EXCEPTION_CATCH_ENTER)) != 0) + { + return E_INVALIDARG; + } + + DAC_ENTER(); + + EX_TRY + { + g_dacNotificationFlags = flags; + status = S_OK; + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +enum +{ + STUB_BUF_FLAGS_START, + + STUB_BUF_METHOD_JITTED, + STUB_BUF_FRAME_PUSHED, + STUB_BUF_STUB_MANAGER_PUSHED, + + STUB_BUF_FLAGS_END, +}; + +union STUB_BUF +{ + CLRDATA_FOLLOW_STUB_BUFFER apiBuf; + struct + { + ULONG64 flags; + ULONG64 addr; + ULONG64 arg1; + } u; +}; + +HRESULT +ClrDataAccess::FollowStubStep( + /* [in] */ Thread* thread, + /* [in] */ ULONG32 inFlags, + /* [in] */ TADDR inAddr, + /* [in] */ union STUB_BUF* inBuffer, + /* [out] */ TADDR* outAddr, + /* [out] */ union STUB_BUF* outBuffer, + /* [out] */ ULONG32* outFlags) +{ + TraceDestination trace; + bool traceDone = false; + BYTE* retAddr; + T_CONTEXT localContext; + REGDISPLAY regDisp; + MethodDesc* methodDesc; + + ZeroMemory(outBuffer, sizeof(*outBuffer)); + + if (inBuffer) + { + switch(inBuffer->u.flags) + { + case STUB_BUF_METHOD_JITTED: + if (inAddr != GFN_TADDR(DACNotifyCompilationFinished)) + { + return E_INVALIDARG; + } + + // It's possible that this notification is + // for a different method, so double-check + // and recycle the notification if necessary. + methodDesc = PTR_MethodDesc(CORDB_ADDRESS_TO_TADDR(inBuffer->u.addr)); + if (methodDesc->HasNativeCode()) + { + *outAddr = methodDesc->GetNativeCode(); + *outFlags = CLRDATA_FOLLOW_STUB_EXIT; + return S_OK; + } + + // We didn't end up with native code so try again. + trace.InitForUnjittedMethod(methodDesc); + traceDone = true; + break; + + case STUB_BUF_FRAME_PUSHED: + if (!thread || + inAddr != inBuffer->u.addr) + { + return E_INVALIDARG; + } + + trace.InitForFramePush(CORDB_ADDRESS_TO_TADDR(inBuffer->u.addr)); + DacGetThreadContext(thread, &localContext); + thread->FillRegDisplay(®Disp, &localContext); + if (!thread->GetFrame()-> + TraceFrame(thread, + TRUE, + &trace, + ®Disp)) + { + return E_FAIL; + } + + traceDone = true; + break; + + case STUB_BUF_STUB_MANAGER_PUSHED: + if (!thread || + inAddr != inBuffer->u.addr || + !inBuffer->u.arg1) + { + return E_INVALIDARG; + } + + trace.InitForManagerPush(CORDB_ADDRESS_TO_TADDR(inBuffer->u.addr), + PTR_StubManager(CORDB_ADDRESS_TO_TADDR(inBuffer->u.arg1))); + DacGetThreadContext(thread, &localContext); + if (!trace.GetStubManager()-> + TraceManager(thread, + &trace, + &localContext, + &retAddr)) + { + return E_FAIL; + } + + traceDone = true; + break; + + default: + return E_INVALIDARG; + } + } + + if ((!traceDone && + !StubManager::TraceStub(inAddr, &trace)) || + !StubManager::FollowTrace(&trace)) + { + return E_NOINTERFACE; + } + + switch(trace.GetTraceType()) + { + case TRACE_UNMANAGED: + case TRACE_MANAGED: + // We've hit non-stub code so we're done. + *outAddr = trace.GetAddress(); + *outFlags = CLRDATA_FOLLOW_STUB_EXIT; + break; + + case TRACE_UNJITTED_METHOD: + // The stub causes jitting, so return + // the address of the jit-complete routine + // so that the real native address can + // be picked up once the JIT is done. + + // One special case is ngen'ed code that + // needs the prestub run. This results in + // an unjitted trace but no jitting will actually + // occur since the code is ngen'ed. Detect + // this and redirect to the actual code. + methodDesc = trace.GetMethodDesc(); + if (methodDesc->IsPreImplemented() && + !methodDesc->IsPointingToNativeCode() && + !methodDesc->IsGenericMethodDefinition() && + methodDesc->HasNativeCode()) + { + *outAddr = methodDesc->GetNativeCode(); + *outFlags = CLRDATA_FOLLOW_STUB_EXIT; + break; + } + + *outAddr = GFN_TADDR(DACNotifyCompilationFinished); + outBuffer->u.flags = STUB_BUF_METHOD_JITTED; + outBuffer->u.addr = PTR_HOST_TO_TADDR(methodDesc); + *outFlags = CLRDATA_FOLLOW_STUB_INTERMEDIATE; + break; + + case TRACE_FRAME_PUSH: + if (!thread) + { + return E_INVALIDARG; + } + + *outAddr = trace.GetAddress(); + outBuffer->u.flags = STUB_BUF_FRAME_PUSHED; + outBuffer->u.addr = trace.GetAddress(); + *outFlags = CLRDATA_FOLLOW_STUB_INTERMEDIATE; + break; + + case TRACE_MGR_PUSH: + if (!thread) + { + return E_INVALIDARG; + } + + *outAddr = trace.GetAddress(); + outBuffer->u.flags = STUB_BUF_STUB_MANAGER_PUSHED; + outBuffer->u.addr = trace.GetAddress(); + outBuffer->u.arg1 = PTR_HOST_TO_TADDR(trace.GetStubManager()); + *outFlags = CLRDATA_FOLLOW_STUB_INTERMEDIATE; + break; + + default: + return E_INVALIDARG; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::FollowStub( + /* [in] */ ULONG32 inFlags, + /* [in] */ CLRDATA_ADDRESS inAddr, + /* [in] */ CLRDATA_FOLLOW_STUB_BUFFER* _inBuffer, + /* [out] */ CLRDATA_ADDRESS* outAddr, + /* [out] */ CLRDATA_FOLLOW_STUB_BUFFER* _outBuffer, + /* [out] */ ULONG32* outFlags) +{ + return FollowStub2(NULL, inFlags, inAddr, _inBuffer, + outAddr, _outBuffer, outFlags); +} + +HRESULT STDMETHODCALLTYPE +ClrDataAccess::FollowStub2( + /* [in] */ IXCLRDataTask* task, + /* [in] */ ULONG32 inFlags, + /* [in] */ CLRDATA_ADDRESS _inAddr, + /* [in] */ CLRDATA_FOLLOW_STUB_BUFFER* _inBuffer, + /* [out] */ CLRDATA_ADDRESS* _outAddr, + /* [out] */ CLRDATA_FOLLOW_STUB_BUFFER* _outBuffer, + /* [out] */ ULONG32* outFlags) +{ + HRESULT status; + + if ((inFlags & ~(CLRDATA_FOLLOW_STUB_DEFAULT)) != 0) + { + return E_INVALIDARG; + } + + STUB_BUF* inBuffer = (STUB_BUF*)_inBuffer; + STUB_BUF* outBuffer = (STUB_BUF*)_outBuffer; + + if (inBuffer && + (inBuffer->u.flags <= STUB_BUF_FLAGS_START || + inBuffer->u.flags >= STUB_BUF_FLAGS_END)) + { + return E_INVALIDARG; + } + + DAC_ENTER(); + + EX_TRY + { + STUB_BUF cycleBuf; + TADDR inAddr = TO_TADDR(_inAddr); + TADDR outAddr; + Thread* thread = task ? ((ClrDataTask*)task)->GetThread() : NULL; + ULONG32 loops = 4; + + for (;;) + { + if ((status = FollowStubStep(thread, + inFlags, + inAddr, + inBuffer, + &outAddr, + outBuffer, + outFlags)) != S_OK) + { + break; + } + + // Some stub tracing just requests further iterations + // of processing, so detect that case and loop. + if (outAddr != inAddr) + { + // We can make forward progress, we're done. + *_outAddr = TO_CDADDR(outAddr); + break; + } + + // We need more processing. As a protection + // against infinite loops in corrupted or buggy + // situations, we only allow this to happen a + // small number of times. + if (--loops == 0) + { + ZeroMemory(outBuffer, sizeof(*outBuffer)); + status = E_FAIL; + break; + } + + cycleBuf = *outBuffer; + inBuffer = &cycleBuf; + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4297) +#endif // _MSC_VER +STDMETHODIMP +ClrDataAccess::GetGcNotification(GcEvtArgs* gcEvtArgs) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + if (gcEvtArgs->typ >= GC_EVENT_TYPE_MAX) + { + status = E_INVALIDARG; + } + else + { + GcNotifications gn(GetHostGcNotificationTable()); + if (!gn.IsActive()) + { + status = E_OUTOFMEMORY; + } + else + { + GcEvtArgs *res = gn.GetNotification(*gcEvtArgs); + if (res != NULL) + { + *gcEvtArgs = *res; + status = S_OK; + } + else + { + status = E_FAIL; + } + } + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +STDMETHODIMP +ClrDataAccess::SetGcNotification(IN GcEvtArgs gcEvtArgs) +{ + HRESULT status; + + DAC_ENTER(); + + EX_TRY + { + if (gcEvtArgs.typ >= GC_EVENT_TYPE_MAX) + { + status = E_INVALIDARG; + } + else + { + GcNotifications gn(GetHostGcNotificationTable()); + if (!gn.IsActive()) + { + status = E_OUTOFMEMORY; + } + else + { + if (gn.SetNotification(gcEvtArgs) && gn.UpdateOutOfProcTable()) + { + status = S_OK; + } + else + { + status = E_FAIL; + } + } + } + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &status)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + + DAC_LEAVE(); + return status; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +HRESULT +ClrDataAccess::Initialize(void) +{ + HRESULT hr; + CLRDATA_ADDRESS base; + + // + // We do not currently support cross-platform + // debugging. Verify that cross-platform is not + // being attempted. + // + + // Determine our platform based on the pre-processor macros set when we were built + +#ifdef FEATURE_PAL + #if defined(DBG_TARGET_X86) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_X86; + #elif defined(DBG_TARGET_AMD64) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_AMD64; + #elif defined(DBG_TARGET_ARM) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_ARM; + #elif defined(DBG_TARGET_ARM64) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_ARM64; + #else + #error Unknown Processor. + #endif +#else + #if defined(DBG_TARGET_X86) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_X86; + #elif defined(DBG_TARGET_AMD64) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_AMD64; + #elif defined(DBG_TARGET_ARM) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_ARM; + #elif defined(DBG_TARGET_ARM64) + CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_ARM64; + #else + #error Unknown Processor. + #endif +#endif + + CorDebugPlatform targetPlatform; + IfFailRet(m_pTarget->GetPlatform(&targetPlatform)); + + if (targetPlatform != hostPlatform) + { + // DAC fatal error: Platform mismatch - the platform reported by the data target + // is not what this version of mscordacwks.dll was built for. + return CORDBG_E_UNCOMPATIBLE_PLATFORMS; + } + + // + // Get the current DLL base for mscorwks globals. + // In case of multiple-CLRs, there may be multiple dlls named "mscorwks". + // code:OpenVirtualProcess can take the base address (clrInstanceId) to select exactly + // which CLR to is being target. If so, m_globalBase will already be set. + // + + if (m_globalBase == 0) + { + // Caller didn't specify which CLR to debug. This supports Whidbey SOS cases, so we should + // be using a legacy data target. + if (m_pLegacyTarget == NULL) + { + DacError(E_INVALIDARG); + UNREACHABLE(); + } + + // Since this is Whidbey, assume there's only 1 CLR named "mscorwks.dll" and pick that. + IfFailRet(m_pLegacyTarget->GetImageBase(MAIN_CLR_DLL_NAME_W, &base)); + + m_globalBase = TO_TADDR(base); + } + + // We don't need to try too hard to prevent + // multiple initializations as each one will + // copy the same data into the globals and so + // cannot interfere with each other. + if (!s_procInit) + { + IfFailRet(GetDacGlobals()); + IfFailRet(DacGetHostVtPtrs()); + s_procInit = true; + } + + // + // DAC is now setup and ready to use + // + + // Do some validation + IfFailRet(VerifyDlls()); + + // To support EH SxS, utilcode requires the base address of the runtime + // as part of its initialization so that functions like "WasThrownByUs" work correctly since + // they use the CLR base address to check if an exception was raised by a given instance of the runtime + // or not. + // + // Thus, when DAC is initialized, initialize utilcode with the base address of the runtime loaded in the + // target process. This is similar to work done in CorDB::SetTargetCLR for mscordbi. + + // Initialize UtilCode for SxS scenarios + CoreClrCallbacks cccallbacks; + cccallbacks.m_hmodCoreCLR = (HINSTANCE)m_globalBase; // Base address of the runtime in the target process + cccallbacks.m_pfnIEE = NULL; + cccallbacks.m_pfnGetCORSystemDirectory = NULL; + cccallbacks.m_pfnGetCLRFunction = NULL; + InitUtilcode(cccallbacks); + + return S_OK; +} + +Thread* +ClrDataAccess::FindClrThreadByTaskId(ULONG64 taskId) +{ + Thread* thread = NULL; + + if (!ThreadStore::s_pThreadStore) + { + return NULL; + } + + while ((thread = ThreadStore::GetAllThreadList(thread, 0, 0))) + { + if (thread->GetThreadId() == (DWORD)taskId) + { + return thread; + } + } + + return NULL; +} + +HRESULT +ClrDataAccess::IsPossibleCodeAddress(IN TADDR address) +{ + SUPPORTS_DAC; + BYTE testRead; + ULONG32 testDone; + + // First do a trivial check on the readability of the + // address. This makes for quick rejection of bogus + // addresses that the debugger sends in when searching + // stacks for return addresses. + // XXX Microsoft - Will this cause problems in minidumps + // where it's possible the stub is identifiable but + // the stub code isn't present? Yes, but the lack + // of that code could confuse the walker on its own + // if it does code analysis. + if ((m_pTarget->ReadVirtual(address, &testRead, sizeof(testRead), + &testDone) != S_OK) || + !testDone) + { + return E_INVALIDARG; + } + + return S_OK; +} + +HRESULT +ClrDataAccess::GetFullMethodName( + IN MethodDesc* methodDesc, + IN ULONG32 symbolChars, + OUT ULONG32* symbolLen, + __out_ecount_part_opt(symbolChars, *symbolLen) LPWSTR symbol + ) +{ + StackSString s; +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + PAL_CPP_TRY + { +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + + TypeString::AppendMethodInternal(s, methodDesc, TypeString::FormatSignature|TypeString::FormatNamespace|TypeString::FormatFullInst); + +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + } + PAL_CPP_CATCH_ALL + { + if (!MdCacheGetEEName(dac_cast<TADDR>(methodDesc), s)) + { + PAL_CPP_RETHROW; + } + } + PAL_CPP_ENDTRY +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + + if (symbol) + { + // Copy as much as we can and truncate the rest. + wcsncpy_s(symbol, symbolChars, s.GetUnicode(), _TRUNCATE); + } + + if (symbolLen) + *symbolLen = s.GetCount() + 1; + + if (symbol != NULL && symbolChars < (s.GetCount() + 1)) + return S_FALSE; + else + return S_OK; +} + +PCSTR +ClrDataAccess::GetJitHelperName( + IN TADDR address, + IN bool dynamicHelpersOnly /*=false*/ + ) +{ + const static PCSTR s_rgHelperNames[] = { +#define JITHELPER(code,fn,sig) #code, +#include <jithelpers.h> + }; + static_assert_no_msg(COUNTOF(s_rgHelperNames) == CORINFO_HELP_COUNT); + +#ifdef FEATURE_PAL + if (!dynamicHelpersOnly) +#else + if (!dynamicHelpersOnly && g_runtimeLoadedBaseAddress <= address && + address < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize) +#endif // FEATURE_PAL + { + // Read the whole table from the target in one shot for better performance + VMHELPDEF * pTable = static_cast<VMHELPDEF *>( + PTR_READ(dac_cast<TADDR>(&hlpFuncTable), CORINFO_HELP_COUNT * sizeof(VMHELPDEF))); + + for (int i = 0; i < CORINFO_HELP_COUNT; i++) + { + if (address == (TADDR)(pTable[i].pfnHelper)) + return s_rgHelperNames[i]; + } + } + + // Check if its a dynamically generated JIT helper + const static CorInfoHelpFunc s_rgDynamicHCallIds[] = { +#define DYNAMICJITHELPER(code, fn, sig) code, +#define JITHELPER(code, fn,sig) +#include <jithelpers.h> + }; + + // Read the whole table from the target in one shot for better performance + VMHELPDEF * pDynamicTable = static_cast<VMHELPDEF *>( + PTR_READ(dac_cast<TADDR>(&hlpDynamicFuncTable), DYNAMIC_CORINFO_HELP_COUNT * sizeof(VMHELPDEF))); + for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++) + { + if (address == (TADDR)(pDynamicTable[d].pfnHelper)) + { + return s_rgHelperNames[s_rgDynamicHCallIds[d]]; + } + } + + return NULL; +} + +HRESULT +ClrDataAccess::RawGetMethodName( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ ULONG32 flags, + /* [in] */ ULONG32 bufLen, + /* [out] */ ULONG32 *symbolLen, + /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR symbolBuf[ ], + /* [out] */ CLRDATA_ADDRESS* displacement) +{ +#ifdef _TARGET_ARM_ + _ASSERTE((address & THUMB_CODE) == 0); + address &= ~THUMB_CODE; +#endif + + const UINT k_cch64BitHexFormat = COUNTOF("1234567812345678"); + HRESULT status; + + if (flags != 0) + { + return E_INVALIDARG; + } + + TADDR taddr; + if( (status = TRY_CLRDATA_ADDRESS_TO_TADDR(address, &taddr)) != S_OK ) + { + return status; + } + + if ((status = IsPossibleCodeAddress(taddr)) != S_OK) + { + return status; + } + + PTR_StubManager pStubManager; + MethodDesc* methodDesc = NULL; + + { + EECodeInfo codeInfo(TO_TADDR(address)); + if (codeInfo.IsValid()) + { + if (displacement) + { + *displacement = codeInfo.GetRelOffset(); + } + + methodDesc = codeInfo.GetMethodDesc(); + goto NameFromMethodDesc; + } + } + + pStubManager = StubManager::FindStubManager(TO_TADDR(address)); + if (pStubManager != NULL) + { + if (displacement) + { + *displacement = 0; + } + + // + // Special-cased stub managers + // +#ifdef FEATURE_PREJIT + if (pStubManager == RangeSectionStubManager::g_pManager) + { + switch (RangeSectionStubManager::GetStubKind(TO_TADDR(address))) + { + case STUB_CODE_BLOCK_PRECODE: + goto PrecodeStub; + + case STUB_CODE_BLOCK_JUMPSTUB: + goto JumpStub; + + default: + break; + } + } + else +#endif + if (pStubManager == PrecodeStubManager::g_pManager) + { + PrecodeStub: + PCODE alignedAddress = AlignDown(TO_TADDR(address), PRECODE_ALIGNMENT); + +#ifdef _TARGET_ARM_ + alignedAddress += THUMB_CODE; +#endif + + SIZE_T maxPrecodeSize = sizeof(StubPrecode); + +#ifdef HAS_THISPTR_RETBUF_PRECODE + maxPrecodeSize = max(maxPrecodeSize, sizeof(ThisPtrRetBufPrecode)); +#endif +#ifdef HAS_REMOTING_PRECODE + maxPrecodeSize = max(maxPrecodeSize, sizeof(RemotingPrecode)); +#endif + + for (SIZE_T i = 0; i < maxPrecodeSize / PRECODE_ALIGNMENT; i++) + { + EX_TRY + { + // Try to find matching precode entrypoint + Precode* pPrecode = Precode::GetPrecodeFromEntryPoint(alignedAddress, TRUE); + if (pPrecode != NULL) + { + methodDesc = pPrecode->GetMethodDesc(); + if (methodDesc != NULL) + { + if (DacValidateMD(methodDesc)) + { + if (displacement) + { + *displacement = TO_TADDR(address) - PCODEToPINSTR(alignedAddress); + } + goto NameFromMethodDesc; + } + } + } + alignedAddress -= PRECODE_ALIGNMENT; + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions) + } + } + else + if (pStubManager == JumpStubStubManager::g_pManager) + { + JumpStub: + PCODE pTarget = decodeBackToBackJump(TO_TADDR(address)); + + HRESULT hr = GetRuntimeNameByAddress(pTarget, flags, bufLen, symbolLen, symbolBuf, NULL); + if (SUCCEEDED(hr)) + { + return hr; + } + + PCSTR pHelperName = GetJitHelperName(pTarget); + if (pHelperName != NULL) + { + hr = ConvertUtf8(pHelperName, bufLen, symbolLen, symbolBuf); + if (FAILED(hr)) + return S_FALSE; + + return hr; + } + } + + static WCHAR s_wszFormatNameWithStubManager[] = W("CLRStub[%s]@%I64x"); + + LPCWSTR wszStubManagerName = pStubManager->GetStubManagerName(TO_TADDR(address)); + _ASSERTE(wszStubManagerName != NULL); + + HRESULT hr = StringCchPrintfW( + symbolBuf, + bufLen, + s_wszFormatNameWithStubManager, + wszStubManagerName, // Arg 1 = stub name + TO_TADDR(address)); // Arg 2 = stub hex address + + if (hr == S_OK) + { + // Printf succeeded, so we have an exact char count to return + if (symbolLen) + { + size_t cchSymbol = wcslen(symbolBuf) + 1; + if (!FitsIn<ULONG32>(cchSymbol)) + return COR_E_OVERFLOW; + + *symbolLen = (ULONG32) cchSymbol; + } + return S_OK; + } + + // Printf failed. Estimate a size that will be at least big enough to hold the name + if (symbolLen) + { + size_t cchSymbol = COUNTOF(s_wszFormatNameWithStubManager) + + wcslen(wszStubManagerName) + + k_cch64BitHexFormat + + 1; + + if (!FitsIn<ULONG32>(cchSymbol)) + return COR_E_OVERFLOW; + + *symbolLen = (ULONG32) cchSymbol; + } + return S_FALSE; + } + + // Do not waste time looking up name for static helper. Debugger can get the actual name from .pdb. + PCSTR pHelperName; + pHelperName = GetJitHelperName(TO_TADDR(address), true /* dynamicHelpersOnly */); + if (pHelperName != NULL) + { + if (displacement) + { + *displacement = 0; + } + + HRESULT hr = ConvertUtf8(pHelperName, bufLen, symbolLen, symbolBuf); + if (FAILED(hr)) + return S_FALSE; + + return S_OK; + } + + return E_NOINTERFACE; + +NameFromMethodDesc: + if (methodDesc->GetClassification() == mcDynamic && + !methodDesc->GetSig()) + { + // XXX Microsoft - Should this case have a more specific name? + static WCHAR s_wszFormatNameAddressOnly[] = W("CLRStub@%I64x"); + + HRESULT hr = StringCchPrintfW( + symbolBuf, + bufLen, + s_wszFormatNameAddressOnly, + TO_TADDR(address)); + + if (hr == S_OK) + { + // Printf succeeded, so we have an exact char count to return + if (symbolLen) + { + size_t cchSymbol = wcslen(symbolBuf) + 1; + if (!FitsIn<ULONG32>(cchSymbol)) + return COR_E_OVERFLOW; + + *symbolLen = (ULONG32) cchSymbol; + } + return S_OK; + } + + // Printf failed. Estimate a size that will be at least big enough to hold the name + if (symbolLen) + { + size_t cchSymbol = COUNTOF(s_wszFormatNameAddressOnly) + + k_cch64BitHexFormat + + 1; + + if (!FitsIn<ULONG32>(cchSymbol)) + return COR_E_OVERFLOW; + + *symbolLen = (ULONG32) cchSymbol; + } + + return S_FALSE; + } + + return GetFullMethodName(methodDesc, bufLen, symbolLen, symbolBuf); +} + +HRESULT +ClrDataAccess::GetMethodExtents(MethodDesc* methodDesc, + METH_EXTENTS** extents) +{ + CLRDATA_ADDRESS_RANGE* curExtent; + + { + // + // Get the information from the methoddesc. + // We'll go through the CodeManager + JitManagers, so this should work + // for all types of managed code. + // + + PCODE methodStart = methodDesc->GetNativeCode(); + if (!methodStart) + { + return E_NOINTERFACE; + } + + EECodeInfo codeInfo(methodStart); + _ASSERTE(codeInfo.IsValid()); + + TADDR codeSize = codeInfo.GetCodeManager()->GetFunctionSize(codeInfo.GetGCInfoToken()); + + *extents = new (nothrow) METH_EXTENTS; + if (!*extents) + { + return E_OUTOFMEMORY; + } + + (*extents)->numExtents = 1; + curExtent = (*extents)->extents; + curExtent->startAddress = TO_CDADDR(methodStart); + curExtent->endAddress = + curExtent->startAddress + codeSize; + curExtent++; + } + + (*extents)->curExtent = 0; + + return S_OK; +} + +// Allocator to pass to the debug-info-stores... +BYTE* DebugInfoStoreNew(void * pData, size_t cBytes) +{ + return new (nothrow) BYTE[cBytes]; +} + +HRESULT +ClrDataAccess::GetMethodVarInfo(MethodDesc* methodDesc, + TADDR address, + ULONG32* numVarInfo, + ICorDebugInfo::NativeVarInfo** varInfo, + ULONG32* codeOffset) +{ + SUPPORTS_DAC; + COUNT_T countNativeVarInfo; + NewHolder<ICorDebugInfo::NativeVarInfo> nativeVars(NULL); + + DebugInfoRequest request; + TADDR nativeCodeStartAddr = PCODEToPINSTR(methodDesc->GetNativeCode()); + request.InitFromStartingAddr(methodDesc, nativeCodeStartAddr); + + BOOL success = DebugInfoManager::GetBoundariesAndVars( + request, + DebugInfoStoreNew, NULL, // allocator + NULL, NULL, + &countNativeVarInfo, &nativeVars); + + + if (!success) + { + return E_FAIL; + } + + if (!nativeVars || !countNativeVarInfo) + { + return E_NOINTERFACE; + } + + *numVarInfo = countNativeVarInfo; + *varInfo = nativeVars; + nativeVars.SuppressRelease(); // To prevent NewHolder from releasing the memory + + if (codeOffset) + { + *codeOffset = (ULONG32) + (address - nativeCodeStartAddr); + } + return S_OK; +} + +HRESULT +ClrDataAccess::GetMethodNativeMap(MethodDesc* methodDesc, + TADDR address, + ULONG32* numMap, + DebuggerILToNativeMap** map, + bool* mapAllocated, + CLRDATA_ADDRESS* codeStart, + ULONG32* codeOffset) +{ + _ASSERTE((codeOffset == NULL) || (address != NULL)); + + // Use the DebugInfoStore to get IL->Native maps. + // It doesn't matter whether we're jitted, ngenned etc. + + DebugInfoRequest request; + TADDR nativeCodeStartAddr = PCODEToPINSTR(methodDesc->GetNativeCode()); + request.InitFromStartingAddr(methodDesc, nativeCodeStartAddr); + + + // Bounds info. + ULONG32 countMapCopy; + NewHolder<ICorDebugInfo::OffsetMapping> mapCopy(NULL); + + BOOL success = DebugInfoManager::GetBoundariesAndVars( + request, + DebugInfoStoreNew, NULL, // allocator + &countMapCopy, &mapCopy, + NULL, NULL); + + if (!success) + { + return E_FAIL; + } + + + // Need to convert map formats. + *numMap = countMapCopy; + + *map = new (nothrow) DebuggerILToNativeMap[countMapCopy]; + if (!*map) + { + return E_OUTOFMEMORY; + } + + ULONG32 i; + for (i = 0; i < *numMap; i++) + { + (*map)[i].ilOffset = mapCopy[i].ilOffset; + (*map)[i].nativeStartOffset = mapCopy[i].nativeOffset; + if (i > 0) + { + (*map)[i - 1].nativeEndOffset = (*map)[i].nativeStartOffset; + } + (*map)[i].source = mapCopy[i].source; + } + if (*numMap >= 1) + { + (*map)[i - 1].nativeEndOffset = 0; + } + + + // Update varion out params. + if (codeStart) + { + *codeStart = TO_CDADDR(nativeCodeStartAddr); + } + if (codeOffset) + { + *codeOffset = (ULONG32) + (address - nativeCodeStartAddr); + } + + *mapAllocated = true; + return S_OK; +} + +// Get the MethodDesc for a function +// Arguments: +// Input: +// pModule - pointer to the module for the function +// memberRef - metadata token for the function +// Return Value: +// MethodDesc for the function +MethodDesc * ClrDataAccess::FindLoadedMethodRefOrDef(Module* pModule, + mdToken memberRef) +{ + CONTRACT(MethodDesc *) + { + GC_NOTRIGGER; + PRECONDITION(CheckPointer(pModule)); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + } + CONTRACT_END; + + // Must have a MemberRef or a MethodDef + mdToken tkType = TypeFromToken(memberRef); + _ASSERTE((tkType == mdtMemberRef) || (tkType == mdtMethodDef)); + + if (tkType == mdtMemberRef) + { + RETURN pModule->LookupMemberRefAsMethod(memberRef); + } + + RETURN pModule->LookupMethodDef(memberRef); +} // FindLoadedMethodRefOrDef + +// +// ReportMem - report a region of memory for dump gathering +// +// If you specify that you expect success, any failure will cause ReportMem to +// return false. If you do not expect success, true is always returned. +// This function only throws when all dump collection should be cancelled. +// +// Arguments: +// addr - the starting target address for the memory to report +// size - the length (in bytes) to report +// fExpectSuccess - if true (the default), then we expect that this region of memory +// should be fully readable. Any read errors indicate a corrupt target. +// +bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*= true*/) +{ + SUPPORTS_DAC_HOST_ONLY; + + // This block of code is to help debugging blocks that we report + // to minidump/heapdump. You can set break point here to view the static + // variable to figure out the size of blocks that we are reporting. + // Most useful is set conditional break point to catch large chuck of + // memory. We will leave it here for all builds. + // + static TADDR debugAddr; + static TSIZE_T debugSize; + debugAddr = addr; + debugSize = size; + + HRESULT status; + if (!addr || addr == (TADDR)-1 || !size) + { + if (fExpectSuccess) + return false; + else + return true; + } + + // + // Try and sanity-check the reported region of memory + // +#ifdef _DEBUG + // in debug builds, sanity-check all reports + const TSIZE_T k_minSizeToCheck = 1; +#else + // in retail builds, only sanity-check larger chunks which have the potential to waste a + // lot of time and/or space. This avoids the overhead of checking for the majority of + // memory regions (which are small). + const TSIZE_T k_minSizeToCheck = 1024; +#endif + if (size >= k_minSizeToCheck) + { + if (!IsFullyReadable(addr, size)) + { + if (!fExpectSuccess) + { + // We know the read might fail (eg. we're trying to find mapped pages in + // a module image), so just skip this block silently. + // Note that the EnumMemoryRegion callback won't necessarily do anything if any part of + // the region is unreadable, and so there is no point in calling it. For cases where we expect + // the read might fail, but we want to report any partial blocks, we have to break up the region + // into pages and try reporting each page anyway + return true; + } + + // We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort + // reporting and continue with the next data structure (where the exception is caught), + // just like we would for a DAC read error (otherwise we might do something stupid + // like get into an infinite loop, or otherwise waste time with corrupt data). + + TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering"); + return false; + } + } + + // Minidumps should never contain data structures that are anywhere near 4MB. If we see this, it's + // probably due to memory corruption. To keep the dump small, we'll truncate the block. Note that + // the size to which the block is truncated is pretty unique, so should be good evidence in a dump + // that this has happened. + // Note that it's hard to say what a good value would be here, or whether we should dump any of the + // data structure at all. Hopefully experience will help guide this going forward. + // @dbgtodo : Extend dump-gathering API to allow a dump-log to be included. + const TSIZE_T kMaxMiniDumpRegion = 4*1024*1024 - 3; // 4MB-3 + if( size > kMaxMiniDumpRegion + && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI + || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE)) + { + TARGET_CONSISTENCY_CHECK( false, "Dump target consistency failure - truncating minidump data structure"); + size = kMaxMiniDumpRegion; + } + + // track the total memory reported. + m_cbMemoryReported += size; + + // ICLRData APIs take only 32-bit sizes. In practice this will almost always be sufficient, but + // in theory we might have some >4GB ranges on large 64-bit processes doing a heap dump + // (for example, the code:LoaderHeap). If necessary, break up the reporting into maximum 4GB + // chunks so we can use the existing API. + // @dbgtodo : ICorDebugDataTarget should probably use 64-bit sizes + while (size) + { + ULONG32 enumSize; + if (size > ULONG_MAX) + { + enumSize = ULONG_MAX; + } + else + { + enumSize = (ULONG32)size; + } + + // Actually perform the memory reporting callback + status = m_enumMemCb->EnumMemoryRegion(TO_CDADDR(addr), enumSize); + if (status != S_OK) + { + m_memStatus = status; + + // If dump generation was cancelled, allow us to throw upstack so we'll actually quit. + if ((fExpectSuccess) && (status != COR_E_OPERATIONCANCELED)) + return false; + } + + // If the return value of EnumMemoryRegion is COR_E_OPERATIONCANCELED, + // it means that user has requested that the minidump gathering be canceled. + // To do this we throw an exception which is caught in EnumMemoryRegionsWrapper. + if (status == COR_E_OPERATIONCANCELED) + { + ThrowHR(status); + } + + // Move onto the next chunk (if any) + size -= enumSize; + addr += enumSize; + } + + return true; +} + + +// +// DacUpdateMemoryRegion - updates/poisons a region of memory of generated dump +// +// Parameters: +// addr - target address of the beginning of the memory region +// bufferSize - number of bytes to update/poison +// buffer - data to be written at given target address +// +bool ClrDataAccess::DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* buffer) +{ + SUPPORTS_DAC_HOST_ONLY; + + HRESULT status; + if (!addr || addr == (TADDR)-1 || !bufferSize) + { + return false; + } + + // track the total memory reported. + m_cbMemoryReported += bufferSize; + + if (m_updateMemCb == NULL) + { + return false; + } + + // Actually perform the memory updating callback + status = m_updateMemCb->UpdateMemoryRegion(TO_CDADDR(addr), (ULONG32)bufferSize, buffer); + if (status != S_OK) + { + return false; + } + + return true; +} + +// +// Check whether a region of target memory is fully readable. +// +// Arguments: +// addr The base target address of the region +// size The size of the region to analyze +// +// Return value: +// True if the entire regions appears to be readable, false otherwise. +// +// Notes: +// The motivation here is that reporting large regions of unmapped address space to dbgeng can result in +// it taking a long time trying to identify a valid subrange. This can happen when the target +// memory is corrupt, and we enumerate a data structure with a dynamic size. Ideally we would just spec +// the ICLRDataEnumMemoryRegionsCallback API to require the client to fail if it detects an unmapped +// memory address in the region. However, we can't change the existing dbgeng code, so for now we'll +// rely on this heuristic here. +// @dbgtodo : Try and get the dbg team to change their EnumMemoryRegion behavior. See DevDiv Bugs 6265 +// +bool ClrDataAccess::IsFullyReadable(TADDR taBase, TSIZE_T dwSize) +{ + // The only way we have to verify that a memory region is readable is to try reading it in it's + // entirety. This is potentially expensive, so we'll rely on a heuristic that spot-checks various + // points in the region. + + // Ensure we've got something to check + if( dwSize == 0 ) + return true; + + // Check for overflow + TADDR taEnd = DacTAddrOffset(taBase, dwSize, 1); + + // Loop through using expontential growth, being sure to check both the first and last byte + TADDR taCurr = taBase; + TSIZE_T dwInc = 4096; + bool bDone = false; + while (!bDone) + { + // Try and read a byte from the target. Note that we don't use PTR_BYTE here because we don't want + // the overhead of inserting entries into the DAC instance cache. + BYTE b; + ULONG32 dwBytesRead; + HRESULT hr = m_pTarget->ReadVirtual(taCurr, &b, 1, &dwBytesRead); + if( hr != S_OK || dwBytesRead < 1 ) + { + return false; + } + + if (taEnd - taCurr <= 1) + { + // We just read the last byte so we're done + _ASSERTE( taCurr = taEnd - 1 ); + bDone = true; + } + else if (dwInc == 0 || dwInc >= taEnd - taCurr) + { + // we've reached the end of the exponential series, check the last byte + taCurr = taEnd - 1; + } + else + { + // advance current pointer (subtraction above ensures this won't overflow) + taCurr += dwInc; + + // double the increment for next time (or set to 0 if it's already the max) + dwInc <<= 1; + } + } + return true; +} + +JITNotification* +ClrDataAccess::GetHostJitNotificationTable() +{ + if (m_jitNotificationTable == NULL) + { + m_jitNotificationTable = + JITNotifications::InitializeNotificationTable(1000); + } + + return m_jitNotificationTable; +} + +GcNotification* +ClrDataAccess::GetHostGcNotificationTable() +{ + if (m_gcNotificationTable == NULL) + { + m_gcNotificationTable = + GcNotifications::InitializeNotificationTable(128); + } + + return m_gcNotificationTable; +} + +/* static */ bool +ClrDataAccess::GetMetaDataFileInfoFromPEFile(PEFile *pPEFile, + DWORD &dwTimeStamp, + DWORD &dwSize, + DWORD &dwDataSize, + DWORD &dwRvaHint, + bool &isNGEN, + __out_ecount(cchFilePath) LPWSTR wszFilePath, + const DWORD cchFilePath) +{ + SUPPORTS_DAC_HOST_ONLY; + PEImage *mdImage = NULL; + PEImageLayout *layout; + IMAGE_DATA_DIRECTORY *pDir = NULL; + COUNT_T uniPathChars = 0; + + isNGEN = false; + + if (pPEFile->HasNativeImage()) + { + mdImage = pPEFile->GetNativeImage(); + _ASSERTE(mdImage != NULL); + layout = mdImage->GetLoadedLayout(); + pDir = &(layout->GetCorHeader()->MetaData); + // For ngen image, the IL metadata is stored for private use. So we need to pass + // the RVA hint to find it to debuggers. + // + if (pDir->Size != 0) + { + isNGEN = true; + dwRvaHint = pDir->VirtualAddress; + dwDataSize = pDir->Size; + } + + } + if (pDir == NULL || pDir->Size == 0) + { + mdImage = pPEFile->GetILimage(); + if (mdImage != NULL) + { + layout = mdImage->GetLoadedLayout(); + pDir = &layout->GetCorHeader()->MetaData; + + // In IL image case, we do not have any hint to IL metadata since it is stored + // in the corheader. + // + dwRvaHint = 0; + dwDataSize = pDir->Size; + } + else + { + return false; + } + } + + // Do not fail if path can not be read. Triage dumps don't have paths and we want to fallback + // on searching metadata from IL image. + mdImage->GetPath().DacGetUnicode(cchFilePath, wszFilePath, &uniPathChars); + + if (!mdImage->HasNTHeaders() || + !mdImage->HasCorHeader() || + !mdImage->HasLoadedLayout() || + (uniPathChars > cchFilePath)) + { + return false; + } + + // It is possible that the module is in-memory. That is the wszFilePath here is empty. + // We will try to use the module name instead in this case for hosting debugger + // to find match. + if (wcslen(wszFilePath) == 0) + { + mdImage->GetModuleFileNameHintForDAC().DacGetUnicode(cchFilePath, wszFilePath, &uniPathChars); + if (uniPathChars > cchFilePath) + { + return false; + } + } + + dwTimeStamp = layout->GetTimeDateStamp(); + dwSize = (ULONG32)layout->GetVirtualSize(); + + return true; +} + +/* static */ +bool ClrDataAccess::GetILImageInfoFromNgenPEFile(PEFile *peFile, + DWORD &dwTimeStamp, + DWORD &dwSize, + __out_ecount(cchFilePath) LPWSTR wszFilePath, + const DWORD cchFilePath) +{ + SUPPORTS_DAC_HOST_ONLY; + DWORD dwWritten = 0; + + // use the IL File name + if (!peFile->GetPath().DacGetUnicode(cchFilePath, wszFilePath, (COUNT_T *)(&dwWritten))) + { + // Use DAC hint to retrieve the IL name. + peFile->GetModuleFileNameHint().DacGetUnicode(cchFilePath, wszFilePath, (COUNT_T *)(&dwWritten)); + } +#ifdef FEATURE_PREJIT + // Need to get IL image information from cached info in the ngen image. + dwTimeStamp = peFile->GetLoaded()->GetNativeVersionInfo()->sourceAssembly.timeStamp; + dwSize = peFile->GetLoaded()->GetNativeVersionInfo()->sourceAssembly.ilImageSize; +#else + dwTimeStamp = 0; + dwSize = 0; +#endif // FEATURE_PREJIT + + return true; +} + +#if defined(FEATURE_CORESYSTEM) +/* static */ +// We extract "ni.dll or .ni.winmd" from the NGEM image name to obtain the IL image name. +// In the end we add given ilExtension. +// This dependecy is based on Apollo installer behavior. +bool ClrDataAccess::GetILImageNameFromNgenImage( LPCWSTR ilExtension, + __out_ecount(cchFilePath) LPWSTR wszFilePath, + const DWORD cchFilePath) +{ + if (wszFilePath == NULL || cchFilePath == 0) + { + return false; + } + + _wcslwr_s(wszFilePath, cchFilePath); + // Find the "ni.dll" or "ni.winmd" extension (check for PEFile isWinRT something to know when is winmd or not. + // If none exists use NGEN image name. + // + const WCHAR* ngenExtension[] = {W("ni.dll"), W("ni.winmd")}; + + for (unsigned i = 0; i < COUNTOF(ngenExtension); ++i) + { + if (wcslen(ilExtension) > wcslen(ngenExtension[i])) + { + // We should not have IL image name bigger than NGEN image. + // It will not fit inside wszFilePath. + continue; + } + LPWSTR wszFileExtension = wcsstr(wszFilePath, ngenExtension[i]); + if (wszFileExtension != 0) + { + LPWSTR wszNextFileExtension = wszFileExtension; + // Find last occurence + do + { + wszFileExtension = wszNextFileExtension; + wszNextFileExtension = wcsstr(wszFileExtension + 1, ngenExtension[i]); + } while (wszNextFileExtension != 0); + + // Overwrite ni.dll or ni.winmd with ilExtension(.dll, .winmd) + if (!memcpy_s(wszFileExtension, + wcslen(ngenExtension[i])*sizeof(WCHAR), + ilExtension, + wcslen(ilExtension)*sizeof(WCHAR))) + { + wszFileExtension[wcslen(ilExtension)] = '\0'; + return true; + } + } + } + + //Use ngen filename if there is no ".ni" + if (wcsstr(wszFilePath, W(".ni")) == 0) + { + return true; + } + + return false; +} +#endif // FEATURE_CORESYSTEM + +void * +ClrDataAccess::GetMetaDataFromHost(PEFile* peFile, + bool* isAlternate) +{ + DWORD imageTimestamp, imageSize, dataSize; + void* buffer = NULL; + WCHAR uniPath[MAX_LONGPATH] = {0}; + bool isAlt = false; + bool isNGEN = false; + DAC_INSTANCE* inst = NULL; + HRESULT hr = S_OK; + DWORD ulRvaHint; + // + // We always ask for the IL image metadata, + // as we expect that to be more + // available than others. The drawback is that + // there may be differences between the IL image + // metadata and native image metadata, so we + // have to mark such alternate metadata so that + // we can fail unsupported usage of it. + // + + // Microsoft - above comment seems to be an unimplemented thing. + // The DAC_MD_IMPORT.isAlternate field gets ultimately set, but + // on the searching I did, I cannot find any usage of it + // other than in the ctor. Should we be doing something, or should + // we remove this comment and the isAlternate field? + // It's possible that test will want us to track whether we have + // an IL image's metadata loaded against an NGEN'ed image + // so the field remains for now. + + if (!ClrDataAccess::GetMetaDataFileInfoFromPEFile( + peFile, + imageTimestamp, + imageSize, + dataSize, + ulRvaHint, + isNGEN, + uniPath, + NumItems(uniPath))) + { + return NULL; + } + + // try direct match for the image that is loaded into the managed process + peFile->GetLoadedMetadata((COUNT_T *)(&dataSize)); + + DWORD allocSize = 0; + if (!ClrSafeInt<DWORD>::addition(dataSize, sizeof(DAC_INSTANCE), allocSize)) + { + DacError(HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW)); + } + + inst = m_instances.Alloc(0, allocSize, DAC_DPTR); + if (!inst) + { + DacError(E_OUTOFMEMORY); + return NULL; + } + + buffer = (void*)(inst + 1); + + // APIs implemented by hosting debugger. It can use the path/filename, timestamp, and + // file size to find an exact match for the image. If that fails for an ngen'ed image, + // we can request the IL image which it came from. + if (m_legacyMetaDataLocator) + { + // Legacy API implemented by hosting debugger. + hr = m_legacyMetaDataLocator->GetMetadata( + uniPath, + imageTimestamp, + imageSize, + NULL, // MVID - not used yet + ulRvaHint, + 0, // flags - reserved for future. + dataSize, + (BYTE*)buffer, + NULL); + } + else + { + hr = m_target3->GetMetaData( + uniPath, + imageTimestamp, + imageSize, + NULL, // MVID - not used yet + ulRvaHint, + 0, // flags - reserved for future. + dataSize, + (BYTE*)buffer, + NULL); + } + if (FAILED(hr) && isNGEN) + { + // We failed to locate the ngen'ed image. We should try to + // find the matching IL image + // + isAlt = true; + if (!ClrDataAccess::GetILImageInfoFromNgenPEFile( + peFile, + imageTimestamp, + imageSize, + uniPath, + NumItems(uniPath))) + { + goto ErrExit; + } + +#if defined(FEATURE_CORESYSTEM) + const WCHAR* ilExtension[] = {W("dll"), W("winmd")}; + WCHAR ngenImageName[MAX_LONGPATH] = {0}; + if (wcscpy_s(ngenImageName, NumItems(ngenImageName), uniPath) != 0) + { + goto ErrExit; + } + for (unsigned i = 0; i < COUNTOF(ilExtension); i++) + { + if (wcscpy_s(uniPath, NumItems(uniPath), ngenImageName) != 0) + { + goto ErrExit; + } + // Transform NGEN image name into IL Image name + if (!GetILImageNameFromNgenImage(ilExtension[i], uniPath, NumItems(uniPath))) + { + goto ErrExit; + } +#endif//FEATURE_CORESYSTEM + + // RVA size in ngen image and IL image is the same. Because the only + // different is in RVA. That is 4 bytes column fixed. + // + + // try again + if (m_legacyMetaDataLocator) + { + hr = m_legacyMetaDataLocator->GetMetadata( + uniPath, + imageTimestamp, + imageSize, + NULL, // MVID - not used yet + 0, // pass zero hint here... important + 0, // flags - reserved for future. + dataSize, + (BYTE*)buffer, + NULL); + } + else + { + hr = m_target3->GetMetaData( + uniPath, + imageTimestamp, + imageSize, + NULL, // MVID - not used yet + 0, // pass zero hint here... important + 0, // flags - reserved for future. + dataSize, + (BYTE*)buffer, + NULL); + } +#if defined(FEATURE_CORESYSTEM) + if (SUCCEEDED(hr)) + { + break; + } + } +#endif // FEATURE_CORESYSTEM + } + + if (FAILED(hr)) + { + goto ErrExit; + } + + *isAlternate = isAlt; + m_instances.AddSuperseded(inst); + return buffer; + +ErrExit: + if (inst != NULL) + { + m_instances.ReturnAlloc(inst); + } + return NULL; +} + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Given a PEFile or a ReflectionModule try to find the corresponding metadata +// We will first ask debugger to locate it. If fail, we will try +// to get it from the target process +// +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +IMDInternalImport* +ClrDataAccess::GetMDImport(const PEFile* peFile, const ReflectionModule* reflectionModule, bool throwEx) +{ + HRESULT status; + PTR_CVOID mdBaseTarget = NULL; + COUNT_T mdSize; + IMDInternalImport* mdImport = NULL; + PVOID mdBaseHost = NULL; + bool isAlternate = false; + + _ASSERTE(peFile == NULL && reflectionModule != NULL || peFile != NULL && reflectionModule == NULL); + TADDR peFileAddr = (peFile != NULL) ? dac_cast<TADDR>(peFile) : dac_cast<TADDR>(reflectionModule); + + // + // Look for one we've already created. + // + mdImport = m_mdImports.Get(peFileAddr); + if (mdImport != NULL) + { + return mdImport; + } + + if (peFile != NULL) + { + // Get the metadata size + mdBaseTarget = ((PEFile*)peFile)->GetLoadedMetadata(&mdSize); + } + else if (reflectionModule != NULL) + { + // Get the metadata + PTR_SBuffer metadataBuffer = reflectionModule->GetDynamicMetadataBuffer(); + if (metadataBuffer != PTR_NULL) + { + mdBaseTarget = dac_cast<PTR_CVOID>((metadataBuffer->DacGetRawBuffer()).StartAddress()); + mdSize = metadataBuffer->GetSize(); + } + else + { + if (throwEx) + { + DacError(E_FAIL); + } + return NULL; + } + } + else + { + if (throwEx) + { + DacError(E_FAIL); + } + return NULL; + } + + if (mdBaseTarget == PTR_NULL) + { + mdBaseHost = NULL; + } + else + { + + // + // Maybe the target process has the metadata + // Find out where the metadata for the image is + // in the target's memory. + // + // + // Read the metadata into the host process. Make sure pass in false in the last + // parameter. This is only matters when producing skinny mini-dump. This will + // prevent metadata gets reported into mini-dump. + // + mdBaseHost = DacInstantiateTypeByAddressNoReport(dac_cast<TADDR>(mdBaseTarget), mdSize, + false); + } + + // Try to see if debugger can locate it + if (peFile != NULL && mdBaseHost == NULL && (m_target3 || m_legacyMetaDataLocator)) + { + // We couldn't read the metadata from memory. Ask + // the target for metadata as it may be able to + // provide it from some alternate means. + mdBaseHost = GetMetaDataFromHost(const_cast<PEFile *>(peFile), &isAlternate); + } + + if (mdBaseHost == NULL) + { + // cannot locate metadata anywhere + if (throwEx) + { + DacError(E_INVALIDARG); + } + return NULL; + } + + // + // Open the MD interface on the host copy of the metadata. + // + + status = GetMDInternalInterface(mdBaseHost, mdSize, ofRead, + IID_IMDInternalImport, + (void**)&mdImport); + if (status != S_OK) + { + if (throwEx) + { + DacError(status); + } + return NULL; + } + + // + // Remember the object for this module for + // possible later use. + // The m_mdImports list does get cleaned up by calls to ClrDataAccess::Flush, + // i.e. every time the process changes state. + + if (m_mdImports.Add(peFileAddr, mdImport, isAlternate) == NULL) + { + mdImport->Release(); + DacError(E_OUTOFMEMORY); + } + + return mdImport; +} + + +// +// Set whether inconsistencies in the target should raise asserts. +// This overrides the default initial setting. +// +// Arguments: +// fEnableAsserts - whether ASSERTs in dacized code should be enabled +// + +void ClrDataAccess::SetTargetConsistencyChecks(bool fEnableAsserts) +{ + LIMITED_METHOD_DAC_CONTRACT; + m_fEnableTargetConsistencyAsserts = fEnableAsserts; +} + +// +// Get whether inconsistencies in the target should raise asserts. +// +// Return value: +// whether ASSERTs in dacized code should be enabled +// +// Notes: +// The implementation of ASSERT accesses this via code:DacTargetConsistencyAssertsEnabled +// +// By default, this is disabled, unless COMPlus_DbgDACEnableAssert is set (see code:ClrDataAccess::ClrDataAccess). +// This is necessary for compatibility. For example, SOS expects to be able to scan for +// valid MethodTables etc. (which may cause ASSERTs), and also doesn't want ASSERTs when working +// with targets with corrupted memory. +// +// Calling code:ClrDataAccess::SetTargetConsistencyChecks overrides the default setting. +// +bool ClrDataAccess::TargetConsistencyAssertsEnabled() +{ + LIMITED_METHOD_DAC_CONTRACT; + return m_fEnableTargetConsistencyAsserts; +} + +#ifdef FEATURE_CORESYSTEM +#define ctime_s _ctime32_s +#define time_t __time32_t +#endif + +// +// VerifyDlls - Validate that the mscorwks in the target matches this version of mscordacwks +// Only done on Windows and Mac builds at the moment. +// See code:CordbProcess::CordbProcess#DBIVersionChecking for more information regarding version checking. +// +HRESULT ClrDataAccess::VerifyDlls() +{ +#ifndef FEATURE_PAL + // Provide a knob for disabling this check if we really want to try and proceed anyway with a + // DAC mismatch. DAC behavior may be arbitrarily bad - globals probably won't be at the same + // address, data structures may be laid out differently, etc. + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDACSkipVerifyDlls)) + { + return S_OK; + } + + // Read the debug directory timestamp from the target mscorwks image using DAC + // Note that we don't use the PE timestamp because the PE file might be changed in ways + // that don't effect the PDB (and therefore don't effect DAC). Specifically, we rebase + // our DLLs at the end of a build, that changes the PE file, but not the PDB. + // Note that if we wanted to be extra careful, we could read the CV contents (which includes + // the GUID signature) and verify it matches. Using the timestamp is useful for helpful error + // messages, and should be sufficient in any real scenario. + DWORD timestamp = 0; + HRESULT hr = S_OK; + DAC_ENTER(); + EX_TRY + { + // Note that we don't need to worry about ensuring the image memory read by this code + // is saved in a minidump. Managed minidump debugging already requires that you have + // the full mscorwks.dll available at debug time (eg. windbg won't even load DAC without it). + PEDecoder pedecoder(dac_cast<PTR_VOID>(m_globalBase)); + + // We use the first codeview debug directory entry since this should always refer to the single + // PDB for mscorwks.dll. + const UINT k_maxDebugEntries = 32; // a reasonable upper limit in case of corruption + for( UINT i = 0; i < k_maxDebugEntries; i++) + { + PTR_IMAGE_DEBUG_DIRECTORY pDebugEntry = pedecoder.GetDebugDirectoryEntry(i); + + // If there are no more entries, then stop + if (pDebugEntry == NULL) + break; + + // Ignore non-codeview entries. Some scenarios (eg. optimized builds), there may be extra + // debug directory entries at the end of some other type. + if (pDebugEntry->Type == IMAGE_DEBUG_TYPE_CODEVIEW) + { + // Found a codeview entry - use it's timestamp for comparison + timestamp = pDebugEntry->TimeDateStamp; + break; + } + } + char szMsgBuf[1024]; + _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE, + "Failed to find any valid codeview debug directory entry in %s image", + MAIN_CLR_MODULE_NAME_A); + _ASSERTE_MSG(timestamp != 0, szMsgBuf); + } + EX_CATCH + { + if (!DacExceptionFilter(GET_EXCEPTION(), this, &hr)) + { + EX_RETHROW; + } + } + EX_END_CATCH(SwallowAllExceptions) + DAC_LEAVE(); + if (FAILED(hr)) + { + return hr; + } + + // Validate that we got a timestamp and it matches what the DAC table told us to expect + if (timestamp == 0 || timestamp != g_dacTableInfo.dwID0) + { + // Timestamp mismatch. This means mscordacwks is being used with a version of + // mscorwks other than the one it was built for. This will not work reliably. + +#ifdef _DEBUG + // Check if verbose asserts are enabled. The default is up to the specific instantiation of + // ClrDataAccess, but can be overridden (in either direction) by a COMPlus_ knob. + // Note that we check this knob every time because it may be handy to turn it on in + // the environment mid-flight. + DWORD dwAssertDefault = m_fEnableDllVerificationAsserts ? 1 : 0; + if (REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgDACAssertOnMismatch, dwAssertDefault)) + { + // Output a nice error message that contains the timestamps in string format. + time_t actualTime = timestamp; + char szActualTime[30]; + ctime_s(szActualTime, sizeof(szActualTime), &actualTime); + + time_t expectedTime = g_dacTableInfo.dwID0; + char szExpectedTime[30]; + ctime_s(szExpectedTime, sizeof(szExpectedTime), &expectedTime); + + // Create a nice detailed message for the assert dialog. + // Note that the strings returned by ctime_s have terminating newline characters. + // This is technically a TARGET_CONSISTENCY_CHECK because a corrupt target could, + // in-theory, have a corrupt mscrowks PE header and cause this check to fail + // unnecessarily. However, this check occurs during startup, before we know + // whether target consistency checks should be enabled, so it's always enabled + // at the moment. + + char szMsgBuf[1024]; + _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE, + "DAC fatal error: %s/mscordacwks.dll version mismatch\n\n"\ + "The debug directory timestamp of the loaded %s does not match the\n"\ + "version mscordacwks.dll was built for.\n"\ + "Expected %s timestamp: %s"\ + "Actual %s timestamp: %s\n"\ + "DAC will now fail to initialize with a CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS\n"\ + "error. If you really want to try and use the mimatched DLLs, you can disable this\n"\ + "check by setting COMPlus_DbgDACSkipVerifyDlls=1. However, using a mismatched DAC\n"\ + "DLL will usually result in arbitrary debugger failures.\n", + MAIN_CLR_DLL_NAME_A, + MAIN_CLR_DLL_NAME_A, + MAIN_CLR_DLL_NAME_A, + szExpectedTime, + MAIN_CLR_DLL_NAME_A, + szActualTime); + _ASSERTE_MSG(false, szMsgBuf); + } +#endif + + // Return a specific hresult indicating this problem + return CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS; + } +#endif // FEATURE_PAL + + return S_OK; +} + +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + +void ClrDataAccess::InitStreamsForWriting(IN CLRDataEnumMemoryFlags flags) +{ + // enforce this should only be called when generating triage and mini-dumps + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + return; + + EX_TRY + { + if (m_streams == NULL) + m_streams = new DacStreamManager(g_MiniMetaDataBuffAddress, g_MiniMetaDataBuffMaxSize); + + if (!m_streams->PrepareStreamsForWriting()) + { + delete m_streams; + m_streams = NULL; + } + } + EX_CATCH + { + if (m_streams != NULL) + { + delete m_streams; + m_streams = NULL; + } + } + EX_END_CATCH(SwallowAllExceptions) +} + +bool ClrDataAccess::MdCacheAddEEName(TADDR taEEStruct, const SString& name) +{ + bool result = false; + EX_TRY + { + if (m_streams != NULL) + result = m_streams->MdCacheAddEEName(taEEStruct, name); + } + EX_CATCH + { + result = false; + } + EX_END_CATCH(SwallowAllExceptions) + + return result; +} + +void ClrDataAccess::EnumStreams(IN CLRDataEnumMemoryFlags flags) +{ + // enforce this should only be called when generating triage and mini-dumps + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + return; + + EX_TRY + { + if (m_streams != NULL) + m_streams->EnumStreams(flags); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions) +} + +bool ClrDataAccess::MdCacheGetEEName(TADDR taEEStruct, SString & eeName) +{ + bool result = false; + EX_TRY + { + if (m_streams == NULL) + m_streams = new DacStreamManager(g_MiniMetaDataBuffAddress, g_MiniMetaDataBuffMaxSize); + + result = m_streams->MdCacheGetEEName(taEEStruct, eeName); + } + EX_CATCH + { + result = false; + } + EX_END_CATCH(SwallowAllExceptions) + + return result; +} + +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + +// Needed for RT_RCDATA. +#define MAKEINTRESOURCE(v) MAKEINTRESOURCEW(v) + +// this funny looking double macro forces x to be macro expanded before L is prepended +#define _WIDE(x) _WIDE2(x) +#define _WIDE2(x) W(x) + +HRESULT +ClrDataAccess::GetDacGlobals() +{ +#ifdef FEATURE_PAL +#ifdef DAC_TABLE_SIZE + if (DAC_TABLE_SIZE != sizeof(g_dacGlobals)) + { + return E_INVALIDARG; + } +#endif + ULONG64 dacTableAddress = m_globalBase + DAC_TABLE_RVA; + if (FAILED(ReadFromDataTarget(m_pTarget, dacTableAddress, (BYTE*)&g_dacGlobals, sizeof(g_dacGlobals)))) + { + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + if (g_dacGlobals.ThreadStore__s_pThreadStore == NULL) + { + return CORDBG_E_UNSUPPORTED; + } + return S_OK; +#else + HRESULT status = E_FAIL; + DWORD rsrcRVA = 0; + LPVOID rsrcData = NULL; + DWORD rsrcSize = 0; + + HRSRC rsrcFound; + HGLOBAL rsrc; + + DWORD resourceSectionRVA = 0; + + if (FAILED(status = GetMachineAndResourceSectionRVA(m_pTarget, m_globalBase, NULL, &resourceSectionRVA))) + { + _ASSERTE_MSG(false, "DAC fatal error: can't locate resource section in " MAIN_CLR_DLL_NAME_A); + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + + if (FAILED(status = GetResourceRvaFromResourceSectionRvaByName(m_pTarget, m_globalBase, + resourceSectionRVA, (DWORD)RT_RCDATA, _WIDE(DACCESS_TABLE_RESOURCE), 0, + &rsrcRVA, &rsrcSize))) + { + _ASSERTE_MSG(false, "DAC fatal error: can't locate DAC table resource in " MAIN_CLR_DLL_NAME_A); + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + + rsrcData = new (nothrow) BYTE[rsrcSize]; + if (rsrcData == NULL) + return E_OUTOFMEMORY; + + if (FAILED(status = ReadFromDataTarget(m_pTarget, m_globalBase + rsrcRVA, (BYTE*)rsrcData, rsrcSize))) + { + _ASSERTE_MSG(false, "DAC fatal error: can't load DAC table resource from " MAIN_CLR_DLL_NAME_A); + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + + + PBYTE rawData = (PBYTE)rsrcData; + DWORD bytesLeft = rsrcSize; + + // Read the header + struct DacTableHeader header; + + // We currently expect the header to be 2 32-bit values and 1 16-byte value, + // make sure there is no packing going on or anything. + static_assert_no_msg(sizeof(DacTableHeader) == 2 * 4 + 16); + + if (bytesLeft < sizeof(DacTableHeader)) + { + _ASSERTE_MSG(false, "DAC fatal error: DAC table too small for header."); + goto Exit; + } + memcpy(&header, rawData, sizeof(DacTableHeader)); + rawData += sizeof(DacTableHeader); + bytesLeft -= sizeof(DacTableHeader); + + // Save the table info for later use + g_dacTableInfo = header.info; + + // Sanity check that the DAC table is the size we expect. + // This could fail if a different version of dacvars.h or vptr_list.h was used when building + // mscordacwks.dll than when running DacTableGen. + + if (offsetof(DacGlobals, Thread__vtAddr) != header.numGlobals * sizeof(ULONG)) + { +#ifdef _DEBUG + char szMsgBuf[1024]; + _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE, + "DAC fatal error: mismatch in number of globals in DAC table. Read from file: %d, expected: %d.", + header.numGlobals, + offsetof(DacGlobals, Thread__vtAddr) / sizeof(ULONG)); + _ASSERTE_MSG(false, szMsgBuf); +#endif // _DEBUG + + status = E_INVALIDARG; + goto Exit; + } + + if (sizeof(DacGlobals) != (header.numGlobals + header.numVptrs) * sizeof(ULONG)) + { +#ifdef _DEBUG + char szMsgBuf[1024]; + _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE, + "DAC fatal error: mismatch in number of vptrs in DAC table. Read from file: %d, expected: %d.", + header.numVptrs, + (sizeof(DacGlobals) - offsetof(DacGlobals, Thread__vtAddr)) / sizeof(ULONG)); + _ASSERTE_MSG(false, szMsgBuf); +#endif // _DEBUG + + status = E_INVALIDARG; + goto Exit; + } + + // Copy the DAC table into g_dacGlobals + if (bytesLeft < sizeof(DacGlobals)) + { + _ASSERTE_MSG(false, "DAC fatal error: DAC table resource too small for DacGlobals."); + status = E_UNEXPECTED; + goto Exit; + } + memcpy(&g_dacGlobals, rawData, sizeof(DacGlobals)); + rawData += sizeof(DacGlobals); + bytesLeft -= sizeof(DacGlobals); + + status = S_OK; + +Exit: + + return status; +#endif +} + +#undef MAKEINTRESOURCE + +//---------------------------------------------------------------------------- +// +// IsExceptionFromManagedCode - report if pExceptionRecord points to a exception belonging to the current runtime +// +// Arguments: +// pExceptionRecord - the exception record +// +// Return Value: +// TRUE if it is +// Otherwise, FALSE +// +//---------------------------------------------------------------------------- +BOOL ClrDataAccess::IsExceptionFromManagedCode(EXCEPTION_RECORD* pExceptionRecord) +{ + DAC_ENTER(); + + HRESULT status; + BOOL flag = FALSE; + + if (::IsExceptionFromManagedCode(pExceptionRecord)) + { + flag = TRUE; + } + + DAC_LEAVE(); + + return flag; +} + +#ifndef FEATURE_PAL + +//---------------------------------------------------------------------------- +// +// GetWatsonBuckets - retrieve Watson buckets from the specified thread +// +// Arguments: +// dwThreadId - the thread ID +// pGM - pointer to the space to store retrieved Watson buckets +// +// Return Value: +// S_OK if the operation is successful. +// or S_FALSE if Watson buckets cannot be found +// else detailed error code. +// +//---------------------------------------------------------------------------- +HRESULT ClrDataAccess::GetWatsonBuckets(DWORD dwThreadId, GenericModeBlock * pGM) +{ + _ASSERTE((dwThreadId != 0) && (pGM != NULL)); + if ((dwThreadId == 0) || (pGM == NULL)) + { + return E_INVALIDARG; + } + + DAC_ENTER(); + + Thread * pThread = DacGetThread(dwThreadId); + _ASSERTE(pThread != NULL); + + HRESULT hr = E_UNEXPECTED; + + if (pThread != NULL) + { + hr = GetClrWatsonBucketsWorker(pThread, pGM); + } + + DAC_LEAVE(); + return hr; +} + +#endif // FEATURE_PAL + +//---------------------------------------------------------------------------- +// +// CLRDataAccessCreateInstance - create and initialize a ClrDataAccess object +// +// Arguments: +// pLegacyTarget - data target object +// pClrDataAccess - ClrDataAccess object +// +// Return Value: +// S_OK on success, else detailed error code. +// +//---------------------------------------------------------------------------- +STDAPI CLRDataAccessCreateInstance(ICLRDataTarget * pLegacyTarget, + ClrDataAccess ** pClrDataAccess) +{ + if ((pLegacyTarget == NULL) || (pClrDataAccess == NULL)) + { + return E_INVALIDARG; + } + + *pClrDataAccess = NULL; + + // Create an adapter which implements the new ICorDebugDataTarget interfaces using + // a legacy implementation of ICLRDataTarget + // ClrDataAccess will take a take a ref on this and delete it when it's released. + DataTargetAdapter * pDtAdapter = new (nothrow) DataTargetAdapter(pLegacyTarget); + if (!pDtAdapter) + { + return E_OUTOFMEMORY; + } + + ClrDataAccess* dacClass = new (nothrow) ClrDataAccess(pDtAdapter, pLegacyTarget); + if (!dacClass) + { + delete pDtAdapter; + return E_OUTOFMEMORY; + } + + HRESULT hr = dacClass->Initialize(); + if (FAILED(hr)) + { + dacClass->Release(); + return hr; + } + + *pClrDataAccess = dacClass; + return S_OK; +} + + +//---------------------------------------------------------------------------- +// +// CLRDataCreateInstance. +// Creates the IXClrData object +// This is the legacy entrypoint to DAC, used by dbgeng/dbghelp (windbg, SOS, watson, etc). +// +//---------------------------------------------------------------------------- +#ifdef __llvm__ +__attribute__((used)) +#endif // __llvm__ +STDAPI +CLRDataCreateInstance(REFIID iid, + ICLRDataTarget * pLegacyTarget, + void ** iface) +{ + if ((pLegacyTarget == NULL) || (iface == NULL)) + { + return E_INVALIDARG; + } + + *iface = NULL; + ClrDataAccess * pClrDataAccess; + HRESULT hr = CLRDataAccessCreateInstance(pLegacyTarget, &pClrDataAccess); + if (hr != S_OK) + { + return hr; + } + + hr = pClrDataAccess->QueryInterface(iid, iface); + + pClrDataAccess->Release(); + return hr; +} + + +//---------------------------------------------------------------------------- +// +// OutOfProcessExceptionEventGetProcessIdAndThreadId - get ProcessID and ThreadID +// +// Arguments: +// hProcess - process handle +// hThread - thread handle +// pPId - pointer to DWORD to store ProcessID +// pThreadId - pointer to DWORD to store ThreadID +// +// Return Value: +// TRUE if the operation is successful. +// FALSE if it fails +// +//---------------------------------------------------------------------------- +BOOL OutOfProcessExceptionEventGetProcessIdAndThreadId(HANDLE hProcess, HANDLE hThread, DWORD * pPId, DWORD * pThreadId) +{ + _ASSERTE((pPId != NULL) && (pThreadId != NULL)); + +#ifdef FEATURE_PAL + // UNIXTODO: mikem 1/13/15 Need appropriate PAL functions for getting ids + *pPId = (DWORD)hProcess; + *pThreadId = (DWORD)hThread; +#else +#if !defined(FEATURE_CORESYSTEM) + HMODULE hKernel32 = WszGetModuleHandle(W("kernel32.dll")); +#else + HMODULE hKernel32 = WszGetModuleHandle(W("api-ms-win-core-processthreads-l1-1-1.dll")); +#endif + if (hKernel32 == NULL) + { + return FALSE; + } + + typedef WINBASEAPI DWORD (WINAPI GET_PROCESSID_OF_THREAD)(HANDLE); + GET_PROCESSID_OF_THREAD * pGetProcessIdOfThread; + + typedef WINBASEAPI DWORD (WINAPI GET_THREADID)(HANDLE); + GET_THREADID * pGetThreadId; + + pGetProcessIdOfThread = (GET_PROCESSID_OF_THREAD *)GetProcAddress(hKernel32, "GetProcessIdOfThread"); + pGetThreadId = (GET_THREADID *)GetProcAddress(hKernel32, "GetThreadId"); + + // OOP callbacks are used on Win7 or later. We should have having below two APIs available. + _ASSERTE((pGetProcessIdOfThread != NULL) && (pGetThreadId != NULL)); + if ((pGetProcessIdOfThread == NULL) || (pGetThreadId == NULL)) + { + return FALSE; + } + + *pPId = (*pGetProcessIdOfThread)(hThread); + *pThreadId = (*pGetThreadId)(hThread); +#endif // FEATURE_PAL + return TRUE; +} + +// WER_RUNTIME_EXCEPTION_INFORMATION will be available from Win7 SDK once Win7 SDK is released. +#if !defined(WER_RUNTIME_EXCEPTION_INFORMATION) +typedef struct _WER_RUNTIME_EXCEPTION_INFORMATION +{ + DWORD dwSize; + HANDLE hProcess; + HANDLE hThread; + EXCEPTION_RECORD exceptionRecord; + CONTEXT context; +} WER_RUNTIME_EXCEPTION_INFORMATION, * PWER_RUNTIME_EXCEPTION_INFORMATION; +#endif // !defined(WER_RUNTIME_EXCEPTION_INFORMATION) + + +#ifndef FEATURE_PAL + +//---------------------------------------------------------------------------- +// +// OutOfProcessExceptionEventGetWatsonBucket - retrieve Watson buckets if it is a managed exception +// +// Arguments: +// pContext - the context passed at helper module registration +// pExceptionInformation - structure that contains information about the crash +// pGM - pointer to the space to store retrieved Watson buckets +// +// Return Value: +// S_OK if the operation is successful. +// or S_FALSE if it is not a managed exception or Watson buckets cannot be found +// else detailed error code. +// +//---------------------------------------------------------------------------- +STDAPI OutOfProcessExceptionEventGetWatsonBucket(__in PDWORD pContext, + __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation, + __out GenericModeBlock * pGMB) +{ + HANDLE hProcess = pExceptionInformation->hProcess; + HANDLE hThread = pExceptionInformation->hThread; + DWORD PId, ThreadId; + + if (!OutOfProcessExceptionEventGetProcessIdAndThreadId(hProcess, hThread, &PId, &ThreadId)) + { + return E_FAIL; + } + + CLRDATA_ADDRESS baseAddressOfRuntime = (CLRDATA_ADDRESS)pContext; + NewHolder<LiveProcDataTarget> dataTarget(NULL); + + dataTarget = new (nothrow) LiveProcDataTarget(hProcess, PId, baseAddressOfRuntime); + if (dataTarget == NULL) + { + return E_OUTOFMEMORY; + } + + NewHolder<ClrDataAccess> pClrDataAccess(NULL); + + HRESULT hr = CLRDataAccessCreateInstance(dataTarget, &pClrDataAccess); + if (hr != S_OK) + { + if (hr == S_FALSE) + { + return E_FAIL; + } + else + { + return hr; + } + } + + if (!pClrDataAccess->IsExceptionFromManagedCode(&pExceptionInformation->exceptionRecord)) + { + return S_FALSE; + } + + return pClrDataAccess->GetWatsonBuckets(ThreadId, pGMB); +} + +//---------------------------------------------------------------------------- +// +// OutOfProcessExceptionEventCallback - claim the ownership of this event if current +// runtime threw the unhandled exception +// +// Arguments: +// pContext - the context passed at helper module registration +// pExceptionInformation - structure that contains information about the crash +// pbOwnershipClaimed - output parameter for claiming the ownership of this event +// pwszEventName - name of the event. If this is NULL, pchSize cannot be NULL. +// This parameter is valid only if * pbOwnershipClaimed is TRUE. +// pchSize - the size of the buffer pointed by pwszEventName +// pdwSignatureCount - the count of signature parameters. Valid values range from +// 0 to 10. If the value returned is greater than 10, only the +// 1st 10 parameters are used for bucketing parameters. This +// parameter is valid only if * pbOwnershipClaimed is TRUE. +// +// Return Value: +// S_OK on success, else detailed error code. +// +// Note: +// This is the 1st function that is called into by WER. This API through its out +// parameters, tells WER as to whether or not it is claiming the crash. If it does +// claim the crash, WER uses the event name specified in the string pointed to by +// pwszEventName for error reporting. WER then proceed to call the +// OutOfProcessExceptionEventSignatureCallback to get the bucketing parameters from +// the helper dll. +// +// This function follows the multiple call paradigms. WER may call into this function +// with *pwszEventName pointer set to NULL. This is to indicate to the function, that +// WER wants to know the buffer size needed by the function to populate the string +// into the buffer. The function should return E_INSUFFICIENTBUFFER with the needed +// buffer size in *pchSize. WER shall then allocate a buffer of size *pchSize for +// pwszEventName and then call this function again at which point the function should +// populate the string and return S_OK. +// +// Note that *pdOwnershipClaimed should be set to TRUE everytime this function is called +// for the helper dll to claim ownership of bucketing. +// +// The Win7 WER spec is at +// http://windows/windows7/docs/COSD%20Documents/Fundamentals/Feedback%20Services%20and%20Platforms/WER-CLR%20Integration%20Dev%20Spec.docx +// +// !!!READ THIS!!! +// Since this is called by external modules it's important that we don't let any exceptions leak out (see Win8 95224). +// +//---------------------------------------------------------------------------- +STDAPI OutOfProcessExceptionEventCallback(__in PDWORD pContext, + __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation, + __out BOOL * pbOwnershipClaimed, + __out_ecount(*pchSize) PWSTR pwszEventName, + __inout PDWORD pchSize, + __out PDWORD pdwSignatureCount) +{ + SUPPORTS_DAC_HOST_ONLY; + + if ((pContext == NULL) || + (pExceptionInformation == NULL) || + (pExceptionInformation->dwSize < sizeof(WER_RUNTIME_EXCEPTION_INFORMATION)) || + (pbOwnershipClaimed == NULL) || + (pchSize == NULL) || + (pdwSignatureCount == NULL)) + { + return E_INVALIDARG; + } + + *pbOwnershipClaimed = FALSE; + + GenericModeBlock gmb; + HRESULT hr = E_FAIL; + + EX_TRY + { + // get Watson buckets if it is a managed exception + hr = OutOfProcessExceptionEventGetWatsonBucket(pContext, pExceptionInformation, &gmb); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + // S_FALSE means either it is not a managed exception or we do not have Watson buckets. + // Since we have set pbOwnershipClaimed to FALSE, we return S_OK to WER. + if (hr == S_FALSE) + { + hr = S_OK; + } + + return hr; + } + + if ((pwszEventName == NULL) || (*pchSize <= wcslen(gmb.wzEventTypeName))) + { + *pchSize = static_cast<DWORD>(wcslen(gmb.wzEventTypeName)) + 1; + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + // copy custom event name + wcscpy_s(pwszEventName, *pchSize, gmb.wzEventTypeName); + *pdwSignatureCount = GetCountBucketParamsForEvent(gmb.wzEventTypeName); + *pbOwnershipClaimed = TRUE; + + return S_OK; +} + + +//---------------------------------------------------------------------------- +// +// OutOfProcessExceptionEventCallback - provide custom Watson buckets +// +// Arguments: +// pContext - the context passed at helper module registration +// pExceptionInformation - structure that contains information about the crash +// dwIndex - the index of the bucketing parameter being requested. Valid values are +// from 0 to 9 +// pwszName - pointer to the name of the bucketing parameter +// pchName - pointer to character count of the pwszName buffer. If pwszName points to +// null, *pchName represents the buffer size (represented in number of characters) +// needed to populate the name in pwszName. +// pwszValue - pointer to the value of the pwszName bucketing parameter +// pchValue - pointer to the character count of the pwszValue buffer. If pwszValue points +// to null, *pchValue represents the buffer size (represented in number of +// characters) needed to populate the value in pwszValue. +// +// Return Value: +// S_OK on success, else detailed error code. +// +// Note: +// This function is called by WER only if the call to OutOfProcessExceptionEventCallback() +// was successful and the value of *pbOwnershipClaimed was TRUE. This function is called +// pdwSignatureCount times to collect the bucketing parameters from the helper dll. +// +// This function also follows the multiple call paradigm as described for the +// OutOfProcessExceptionEventCallback() function. The buffer sizes needed for +// this function are of the pwszName and pwszValue buffers. +// +// !!!READ THIS!!! +// Since this is called by external modules it's important that we don't let any exceptions leak out (see Win8 95224). +// +//---------------------------------------------------------------------------- +STDAPI OutOfProcessExceptionEventSignatureCallback(__in PDWORD pContext, + __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation, + __in DWORD dwIndex, + __out_ecount(*pchName) PWSTR pwszName, + __inout PDWORD pchName, + __out_ecount(*pchValue) PWSTR pwszValue, + __inout PDWORD pchValue) +{ + SUPPORTS_DAC_HOST_ONLY; + + if ((pContext == NULL) || + (pExceptionInformation == NULL) || + (pExceptionInformation->dwSize < sizeof(WER_RUNTIME_EXCEPTION_INFORMATION)) || + (pchName == NULL) || + (pchValue == NULL)) + { + return E_INVALIDARG; + } + + if ((pwszName == NULL) || (*pchName == 0)) + { + *pchName = 1; + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + GenericModeBlock gmb; + const PWSTR pwszBucketValues[] = {gmb.wzP1, + gmb.wzP2, + gmb.wzP3, + gmb.wzP4, + gmb.wzP5, + gmb.wzP6, + gmb.wzP7, + gmb.wzP8, + gmb.wzP9, + gmb.wzP10}; + + HRESULT hr = E_FAIL; + + EX_TRY + { + // get Watson buckets if it is a managed exception + hr = OutOfProcessExceptionEventGetWatsonBucket(pContext, pExceptionInformation, &gmb); + } + EX_CATCH_HRESULT(hr); + +#ifndef FEATURE_WINDOWSPHONE + // we can't assert this on phone as it's possible for the OS to kill + // the faulting process before WER crash reporting has completed. + _ASSERTE(hr == S_OK); +#else + _ASSERTE(hr == S_OK || hr == CORDBG_E_READVIRTUAL_FAILURE); +#endif + if (hr != S_OK) + { + // S_FALSE means either it is not a managed exception or we do not have Watson buckets. + // Either case is a logic error becuase this function is called by WER only if the call + // to OutOfProcessExceptionEventCallback() was successful and the value of + // *pbOwnershipClaimed was TRUE. + if (hr == S_FALSE) + { + hr = E_FAIL; + } + + return hr; + } + + DWORD paramCount = GetCountBucketParamsForEvent(gmb.wzEventTypeName); + + if (dwIndex >= paramCount) + { + _ASSERTE(!"dwIndex is out of range"); + return E_INVALIDARG; + } + + // Return pwszName as an emptry string to let WER use localized version of "Parameter n" + *pwszName = W('\0'); + + if ((pwszValue == NULL) || (*pchValue <= wcslen(pwszBucketValues[dwIndex]))) + { + *pchValue = static_cast<DWORD>(wcslen(pwszBucketValues[dwIndex]))+ 1; + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + // copy custom Watson bucket value + wcscpy_s(pwszValue, *pchValue, pwszBucketValues[dwIndex]); + + return S_OK; +} + +#endif // FEATURE_PAL + +//---------------------------------------------------------------------------- +// +// OutOfProcessExceptionEventCallback - provide custom debugger launch string +// +// Arguments: +// pContext - the context passed at helper module registration +// pExceptionInformation - structure that contains information about the crash +// pbCustomDebuggerNeeded - pointer to a BOOL. If this BOOL is set to TRUE, then +// a custom debugger launch option is needed by the +// process. In that case, the subsequent parameters will +// be meaningfully used. If this is FALSE, the subsequent +// parameters will be ignored. +// pwszDebuggerLaunch - pointer to a string that will be used to launch the debugger, +// if the debugger is launched. The value of this string overrides +// the default debugger launch string used by WER. +// pchSize - pointer to the character count of the pwszDebuggerLaunch buffer. If +// pwszDebuggerLaunch points to null, *pchSize represents the buffer size +// (represented in number of characters) needed to populate the debugger +// launch string in pwszDebuggerLaunch. +// pbAutoLaunchDebugger - pointer to a BOOL. If this BOOL is set to TRUE, WER will +// directly launch the debugger. If set to FALSE, WER will show +// the debug option to the user in the WER UI. +// +// Return Value: +// S_OK on success, else detailed error code. +// +// Note: +// This function is called into by WER only if the call to OutOfProcessExceptionEventCallback() +// was successful and the value of *pbOwnershipClaimed was TRUE. This function allows the helper +// dll to customize the debugger launch options including the launch string. +// +// This function also follows the multiple call paradigm as described for the +// OutOfProcessExceptionEventCallback() function. The buffer sizes needed for +// this function are of the pwszName and pwszValue buffers. +// +//---------------------------------------------------------------------------- +STDAPI OutOfProcessExceptionEventDebuggerLaunchCallback(__in PDWORD pContext, + __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation, + __out BOOL * pbCustomDebuggerNeeded, + __out_ecount_opt(*pchSize) PWSTR pwszDebuggerLaunch, + __inout PDWORD pchSize, + __out BOOL * pbAutoLaunchDebugger) +{ + SUPPORTS_DAC_HOST_ONLY; + + if ((pContext == NULL) || + (pExceptionInformation == NULL) || + (pExceptionInformation->dwSize < sizeof(WER_RUNTIME_EXCEPTION_INFORMATION)) || + (pbCustomDebuggerNeeded == NULL) || + (pwszDebuggerLaunch == NULL) || + (pchSize == NULL) || + (pbAutoLaunchDebugger == NULL)) + { + return E_INVALIDARG; + } + + // Starting from CLRv4 managed debugger string and setting are unified with native debuggers. + // There is no need to provide custom debugger string for WER. + *pbCustomDebuggerNeeded = FALSE; + + return S_OK; +} + +// DacHandleEnum + +#include "handletablepriv.h" +#include "comcallablewrapper.h" + +DacHandleWalker::DacHandleWalker() + : mDac(0), m_instanceAge(0), mMap(0), mIndex(0), + mTypeMask(0), mGenerationFilter(-1), mChunkIndex(0), mCurr(0), + mIteratorIndex(0) +{ + SUPPORTS_DAC; +} + +DacHandleWalker::~DacHandleWalker() +{ + SUPPORTS_DAC; + + HandleChunkHead *curr = mHead.Next; + + while (curr) + { + HandleChunkHead *tmp = curr; + curr = curr->Next; + delete tmp; + } +} + +HRESULT DacHandleWalker::Init(ClrDataAccess *dac, UINT types[], UINT typeCount) +{ + SUPPORTS_DAC; + + if (dac == NULL || types == NULL) + return E_POINTER; + + mDac = dac; + m_instanceAge = dac->m_instanceAge; + + return Init(BuildTypemask(types, typeCount)); +} + +HRESULT DacHandleWalker::Init(ClrDataAccess *dac, UINT types[], UINT typeCount, int gen) +{ + SUPPORTS_DAC; + + if (gen < 0 || gen > (int)GCHeap::GetMaxGeneration()) + return E_INVALIDARG; + + mGenerationFilter = gen; + + return Init(dac, types, typeCount); +} + +HRESULT DacHandleWalker::Init(UINT32 typemask) +{ + SUPPORTS_DAC; + + mMap = &g_HandleTableMap; + mTypeMask = typemask; + + return S_OK; +} + +UINT32 DacHandleWalker::BuildTypemask(UINT types[], UINT typeCount) +{ + SUPPORTS_DAC; + + UINT32 mask = 0; + + for (UINT i = 0; i < typeCount; ++i) + { + _ASSERTE(types[i] < 32); + mask |= (1 << types[i]); + } + + return mask; +} + +HRESULT DacHandleWalker::Next(unsigned int celt, + SOSHandleData handles[], + unsigned int *pceltFetched) +{ + SUPPORTS_DAC; + + if (handles == NULL || pceltFetched == NULL) + return E_POINTER; + + SOSHelperEnter(); + + hr = DoHandleWalk<SOSHandleData, unsigned int, DacHandleWalker::EnumCallbackSOS>(celt, handles, pceltFetched); + + SOSHelperLeave(); + + return hr; +} + +bool DacHandleWalker::FetchMoreHandles(HANDLESCANPROC callback) +{ + SUPPORTS_DAC; + + // The table slots are based on the number of GC heaps in the process. + int max_slots = 1; + +#ifdef FEATURE_SVR_GC + if (GCHeap::IsServerHeap()) + max_slots = GCHeapCount(); +#endif // FEATURE_SVR_GC + + // Reset the Count on all cached chunks. We reuse chunks after allocating + // them, and the count is the only thing which needs resetting. + for (HandleChunkHead *curr = &mHead; curr; curr = curr->Next) + curr->Count = 0; + + DacHandleWalkerParam param(&mHead); + + do + { + // Have we advanced past the end of the current bucket? + if (mMap && mIndex >= INITIAL_HANDLE_TABLE_ARRAY_SIZE) + { + mIndex = 0; + mMap = mMap->pNext; + } + + // Have we walked the entire handle table map? + if (mMap == NULL) + { + mCurr = NULL; + return false; + } + + if (mMap->pBuckets[mIndex] != NULL) + { + for (int i = 0; i < max_slots; ++i) + { + HHANDLETABLE hTable = mMap->pBuckets[mIndex]->pTable[i]; + if (hTable) + { + // Yikes! The handle table callbacks don't produce the handle type or + // the AppDomain that we need, and it's too difficult to propogate out + // these things (especially the type) without worrying about performance + // implications for the GC. Instead we'll have the callback walk each + // type individually. There are only a few handle types, and the handle + // table has a fast-path for only walking a single type anyway. + UINT32 handleType = 0; + for (UINT32 mask = mTypeMask; mask; mask >>= 1, handleType++) + { + if (mask & 1) + { + HandleTable *pTable = (HandleTable *)hTable; + PTR_AppDomain pDomain = SystemDomain::GetAppDomainAtIndex(pTable->uADIndex); + param.AppDomain = TO_CDADDR(pDomain.GetAddr()); + param.Type = handleType; + + // Either enumerate the handles regularly, or walk the handle + // table as the GC does if a generation filter was requested. + if (mGenerationFilter != -1) + HndScanHandlesForGC(hTable, callback, + (LPARAM)¶m, 0, + &handleType, 1, + mGenerationFilter, GCHeap::GetMaxGeneration(), 0); + else + HndEnumHandles(hTable, &handleType, 1, callback, (LPARAM)¶m, 0, FALSE); + } + } + } + } + } + + // Stop looping as soon as we have found data. We also stop if we have a failed HRESULT during + // the callback (this should indicate OOM). + mIndex++; + } while (mHead.Count == 0 && SUCCEEDED(param.Result)); + + mCurr = mHead.Next; + return true; +} + + +HRESULT DacHandleWalker::Skip(unsigned int celt) +{ + return E_NOTIMPL; +} + +HRESULT DacHandleWalker::Reset() +{ + return E_NOTIMPL; +} + +HRESULT DacHandleWalker::GetCount(unsigned int *pcelt) +{ + return E_NOTIMPL; +} + + +void DacHandleWalker::GetRefCountedHandleInfo( + OBJECTREF oref, unsigned int uType, + unsigned int *pRefCount, unsigned int *pJupiterRefCount, BOOL *pIsPegged, BOOL *pIsStrong) +{ + SUPPORTS_DAC; + +#ifdef FEATURE_COMINTEROP + if (uType == HNDTYPE_REFCOUNTED) + { + // get refcount from the CCW + PTR_ComCallWrapper pWrap = ComCallWrapper::GetWrapperForObject(oref); + if (pWrap != NULL) + { + if (pRefCount) + *pRefCount = (unsigned int)pWrap->GetRefCount(); + + if (pJupiterRefCount) + *pJupiterRefCount = (unsigned int)pWrap->GetJupiterRefCount(); + + if (pIsPegged) + *pIsPegged = pWrap->IsConsideredPegged(); + + if (pIsStrong) + *pIsStrong = pWrap->IsWrapperActive(); + + return; + } + } +#endif // FEATURE_COMINTEROP + + if (pRefCount) + *pRefCount = 0; + + if (pJupiterRefCount) + *pJupiterRefCount = 0; + + if (pIsPegged) + *pIsPegged = FALSE; + + if (pIsStrong) + *pIsStrong = FALSE; +} + +void CALLBACK DacHandleWalker::EnumCallbackSOS(PTR_UNCHECKED_OBJECTREF handle, uintptr_t *pExtraInfo, uintptr_t param1, uintptr_t param2) +{ + SUPPORTS_DAC; + + DacHandleWalkerParam *param = (DacHandleWalkerParam *)param1; + HandleChunkHead *curr = param->Curr; + + // If we failed on a previous call (OOM) don't keep trying to allocate, it's not going to work. + if (FAILED(param->Result)) + return; + + // We've moved past the size of the current chunk. We'll allocate a new chunk + // and stuff the handles there. These are cleaned up by the destructor + if (curr->Count >= (curr->Size/sizeof(SOSHandleData))) + { + if (curr->Next == NULL) + { + HandleChunk *next = new (nothrow) HandleChunk; + if (next != NULL) + { + curr->Next = next; + } + else + { + param->Result = E_OUTOFMEMORY; + return; + } + } + + curr = param->Curr = param->Curr->Next; + } + + // Fill the current handle. + SOSHandleData *dataArray = (SOSHandleData*)curr->pData; + SOSHandleData &data = dataArray[curr->Count++]; + + data.Handle = TO_CDADDR(handle.GetAddr()); + data.Type = param->Type; + if (param->Type == HNDTYPE_DEPENDENT) + data.Secondary = GetDependentHandleSecondary(handle.GetAddr()).GetAddr(); +#ifdef FEATURE_COMINTEROP + else if (param->Type == HNDTYPE_WEAK_WINRT) + data.Secondary = HndGetHandleExtraInfo(handle.GetAddr()); +#endif // FEATURE_COMINTEROP + else + data.Secondary = 0; + data.AppDomain = param->AppDomain; + GetRefCountedHandleInfo((OBJECTREF)*handle, param->Type, &data.RefCount, &data.JupiterRefCount, &data.IsPegged, &data.StrongReference); + data.StrongReference |= (BOOL)IsAlwaysStrongReference(param->Type); +} + +DacStackReferenceWalker::DacStackReferenceWalker(ClrDataAccess *dac, DWORD osThreadID) + : mDac(dac), m_instanceAge(dac ? dac->m_instanceAge : 0), mThread(0), mErrors(0), mEnumerated(false), + mChunkIndex(0), mCurr(0), mIteratorIndex(0) +{ + Thread *curr = NULL; + + for (curr = ThreadStore::GetThreadList(curr); + curr; + curr = ThreadStore::GetThreadList(curr)) + { + if (curr->GetOSThreadId() == osThreadID) + { + mThread = curr; + break; + } + } +} + +DacStackReferenceWalker::~DacStackReferenceWalker() +{ + StackRefChunkHead *curr = mHead.next; + + while (curr) + { + StackRefChunkHead *tmp = curr; + curr = curr->next; + delete tmp; + } +} + +HRESULT DacStackReferenceWalker::Init() +{ + if (!mThread) + return E_INVALIDARG; + return mHeap.Init(); +} + +HRESULT STDMETHODCALLTYPE DacStackReferenceWalker::Skip(unsigned int count) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DacStackReferenceWalker::Reset() +{ + return E_NOTIMPL; +} + +HRESULT DacStackReferenceWalker::GetCount(unsigned int *pCount) +{ + if (!pCount) + return E_POINTER; + + SOSHelperEnter(); + + if (!mEnumerated) + { + // Fill out our data structures. + WalkStack<unsigned int, SOSStackRefData>(0, NULL, DacStackReferenceWalker::GCReportCallbackSOS, DacStackReferenceWalker::GCEnumCallbackSOS); + } + + unsigned int count = 0; + for(StackRefChunkHead *curr = &mHead; curr; curr = curr->next) + count += curr->count; + + *pCount = count; + + SOSHelperLeave(); + return hr; +} + +HRESULT DacStackReferenceWalker::Next(unsigned int count, + SOSStackRefData stackRefs[], + unsigned int *pFetched) +{ + if (stackRefs == NULL || pFetched == NULL) + return E_POINTER; + + SOSHelperEnter(); + + hr = DoStackWalk<unsigned int, SOSStackRefData, + DacStackReferenceWalker::GCReportCallbackSOS, + DacStackReferenceWalker::GCEnumCallbackSOS> + (count, stackRefs, pFetched); + + SOSHelperLeave(); + + return hr; +} + +HRESULT DacStackReferenceWalker::EnumerateErrors(ISOSStackRefErrorEnum **ppEnum) +{ + if (!ppEnum) + return E_POINTER; + + SOSHelperEnter(); + + if (mThread) + { + // Fill out our data structures. + WalkStack<unsigned int, SOSStackRefData>(0, NULL, DacStackReferenceWalker::GCReportCallbackSOS, DacStackReferenceWalker::GCEnumCallbackSOS); + } + + DacStackReferenceErrorEnum *pEnum = new DacStackReferenceErrorEnum(this, mErrors); + hr = pEnum->QueryInterface(__uuidof(ISOSStackRefErrorEnum), (void**)ppEnum); + + SOSHelperLeave(); + return hr; +} + +CLRDATA_ADDRESS DacStackReferenceWalker::ReadPointer(TADDR addr) +{ + ULONG32 bytesRead = 0; + TADDR result = 0; + HRESULT hr = mDac->m_pTarget->ReadVirtual(addr, (BYTE*)&result, sizeof(TADDR), &bytesRead); + + if (FAILED(hr) || (bytesRead != sizeof(TADDR))) + return (CLRDATA_ADDRESS)~0; + + return TO_CDADDR(result); +} + + +void DacStackReferenceWalker::GCEnumCallbackSOS(LPVOID hCallback, OBJECTREF *pObject, uint32_t flags, DacSlotLocation loc) +{ + GCCONTEXT *gcctx = (GCCONTEXT *)hCallback; + DacScanContext *dsc = (DacScanContext*)gcctx->sc; + + // Yuck. The GcInfoDecoder reports a local pointer for registers (as it's reading out of the REGDISPLAY + // in the stack walk), and it reports a TADDR for stack locations. This is architecturally difficulty + // to fix, so we are leaving it for now. + TADDR addr = 0; + TADDR obj = 0; + + if (loc.targetPtr) + { + addr = (TADDR)pObject; + obj = TO_TADDR(dsc->pWalker->ReadPointer((CORDB_ADDRESS)addr)); + } + else + { + obj = pObject->GetAddr(); + } + + if (flags & GC_CALL_INTERIOR) + { + CORDB_ADDRESS fixed_obj = 0; + HRESULT hr = dsc->pWalker->mHeap.ListNearObjects((CORDB_ADDRESS)obj, NULL, &fixed_obj, NULL); + + // If we failed...oh well, SOS won't mind. We'll just report the interior pointer as is. + if (SUCCEEDED(hr)) + obj = TO_TADDR(fixed_obj); + } + + SOSStackRefData *data = dsc->pWalker->GetNextObject<SOSStackRefData>(dsc); + if (data != NULL) + { + // Report where the object and where it was found. + data->HasRegisterInformation = true; + data->Register = loc.reg; + data->Offset = loc.regOffset; + data->Address = TO_CDADDR(addr); + data->Object = TO_CDADDR(obj); + data->Flags = flags; + + // Report the frame that the data came from. + data->StackPointer = TO_CDADDR(dsc->sp); + + if (dsc->pFrame) + { + data->SourceType = SOS_StackSourceFrame; + data->Source = dac_cast<PTR_Frame>(dsc->pFrame).GetAddr(); + } + else + { + data->SourceType = SOS_StackSourceIP; + data->Source = TO_CDADDR(dsc->pc); + } + } +} + + +void DacStackReferenceWalker::GCReportCallbackSOS(PTR_PTR_Object ppObj, ScanContext *sc, uint32_t flags) +{ + DacScanContext *dsc = (DacScanContext*)sc; + CLRDATA_ADDRESS obj = dsc->pWalker->ReadPointer(ppObj.GetAddr()); + + if (flags & GC_CALL_INTERIOR) + { + CORDB_ADDRESS fixed_addr = 0; + HRESULT hr = dsc->pWalker->mHeap.ListNearObjects((CORDB_ADDRESS)obj, NULL, &fixed_addr, NULL); + + // If we failed...oh well, SOS won't mind. We'll just report the interior pointer as is. + if (SUCCEEDED(hr)) + obj = TO_CDADDR(fixed_addr); + } + + SOSStackRefData *data = dsc->pWalker->GetNextObject<SOSStackRefData>(dsc); + if (data != NULL) + { + data->HasRegisterInformation = false; + data->Register = 0; + data->Offset = 0; + data->Address = ppObj.GetAddr(); + data->Object = obj; + data->Flags = flags; + data->StackPointer = TO_CDADDR(dsc->sp); + + if (dsc->pFrame) + { + data->SourceType = SOS_StackSourceFrame; + data->Source = dac_cast<PTR_Frame>(dsc->pFrame).GetAddr(); + } + else + { + data->SourceType = SOS_StackSourceIP; + data->Source = TO_CDADDR(dsc->pc); + } + } +} + +StackWalkAction DacStackReferenceWalker::Callback(CrawlFrame *pCF, VOID *pData) +{ + // + // KEEP IN SYNC WITH GcStackCrawlCallBack in vm\gcscan.cpp + // + + GCCONTEXT *gcctx = (GCCONTEXT*)pData; + DacScanContext *dsc = (DacScanContext*)gcctx->sc; + + MethodDesc *pMD = pCF->GetFunction(); + gcctx->sc->pMD = pMD; + gcctx->sc->pCurrentDomain = pCF->GetAppDomain(); + + PREGDISPLAY pRD = pCF->GetRegisterSet(); + dsc->sp = (TADDR)GetRegdisplaySP(pRD);; + dsc->pc = PCODEToPINSTR(GetControlPC(pRD)); + + ResetPointerHolder<CrawlFrame*> rph(&gcctx->cf); + gcctx->cf = pCF; + + bool fReportGCReferences = true; +#if defined(WIN64EXCEPTIONS) + // On Win64 and ARM, we may have unwound this crawlFrame and thus, shouldn't report the invalid + // references it may contain. + // todo. + fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences(); +#endif // defined(WIN64EXCEPTIONS) + + Frame *pFrame = ((DacScanContext*)gcctx->sc)->pFrame = pCF->GetFrame(); + + EX_TRY + { + if (fReportGCReferences) + { + if (pCF->IsFrameless()) + { + ICodeManager * pCM = pCF->GetCodeManager(); + _ASSERTE(pCM != NULL); + + unsigned flags = pCF->GetCodeManagerFlags(); + + pCM->EnumGcRefs(pCF->GetRegisterSet(), + pCF->GetCodeInfo(), + flags, + dsc->pEnumFunc, + pData); + } + else + { + pFrame->GcScanRoots(gcctx->f, gcctx->sc); + } + } + } + EX_CATCH + { + SOSStackErrorList *err = new SOSStackErrorList; + err->pNext = NULL; + + if (pFrame) + { + err->error.SourceType = SOS_StackSourceFrame; + err->error.Source = dac_cast<PTR_Frame>(pFrame).GetAddr(); + } + else + { + err->error.SourceType = SOS_StackSourceIP; + err->error.Source = TO_CDADDR(dsc->pc); + } + + if (dsc->pWalker->mErrors == NULL) + { + dsc->pWalker->mErrors = err; + } + else + { + // This exception case should be non-existent. It only happens when there is either + // a clr!Frame on the callstack which is not properly dac-ized, or when a call down + // EnumGcRefs causes a data read exception. Since this is so rare, we don't worry + // about making this code very efficient. + SOSStackErrorList *curr = dsc->pWalker->mErrors; + while (curr->pNext) + curr = curr->pNext; + + curr->pNext = err; + } + } + EX_END_CATCH(SwallowAllExceptions) + +#if 0 + // todo + + // If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it + // doesn't get collected and yank the method code out from under us). + + // Be careful to only promote the reference -- we can also be called to relocate the reference and + // that can lead to all sorts of problems since we could be racing for the relocation with the long + // weak handle we recover the reference from. Promoting the reference is enough, the handle in the + // reference will be relocated properly as long as we keep it alive till the end of the collection + // as long as the reference is actually maintained by the long weak handle. + if (pMD) + { + BOOL fMaybeCollectibleMethod = TRUE; + + // If this is a frameless method then the jitmanager can answer the question of whether + // or not this is LCG simply by looking at the heap where the code lives, however there + // is also the prestub case where we need to explicitly look at the MD for stuff that isn't + // ngen'd + if (pCF->IsFrameless() && pMD->IsLCGMethod()) + { + fMaybeCollectibleMethod = ExecutionManager::IsCollectibleMethod(pCF->GetMethodToken()); + } + + if (fMaybeCollectibleMethod && pMD->IsLCGMethod()) + { + PTR_Object obj = OBJECTREFToObject(pMD->AsDynamicMethodDesc()->GetLCGMethodResolver()->GetManagedResolver()); + dsc->pWalker->ReportObject(obj); + } + else + { + if (fMaybeCollectibleMethod) + { + PTR_Object obj = pMD->GetLoaderAllocator()->GetExposedObject(); + dsc->pWalker->ReportObject(obj); + } + + if (fReportGCReferences) + { + GenericParamContextType paramContextType = GENERIC_PARAM_CONTEXT_NONE; + + if (pCF->IsFrameless()) + { + // We need to grab the Context Type here because there are cases where the MethodDesc + // is shared, and thus indicates there should be an instantion argument, but the JIT + // was still allowed to optimize it away and we won't grab it below because we're not + // reporting any references from this frame. + paramContextType = pCF->GetCodeManager()->GetParamContextType(pCF->GetRegisterSet(), pCF->GetCodeInfo()); + } + else + { + if (pMD->RequiresInstMethodDescArg()) + paramContextType = GENERIC_PARAM_CONTEXT_METHODDESC; + else if (pMD->RequiresInstMethodTableArg()) + paramContextType = GENERIC_PARAM_CONTEXT_METHODTABLE; + } + + // Handle the case where the method is a static shared generic method and we need to keep the type of the generic parameters alive + if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC) + { + MethodDesc *pMDReal = dac_cast<PTR_MethodDesc>(pCF->GetParamTypeArg()); + _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); + if (pMDReal != NULL) + { + PTR_Object obj = pMDReal->GetLoaderAllocator()->GetExposedObject(); + dsc->pWalker->ReportObject(obj); + } + } + else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE) + { + MethodTable *pMTReal = dac_cast<PTR_MethodTable>(pCF->GetParamTypeArg()); + _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); + if (pMTReal != NULL) + { + PTR_Object obj = pMTReal->GetLoaderAllocator()->GetExposedObject(); + dsc->pWalker->ReportObject(obj); + } + } + } + } + } +#endif + + return SWA_CONTINUE; +} + + +DacStackReferenceErrorEnum::DacStackReferenceErrorEnum(DacStackReferenceWalker *pEnum, SOSStackErrorList *pErrors) + : mEnum(pEnum), mHead(pErrors), mCurr(pErrors) +{ + _ASSERTE(mEnum); + + if (mHead != NULL) + mEnum->AddRef(); +} + +DacStackReferenceErrorEnum::~DacStackReferenceErrorEnum() +{ + if (mHead) + mEnum->Release(); +} + +HRESULT DacStackReferenceErrorEnum::Skip(unsigned int count) +{ + unsigned int i = 0; + for (i = 0; i < count && mCurr; ++i) + mCurr = mCurr->pNext; + + return i < count ? S_FALSE : S_OK; +} + +HRESULT DacStackReferenceErrorEnum::Reset() +{ + mCurr = mHead; + + return S_OK; +} + +HRESULT DacStackReferenceErrorEnum::GetCount(unsigned int *pCount) +{ + SOSStackErrorList *curr = mHead; + unsigned int count = 0; + + while (curr) + { + curr = curr->pNext; + count++; + } + + *pCount = count; + return S_OK; +} + +HRESULT DacStackReferenceErrorEnum::Next(unsigned int count, SOSStackRefError ref[], unsigned int *pFetched) +{ + if (pFetched == NULL || ref == NULL) + return E_POINTER; + + unsigned int i; + for (i = 0; i < count && mCurr; ++i, mCurr = mCurr->pNext) + ref[i] = mCurr->error; + + *pFetched = i; + return i < count ? S_FALSE : S_OK; +} |