diff options
Diffstat (limited to 'src/debug/di/publish.cpp')
-rw-r--r-- | src/debug/di/publish.cpp | 1282 |
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) |