summaryrefslogtreecommitdiff
path: root/src/debug/di/publish.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/publish.cpp')
-rw-r--r--src/debug/di/publish.cpp1282
1 files changed, 1282 insertions, 0 deletions
diff --git a/src/debug/di/publish.cpp b/src/debug/di/publish.cpp
new file mode 100644
index 0000000000..888988a10f
--- /dev/null
+++ b/src/debug/di/publish.cpp
@@ -0,0 +1,1282 @@
+// 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: publish.cpp
+//
+
+//
+//*****************************************************************************
+
+
+#include "stdafx.h"
+#ifdef FEATURE_DBG_PUBLISH
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+// Publish shares header files with the rest of ICorDebug.
+// ICorDebug should not call ReadProcessMemory & other APIs directly, it should instead go through
+// the Data-target. ICD headers #define these APIs to help enforce this.
+// Since Publish is separate and doesn't use data-targets, it can access the APIs directly.
+// see code:RSDebuggingInfo#UseDataTarget
+#undef ReadProcessMemory
+
+//****************************************************************************
+//************ App Domain Publishing Service API Implementation **************
+//****************************************************************************
+
+// This function enumerates all the process in the system and returns
+// their PIDs
+BOOL GetAllProcessesInSystem(DWORD *ProcessId,
+ DWORD dwArraySize,
+ DWORD *pdwNumEntries)
+{
+ HandleHolder hSnapshotHolder;
+
+#if !defined(FEATURE_CORESYSTEM)
+ // Load the dll "kernel32.dll".
+ HModuleHolder hDll = WszLoadLibrary(W("kernel32"));
+ _ASSERTE(hDll != NULL);
+
+ if (hDll == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to load the dll for enumerating processes. "
+ "LoadLibrary (kernel32.dll) failed.\n"));
+ return FALSE;
+ }
+#else
+ // Load the dll "api-ms-win-obsolete-kernel32-l1-1-0.dll".
+ HModuleHolder hDll = WszLoadLibrary(W("api-ms-win-obsolete-kernel32-l1-1-0.dll"));
+ _ASSERTE(hDll != NULL);
+
+ if (hDll == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to load the dll for enumerating processes. "
+ "LoadLibrary (api-ms-win-obsolete-kernel32-l1-1-0.dll) failed.\n"));
+ return FALSE;
+ }
+#endif
+
+
+ // Create the Process' Snapshot
+ // Get the pointer to the requested function
+ FARPROC pProcAddr = GetProcAddress(hDll, "CreateToolhelp32Snapshot");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (CreateToolhelp32Snapshot) failed.\n"));
+ return FALSE;
+ }
+
+
+
+ // Handle from CreateToolHelp32Snapshot must be freed via CloseHandle().
+ typedef HANDLE CREATETOOLHELP32SNAPSHOT(DWORD, DWORD);
+
+ HANDLE hSnapshot =
+ ((CREATETOOLHELP32SNAPSHOT *)pProcAddr)(TH32CS_SNAPPROCESS, NULL);
+
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to create snapshot of processes in the system. "
+ "CreateToolhelp32Snapshot() failed.\n"));
+ return FALSE;
+ }
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ hSnapshotHolder.Assign(hSnapshot);
+
+ // Get the first process in the process list
+ // Get the pointer to the requested function
+ pProcAddr = GetProcAddress(hDll, "Process32First");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (Process32First) failed.\n"));
+ return FALSE;
+ }
+
+ PROCESSENTRY32 PE32;
+
+ // need to initialize the dwSize field before calling Process32First
+ PE32.dwSize = sizeof (PROCESSENTRY32);
+
+ typedef BOOL PROCESS32FIRST(HANDLE, LPPROCESSENTRY32);
+
+ BOOL succ =
+ ((PROCESS32FIRST *)pProcAddr)(hSnapshot, &PE32);
+
+ if (succ != TRUE)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to create snapshot of processes in the system. "
+ "Process32First() returned FALSE.\n"));
+ return FALSE;
+ }
+
+
+ // Loop over and get all the remaining processes
+ // Get the pointer to the requested function
+ pProcAddr = GetProcAddress(hDll, "Process32Next");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (Process32Next) failed.\n"));
+ return FALSE;
+ }
+
+ typedef BOOL PROCESS32NEXT(HANDLE, LPPROCESSENTRY32);
+
+ int iIndex = 0;
+
+ do
+ {
+ ProcessId [iIndex++] = PE32.th32ProcessID;
+
+ succ = ((PROCESS32NEXT *)pProcAddr)(hSnapshot, &PE32);
+
+ } while ((succ == TRUE) && (iIndex < (int)dwArraySize));
+
+ // I would like to know if we're running more than 512 processes on Win95!!
+ _ASSERTE (iIndex < (int)dwArraySize);
+
+ *pdwNumEntries = iIndex;
+
+ // If we made it this far, we succeeded
+ return TRUE;
+}
+
+
+// We never want to wait infinite on an object that we can't verify.
+// Wait with a timeout.
+const DWORD SAFETY_TIMEOUT = 2000;
+
+// ******************************************
+// CorpubPublish
+// ******************************************
+
+CorpubPublish::CorpubPublish()
+ : CordbCommonBase(0),
+ m_fpGetModuleFileNameEx(NULL)
+{
+ // Try to get psapi!GetModuleFileNameExW once, and then every process object can use it.
+ // If we can't get it, then we'll fallback to getting information from the IPC block.
+#if !defined(FEATURE_CORESYSTEM)
+ m_hPSAPIdll = WszLoadLibrary(W("psapi.dll"));
+#else
+ m_hPSAPIdll = WszLoadLibrary(W("api-ms-win-obsolete-psapi-l1-1-0.dll"));
+#endif
+
+ if (m_hPSAPIdll != NULL)
+ {
+ m_fpGetModuleFileNameEx = (FPGetModuleFileNameEx*) GetProcAddress(m_hPSAPIdll, "GetModuleFileNameExW");
+ }
+
+ CordbCommonBase::InitializeCommon();
+}
+
+CorpubPublish::~CorpubPublish()
+{
+ // m_hPSAPIdll is a module holder, so the dtor will free it automatically for us.
+}
+
+
+COM_METHOD CorpubPublish::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublish)
+ *ppInterface = (ICorPublish*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublish*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+COM_METHOD CorpubPublish::EnumProcesses(COR_PUB_ENUMPROCESS Type,
+ ICorPublishProcessEnum **ppIEnum)
+{
+ HRESULT hr = E_FAIL;
+ CorpubProcess* pProcessList = NULL ;
+ CorpubProcessEnum* pProcEnum = NULL;
+ *ppIEnum = NULL;
+
+ if( Type != COR_PUB_MANAGEDONLY )
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+
+ // call function to get PIDs for all processes in the system
+#define MAX_PROCESSES 512
+
+ DWORD ProcessId[MAX_PROCESSES];
+ DWORD dwNumProcesses = 0;
+ if( !GetAllProcessesInSystem(ProcessId, MAX_PROCESSES, &dwNumProcesses) )
+ {
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // iterate over all the processes to fetch all the managed processes
+ for (int i = 0; i < (int)dwNumProcesses; i++)
+ {
+ CorpubProcess *pProcess = NULL;
+ hr = GetProcessInternal( ProcessId[i], &pProcess );
+ if( FAILED(hr) )
+ {
+ _ASSERTE( pProcess == NULL );
+ goto exit; // a serious error has occurred, abort
+ }
+
+ if( hr == S_OK )
+ {
+ // Success, Add the process to the list.
+ _ASSERTE( pProcess != NULL );
+ pProcess->SetNext( pProcessList );
+ pProcessList = pProcess;
+ }
+ else
+ {
+ // Ignore this process (isn't managed, or shut down, etc.)
+ _ASSERTE( pProcess == NULL );
+ }
+ }
+
+ // create and return the ICorPublishProcessEnum
+ pProcEnum = new (nothrow) CorpubProcessEnum(pProcessList);
+ if (pProcEnum == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+ pProcEnum->AddRef();
+
+ hr = pProcEnum->QueryInterface(IID_ICorPublishProcessEnum, (void**)ppIEnum);
+ if( FAILED(hr) )
+ {
+ goto exit;
+ }
+
+ hr = S_OK;
+
+exit:
+ // release our handle on the process objects
+ while (pProcessList != NULL)
+ {
+ CorpubProcess *pTmp = pProcessList;
+ pProcessList = pProcessList->GetNextProcess();
+ pTmp->Release();
+ }
+ if( pProcEnum != NULL )
+ {
+ pProcEnum->Release();
+ pProcEnum = NULL;
+ }
+
+ return hr;
+}
+
+
+HRESULT CorpubPublish::GetProcess(unsigned pid,
+ ICorPublishProcess **ppProcess)
+{
+ *ppProcess = NULL;
+
+ // Query for this specific process (even if we've already handed out a
+ // now-stale process object for this pid)
+ CorpubProcess * pProcess = NULL;
+ HRESULT hr = GetProcessInternal( pid, &pProcess );
+ if( hr != S_OK )
+ {
+ // Couldn't get this process (doesn't exist, or isn't managed)
+ _ASSERTE( pProcess == NULL );
+ if( FAILED(hr) )
+ {
+ return hr; // there was a serious error trying to get this process info
+ }
+ return E_INVALIDARG; // this process doesn't exist, isn't managed or is shutting down
+ }
+
+ // QI to ICorPublishProcess and return it
+ _ASSERTE( pProcess != NULL );
+ hr = pProcess->QueryInterface(IID_ICorPublishProcess, (void**)ppProcess);
+ pProcess->Release();
+ return hr;
+}
+
+
+// Attempts to create a CorpubProcess object for a specific managed process
+// On success returns S_OK and sets ppProcess to a new AddRef'd CorpubProcess
+// object. Otherwise, returns S_FALSE if the process isn't managed or if it has
+// terminated (i.e. it should be ignored), or and error code on a serious failure.
+HRESULT CorpubPublish::GetProcessInternal(
+ unsigned pid,
+ CorpubProcess **ppProcess )
+{
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return E_NOTIMPL;
+
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ HRESULT hr = S_OK;
+ *ppProcess = NULL;
+
+ NewHolder<IPCReaderInterface> pIPCReader( new (nothrow) IPCReaderInterface() );
+ if (pIPCReader == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "CP::EP: Failed to allocate memory for IPCReaderInterface.\n"));
+ return E_OUTOFMEMORY;
+ }
+
+ // See if it is a managed process by trying to open the shared
+ // memory block.
+ hr = pIPCReader->OpenLegacyPrivateBlockTempV4OnPid(pid);
+ if (FAILED(hr))
+ {
+ return S_FALSE; // Not a managed process
+ }
+
+ // Get the AppDomainIPCBlock
+ AppDomainEnumerationIPCBlock *pAppDomainCB = pIPCReader->GetAppDomainBlock();
+ if (pAppDomainCB == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: Failed to obtain AppDomainIPCBlock.\n"));
+ return S_FALSE;
+ }
+
+ // Get the process handle.
+ HANDLE hProcess = OpenProcess((PROCESS_VM_READ |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_DUP_HANDLE |
+ SYNCHRONIZE),
+ FALSE, pid);
+ if (hProcess == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: OpenProcess() returned NULL handle.\n"));
+ return S_FALSE;
+ }
+
+ // If the mutex isn't filled in, the CLR is either starting up or shutting down
+ if (pAppDomainCB->m_hMutex == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: IPC block isn't properly filled in.\n"));
+ return S_FALSE;
+ }
+
+ // Dup the valid mutex handle into this process.
+ HANDLE hMutex;
+ if( !pAppDomainCB->m_hMutex.DuplicateToLocalProcess(hProcess, &hMutex) )
+ {
+ return S_FALSE;
+ }
+
+ // Acquire the mutex, only waiting two seconds.
+ // We can't actually gaurantee that the target put a mutex object in here.
+ DWORD dwRetVal = WaitForSingleObject(hMutex, SAFETY_TIMEOUT);
+
+ if (dwRetVal == WAIT_OBJECT_0)
+ {
+ // Make sure the mutex handle is still valid. If
+ // its not, then we lost a shutdown race.
+ if (pAppDomainCB->m_hMutex == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: lost shutdown race, skipping...\n"));
+
+ ReleaseMutex(hMutex);
+ CloseHandle(hMutex);
+ return S_FALSE;
+ }
+ }
+ else
+ {
+ // Again, landing here is most probably a shutdown race. Its okay, though...
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: failed to get IPC mutex.\n"));
+
+ if (dwRetVal == WAIT_ABANDONED)
+ {
+ ReleaseMutex(hMutex);
+ }
+ CloseHandle(hMutex);
+ return S_FALSE;
+ }
+ // Beware: if the target pid is not properly honoring the mutex, the data in the
+ // IPC block may still shift underneath us.
+
+ // If we get here, then hMutex is held by this process.
+
+ // Now create the CorpubProcess object for the ProcessID
+ CorpubProcess *pProc = new (nothrow) CorpubProcess(pid,
+ true,
+ hProcess,
+ hMutex,
+ pAppDomainCB,
+ pIPCReader,
+ m_fpGetModuleFileNameEx);
+
+ // Release our lock on the IPC block.
+ ReleaseMutex(hMutex);
+
+ if (pProc == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pIPCReader.SuppressRelease();
+
+ // Success, return the Process object
+ pProc->AddRef();
+ *ppProcess = pProc;
+ return S_OK;
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+}
+
+
+
+// ******************************************
+// CorpubProcess
+// ******************************************
+
+// Constructor
+CorpubProcess::CorpubProcess(DWORD dwProcessId,
+ bool fManaged,
+ HANDLE hProcess,
+ HANDLE hMutex,
+ AppDomainEnumerationIPCBlock *pAD,
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *pIPCReader,
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ FPGetModuleFileNameEx * fpGetModuleFileNameEx)
+ : CordbCommonBase(0, enumCorpubProcess),
+ m_dwProcessId(dwProcessId),
+ m_fIsManaged(fManaged),
+ m_hProcess(hProcess),
+ m_hMutex(hMutex),
+ m_AppDomainCB(pAD),
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ m_pIPCReader(pIPCReader),
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ m_pNext(NULL)
+{
+ {
+ // First try to get the process name from the OS. That can't be spoofed by badly formed IPC block.
+ // psapi!GetModuleFileNameExW can get that, but it's not available on all platforms so we
+ // need to load it dynamically.
+ if (fpGetModuleFileNameEx != NULL)
+ {
+ // MSDN is very confused about whether the lenght is in bytes (MSDN 2002) or chars (MSDN 2004).
+ // We err on the safe side by having buffer that's twice as large, and ignoring
+ // the units on the return value.
+ WCHAR szName[MAX_LONGPATH * sizeof(WCHAR)];
+
+ DWORD lenInCharsOrBytes = MAX_LONGPATH*sizeof(WCHAR);
+
+ // Pass NULL module handle to get "Main Module", which will give us the process name.
+ DWORD ret = (*fpGetModuleFileNameEx) (hProcess, NULL, szName, lenInCharsOrBytes);
+ if (ret > 0)
+ {
+ // Recompute string length because we don't know if 'ret' is in bytes or char.
+ SIZE_T len = wcslen(szName) + 1;
+ m_szProcessName = new (nothrow) WCHAR[len];
+ if (m_szProcessName != NULL)
+ {
+ wcscpy_s(m_szProcessName, len, szName);
+ goto exit;
+ }
+ }
+ }
+
+ // This is a security feature on WinXp + above, so make sure it worked there.
+ CONSISTENCY_CHECK_MSGF(FALSE, ("On XP/2k03 OSes + above, we should have been able to get\n"
+ "the module name from psapi!GetModuleFileNameEx. fp=0x%p\n.", fpGetModuleFileNameEx));
+ }
+ // We couldn't get it from the OS, so fallthrough to getting it from the IPC block.
+
+ // Fetch the process name from the AppDomainIPCBlock
+ _ASSERTE (pAD->m_szProcessName != NULL);
+
+ if (pAD->m_szProcessName == NULL)
+ m_szProcessName = NULL;
+ else
+ {
+ SIZE_T nBytesRead;
+
+ _ASSERTE(pAD->m_iProcessNameLengthInBytes > 0);
+
+ // Note: this assumes we're reading the null terminator from
+ // the IPC block.
+ m_szProcessName = (WCHAR*) new (nothrow) char[pAD->m_iProcessNameLengthInBytes];
+
+ if (m_szProcessName == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::CP: Failed to allocate memory for ProcessName.\n"));
+
+ goto exit;
+ }
+
+ BOOL bSucc = ReadProcessMemory(hProcess,
+ pAD->m_szProcessName,
+ m_szProcessName,
+ pAD->m_iProcessNameLengthInBytes,
+ &nBytesRead);
+
+ if ((bSucc == 0) ||
+ (nBytesRead != (SIZE_T)pAD->m_iProcessNameLengthInBytes))
+ {
+ // The EE may have done a rude exit
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::EAD: ReadProcessMemory (ProcessName) failed.\n"));
+ }
+ }
+
+exit:
+ ;
+}
+
+CorpubProcess::~CorpubProcess()
+{
+ delete [] m_szProcessName;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ delete m_pIPCReader;
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ CloseHandle(m_hProcess);
+ CloseHandle(m_hMutex);
+}
+
+
+HRESULT CorpubProcess::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishProcess)
+ *ppInterface = (ICorPublishProcess*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishProcess*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+// Helper to tell if this process has exited.
+bool CorpubProcess::IsExited()
+{
+ DWORD res = WaitForSingleObject(this->m_hProcess, 0);
+ return (res == WAIT_OBJECT_0);
+}
+
+
+HRESULT CorpubProcess::IsManaged(BOOL *pbManaged)
+{
+ *pbManaged = (m_fIsManaged == true) ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+// Helper.
+// Allocates a local buffer (using 'new') and fills it by copying it from remote memory.
+// Returns:
+// - on success, S_OK, *ppNewLocalBuffer points to a newly allocated buffer containing
+// the full copy from remote memoy. Caller must use 'delete []' to free this.
+// - on failure, a failing HR. No memory is allocated.
+HRESULT AllocateAndReadRemoteBuffer(
+ HANDLE hProcess,
+ void * pRemotePtr,
+ SIZE_T cbSize, // size of buffer to allocate + copy.
+ BYTE * * ppNewLocalBuffer
+)
+{
+ _ASSERTE(ppNewLocalBuffer != NULL);
+ *ppNewLocalBuffer = NULL;
+
+
+ if (pRemotePtr == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ BYTE *pLocalBuffer = new (nothrow) BYTE[cbSize];
+
+ if (pLocalBuffer == NULL)
+ {
+ _ASSERTE(!"Failed to alloc memory. Likely size is bogusly large, perhaps from an attacker.");
+ return E_OUTOFMEMORY;
+ }
+
+ SIZE_T nBytesRead;
+
+ // Need to read in the remote process' memory
+ BOOL bSucc = ReadProcessMemory(hProcess,
+ pRemotePtr,
+ pLocalBuffer, cbSize,
+ &nBytesRead);
+
+ if ((bSucc == 0) || (nBytesRead != cbSize))
+ {
+ // The EE may have done a rude exit
+ delete [] pLocalBuffer;
+ return E_FAIL;
+ }
+
+ *ppNewLocalBuffer = pLocalBuffer;
+ return S_OK;
+}
+
+// Wrapper around AllocateAndReadRemoteBuffer,
+// to ensure that we're reading an remote-null terminated string.
+// Ensures that string is null-terminated.
+HRESULT AllocateAndReadRemoteString(
+ HANDLE hProcess,
+ void * pRemotePtr,
+ SIZE_T cbSize, // size of buffer to allocate + copy.
+ __deref_out_bcount(cbSize) WCHAR * * ppNewLocalBuffer
+ )
+{
+ // Make sure buffer has right geometry.
+ if (cbSize < 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ // If it's not on a WCHAR boundary, then we may have a 1-byte buffer-overflow.
+ SIZE_T ceSize = cbSize / sizeof(WCHAR);
+ if ((ceSize * sizeof(WCHAR)) != cbSize)
+ {
+ return E_INVALIDARG;
+ }
+
+ // It should at least have 1 char for the null terminator.
+ if (ceSize < 1)
+ {
+ return E_INVALIDARG;
+ }
+
+
+ HRESULT hr = AllocateAndReadRemoteBuffer(hProcess, pRemotePtr, cbSize, (BYTE**) ppNewLocalBuffer);
+ if (SUCCEEDED(hr))
+ {
+ // Ensure that the string we just read is actually null terminated.
+ // We can't call wcslen() on it yet, since that may AV on a non-null terminated string.
+ WCHAR * pString = *ppNewLocalBuffer;
+
+ if (pString[ceSize - 1] == W('\0'))
+ {
+ // String is null terminated.
+ return S_OK;
+ }
+ pString[ceSize - 1] = W('\0');
+
+ SIZE_T ceTestLen = wcslen(pString);
+ if (ceTestLen == ceSize - 1)
+ {
+ // String was not previously null-terminated.
+ delete [] ppNewLocalBuffer;
+ return E_INVALIDARG;
+ }
+ }
+ return S_OK;
+}
+
+//
+// Enumerate the list of known application domains in the target process.
+//
+HRESULT CorpubProcess::EnumAppDomains(ICorPublishAppDomainEnum **ppIEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppIEnum, ICorPublishAppDomainEnum **);
+ *ppIEnum = NULL;
+
+ int i;
+
+ HRESULT hr = S_OK;
+ WCHAR *pAppDomainName = NULL;
+ CorpubAppDomain *pAppDomainHead = NULL;
+
+ // Lock the IPC block:
+ // We can't trust any of the data in the IPC block (including our own mutex handle),
+ // because we don't want bugs in the debuggee escalating into bugs in the debugger.
+ DWORD res = WaitForSingleObject(m_hMutex, SAFETY_TIMEOUT);
+
+ if (res == WAIT_TIMEOUT)
+ {
+ // This should only happen if the target process is illbehaved.
+ return CORDBG_E_TIMEOUT;
+ }
+
+ // If the process has gone away, or if it has cleared out its control block, then
+ // we've lost the race to access this process before it is terminated.
+ // Note that if the EE does a rude process exit, it won't have cleared the control block so there
+ // will be a small race window.
+ if (this->IsExited() || this->m_AppDomainCB->m_hMutex == NULL )
+ {
+ // This is the common case. A process holding the mutex shouldn't normally exit,
+ // but once it releases the mutex, it may exit asynchronously.
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ if (res == WAIT_FAILED)
+ {
+ // This should be the next most common failure case
+ return HRESULT_FROM_GetLastError();
+ }
+
+ if (res != WAIT_OBJECT_0)
+ {
+ // Catch all other possible failures
+ return E_FAIL;
+ }
+
+ int iAppDomainCount = 0;
+ AppDomainInfo *pADI = NULL;
+
+ // Make a copy of the IPC block so that we can gaurantee that it's not changing on us.
+ AppDomainEnumerationIPCBlock tempBlock;
+ memcpy(&tempBlock, m_AppDomainCB, sizeof(tempBlock));
+
+ // Allocate memory to read the remote process' memory into
+ const SIZE_T cbADI = tempBlock.m_iSizeInBytes;
+
+ // It's possible the process will not have any appdomains.
+ if ((tempBlock.m_rgListOfAppDomains == NULL) != (tempBlock.m_iSizeInBytes == 0))
+ {
+ _ASSERTE(!"Inconsistent IPC block in publish.");
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // All the data in the IPC block is signed integers. They should never be negative,
+ // so check that now.
+ if ((tempBlock.m_iTotalSlots < 0) ||
+ (tempBlock.m_iNumOfUsedSlots < 0) ||
+ (tempBlock.m_iLastFreedSlot < 0) ||
+ (tempBlock.m_iSizeInBytes < 0) ||
+ (tempBlock.m_iProcessNameLengthInBytes < 0))
+ {
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // Check other invariants.
+ if (cbADI != tempBlock.m_iTotalSlots * sizeof(AppDomainInfo))
+ {
+ _ASSERTE(!"Size mismatch");
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ hr = AllocateAndReadRemoteBuffer(m_hProcess, tempBlock.m_rgListOfAppDomains, cbADI, (BYTE**) &pADI);
+ if (FAILED(hr))
+ {
+ goto exit;
+ }
+ _ASSERTE(pADI != NULL);
+
+ // Collect all the AppDomain info info a list of CorpubAppDomains
+ for (i = 0; i < tempBlock.m_iTotalSlots; i++)
+ {
+ if (!pADI[i].IsEmpty())
+ {
+ // Should be positive, and at least have a null-terminator character.
+ if (pADI[i].m_iNameLengthInBytes <= 1)
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+ hr = AllocateAndReadRemoteString(m_hProcess,
+ (void*) pADI[i].m_szAppDomainName, pADI[i].m_iNameLengthInBytes, // remote string + size in bytes
+ &pAppDomainName);
+ if (FAILED(hr))
+ {
+ goto exit;
+ }
+
+ // create a new AppDomainObject. This will take ownership of pAppDomainName.
+ // We know the string is a well-formed null-terminated string,
+ // but beyond that, we can't verify that the data is actually truthful.
+ CorpubAppDomain *pCurrentAppDomain = new (nothrow) CorpubAppDomain(pAppDomainName,
+ pADI[i].m_id);
+
+ if (pCurrentAppDomain == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::EAD: Failed to allocate memory for CorpubAppDomain.\n"));
+
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+
+ // Since CorpubAppDomain now owns pAppDomain's memory, we don't worry about freeing it.
+ pAppDomainName = NULL;
+
+ // Add the appdomain to the list.
+ pCurrentAppDomain->SetNext(pAppDomainHead);
+ pAppDomainHead = pCurrentAppDomain;
+
+ // Shortcut to opt out of reading the rest of the array if it's empty.
+ if (++iAppDomainCount >= tempBlock.m_iNumOfUsedSlots)
+ break;
+ }
+ }
+
+ {
+ _ASSERTE ((iAppDomainCount >= tempBlock.m_iNumOfUsedSlots)
+ && (i <= tempBlock.m_iTotalSlots));
+
+ // create and return the ICorPublishAppDomainEnum object, handing off the AppDomain list to it
+ CorpubAppDomainEnum *pTemp = new (nothrow) CorpubAppDomainEnum(pAppDomainHead);
+
+ if (pTemp == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+
+ pAppDomainHead = NULL; // handed off AppDomain list to enum, don't delete below
+
+ hr = pTemp->QueryInterface(IID_ICorPublishAppDomainEnum,
+ (void **)ppIEnum);
+ }
+
+exit:
+ ReleaseMutex(m_hMutex);
+
+ // If we didn't hand off the AppDomain objects, delete them
+ while( pAppDomainHead != NULL )
+ {
+ CorpubAppDomain *pTemp = pAppDomainHead;
+ pAppDomainHead = pAppDomainHead->GetNextAppDomain();
+ delete pTemp;
+ }
+
+ if (pADI != NULL)
+ delete[] pADI;
+
+ if (pAppDomainName != NULL)
+ delete [] pAppDomainName;
+
+ // Either we succeeded && provided an enumerator; or we failed and didn't provide an enum.
+ _ASSERTE(SUCCEEDED(hr) == (*ppIEnum != NULL));
+ return hr;
+}
+
+/*
+ * Returns the OS ID for the process in question.
+ */
+HRESULT CorpubProcess::GetProcessID(unsigned *pid)
+{
+ *pid = m_dwProcessId;
+
+ return S_OK;
+}
+
+/*
+ * Get the display name for a process.
+ */
+HRESULT CorpubProcess::GetDisplayName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ // Reasonable defaults
+ if (szName)
+ *szName = 0;
+
+ if (pcchName)
+ *pcchName = 0;
+
+ const WCHAR *szTempName = m_szProcessName;
+
+ // In case we didn't get the name (most likely out of memory on ctor).
+ if (!szTempName)
+ szTempName = W("<unknown>");
+
+ return CopyOutString(szTempName, cchName, pcchName, szName);
+}
+
+
+// ******************************************
+// CorpubAppDomain
+// ******************************************
+
+CorpubAppDomain::CorpubAppDomain (__in LPWSTR szAppDomainName, ULONG Id)
+ : CordbCommonBase (0, enumCorpubAppDomain),
+ m_pNext (NULL),
+ m_szAppDomainName (szAppDomainName),
+ m_id (Id)
+{
+ _ASSERTE(m_szAppDomainName != NULL);
+}
+
+CorpubAppDomain::~CorpubAppDomain()
+{
+ delete [] m_szAppDomainName;
+}
+
+HRESULT CorpubAppDomain::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishAppDomain)
+ *ppInterface = (ICorPublishAppDomain*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishAppDomain*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+/*
+ * Get the name and ID for an application domain.
+ */
+HRESULT CorpubAppDomain::GetID (ULONG32 *pId)
+{
+ VALIDATE_POINTER_TO_OBJECT(pId, ULONG32 *);
+
+ *pId = m_id;
+
+ return S_OK;
+}
+
+/*
+ * Get the name for an application domain.
+ */
+HRESULT CorpubAppDomain::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ const WCHAR *szTempName = m_szAppDomainName;
+
+ // In case we didn't get the name (most likely out of memory on ctor).
+ if (!szTempName)
+ szTempName = W("<unknown>");
+
+ return CopyOutString(szTempName, cchName, pcchName, szName);
+}
+
+
+
+// ******************************************
+// CorpubProcessEnum
+// ******************************************
+
+CorpubProcessEnum::CorpubProcessEnum (CorpubProcess *pFirst)
+ : CordbCommonBase (0, enumCorpubProcessEnum),
+ m_pFirst (pFirst),
+ m_pCurrent (pFirst)
+{
+ // Increment the ref count on each process, we own the list
+ CorpubProcess * cur = pFirst;
+ while( cur != NULL )
+ {
+ cur->AddRef();
+ cur = cur->GetNextProcess();
+ }
+}
+
+CorpubProcessEnum::~CorpubProcessEnum()
+{
+ // Release each process in the list (our client may still have a reference
+ // to some of them)
+ while (m_pFirst != NULL)
+ {
+ CorpubProcess *pTmp = m_pFirst;
+ m_pFirst = m_pFirst->GetNextProcess();
+ pTmp->Release();
+ }
+}
+
+HRESULT CorpubProcessEnum::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishProcessEnum)
+ *ppInterface = (ICorPublishProcessEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishProcessEnum*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT CorpubProcessEnum::Skip(ULONG celt)
+{
+ while ((m_pCurrent != NULL) && (celt-- > 0))
+ {
+ m_pCurrent = m_pCurrent->GetNextProcess();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Reset()
+{
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Clone(ICorPublishEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorPublishEnum **);
+ return E_NOTIMPL;
+}
+
+HRESULT CorpubProcessEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ CorpubProcess *pTemp = m_pFirst;
+
+ *pcelt = 0;
+
+ while (pTemp != NULL)
+ {
+ (*pcelt)++;
+ pTemp = pTemp->GetNextProcess();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Next(ULONG celt,
+ ICorPublishProcess *objects[],
+ ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorPublishProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ hr = m_pCurrent->QueryInterface (IID_ICorPublishProcess,
+ (void**)&objects[count]);
+
+ if (hr != S_OK)
+ {
+ break;
+ }
+
+ count++;
+ m_pCurrent = m_pCurrent->GetNextProcess();
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+// ******************************************
+// CorpubAppDomainEnum
+// ******************************************
+CorpubAppDomainEnum::CorpubAppDomainEnum (CorpubAppDomain *pFirst)
+ : CordbCommonBase (0, enumCorpubAppDomainEnum),
+ m_pFirst (pFirst),
+ m_pCurrent (pFirst)
+{
+ CorpubAppDomain *pCur = pFirst;
+ while( pCur != NULL )
+ {
+ pCur->AddRef();
+ pCur = pCur->GetNextAppDomain();
+ }
+}
+
+CorpubAppDomainEnum::~CorpubAppDomainEnum()
+{
+ // Delete all the app domains
+ while (m_pFirst != NULL )
+ {
+ CorpubAppDomain *pTemp = m_pFirst;
+ m_pFirst = m_pFirst->GetNextAppDomain();
+ pTemp->Release();
+ }
+}
+
+HRESULT CorpubAppDomainEnum::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishAppDomainEnum)
+ *ppInterface = (ICorPublishAppDomainEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishAppDomainEnum*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT CorpubAppDomainEnum::Skip(ULONG celt)
+{
+ while ((m_pCurrent != NULL) && (celt-- > 0))
+ {
+ m_pCurrent = m_pCurrent->GetNextAppDomain();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Reset()
+{
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Clone(ICorPublishEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorPublishEnum **);
+ return E_NOTIMPL;
+}
+
+HRESULT CorpubAppDomainEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ CorpubAppDomain *pTemp = m_pFirst;
+
+ *pcelt = 0;
+
+ while (pTemp != NULL)
+ {
+ (*pcelt)++;
+ pTemp = pTemp->GetNextAppDomain();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Next(ULONG celt,
+ ICorPublishAppDomain *objects[],
+ ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorPublishProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ hr = m_pCurrent->QueryInterface (IID_ICorPublishAppDomain,
+ (void **)&objects[count]);
+
+ if (hr != S_OK)
+ {
+ break;
+ }
+
+ count++;
+ m_pCurrent = m_pCurrent->GetNextAppDomain();
+ }
+
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+#endif // defined(FEATURE_DBG_PUBLISH)