// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved. #if LC_VERSION == 1 // BEGIN EPIC MOD //#include PCH_INCLUDE // END EPIC MOD #include "LC_ClientUserCommandThread.h" #include "LC_CommandMap.h" #include "LC_DuplexPipeClient.h" #include "LC_ClientCommandActions.h" #include "LC_Event.h" #include "LC_Process.h" #include "LC_CriticalSection.h" // BEGIN EPIC MOD //#include "LC_ClientExceptionHandler.h" // END EPIC MOD #include "LC_StringUtil.h" #include "LC_Executable.h" #include "LC_MemoryStream.h" // BEGIN EPIC MOD #include "LC_Platform.h" #include "LC_Foundation_Windows.h" #include "LC_Thread.h" #include "LC_Logging.h" #include #include // END EPIC MOD namespace { template class ProxyCommand : public ClientUserCommandThread::BaseCommand { public: ProxyCommand(bool expectResponse, size_t payloadSize) : BaseCommand(expectResponse) , m_command() , m_payload(payloadSize) { } virtual void Execute(DuplexPipe* pipe) override { pipe->SendCommandAndWaitForAck(m_command, m_payload.GetData(), m_payload.GetSize()); } T m_command; memoryStream::Writer m_payload; LC_DISABLE_COPY(ProxyCommand); LC_DISABLE_MOVE(ProxyCommand); LC_DISABLE_ASSIGNMENT(ProxyCommand); LC_DISABLE_MOVE_ASSIGNMENT(ProxyCommand); }; // gathers module data for the given module, its import modules, the import's import modules, and so forth // BEGIN EPIC MOD static std::vector GatherImportModuleData(Windows::HMODULE mainModule) // END EPIC MOD { // BEGIN EPIC MOD std::vector moduleDatas; // END EPIC MOD moduleDatas.reserve(1024u); // BEGIN EPIC MOD std::unordered_set loadedModules; // END EPIC MOD loadedModules.reserve(1024u); // BEGIN EPIC MOD std::vector modules; // END EPIC MOD modules.reserve(1024u); modules.push_back(mainModule); while (!modules.empty()) { const Windows::HMODULE module = modules.back(); modules.pop_back(); // get the absolute path of the module. // this automatically takes care of API sets used by Windows 7 and later. in a nutshell, these API sets // allow redirection of an API DLL to an underlying OS DLL, e.g. api-ms-win-core-apiquery-l1-1-0.dll redirects // to ntdll.dll. wchar_t fullPath[MAX_PATH]; ::GetModuleFileNameW(module, fullPath, MAX_PATH); // did we load the imports of this module already? auto findIt = loadedModules.find(fullPath); if (findIt == loadedModules.end()) { loadedModules.insert(fullPath); // add data for this module { commands::ModuleData moduleData = {}; moduleData.base = module; wcscpy_s(moduleData.path, fullPath); moduleDatas.emplace_back(moduleData); } executable::Image* image = executable::OpenImage(fullPath, Filesystem::OpenMode::READ); if (image) { executable::ImageSectionDB* imageSections = executable::GatherImageSectionDB(image); if (imageSections) { executable::ImportModuleDB* importModules = executable::GatherImportModuleDB(image, imageSections); if (importModules) { for (size_t i = 0; i < importModules->modules.size(); ++i) { const char* importModulePath = importModules->modules[i].path; // only add the import module if it is loaded into the process // BEGIN EPIC MOD Windows::HMODULE importModule = ::GetModuleHandleA(importModulePath); // END EPIC MOD if (importModule) { modules.push_back(importModule); } else { LC_ERROR_USER("Cannot enable module %s because it is not loaded by this process.", importModulePath); } } executable::DestroyImportModuleDB(importModules); } executable::DestroyImageSectionDB(imageSections); } executable::CloseImage(image); } } } return moduleDatas; } } ClientUserCommandThread::BaseCommand::BaseCommand(bool expectResponse) : m_expectResponse(expectResponse) { } ClientUserCommandThread::BaseCommand::~BaseCommand(void) { } bool ClientUserCommandThread::BaseCommand::ExpectsResponse(void) const { return m_expectResponse; } ClientUserCommandThread::ClientUserCommandThread(DuplexPipeClient* pipeClient, DuplexPipeClient* exceptionPipeClient) : m_thread(Thread::INVALID_HANDLE) , m_processGroupName() , m_pipe(pipeClient) , m_exceptionPipe(exceptionPipeClient) , m_userCommandQueue() , m_userCommandQueueCS() , m_userCommandQueueSema(0, 65535u) { } ClientUserCommandThread::~ClientUserCommandThread(void) { } Thread::Id ClientUserCommandThread::Start(const std::wstring& processGroupName, Event* waitForStartEvent, CriticalSection* pipeAccessCS) { m_processGroupName = processGroupName; // spawn a thread that does the work // BEGIN EPIC MOD m_thread = Thread::CreateFromMemberFunction("Live coding user commands", 128u * 1024u, this, &ClientUserCommandThread::ThreadFunction, waitForStartEvent, pipeAccessCS); // END EPIC MOD return Thread::GetId(m_thread); } void ClientUserCommandThread::Join(void) { if (m_thread != Thread::INVALID_HANDLE) { Thread::Join(m_thread); Thread::Close(m_thread); } } void* ClientUserCommandThread::EnableModule(const wchar_t* nameOfExeOrDll) { return EnableModules(&nameOfExeOrDll, 1u); } void* ClientUserCommandThread::EnableModules(const wchar_t* namesOfExeOrDll[], unsigned int count) { // BEGIN EPIC MOD std::vector loadedModules; // END EPIC MOD loadedModules.reserve(count); for (unsigned int i = 0u; i < count; ++i) { // BEGIN EPIC MOD Windows::HMODULE module = ::GetModuleHandleW(namesOfExeOrDll[i]); // END EPIC MOD if (module) { loadedModules.push_back(module); } else { LC_ERROR_USER("Cannot enable module %S because it is not loaded by this process.", namesOfExeOrDll[i]); } } const size_t loadedModuleCount = loadedModules.size(); if (loadedModuleCount == 0u) { // nothing to load return nullptr; } ProxyCommand* proxy = new ProxyCommand(true, sizeof(commands::ModuleData) * loadedModuleCount); proxy->m_command.processId = Process::Current::GetId(); proxy->m_command.moduleCount = static_cast(loadedModuleCount); proxy->m_command.token = new Event(nullptr, Event::Type::AUTO_RESET); for (size_t i = 0u; i < loadedModuleCount; ++i) { // BEGIN EPIC MOD Windows::HMODULE module = loadedModules[i]; // END EPIC MOD commands::ModuleData moduleData = {}; moduleData.base = module; ::GetModuleFileNameW(module, moduleData.path, MAX_PATH); proxy->m_payload.Write(moduleData); } PushUserCommand(proxy); return proxy->m_command.token; } void* ClientUserCommandThread::EnableAllModules(const wchar_t* nameOfExeOrDll) { // BEGIN EPIC MOD Windows::HMODULE module = ::GetModuleHandleW(nameOfExeOrDll); // END EPIC MOD if (!module) { LC_ERROR_USER("Cannot enable module %S because it is not loaded by this process.", nameOfExeOrDll); return nullptr; } // BEGIN EPIC MOD const std::vector& allModuleData = GatherImportModuleData(module); // END EPIC MOD const size_t moduleCount = allModuleData.size(); ProxyCommand* proxy = new ProxyCommand(true, sizeof(commands::ModuleData) * moduleCount); proxy->m_command.processId = Process::Current::GetId(); proxy->m_command.moduleCount = static_cast(moduleCount); proxy->m_command.token = new Event(nullptr, Event::Type::AUTO_RESET); for (size_t i = 0u; i < moduleCount; ++i) { const commands::ModuleData& moduleData = allModuleData[i]; proxy->m_payload.Write(moduleData); } PushUserCommand(proxy); return proxy->m_command.token; } void* ClientUserCommandThread::DisableModule(const wchar_t* nameOfExeOrDll) { return DisableModules(&nameOfExeOrDll, 1u); } void* ClientUserCommandThread::DisableModules(const wchar_t* namesOfExeOrDll[], unsigned int count) { // BEGIN EPIC MOD std::vector loadedModules; // END EPIC MOD loadedModules.reserve(count); for (unsigned int i = 0u; i < count; ++i) { // BEGIN EPIC MOD Windows::HMODULE module = ::GetModuleHandleW(namesOfExeOrDll[i]); // END EPIC MOD if (module) { loadedModules.push_back(module); } else { LC_ERROR_USER("Cannot disable module %S because it is not loaded by this process.", namesOfExeOrDll[i]); } } const size_t loadedModuleCount = loadedModules.size(); if (loadedModuleCount == 0u) { // nothing to unload return nullptr; } ProxyCommand* proxy = new ProxyCommand(true, sizeof(commands::ModuleData) * loadedModuleCount); proxy->m_command.processId = Process::Current::GetId(); proxy->m_command.moduleCount = static_cast(loadedModuleCount); proxy->m_command.token = new Event(nullptr, Event::Type::AUTO_RESET); for (size_t i = 0u; i < loadedModuleCount; ++i) { // BEGIN EPIC MOD Windows::HMODULE module = loadedModules[i]; // END EPIC MOD commands::ModuleData moduleData = {}; moduleData.base = module; ::GetModuleFileNameW(module, moduleData.path, MAX_PATH); proxy->m_payload.Write(moduleData); } PushUserCommand(proxy); return proxy->m_command.token; } void* ClientUserCommandThread::DisableAllModules(const wchar_t* nameOfExeOrDll) { // BEGIN EPIC MOD Windows::HMODULE module = ::GetModuleHandleW(nameOfExeOrDll); // END EPIC MOD if (!module) { LC_ERROR_USER("Cannot disable module %S because it is not loaded by this process.", nameOfExeOrDll); return nullptr; } // BEGIN EPIC MOD const std::vector& allModuleData = GatherImportModuleData(module); // END EPIC MOD const size_t moduleCount = allModuleData.size(); ProxyCommand* proxy = new ProxyCommand(true, sizeof(commands::ModuleData) * moduleCount); proxy->m_command.processId = Process::Current::GetId(); proxy->m_command.moduleCount = static_cast(moduleCount); proxy->m_command.token = new Event(nullptr, Event::Type::AUTO_RESET); for (size_t i = 0u; i < moduleCount; ++i) { const commands::ModuleData& moduleData = allModuleData[i]; proxy->m_payload.Write(moduleData); } PushUserCommand(proxy); return proxy->m_command.token; } // BEGIN EPIC MOD - Adding TryWaitForToken bool ClientUserCommandThread::TryWaitForToken(void* token) { Event* event = static_cast(token); if (m_thread != Thread::INVALID_HANDLE) { // thread was successfully initialized, try waiting until the command has been executed in the queue, non-blocking. if (event->TryWait()) { delete event; return true; } } return false; } // END EPIC MOD void ClientUserCommandThread::WaitForToken(void* token) { Event* event = static_cast(token); if (m_thread != Thread::INVALID_HANDLE) { // thread was successfully initialized, wait until the command has been executed in the queue event->Wait(); } delete event; } void ClientUserCommandThread::TriggerRecompile(void) { ProxyCommand* proxy = new ProxyCommand(false, 0u); PushUserCommand(proxy); } void ClientUserCommandThread::LogMessage(const wchar_t* message) { const size_t lengthWithoutNull = wcslen(message); const size_t payloadSize = sizeof(wchar_t) * (lengthWithoutNull + 1u); ProxyCommand* proxy = new ProxyCommand(false, payloadSize); proxy->m_payload.Write(message, payloadSize); PushUserCommand(proxy); } void ClientUserCommandThread::BuildPatch(const wchar_t* moduleNames[], const wchar_t* objPaths[], const wchar_t* amalgamatedObjPaths[], unsigned int count) { const size_t perFileSize = sizeof(wchar_t) * MAX_PATH * 3u; ProxyCommand* proxy = new ProxyCommand(false, perFileSize*count); proxy->m_command.fileCount = count; for (unsigned int i = 0u; i < count; ++i) { commands::BuildPatch::PatchData patchData = {}; wcscpy_s(patchData.moduleName, moduleNames[i]); wcscpy_s(patchData.objPath, objPaths[i]); // the amalgamated object paths are optional if (amalgamatedObjPaths && amalgamatedObjPaths[i]) { wcscpy_s(patchData.amalgamatedObjPath, amalgamatedObjPaths[i]); } proxy->m_payload.Write(patchData); } PushUserCommand(proxy); } void ClientUserCommandThread::TriggerRestart(void) { ProxyCommand* proxy = new ProxyCommand(false, 0u); PushUserCommand(proxy); } void ClientUserCommandThread::ApplySettingBool(const char* settingName, int value) { ProxyCommand* proxy = new ProxyCommand(false, 0u); strcpy_s(proxy->m_command.settingName, settingName); proxy->m_command.settingValue = value; PushUserCommand(proxy); } void ClientUserCommandThread::ApplySettingInt(const char* settingName, int value) { ProxyCommand* proxy = new ProxyCommand(false, 0u); strcpy_s(proxy->m_command.settingName, settingName); proxy->m_command.settingValue = value; PushUserCommand(proxy); } void ClientUserCommandThread::ApplySettingString(const char* settingName, const wchar_t* value) { ProxyCommand* proxy = new ProxyCommand(false, 0u); strcpy_s(proxy->m_command.settingName, settingName); wcscpy_s(proxy->m_command.settingValue, value); PushUserCommand(proxy); } // BEGIN EPIC MOD - Adding ShowConsole command void ClientUserCommandThread::ShowConsole() { ProxyCommand* proxy = new ProxyCommand(false, 0u); PushUserCommand(proxy); } // END EPIC MOD // BEGIN EPIC MOD - Adding SetVisible command void ClientUserCommandThread::SetVisible(bool visible) { ProxyCommand* proxy = new ProxyCommand(false, 0u); proxy->m_command.visible = visible; PushUserCommand(proxy); } // END EPIC MOD // BEGIN EPIC MOD - Adding SetActive command void ClientUserCommandThread::SetActive(bool active) { ProxyCommand* proxy = new ProxyCommand(false, 0u); proxy->m_command.active = active; PushUserCommand(proxy); } // END EPIC MOD // BEGIN EPIC MOD - Adding SetBuildArguments command void ClientUserCommandThread::SetBuildArguments(const wchar_t* arguments) { ProxyCommand* proxy = new ProxyCommand(false, 0u); proxy->m_command.processId = Process::Current::GetId(); wcscpy_s(proxy->m_command.arguments, arguments); PushUserCommand(proxy); } // END EPIC MOD // BEGIN EPIC MOD - Adding support for lazy-loading modules void* ClientUserCommandThread::EnableLazyLoadedModule(const wchar_t* fileName, Windows::HMODULE moduleBase) { ProxyCommand* proxy = new ProxyCommand(true, 0u); proxy->m_command.processId = Process::Current::GetId(); wcscpy_s(proxy->m_command.fileName, fileName); proxy->m_command.moduleBase = moduleBase; proxy->m_command.token = new Event(nullptr, Event::Type::AUTO_RESET); PushUserCommand(proxy); return proxy->m_command.token; } // END EPIC MOD // BEGIN EPIC MOD void ClientUserCommandThread::SetReinstancingFlow(bool enable) { ProxyCommand* proxy = new ProxyCommand(false, 0u); proxy->m_command.processId = Process::Current::GetId(); proxy->m_command.enable = enable; PushUserCommand(proxy); } // END EPIC MOD // BEGIN EPIC MOD void ClientUserCommandThread::DisableCompileFinishNotification() { ProxyCommand* proxy = new ProxyCommand(false, 0u); proxy->m_command.processId = Process::Current::GetId(); PushUserCommand(proxy); } // END EPIC MOD // BEGIN EPIC MOD namespace { std::vector GatherModuleHandles(const wchar_t* moduleNames[], unsigned int moduleCount) { std::vector moduleHandles; moduleHandles.reserve(moduleCount); for (unsigned int i = 0u; i < moduleCount; ++i) { Windows::HMODULE module = ::GetModuleHandleW(moduleNames[i]); if (module) { moduleHandles.push_back(module); } else { LC_ERROR_USER("Cannot enable module %S because it is not loaded by this process.", moduleNames[i]); } } return moduleHandles; } void AppendModuleHandles(ProxyCommand* proxy, const std::vector& moduleHandles) { for (Windows::HMODULE moduleHandle : moduleHandles) { commands::ModuleData moduleData = {}; moduleData.base = moduleHandle; ::GetModuleFileNameW(moduleHandle, moduleData.path, MAX_PATH); proxy->m_payload.Write(moduleData); } } } void* ClientUserCommandThread::EnableModulesEx(const wchar_t* moduleNames[], unsigned int moduleCount, const wchar_t* lazyLoadModuleNames[], unsigned int lazyLoadModuleCount, const uintptr_t* reservedPages, unsigned int reservedPagesCount) { std::vector moduleHandles = GatherModuleHandles(moduleNames, moduleCount); std::vector lazyLoadModuleHandles = GatherModuleHandles(lazyLoadModuleNames, lazyLoadModuleCount); if (moduleHandles.size() == 0 && lazyLoadModuleHandles.size() == 0) { return nullptr; } ProxyCommand* proxy = new ProxyCommand(true, sizeof(commands::ModuleData) * moduleHandles.size() + sizeof(commands::ModuleData) * lazyLoadModuleHandles.size() + sizeof(uintptr_t) * reservedPagesCount); proxy->m_command.processId = Process::Current::GetId(); proxy->m_command.moduleCount = static_cast(moduleHandles.size()); proxy->m_command.lazyLoadModuleCount = static_cast(lazyLoadModuleHandles.size()); proxy->m_command.reservedPagesCount = reservedPagesCount; proxy->m_command.token = new Event(nullptr, Event::Type::AUTO_RESET); AppendModuleHandles(proxy, moduleHandles); AppendModuleHandles(proxy, lazyLoadModuleHandles); for (unsigned int i = 0; i < reservedPagesCount; ++i) { proxy->m_payload.Write(reservedPages[i]); } PushUserCommand(proxy); return proxy->m_command.token; } // END EPIC MOD void ClientUserCommandThread::InstallExceptionHandler(void) { // BEGIN EPIC MOD - Using internal CrashReporter // exceptionHandler::Register(this); // END EPIC MOD } ClientUserCommandThread::ExceptionResult ClientUserCommandThread::HandleException(EXCEPTION_RECORD* exception, CONTEXT* context, Thread::Id threadId) { commands::HandleException serverCommand; serverCommand.processId = Process::Current::GetId(); serverCommand.threadId = threadId; serverCommand.exception = *exception; serverCommand.context = *context; serverCommand.clientContextPtr = context; m_exceptionPipe->SendCommandAndWaitForAck(serverCommand, nullptr, 0u); ExceptionResult result = {}; CommandMap commandMap; commandMap.RegisterAction(); commandMap.HandleCommands(m_exceptionPipe, &result); return result; } void ClientUserCommandThread::End(void) { // signal to the thread that a new item is in the queue to make it break out of its main loop PushUserCommand(nullptr); } void ClientUserCommandThread::PushUserCommand(BaseCommand* command) { { CriticalSection::ScopedLock lock(&m_userCommandQueueCS); m_userCommandQueue.push_front(command); } // signal to the thread that a new item is in the queue m_userCommandQueueSema.Signal(); } ClientUserCommandThread::BaseCommand* ClientUserCommandThread::PopUserCommand(void) { m_userCommandQueueSema.Wait(); CriticalSection::ScopedLock lock(&m_userCommandQueueCS); BaseCommand* command = m_userCommandQueue.back(); m_userCommandQueue.pop_back(); return command; } Thread::ReturnValue ClientUserCommandThread::ThreadFunction(Event* waitForStartEvent, CriticalSection* pipeAccessCS) { // wait until we get the signal that the thread can start waitForStartEvent->Wait(); CommandMap moduleCommandMap; moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); // those commands are needed when loading compiled patches into spawned executables moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); // BEGIN EPIC MOD moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); moduleCommandMap.RegisterAction(); // END EPIC MOD for (;;) { // wait until a command becomes available in the queue BaseCommand* command = PopUserCommand(); if (command == nullptr) { // BEGIN EPIC MOD - Using internal CrashReporter // // no new item available, bail out // exceptionHandler::Unregister(); // END EPIC MOD - Using internal CrashReporter return Thread::ReturnValue(2u); } if (!m_pipe->IsValid()) { // BEGIN EPIC MOD - Using internal CrashReporter // // pipe was closed or is broken, bail out // exceptionHandler::Unregister(); // END EPIC MOD - Using internal CrashReporter return Thread::ReturnValue(1u); } // lock critical section for accessing the pipe. // we need to make sure that other threads talking through the pipe don't use it at the same time. { CriticalSection::ScopedLock pipeLock(pipeAccessCS); command->Execute(m_pipe); if (command->ExpectsResponse()) { moduleCommandMap.HandleCommands(m_pipe, nullptr); } } delete command; } } #endif // LC_VERSION