diff options
Diffstat (limited to 'src/ipcman/ipcfunccallimpl.cpp')
-rw-r--r-- | src/ipcman/ipcfunccallimpl.cpp | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/src/ipcman/ipcfunccallimpl.cpp b/src/ipcman/ipcfunccallimpl.cpp new file mode 100644 index 0000000000..7107e28905 --- /dev/null +++ b/src/ipcman/ipcfunccallimpl.cpp @@ -0,0 +1,653 @@ +// 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: IPCFuncCallImpl.cpp +// +// Implement support for a cross process function call. +// +//***************************************************************************** + +#include "stdafx.h" +#include "ipcfunccall.h" +#include "ipcshared.h" + +#if defined(FEATURE_PERFMON) && defined(FEATURE_IPCMAN) + +// #define ENABLE_TIMING + +#ifdef ENABLE_TIMING +#include "timer.h" +CTimer g_time; +#endif // ENABLE_TIMING + +//----------------------------------------------------------------------------- +// <TODO>@todo: This is very generic. However, If we want to support multiple +// functions calls, we will have to decorate the event object names.</TODO> +//----------------------------------------------------------------------------- + +#define NamePrexix L"Global\\CLR_" + +// Name of sync objects +#define StartEnumEventName NamePrexix L"PerfMon_StartEnumEvent" +#define DoneEnumEventName NamePrexix L"PerfMon_DoneEnumEvent" +#define WrapMutexName NamePrexix L"PerfMon_WrapMutex" + +// Time the Source Caller is willing to wait for Handler to finish +// Note, a nefarious handler can at worst case make caller +// wait twice the delay below. +const DWORD START_ENUM_TIMEOUT = 500; // time out in milliseconds + +//----------------------------------------------------------------------------- +// Wrap an unsafe call in a mutex to assure safety +// Biggest error issues are: +// 1. Timeout (probably handler doesn't exist) +// 2. Handler can be destroyed at any time. +//----------------------------------------------------------------------------- +IPCFuncCallSource::EError IPCFuncCallSource::DoThreadSafeCall() +{ + WRAPPER_NO_CONTRACT; + + DWORD dwDesiredAccess; + DWORD dwErr; + EError err = Ok; + +#if defined(ENABLE_TIMING) + g_time.Reset(); + g_time.Start(); +#endif + + dwDesiredAccess = EVENT_MODIFY_STATE; + + HANDLE hStartEnum = NULL; + HANDLE hDoneEnum = NULL; + HANDLE hWrapCall = NULL; + DWORD dwWaitRet; + + // Check if we have a handler (handler creates the events) and + // abort if not. Do this check asap to optimize the most common + // case of no handler. + hStartEnum = WszOpenEvent(dwDesiredAccess, + FALSE, + StartEnumEventName); + if (hStartEnum == NULL) + { + dwErr = GetLastError(); + err = Fail_NoHandler; + goto errExit; + } + + hDoneEnum = WszOpenEvent(dwDesiredAccess, + FALSE, + DoneEnumEventName); + if (hDoneEnum == NULL) + { + dwErr = GetLastError(); + err = Fail_NoHandler; + goto errExit; + } + + // Need to create the mutex + hWrapCall = WszCreateMutex(NULL, FALSE, WrapMutexName); + if (hWrapCall == NULL) + { + dwErr = GetLastError(); + err = Fail_CreateMutex; + goto errExit; + } + + +// Wait for our turn + dwWaitRet = WaitForSingleObject(hWrapCall, START_ENUM_TIMEOUT); + dwErr = GetLastError(); + switch(dwWaitRet) { + case WAIT_OBJECT_0: + // Good case. All other cases are errors and goto errExit. + break; + + case WAIT_TIMEOUT: + err = Fail_Timeout_Lock; + goto errExit; + break; + default: + err = Failed; + goto errExit; + break; + } + + // Our turn: Make the function call + { + BOOL fSetOK = 0; + + // Reset the 'Done event' to make sure that Handler sets it after they start. + fSetOK = ResetEvent(hDoneEnum); + _ASSERTE(fSetOK); + dwErr = GetLastError(); + + // Signal Handler to execute callback + fSetOK = SetEvent(hStartEnum); + _ASSERTE(fSetOK); + dwErr = GetLastError(); + + // Now wait for handler to finish. + + dwWaitRet = WaitForSingleObject(hDoneEnum, START_ENUM_TIMEOUT); + dwErr = GetLastError(); + switch (dwWaitRet) + { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + err = Fail_Timeout_Call; + break; + default: + err = Failed; + break; + } + + + BOOL fMutexOk; + fMutexOk = ReleaseMutex(hWrapCall); + _ASSERTE(fMutexOk); + dwErr = GetLastError(); + + } // End function call + + + +errExit: +// Close all handles + if (hStartEnum != NULL) + { + CloseHandle(hStartEnum); + hStartEnum = NULL; + + } + if (hDoneEnum != NULL) + { + CloseHandle(hDoneEnum); + hDoneEnum = NULL; + } + if (hWrapCall != NULL) + { + CloseHandle(hWrapCall); + hWrapCall = NULL; + } + +#if defined(ENABLE_TIMING) + g_time.End(); + DWORD dwTime = g_time.GetEllapsedMS(); +#endif + + + return err; + +} + + +// Reset vars so we can be sure that Init was called +IPCFuncCallHandler::IPCFuncCallHandler() +{ + m_hStartEnum = NULL; // event to notify start call + m_hDoneEnum = NULL; // event to notify end call + m_hAuxThread = NULL; // thread to listen for m_hStartEnum + m_pfnCallback = NULL; // Callback handler + m_pfnCleanupCallback = NULL; // Cleanup callback handler + m_fShutdownAuxThread = FALSE; + m_hShutdownThread = NULL; + m_hCallbackModule = NULL; // module in which the aux thread's start function lives +} + +IPCFuncCallHandler::~IPCFuncCallHandler() +{ + // If Terminate was not called then do so now. This should have been + // called from CloseCtrs perf counters API. But in Windows XP this order is + // not guaranteed. + TerminateFCHandler(); +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning (disable: 6320) //We handle ALL exceptions so that the host process doesnt die +#endif + +//----------------------------------------------------------------------------- +// Thread callback +//----------------------------------------------------------------------------- +DWORD WINAPI HandlerAuxThreadProc( + LPVOID lpParameter // thread data +) +{ + IPCFuncCallHandler * pHandler = (IPCFuncCallHandler *) lpParameter; + + struct Param + { + IPCFuncCallHandler * pHandler; + } param; + param.pHandler = pHandler; + + PAL_TRY(Param *, pParam, ¶m) + { + HANDLER_CALLBACK pfnCallback = pParam->pHandler->m_pfnCallback; + + DWORD dwErr = 0; + DWORD dwWaitRet; + + HANDLE lpHandles[] = {pParam->pHandler->m_hShutdownThread, pParam->pHandler->m_hStartEnum}; + DWORD dwHandleCount = 2; + + do { + dwWaitRet = WaitForMultipleObjects(dwHandleCount, lpHandles, FALSE /*Wait Any*/, INFINITE); + dwErr = GetLastError(); + + // If we are in terminate mode then exit this helper thread. + if (pParam->pHandler->m_fShutdownAuxThread) + break; + + // Keep the 0th index for the terminate thread so that we never miss it + // in case of multiple events. note that the ShutdownAuxThread flag above it purely + // to protect us against some bug in waitForMultipleObjects. + if ((dwWaitRet-WAIT_OBJECT_0) == 0) + break; + + // execute callback if wait succeeded + if ((dwWaitRet-WAIT_OBJECT_0) == 1) + { + (*pfnCallback)(); + + // reset manual event + BOOL fResetOK; + fResetOK = ResetEvent(pParam->pHandler->m_hStartEnum); + _ASSERTE(fResetOK); + dwErr = GetLastError(); + + BOOL fSetOK; + fSetOK = SetEvent(pParam->pHandler->m_hDoneEnum); + _ASSERTE(fSetOK); + dwErr = GetLastError(); + } + } while (dwWaitRet != WAIT_FAILED); + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + WCHAR wszMsg[128]; + swprintf_s(wszMsg, COUNTOF(wszMsg), L"HandlerAuxThreadProc caught exception %x", GetExceptionCode()); + ClrReportEvent(L".NET Runtime", + EVENTLOG_ERROR_TYPE, + 0, + 0, + NULL, + wszMsg); + } + PAL_ENDTRY + + + pHandler->SafeCleanup(); + + HMODULE hCallbackModule = pHandler->m_hCallbackModule; + pHandler->m_hCallbackModule = NULL; + + pHandler->m_fShutdownAuxThread = FALSE; + + // Close the thread's handle and clear the shut down flag. Note the order here is very tricky + // to avoid a race. We must set the shutdown flag first to ensure that once m_hAuxThread is set + // to NULL no further modification happens to pHandler. + HANDLE hThread = InterlockedExchangeT(&pHandler->m_hAuxThread, NULL); + + // If hThread was null then WaitForCompletion will close it. + if (hThread != NULL) + CloseHandle(hThread); + + FreeLibraryAndExitThread (hCallbackModule, 0); + // Above call doesn't return + + return 0; +} + +#ifdef _MSC_VER +#pragma warning (pop) //6320 +#endif + + +//----------------------------------------------------------------------------- +// Receieves the call. This should be in a different process than the source +//----------------------------------------------------------------------------- +HRESULT IPCFuncCallHandler::InitFCHandler(HANDLER_CALLBACK pfnCallback, HANDLER_CALLBACK pfnCleanupCallback) +{ + // If the thread is still in the process of shutting down then + // we have to fail. + if (!IsShutdownComplete()) + { + _ASSERTE(!"shutdown should have completed before calling this function"); + return E_FAIL; + } + + m_pfnCallback = pfnCallback; + m_pfnCleanupCallback = pfnCleanupCallback; + + HRESULT hr = NOERROR; + DWORD dwThreadId; + DWORD dwErr = 0; + DWORD dwDesiredAccess; + DWORD dwRet = 0; + HANDLE hToken = NULL; + + SetLastError(0); + + // Grab the SA + DWORD dwPid = 0; + SECURITY_ATTRIBUTES *pSA = NULL; + + dwDesiredAccess = EVENT_MODIFY_STATE | SYNCHRONIZE; + + dwPid = GetCurrentProcessId(); + hr = IPCShared::CreateWinNTDescriptor(dwPid, FALSE, &pSA, Event, eDescriptor_Public); + + if (FAILED(hr)) + goto errExit;; + + // try to open event first (another process may already have created one) + m_hStartEnum = WszOpenEvent(dwDesiredAccess, + FALSE, + L"Global\\" StartEnumEventName); + if (m_hStartEnum == NULL) + { + // Create the StartEnum Event + m_hStartEnum = WszCreateEvent(pSA, + TRUE, // manual event for multiple instances of corperfmonext.dll + FALSE, + StartEnumEventName); + } + + if (m_hStartEnum == NULL) + { + dwErr = GetLastError(); + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + + // try to open event first (another process may already have created one) + m_hDoneEnum = WszOpenEvent(dwDesiredAccess, + FALSE, + L"Global\\" DoneEnumEventName); + + if (m_hDoneEnum == NULL) + { + // Create the EndEnumEvent + m_hDoneEnum = WszCreateEvent(pSA, + TRUE, // manual event for multiple instances of corperfmonext.dll + FALSE, + DoneEnumEventName); + } + if (m_hDoneEnum == NULL) + { + dwErr = GetLastError(); + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + + // Create the ShutdownThread Event + m_hShutdownThread = WszCreateEvent(pSA, + TRUE, /* Manual Reset */ + FALSE, /* Initial state not signalled */ + NULL); + + dwErr = GetLastError(); + if (m_hShutdownThread == NULL) + { + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + + BOOL bSuccess = FALSE; + + // Get current thread token with duplicate and impersonation access + // Will use this token for polling thread impersonation if current + // thread is impersonating + bSuccess = OpenThreadToken( + GetCurrentThread(), + TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, + TRUE, + &hToken + ); + + dwErr = GetLastError(); + // token won't exist if running local becase we are not impersonating + if (FALSE == bSuccess && ERROR_NO_TOKEN != dwErr) + { + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + + // at this point, we should either have a valid token or we failed + // to get the token because one does not exist on this thread + _ASSERTE(NULL != hToken || ERROR_NO_TOKEN == dwErr); + + // The thread that we are about to create should always + // find the code in memory. So we take a ref on the DLL. + // and do a free library at the end of the thread's start function + m_hCallbackModule = WszLoadLibrary (L"CorPerfmonExt.dll"); + + dwErr = GetLastError(); + if (m_hCallbackModule == NULL) + { + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + + // Create thread suspended so we can set impersonation token + m_hAuxThread = CreateThread( + NULL, + 0, + HandlerAuxThreadProc, + this, + CREATE_SUSPENDED, + &dwThreadId); + + dwErr = GetLastError(); + if (m_hAuxThread.Load() == NULL) + { + hr = HRESULT_FROM_WIN32(dwErr); + + // In case of an error free this library here otherwise + // the thread's exit would take care of it. + if (m_hCallbackModule) + FreeLibrary (m_hCallbackModule); + goto errExit; + } + + // If we got a token for the current thread, + // set token on new thread + if (NULL != hToken) + { + bSuccess = SetThreadToken((PHANDLE)m_hAuxThread.GetPointer(), hToken); + + dwErr = GetLastError(); + if (FALSE == bSuccess) + { + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + } + + // Resume the newly created thread + dwRet = ResumeThread(m_hAuxThread); + + dwErr = GetLastError(); + if (dwRet == (DWORD)(-1)) + { + hr = HRESULT_FROM_WIN32(dwErr); + goto errExit; + } + + _ASSERTE(1 == dwRet); + +errExit: + if (NULL != hToken) + { + CloseHandle(hToken); + } + + if (!SUCCEEDED(hr)) + { + TerminateFCHandler(); + } + + if (pSA != NULL) + { + IPCShared::DestroySecurityAttributes( pSA ); + } + return hr; + +} + +//----------------------------------------------------------------------------- +// Close all our handles +//----------------------------------------------------------------------------- +void IPCFuncCallHandler::SafeCleanup() +{ + // Call the cleanup callback + + if (m_pfnCleanupCallback != NULL) + { + struct Param + { + IPCFuncCallHandler * pHandler; + } param; + param.pHandler = this; + + PAL_TRY(Param *, pParam, ¶m) + { + HANDLER_CALLBACK pfnCleanupCallback = pParam->pHandler->m_pfnCleanupCallback; + + (*pfnCleanupCallback)(); + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + WCHAR wszMsg[128]; + swprintf_s(wszMsg, COUNTOF(wszMsg), L"HandlerAuxThreadProc caught exception %x", GetExceptionCode()); + ClrReportEvent(L".NET Runtime", + EVENTLOG_ERROR_TYPE, + 0, + 0, + NULL, + wszMsg); + } + PAL_ENDTRY + } + + + // Release all the handles + + if (m_hStartEnum != NULL) + { + CloseHandle(m_hStartEnum); + m_hStartEnum = NULL; + } + + if (m_hDoneEnum != NULL) + { + CloseHandle(m_hDoneEnum); + m_hDoneEnum = NULL; + } + + if (m_hShutdownThread != NULL) + { + CloseHandle(m_hShutdownThread); + m_hShutdownThread = NULL; + } + + m_pfnCallback = NULL; + m_pfnCleanupCallback = NULL; +} + +void IPCFuncCallHandler::TerminateFCHandler() +{ + // If the thread is in the process of shutting down then + // there is nothing to do + if (m_fShutdownAuxThread) + { + return; + } + + // If this IPCFuncCallHandler has not been initialized yet + // then there is nothing to do + if ((m_hStartEnum == NULL) && + (m_hDoneEnum == NULL) && + (m_hAuxThread.Load() == NULL) && + (m_pfnCallback == NULL)) + { + return; + } + + if(m_hAuxThread.Load() != NULL) + { + // Always resume the thread to make sure it is not suspended + if (ResumeThread(m_hAuxThread) == (DWORD)(-1)) + { + _ASSERTE (!"TerminateFCHandler: ResumeThread(m_hAuxThread) failed"); + } + + // First make sure that we make the aux thread gracefully exit + m_fShutdownAuxThread = TRUE; + + // Hope that this set event makes the thread quit. + if (!SetEvent (m_hShutdownThread)) + { + _ASSERTE (!"TerminateFCHandler: SetEvent(m_hShutdownThread) failed"); + } + } + else + { + // We failed during InitFCHandler before creating the auxilliary thread + SafeCleanup(); + + } + + // The aux thread is responsible for cleanup. When it is finished cleaning + // up it will set m_fShutdownAuxThread to FALSE. +} + +BOOL IPCFuncCallHandler::IsShutdownComplete() +{ + return m_hAuxThread.Load() == NULL; +} + +void IPCFuncCallHandler::WaitForShutdown() +{ + // Check to see if the thread handle is null. If it is then the thread has shut down, + // otherwise we will wait for the thread to exit. + HANDLE hThread = InterlockedExchangeT(&m_hAuxThread, NULL); + + if (hThread != NULL) + { + // Otherwise wait for the thread to complete and we close its handle. + DWORD result = WaitForSingleObject(hThread, INFINITE); + _ASSERTE(result == WAIT_OBJECT_0); + + CloseHandle(hThread); + } +} +#else // !FEATURE_PERFMON || !FEATURE_IPCMAN + + +// Telesto stubs + +//----------------------------------------------------------------------------- +// Wrap an unsafe call in a mutex to assure safety +// Biggest error issues are: +// 1. Timeout (probably handler doesn't exist) +// 2. Handler can be destroyed at any time. +//----------------------------------------------------------------------------- +IPCFuncCallSource::EError IPCFuncCallSource::DoThreadSafeCall() +{ + return Ok; +} + +#endif // FEATURE_PERFMON && FEATURE_IPCMAN |