//----------------------------------------------------------------------------- /// Called to indicate that a resource is being destroyed /// detaches from anything that was attached in OnCreate /// \param type the type of resource that is being destroyed /// \param pPtr pointer to the resource that is being destroyed /// \return should return false on error; true otherwise //----------------------------------------------------------------------------- bool DX12FrameProfilerLayer::OnDestroy(CREATION_TYPE type, void* pPtr) { PS_UNREFERENCED_PARAMETER(pPtr); PS_UNREFERENCED_PARAMETER(type); return true; }
//-------------------------------------------------------------------------- /// Invoked when the MantleTraceAnalyzerLayer is created. /// \param inType The incoming type of interface being created. /// \param pInPtr An interface pointer being created. /// \returns True if creation was successful. //-------------------------------------------------------------------------- bool MultithreadedTraceAnalyzerLayer::OnCreate(CREATION_TYPE inType, void* pInPtr) { PS_UNREFERENCED_PARAMETER(inType); PS_UNREFERENCED_PARAMETER(pInPtr); return true; }
//-------------------------------------------------------------------------- /// A handler invoked when a key is pressed within the instrumented application. /// \param inCode The code for the keypress event. /// \param wParam The WPARAM for the keypress event. Contains the virtual key code for the pressed key. /// \param lParam The LPARAM for the keypress event. Contains flags for the key press event. //-------------------------------------------------------------------------- void ModernAPILayerManager::OnKeyPressed(int inCode, WPARAM wParam, LPARAM lParam) { PS_UNREFERENCED_PARAMETER(inCode); PS_UNREFERENCED_PARAMETER(lParam); // If the capture key was pressed, we need to trigger a capture. if (wParam == kPrintScreenVirtualKeyCode) { mbTraceTriggeredFromKeypress = true; } }
//-------------------------------------------------------------------------- /// Called by the CommandProcessor to give the CommandResponse a chance to /// parse all of the command parameters before the app can call IsActive() /// \param rCommObj incoming command which has the parameters that need to /// be parsed /// \return true if the params could be parsed; false otherwise //-------------------------------------------------------------------------- bool CommandResponse::GetParams(CommandObject& rCommObj) { PS_UNREFERENCED_PARAMETER(rCommObj); /// the default CommandResponse has no parameters, /// so there is nothing to do here. /// this should be overwritten when CommandResponse is inherited return true; }
//----------------------------------------------------------------------------- /// Called to indicate that a resource is being created /// The layer must create its resources and hook functions here /// \param type the type of resource that is being created /// \param pPtr pointer to the resource that is being created /// \return should return false on error; true otherwise //----------------------------------------------------------------------------- bool DX12FrameProfilerLayer::OnCreate(CREATION_TYPE type, void* pPtr) { PS_UNREFERENCED_PARAMETER(type); PS_UNREFERENCED_PARAMETER(pPtr); bool bInitSuccessful = false; // Don't initialize the Frame Profiler more than once. static bool bAlreadyInitialized = false; if (bAlreadyInitialized == false) { bInitSuccessful = true; bAlreadyInitialized = true; } return bInitSuccessful; }
/// Debug function used to filter out specific commands. /// \param strCmd Command string /// \return True if allowed, false if not bool AllowCommand(char* strCmd) { PS_UNREFERENCED_PARAMETER(strCmd); return true; // Leave commented out while developing //if ( strstr ( strCmd, "IndexBufferInfo.xml" ) != NULL ) //{ // return true; //} //if ( strstr ( strCmd, "BreakPoint" ) != NULL ) //{ // return true; //} //return false; }
//-------------------------------------------------------------- // 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; }
bool ProcessTracker::HandleRequest(HTTPRequestHeader* pRequestHeader, CommunicationID requestID, NetSocket* pClientSocket, bool renderLoopStalled) { char* ptr = pRequestHeader->GetUrl(); char* sCmd = &ptr[1]; #ifdef CODEXL_GRAPHICS #ifdef USE_GRAPHICS_SERVER_STATUS_RETURN_CODES // Handle process not running condition if (pRequestHeader->CheckProcessStillRunning() == false) { Log(logMESSAGE, "Rejecting the command above due to process no longer running: %s\n", pRequestHeader->GetUrl()); // Need to return the correct error data for process not running HandleServerStatusResponse(GRAPHICS_SERVER_STATE_PROCESS_NOT_RUNNING, pRequestHeader, pClientSocket); // This request never gets passed to the graphics server. return true; } // Check if renering has stalled. if (renderLoopStalled == true) { Log(logMESSAGE, "Rejecting the command above due to render stall: %s\n", pRequestHeader->GetUrl()); // Need to return the correct error data for process not running HandleServerStatusResponse(GRAPHICS_SERVER_STATE_STALLED, pRequestHeader, pClientSocket); // This request never gets passed to the graphics server. return true; } #else UNREFERENCED_PARAMETER(renderLoopStalled); #endif #else PS_UNREFERENCED_PARAMETER(renderLoopStalled); #endif #ifdef _WIN32 // if using AppInit_Dll, clear the registry as soon as a process.xml request is sent. // TODO: move this to where a connection has definately been made if (SG_GET_BOOL(OptionAppInitDll) == true) { if (registryCleared == false) { RestoreAppInit(); registryCleared = true; } } #endif if (IsToken(&sCmd, "inject?")) { DoInjectCommand(requestID, &sCmd, pClientSocket); } else { // do commands that depend on PID for (WrapperMap::iterator wrapperIter = g_activeWrappersMap.begin(); wrapperIter != g_activeWrappersMap.end(); ++wrapperIter) { // parse out the process ID unsigned long pid = 0; sscanf_s(wrapperIter->first.c_str(), "%lu/", &pid); // make PID string for easier parsing of the command gtASCIIString strPID; strPID.appendFormattedString("%lu", pid); gtASCIIString strPidSlashPlugin = wrapperIter->first.c_str(); // the key in the activeWrappersMap is formatted as pid/plugin (ie: "435/DX11") // and this conveniently matches the format of the commands coming into the server, so parse for matching commands gtASCIIString tmpString = strPidSlashPlugin; tmpString += "/"; if (IsToken(&sCmd, tmpString.asCharArray())) { // we know this command is targetting the current plugin pRequestHeader->SetUrl(sCmd); // pass the request to the plugin #ifdef _WIN32 const char* memoryName = strPidSlashPlugin.asCharArray(); #else // the '/' character can't be used as a filename in Linux, so just use the plugin name as the shared memory name // (ignore the process ID) char memoryName[PS_MAX_PATH]; char pluginShortDesc[ PS_MAX_PATH ]; sscanf_s(strPidSlashPlugin.asCharArray(), "%lu/%s", &pid, pluginShortDesc, sizeof(pluginShortDesc)); sprintf_s(memoryName, PS_MAX_PATH, "%lu %s", pid, pluginShortDesc); #endif if (PassRequestToPlugin(memoryName, pRequestHeader, pid, pClientSocket)) { return true; } else { Log(logERROR, "Request '%s' is not targeted to an active plugin.\n", GetRequestText(requestID)); SendHTMLResponse(requestID, "<html>Error: Targeted plugin is not currently active.</html>", pClientSocket); } } else { tmpString = strPID; tmpString += "/Kill"; if (IsToken(&sCmd, tmpString.asCharArray())) { if (KillProcess(pid)) { SendTextResponse(requestID, "Ok", pClientSocket); // if the app was launched from the command line or drag and drop // then killing the app should cause the server to shutdown also if (g_bAppSpecifiedAtCmdLine) { g_shutdownEvent.Signal(); CloseStreamThread(); CloseStreamSockets(); SendTextResponse(requestID, "OK", pClientSocket); return true; } } else { Log(logERROR, "Failed to Kill the process\n"); SendTextResponse(requestID, "Error: Failed to Kill the process.", pClientSocket); } } } } } return false; }
//////////////////////////////////////////////////////////////////////////////////////////// /// 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"); } } }
//-------------------------------------------------------------- /// DllMain /// \param hModule Module /// \param ul_reason_for_call Reason for call /// \param lpReserved Reserved /// \return True or false //-------------------------------------------------------------- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { PS_UNREFERENCED_PARAMETER(hModule); PS_UNREFERENCED_PARAMETER(lpReserved); if (AMDT::InitHookDLL(ul_reason_for_call) == false) { return TRUE; } BOOL retVal = TRUE; char modulename[MAX_PATH]; GetModuleFileNameA(NULL, modulename, MAX_PATH); switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { if (OSWrappers::IsProcessRunning(sWebServerName, true) && InjectionAllowed(modulename)) { Log(logMESSAGE, "Attaching to %s\n", modulename); Log(logMESSAGE, "DllMain DLL_PROCESS_ATTACH module %s\n", modulename); if (SG_GET_BOOL(OptionDllReplacement) == true) { UpdateHooksOnLoadLibrary(); } #ifdef _WIN32 { // Initialize and register the unhandled exception handler: bool rc1 = UnhandledExceptionHandler::init(); if (rc1) { Log(logMESSAGE, "Registered unhandled exception handler\n"); } else { Log(logERROR, "Failed to register unhandled exception handler\n"); } } #endif // @Note: Do we need to do this? Overwriting it doesn't seem to break anything? // get current directory so we can specify as a location for DLLs to be loaded from char curDir[PS_MAX_PATH]; GetCurrentDirectory(PS_MAX_PATH, curDir); // SetDllDirectory requires XP SP1 or later. SetDllDirectory(curDir); sprintf_s(g_MicroDLLPath, PS_MAX_PATH, "%s", SG_GET_PATH(MicroDLLPath)); CollectWrapperInfo(); if (SG_GET_BOOL(OptionNoProcessTrack) == false) { HookCreateProcess(); Log(logMESSAGE, "Process Tracking is ON\n"); } else { Log(logMESSAGE, "Process Tracking is OFF\n"); } // At the moment, dxgi.dll is implicitly loaded from HookLoadLibrary // Force the Dll folder to point to our replaced dll's so the replace version // of dxgi.dll is loaded if (SG_GET_BOOL(OptionDllReplacement) == true) { // Get architecture of parent application (32 or 64 bit) osModuleArchitecture binaryType; OSWrappers::GetBinaryType(modulename, &binaryType); DllReplacement::SetDllDirectory(binaryType == OS_X86_64_ARCHITECTURE); } if (SG_GET_BOOL(OptionManualDllReplacement) == false) { HookLoadLibrary(); } } else { // set return value to FALSE. This will indicate a load error so the loader will // next unload this dll retVal = FALSE; } break; } case DLL_THREAD_ATTACH: { Log(logMESSAGE, "DllMain DLL_THREAD_ATTACH to %i %s\n", GetCurrentProcessId(), modulename); } break; case DLL_THREAD_DETACH: { Log(logMESSAGE, "DllMain DLL_THREAD_DETACH to %i %s\n", GetCurrentProcessId(), modulename); } break; case DLL_PROCESS_DETACH: { if (OSWrappers::IsProcessRunning(sWebServerName, true)) { Log(logMESSAGE, "DllMain DLL_PROCESS_DETACH from module %s\n", modulename); if (SG_GET_BOOL(OptionNoProcessTrack) == false) { UnhookCreateProcess(); } UnhookLoadLibrary(); } } break; default: { Log(logMESSAGE, "DllMain Unhandled switch case module %s\n", modulename); } break; } return retVal; }