bool Lock() { if (m_mutex->OpenOrCreate(s_mutexName) == false) { LogConsole(logERROR, "Could not create Mutex (%d).\n", osGetLastSystemError()); return (false); } if (m_mutex->Lock() == false) { LogConsole(logERROR, "Could not Lock Mutex (%d).\n", osGetLastSystemError()); return (false); } return true; }
//----------------------------------------------------------------------------- /// Lock global shared memory region for exclusive access. /// \return true if the shared memory could be locked; false otherwise //----------------------------------------------------------------------------- bool SharedGlobal::Lock(void) { PsAssert(this != NULL); PsAssert(m_Mutex != NULL); if (m_Mutex->lock() == false) { Log(logERROR, "Error occurred while waiting for Mutex :%d\n", osGetLastSystemError()); return false; } return true; }
//----------------------------------------------------------------------------- /// Initialize all class data. This should only be called once /// \return true if everything is properly initialized; false otherwise //----------------------------------------------------------------------------- bool SharedGlobal::Initialize() { // Create the Mutex used to control access to the Shared Memory block m_Mutex = new osMutex(); SharedMemory::MemStatus status = m_MapFile->OpenOrCreate(sizeof(PsSharedGlobal), SHARED_MEMORY_NAME); if (SharedMemory::ERROR_CREATE == status) { LogConsole(logERROR, "Could not create file mapping object (%d).\n", osGetLastSystemError()); return (false); } if (SharedMemory::ERROR_MAPPING == status) { LogConsole(logERROR, "Could not map view of file (%d).\n", osGetLastSystemError()); return (false); } m_bInitialized = true; return (true); }
// --------------------------------------------------------------------------- // Name: osGetLastSystemErrorAsString // Description: Outputs the last error code recorded by the operating system // for the calling thread, translated into a string. // Author: AMD Developer Tools Team // Date: 28/1/2008 // --------------------------------------------------------------------------- void osGetLastSystemErrorAsString(gtString& systemErrorAsString) { systemErrorAsString = OS_STR_unknownSystemError; // Get the system's last recorded error code: osSystemErrorCode systemLastError = osGetLastSystemError(); // If no system error was recorded: if (systemLastError == 0) { systemErrorAsString = OS_STR_noSystemError; } else { #if GR_LINUX_VARIANT == GR_GENERIC_LINUX_VARIANT // Get a string describing the system error: char buff[OS_SYSTEM_ERROR_BUFF_SIZE]; char* pErrMsg = strerror_r(systemLastError, buff, OS_SYSTEM_ERROR_BUFF_SIZE); if (pErrMsg != NULL) { // Output the string we got: systemErrorAsString.fromASCIIString(pErrMsg); } #elif GR_LINUX_VARIANT == GR_MAC_OS_X_LINUX_VARIANT // Get a string describing the system error: char buff[OS_SYSTEM_ERROR_BUFF_SIZE + 1]; int rc1 = strerror_r(systemLastError, buff, OS_SYSTEM_ERROR_BUFF_SIZE); if (rc1 == 0) { // Null-terminate the string: buff[OS_SYSTEM_ERROR_BUFF_SIZE] = '\0'; // Output the string we got: systemErrorAsString.fromASCIIString(buff); } #else #error Unknown Linux variant #endif } }
//--------------------------------------------------------- /// SendResponse /// /// Sends the response either over sockets or shared memory /// /// \param requestID the requestID to send the response to /// \param cpsMimeType the mimetype to send the response as /// \param cpsResponse the response to send /// \param uResponseSize the size of the response being sent /// \param bStreaming indicates that the response it to a streaming request /// /// \return true if the response is 'sent' correctly; false otherwise //--------------------------------------------------------- bool SendResponse(CommunicationID requestID, const char* cpsMimeType, const char* cpsResponse, unsigned int uResponseSize, bool bStreaming) { // find out if this is a streaming response if (bStreaming) { Log(logTRACE, "Sending response over socket\n"); // this is a streaming response // use the socket for comms return SendMimeResponse(requestID, cpsMimeType, cpsResponse, uResponseSize); } // use Shared memory if this is not a streaming response if (smLockPut("PLUGINS_TO_GPS", sizeof(requestID) + ((unsigned long) strlen(cpsMimeType) * sizeof(const char)) + uResponseSize, 3) == false) { Log(logASSERT, "Not enough space in shared memory for response.\n"); return false; } NamedSemaphore semaphore; bool opened = semaphore.Open("PLUGINS_TO_GPS_SEMAPHORE"); if (opened) { if (semaphore.Signal() == false) { Log(logWARNING, "Failed to signal PLUGINS_TO_GPS_SEMAPHORE. Response may be lost. Error is %d, Previous count is 0\n", osGetLastSystemError()); } semaphore.Close(); } else { Log(logWARNING, "Failed to open PLUGINS_TO_GPS_SEMAPHORE. Response may be delayed.\n"); } bool bResult = (smPut("PLUGINS_TO_GPS", &requestID, sizeof(requestID)) && smPut("PLUGINS_TO_GPS", (void*)cpsMimeType, (unsigned long) strlen(cpsMimeType) * sizeof(const char)) && smPut("PLUGINS_TO_GPS", (void*)cpsResponse, uResponseSize)); smUnlockPut("PLUGINS_TO_GPS"); if (bResult == false) { Log(logASSERT, "Failed to put part of the response into shared memory\n"); } else { // remove the request RemoveRequest(requestID); } return bResult; }
//----------------------------------------------------------------------------- bool OutputHTTPError(NetSocket* socket, int nErrorCode) { /// generate the error code html static char headerBuffer[COMM_BUFFER_SIZE]; static char htmlBuffer[COMM_BUFFER_SIZE]; sprintf_s(htmlBuffer, COMM_BUFFER_SIZE, "<html><body><h2>Error: %d</h2></body></html>", nErrorCode); sprintf_s(headerBuffer, COMM_BUFFER_SIZE, "HTTP/1.0 %d\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n", nErrorCode, strlen(htmlBuffer)); bool nRes1; bool nRes2; nRes1 = socket->Send(headerBuffer, (DWORD)strlen(headerBuffer)); nRes2 = socket->Send(htmlBuffer, (DWORD)strlen(htmlBuffer)); socket->close(); if ((nRes1 == false) || (nRes2 == false)) { Log(logERROR, "Failed to send HTTPError %d over socket %lu because of error %lu\n", nErrorCode, socket, osGetLastSystemError()); return false; } return true; }
//----------------------------------------------------------------------------- bool Send(Response& rResponse, const char* mime, const char* pData, unsigned long dwSize) { char sendbuffer[COMM_BUFFER_SIZE]; sendbuffer[0] = 0; // send the http header and the file contents to the browser if (rResponse.m_bNeedToSendHeader == true) { GenerateHeader(rResponse, sendbuffer, COMM_BUFFER_SIZE); rResponse.m_bNeedToSendHeader = false; } if (rResponse.m_bStreamingEnabled == true) { strncat_s(sendbuffer, COMM_BUFFER_SIZE, "--BoundaryString\r\n", COMM_BUFFER_SIZE); } DWORD len = (DWORD)strlen(sendbuffer); sprintf_s(sendbuffer + len, COMM_BUFFER_SIZE - len, "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "\r\n", mime, dwSize); // send header bool res = rResponse.client_socket->Send(sendbuffer, (DWORD)strlen(sendbuffer)); if (res == true) { // if header could be sent // send data res = rResponse.client_socket->Send(pData, dwSize); } else { osSystemErrorCode systemLastError = osGetLastSystemError(); // If no system error was recorded: if (systemLastError != 0) { gtString systemErrorString; osGetLastSystemErrorAsString(systemErrorString); Log(logERROR, "Failed to send %s response data due to error %d: %s\n", mime, systemLastError, systemErrorString.asASCIICharArray()); } // there was an error sending // so close the connection and return false CloseConnection(rResponse); return false; } // if the response is not streaming, // the connection should be closed // otherwise it should be left open if (rResponse.m_bStreamingEnabled == false) { CloseConnection(rResponse); } return true; }
//-------------------------------------------------------------- // LaunchAppInNewProcess //-------------------------------------------------------------- PROCESS_INFORMATION ProcessTracker::LaunchAppInNewProcess(gtASCIIString strApp, gtASCIIString strDir, gtASCIIString strArgs, osModuleArchitecture binaryType) { #ifdef _LINUX PS_UNREFERENCED_PARAMETER(binaryType); #endif LogConsole(logMESSAGE, "About to launch: %s\n", strApp.asCharArray()); LogConsole(logMESSAGE, "Params: %s\n", strArgs.asCharArray()); LogConsole(logMESSAGE, "Working Directory: %s\n", strDir.asCharArray()); // Get app directory and make it default if (strDir.isEmpty()) { size_t pos = strApp.find_last_of("\\"); if (pos != std::string::npos) { strDir = strApp.substr(0, (int)pos); if (strApp[0] == '\"') { strDir += "\""; } } } PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); #ifdef _WIN32 DWORD dwFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED; SetLastError(0); STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); #endif // Cmd line has to include the exe name since many apps expect the executable name to be in argv[0]! // Note argv[0] on the command line needs to be surrounded with quotes if it contains spaces. // The arguments in strArgs have already been "quoted" as they are parsed. gtASCIIString strCmdLine = AddQuotesIfStringHasSpaces(strApp.asCharArray()); strCmdLine += " "; strCmdLine += strArgs; LogConsole(logMESSAGE, "strApp: %s\n", strApp.asCharArray()); LogConsole(logMESSAGE, "strCmdLine: %s\n", strCmdLine.asCharArray()); // Attempt to initialize the environment that the new process will run in. The child process should inherit "this" environment. if (!PrelaunchEnvironmentInitialization()) { // Log a warning if this failed- initializing the environment for the new process can fail if Mantle support isn't installed. // In these cases, if the user is attempting to debug a Mantle application, they will have bigger problems to deal with. // In cases where a DX/GL app is being debugged, this warning can be ignored without any side effects. Log(logWARNING, "Environment initialization failed. If using DX/GL, it is safe to ignore this warning.\n"); } BOOL succeeded = FALSE; #ifdef _WIN32 char microDLLPath[PS_MAX_PATH]; const char* strServerPath; strServerPath = SG_GET_PATH(ServerPath); if (SG_GET_BOOL(OptionDllReplacement) == true) { DllReplacement::SetDllDirectory(binaryType == OS_X86_64_ARCHITECTURE); } // if using manual dll replacement or the AppInit_DLLs registry setting, don't use any kind of dll injection if (SG_GET_BOOL(OptionManualDllReplacement) == true || SG_GET_BOOL(OptionAppInitDll)) { succeeded = CreateProcess(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(), NULL, NULL, TRUE, dwFlags, NULL, strDir.asCharArray(), &si, &pi); } else { #ifdef X64 // can only launch 64 bit applications if (binaryType != OS_X86_64_ARCHITECTURE) { sprintf_s(microDLLPath, PS_MAX_PATH, "%s" MICRODLLNAME "%s%s.dll", SG_GET_PATH(ServerPath), GDT_DEBUG_SUFFIX, GDT_BUILD_SUFFIX); succeeded = AMDT::CreateProcessAndInjectDll(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(), NULL, NULL, TRUE, dwFlags, NULL, strDir.asCharArray(), &si, &pi, microDLLPath); } #else if (binaryType != OS_I386_ARCHITECTURE) { sprintf_s(microDLLPath, PS_MAX_PATH, "%s" MICRODLLNAME "-x64%s%s.dll", SG_GET_PATH(ServerPath), GDT_DEBUG_SUFFIX, GDT_BUILD_SUFFIX); succeeded = AMDT::CreateProcessAndInjectDll(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(), NULL, NULL, TRUE, dwFlags, NULL, strDir.asCharArray(), &si, &pi, microDLLPath); } #endif // X64 else { succeeded = AMDT::CreateProcessAndInjectDll(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(), NULL, NULL, TRUE, dwFlags, NULL, strDir.asCharArray(), &si, &pi, SG_GET_PATH(MicroDLLPath)); } } #else // Create the app process succeeded = CreateProcess(strApp.asCharArray(), strCmdLine.asCharArray(), strDir.asCharArray(), &pi); #endif // _WIN32 if (!succeeded) { osSystemErrorCode systemLastError = osGetLastSystemError(); gtString systemErrorString; osGetLastSystemErrorAsString(systemErrorString); Log(logERROR, "CreateProcessAndInjectDll failed; Error %d: %s\n", systemLastError, systemErrorString.asASCIICharArray()); pi.dwProcessId = 0; } #ifdef _WIN32 else { // Check to see if the Steam.exe has been hooked and if so, set the value in shared memory // If Steam.exe was used to launch the target application, then the checks for cmd.exe and fcx.exe // need to be ignored. if (strApp.length() > 0) { if (strstr(strApp.toLowerCase().asCharArray(), "steam.exe") != NULL) { SG_SET_BOOL(SteamInjected, true); } ShowLauncherReminder(strApp.toLowerCase().asCharArray()); } else { if (strstr(strCmdLine.toLowerCase().asCharArray(), "steam.exe") != NULL) { SG_SET_BOOL(SteamInjected, true); } ShowLauncherReminder(strCmdLine.toLowerCase().asCharArray()); } } #endif // _WIN32 return pi; }
//-------------------------------------------------------------- /// Writes information about the allowed plugins into shared memory /// and launches the application specified in s_strInjectedAppName /// \return true if the app is successfully launched; false otherwise //-------------------------------------------------------------- bool ProcessTracker::WritePluginsToSharedMemoryAndLaunchApp() { bool bGetType = false; if (OSDependentModulesInitialization() == false) { Log(logERROR, "Failed to initialize for plugins\n"); return false; } osModuleArchitecture binaryType; bGetType = OSWrappers::GetBinaryType(m_injectedAppName.c_str(), &binaryType); #ifdef _WIN32 if (!bGetType) { // try without quotes, should be W8x preference. This will update m_injectedAppName for later use also. m_injectedAppName.erase(0, 1); m_injectedAppName.resize(m_injectedAppName.size() - 1); bGetType = OSWrappers::GetBinaryType(m_injectedAppName.c_str(), &binaryType); } #endif // def _WIN32 if (!bGetType) { // file is not executable LogConsole(logERROR, "%s is not an executable file\n", m_injectedAppName.c_str()); return false; } Log(logMESSAGE, "WritePluginsToSharedMemoryAndLaunchApp()\n"); if (false == CreateSharedMemory()) { return false; } // now actually start the application PROCESS_INFORMATION pi = LaunchAppInNewProcess(m_injectedAppName.c_str(), m_injectedAppDir.c_str(), m_injectedAppArgs.c_str(), binaryType); if (pi.dwProcessId == 0) { LogConsole(logWARNING, "Failed to start application: %s %s\n\n", m_injectedAppName.c_str(), m_injectedAppArgs.c_str()); return false; } Log(logMESSAGE, "About to resume application thread\n"); #ifdef _WIN32 if (osResumeSuspendedProcess(0, 0, pi.hThread, false) == false) { osSystemErrorCode systemLastError = osGetLastSystemError(); gtString systemErrorString; osGetLastSystemErrorAsString(systemErrorString); Log(logERROR, "Resuming thread failed; Error %d: %s\n", systemLastError, systemErrorString.asASCIICharArray()); } #endif m_injectedAppID = pi.dwProcessId; return true; }
//////////////////////////////////////////////////////////////////////////////////////////// /// Passes the supplied request header to the specifed plugin /// \param strDestination the short description of the plugin to pass the request to /// \param pRequestHeader the request header to pass to the plugin /// \param pid the process id to pass the request to. /// \param pClientSocket the socket used to read the request /// \return true if the request was passed to the plugin; false otherwise //////////////////////////////////////////////////////////////////////////////////////////// bool ProcessTracker::PassRequestToPlugin(const char* strDestination, HTTPRequestHeader* pRequestHeader, unsigned long pid, NetSocket* pClientSocket) { if (strstr(pRequestHeader->GetHeaderData()->url, STR_STREAM_TOKEN) != NULL) { Log(logTRACE, "Duplicating Socket for request: %s\n", pRequestHeader->GetHeaderData()->url); // this is a streaming request, duplicate the socket // to reduce the overhead incurred by the shared memory approach #if defined (_WIN32) if (pClientSocket->DuplicateToPID(pid, &pRequestHeader->GetHeaderData()->ProtoInfo) != 0) #else PS_UNREFERENCED_PARAMETER(pid); if (CreateStreamSocket(pRequestHeader, pClientSocket) == false) #endif { Log(logERROR, "Failed to duplicate socket for streaming request. Error %lu\n", osGetLastSystemError()); return false; } } bool bResult = false; if (smLockPut(strDestination, sizeof(HTTPRequestHeader), 1)) { // Check to see if the postdata failed to come over in some way. if (pRequestHeader->GetPostDataSize() > 0 && pRequestHeader->GetPostData() == NULL) { Log(logERROR, "PostData size is %d, but PostData is NULL\n", pRequestHeader->GetPostDataSize()); // Force the data size to zero pRequestHeader->SetPostDataSize(0); } // Create a new record of this request // We will check to see when it comes back from the server. //RequestInFlight* pNewRequest = new RequestInFlight(pRequestHeader, pClientSocket); // Add the new record to the DB. //RequestsInFlightDatabase::Instance()->Add(pClientSocket, pNewRequest); bResult = smPut(strDestination, pRequestHeader->GetHeaderData(), sizeof(HTTPHeaderData)); // Keep in for debugging //StreamLog::Ref() << "1) smPut HTTPRequestHeader\n" ; // If there is POST data we must send that too. if (pRequestHeader->GetPostDataSize() > 0 && pRequestHeader->GetPostData() != NULL) { bResult = smPut(strDestination, pRequestHeader->GetPostData(), pRequestHeader->GetPostDataSize()); // Keep in for debugging //StreamLog::Ref() << "2) smPut Shader Code: " << pRequestHeader->pPostData << "\n"; } smUnlockPut(strDestination); #ifdef DEBUG_COMMS_PERFORMANCE CommandTimingManager::Instance()->IncrementServerLoadingCount(1) ; #endif } #ifdef DEBUG_COMMS_PERFORMANCE // Record the time now that we have sent the command over the shared memory. CommandTiming* cmdTiming = CommandTimingManager::Instance()->GetTimingFromPendingList(pClientSocket); if (cmdTiming != NULL) { LARGE_INTEGER nPerformanceCount; QueryPerformanceCounter(&nPerformanceCount); cmdTiming->SetPostSharedMemorySend(nPerformanceCount); // Set the current server loading value cmdTiming->SetServerLoadingCount(CommandTimingManager::Instance()->GetServerLoadingCount()); } #endif return bResult; }
//-------------------------------------------------------------- /// Creates and then waits for the PLUGINS_TO_GPS_SEMAPHORE to be /// signaled, then reads the responses in PLUGINS_TO_GPS shared /// memory and sends them to the requester. /// \param pData should be NULL (it is ignored though) //-------------------------------------------------------------- void PluginResponseThread::WaitForPluginResponses(void* pData) { PS_UNREFERENCED_PARAMETER(pData); //create a semaphore which allows putting at most MAX_SEM_COUNT response into SM; NamedSemaphore semaphore; semaphore.Create("PLUGINS_TO_GPS_SEMAPHORE"); bool bEvent; for (;;) // loop forever { bEvent = semaphore.Wait(); if (bEvent == false) { Log(logERROR, "Failed to wait on an event (Error %d). Closing response thread.\n", osGetLastSystemError()); smClose("PLUGINS_TO_GPS"); semaphore.Close(); return; } // the PLUGINS_TO_GPS_EVENT was signaled // retrieve and send the response if (smLockGet("PLUGINS_TO_GPS")) { // read data from shared memory CommunicationID requestID = 0; char pcMimeType[PS_MAX_PATH]; // the response was signaled, read out one response if (bEvent == true) { requestID = 0; memset(pcMimeType, 0, PS_MAX_PATH); char* pResponse = NULL; unsigned long uResponseSize = 0; LARGE_INTEGER nPreSharedMemoryGetTime; OSWrappers::QueryPerformanceCounter(&nPreSharedMemoryGetTime); if (smGet("PLUGINS_TO_GPS", &requestID, sizeof(CommunicationID)) == sizeof(CommunicationID)) { // successfully got the requestID // Get the socket associated with this requestID and remove the socket since we're now donw with it. NetSocket* client_socket = ProcessTracker::Instance()->GetSocketFromHandle(requestID); ProcessTracker::Instance()->RemoveSocketFromMap(requestID); #ifdef CODEXL_GRAPHICS #ifdef USE_GRAPHICS_SERVER_STATUS_RETURN_CODES // We can remove this message from the DB as we no longer need to monitor it anymore RequestsInFlightDatabase::Instance()->Remove(client_socket); #endif #endif // now try to get the mime type if (smGet("PLUGINS_TO_GPS", &pcMimeType, PS_MAX_PATH) > 0) { // successfully got the mime type // get response size while (uResponseSize == 0) { uResponseSize = smGet("PLUGINS_TO_GPS", NULL, 0); } try { pResponse = new char[uResponseSize]; memset(pResponse, 0, uResponseSize * sizeof(char)); } catch (std::bad_alloc) { Log(logERROR, "Failed to allocate memory for response of size: %lu\n", uResponseSize); pResponse = NULL; } if (pResponse != NULL) { // Read from shared memory if (smGet("PLUGINS_TO_GPS", pResponse, uResponseSize) == 0) { Log(logERROR, "Failed to get response from sharedMemory.\n"); smReset("PLUGINS_TO_GPS"); } #ifdef DEBUG_COMMS_PERFORMANCE // Record the time now CommandTiming* pTiming = CommandTimingManager::Instance()->HandleResponse((NetSocket*)requestID); if (pTiming != NULL) { LARGE_INTEGER nPerformanceCount; QueryPerformanceCounter(&nPerformanceCount); pTiming->SetWebServerRoundTripEnd(nPerformanceCount); // Must set this one before SetPreSharedMemoryGet pTiming->SetPreSharedMemoryGet(nPreSharedMemoryGetTime); pTiming->SetResponseSize(uResponseSize); } #endif // Send the data back to the client SendMimeResponse(requestID, pcMimeType, pResponse, uResponseSize, client_socket); #ifdef DEBUG_COMMS_PERFORMANCE CommandTimingManager::Instance()->IncrementServerLoadingCount(-1); #endif SAFE_DELETE_ARRAY(pResponse); } else { smReset("PLUGINS_TO_GPS"); SendMimeResponse(requestID, "plain/text", "Error: Failed to get response from shared memory\n", 49 * sizeof(char), client_socket); } } else { smReset("PLUGINS_TO_GPS"); SendMimeResponse(requestID, "plain/text", "Error: Could not read mime type\n", 32 * sizeof(char), client_socket); } } else { // in this case, we don't know socket the communication was on, so we can't send any errors or even close the socket // just have to let the client time out. smReset("PLUGINS_TO_GPS"); Log(logERROR, "Failed to get requestID from sharedMemory.\n"); } } // end while sm has data or no responses have been read smUnlockGet("PLUGINS_TO_GPS"); } else { Log(logERROR, "LockGet Failed\n"); } } }