//-------------------------------------------------------------------------- /// Before launching a new process, this function will initialize the environment for the /// new process. /// \return true if the the environment for the new process was initialized fully. //-------------------------------------------------------------------------- bool ProcessTracker::PrelaunchEnvironmentInitialization() { // Start by assuming that this will work without a problem. bool bEnvironmentInitialized = true; #ifndef _WIN32 // Get the server path. The GLServer plugin will be in the same place. // LD_PRELOAD has to be set up before the app is run so the plugin can be attached const char* strServerPath; strServerPath = SG_GET_PATH(ServerPath); char ext[PS_MAX_PATH]; sprintf_s(ext, PS_MAX_PATH, "%s.so", GDT_PROJECT_SUFFIX); char strLdPreload[PS_MAX_PATH]; sprintf_s(strLdPreload, PS_MAX_PATH, "%sPlugins/%sServer%s", strServerPath, m_injectedAppPluginName.c_str(), ext); // LogConsole(logMESSAGE, "LD_PRELOAD is >>>%s<<<\n", strLdPreload); // Set the LD_PRELOAD environment variable setenv("LD_PRELOAD", strLdPreload, 1); #endif #if ENABLE_VULKAN SetupVulkanEnvVariables(); #endif return bEnvironmentInitialized; }
// Returns reference to file that is currently being used. // const char* GetLogFilename() { if (SG_GET_BOOL(OptionNoLogfile)) { return (NULL); } else { return (SG_GET_PATH(LogfilePath)); } }
//-------------------------------------------------------------------------- /// Initialize GPA. /// \param inAPI The API being initialized. /// \returns True if GPA initialization was successful. //-------------------------------------------------------------------------- bool ModernAPIFrameProfilerLayer::InitializeGPA(GPA_API_Type inAPI) { const char* errorMessage = NULL; bool bLoadSuccessful = mGPALoader.Load(SG_GET_PATH(GPUPerfAPIPath), inAPI, &errorMessage); if (!bLoadSuccessful) { Log(logERROR, "Failed to load GPA. Load error: %s\n", errorMessage); } else { if (mGPALoader.GPA_RegisterLoggingCallback(GPA_LOGGING_ERROR_AND_MESSAGE, (GPA_LoggingCallbackPtrType)&ModernAPIFrameProfilerLayer::GPALoggingCallback) != GPA_STATUS_OK) { Log(logERROR, "Failed to register profiler logging callback.\n"); } } return bLoadSuccessful; }
//-------------------------------------------------------------- /// Is micro dll allowed to be injected into this process? /// check the current executable with the filelist specified. If /// it is in the list, or the list contains 'all', then injection /// is allowed. /// \param modulename name of the executable being started /// \returns true if injection is allowed, false otherwise static bool InjectionAllowed(const char* modulename) { // if not using AppInit_DLLs registry key, always allow injection if (SG_GET_BOOL(OptionAppInitDll) == false) { return true; } // get the filename from the path. If there's an error here, don't inject char path[PS_MAX_PATH]; char* filename; DWORD res = GetFullPathName(modulename, PS_MAX_PATH, path, &filename); if (res == 0) { return false; } gtASCIIString fileString = SG_GET_PATH(AppInitDllFileList); // if 'all' specified, always inject if (fileString.compareNoCase("all") == 0) { return true; } // split the list up and see if the current exe is in the list std::list<gtASCIIString> fileList; fileString.Split(",", false, fileList); for (std::list<gtASCIIString>::const_iterator it = fileList.begin(); it != fileList.end(); ++it) { if ((*it).compareNoCase(filename) == 0) { // file is in list, inject 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; }
//-------------------------------------------------------------- /// 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; }