Files
UnrealEngine/Engine/Source/Developer/Windows/LiveCodingServer/Private/External/LC_VisualStudioAutomation.cpp
2025-05-18 13:04:45 +08:00

693 lines
17 KiB
C++

// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved.
#if LC_VERSION == 1
// BEGIN EPIC MOD
//#include PCH_INCLUDE
// END EPIC MOD
#include "LC_VisualStudioAutomation.h"
#include "LC_COMThread.h"
#include "LC_StringUtil.h"
#include "LC_Thread.h"
// BEGIN EPIC MOD
#include "LC_Logging.h"
#include "Microsoft/COMPointer.h"
// END EPIC MOD
// BEGIN EPIC MOD
#if WITH_VISUALSTUDIO_DTE
// END EPIC MOD
namespace
{
static COMThread* g_comThread = nullptr;
// helper function that checks whether the debugger is currently held for debugging, e.g. at a breakpoint, an exception
static bool IsHeldForDebugging(const EnvDTE::DebuggerPtr& debugger)
{
EnvDTE::dbgDebugMode mode = EnvDTE::dbgDesignMode;
debugger->get_CurrentMode(&mode);
return (mode == EnvDTE::dbgBreakMode);
}
// helper function that waits until the debugger finished executing a command and is in break mode again
static bool WaitUntilBreakMode(const EnvDTE::DebuggerPtr& debugger)
{
const unsigned int RETRY_WAIT_TIME = 5u;
const unsigned int RETRY_TIMEOUT = 500u;
EnvDTE::dbgDebugMode mode = EnvDTE::dbgDesignMode;
debugger->get_CurrentMode(&mode);
unsigned int msWaited = 0u;
while (mode != EnvDTE::dbgBreakMode)
{
Thread::Current::SleepMilliSeconds(RETRY_WAIT_TIME);
msWaited += RETRY_WAIT_TIME;
debugger->get_CurrentMode(&mode);
if (msWaited >= RETRY_TIMEOUT)
{
// enough time has passed, bail out
return false;
}
}
return true;
}
}
void visualStudio::Startup(void)
{
// start the COM thread that does the actual work
g_comThread = new COMThread;
}
void visualStudio::Shutdown(void)
{
// shut down the COM thread
memory::DeleteAndNull(g_comThread);
}
static EnvDTE::DebuggerPtr FindDebuggerAttachedToProcess(Process::Id processId)
{
// get all objects from the running object table (ROT)
// BEGIN EPIC MOD
TComPtr<IRunningObjectTable> rot = nullptr;
// END EPIC MOD
HRESULT result = ::GetRunningObjectTable(0u, &rot);
if (FAILED(result) || (!rot))
{
LC_ERROR_DEV("Could not initialize running object table. Error: 0x%X", result);
return nullptr;
}
// BEGIN EPIC MOD
TComPtr<IEnumMoniker> enumMoniker = nullptr;
// END EPIC MOD
result = rot->EnumRunning(&enumMoniker);
if (FAILED(result) || (!enumMoniker))
{
LC_ERROR_DEV("Could not enumerate running objects. Error: 0x%X", result);
return nullptr;
}
enumMoniker->Reset();
do
{
// BEGIN EPIC MOD
TComPtr<IMoniker> next = nullptr;
// END EPIC MOD
result = enumMoniker->Next(1u, &next, NULL);
if (SUCCEEDED(result) && next)
{
// BEGIN EPIC MOD
TComPtr<IBindCtx> context = nullptr;
// END EPIC MOD
result = ::CreateBindCtx(0, &context);
if (FAILED(result) || (!context))
{
LC_ERROR_DEV("Could not create COM binding context. Error: 0x%X", result);
continue;
}
wchar_t* displayName = nullptr;
result = next->GetDisplayName(context, NULL, &displayName);
if (FAILED(result) || (!displayName))
{
LC_ERROR_DEV("Could not retrieve display name. Error: 0x%X", result);
continue;
}
// only try objects which are a specific version of Visual Studio
if (string::Contains(displayName, L"VisualStudio.DTE."))
{
// free display name using COM allocator
// BEGIN EPIC MOD
TComPtr<IMalloc> comMalloc = nullptr;
// END EPIC MOD
result = ::CoGetMalloc(1u, &comMalloc);
if (SUCCEEDED(result) && comMalloc)
{
comMalloc->Free(displayName);
}
// BEGIN EPIC MOD
TComPtr<IUnknown> unknown = nullptr;
result = rot->GetObject(next, &unknown);
// END EPIC MOD
if (FAILED(result) || (!unknown))
{
LC_ERROR_DEV("Could not retrieve COM object from running object table. Error: 0x%X", result);
continue;
}
EnvDTE::_DTEPtr dte = nullptr;
result = unknown->QueryInterface(&dte);
if (FAILED(result) || (!dte))
{
// this COM object doesn't support the DTE interface
LC_ERROR_DEV("Could not convert IUnknown to DTE interface. Error: 0x%X", result);
continue;
}
EnvDTE::DebuggerPtr debugger = nullptr;
result = dte->get_Debugger(&debugger);
if (FAILED(result) || (!debugger))
{
// cannot access debugger, which means that the process is currently not being debugged
LC_LOG_DEV("Could not access debugger interface. Error: 0x%X", result);
continue;
}
// fetch all processes to which this debugger is attached
EnvDTE::ProcessesPtr allProcesses = nullptr;
result = debugger->get_DebuggedProcesses(&allProcesses);
if (FAILED(result) || (!allProcesses))
{
LC_ERROR_DEV("Could not retrieve processes from debugger. Error: 0x%X", result);
continue;
}
long processCount = 0;
result = allProcesses->get_Count(&processCount);
if (FAILED(result) || (processCount <= 0))
{
LC_ERROR_DEV("Could not retrieve process count from debugger. Error: 0x%X", result);
continue;
}
// check all processes if any of them is the one we're looking for
for (long i = 0u; i < processCount; ++i)
{
EnvDTE::ProcessPtr singleProcess = nullptr;
result = allProcesses->Item(variant_t(i + 1), &singleProcess);
if (FAILED(result) || (!singleProcess))
{
LC_ERROR_DEV("Could not retrieve process from debugger. Error: 0x%X", result);
continue;
}
long debuggerProcessId = 0;
result = singleProcess->get_ProcessID(&debuggerProcessId);
if (FAILED(result) || (debuggerProcessId <= 0))
{
LC_ERROR_DEV("Could not retrieve process ID from debugger. Error: 0x%X", result);
continue;
}
// we got a valid processId
if (static_cast<unsigned int>(debuggerProcessId) == +processId)
{
// found debugger attached to our process
return debugger;
}
}
}
}
}
while (result != S_FALSE);
return nullptr;
}
EnvDTE::DebuggerPtr visualStudio::FindDebuggerAttachedToProcess(Process::Id processId)
{
return g_comThread->CallInThread(&::FindDebuggerAttachedToProcess, processId);
}
static EnvDTE::DebuggerPtr FindDebuggerForProcess(Process::Id processId)
{
// get all objects from the running object table (ROT)
// BEGIN EPIC MOD
TComPtr<IRunningObjectTable> rot = nullptr;
// END EPIC MOD
HRESULT result = ::GetRunningObjectTable(0u, &rot);
if (FAILED(result) || (!rot))
{
LC_ERROR_DEV("Could not initialize running object table. Error: 0x%X", result);
return nullptr;
}
// BEGIN EPIC MOD
TComPtr<IEnumMoniker> enumMoniker = nullptr;
// END EPIC MOD
result = rot->EnumRunning(&enumMoniker);
if (FAILED(result) || (!enumMoniker))
{
LC_ERROR_DEV("Could not enumerate running objects. Error: 0x%X", result);
return nullptr;
}
enumMoniker->Reset();
do
{
// BEGIN EPIC MOD
TComPtr<IMoniker> next = nullptr;
// END EPIC MOD
result = enumMoniker->Next(1u, &next, NULL);
if (SUCCEEDED(result) && next)
{
// BEGIN EPIC MOD
TComPtr<IBindCtx> context = nullptr;
// END EPIC MOD
result = ::CreateBindCtx(0, &context);
if (FAILED(result) || (!context))
{
LC_ERROR_DEV("Could not create COM binding context. Error: 0x%X", result);
continue;
}
wchar_t* displayName = nullptr;
result = next->GetDisplayName(context, NULL, &displayName);
if (FAILED(result) || (!displayName))
{
LC_ERROR_DEV("Could not retrieve display name. Error: 0x%X", result);
continue;
}
// only try objects which are a specific version of Visual Studio
if (string::Contains(displayName, L"VisualStudio.DTE."))
{
// free display name using COM allocator
// BEGIN EPIC MOD
TComPtr<IMalloc> comMalloc = nullptr;
// END EPIC MOD
result = ::CoGetMalloc(1u, &comMalloc);
if (SUCCEEDED(result) && comMalloc)
{
comMalloc->Free(displayName);
}
// BEGIN EPIC MOD
TComPtr<IUnknown> unknown = nullptr;
result = rot->GetObject(next, &unknown);
// END EPIC MOD
if (FAILED(result) || (!unknown))
{
LC_ERROR_DEV("Could not retrieve COM object from running object table. Error: 0x%X", result);
continue;
}
EnvDTE::_DTEPtr dte = nullptr;
result = unknown->QueryInterface(&dte);
if (FAILED(result) || (!dte))
{
// this COM object doesn't support the DTE interface
LC_ERROR_DEV("Could not convert IUnknown to DTE interface. Error: 0x%X", result);
continue;
}
EnvDTE::DebuggerPtr debugger = nullptr;
result = dte->get_Debugger(&debugger);
if (FAILED(result) || (!debugger))
{
// cannot access debugger, which means that the process is currently not being debugged
LC_LOG_DEV("Could not access debugger interface. Error: 0x%X", result);
continue;
}
EnvDTE::ProcessPtr process = nullptr;
result = debugger->get_CurrentProcess(&process);
if (FAILED(result) || (!process))
{
// cannot access current process, reason unknown
LC_ERROR_DEV("Could not access current process in debugger. Error: 0x%X", result);
continue;
}
long debuggerProcessId = 0;
result = process->get_ProcessID(&debuggerProcessId);
if (FAILED(result) || (debuggerProcessId <= 0))
{
LC_ERROR_DEV("Could not retrieve process ID from debugger. Error: 0x%X", result);
continue;
}
// we got a valid processId
if (static_cast<unsigned int>(debuggerProcessId) == +processId)
{
// found debugger debugging our process
return debugger;
}
}
}
}
while (result != S_FALSE);
return nullptr;
}
EnvDTE::DebuggerPtr visualStudio::FindDebuggerForProcess(Process::Id processId)
{
return g_comThread->CallInThread(&::FindDebuggerForProcess, processId);
}
static bool AttachToProcess(const EnvDTE::DebuggerPtr& debugger, Process::Id processId)
{
// fetch all local processes running on this machine
EnvDTE::ProcessesPtr allProcesses = nullptr;
HRESULT result = debugger->get_LocalProcesses(&allProcesses);
if (FAILED(result) || (!allProcesses))
{
LC_ERROR_DEV("Could not retrieve local processes from debugger. Error: 0x%X", result);
return false;
}
long processCount = 0;
result = allProcesses->get_Count(&processCount);
if (FAILED(result) || (processCount <= 0))
{
LC_ERROR_DEV("Could not retrieve local process count from debugger. Error: 0x%X", result);
return false;
}
// check all processes if any of them is the one we're looking for
for (long i = 0u; i < processCount; ++i)
{
EnvDTE::ProcessPtr singleProcess = nullptr;
result = allProcesses->Item(variant_t(i + 1), &singleProcess);
if (FAILED(result) || (!singleProcess))
{
LC_ERROR_DEV("Could not retrieve local process from debugger. Error: 0x%X", result);
continue;
}
long localProcessId = 0;
result = singleProcess->get_ProcessID(&localProcessId);
if (FAILED(result) || (localProcessId <= 0))
{
LC_ERROR_DEV("Could not retrieve local process ID from debugger. Error: 0x%X", result);
continue;
}
// we got a valid processId
if (static_cast<unsigned int>(localProcessId) == +processId)
{
// this is the process we want to attach to
result = singleProcess->Attach();
if (FAILED(result))
{
LC_ERROR_USER("Could not attach debugger to process. Error: 0x%X", result);
return false;
}
return true;
}
}
return false;
}
bool visualStudio::AttachToProcess(const EnvDTE::DebuggerPtr& debugger, Process::Id processId)
{
return g_comThread->CallInThread(&::AttachToProcess, debugger, processId);
}
static types::vector<EnvDTE::ThreadPtr> EnumerateThreads(const EnvDTE::DebuggerPtr& debugger)
{
types::vector<EnvDTE::ThreadPtr> threads;
EnvDTE::ProgramPtr program = nullptr;
HRESULT result = debugger->get_CurrentProgram(&program);
if (FAILED(result) || (!program))
{
LC_ERROR_DEV("Could not retrieve current program from debugger. Error: 0x%X", result);
return threads;
}
EnvDTE::ThreadsPtr allThreads = nullptr;
result = program->get_Threads(&allThreads);
if (FAILED(result) || (!allThreads))
{
LC_ERROR_DEV("Could not retrieve running threads from debugger. Error: 0x%X", result);
return threads;
}
long threadCount = 0;
result = allThreads->get_Count(&threadCount);
if (FAILED(result) || (threadCount <= 0))
{
LC_ERROR_DEV("Could not retrieve thread count from debugger. Error: 0x%X", result);
return threads;
}
threads.reserve(static_cast<size_t>(threadCount));
for (long i = 0u; i < threadCount; ++i)
{
EnvDTE::ThreadPtr singleThread = nullptr;
result = allThreads->Item(variant_t(i + 1), &singleThread);
if (FAILED(result) || (!singleThread))
{
LC_ERROR_DEV("Could not retrieve thread from debugger. Error: 0x%X", result);
continue;
}
threads.push_back(singleThread);
}
return threads;
}
types::vector<EnvDTE::ThreadPtr> visualStudio::EnumerateThreads(const EnvDTE::DebuggerPtr& debugger)
{
return g_comThread->CallInThread(&::EnumerateThreads, debugger);
}
static bool FreezeThreads(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads)
{
if (!IsHeldForDebugging(debugger))
{
LC_WARNING_USER("FreezeThreads: Debugger is no longer held for debugging");
return false;
}
bool success = true;
const size_t count = threads.size();
for (size_t i = 0u; i < count; ++i)
{
EnvDTE::ThreadPtr singleThread = threads[i];
const HRESULT result = singleThread->Freeze();
if (FAILED(result))
{
LC_ERROR_USER("FreezeThreads: Failed to freeze thread");
return false;
}
if (!WaitUntilBreakMode(debugger))
{
LC_WARNING_USER("FreezeThreads: Cannot wait for debugger break mode");
return false;
}
success &= SUCCEEDED(result);
}
return success;
}
bool visualStudio::FreezeThreads(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads)
{
return g_comThread->CallInThread(&::FreezeThreads, debugger, threads);
}
static bool FreezeThread(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads, Thread::Id threadId)
{
if (!IsHeldForDebugging(debugger))
{
LC_WARNING_USER("FreezeThread: Debugger is no longer held for debugging");
return false;
}
const size_t count = threads.size();
for (size_t i = 0u; i < count; ++i)
{
EnvDTE::ThreadPtr singleThread = threads[i];
long id = 0;
HRESULT result = singleThread->get_ID(&id);
if (FAILED(result) || (id <= 0))
{
continue;
}
if (static_cast<unsigned int>(id) == +threadId)
{
// found the thread we're looking for
result = singleThread->Freeze();
if (FAILED(result))
{
LC_ERROR_USER("FreezeThread: Failed to freeze thread");
return false;
}
if (!WaitUntilBreakMode(debugger))
{
LC_WARNING_USER("FreezeThread: Cannot wait for debugger break mode");
return false;
}
return SUCCEEDED(result);
}
}
return false;
}
bool visualStudio::FreezeThread(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads, Thread::Id threadId)
{
return g_comThread->CallInThread(&::FreezeThread, debugger, threads, threadId);
}
static bool ThawThreads(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads)
{
if (!IsHeldForDebugging(debugger))
{
LC_WARNING_USER("ThawThreads: Debugger is no longer held for debugging");
return false;
}
bool success = true;
const size_t count = threads.size();
for (size_t i = 0u; i < count; ++i)
{
EnvDTE::ThreadPtr singleThread = threads[i];
const HRESULT result = singleThread->Thaw();
if (FAILED(result))
{
LC_ERROR_USER("ThawThreads: Failed to thaw thread");
return false;
}
if (!WaitUntilBreakMode(debugger))
{
LC_WARNING_USER("ThawThreads: Cannot wait for debugger break mode");
return false;
}
success &= SUCCEEDED(result);
}
return success;
}
bool visualStudio::ThawThreads(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads)
{
return g_comThread->CallInThread(&::ThawThreads, debugger, threads);
}
static bool ThawThread(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads, Thread::Id threadId)
{
if (!IsHeldForDebugging(debugger))
{
LC_WARNING_USER("ThawThread: Debugger is no longer held for debugging");
return false;
}
const size_t count = threads.size();
for (size_t i = 0u; i < count; ++i)
{
EnvDTE::ThreadPtr singleThread = threads[i];
long id = 0;
HRESULT result = singleThread->get_ID(&id);
if (FAILED(result) || (id <= 0))
{
continue;
}
if (static_cast<unsigned int>(id) == +threadId)
{
// found the thread we're looking for
result = singleThread->Thaw();
if (FAILED(result))
{
LC_ERROR_USER("ThawThread: Failed to thaw thread");
return false;
}
if (!WaitUntilBreakMode(debugger))
{
LC_WARNING_USER("ThawThread: Cannot wait for debugger break mode");
return false;
}
return SUCCEEDED(result);
}
}
return false;
}
bool visualStudio::ThawThread(const EnvDTE::DebuggerPtr& debugger, const types::vector<EnvDTE::ThreadPtr>& threads, Thread::Id threadId)
{
return g_comThread->CallInThread(&::ThawThread, debugger, threads, threadId);
}
static bool Resume(const EnvDTE::DebuggerPtr& debugger)
{
// BEGIN EPIC MOD
const HRESULT result = debugger->Go(variant_t(Windows::FALSE));
// END EPIC MOD
return SUCCEEDED(result);
}
bool visualStudio::Resume(const EnvDTE::DebuggerPtr& debugger)
{
return g_comThread->CallInThread(&::Resume, debugger);
}
static bool Break(const EnvDTE::DebuggerPtr& debugger)
{
// wait until the debugger really enters break-mode
// BEGIN EPIC MOD
const HRESULT result = debugger->Break(variant_t(Windows::TRUE));
// END EPIC MOD
return SUCCEEDED(result);
}
bool visualStudio::Break(const EnvDTE::DebuggerPtr& debugger)
{
return g_comThread->CallInThread(&::Break, debugger);
}
// BEGIN EPIC MOD
#endif
// END EPIC MOD
#endif