2019-03-27 15:03:08 -04:00
// Copyright 2011-2019 Molecular Matters GmbH, all rights reserved.
# include "LC_LiveProcess.h"
# include "LC_HeartBeat.h"
# include "LC_CodeCave.h"
2019-07-24 15:05:52 -04:00
# include "LC_Event.h"
# include "LC_PrimitiveNames.h"
# include "LC_VisualStudioAutomation.h"
# include "LC_Logging.h"
2019-03-27 15:03:08 -04:00
2019-07-24 15:05:52 -04:00
LiveProcess : : LiveProcess ( process : : Handle processHandle , unsigned int processId , unsigned int commandThreadId , const void * jumpToSelf , const DuplexPipe * pipe ,
const wchar_t * imagePath , const wchar_t * commandLine , const wchar_t * workingDirectory , const void * environment , size_t environmentSize )
2019-03-27 15:03:08 -04:00
: m_processHandle ( processHandle )
, m_processId ( processId )
, m_commandThreadId ( commandThreadId )
2019-05-29 20:48:56 -04:00
, m_jumpToSelf ( jumpToSelf )
2019-03-27 15:03:08 -04:00
, m_pipe ( pipe )
2019-07-24 15:05:52 -04:00
, m_imagePath ( imagePath )
, m_commandLine ( commandLine )
, m_workingDirectory ( workingDirectory )
, m_environment ( environment , environmentSize )
2019-03-27 15:03:08 -04:00
, m_imagesTriedToLoad ( )
, m_heartBeatDelta ( 0ull )
2019-07-24 15:05:52 -04:00
# if WITH_VISUALSTUDIO_DTE
, m_vsDebugger ( nullptr )
, m_vsDebuggerThreads ( )
# endif
2019-03-27 15:03:08 -04:00
, m_codeCave ( nullptr )
2019-07-24 15:05:52 -04:00
, m_restartState ( RestartState : : DEFAULT )
2019-03-27 15:03:08 -04:00
{
m_imagesTriedToLoad . reserve ( 256u ) ;
}
void LiveProcess : : ReadHeartBeatDelta ( const wchar_t * const processGroupName )
{
HeartBeat heartBeat ( processGroupName , m_processId ) ;
m_heartBeatDelta = heartBeat . ReadBeatDelta ( ) ;
}
bool LiveProcess : : MadeProgress ( void ) const
{
if ( m_heartBeatDelta > = 100ull * 10000ull )
{
// the client process hasn't stored a new heart beat in more than 100ms.
// as long as it is running, it stores a new heart beat every 10ms, so we conclude that it
// didn't make progress, e.g. because it is being held in the debugger.
return false ;
}
return true ;
}
2019-07-24 15:05:52 -04:00
void LiveProcess : : HandleDebuggingPreCompile ( void )
{
# if WITH_VISUALSTUDIO_DTE
if ( ! MadeProgress ( ) )
{
// this process did not make progress.
// try to find a debugger that's currently debugging our process.
m_vsDebugger = visualStudio : : FindDebuggerForProcess ( m_processId ) ;
if ( m_vsDebugger )
{
// found a debugger.
// enumerate all threads, freeze every thread but the command thread, and let the debugger resume.
// this "halts" the process but lets the command thread act on commands sent by us.
m_vsDebuggerThreads = visualStudio : : EnumerateThreads ( m_vsDebugger ) ;
if ( m_vsDebuggerThreads . size ( ) > 0u )
{
visualStudio : : FreezeThreads ( m_vsDebugger , m_vsDebuggerThreads ) ;
visualStudio : : ThawThread ( m_vsDebugger , m_vsDebuggerThreads , m_commandThreadId ) ;
visualStudio : : Resume ( m_vsDebugger ) ;
LC_SUCCESS_USER ( " Automating debugger attached to process (PID: %d) " , m_processId ) ;
return ;
}
}
// no debugger could be found or an error occurred.
// continue by installing a code cave.
LC_LOG_USER ( " Failed to automate debugger attached to process (PID: %d), using fallback mechanism " , m_processId ) ;
LC_SUCCESS_USER ( " Waiting for client process (PID: %d), hit 'Continue' (F5 in Visual Studio) if being held in the debugger " , m_processId ) ;
}
# endif
// this process either made progress and is not held in the debugger, or we failed automating the debugger.
// "halt" this process by installing a code cave.
InstallCodeCave ( ) ;
}
void LiveProcess : : HandleDebuggingPostCompile ( void )
{
if ( m_codeCave )
{
// we installed a code cave previously, remove it
UninstallCodeCave ( ) ;
}
# if WITH_VISUALSTUDIO_DTE
else if ( m_vsDebugger )
{
// we automated the debugger previously. break into the debugger again and resume all threads.
// when debugging a C# project that calls into C++ code, the VS debugger sometimes creates new MTA threads in between our PreCompile and PostCompile calls.
// try getting a new list of threads and thaw them all as well.
visualStudio : : Break ( m_vsDebugger ) ;
types : : vector < EnvDTE : : ThreadPtr > newDebuggerThreads = visualStudio : : EnumerateThreads ( m_vsDebugger ) ;
visualStudio : : ThawThreads ( m_vsDebugger , newDebuggerThreads ) ;
visualStudio : : ThawThreads ( m_vsDebugger , m_vsDebuggerThreads ) ;
}
m_vsDebugger = nullptr ;
# endif
}
2019-03-27 15:03:08 -04:00
void LiveProcess : : InstallCodeCave ( void )
{
2019-05-29 20:48:56 -04:00
m_codeCave = new CodeCave ( m_processHandle , m_processId , m_commandThreadId , m_jumpToSelf ) ;
2019-03-27 15:03:08 -04:00
m_codeCave - > Install ( ) ;
}
void LiveProcess : : UninstallCodeCave ( void )
{
m_codeCave - > Uninstall ( ) ;
delete m_codeCave ;
m_codeCave = nullptr ;
}
void LiveProcess : : AddLoadedImage ( const executable : : Header & imageHeader )
{
m_imagesTriedToLoad . insert ( imageHeader ) ;
}
void LiveProcess : : RemoveLoadedImage ( const executable : : Header & imageHeader )
{
m_imagesTriedToLoad . erase ( imageHeader ) ;
}
bool LiveProcess : : TriedToLoadImage ( const executable : : Header & imageHeader ) const
{
return ( m_imagesTriedToLoad . find ( imageHeader ) ! = m_imagesTriedToLoad . end ( ) ) ;
}
2019-07-24 15:05:52 -04:00
bool LiveProcess : : PrepareForRestart ( void )
{
// signal to the target process that a restart for this process was requested
Event requestRestart ( primitiveNames : : RequestRestart ( m_processId ) . c_str ( ) , Event : : Type : : AUTO_RESET ) ;
requestRestart . Signal ( ) ;
// the client code in the target is now inside the lpp::lppWantsRestart() code block.
// wait until it calls lpp::lppRestart() after finishing custom client code.
// give the client 10 seconds to finish up.
Event restartPrepared ( primitiveNames : : PreparedRestart ( m_processId ) . c_str ( ) , Event : : Type : : AUTO_RESET ) ;
const bool success = restartPrepared . WaitTimeout ( 10u * 1000u ) ;
if ( success )
{
m_restartState = RestartState : : SUCCESSFUL_PREPARE ;
return true ;
}
else
{
LC_ERROR_USER ( " Client did not respond to restart request within 10 seconds, aborting restart (PID: %d) " , m_processId ) ;
m_restartState = RestartState : : FAILED_PREPARE ;
return false ;
}
}
void LiveProcess : : Restart ( void )
{
if ( m_restartState = = RestartState : : SUCCESSFUL_PREPARE )
{
// in case PrepareForRestart was successful, the client is now waiting for the signal to restart.
// tell the client to initiate a restart now.
Event executeRestart ( primitiveNames : : Restart ( m_processId ) . c_str ( ) , Event : : Type : : AUTO_RESET ) ;
executeRestart . Signal ( ) ;
// wait until the client terminates
process : : Wait ( m_processHandle ) ;
// restart the target application
process : : Spawn ( m_imagePath . c_str ( ) , m_workingDirectory . c_str ( ) , m_commandLine . c_str ( ) , m_environment . GetData ( ) , process : : SpawnFlags : : NONE ) ;
m_restartState = RestartState : : SUCCESSFUL ;
}
}
bool LiveProcess : : WasSuccessfulRestart ( void ) const
{
return ( m_restartState = = RestartState : : SUCCESSFUL ) ;
}
2019-03-27 15:03:08 -04:00
// BEGIN EPIC MOD - Allow lazy-loading modules
void LiveProcess : : AddLazyLoadedModule ( const std : : wstring moduleName , Windows : : HMODULE moduleBase )
{
LazyLoadedModule module ;
module . m_moduleBase = moduleBase ;
module . m_loaded = false ;
m_lazyLoadedModules . insert ( std : : make_pair ( moduleName , module ) ) ;
}
void LiveProcess : : SetLazyLoadedModuleAsLoaded ( const std : : wstring moduleName )
{
std : : unordered_map < std : : wstring , LazyLoadedModule > : : iterator it = m_lazyLoadedModules . find ( moduleName ) ;
if ( it ! = m_lazyLoadedModules . end ( ) )
{
it - > second . m_loaded = true ;
}
}
bool LiveProcess : : IsPendingLazyLoadedModule ( const std : : wstring & moduleName ) const
{
std : : unordered_map < std : : wstring , LazyLoadedModule > : : const_iterator iter = m_lazyLoadedModules . find ( moduleName ) ;
return iter ! = m_lazyLoadedModules . end ( ) & & ! iter - > second . m_loaded ;
}
Windows : : HMODULE LiveProcess : : GetLazyLoadedModuleBase ( const std : : wstring & moduleName ) const
{
std : : unordered_map < std : : wstring , LazyLoadedModule > : : const_iterator iter = m_lazyLoadedModules . find ( moduleName ) ;
if ( iter = = m_lazyLoadedModules . end ( ) )
{
return nullptr ;
}
else
{
return iter - > second . m_moduleBase ;
}
}
2019-07-24 15:05:52 -04:00
// END EPIC MOD