diff options
Diffstat (limited to 'src/dlls/dbgshim/dbgshim.cpp')
-rw-r--r-- | src/dlls/dbgshim/dbgshim.cpp | 1093 |
1 files changed, 1093 insertions, 0 deletions
diff --git a/src/dlls/dbgshim/dbgshim.cpp b/src/dlls/dbgshim/dbgshim.cpp new file mode 100644 index 0000000000..65fe6c3621 --- /dev/null +++ b/src/dlls/dbgshim/dbgshim.cpp @@ -0,0 +1,1093 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// DbgShim.cpp +// +// This contains the APIs for creating a telesto managed-debugging session. These APIs serve to locate an +// mscordbi.dll for a given telesto dll and then instantiate the ICorDebug object. +// +//***************************************************************************** + +#include <winwrap.h> +#include <utilcode.h> +#include <log.h> +#include <tlhelp32.h> +#include <cor.h> +#include <sstring.h> +#include <securityutil.h> + +#include <ex.h> +#include <cordebug.h> // for Version nunmbers +#include <pedecoder.h> +#include <getproductversionnumber.h> +#include <dbgenginemetrics.h> + +#define PSAPI_VERSION 2 +#include <psapi.h> + +#include "dbgshim.h" + +/* + +// Here's a High-level overview of the API usage + +From the debugger: +A debugger calls GetStartupNotificationEvent(pid of debuggee) to get an event, which is signalled when that +process loads a Telesto. The debugger thus waits on that event, and when it's signalled, it can call +EnumerateCLRs / CloseCLREnumeration to get an array of Telestos in the target process (including the one +that was just loaded). +It can then call CreateVersionStringFromModule, CreateDebuggingInterfaceFromVersion to attach to +any or all Telestos of interest. + + +From the debuggee: +When a new Telesto spins up, it checks for the startup event (created via GetStartupNotificationEvent), and if it +exists, it will: +- signal it +- wait on the "Continue" event, thus giving a debugger a chance to attach to the telesto + + +Notes: +- There is no CreateProcess (Launch) case. All Launching is really an "Early-attach case". + +*/ + + +// Contract for public APIs. These must be NOTHROW. +#define PUBLIC_CONTRACT \ + CONTRACTL \ + { \ + NOTHROW; \ + } \ + CONTRACTL_END; \ + +//----------------------------------------------------------------------------- +// Public API. +// +// GetStartupNotificationEvent -- creates a global, named event that is PID- +// qualified (i.e. process global) that is used to notify the debugger of +// any CLR instance startup in the process. +// +// debuggeePID -- process ID of the target process +// phStartupEvent -- out param for the returned event handle +// +//----------------------------------------------------------------------------- +#define StartupNotifyEventNamePrefix W("TelestoStartupEvent_") +const int cchEventNameBufferSize = sizeof(StartupNotifyEventNamePrefix)/sizeof(WCHAR) + 8; // + hex DWORD (8). NULL terminator is included in sizeof(StartupNotifyEventNamePrefix) +HRESULT GetStartupNotificationEvent(DWORD debuggeePID, + __out HANDLE* phStartupEvent) +{ + PUBLIC_CONTRACT; + HRESULT hr; + + if (phStartupEvent == NULL) + return E_INVALIDARG; + + // Note this event name doesn't have a Global prefix, and so debugging across sessions will not work. + WCHAR szEventName[cchEventNameBufferSize]; + swprintf_s(szEventName, cchEventNameBufferSize, StartupNotifyEventNamePrefix W("%08x"), debuggeePID); + + // Determine an appropriate ACL and SECURITY_ATTRIBUTES to apply to this event. We use the same logic + // here as the debugger uses for other events (like the setup-sync-event). Specifically, this does + // the work to ensure a debuggee running as another user, or with a low integrity level can signal + // this event. + PACL pACL = NULL; + SECURITY_ATTRIBUTES * pSA = NULL; + IfFailRet(SecurityUtil::GetACLOfPid(debuggeePID, &pACL)); + SecurityUtil secUtil(pACL); + + HandleHolder hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, debuggeePID); + if (hProcess == NULL) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + IfFailRet(secUtil.Init(hProcess)); + IfFailRet(secUtil.GetSA(&pSA)); + + HANDLE startupEvent = WszCreateEvent(pSA, + FALSE, // false -> auto-reset + FALSE, // false -> initially non-signaled + szEventName); + DWORD dwStatus = GetLastError(); + if (NULL == startupEvent) + { + // if the event already exists, try to open it, otherwise we fail. + + if (ERROR_ALREADY_EXISTS != dwStatus) + return E_FAIL; + + startupEvent = WszOpenEvent(SYNCHRONIZE, FALSE, szEventName); + + if (NULL == startupEvent) + return E_FAIL; + } + + *phStartupEvent = startupEvent; + + return S_OK; +} + +HRESULT GetContinueStartupEvent(DWORD debuggeePID, + LPCWSTR szTelestoFullPath, + __out HANDLE* phContinueStartupEvent); + +//----------------------------------------------------------------------------- +// Public API. +// +// CloseCLREnumeration -- used to free resources allocated by EnumerateCLRs +// +// pHandleArray -- handle array originally returned by EnumerateCLRs +// pStringArray -- string array originally returned by EnumerateCLRs +// dwArrayLength -- array length originally returned by EnumerateCLRs +// +//----------------------------------------------------------------------------- +HRESULT CloseCLREnumeration(HANDLE* pHandleArray, LPWSTR* pStringArray, DWORD dwArrayLength) +{ + PUBLIC_CONTRACT; + + if ((pHandleArray + dwArrayLength) != (HANDLE*)pStringArray) + return E_INVALIDARG; + + // It's possible that EnumerateCLRs found nothing to enumerate, in which case + // pointers and count are zeroed. If a debugger calls this function in that + // case, let's not try to delete [] on NULL. + if (pHandleArray == NULL) + return S_OK; + + for (DWORD i = 0; i < dwArrayLength; i++) + { + HANDLE hTemp = pHandleArray[i]; + if ( (NULL != hTemp) + && (INVALID_HANDLE_VALUE != hTemp)) + { + CloseHandle(hTemp); + } + } + + delete[] pHandleArray; + return S_OK; +} + +// Refer to clr\src\mscoree\mscorwks_ntdef.src. +const WORD kOrdinalForMetrics = 2; + +//----------------------------------------------------------------------------- +// The CLR_ENGINE_METRICS is a static struct in coreclr.dll. It's exported by coreclr.dll at ordinal 2 in +// the export address table. This function returns the CLR_ENGINE_METRICS and the RVA to the continue +// startup event for a coreclr.dll specified by its full path. +// +// Arguments: +// szTelestoFullPath - (in) full path of telesto +// pEngineMetricsOut - (out) filled in based on metrics from target telesto. +// pdwRVAContinueStartupEvent - (out; optional) return the RVA to the continue startup event +// +// Returns: +// Throwss on error. +// +// Notes: +// When VS pops up the attach dialog box, it is actually enumerating all the processes on the machine +// (if the appropiate checkbox is checked) and checking each process to see if a DLL named "coreclr.dll" +// is loaded. If there is one, we will go down this code path, but there is no guarantee that the +// coreclr.dll is ours. A malicious user can be running a process with a bogus coreclr.dll loaded. +// That's why we need to be extra careful reading coreclr.dll in this function. +//----------------------------------------------------------------------------- +void GetTargetCLRMetrics(LPCWSTR szTelestoFullPath, + CLR_ENGINE_METRICS * pEngineMetricsOut, + DWORD * pdwRVAContinueStartupEvent = NULL) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + CONSISTENCY_CHECK(szTelestoFullPath != NULL); + CONSISTENCY_CHECK(pEngineMetricsOut != NULL); + + HRESULT hr = S_OK; + + HandleHolder hCoreClrFile = WszCreateFile(szTelestoFullPath, + GENERIC_READ, + FILE_SHARE_READ, + NULL, // default security descriptor + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hCoreClrFile == INVALID_HANDLE_VALUE) + { + ThrowLastError(); + } + + DWORD cbFileHigh = 0; + DWORD cbFileLow = GetFileSize(hCoreClrFile, &cbFileHigh); + if (cbFileLow == INVALID_FILE_SIZE) + { + ThrowLastError(); + } + + // A maximum size of 100 MB should be more than enough for coreclr.dll. + if ((cbFileHigh != 0) || (cbFileLow > 0x6400000) || (cbFileLow == 0)) + { + ThrowHR(E_FAIL); + } + + HandleHolder hCoreClrMap = WszCreateFileMapping(hCoreClrFile, NULL, PAGE_READONLY, cbFileHigh, cbFileLow, NULL); + if (hCoreClrMap == NULL) + { + ThrowLastError(); + } + + MapViewHolder hCoreClrMapView = MapViewOfFile(hCoreClrMap, FILE_MAP_READ, 0, 0, 0); + if (hCoreClrMapView == NULL) + { + ThrowLastError(); + } + + // At this point we have read the file into the process, but be careful because it is flat, i.e. not mapped. + // We need to translate RVAs into file offsets, but fortunately PEDecoder can do all of that for us. + PEDecoder pedecoder(hCoreClrMapView, (COUNT_T)cbFileLow); + + // Check the NT headers. + if (!pedecoder.CheckNTFormat()) + { + ThrowHR(E_FAIL); + } + + // At this point we can safely read anything in the NT headers. + + if (!pedecoder.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT) || + !pedecoder.CheckDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT)) + { + ThrowHR(E_FAIL); + } + IMAGE_DATA_DIRECTORY * pExportDirectoryEntry = pedecoder.GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // At this point we can safely read the IMAGE_DATA_DIRECTORY of the export directory. + + if (!pedecoder.CheckDirectory(pExportDirectoryEntry)) + { + ThrowHR(E_FAIL); + } + IMAGE_EXPORT_DIRECTORY * pExportDir = + reinterpret_cast<IMAGE_EXPORT_DIRECTORY *>(pedecoder.GetDirectoryData(pExportDirectoryEntry)); + + // At this point we have checked that everything in the export directory is readable. + + // Check to make sure the ordinal we have fits in the table in the export directory. + // The "base" here is like the starting index of the arrays in the export directory. + if ((pExportDir->Base > kOrdinalForMetrics) || + (pExportDir->NumberOfFunctions < (kOrdinalForMetrics - pExportDir->Base))) + { + ThrowHR(E_FAIL); + } + DWORD dwRealIndex = kOrdinalForMetrics - pExportDir->Base; + + // Check that we can read the RVA at the element (specified by the ordinal) in the export address table. + // Then read the RVA to the CLR_ENGINE_METRICS. + if (!pedecoder.CheckRva(pExportDir->AddressOfFunctions, (dwRealIndex + 1) * sizeof(DWORD))) + { + ThrowHR(E_FAIL); + } + DWORD rvaMetrics = *reinterpret_cast<DWORD *>( + pedecoder.GetRvaData(pExportDir->AddressOfFunctions + dwRealIndex * sizeof(DWORD))); + + // Make sure we can safely read the CLR_ENGINE_METRICS at the RVA we have retrieved. + if (!pedecoder.CheckRva(rvaMetrics, sizeof(*pEngineMetricsOut))) + { + ThrowHR(E_FAIL); + } + + // Finally, copy the CLR_ENGINE_METRICS into the output buffer. + CLR_ENGINE_METRICS * pMetricsInFile = reinterpret_cast<CLR_ENGINE_METRICS *>(pedecoder.GetRvaData(rvaMetrics)); + *pEngineMetricsOut = *pMetricsInFile; + + // At this point, we have retrieved the CLR_ENGINE_METRICS from the target process and + // stored it in output buffer. + if (pEngineMetricsOut->cbSize != sizeof(*pEngineMetricsOut)) + { + ThrowHR(E_INVALIDARG); + } + + if (pdwRVAContinueStartupEvent != NULL) + { + // Note that the pointer stored in the CLR_ENGINE_METRICS is assuming that the DLL is loaded at its + // preferred base address. We need to translate that to an RVA. + if (((SIZE_T)pEngineMetricsOut->phContinueStartupEvent < (SIZE_T)pedecoder.GetPreferredBase()) || + ((SIZE_T)pEngineMetricsOut->phContinueStartupEvent > + ((SIZE_T)pedecoder.GetPreferredBase() + pedecoder.GetVirtualSize()))) + { + ThrowHR(E_FAIL); + } + + DWORD rvaContinueStartupEvent = + (DWORD)((SIZE_T)pEngineMetricsOut->phContinueStartupEvent - (SIZE_T)pedecoder.GetPreferredBase()); + + // We can't use CheckRva() here because for unmapped files it actually checks the RVA against the file + // size as well. We have already checked the RVA above. Now just check that the entire HANDLE + // falls in the loaded image. + if ((rvaContinueStartupEvent + sizeof(HANDLE)) > pedecoder.GetVirtualSize()) + { + ThrowHR(E_FAIL); + } + + *pdwRVAContinueStartupEvent = rvaContinueStartupEvent; + } + + // Holder will call FreeLibrary() +} + +// Returns true iff the module represents CoreClr. +bool IsCoreClr(const WCHAR* pModulePath) +{ + _ASSERTE(pModulePath != NULL); + + //strip off everything up to and including the last slash in the path to get name + const WCHAR* pModuleName = pModulePath; + while(wcschr(pModuleName, W('\\')) != NULL) + { + pModuleName = wcschr(pModuleName, W('\\')); + pModuleName++; // pass the slash + } + + // MAIN_CLR_MODULE_NAME_W gets changed for desktop builds, so we directly code against the CoreClr name. + return _wcsicmp(pModuleName, MAKEDLLNAME_W(W("coreclr"))) == 0; +} + +// Returns true iff the module sent is named CoreClr.dll and has the metrics expected in it's PE header. +bool IsCoreClrWithGoodHeader(HANDLE hProcess, HMODULE hModule) +{ + HRESULT hr = S_OK; + + WCHAR modulePath[MAX_PATH]; + modulePath[0] = W('\0'); + if(0 == GetModuleFileNameEx(hProcess, hModule, modulePath, MAX_PATH)) + { + return false; + } + else + { + modulePath[MAX_PATH-1] = 0; // on older OS'es this doesn't get null terminated automatically on truncation + } + + if (IsCoreClr(modulePath)) + { + // We don't care about the particular error returned, only that + // what we tried wasn't a 'real' coreclr.dll. + EX_TRY + { + CLR_ENGINE_METRICS metricsStruct; + GetTargetCLRMetrics(modulePath, &metricsStruct); // throws + + // If we got this far, then we think it's a good one. + } + EX_CATCH_HRESULT(hr); + return (hr == S_OK); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Public API. +// +// EnumerateCLRs -- returns an array of full paths to each coreclr.dll in the +// target process. Also returns a corresponding array of continue events +// that *MUST* be signaled by the caller in order to allow the CLRs in the +// target process to proceed. +// +// debuggeePID -- process ID of the target process +// ppHandleArrayOut -- out parameter in which an array of handles is returned. +// the length of this array is returned by the pdwArrayLengthOut out param +// ppStringArrayOut -- out parameter in which an array of full paths to each +// coreclr.dll in the process is returned. The length of this array is the +// same as the handle array and is returned by the pdwArrayLengthOut param +// pdwArrayLengthOut -- out param in which the length of the two returned arrays +// are returned. +// +// Notes: +// Callers use code:CloseCLREnumeration to free the returned arrays. +//----------------------------------------------------------------------------- +HRESULT EnumerateCLRs(DWORD debuggeePID, + __out HANDLE** ppHandleArrayOut, + __out LPWSTR** ppStringArrayOut, + __out DWORD* pdwArrayLengthOut) +{ + PUBLIC_CONTRACT; + + // All out params must be non-NULL. + if ((ppHandleArrayOut == NULL) || (ppStringArrayOut == NULL) || (pdwArrayLengthOut == NULL)) + return E_INVALIDARG; + + HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID); + if (NULL == hProcess) + ThrowHR(E_FAIL); + + // These shouldn't be freed + HMODULE modules[1000]; + DWORD cbNeeded; + if(!EnumProcessModules(hProcess, modules, sizeof(modules), &cbNeeded)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + // + // count the number of coreclr.dll entries + // + DWORD count = 0; + DWORD countModules = cbNeeded/sizeof(HMODULE); + for(DWORD i = 0; i < countModules; i++) + { + if (IsCoreClrWithGoodHeader(hProcess, modules[i])) + { + count++; + } + } + + + // If we didn't find anything, no point in continuing. + if (count == 0) + { + *ppHandleArrayOut = NULL; + *ppStringArrayOut = NULL; + *pdwArrayLengthOut = 0; + + return S_OK; + } + + size_t cbEventArrayData = sizeof(HANDLE) * count; // event array data + size_t cbStringArrayData = sizeof(LPWSTR) * count; // string array data + size_t cbStringData = sizeof(WCHAR) * count * MAX_PATH; // strings data + size_t cbBuffer = cbEventArrayData + cbStringArrayData + cbStringData; + + BYTE* pOutBuffer = new (nothrow) BYTE[cbBuffer]; + if (NULL == pOutBuffer) + return E_OUTOFMEMORY; + + ZeroMemory(pOutBuffer, cbBuffer); + + HANDLE* pEventArray = (HANDLE*) &pOutBuffer[0]; + LPWSTR* pStringArray = (LPWSTR*) &pOutBuffer[cbEventArrayData]; + WCHAR* pStringData = (WCHAR*) &pOutBuffer[cbEventArrayData + cbStringArrayData]; + + + // There's no guarantee that another coreclr hasn't loaded already anyhow, + // so if we get the corner case that the second time through we enumerate + // more coreclrs, just ignore the extras. + // This mismatch could happen when + // a) take module shapshot + // b) underlying file is opened for exclusive access/deleted/moved/ACL'd etc so we can't open it + // c) count is determined + // d) file is closed/copied/moved/ACL'd etc so we can find/open it again + // e) this loop runs + // Thus the loop checks idx < count + + DWORD idx = 0; + for(DWORD i = 0; i < countModules && idx < count; i++) + { + if (IsCoreClrWithGoodHeader(hProcess, modules[i])) + { + // fill in path + pStringArray[idx] = &pStringData[idx * MAX_PATH]; + GetModuleFileNameEx(hProcess, modules[i], pStringArray[idx], MAX_PATH); + + // fill in event handle -- if GetContinueStartupEvent fails, it will still return + // INVALID_HANDLE_VALUE in hContinueStartupEvent, which is what we want. we don't + // want to bail out of the enumeration altogether if we can't get an event from + // one telesto. + + HANDLE hContinueStartupEvent = INVALID_HANDLE_VALUE; + HRESULT hr = GetContinueStartupEvent(debuggeePID, pStringArray[idx], &hContinueStartupEvent); + _ASSERTE(SUCCEEDED(hr) == (hContinueStartupEvent != INVALID_HANDLE_VALUE)); + + pEventArray[idx] = hContinueStartupEvent; + + idx++; + } + } + + // Patch things up so CloseCLREnumeration() can still have it's + // pointer arithmatic checks succeed, and the user doesn't see a 'dead' entry. + // Specifically, it's expected that pEventArray and pStringArray point to the + // same contiguous chunk of memory so that pStringArray == pEventArray[*pdwArrayLengthOut]. + // This is expected to be a very rare case. + if (idx < count) + { + // Move the string pointers back. + LPWSTR* pSATemp = (LPWSTR*)&pOutBuffer[sizeof(HANDLE)*idx]; + for (DWORD i = 0; i < idx; i++) + { + pSATemp[i] = pStringArray[i]; + } + + // Fix up string array pointer. + pStringArray = (LPWSTR*)&pOutBuffer[sizeof(HANDLE)*idx]; + + // Strings themselves don't need moved. + } + + *ppHandleArrayOut = pEventArray; + *ppStringArrayOut = pStringArray; + *pdwArrayLengthOut = idx; + + return S_OK; +} + +//----------------------------------------------------------------------------- +// Get the base address of a module from the remote process. +// +// Returns: +// - On success, base address (in remote process) of mscoree, +// - NULL if the module is not loaded. +// - else Throws. *ppBaseAddress = NULL +//----------------------------------------------------------------------------- +BYTE* GetRemoteModuleBaseAddress(DWORD dwPID, LPCWSTR szFullModulePath) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID); + if (NULL == hProcess) + ThrowHR(E_FAIL); + + // These shouldn't be freed + HMODULE modules[1000]; + DWORD cbNeeded; + if(!EnumProcessModules(hProcess, modules, sizeof(modules), &cbNeeded)) + { + ThrowHR(HRESULT_FROM_WIN32(GetLastError())); + } + + DWORD countModules = cbNeeded/sizeof(HMODULE); + for(DWORD i = 0; i < countModules; i++) + { + WCHAR modulePath[MAX_PATH]; + if(0 == GetModuleFileNameEx(hProcess, modules[i], modulePath, MAX_PATH)) + { + continue; + } + else + { + modulePath[MAX_PATH-1] = 0; // on older OS'es this doesn't get null terminated automatically + if (_wcsicmp(modulePath, szFullModulePath) == 0) + { + return (BYTE*) modules[i]; + } + } + } + + + // Successfully enumerated modules but couldn't find the requested one. + return NULL; +} + +// DBI version: max 8 hex chars +// SEMICOLON: 1 +// PID: max 8 hex chars +// SEMICOLON: 1 +// HMODULE: max 16 hex chars (64-bit) +// SEMICOLON: 1 +// PROTOCOL STRING: (variable length) +const int c_iMaxVersionStringLen = 8 + 1 + 8 + 1 + 16; // 64-bit hmodule +const int c_iMinVersionStringLen = 8 + 1 + 8 + 1 + 8; // 32-bit hmodule +const int c_idxFirstSemi = 8; +const int c_idxSecondSemi = 17; + +//----------------------------------------------------------------------------- +// Public API. +// Given a path to a coreclr.dll, get the Version string. +// +// Arguments: +// pidDebuggee - OS process ID of debuggee. +// szModuleName - a full or relative path to a valid coreclr.dll in the debuggee. +// pBuffer - the buffer to fill the version string into +// if pdwLength != NULL, we set *pdwLength to the length of the version string on +// output (including the null terminator). +// cchBuffer - length of pBuffer on input in characters +// +// Returns: +// S_OK - on success. +// E_INVALIDARG - +// HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the buffer is too small. +// +// Notes: +// The null-terminated version string including null, is +// copied to pVersion on output. Thus *pdwLength == wcslen(pBuffer)+1. +// The version string is an opaque string that can only be passed back to other +// DbgShim APIs. +//----------------------------------------------------------------------------- +HRESULT CreateVersionStringFromModule(DWORD pidDebuggee, + LPCWSTR szModuleName, + __out_ecount_part(cchBuffer, *pdwLength) LPWSTR pBuffer, + DWORD cchBuffer, + __out DWORD* pdwLength) +{ + PUBLIC_CONTRACT; + + if (szModuleName == NULL) + { + return E_INVALIDARG; + } + + // it is ok for both to be null (to query the required buffer size) or both to be non-null. + if ((pBuffer == NULL) != (cchBuffer == 0)) + { + return E_INVALIDARG; + } + + SIZE_T nLengthWithNull = c_iMaxVersionStringLen + 1; + _ASSERTE(nLengthWithNull > 0); + + if (pdwLength != NULL) + { + *pdwLength = (DWORD) nLengthWithNull; + } + + if (nLengthWithNull > cchBuffer) + { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else if (pBuffer != NULL) + { + HRESULT hr = S_OK; + EX_TRY + { + CorDebugInterfaceVersion dbiVersion = CorDebugInvalidVersion; + DWORD pid = pidDebuggee; + + CLR_ENGINE_METRICS metricsStruct; + + GetTargetCLRMetrics(szModuleName, &metricsStruct); // throws + dbiVersion = (CorDebugInterfaceVersion) metricsStruct.dwDbiVersion; + + BYTE* hmodTargetCLR = GetRemoteModuleBaseAddress(pidDebuggee, szModuleName); // throws + + swprintf_s(pBuffer, cchBuffer, W("%08x;%08x;%p"), dbiVersion, pid, hmodTargetCLR); + } + EX_CATCH_HRESULT(hr); + return hr; + } + + return S_OK; +} + +// Functions that we'll look for in the loaded Mscordbi module. +typedef HRESULT (STDAPICALLTYPE *FPCoreCLRCreateCordbObject)( + int iDebuggerVersion, + DWORD pid, + HMODULE hmodTargetCLR, + IUnknown ** ppCordb); + +//----------------------------------------------------------------------------- +// Parse a version string into useful data. +// +// Arguments: +// szDebuggeeVersion - (in) null terminated version string +// piDebuggerVersion - (out) interface number that the debugger expects to use. +// pdwPidDebuggee - (out) OS process ID of debuggee +// phmodTargetCLR - (out) module handle of CoreClr within the debuggee. +// +// Returns: +// S_OK on success. Else failures. +// +// Notes: +// The version string is coming from the target CoreClr and in the case of a corrupted target, could be +// an arbitrary string. It should be treated as untrusted public input. +//----------------------------------------------------------------------------- +HRESULT ParseVersionString(LPCWSTR szDebuggeeVersion, CorDebugInterfaceVersion * piDebuggerVersion, DWORD * pdwPidDebuggee, + HMODULE * phmodTargetCLR) +{ + if ((piDebuggerVersion == NULL) || + (pdwPidDebuggee == NULL) || + (phmodTargetCLR == NULL) || + (wcslen(szDebuggeeVersion) < c_iMinVersionStringLen) || + (W(';') != szDebuggeeVersion[c_idxFirstSemi]) || + (W(';') != szDebuggeeVersion[c_idxSecondSemi])) + { + return E_INVALIDARG; + } + + + int numFieldsAssigned = swscanf_s(szDebuggeeVersion, W("%08x;%08x;%p;"), piDebuggerVersion, pdwPidDebuggee, + phmodTargetCLR); + if (numFieldsAssigned != 3) + { + return E_FAIL; + } + + return S_OK; +} + +//----------------------------------------------------------------------------- +// Appends "\mscordbi.dll" to the path. This converts a directory name into the full path to mscordbi.dll. +// +// Arguments: +// szFullDbiPath - (in/out): on input, the directory containing dbi. On output, the full path to dbi.dll. +//----------------------------------------------------------------------------- +void AppendDbiDllName(SString & szFullDbiPath) +{ + const WCHAR * pDbiDllName = W("\\") MAKEDLLNAME_W(W("mscordbi")); + szFullDbiPath.Append(pDbiDllName); +} + +//----------------------------------------------------------------------------- +// Return a path to the dbi next to the runtime, if present. +// +// Arguments: +// pidDebuggee - OS process ID of debuggee +// hmodTargetCLR - handle to CoreClr within debuggee process +// szFullDbiPath - (out) the full path of Mscordbi.dll next to the debuggee's CoreClr.dll. +// +// Notes: +// This just calculates a filename and does not determine if the file actually exists. +//----------------------------------------------------------------------------- +void GetDbiFilenameNextToRuntime(DWORD pidDebuggee, HMODULE hmodTargetCLR, SString & szFullDbiPath, + SString & szFullCoreClrPath) +{ + szFullDbiPath.Clear(); + + // + // Step 1: (pid, hmodule) --> full path + // + HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pidDebuggee); + WCHAR modulePath[MAX_PATH]; + if(0 == GetModuleFileNameEx(hProcess, hmodTargetCLR, modulePath, MAX_PATH)) + { + ThrowHR(E_FAIL); + } + + // + // Step 2: 'Coreclr.dll' --> 'mscordbi.dll' + // + WCHAR * pCoreClrPath = modulePath; + WCHAR * pLast = wcsrchr(pCoreClrPath, '\\'); + if (pLast == NULL) + { + ThrowHR(E_FAIL); + } + + + // Change: + // c:\abc\coreclr.dll + // 01234567890 + // c:\abc\mscordbi.dll + + // Copy everything up to but not including the last '\', thus excluding '\coreclr.dll' + // Then append '\mscordbi.dll' to get a full path to dbi. + COUNT_T len = (COUNT_T) (pLast - pCoreClrPath); // length not including final '\' + szFullDbiPath.Set(pCoreClrPath, len); + + AppendDbiDllName(szFullDbiPath); + + szFullCoreClrPath.Set(pCoreClrPath, (COUNT_T)wcslen(pCoreClrPath)); +} + +//--------------------------------------------------------------------------------------- +// +// The current policy is that the DBI DLL must live right next to the coreclr DLL. We check the product +// version number of both of them to make sure they match. +// +// Arguments: +// szFullDbiPath - full path to mscordbi.dll +// szFullCoreClrPath - full path to coreclr.dll +// +// Return Value: +// true if the versions match +// + +bool CheckDbiAndRuntimeVersion(SString & szFullDbiPath, SString & szFullCoreClrPath) +{ + DWORD dwDbiVersionMS = 0; + DWORD dwDbiVersionLS = 0; + DWORD dwCoreClrVersionMS = 0; + DWORD dwCoreClrVersionLS = 0; + + // The version numbers follow the convention used by VS_FIXEDFILEINFO. + GetProductVersionNumber(szFullDbiPath, &dwDbiVersionMS, &dwDbiVersionLS); + GetProductVersionNumber(szFullCoreClrPath, &dwCoreClrVersionMS, &dwCoreClrVersionLS); + + if ((dwDbiVersionMS == dwCoreClrVersionMS) && + (dwDbiVersionLS == dwCoreClrVersionLS)) + { + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +// Public API. +// Given a version string, create the matching mscordbi.dll for it. +// Create a managed debugging interface for the specified version. +// +// Parameters: +// iDebuggerVersion - the version of interface the debugger (eg, Cordbg) expects. +// szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll +// ppCordb - the outparameter used to return the debugging interface object. +// +// Return: +// S_OK on success. *ppCordb will be non-null. +// CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if +// the right debug pack is not installed. +// else Error. (*ppCordb will be null) +//----------------------------------------------------------------------------- +HRESULT CreateDebuggingInterfaceFromVersionEx( + int iDebuggerVersion, + LPCWSTR szDebuggeeVersion, + IUnknown ** ppCordb +) +{ + PUBLIC_CONTRACT; + + HRESULT hrIgnore; // ignored HResult + HRESULT hr = S_OK; + HMODULE hMod = NULL; + IUnknown * pCordb = NULL; + + SString szFullDbiPath; + SString szFullCoreClrPath; + + LOG((LF_CORDB, LL_EVERYTHING, "Calling CreateDebuggerInterfaceFromVersion, ver=%S\n", szDebuggeeVersion)); + + if ((szDebuggeeVersion == NULL) || (ppCordb == NULL)) + { + hr = E_INVALIDARG; + goto Exit; + } + + *ppCordb = NULL; + + // + // Step 1: Parse version information into internal data structures + // + + CorDebugInterfaceVersion iTargetVersion; // the CorDebugInterfaceVersion (CorDebugVersion_2_0) + DWORD pidDebuggee; // OS process ID of the debuggee + HMODULE hmodTargetCLR; // module of Telesto in target (the clrInstanceId) + + hr = ParseVersionString(szDebuggeeVersion, &iTargetVersion, &pidDebuggee, &hmodTargetCLR); + if (FAILED(hr)) + goto Exit; + + + // + // Step 2: Find the proper Dbi module (mscordbi.dll) and load it. + // + + // Check for dbi next to target CLR. + // This will be very common for internal developer setups, but not common in end-user setups. + EX_TRY + { + GetDbiFilenameNextToRuntime(pidDebuggee, hmodTargetCLR, szFullDbiPath, szFullCoreClrPath); + + if (!CheckDbiAndRuntimeVersion(szFullDbiPath, szFullCoreClrPath)) + { + hr = CORDBG_E_INCOMPATIBLE_PROTOCOL; + goto Exit; + } + + // We calculated where dbi would be, but haven't yet verified if it's there. + // Try to load it. We're using this to check for file existence. + + // Issue:951525: coreclr mscordbi load fails on downlevel OS since LoadLibraryEx can't find + // dependent forwarder DLLs. Force LoadLibrary to look for dependencies in szFullDbiPath plus the default + // search paths. + hMod = WszLoadLibraryEx(szFullDbiPath, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + } + EX_CATCH_HRESULT(hrIgnore); // failure leaves hMod null + + // Couldn't find Dbi, likely because the right debug pack is not installed. Failure. + if (NULL == hMod) + { + // Check for the following two HRESULTs and return them specifically. These are returned by + // CreateToolhelp32Snapshot() and could be transient errors. The debugger may choose to retry. + if ((hrIgnore == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) || (hrIgnore == HRESULT_FROM_WIN32(ERROR_BAD_LENGTH))) + { + hr = hrIgnore; + } + else + { + hr = CORDBG_E_DEBUG_COMPONENT_MISSING; + } + goto Exit; + } + + // + // Step 3: Now that module is loaded, instantiate an ICorDebug. + // + + FPCoreCLRCreateCordbObject fpCreate2 = (FPCoreCLRCreateCordbObject) GetProcAddress(hMod, "CoreCLRCreateCordbObject"); + if (fpCreate2 == NULL) + { + // New-style creation API didn't exist - this DBI must be the wrong version, for the Mix07 protocol + hr = CORDBG_E_INCOMPATIBLE_PROTOCOL; + goto Exit; + } + + // Invoke to instantiate an ICorDebug. This export was introduced after the Mix'07 release. + hr = fpCreate2(iDebuggerVersion, pidDebuggee, hmodTargetCLR, &pCordb); + _ASSERTE((pCordb == NULL) == FAILED(hr)); + + + + +Exit: + if (FAILED(hr)) + { + if (pCordb != NULL) + { + pCordb->Release(); + pCordb = NULL; + } + + if (hMod != NULL) + { + _ASSERTE(pCordb == NULL); + FreeLibrary(hMod); + } + } + + // Set our outparam. + if (ppCordb != NULL) + { + *ppCordb = pCordb; + } + + // On success case, mscordbi.dll is leaked. + // - We never give the caller back the module handle, so our caller can't do FreeLibrary(). + // - ICorDebug can't unload itself. + + return hr; +} + + +//----------------------------------------------------------------------------- +// Public API. +// Superceded by CreateDebuggingInterfaceFromVersionEx in SLv4. +// Given a version string, create the matching mscordbi.dll for it. +// Create a managed debugging interface for the specified version. +// +// Parameters: +// szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll +// ppCordb - the outparameter used to return the debugging interface object. +// +// Return: +// S_OK on success. *ppCordb will be non-null. +// CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if +// the right debug pack is not installed. +// else Error. (*ppCordb will be null) +//----------------------------------------------------------------------------- +HRESULT CreateDebuggingInterfaceFromVersion( + LPCWSTR szDebuggeeVersion, + IUnknown ** ppCordb +) +{ + PUBLIC_CONTRACT; + + return CreateDebuggingInterfaceFromVersionEx(CorDebugVersion_2_0, + szDebuggeeVersion, + ppCordb); +} + + +//------------------------------------------------------------------------------ +// Manually retrieves the "continue startup" event from the correct CLR instance +// in the target process. +// +// Arguments: +// debuggeePID - (in) OS Process ID of debuggee +// szTelestoFullPath - (in) full path to telesto within the process. +// phContinueStartupEvent - (out) +// +// Returns: +// S_OK on success. +//------------------------------------------------------------------------------ +HRESULT GetContinueStartupEvent(DWORD debuggeePID, + LPCWSTR szTelestoFullPath, + __out HANDLE* phContinueStartupEvent) +{ + if ((phContinueStartupEvent == NULL) || (szTelestoFullPath == NULL)) + return E_INVALIDARG; + + HRESULT hr = S_OK; + EX_TRY + { + *phContinueStartupEvent = INVALID_HANDLE_VALUE; + + DWORD dwCoreClrContinueEventOffset = 0; + CLR_ENGINE_METRICS metricsStruct; + + GetTargetCLRMetrics(szTelestoFullPath, &metricsStruct, &dwCoreClrContinueEventOffset); // throws + + + BYTE* pbBaseAddress = GetRemoteModuleBaseAddress(debuggeePID, szTelestoFullPath); // throws + + + HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID); + if (NULL == hProcess) + ThrowHR(E_FAIL); + + HANDLE continueEvent = NULL; + + SIZE_T nBytesRead; + if (!ReadProcessMemory(hProcess, pbBaseAddress + dwCoreClrContinueEventOffset, &continueEvent, + sizeof(continueEvent), &nBytesRead)) + { + ThrowHR(E_FAIL); + } + + if (NULL != continueEvent) + { + if (!DuplicateHandle(hProcess, continueEvent, GetCurrentProcess(), &continueEvent, + EVENT_MODIFY_STATE, FALSE, 0)) + { + ThrowHR(E_FAIL); + } + } + + *phContinueStartupEvent = continueEvent; + } + EX_CATCH_HRESULT(hr) + return hr; +} + + +#if defined(FEATURE_CORESYSTEM) && defined(_TARGET_X86_) +#include "debugshim.h" +#endif + +HRESULT CLRCreateInstance(REFCLSID clsid, REFIID riid, LPVOID *ppInterface) +{ +#if defined(FEATURE_CORESYSTEM) && defined(_TARGET_X86_) + + if (ppInterface == NULL) + return E_POINTER; + + if (clsid != CLSID_CLRDebugging || riid != IID_ICLRDebugging) + return E_NOINTERFACE; + +#if defined(FEATURE_CORESYSTEM) + GUID skuId = CLR_ID_ONECORE_CLR; +#elif defined(FEATURE_CORECLR) + GUID skuId = CLR_ID_CORECLR; +#else + GUID skuId = CLR_ID_V4_DESKTOP; +#endif + + CLRDebuggingImpl *pDebuggingImpl = new CLRDebuggingImpl(skuId); + return pDebuggingImpl->QueryInterface(riid, ppInterface); +#else + return E_NOTIMPL; +#endif +} + + + + |