// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved. #if LC_VERSION == 1 // BEGIN EPIC MOD //#include PCH_INCLUDE // END EPIC MOD #include "LC_LiveModule.h" #include "LC_Telemetry.h" #include "LC_StringUtil.h" #include "LC_Filesystem.h" #include "LC_Environment.h" #include "LC_SymbolReconstruction.h" #include "LC_Thread.h" #include "LC_Process.h" #include "LC_Compiler.h" #include "LC_SyncPoint.h" #include "LC_ExecutablePatcher.h" #include "LC_Executable.h" #include "LC_Hook.h" #include "LC_RelocationPatcher.h" #include "LC_FunctionPatcher.h" #include "LC_Disassembler.h" #include "LC_Patch.h" #include "LC_PointerUtil.h" #include "LC_DuplexPipe.h" #include "LC_CommandMap.h" #include "LC_CoffDetail.h" #include "LC_FileAttributeCache.h" #include "LC_NameMangling.h" #include "LC_DirectoryCache.h" #include "LC_CompilerOptions.h" #include "LC_ModulePatch.h" #include "LC_Amalgamation.h" // BEGIN EPIC MOD //#include "LC_App.h" // END EPIC MOD #include "LC_Scheduler.h" #include "LC_LiveProcess.h" #include "LC_UniqueId.h" // BEGIN EPIC MOD //#include "LC_FASTBuild.h" // END EPIC MOD #include "LC_VirtualMemoryRange.h" #include "LPP_API.h" // BEGIN EPIC MOD #include "LiveCodingServer.h" #include "LC_AppSettings.h" #include // needed for _PVFV // END EPIC MOD // BEGIN EPIC MOD - Support for UE debug visualizers #include "Misc/Paths.h" // END EPIC MOD namespace { #if LC_64_BIT static const void* GetLowerBoundIn4GBRange(const void* moduleBase) { // nothing can be loaded at address 0x0, 64 KB seems like a realistic minimum const uint64_t LOWEST_ADDRESS = 64u * 1024u; const uint64_t base = pointer::AsInteger(moduleBase); // make sure we don't underflow if (base >= 0x80000000ull + LOWEST_ADDRESS) { return pointer::FromInteger(base - 0x80000000ull); } // operation would underflow return pointer::FromInteger(LOWEST_ADDRESS); } static const void* GetUpperBoundIn4GBRange(const void* moduleBase) { const uint64_t HighestPossibleAddress = 0x00007FFFFFFF0000ull; const uint64_t HighestPossibleAddressThreshold = HighestPossibleAddress - 2ull * 1024ull * 1024ull * 1024ull; const uint64_t base = pointer::AsInteger(moduleBase); // make sure we don't overflow if (base <= HighestPossibleAddressThreshold) { return pointer::FromInteger(base + 2ull * 1024ull * 1024ull * 1024ull); } // operation would overflow return pointer::FromInteger(HighestPossibleAddress); } #endif // common linker options: // *) create x86/x64 code // *) don't echo command-line options // *) disable incremental linking, otherwise the linker will emit a warning // *) no manifests needed // *) generate debug information // *) create a hot-patchable image // *) we explicitly want the .dll to be loaded anywhere in the address space, because that forces the linker to // include a relocation table in the PE image // *) disable ASLR (address space layout randomization) to load the .dll at the preferred image base, if possible // *) don't link against any of the default libraries // *) turn on OPT:REF to keep .dll and .pdb as small as possible. /OPT:ICF is not used, because binary identical but // otherwise different functions would get folded, leading to confusing call stacks and wrong debug information // *) create a .dll static const wchar_t COMMON_LINKER_OPTIONS[] = L"" #if LC_64_BIT L"/MACHINE:X64 " #else L"/MACHINE:X86 " #endif L"/NOLOGO " L"/INCREMENTAL:NO " L"/MANIFEST:NO " L"/DEBUG " L"/FUNCTIONPADMIN " L"/FIXED:NO " L"/DYNAMICBASE:NO " L"/NODEFAULTLIB " L"/OPT:REF " L"/OPT:NOICF " L"/DLL\n"; static CriticalSection g_compileOutputCS; struct CompileFlags { enum Enum { NONE = 0, SERIALIZE_PDB_ACCESS = 1u << 0u }; }; // helper function that returns the compiler path for a compiland, taking into account UI settings static std::wstring GetCompilerPath(const symbols::Compiland* compiland) { const std::wstring compilerPath = string::ToWideString(compiland->compilerPath.c_str()); // check whether compiler path is overridden const std::wstring overriddenCompilerPath = appSettings::GetCompilerPath(); if (overriddenCompilerPath.length() != 0u) { // should the overridden path be used as fallback only? if (appSettings::g_useCompilerOverrideAsFallback->GetValue()) { // yes, so test whether a compiler at the compiland's compiler path exists const Filesystem::PathAttributes& attributes = Filesystem::GetAttributes(compilerPath.c_str()); if (Filesystem::DoesExist(attributes)) { // compiler exists, use it return compilerPath; } else { // compiler does not exist, use the fallback return overriddenCompilerPath; } } else { // no, the override should always be used return overriddenCompilerPath; } } else { // not overridden, use the compiland's compiler return compilerPath; } } // helper function that returns the linker path, taking into account UI settings static std::wstring GetLinkerPath(const symbols::LinkerDB* linkerDb) { const std::wstring linkerPath = string::ToWideString(linkerDb->linkerPath.c_str()); // check whether linker path is overridden const std::wstring overriddenLinkerPath = appSettings::GetLinkerPath(); if (overriddenLinkerPath.length() != 0u) { // should the overridden path be used as fallback only? if (appSettings::g_useLinkerOverrideAsFallback->GetValue()) { // yes, so test whether a linker at the given path exists const Filesystem::PathAttributes& attributes = Filesystem::GetAttributes(linkerPath.c_str()); if (Filesystem::DoesExist(attributes)) { // linker exists, use it return linkerPath; } else { // linker does not exist, use the fallback return overriddenLinkerPath; } } else { // no, the override should always be used return overriddenLinkerPath; } } else { // not overridden return linkerPath; } } // helper function that determines the type of symbol removal strategy to use, depending on the linker static coff::SymbolRemovalStrategy::Enum DetermineSymbolRemovalStrategy(const symbols::LinkerDB* linkerDb) { const std::wstring linkerPath = GetLinkerPath(linkerDb); compiler::LinkerType::Enum linkerType = compiler::DetermineLinkerType(linkerPath.c_str()); if (linkerType == compiler::LinkerType::LLD) { return coff::SymbolRemovalStrategy::LLD_COMPATIBLE; } return coff::SymbolRemovalStrategy::MSVC_COMPATIBLE; } // helper function for creating a CallHooks command static commands::CallHooks MakeCallHooksCommand(hook::Type::Enum type, const void* moduleBase, uint32_t firstRva, uint32_t lastRva) { return commands::CallHooks { type, pointer::Offset(moduleBase, firstRva), pointer::Offset(moduleBase, lastRva) }; } // helper function for creating a CallHooks command static commands::CallHooks MakeCallHooksCommand(hook::Type::Enum type, const void* moduleBase, const ModuleCache::FindHookData& hookData) { return MakeCallHooksCommand(type, moduleBase, hookData.firstRva, hookData.lastRva); } static LiveModule::CompileResult Compile(ModuleCache* moduleCache, const symbols::ObjPath& normalizedObjPath, symbols::Compiland* compiland, const LiveModule::PerProcessData* processData, size_t processCount, unsigned int flags, LiveModule::UpdateType::Enum updateType) { const std::wstring compilerPath = GetCompilerPath(compiland); const compiler::CompilerType::Enum compilerType = compiler::DetermineCompilerType(compilerPath.c_str()); // AMALGAMATION // for files that are part of an amalgamation, check their current command-line options, file timestamps, etc. against // those stored in the database. if nothing has changed, then don't compile the file at all. const bool isPartOfAmalgamation = symbols::IsPartOfAmalgamation(compiland); if (isPartOfAmalgamation) { if (amalgamation::ReadAndCompareDatabase(normalizedObjPath, compilerPath, compiland, appSettings::g_compilerOptions->GetValue())) { // nothing has changed according to the amalgamation database, so we can skip compilation of this file LC_LOG_USER("Ignoring up-to-date split file %s", normalizedObjPath.c_str()); return LiveModule::CompileResult { 0u, false }; } else { // this split file is going to be compiled. delete its database to ensure that when this file fails // to compile or the process terminates, the file gets compiled in the next Live++ session because // no database will be found on disk. amalgamation::DeleteDatabase(normalizedObjPath); } } // the compiler command-line options potentially get very long, reserve enough space. // note that the compiler expects commands in a response file to be in ANSI, not UTF-16. std::string compilerOptions; compilerOptions.reserve(1u * 1024u * 1024u); if (compilerType == compiler::CompilerType::CL) { // add the "compile only" switch in any case. if it's already there, no harm done. // for compilands that were compiled AND linked using cl.exe (which can call the linker internally!), this // needs to be added. compilerOptions += "-c "; // add compiler options based on flags if (flags & CompileFlags::SERIALIZE_PDB_ACCESS) { compilerOptions += "-FS "; } } // add the real command line for this compiland. // note that this must always come first for Clang, because of the first "-cc1" command. compilerOptions += compiland->commandLine.c_str(); compilerOptions += " "; // add custom compiler options { const std::wstring customOptions = appSettings::g_compilerOptions->GetValue(); if (customOptions.length() != 0u) { compilerOptions += string::ToAnsiString(string::ToUtf8String(customOptions)); compilerOptions += " "; } } if (compilerType == compiler::CompilerType::CL) { // add the command line that specifies the .pdb path in case its not contained in the compiland's command line. // note that for builds using /Z7, the PDB path is optional and not needed. const bool hasPdbPath = (compiland->pdbPath.GetLength() != 0u); const bool hasPdbCommandLine = string::Contains(compiland->commandLine.c_str(), "-Fd"); if (hasPdbPath && !hasPdbCommandLine) { compilerOptions += "-Fd\""; // the .PDB path could contain UTF8 characters, but the response file wants ANSI compilerOptions += string::ToAnsiString(compiland->pdbPath); compilerOptions += "\" "; } // add the command line that specifies the output .obj path in case its not contained in the compiland's command line if (!string::Contains(compiland->commandLine.c_str(), "-Fo")) { compilerOptions += "-Fo\""; // the .obj path could contain UTF8 characters, but the response file wants ANSI compilerOptions += string::ToAnsiString(compiland->originalObjPath); compilerOptions += "\" "; } } else if (compilerType == compiler::CompilerType::CLANG) { // for Clang, the output .obj path must always be specified compilerOptions += "-o\""; // the .obj path could contain UTF8 characters, but the response file wants ANSI. // additionally, Clang wants double backslashes. compilerOptions += string::ToAnsiString(ImmutableString(string::ReplaceAll(string::ReplaceAll(compiland->originalObjPath.c_str(), "\\", "/"), "/", "\\\\").c_str())); compilerOptions += "\" "; } // add the name of the compiland's source compilerOptions += "\""; // prettify the source path so that e.g. error messages will read C:\Folder\File.cpp rather than c:\folder\file.cpp. // normalizing is NOT allowed, we don't want to follow reparse points! { const std::wstring wideSrcPath = string::ToWideString(compiland->srcPath); const Filesystem::Path prettyPath = Filesystem::NormalizePathWithoutResolvingLinks(wideSrcPath.c_str()); if (compilerType == compiler::CompilerType::CL) { compilerOptions += string::ToAnsiString(string::ToUtf8String(prettyPath.GetString())); } else if (compilerType == compiler::CompilerType::CLANG) { // Clang wants double backslashes compilerOptions += string::ToAnsiString(string::ToUtf8String(string::ReplaceAll(string::ReplaceAll(prettyPath.GetString(), L"\\", L"/"), L"/", L"\\\\"))); } } compilerOptions += "\""; // create a temporary file that acts as a so-called response file for the compiler, and contains // the whole compiler command-line. this is done because the latter can get very long, longer // than the limit of 32k characters. const Filesystem::Path responseFilePath = Filesystem::GenerateTempFilename(); Filesystem::CreateFileWithData(responseFilePath.GetString(), compilerOptions.c_str(), compilerOptions.size() * sizeof(char)); std::wstring compilerCommandLine; compilerCommandLine.reserve(256u); // start command line with quoted name of cl.exe, e.g. "C:\Program Files (x86)\Microsoft Visual Studio 14\VC\bin\cl.exe" compilerCommandLine += L"\""; compilerCommandLine += compilerPath; compilerCommandLine += L"\" "; // add response file to command line compilerCommandLine += L"@\""; compilerCommandLine += responseFilePath.GetString(); compilerCommandLine += L"\""; const Process::Environment environment = compiler::GetEnvironmentFromCache(compilerPath.c_str()); const void* environmentData = environment.data; std::wstring workingDirectory = string::ToWideString(compiland->workingDirectory); // if the working directory does not exist, use the compiler's directory instead. // otherwise, remote/distributed builds would use working directories on remote machines. { const Filesystem::PathAttributes& attributes = Filesystem::GetAttributes(workingDirectory.c_str()); if (!Filesystem::DoesExist(attributes)) { workingDirectory = Filesystem::GetDirectory(compilerPath.c_str()).GetString(); } } LC_LOG_USER("Compiling %s %s", isPartOfAmalgamation ? "split file" : "file", normalizedObjPath.c_str()); Process::Context* processContext = Process::Spawn(compilerPath.c_str(), workingDirectory.c_str(), compilerCommandLine.c_str(), environmentData, Process::SpawnFlags::REDIRECT_STDOUT | Process::SpawnFlags::NO_WINDOW); const unsigned int exitCode = Process::Wait(processContext); const std::wstring processStdout = Process::GetStdOutData(processContext); const wchar_t* compilerOutput = processStdout.c_str(); // log the complete command-line into the DEV log { LC_LOG_DEV("Compiler command-line: "); Logging::LogNoFormat(compilerOptions.c_str()); Logging::LogNoFormat("\n"); } { CriticalSection::ScopedLock lock(&g_compileOutputCS); // log to local UI Logging::LogNoFormat(compilerOutput); const size_t outputLength = processStdout.length(); if (outputLength != 0u) { if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { // send log to host DLL for (size_t p = 0u; p < processCount; ++p) { const DuplexPipe* pipe = processData[p].liveProcess->GetPipe(); commands::LogOutput cmd {}; pipe->SendCommandAndWaitForAck(cmd, compilerOutput, (outputLength + 1u) * sizeof(wchar_t)); } // send log to all registered hooks in case compilation was not successful if (exitCode != 0u) { const ModuleCache::FindHookData& hookData = moduleCache->FindHooksInSectionBackwards(ModuleCache::SEARCH_ALL_MODULES, ImmutableString(LPP_COMPILE_ERROR_MESSAGE_SECTION)); if ((hookData.firstRva != 0u) && (hookData.lastRva != 0u)) { const size_t count = hookData.data->processes.size(); for (size_t p = 0u; p < count; ++p) { const ModuleCache::ProcessData& hookProcessData = hookData.data->processes[p]; void* moduleBase = hookProcessData.moduleBase; const DuplexPipe* pipe = hookProcessData.pipe; pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::COMPILE_ERROR_MESSAGE, moduleBase, hookData), compilerOutput, (outputLength + 1u) * sizeof(wchar_t)); } } } } } } Process::Destroy(processContext); Filesystem::Delete(responseFilePath.GetString()); return LiveModule::CompileResult { exitCode, true }; } struct SymbolAndRelocation { const coff::Symbol* symbol; const coff::Relocation* relocation; }; static const symbols::Symbol* FindOriginalSymbolForStrippedCandidate ( const ModuleCache* moduleCache, const ImmutableString& symbolName, const coff::CoffDB* coffDb, const types::vector& cache ) { if (!coffDb) { return nullptr; } // if the given symbol exists in the live module already, and all relocations to it would // be patched anyway, then we don't need it. const ModuleCache::FindSymbolData findData = moduleCache->FindSymbolByName(ModuleCache::SEARCH_ALL_MODULES, symbolName); if (!findData.symbol) { // this symbol does not exist in our live module yet, so we absolutely need it return nullptr; } if (!relocations::WouldPatchRelocation(symbolName)) { // we would not patch relocations to this symbol, hence it's needed return nullptr; } // TODO: all relocations point to the same destination symbol, hence all relocations also have the same destination section index. // if a relocation wouldn't be patched because of certain characteristics of the section, we can do that ONCE here, and don't need to // check every relocation. const size_t relocationCount = cache.size(); for (size_t i = 0u; i < relocationCount; ++i) { const coff::Symbol* symbol = cache[i].symbol; const coff::Relocation* relocation = cache[i].relocation; const ImmutableString& srcSymbolName = coff::GetSymbolName(coffDb, symbol); // this is a relocation to the symbol in question if (!relocations::WouldPatchRelocation(relocation, coffDb, srcSymbolName, findData)) { // this relocation to the symbol would not be patched by us, hence we probably need this symbol. // however, there are special cases where we want to strip symbols even though we might not patch // all relocations to it. // special case #1: a relocation from an exception-related unwind symbol (?dtor$) to a // dynamic initializer (??__E), e.g. a relocation from ?dtor$0@?0???__ESomeGlobalVariable@@YAXXZ@4HA // ("int `void __cdecl `dynamic initializer for 'SomeGlobalVariable''(void)'::`1'::dtor$0") // in this case, the relocation is not patched, but the dynamic initializer refers to an already // existing symbol. that dynamic initializer will be removed by us later on anyway, hence // those relocations do not really need patching. if (symbols::IsExceptionUnwindSymbolForDynamicInitializer(srcSymbolName)) { LC_LOG_DEV("Ignoring unpatched relocation from symbol %s", srcSymbolName.c_str()); continue; } return nullptr; } } // the symbol exists already, and we would patch all relocations to it anyway, so remove it return findData.symbol; } struct CacheUpdate { enum Enum { ALL, NON_EXISTANT }; }; template static types::vector UpdateCoffCache(const T& compilands, CoffCache* coffCache, CacheUpdate::Enum updateType, const types::vector& modifiedOrNewObjFiles) { LC_LOG_INDENT_DEV; types::vector updatedCoffs; updatedCoffs.reserve(compilands.size()); auto taskRoot = scheduler::CreateEmptyTask(); types::vector tasks; tasks.reserve(compilands.size()); for (auto it = compilands.begin(); it != compilands.end(); ++it) { symbols::ObjPath objPath = it->first; const std::wstring& wideObjPath = string::ToWideString(objPath); const symbols::Compiland* compiland = it->second; const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); const bool shouldUpdate = (updateType == CacheUpdate::NON_EXISTANT) ? (coffCache->Lookup(objPath) == nullptr) // NON-EXISTANT: update cache only for files which don't have an entry yet : true; // ALL: always update the entry if (shouldUpdate) { updatedCoffs.push_back(objPath); auto task = scheduler::CreateTask(taskRoot, [objPath, wideObjPath, coffCache, compilandUniqueId]() { LC_LOG_DEV("Updating COFF cache for file %s", objPath.c_str()); coff::ObjFile* objFile = coff::OpenObj(wideObjPath.c_str()); if (objFile && objFile->memoryFile) { coff::CoffDB* database = coff::GatherDatabase(objFile, compilandUniqueId); if (database) { coffCache->Update(objPath, database); } coff::CloseObj(objFile); } return true; }); scheduler::RunTask(task); tasks.emplace_back(task); } } // wait for all tasks to end scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); // destroy all tasks scheduler::DestroyTasks(tasks); scheduler::DestroyTask(taskRoot); return updatedCoffs; } #if LC_64_BIT static executable::PreferredBase FindPreferredImageBase(uint32_t imageSize, Process::Id processId, Process::Handle processHandle, void* moduleBase) { // work out the lower and upper bound of the memory region into which a patch could be loaded const uint32_t exeSize = Process::GetModuleSize(processHandle, moduleBase); const uint32_t patchSize = imageSize; const void* lowerBound = GetLowerBoundIn4GBRange(pointer::Offset(moduleBase, exeSize)); const void* upperBound = GetUpperBoundIn4GBRange(moduleBase); LC_LOG_DEV("Scanning memory range from 0x%p to 0x%p (base: 0x%p, exeSize: 0x%X, patchSize: 0x%X, PID: %d)", lowerBound, upperBound, moduleBase, exeSize, patchSize, +processId); // modules can only be loaded at 64KB boundaries, so we should scan memory only at aligned addresses const size_t MODULE_ALIGNMENT = 64u * 1024u; void* preferredBase = Process::ScanMemoryRange(processHandle, lowerBound, upperBound, patchSize, MODULE_ALIGNMENT); const executable::PreferredBase preferredImageBase = pointer::AsInteger(preferredBase); LC_LOG_DEV("Preferred base address for image: 0x%" PRIX64 " (PID: %d)", preferredImageBase, +processId); return preferredImageBase; } #endif // helper function that returns the instruction pointers of all threads of a process static types::vector EnumerateInstructionPointers(Process::Id processId) { const std::vector& threadIds = Process::EnumerateThreads(processId); const size_t threadCount = threadIds.size(); types::vector instructionPointers; instructionPointers.reserve(threadCount); for (size_t i=0u; i < threadCount; ++i) { const Thread::Id threadId = threadIds[i]; Thread::Handle threadHandle = Thread::Open(threadId); const Thread::Context context = Thread::GetContext(threadHandle); const void* ip = Thread::ReadInstructionPointer(&context); instructionPointers.push_back(ip); Thread::Close(threadHandle); } return instructionPointers; } // helper function that checks whether a patch was loaded at a valid address static bool CheckPatchAddressValidity(void* originalModuleBase, void* patchBase, Process::Handle processHandle) { if (!patchBase) { return false; } #if LC_64_BIT // even though we rebased the image, the OS might have decided to load the DLL at a different address (though that really // should not happen). // so for 64-bit applications, check whether the patch was loaded at an address that can be reached via +/-2GB offsets from // the original executable. if its outside this range, we cannot use it. else { if (patchBase >= originalModuleBase) { const uint32_t patchSize = Process::GetModuleSize(processHandle, patchBase); const uint64_t displacement = pointer::Displacement(originalModuleBase, pointer::Offset(patchBase, patchSize)); if (displacement > 0x80000000ull) { LC_ERROR_USER("Patch was loaded outside 2GB range and cannot be activated."); LC_ERROR_DEV("Patch loaded outside range (disp: 0x%p, base: 0x%p, patch base: 0x%p, patch size: 0x%X)", displacement, originalModuleBase, patchBase, patchSize); return false; } } else { const uint32_t exeSize = Process::GetModuleSize(processHandle, originalModuleBase); const uint64_t displacement = pointer::Displacement(patchBase, pointer::Offset(originalModuleBase, exeSize)); if (displacement > 0x80000000ull) { LC_ERROR_USER("Patch was loaded outside 2GB range and cannot be activated."); LC_ERROR_DEV("Patch loaded outside range (disp: 0x%p, base: 0x%p, patch base: 0x%p, exe size: 0x%X)", displacement, originalModuleBase, patchBase, exeSize); return false; } } } #else LC_UNUSED(originalModuleBase); LC_UNUSED(processHandle); #endif return true; } // helper strings that denote UE4-specific symbols. // note that the global object array is a reference type, which we don't want to put into our UE4-specific library. // therefore, the object array in the DLL (reference) has a different type than the object array in our LIB (pointer). #if LC_64_BIT static const char* const g_ue4NameTableInDLL = "?GFNameTableForDebuggerVisualizers_MT@@3PEAPEAPEAUFNameEntry@@EA"; static const char* const g_ue4ObjectArrayInDLL = "?GObjectArrayForDebugVisualizers@@3AEAPEAVFChunkedFixedUObjectArray@@EA"; static const char* const g_ue4NameTableInLIB = "?GFNameTableForDebuggerVisualizers_MT@@3PEAPEAPEAUFNameEntry@@EA"; static const char* const g_ue4ObjectArrayInLIB = "?GObjectArrayForDebugVisualizers@@3PEAVFChunkedFixedUObjectArray@@EA"; #else static const char* const g_ue4NameTableInDLL = "?GFNameTableForDebuggerVisualizers_MT@@3PAPAPAUFNameEntry@@A"; static const char* const g_ue4ObjectArrayInDLL = "?GObjectArrayForDebugVisualizers@@3AAPAVFChunkedFixedUObjectArray@@A"; static const char* const g_ue4NameTableInLIB = "?GFNameTableForDebuggerVisualizers_MT@@3PAPAPAUFNameEntry@@A"; static const char* const g_ue4ObjectArrayInLIB = "?GObjectArrayForDebugVisualizers@@3PAVFChunkedFixedUObjectArray@@A"; #endif // helper function to patch UE4-specific symbols static void PatchUE4NatVisSymbols(void* originalModuleBase, void* patchBase, uint32_t originalRva, uint32_t patchRva, Process::Handle processHandle) { const void* originalAddr = pointer::Offset(originalModuleBase, originalRva); void* patchAddr = pointer::Offset(patchBase, patchRva); const uintptr_t value = Process::ReadProcessMemory(processHandle, originalAddr); Process::WriteProcessMemory(processHandle, patchAddr, value); } // helper function to patch security cookies static void PatchSecurityCookie(void* originalModuleBase, void* patchBase, uint32_t originalRva, uint32_t patchRva, Process::Handle processHandle) { const void* cookieAddr = pointer::Offset(originalModuleBase, originalRva); void* newCookieAddr = pointer::Offset(patchBase, patchRva); #if LC_64_BIT typedef uint64_t CookieType; #else typedef uint32_t CookieType; #endif const CookieType cookie = Process::ReadProcessMemory(processHandle, cookieAddr); Process::WriteProcessMemory(processHandle, newCookieAddr, cookie); } // helper function to patch DllMain static void PatchDllMain(void* patchBase, uint32_t dllMainRva, Process::Handle processHandle) { LC_LOG_DEV("Disabling optional DLL entry point"); // the code with which we replace DllMain is simply: // return TRUE; // this needs to return 1 in the (e)ax register and return from the function (which is done differently // depending on the architecture) #if LC_64_BIT // the code to inject on x64 is: // B0 01 mov al, 1 // C3 ret different calling convention than x86 // BEGIN EPIC MOD const uint8_t PatchData[3u] = { 0xB0, 0x01, 0xC3 }; // END EPIC MOD #else // the code to inject on x86 is: // B0 01 mov al, 1 // C2 0C 00 ret 0Ch different calling convention than x64 // BEGIN EPIC MOD const uint8_t PatchData[5u] = { 0xB0, 0x01, 0xC2, 0x0C, 0x00 }; // END EPIC MOD #endif uint8_t* address = pointer::Offset(patchBase, dllMainRva); // BEGIN EPIC MOD Process::WriteProcessMemory(processHandle, address, PatchData, sizeof(PatchData)); // END EPIC MOD } // helper function that generates a threshold value when to split amalgamated files, based on global app settings static unsigned int GetAmalgamatedSplitThreshold(void) { // changing these settings during a Live++ session is not supported, hence we use their initial values // rather than their current values. const bool shouldSplit = appSettings::g_amalgamationSplitIntoSingleParts->GetInitialValue(); if (!shouldSplit) { return 0u; } const int threshold = appSettings::g_amalgamationSplitMinCppCount->GetInitialValue(); if (threshold <= 1) { // negative values are illegal, and we don't attempt any splitting for 0 or 1 files, obviously return 0u; } return static_cast(threshold); } // helper function for calling compile start hooks static void CallCompileStartHooks(ModuleCache* moduleCache, LiveModule::UpdateType::Enum updateType) { if (updateType == LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { return; } const ModuleCache::FindHookData& hookData = moduleCache->FindHooksInSectionBackwards(ModuleCache::SEARCH_ALL_MODULES, ImmutableString(LPP_COMPILE_START_SECTION)); if ((hookData.firstRva != 0u) && (hookData.lastRva != 0u)) { const size_t count = hookData.data->processes.size(); for (size_t p = 0u; p < count; ++p) { const ModuleCache::ProcessData& processData = hookData.data->processes[p]; const Process::Id pid = processData.processId; void* moduleBase = processData.moduleBase; const DuplexPipe* pipe = processData.pipe; LC_LOG_USER("Calling compile start hooks (PID: %d)", +pid); pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::COMPILE_START, moduleBase, hookData), nullptr, 0u); } } } // helper function for calling compile success hooks static void CallCompileSuccessHooks(ModuleCache* moduleCache, LiveModule::UpdateType::Enum updateType) { if (updateType == LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { return; } const ModuleCache::FindHookData& hookData = moduleCache->FindHooksInSectionBackwards(ModuleCache::SEARCH_ALL_MODULES, ImmutableString(LPP_COMPILE_SUCCESS_SECTION)); if ((hookData.firstRva != 0u) && (hookData.lastRva != 0u)) { const size_t count = hookData.data->processes.size(); for (size_t p = 0u; p < count; ++p) { const ModuleCache::ProcessData& processData = hookData.data->processes[p]; const Process::Id pid = processData.processId; void* moduleBase = processData.moduleBase; const DuplexPipe* pipe = processData.pipe; LC_LOG_USER("Calling compile success hooks (PID: %d)", +pid); pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::COMPILE_SUCCESS, moduleBase, hookData), nullptr, 0u); } } } // helper function for calling compile error hooks static void CallCompileErrorHooks(ModuleCache* moduleCache, LiveModule::UpdateType::Enum updateType) { if (updateType == LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { return; } const ModuleCache::FindHookData& hookData = moduleCache->FindHooksInSectionBackwards(ModuleCache::SEARCH_ALL_MODULES, ImmutableString(LPP_COMPILE_ERROR_SECTION)); if ((hookData.firstRva != 0u) && (hookData.lastRva != 0u)) { const size_t count = hookData.data->processes.size(); for (size_t p = 0u; p < count; ++p) { const ModuleCache::ProcessData& processData = hookData.data->processes[p]; const Process::Id pid = processData.processId; void* moduleBase = processData.moduleBase; const DuplexPipe* pipe = processData.pipe; LC_LOG_USER("Calling compile error hooks (PID: %d)", +pid); pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::COMPILE_ERROR, moduleBase, hookData), nullptr, 0u); } } } } LiveModule::LiveModule(const wchar_t* moduleName, const executable::Header& imageHeader, RunMode::Enum runMode) : m_moduleName(moduleName) , m_imageHeader(imageHeader) , m_runMode(runMode) , m_compiledModulePatches() { m_modifiedFiles.reserve(16u); m_compiledCompilands.reserve(16u); m_compiledModulePatches.reserve(64u); } LiveModule::~LiveModule(void) { delete m_coffCache; delete m_moduleCache; delete m_contributionDB; delete m_compilandDB; delete m_libraryDB; delete m_linkerDB; delete m_thunkDB; delete m_imageSectionDB; } void LiveModule::Load(symbols::Provider* provider, symbols::DiaCompilandDB* diaCompilandDb) { telemetry::Scope loadLiveModuleScope("Loading live module"); m_coffCache = new CoffCache; m_moduleCache = new ModuleCache; // this is so fast there's nothing to gain in doing this concurrently IDiaSymbol* linkerSymbol = symbols::FindLinkerSymbol(diaCompilandDb); auto taskRoot = scheduler::CreateEmptyTask(); // because we only read from the PDB file, most of the functions that gather data from the // PDB can run concurrently. however, the msdia DLL will block in certain functions when // being called from more than one thread. this is why we open a second and third DIA provider // that allow us to gather different data streams from different threads. auto taskSymbolDB = scheduler::CreateTask(taskRoot, [provider]() { return symbols::GatherSymbols(provider); }); scheduler::RunTask(taskSymbolDB); auto taskLibraryDB = scheduler::CreateTask(taskRoot, [diaCompilandDb]() { return symbols::GatherLibraries(diaCompilandDb); }); scheduler::RunTask(taskLibraryDB); auto taskContributionDB = scheduler::CreateTask(taskRoot, [this]() { symbols::Provider* localProvider = symbols::OpenEXE(m_moduleName.c_str(), symbols::OpenOptions::NONE); symbols::DiaCompilandDB* localDiaCompilandDb = symbols::GatherDiaCompilands(localProvider); auto db = symbols::GatherContributions(localProvider); symbols::DestroyDiaCompilandDB(localDiaCompilandDb); symbols::Close(localProvider); return db; }); scheduler::RunTask(taskContributionDB); auto taskCompilandDB = scheduler::CreateTask(taskRoot, [this]() { symbols::Provider* localProvider = symbols::OpenEXE(m_moduleName.c_str(), symbols::OpenOptions::NONE); symbols::DiaCompilandDB* localDiaCompilandDb = symbols::GatherDiaCompilands(localProvider); uint32_t options = 0u; if (appSettings::g_enableDevLogCompilands->GetValue()) { options |= symbols::CompilandOptions::GENERATE_LOGS; } if (appSettings::g_compilerForcePchPdbs->GetValue()) { options |= symbols::CompilandOptions::FORCE_PCH_PDBS; } // in case the user wants to use a completely external build system, we track .objs only if (m_runMode == RunMode::EXTERNAL_BUILD_SYSTEM) { options |= symbols::CompilandOptions::TRACK_OBJ_ONLY; } symbols::CompilandDB* db = nullptr; // BEGIN EPIC MOD #if 1 db = symbols::GatherCompilands(localProvider, localDiaCompilandDb, GetAmalgamatedSplitThreshold(), options); #else const std::wstring fastBuildDatabasePath = appSettings::g_fastBuildDatabasePath->GetValue(); const std::wstring fastBuildDllName = appSettings::g_fastBuildDllName->GetValue(); if (fastBuildDatabasePath.length() > 0u) { db = FASTBuild::GatherCompilands(fastBuildDllName.c_str(), string::ToUtf8String(fastBuildDatabasePath).c_str(), string::ToUtf8String(m_moduleName).c_str(), GetAmalgamatedSplitThreshold(), options); // safety net if (!db) { db = new symbols::CompilandDB; } } else { db = symbols::GatherCompilands(localProvider, localDiaCompilandDb, GetAmalgamatedSplitThreshold(), options); } #endif // END EPIC MOD symbols::DestroyDiaCompilandDB(localDiaCompilandDb); symbols::Close(localProvider); return db; }); scheduler::RunTask(taskCompilandDB); auto taskThunkDB = scheduler::CreateTask(taskRoot, [linkerSymbol]() { return symbols::GatherThunks(linkerSymbol); }); scheduler::RunTask(taskThunkDB); auto taskImageSectionDB = scheduler::CreateTask(taskRoot, [linkerSymbol]() { return symbols::GatherImageSections(linkerSymbol); }); scheduler::RunTask(taskImageSectionDB); auto taskLinkerDB = scheduler::CreateTask(taskRoot, [linkerSymbol]() { return symbols::GatherLinker(linkerSymbol); }); scheduler::RunTask(taskLinkerDB); // ensure asynchronous operations have finished scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); m_symbolDB = taskSymbolDB->GetResult(); m_contributionDB = taskContributionDB->GetResult(); m_compilandDB = taskCompilandDB->GetResult(); m_libraryDB = taskLibraryDB->GetResult(); m_thunkDB = taskThunkDB->GetResult(); m_imageSectionDB = taskImageSectionDB->GetResult(); m_linkerDB = taskLinkerDB->GetResult(); // kill tasks scheduler::DestroyTask(taskRoot); scheduler::DestroyTask(taskSymbolDB); scheduler::DestroyTask(taskContributionDB); scheduler::DestroyTask(taskCompilandDB); scheduler::DestroyTask(taskLibraryDB); scheduler::DestroyTask(taskThunkDB); scheduler::DestroyTask(taskImageSectionDB); scheduler::DestroyTask(taskLinkerDB); symbols::FinalizeContributions(m_compilandDB, m_contributionDB); // check linker command-line for missing/wrong linker options { // the command-line is optional if (m_linkerDB->commandLine.GetLength() != 0u) { const std::string& upperCaseCmdLine = string::ToUpper(m_linkerDB->commandLine.c_str()); // check for /FUNCTIONPADMIN { // /FUNCTIONPADMIN is off by default const bool containsFunctionpadmin = string::Contains(upperCaseCmdLine.c_str(), "/FUNCTIONPADMIN"); if (!containsFunctionpadmin) { LC_WARNING_USER("Linker option /FUNCTIONPADMIN seems to be missing for module %S, some functions might not be patchable", m_moduleName.c_str()); } } // check for /OPT:NOREF and /OPT:NOICF { const bool containsOptRef = string::Contains(upperCaseCmdLine.c_str(), "/OPT:REF"); const bool containsOptIcf = string::Contains(upperCaseCmdLine.c_str(), "/OPT:ICF"); // having either of those one explicitly is wrong if (containsOptRef) { LC_WARNING_USER("Unsupported linker option /OPT:REF is set for module %S, some functions might not be patchable", m_moduleName.c_str()); } if (containsOptIcf) { LC_WARNING_USER("Unsupported linker option /OPT:ICF is set for module %S, some functions might not be patchable", m_moduleName.c_str()); } const bool containsDebug = string::Contains(upperCaseCmdLine.c_str(), "/DEBUG"); // when /DEBUG is specified, /OPT defaults to NOREF, so it is ok if neither /OPT:NOREF nor /OPT:NOICF are specified. // in other builds however, both /OPT:NOREF and /OPT:NOICF must be set explicitly. if (!containsDebug) { const bool containsOptNoRef = string::Contains(upperCaseCmdLine.c_str(), "/OPT:NOREF"); const bool containsOptNoIcf = string::Contains(upperCaseCmdLine.c_str(), "/OPT:NOICF"); // not having those is wrong if (!containsOptNoRef) { LC_WARNING_USER("Linker option /OPT:NOREF seems to be missing for module %S, some functions might not be patchable", m_moduleName.c_str()); } if (!containsOptNoIcf) { LC_WARNING_USER("Linker option /OPT:NOICF seems to be missing for module %S, some functions might not be patchable", m_moduleName.c_str()); } } } } } symbols::DestroyLinkerSymbol(linkerSymbol); // build a cache that stores all external/public symbols for each compiland. // at the same time, build a list of precompiled header symbols and the compiland they're stored in. // this is done simultaneously because it touches the same data. // additionally, we *also* get all weak symbols that are part of a library. those need special treatment when // linking. { // we only know public symbols at this point, so walk all of them and find their corresponding contribution. // there are two ways to go about this: // 1) walk all symbols, find their contribution // 2) walk all contributions, find their symbol // this needs to be done using 1), otherwise some external symbols cannot be found because their contributions // have been merged. for (auto it : m_symbolDB->symbolsByRva) { const uint32_t rva = it.first; const symbols::Symbol* symbol = it.second; const symbols::Contribution* contribution = symbols::FindContributionByRVA(m_contributionDB, rva); if (contribution) { const ImmutableString& compilandName = symbols::GetContributionCompilandName(m_contributionDB, contribution); m_externalSymbolsPerCompilandCache[compilandName].push_back(symbol); // is this a symbol emitted from a precompiled header? if (symbols::IsPchSymbol(symbol->name)) { // yes, store it in our database m_pchSymbolToCompilandName.emplace(symbol->name, compilandName); } } } } if (m_runMode == RunMode::EXTERNAL_BUILD_SYSTEM) { LC_LOG_DEV("Caching all .objs on Load() due to external build system being used"); // the user wants to use an external build system. in this case, we only track .objs for changes and never // compile anything ourselves. we cannot load .objs lazily in this case, so we have to do that right now. struct GatherResult { coff::CoffDB* database; symbols::ObjPath objPath; GatherResult(void) = default; // BEGIN EPIC MOD GatherResult(coff::CoffDB* _database, symbols::ObjPath _objPath) : database(_database), objPath(_objPath) {} // END EPIC MOD GatherResult(const GatherResult& other) = default; GatherResult(GatherResult&& other) = default; GatherResult& operator=(const GatherResult&) = delete; GatherResult& operator=(GatherResult&&) = default; }; scheduler::TaskBase* gatherTaskRoot = scheduler::CreateEmptyTask(); types::vector*> gatherTasks; gatherTasks.reserve(m_compilandDB->compilands.size()); for (auto it : m_compilandDB->compilands) { const symbols::ObjPath& objPath = it.first; symbols::Compiland* compiland = it.second; LC_LOG_DEV("Updating COFF cache for %s", objPath.c_str()); // do the loading and gathering concurrently auto task = scheduler::CreateTask(gatherTaskRoot, [objPath, compiland]() { const std::wstring& wideObjPath = string::ToWideString(objPath); coff::ObjFile* objFile = coff::OpenObj(wideObjPath.c_str()); coff::CoffDB* database = coff::GatherDatabase(objFile, compiland->uniqueId); coff::CloseObj(objFile); return GatherResult { database, objPath }; }); scheduler::RunTask(task); gatherTasks.emplace_back(task); } // wait for all tasks to end scheduler::RunTask(gatherTaskRoot); scheduler::WaitForTask(gatherTaskRoot); // store the databases into the cache { const size_t count = gatherTasks.size(); for (size_t i = 0u; i < count; ++i) { const GatherResult& result = gatherTasks[i]->GetResult(); coff::CoffDB* database = result.database; if (database) { m_coffCache->Update(result.objPath, database); } } } // destroy tasks scheduler::DestroyTasks(gatherTasks); scheduler::DestroyTask(gatherTaskRoot); } // now that all the databases are built, store their info into the module cache // BEGIN EPIC MOD m_mainModuleToken = m_moduleCache->Insert(m_symbolDB, m_contributionDB, m_compilandDB, m_thunkDB, m_imageSectionDB, provider->lastModificationTime); // END EPIC MOD } void LiveModule::Unload(void) { const size_t patchCount = m_moduleCache->GetSize(); if (patchCount == 0u) { return; } // do not unload the first "patch", as it is the main module that the user unloads for (size_t i = 0u; i < patchCount - 1u; ++i) { // it is crucial to unload patches from last to first, because relocations probably link back // to the original module! const ModuleCache::Data& entry = m_moduleCache->GetEntry(patchCount - 1u - i); const size_t processCount = entry.processes.size(); for (size_t p = 0u; p < processCount; ++p) { const ModuleCache::ProcessData& process = entry.processes[p]; if (!Process::IsActive(process.processHandle)) { // this process is no longer valid, ignore it continue; } const DuplexPipe* clientPipe = process.pipe; clientPipe->SendCommandAndWaitForAck(commands::UnloadPatch { static_cast(process.moduleBase) }, nullptr, 0u); } } } void LiveModule::RegisterProcess(LiveProcess* liveProcess, void* moduleBase, const std::wstring& modulePath) { m_moduleCache->RegisterProcess(m_mainModuleToken, liveProcess, moduleBase); liveProcess->ReserveVirtualMemoryPages(moduleBase); PerProcessData perProcessData = { liveProcess, moduleBase, modulePath }; m_perProcessData.emplace_back(perProcessData); } void LiveModule::UnregisterProcess(LiveProcess* liveProcess) { const Process::Id processId = liveProcess->GetProcessId(); m_moduleCache->UnregisterProcess(liveProcess); m_patchedAddressesPerProcess.erase(processId); for (auto it = m_perProcessData.begin(); it != m_perProcessData.end(); ++it) { const PerProcessData& data = *it; if (data.liveProcess == liveProcess) { liveProcess->FreeVirtualMemoryPages(data.originalModuleBase); m_perProcessData.erase(it); break; } } } void LiveModule::DisableControlFlowGuard(LiveProcess* liveProcess, void* moduleBase) { Process::Handle processHandle = liveProcess->GetProcessHandle(); // disable control flow guard (CFG) checks // https://msdn.microsoft.com/en-us/library/windows/desktop/mt637065(v=vs.85).aspx { // all CFG-enabled builds use a function pointer __guard_check_icall_fptr that initially (at compile-time) points // to _guard_check_icall_nop. additionally, some code (e.g. in the CRT) will directly call _guard_check_icall. // when such a CFG-enabled executable is loaded by a CFG-aware OS, the module loader // will automatically patch this function pointer to point to _guard_check_icall, and let _guard_check_icall point // to ntdll.dll!LdrpValidateUserCallTarget, which is not exported by the DLL, unfortunately. // we could easily find the function pointer and patch it to _guard_check_icall_nop so that checks do nothing, // but other DLLs (e.g. the CRT) contain their own copy of this function pointer, which we cannot patch because // we don't have that DLL's symbols. // one solution is to patch ntdll.dll!LdrpValidateUserCallTarget directly, because all checks will ultimately call // this function, but first we have to get its address. const symbols::Symbol* cfgFuncPtr = symbols::FindSymbolByName(m_symbolDB, ImmutableString(LC_IDENTIFIER("__guard_check_icall_fptr"))); if (cfgFuncPtr) { // read where the __guard_check_icall_fptr function pointer currently points to. // there are three possibilities: // 1) the compiler is CFG-aware, but /guard:CF was not set // 2) the compiler is CFG-aware, /guard:CF was set, but the module is loaded by an OS that is not CFG-aware // 3) the compiler is CFG-aware, /guard:CF was set, and the module is loaded by a CFG-aware OS // in cases 1) and 2), the function pointer will point to _guard_check_icall_nop, while in case 3) it will point to // ntdll.dll!LdrpValidateUserCallTarget. // this means that we can simply read the address the function pointer points to, and patch the function at that // address to return immediately. this works in all three cases, and effectively disables CFG for *all* modules // in this process. { // make sure the process gets suspended while writing to its memory. // otherwise, writing could change the page protection of an executable page while code is currently executing // (when using the lpp*Async API), which would lead to a crash. Process::Suspend(processHandle); void* addr = Process::ReadProcessMemory(processHandle, pointer::Offset(moduleBase, cfgFuncPtr->rva)); const uint8_t OPCODE_RET = 0xC3; Process::WriteProcessMemory(processHandle, addr, OPCODE_RET); Process::Resume(processHandle); } } } } void LiveModule::UpdateDirectoryCache(DirectoryCache* cache) { // walk all dependencies and generate/update cache entries for them for (auto it : m_compilandDB->dependencies) { symbols::Dependency* dependency = it.second; if (!dependency->parentDirectory) { // dependency does not have a valid parent directory entry yet, create a new one const ImmutableString& path = it.first; UpdateDirectoryCache(path, dependency, cache); } // merge initial changes of dependencies into the parent directories. // this ensures that modifications to source files that were done before a module was loaded, // will automatically be picked up by us. dependency->parentDirectory->hadChange |= dependency->hadInitialChange; } } // BEGIN EPIC MOD LiveModule::ErrorType::Enum LiveModule::Update(FileAttributeCache* fileCache, DirectoryCache* directoryCache, UpdateType::Enum updateType, const types::vector& modifiedOrNewObjFiles, const types::vector& additionalLibraries) // END EPIC MOD { telemetry::Scope updateScope("Update live module"); LC_LOG_DEV("LiveModule Update: %S", m_moduleName.c_str()); // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Checking modified files..."); // END EPIC MOD bool usesClang = false; bool forceAmalgamationPartsLinkage = false; // only check for modifications if no files have been handed to us if (modifiedOrNewObjFiles.size() == 0u) { // check all files whether they changed for (auto compilandIt = m_compilandDB->dependencies.begin(); compilandIt != m_compilandDB->dependencies.end(); ++compilandIt) { symbols::Dependency* dependency = compilandIt->second; if (!dependency->parentDirectory->hadChange) { // no need to check this compiland, the parent directory didn't notice a change continue; } const std::wstring filePath = string::ToWideString(compilandIt->first); const types::vector& objPaths = dependency->objPaths; const FileAttributeCache::Data& cacheData = fileCache->UpdateCacheData(filePath); const uint64_t currentTime = cacheData.lastModificationTime; if (currentTime != dependency->lastModification) { dependency->lastModification = currentTime; { const Filesystem::Path prettyPath = Filesystem::NormalizePathWithoutResolvingLinks(filePath.c_str()); LC_LOG_USER("File %S was modified", prettyPath.GetString()); } // AMALGAMATION if (appSettings::g_amalgamationSplitIntoSingleParts->GetValue()) { // look at each file individually and determine what to do for (auto it : objPaths) { symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, it); if (compiland) { if (symbols::IsAmalgamation(compiland)) { // split amalgamated file symbols::AmalgamatedCompiland* amalgamatedCompiland = symbols::FindAmalgamatedCompiland(m_compilandDB, it); if (amalgamatedCompiland) { // the amalgamated compiland needs to be split into its single parts. // add all compilands that are part of the amalgamation for compilation. // we always split in this case to trigger recompiles when included headers change. LC_LOG_USER("Splitting amalgamated/unity file %s", it.c_str()); if (!amalgamatedCompiland->isSplit) { // this is the first time the amalgamation is split into single files forceAmalgamationPartsLinkage = true; } m_modifiedFiles.insert(amalgamatedCompiland->singleParts.begin(), amalgamatedCompiland->singleParts.end()); amalgamatedCompiland->isSplit = true; } } else if (symbols::IsPartOfAmalgamation(compiland)) { // this file is part of an amalgamation. // if the amalgamation needs to be split, do that now. // in any case, this file needs to be recompiled. m_modifiedFiles.insert(it); // find the amalgamated compiland this file belongs to const ImmutableString& amalgamatedObjPath = compiland->amalgamationPath; symbols::AmalgamatedCompiland* amalgamatedCompiland = symbols::FindAmalgamatedCompiland(m_compilandDB, amalgamatedObjPath); if (amalgamatedCompiland) { if (!amalgamatedCompiland->isSplit) { // this is the first time the amalgamation is split into single files forceAmalgamationPartsLinkage = true; // the amalgamated compiland needs to be split into its single parts. // add all compilands that are part of the amalgamation for compilation, and mark the // amalgamated compiland as being split. LC_LOG_USER("Splitting amalgamated/unity file %s", amalgamatedObjPath.c_str()); m_modifiedFiles.insert(amalgamatedCompiland->singleParts.begin(), amalgamatedCompiland->singleParts.end()); amalgamatedCompiland->isSplit = true; } } } else { m_modifiedFiles.insert(it); } } } } else { // don't need to do anything fancy, just add all affected .objs m_modifiedFiles.insert(objPaths.begin(), objPaths.end()); } } } if (m_runMode == RunMode::DEFAULT) { if (m_modifiedFiles.size() == 0u) { if (m_compiledCompilands.size() == 0u) { // no change detected in this module return ErrorType::NO_CHANGE; } else { // there are still compiled files that haven't been linked } } else { // BEGIN EPIC MOD LC_LOG_USER("Detected %zu file(s) to be compiled for Live coding module %S", m_modifiedFiles.size(), m_moduleName.c_str()); // END EPIC MOD } } else if (m_runMode == RunMode::EXTERNAL_BUILD_SYSTEM) { if (m_modifiedFiles.size() == 0u) { // no changed .obj detected in this module return ErrorType::NO_CHANGE; } } } else { for (size_t i = 0u; i < modifiedOrNewObjFiles.size(); ++i) { LC_LOG_USER("File %S was modified or is new", modifiedOrNewObjFiles[i].objPath.c_str()); } // BEGIN EPIC MOD LC_LOG_USER("Building patch from %zu file(s) for Live coding module %S", modifiedOrNewObjFiles.size(), m_moduleName.c_str()); // END EPIC MOD } // let the user know that we're about to compile CallCompileStartHooks(m_moduleCache, updateType); // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Updating first time COFF cache..."); // END EPIC MOD // before starting to compile, update the COFF cache for files that have been touched for the first time struct ModifiedFile { symbols::ObjPath amalgamatedObjPath; symbols::ObjPath objPath; symbols::Compiland* compiland; bool compiledOnce; LC_DISABLE_ASSIGNMENT(ModifiedFile); }; // linearized version of all modified files which have their compiland stored in the database types::vector availableModifiedFiles; availableModifiedFiles.reserve(m_modifiedFiles.size()); // don't update the COFF cache in case some .obj files have been handed to us. // this is only allowed in external build system mode and all existing .objs will have been reconstructed already then. // new files will automatically get reconstructed when loading the patch and its PDB. if (modifiedOrNewObjFiles.size() == 0u) { telemetry::Scope updatingCoffCache("Updating first time COFF cache"); struct GatherResult { size_t fileIndex; coff::CoffDB* database; }; scheduler::TaskBase* taskRoot = scheduler::CreateEmptyTask(); types::vector*> gatherTasks; gatherTasks.reserve(m_modifiedFiles.size()); { types::StringSet updatedFiles; updatedFiles.reserve(m_modifiedFiles.size()); size_t fileIndex = 0u; for (auto fileIt = m_modifiedFiles.begin(); fileIt != m_modifiedFiles.end(); ++fileIt) { const symbols::ObjPath& objPath = *fileIt; symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); if (!compiland) { LC_ERROR_DEV("Cannot determine compiland belonging to file %s", objPath.c_str()); continue; } // AMALGAMATION // if this is the first time this .obj is touched, load it into our cache before compiling. // we need it for reconstructing symbols lazily later. // note that parts of amalgamated .obj must have their symbols reconstructed from the original // amalgamated file, not their single parts. const bool isPartOfAmalgamation = symbols::IsPartOfAmalgamation(compiland); const ImmutableString& amalgamatedObjPath = isPartOfAmalgamation ? compiland->amalgamationPath : objPath; availableModifiedFiles.emplace_back(ModifiedFile { amalgamatedObjPath, objPath, compiland, false }); if (!m_coffCache->Lookup(amalgamatedObjPath)) { const auto updatedFileIt = updatedFiles.find(amalgamatedObjPath); if (updatedFileIt == updatedFiles.end()) { updatedFiles.insert(amalgamatedObjPath); if (isPartOfAmalgamation) { LC_LOG_DEV("Touched %s for the first time, triggering COFF cache update for amalgamated file %s", objPath.c_str(), amalgamatedObjPath.c_str()); } else { LC_LOG_DEV("Touched %s for the first time, updating COFF cache", objPath.c_str()); } // do the loading and gathering concurrently auto task = scheduler::CreateTask(taskRoot, [fileIndex, amalgamatedObjPath, compiland]() { const std::wstring& wideObjPath = string::ToWideString(amalgamatedObjPath); coff::ObjFile* objFile = coff::OpenObj(wideObjPath.c_str()); coff::CoffDB* database = coff::GatherDatabase(objFile, compiland->uniqueId); coff::CloseObj(objFile); return GatherResult { fileIndex, database }; }); scheduler::RunTask(task); gatherTasks.emplace_back(task); } } ++fileIndex; } } // wait for all tasks to end scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); // store the databases into the cache { const size_t count = gatherTasks.size(); for (size_t i=0u; i < count; ++i) { const GatherResult& result = gatherTasks[i]->GetResult(); const size_t fileIndex = result.fileIndex; coff::CoffDB* database = result.database; if (database) { const symbols::ObjPath& amalgamatedObjPath = availableModifiedFiles[fileIndex].amalgamatedObjPath; m_coffCache->Update(amalgamatedObjPath, database); } } } // destroy tasks scheduler::DestroyTasks(gatherTasks); scheduler::DestroyTask(taskRoot); } const PerProcessData* processData = m_perProcessData.data(); const size_t processCount = m_perProcessData.size(); // BEGIN EPIC MOD bool enableReinstancingFlow = false; if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; enableReinstancingFlow |= data.liveProcess->IsReinstancingFlowEnabled(); } } // END EPIC MOD // recompile changed files if (m_runMode == RunMode::DEFAULT) { // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Compiling..."); // END EPIC MOD struct LocalCompileResult { size_t fileIndex; double compileTime; CompileResult compileResult; }; ModuleCache* moduleCache = m_moduleCache; double wholeCompileTime = 0.0; // now figure out which files can be compiled in parallel. // first, all PCHs (if any) have to be rebuilt. { telemetry::Scope compilingPCHs("Compiling PCHs"); unsigned int failedCompiles = 0u; auto taskRoot = scheduler::CreateEmptyTask(); types::vector*> compileTasks; compileTasks.reserve(m_modifiedFiles.size()); const size_t count = availableModifiedFiles.size(); for (size_t i = 0u; i < count; ++i) { const symbols::ObjPath& objPath = availableModifiedFiles[i].objPath; symbols::Compiland* compiland = availableModifiedFiles[i].compiland; if (compilerOptions::CreatesPrecompiledHeader(compiland->commandLine.c_str())) { auto task = scheduler::CreateTask(taskRoot, [i, moduleCache, objPath, compiland, processData, processCount, updateType]() { telemetry::Scope compileScope("Compile"); const CompileResult& result = Compile(moduleCache, objPath, compiland, processData, processCount, 0u, updateType); return LocalCompileResult{ i, compileScope.ReadSeconds(), result }; }); scheduler::RunTask(task); availableModifiedFiles[i].compiledOnce = true; compileTasks.emplace_back(task); } } // wait for all tasks to end scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); // if any of the PCHs failed to compile, we need to bail out and cannot compile other files const size_t taskCount = compileTasks.size(); for (size_t i = 0u; i < taskCount; ++i) { const LocalCompileResult& result = compileTasks[i]->GetResult(); const size_t fileIndex = result.fileIndex; const symbols::ObjPath& objPath = availableModifiedFiles[fileIndex].objPath; symbols::Compiland* compiland = availableModifiedFiles[fileIndex].compiland; const CompileResult& compileResult = result.compileResult; const double compileTime = result.compileTime; OnCompiledFile(objPath, compiland, compileResult, compileTime, forceAmalgamationPartsLinkage); if (compileResult.exitCode != 0u) { ++failedCompiles; } } scheduler::DestroyTasks(compileTasks); scheduler::DestroyTask(taskRoot); // at least one of the files could not be compiled if (failedCompiles != 0u) { // note that the array of compilands compiled so far is not cleared - we need them for the next successful // run in order to link them. LC_ERROR_USER("Compilation failed, %u PCH(s) could not be compiled (%.3fs)", failedCompiles, compilingPCHs.ReadSeconds()); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::COMPILE_ERROR; } wholeCompileTime += compilingPCHs.ReadSeconds(); } // second, all files that use /Z7 can be compiled in parallel, because the compiler does not write to any PDB file, // only to individual object files. { telemetry::Scope compilingZ7s("Compiling files using /Z7"); unsigned int failedCompiles = 0u; auto taskRoot = scheduler::CreateEmptyTask(); types::vector*> compileTasks; compileTasks.reserve(m_modifiedFiles.size()); const size_t count = availableModifiedFiles.size(); for (size_t i = 0u; i < count; ++i) { if (availableModifiedFiles[i].compiledOnce) { continue; } const symbols::ObjPath& objPath = availableModifiedFiles[i].objPath; symbols::Compiland* compiland = availableModifiedFiles[i].compiland; if (compilerOptions::UsesC7DebugFormat(compiland->commandLine.c_str())) { auto task = scheduler::CreateTask(taskRoot, [i, moduleCache, objPath, compiland, processData, processCount, updateType]() { telemetry::Scope compileScope("Compile"); const CompileResult& result = Compile(moduleCache, objPath, compiland, processData, processCount, 0u, updateType); return LocalCompileResult{ i, compileScope.ReadSeconds(), result }; }); scheduler::RunTask(task); availableModifiedFiles[i].compiledOnce = true; compileTasks.emplace_back(task); } } // wait for all tasks to end scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); // bail out if any of the files failed to compile const size_t taskCount = compileTasks.size(); for (size_t i = 0u; i < taskCount; ++i) { const LocalCompileResult& result = compileTasks[i]->GetResult(); const size_t fileIndex = result.fileIndex; const symbols::ObjPath& objPath = availableModifiedFiles[fileIndex].objPath; symbols::Compiland* compiland = availableModifiedFiles[fileIndex].compiland; const CompileResult& compileResult = result.compileResult; const double compileTime = result.compileTime; OnCompiledFile(objPath, compiland, compileResult, compileTime, forceAmalgamationPartsLinkage); if (compileResult.exitCode != 0u) { ++failedCompiles; } } scheduler::DestroyTasks(compileTasks); scheduler::DestroyTask(taskRoot); // at least one of the files could not be compiled if (failedCompiles != 0u) { // note that the array of compilands compiled so far is not cleared - we need them for the next successful // run in order to link them. LC_ERROR_USER("Compilation failed, %u file(s) could not be compiled (%.3fs)", failedCompiles, compilingZ7s.ReadSeconds()); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::COMPILE_ERROR; } wholeCompileTime += compilingZ7s.ReadSeconds(); } // third, all files that use either /Zi or /ZI need special treatment, because the compiler writes to a PDB file, and // accesses to that file need to be serialized by using the /FS option. // furthermore, all files that have /Gm (Enable Minimal Rebuild) set cannot be compiled in parallel at all. { telemetry::Scope compilingZis("Compiling files using /Zi"); unsigned int failedCompiles = 0u; auto taskRoot = scheduler::CreateEmptyTask(); types::vector*> compileTasks; compileTasks.reserve(m_modifiedFiles.size()); types::StringMap> filesPerPdb; filesPerPdb.reserve(m_modifiedFiles.size()); const size_t count = availableModifiedFiles.size(); for (size_t i = 0u; i < count; ++i) { if (availableModifiedFiles[i].compiledOnce) { continue; } availableModifiedFiles[i].compiledOnce = true; const symbols::ObjPath& objPath = availableModifiedFiles[i].objPath; symbols::Compiland* compiland = availableModifiedFiles[i].compiland; if (compilerOptions::UsesMinimalRebuild(compiland->commandLine.c_str())) { // this file cannot be compiled in parallel, tell the user LC_WARNING_USER("Compiland %s uses compiler option \"Enable Minimal Rebuild (/Gm)\" and cannot be compiled concurrently. It is generally recommended to disable this compiler option.", objPath.c_str()); telemetry::Scope compileScope("Compile"); const CompileResult& result = Compile(moduleCache, objPath, compiland, processData, processCount, 0u, updateType); OnCompiledFile(objPath, compiland, result, compileScope.ReadSeconds(), forceAmalgamationPartsLinkage); if (result.exitCode != 0u) { ++failedCompiles; } } else { // this file uses /Zi and writes to a PDB file. store it into a map indexed by the PDB file. // files that write to the same PDB upon compilation need to be serialized using the /FS option. filesPerPdb[compiland->pdbPath].push_back(i); } } for (auto pdbIt = filesPerPdb.begin(); pdbIt != filesPerPdb.end(); ++pdbIt) { const types::vector& indices = pdbIt->second; const size_t indexCount = indices.size(); if (indexCount == 1u) { const size_t fileIndex = indices[0]; const symbols::ObjPath& objPath = availableModifiedFiles[fileIndex].objPath; symbols::Compiland* compiland = availableModifiedFiles[fileIndex].compiland; // this PDB file is being written to by one compiland only, we can compile that without any extra options auto task = scheduler::CreateTask(taskRoot, [fileIndex, moduleCache, objPath, compiland, processData, processCount, updateType]() { telemetry::Scope compileScope("Compile"); const CompileResult& result = Compile(moduleCache, objPath, compiland, processData, processCount, 0u, updateType); return LocalCompileResult{ fileIndex, compileScope.ReadSeconds(), result }; }); scheduler::RunTask(task); compileTasks.emplace_back(task); } else { // the corresponding PDB file is being written to by several compilands, serialize access using the /FS option for (size_t i = 0u; i < indexCount; ++i) { const size_t fileIndex = indices[i]; const symbols::ObjPath& objPath = availableModifiedFiles[fileIndex].objPath; symbols::Compiland* compiland = availableModifiedFiles[fileIndex].compiland; auto task = scheduler::CreateTask(taskRoot, [fileIndex, moduleCache, objPath, compiland, processData, processCount, updateType]() { telemetry::Scope compileScope("Compile"); const CompileResult& result = Compile(moduleCache, objPath, compiland, processData, processCount, CompileFlags::SERIALIZE_PDB_ACCESS, updateType); return LocalCompileResult{ fileIndex, compileScope.ReadSeconds(), result }; }); scheduler::RunTask(task); compileTasks.emplace_back(task); } } } // wait for all tasks to end scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); // bail out if any of the files failed to compile const size_t taskCount = compileTasks.size(); for (size_t i = 0u; i < taskCount; ++i) { const LocalCompileResult& result = compileTasks[i]->GetResult(); const size_t fileIndex = result.fileIndex; const symbols::ObjPath& objPath = availableModifiedFiles[fileIndex].objPath; symbols::Compiland* compiland = availableModifiedFiles[fileIndex].compiland; const CompileResult& compileResult = result.compileResult; const double compileTime = result.compileTime; OnCompiledFile(objPath, compiland, compileResult, compileTime, forceAmalgamationPartsLinkage); if (compileResult.exitCode != 0u) { ++failedCompiles; } } scheduler::DestroyTasks(compileTasks); scheduler::DestroyTask(taskRoot); // at least one of the files could not be compiled if (failedCompiles != 0u) { // note that the array of compilands compiled so far is not cleared - we need them for the next successful // run in order to link them. LC_ERROR_USER("Compilation failed, %u file(s) could not be compiled (%.3fs)", failedCompiles, compilingZis.ReadSeconds()); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::COMPILE_ERROR; } wholeCompileTime += compilingZis.ReadSeconds(); } LC_SUCCESS_USER("Successfully compiled modified files (%.3fs)", wholeCompileTime); } else if (m_runMode == RunMode::EXTERNAL_BUILD_SYSTEM) { if (modifiedOrNewObjFiles.size() == 0u) { // files were compiled by an external build system, we just have to mark them appropriately const size_t count = availableModifiedFiles.size(); for (size_t i = 0u; i < count; ++i) { const symbols::ObjPath& objPath = availableModifiedFiles[i].objPath; symbols::Compiland* compiland = availableModifiedFiles[i].compiland; m_compiledCompilands.emplace(objPath, compiland); symbols::MarkCompilandAsRecompiled(compiland); } } else { // files were compiled by an external build system and handed to us. // there could also be new files. const size_t count = modifiedOrNewObjFiles.size(); for (size_t i = 0u; i < count; ++i) { const std::wstring& wideObjPath = modifiedOrNewObjFiles[i].objPath; const symbols::ObjPath& objPath = string::ToUtf8String(wideObjPath); symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); // compiland will be nullptr for new files, this is OK m_compiledCompilands.emplace(objPath, compiland); } } m_modifiedFiles.clear(); } // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Updating compilands..."); // END EPIC MOD // we want to link a minimal .DLL file that contains all modified .OBJ files and only those required for resolving symbols. // because we require users to use /OPT:NOREF and /OPT:NOICF, finding the set of files that need to be linked in is // easy. // primarily, this set consists of all files that have been modified, and precompiled header files which do not belong // to a library - those are needed to have precompiled debug information available. // secondarily, most of the modified files will have unresolved symbols that would need to pull in other files. // due to /OPT:NOREF though, all symbols (both data & code) which are part of any of the main .obj linked into the // .exe will be available. those symbols that aren't must be part of a library then, which will be linked in anyway. typedef std::pair CompilandInfo; // stores from which .OBJ an external symbol originated types::StringMap externalSymbols; externalSymbols.reserve(16384u); // stores which compilands need to be linked in types::StringSet neededCompilands; neededCompilands.reserve(m_compilandDB->compilands.size()); { telemetry::Scope gatherNeededCompilandsScope("Gather needed compilands"); LC_LOG_DEV("Finding set of .obj files"); LC_LOG_INDENT_DEV; auto UpdateExternalSymbolsAndNeededFiles = [&] ( const symbols::ObjPath& objPath, const symbols::Compiland* compiland, uint32_t compilandUniqueId, const types::StringMap& pchSymbolToCompilandName, types::StringMap& externalSymbols, types::StringSet& neededCompilands ) { coff::ObjFile* coffFile = coff::OpenObj(string::ToWideString(objPath).c_str()); if (coffFile && coffFile->memoryFile) { coff::ExternalSymbolDB* externalSymbolDb = coff::GatherExternalSymbolDatabase(coffFile, compilandUniqueId); types::vector linkerDirectives = coff::ExtractLinkerDirectives(coffFile); coff::CloseObj(coffFile); if (externalSymbolDb) { LC_LOG_DEV("Updated external symbols for compiland %s", objPath.c_str()); const size_t symbolCount = externalSymbolDb->symbols.size(); for (size_t i = 0u; i < symbolCount; ++i) { const ImmutableString& symbolName = externalSymbolDb->symbols[i]; externalSymbols.emplace(symbolName, CompilandInfo { objPath, compiland }); } coff::DestroyDatabase(externalSymbolDb); } else { LC_ERROR_DEV("External symbol database for COFF %s is invalid", objPath.c_str()); } // we need to pull in any precompiled headers that might be used by this compiland. // check the linker includes if they want to force-link any precompiled header symbol. for (size_t i = 0u; i < linkerDirectives.size(); ++i) { const std::string& directive = linkerDirectives[i]; // note that directives appear in both lower- and upper-case, so convert to upper-case first const std::string& upperCaseDirective = string::ToUpper(directive); if (string::Contains(upperCaseDirective.c_str(), "INCLUDE:")) { const std::size_t colonPos = directive.find(':'); const std::string symbolName(directive.c_str() + colonPos + 1u, directive.c_str() + directive.length()); // is this a symbol emitted by a precompiled header? const auto compilandIt = pchSymbolToCompilandName.find(ImmutableString(symbolName.c_str())); if (compilandIt != pchSymbolToCompilandName.end()) { // yes, so pull in this compiland as well const symbols::ObjPath& pchObjPath = compilandIt->second; LC_LOG_DEV("%s requires precompiled header %s", objPath.c_str(), pchObjPath.c_str()); neededCompilands.emplace(pchObjPath); } } } } }; if (modifiedOrNewObjFiles.size() == 0u) { // we haven't been given any modified or new files, so check which compilands were recompiled and work from there for (auto it = m_compilandDB->compilands.begin(); it != m_compilandDB->compilands.end(); ++it) { const symbols::ObjPath& objPath = it->first; const symbols::Compiland* compiland = it->second; if (IsCompilandRecompiled(compiland)) { // this file was changed/recompiled, so the new .OBJ needs to be linked in, even // though the file might be contained in a library. // we need to gather the external symbols again and cannot take the ones stored in the cache. LC_LOG_DEV("%s is recompiled", objPath.c_str()); neededCompilands.emplace(objPath); UpdateExternalSymbolsAndNeededFiles(objPath, compiland, compiland->uniqueId, m_pchSymbolToCompilandName, externalSymbols, neededCompilands); } else { // this file has not changed, so consult the cache for external symbols auto cacheIt = m_externalSymbolsPerCompilandCache.find(objPath); if (cacheIt != m_externalSymbolsPerCompilandCache.end()) { const size_t symbolCount = cacheIt->second.size(); for (size_t i = 0u; i < symbolCount; ++i) { const ImmutableString& symbolName = cacheIt->second[i]->name; externalSymbols.emplace(symbolName, CompilandInfo { objPath, compiland }); } } else { // this compiland does not store any external symbol } } } } else { for (auto it = modifiedOrNewObjFiles.begin(); it != modifiedOrNewObjFiles.end(); ++it) { const symbols::ObjPath objPath(string::ToUtf8String(it->objPath)); const symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); // new compilands won't be found in the database, so there's no unique ID yet that we can use const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, it->objPath.c_str(), modifiedOrNewObjFiles); // this file was either modified or is new. in any case, the new .OBJ needs to be linked in, even // though the file might be contained in a library. // we need to gather the external symbols again and cannot take the ones stored in the cache. LC_LOG_DEV("%s %s", objPath.c_str(), compiland ? "was recompiled" : "is new"); neededCompilands.emplace(objPath); UpdateExternalSymbolsAndNeededFiles(objPath, compiland, compilandUniqueId, m_pchSymbolToCompilandName, externalSymbols, neededCompilands); } } } // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Reconstructing symbols..."); // END EPIC MOD // we now have a list of all .obj files that are going to be part of the next patch. // reconstruct symbols lazily for those object files that have not been reconstructed yet from the initial main executable. { telemetry::Scope reconstructingSymbolsFromObjScope("Reconstructing symbols"); LC_LOG_DEV("Reconstructing symbols from OBJ"); LC_LOG_INDENT_DEV; // find out which .obj files haven't been reconstructed yet types::vector objToReconstruct; objToReconstruct.reserve(neededCompilands.size()); for (auto it = neededCompilands.begin(); it != neededCompilands.end(); ++it) { const symbols::ObjPath& objPath = *it; if (m_reconstructedCompilands.find(objPath) == m_reconstructedCompilands.end()) { // AMALGAMATION if (appSettings::g_amalgamationSplitIntoSingleParts->GetValue()) { // make sure that existing amalgamated .objs (if any) are reconstructed first const symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); if (compiland && symbols::IsPartOfAmalgamation(compiland)) { if (m_reconstructedCompilands.find(compiland->amalgamationPath) == m_reconstructedCompilands.end()) { // no entry yet for the amalgamation, must be reconstructed LC_LOG_DEV("Amalgamated file %s not in cache yet", compiland->amalgamationPath.c_str()); objToReconstruct.emplace_back(compiland->amalgamationPath); m_reconstructedCompilands.insert(compiland->amalgamationPath); } } } // no entry yet, must be reconstructed LC_LOG_DEV("%s not in cache yet", objPath.c_str()); objToReconstruct.emplace_back(objPath); m_reconstructedCompilands.insert(objPath); } } const size_t count = objToReconstruct.size(); if (count > 0u) { executable::Image* image = executable::OpenImage(m_moduleName.c_str(), Filesystem::OpenMode::READ); executable::ImageSectionDB* imageSections = executable::GatherImageSectionDB(image); // load and cache all .obj not in the cache yet concurrently { auto taskRoot = scheduler::CreateEmptyTask(); types::vector tasks; tasks.reserve(count); for (size_t i = 0u; i < count; ++i) { symbols::ObjPath objPath = objToReconstruct[i]; if (!m_coffCache->Lookup(objPath)) { // there is no entry yet for this COFF in the cache. // this means that this .obj was not recompiled (otherwise it would have an entry already), // but has been pulled in for the first time due to unresolved symbols. auto task = scheduler::CreateTask(taskRoot, [this, objPath, &modifiedOrNewObjFiles]() { const symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); const std::wstring& wideObjPath = string::ToWideString(objPath); const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); LC_LOG_DEV("Need %s for the first time, updating COFF cache", objPath.c_str()); coff::ObjFile* objFile = coff::OpenObj(wideObjPath.c_str()); if (objFile && objFile->memoryFile) { // note that even though we might be dealing with a single-part .obj of an amalgamated .obj // here, the symbols will be disambiguated using the same uniqueId as the original amalgamated file. coff::CoffDB* database = coff::GatherDatabase(objFile, compilandUniqueId); if (database) { m_coffCache->Update(objPath, database); } coff::CloseObj(objFile); } return true; }); scheduler::RunTask(task); tasks.emplace_back(task); } } // wait for all tasks to end scheduler::RunTask(taskRoot); scheduler::WaitForTask(taskRoot); // destroy tasks scheduler::DestroyTasks(tasks); scheduler::DestroyTask(taskRoot); } types::StringSet noSymbolsToIgnore; // with the COFF cache filled, gather the dynamic initializers and remaining symbols by walking the module symbols::Provider* provider = symbols::OpenEXE(m_moduleName.c_str(), symbols::OpenOptions::NONE); { symbols::GatherDynamicInitializers(provider, image, imageSections, m_imageSectionDB, m_contributionDB, m_compilandDB, m_coffCache, m_symbolDB); for (size_t i = 0u; i < count; ++i) { const symbols::ObjPath& objPath = objToReconstruct[i]; const coff::CoffDB* database = m_coffCache->Lookup(objPath); if (!database) { LC_ERROR_USER("COFF database for compiland %s is invalid (lazy reconstruct)", objPath.c_str()); continue; } symbols::ReconstructFromExecutableCoff(provider, image, imageSections, database, noSymbolsToIgnore, objPath, m_contributionDB, m_thunkDB, m_imageSectionDB, m_symbolDB); } } symbols::Close(provider); executable::DestroyImageSectionDB(imageSections); executable::CloseImage(image); } } // now that everything is loaded and reconstructed, transform the symbols containing anonymous namespace names symbols::TransformAnonymousNamespaceSymbols(m_symbolDB, m_contributionDB, m_compilandDB, modifiedOrNewObjFiles); // update the COFF cache for all compiled files UpdateCoffCache(m_compiledCompilands, m_coffCache, CacheUpdate::ALL, modifiedOrNewObjFiles); // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Stripping COFFs..."); // END EPIC MOD // strip symbols which are already part of any of the modules typedef types::StringSet StrippedSymbols; types::StringMap strippedSymbolsPerCompiland; strippedSymbolsPerCompiland.reserve(neededCompilands.size()); types::StringMap forceStrippedSymbolsPerCompiland; forceStrippedSymbolsPerCompiland.reserve(neededCompilands.size()); { telemetry::Scope strippingScope("Stripping COFFs"); LC_LOG_DEV("Stripping .OBJ files"); LC_LOG_INDENT_DEV; // decide symbol removal strategy once, based on the type of linker we have const coff::SymbolRemovalStrategy::Enum removalStrategy = DetermineSymbolRemovalStrategy(m_linkerDB); types::StringMap rawCoffDb; // first pass, read raw COFFs for needed compilands for (auto compilandIt = neededCompilands.begin(); compilandIt != neededCompilands.end(); ++compilandIt) { const symbols::ObjPath& objPath = *compilandIt; symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); const std::wstring wideObjPath = string::ToWideString(objPath); const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); coff::ObjFile* objFile = coff::OpenObj(wideObjPath.c_str()); if (objFile && objFile->memoryFile) { coff::RawCoff* rawCoff = coff::ReadRaw(objFile, compilandUniqueId); coff::CloseObj(objFile); if (rawCoff) { rawCoffDb.emplace(objPath, rawCoff); } } } // a simple cache that stores the symbol and relocation per destination symbol. // i.e. the cache is indexed by the destination symbol of a relocation, and stores all symbols and relocations // that relocate to that destination symbol. typedef types::vector> RelocationsPerDestinationSymbolCache; types::StringMap relocationsCachePerCompiland; // second pass, strip symbols for each raw COFF for (auto coffIt = rawCoffDb.begin(); coffIt != rawCoffDb.end(); ++coffIt) { const symbols::ObjPath& objPath = coffIt->first; const std::wstring wideObjPath = string::ToWideString(objPath); coff::RawCoff* rawCoff = coffIt->second; LC_LOG_DEV("Stripping file %s", objPath.c_str()); // before stripping the file, move the original one to a backup location. // we need it after linking has finished { const std::wstring bakPath = wideObjPath + L".bak"; Filesystem::Move(wideObjPath.c_str(), bakPath.c_str()); } // remove linker directives which we don't want or need. // *) /EDITANDCONTINUE will cause a warning in combination with OPT:REF and OPT:ICF, which we use. // *) /EXPORT will cause a .lib and .exp to be written for files which originally // are part of a DLL and export at least one symbol. we don't need those files. // *) /INCLUDE can cause symbols we already have to be pulled in again from .lib files. // this leads to code and data duplication, so it must be removed for symbols which are // already known to us. // *) /ENTRY sets a different entry point in the patch DLL, which would cause all kinds of problems. { types::vector linkerDirectives = coff::ExtractLinkerDirectives(rawCoff); for (auto it = linkerDirectives.begin(); it != linkerDirectives.end(); /*nothing*/) { const std::string& directive = *it; // note that directives appear in both lower- and upper-case, so convert to upper-case first const std::string& upperCaseDirective = string::ToUpper(directive); if (string::Contains(upperCaseDirective.c_str(), "EDITANDCONTINUE")) { it = linkerDirectives.erase(it); continue; } else if (string::Contains(upperCaseDirective.c_str(), "EXPORT:")) { it = linkerDirectives.erase(it); continue; } else if (string::Contains(upperCaseDirective.c_str(), "INCLUDE:")) { const std::size_t colonPos = directive.find(':'); // BEGIN EPIC MOD - This fix is already in Live++ 2 std::string symbolName(directive.c_str() + colonPos + 1u, directive.c_str() + directive.length()); if (symbolName.length() >= 2 && symbolName.front() == '\"' && symbolName.back() == '\"') { symbolName = symbolName.substr(1, symbolName.length() - 2); } // END EPIC MOD const ModuleCache::FindSymbolData findData = m_moduleCache->FindSymbolByName(ModuleCache::SEARCH_ALL_MODULES, ImmutableString(symbolName.c_str())); if (findData.symbol) { LC_LOG_DEV("Removing linker /INCLUDE directive to symbol %s", symbolName.c_str()); it = linkerDirectives.erase(it); continue; } } else if (string::Contains(upperCaseDirective.c_str(), "ENTRY:")) { it = linkerDirectives.erase(it); continue; } ++it; } coff::ReplaceLinkerDirectives(rawCoff, linkerDirectives); } // fill relocations cache const size_t symbolCount = coff::GetSymbolCount(rawCoff); RelocationsPerDestinationSymbolCache relocationsPerDstSymbol; relocationsPerDstSymbol.resize(symbolCount); const coff::CoffDB* coffDb = m_coffCache->Lookup(objPath); if (coffDb) { const size_t count = coffDb->symbols.size(); for (size_t i = 0u; i < count; ++i) { const coff::Symbol* symbol = coffDb->symbols[i]; const size_t relocationCount = symbol->relocations.size(); for (size_t j = 0u; j < relocationCount; ++j) { const coff::Relocation* relocation = symbol->relocations[j]; relocationsPerDstSymbol[relocation->dstSymbolNameIndex].push_back(SymbolAndRelocation { symbol, relocation }); } } } StrippedSymbols& strippedSymbols = strippedSymbolsPerCompiland[objPath]; strippedSymbols.reserve(symbolCount); StrippedSymbols& forceStrippedSymbols = forceStrippedSymbolsPerCompiland[objPath]; forceStrippedSymbols.reserve(symbolCount); for (size_t i = 0u; i < symbolCount; i += coff::GetAuxSymbolCount(rawCoff, i) + 1u) { if (coff::IsAbsoluteSymbol(rawCoff, i)) { continue; } else if (coff::IsDebugSymbol(rawCoff, i)) { continue; } else if (coff::IsSectionSymbol(rawCoff, i)) { continue; } const ImmutableString& symbolName = coff::GetSymbolName(rawCoff, i); if (symbols::IsStringLiteral(symbolName)) { continue; } else if (symbols::IsFloatingPointSseAvxConstant(symbolName)) { continue; } else if (symbols::IsLineNumber(symbolName)) { continue; } if (symbols::IsPchSymbol(symbolName)) { // never strip symbols that force-link the PCH continue; } else if (symbols::IsRttiObjectLocator(symbolName)) { // never strip RTTI object locators, because its relocations are not handled // by our COFF mechanism. continue; } else if (symbols::IsPointerToDynamicInitializer(symbolName)) { // never strip $initializer$ symbols. these are only (very small) function pointers // to dynamic initializers so stripping them doesn't yield much. // additionally - and this is more important! - we need them to be intact so we can // reconstruct symbols from them in case we cannot find certain dynamic initializer symbols. continue; } else if (symbols::IsExceptionRelatedSymbol(symbolName)) { // never strip symbols belonging to any exception mechanism. // in x64, throwing an exception calls _CxxThrowException, which (later on) ends up // relying on __CxxFrameHandler3 - if we strip that function, relocations inside exception // data structures will not be patched properly, and the code will crash with the following // callstack: /* ExeDynamicRuntime.exe!__CxxFrameHandler3() ntdll.dll!RtlpExecuteHandlerForException() ntdll.dll!RtlDispatchException() ntdll.dll!KiUserExceptionDispatch() KernelBase.dll!RaiseException() vcruntime140d.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) */ continue; } // BEGIN EPIC MOD else if (symbols::IsUENoStripSymbol(symbolName)) { continue; } // END EPIC MOD LC_LOG_DEV("Considering symbol %s for stripping", symbolName.c_str()); LC_LOG_INDENT_DEV; bool tryStrip = false; bool doStrip = false; const coff::SymbolType::Enum type = coff::GetSymbolType(rawCoff, i); if (coff::IsUndefinedSymbol(rawCoff, i)) { // this is an undefined symbol to any other translation unit. // if the symbol is not part of any of the .obj we recompiled, but comes from an .obj // that would otherwise be linked in (e.g. the PCH), we strip this symbol and force a relocation // to it later on. because its file wasn't recompiled, it couldn't possible have changed, // therefore it is safe to relocate to it. const auto& symbolIt = externalSymbols.find(symbolName); if (symbolIt != externalSymbols.end()) { const symbols::Compiland* otherCompiland = symbolIt->second.second; if (otherCompiland) { if (symbols::IsCompilandRecompiled(otherCompiland)) { // the external symbol comes from one of the *other* recompiled .obj. // in this case, the symbol might have changed, so we are only allowed to strip it // if all relocations to it would be patched anyway. tryStrip = true; LC_LOG_DEV("Symbol comes from recompiled compiland"); } else { // the external symbol comes from an .obj that was not recompiled. // in this case, the symbol couldn't have changed, so we strip it directly // in case it exists in our live module already. const ModuleCache::FindSymbolData findData = m_moduleCache->FindSymbolByName(ModuleCache::SEARCH_ALL_MODULES, symbolName); if (findData.symbol) { doStrip = true; forceStrippedSymbols.insert(symbolName); } else { LC_LOG_DEV("Symbol seems to be new (compiland)"); } } } else { // the symbol must come from a new .obj, so we aren't allowed to strip it LC_LOG_DEV("Symbol comes from new compiland"); } } else { // the symbol doesn't come from any of the translation units, so it must be a new // symbol or one coming from a library. if it exists already, it cannot have changed, // so we strip it directly. const ModuleCache::FindSymbolData findData = m_moduleCache->FindSymbolByName(ModuleCache::SEARCH_ALL_MODULES, symbolName); if (findData.symbol) { doStrip = true; forceStrippedSymbols.insert(symbolName); } else { LC_LOG_DEV("Symbol seems to be new (library)"); } } } else { // this is a symbol defined in this translation unit. // data symbols can be stripped if they already exist and we would relocate to it anyway, // functions are always kept. if ((type == coff::SymbolType::EXTERNAL_DATA) || (type == coff::SymbolType::STATIC_DATA)) { // don't try stripping MSVC JustMyCode symbols const uint32_t symbolSectionIndex = coff::GetSymbolSectionIndex(rawCoff, i); const ImmutableString& sectionName = coff::GetSectionName(rawCoff, symbolSectionIndex); if (coff::IsMSVCJustMyCodeSection(sectionName.c_str())) { LC_LOG_DEV("Symbol is an MSVC JustMyCode symbol"); } else { tryStrip = true; } } else { LC_LOG_DEV("Symbol is a function defined in this compiland"); } } if (tryStrip) { LC_LOG_DEV("Trying to strip symbol %s", symbolName.c_str()); // if this symbol already exists and we would relocate to it, then strip it from the OBJ const symbols::Symbol* strippedSymbol = FindOriginalSymbolForStrippedCandidate(m_moduleCache, symbolName, coffDb, relocationsPerDstSymbol[i]); if (strippedSymbol) { doStrip = true; } } if (doStrip) { coff::RemoveSymbol(rawCoff, i, removalStrategy); strippedSymbols.insert(symbolName); // we deliberately do not remove the relocations to this symbol, otherwise the debug // information is incorrect, and the patch PDB will contain wrong addresses, which would // ultimately lead to us patching relocations and functions with a wrong address. } } relocationsCachePerCompiland.emplace(objPath, relocationsPerDstSymbol); } // third pass, make sure that symbols that have been stripped in one COFF are stripped in all COFFs where they are undefined. // otherwise, we would run into linker errors due to unresolved symbols. // this only needs to be done if there is more than one needed compiland. if (neededCompilands.size() > 1u) { LC_LOG_DEV("Performing global COFF stripping"); LC_LOG_INDENT_DEV; // merge all stripped symbols into one set StrippedSymbols allStrippedSymbols; StrippedSymbols allForceStrippedSymbols; for (auto symbolsIt = strippedSymbolsPerCompiland.begin(); symbolsIt != strippedSymbolsPerCompiland.end(); ++symbolsIt) { const StrippedSymbols& strippedSymbols = symbolsIt->second; allStrippedSymbols.insert(strippedSymbols.begin(), strippedSymbols.end()); } for (auto symbolsIt = forceStrippedSymbolsPerCompiland.begin(); symbolsIt != forceStrippedSymbolsPerCompiland.end(); ++symbolsIt) { const StrippedSymbols& strippedSymbols = symbolsIt->second; allForceStrippedSymbols.insert(strippedSymbols.begin(), strippedSymbols.end()); } // walk all COFFs and strip all symbols that were stripped in other COFFs for (auto coffIt = rawCoffDb.begin(); coffIt != rawCoffDb.end(); ++coffIt) { const symbols::ObjPath& objPath = coffIt->first; LC_LOG_DEV("Compiland %s", objPath.c_str()); LC_LOG_INDENT_DEV; StrippedSymbols& strippedSymbols = strippedSymbolsPerCompiland[objPath]; StrippedSymbols& forceStrippedSymbols = forceStrippedSymbolsPerCompiland[objPath]; coff::RawCoff* rawCoff = coffIt->second; const size_t symbolCount = coff::GetSymbolCount(rawCoff); for (size_t i = 0u; i < symbolCount; i += coff::GetAuxSymbolCount(rawCoff, i) + 1u) { if (coff::IsAbsoluteSymbol(rawCoff, i)) { continue; } else if (coff::IsDebugSymbol(rawCoff, i)) { continue; } else if (coff::IsSectionSymbol(rawCoff, i)) { continue; } else if (coff::IsRemovedSymbol(rawCoff, i, removalStrategy)) { // this symbol has been removed already continue; } else if (!coff::IsUndefinedSymbol(rawCoff, i)) { // we are only allowed to consider undefined symbols continue; } const ImmutableString& symbolName = coff::GetSymbolName(rawCoff, i); { const auto findIt = allStrippedSymbols.find(symbolName); const auto forceFindIt = allForceStrippedSymbols.find(symbolName); if ((findIt != allStrippedSymbols.end()) || (forceFindIt != allForceStrippedSymbols.end())) { // this is an undefined symbol that needs to be stripped. // because it's undefined, we need to make sure that *all* relocations to it are always patched, // hence we mark the symbol as force stripped. LC_LOG_DEV("Stripping symbol %s", symbolName.c_str()); coff::RemoveSymbol(rawCoff, i, removalStrategy); strippedSymbols.insert(symbolName); forceStrippedSymbols.insert(symbolName); } } } } } // last pass, strip all sections that no longer contain symbols for (auto coffIt = rawCoffDb.begin(); coffIt != rawCoffDb.end(); ++coffIt) { const symbols::ObjPath& objPath = coffIt->first; const std::wstring wideObjPath = string::ToWideString(objPath); coff::RawCoff* rawCoff = coffIt->second; const size_t symbolCount = coff::GetSymbolCount(rawCoff); const coff::CoffDB* coffDb = m_coffCache->Lookup(objPath); const RelocationsPerDestinationSymbolCache& relocationsPerDstSymbol = relocationsCachePerCompiland[objPath]; StrippedSymbols& strippedSymbols = strippedSymbolsPerCompiland[objPath]; // now that we removed symbols (and corresponding relocations), strip all sections that no longer // store any meaningful information. types::unordered_set sectionsWithMeaningfulSymbols; for (size_t i = 0u; i < symbolCount; i += coff::GetAuxSymbolCount(rawCoff, i) + 1u) { if (coff::IsAbsoluteSymbol(rawCoff, i)) { continue; } else if (coff::IsDebugSymbol(rawCoff, i)) { continue; } else if (coff::IsUndefinedSymbol(rawCoff, i)) { continue; } else if (coff::IsSectionSymbol(rawCoff, i)) { continue; } else if (coff::IsRemovedSymbol(rawCoff, i, removalStrategy)) { continue; } // if this symbol is not one we deleted, this section stores at least one meaningful symbol const uint32_t symbolSectionIndex = coff::GetSymbolSectionIndex(rawCoff, i); sectionsWithMeaningfulSymbols.insert(symbolSectionIndex); } // COFF compiled with Clang often have sections that don't contain any symbol in the symbol table, but relocations that refer to these sections. // we must make sure that those sections are not stripped in case any non-stripped symbol still refers to them. // TODO: we should not be doing this for each compiland, this is overhead just for Clang. const symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); // in external build system mode, we don't have a compiland for completely new files. // in this case, we just assume that the file was built with MSVC. compiler::CompilerType::Enum compilerType = compiler::CompilerType::CL; if (compiland) { const std::wstring compilerPath = GetCompilerPath(compiland); compilerType = compiler::DetermineCompilerType(compilerPath.c_str()); if (compilerType == compiler::CompilerType::CLANG) { usesClang = true; for (size_t i = 0u; i < relocationsPerDstSymbol.size(); ++i) { const auto& relocationsForSymbol = relocationsPerDstSymbol[i]; if (relocationsForSymbol.size() == 0u) { continue; } const size_t relocationCount = relocationsForSymbol.size(); for (size_t j = 0u; j < relocationCount; ++j) { const coff::Relocation* relocation = relocationsForSymbol[j].relocation; if (relocation->dstIsSection) { // this relocation refers to a section. // if the source symbol was not stripped by us, then this section still contains meaningful data and must be kept. const coff::Symbol* symbol = relocationsForSymbol[j].symbol; const ImmutableString& srcSymbolName = coff::GetSymbolName(coffDb, symbol); // TODO: can this be made better? if (strippedSymbolsPerCompiland.find(srcSymbolName) == strippedSymbolsPerCompiland.end()) { // the source symbol was not stripped, so we must keep this section if (relocation->dstSectionIndex >= 0) { sectionsWithMeaningfulSymbols.insert(static_cast(relocation->dstSectionIndex)); } // all relocations refer to the same destination symbol, so we can stop checking more source symbols break; } } } } } } const size_t sectionCount = coff::GetSectionCount(rawCoff); for (size_t i = 0u; i < sectionCount; ++i) { const IMAGE_SECTION_HEADER* header = &rawCoff->sections[i].header; if (coffDetail::IsDirectiveSection(header)) { continue; } else if (coffDetail::IsDiscardableSection(header)) { // usually, having discardable COMDAT sections is not a problem - this is what .debug$S sections are. // however, discardable COMDAT sections which are marked 'pick any' by using __declspec(selectany) // must hold at least one symbol, otherwise they must be removed. // if they are not removed, the linker will complain with: // LNK1143: invalid or corrupt file: no symbol for COMDAT section 0x4 if (!coff::IsSelectAnyComdatSection(rawCoff, i)) { // probably a debug section. we are only allowed to remove these via their corresponding COMDAT section. continue; } } else if (!coffDetail::IsPartOfImage(header)) { // probably a debug section. we are only allowed to remove these via their corresponding COMDAT section continue; } // don't strip sections implicitly needed by the linker. // on Clang, they don't contain any symbols, but relocations to meaningful symbols. if (compilerType == compiler::CompilerType::CLANG) { const ImmutableString& sectionName = coff::GetSectionName(rawCoff, i); // dynamic initializers if (string::StartsWith(sectionName.c_str(), ".CRT$")) { continue; } // exception data else if (string::StartsWith(sectionName.c_str(), ".xdata")) { continue; } else if (string::StartsWith(sectionName.c_str(), ".pdata")) { continue; } } if (sectionsWithMeaningfulSymbols.find(i) == sectionsWithMeaningfulSymbols.end()) { // this section has no more meaningful symbols, remove it coff::RemoveSection(rawCoff, i); // also remove all COMDAT sections that can only be linked in case this section exists coff::RemoveAssociatedComdatSections(rawCoff, i); } } // walk over the symbols one last time, and remove the ones that now live in a section that has been // removed in the last step due to removing associated COMDAT sections. for (size_t i = 0u; i < symbolCount; i += coff::GetAuxSymbolCount(rawCoff, i) + 1u) { if (coff::IsAbsoluteSymbol(rawCoff, i)) { continue; } else if (coff::IsDebugSymbol(rawCoff, i)) { continue; } else if (coff::IsUndefinedSymbol(rawCoff, i)) { continue; } else if (coff::IsSectionSymbol(rawCoff, i)) { continue; } else if (coff::IsRemovedSymbol(rawCoff, i, removalStrategy)) { continue; } const uint32_t symbolSectionIndex = coff::GetSymbolSectionIndex(rawCoff, i); const coff::RawSection& section = rawCoff->sections[symbolSectionIndex]; if (section.wasRemoved) { const ImmutableString& symbolName = coff::GetSymbolName(rawCoff, i); const symbols::Symbol* strippedSymbol = FindOriginalSymbolForStrippedCandidate(m_moduleCache, symbolName, coffDb, relocationsPerDstSymbol[i]); if (strippedSymbol) { coff::RemoveSymbol(rawCoff, i, removalStrategy); coff::RemoveRelocations(rawCoff, i); strippedSymbols.insert(symbolName); } } // Clang workaround: in certain cases, we don't find the _GLOBAL__sub_I_ dynamic initializer symbol, which can lead to // a crash in user code, unfortunately. // as a workaround, we patch the code of the function itself to return immediately, so that even when it is called, it doesn't do anything. if (usesClang) { const ImmutableString& symbolName = coff::GetSymbolName(rawCoff, i); const bool isClangDynamicInitializer = (string::StartsWith(symbolName.c_str(), "_GLOBAL__sub_I_")); if (isClangDynamicInitializer) { coff::PatchFunctionSymbol(rawCoff, i, symbolSectionIndex); } } } coff::WriteRaw(wideObjPath.c_str(), rawCoff, removalStrategy); coff::DestroyRaw(rawCoff); } } // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Generating linker command line..."); // END EPIC MOD telemetry::Scope generateLinkerCommandLine("Generate linker command line"); // link all .obj files into a single executable. the linker command-line options potentially get very long, // reserve enough space. std::wstring linkerOptions; linkerOptions.reserve(4u * 1024u * 1024u); // UTF-16 response files must include a byte-order mark const wchar_t BOM_0xFFFE = 65279u; // ends up as FF FE in the file linkerOptions.push_back(BOM_0xFFFE); // add custom linker options linkerOptions += appSettings::g_linkerOptions->GetValue(); linkerOptions += L" "; linkerOptions += COMMON_LINKER_OPTIONS; // compilation of all files succeeded. grab their external symbols database and update the cache entry. // additionally build a list of all external functions to be included by the linker. LC_LOG_DEV("Gathering external symbols"); for (auto compilandIt = m_compiledCompilands.begin(); compilandIt != m_compiledCompilands.end(); ++compilandIt) { const symbols::ObjPath& objPath = compilandIt->first; const std::wstring& wideObjPath = string::ToWideString(objPath); const symbols::Compiland* compiland = compilandIt->second; const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); coff::ObjFile* coffFile = coff::OpenObj(wideObjPath.c_str()); if (coffFile && coffFile->memoryFile) { coff::ExternalSymbolDB* externalSymbolDb = coff::GatherExternalSymbolDatabase(coffFile, compilandUniqueId); coff::CloseObj(coffFile); // force the linker to include references to all external functions which we're going to hook, // so they're not kicked out by OPT:REF. if (externalSymbolDb) { const size_t symbolCount = externalSymbolDb->symbols.size(); for (size_t i = 0u; i < symbolCount; ++i) { const coff::SymbolType::Enum type = externalSymbolDb->types[i]; if (type == coff::SymbolType::EXTERNAL_FUNCTION) { const ImmutableString& function = externalSymbolDb->symbols[i]; linkerOptions += L"/INCLUDE:"; linkerOptions += string::ToWideString(function); linkerOptions += L"\n"; } } coff::DestroyDatabase(externalSymbolDb); } else { LC_ERROR_USER("External symbol database for COFF %s is invalid", objPath.c_str()); } } } // generate path for .pdb and .exe file with monotonically increasing counter std::wstring pdbPath; std::wstring exePath; bool isExeOrPdbFileStillThere = false; do { std::wstring patchInstanceStr(L".patch_"); patchInstanceStr += std::to_wstring(m_patchCounter); // depending on the Visual Studio version and project settings, PDB files may be generated incrementally! // this means that if the PDB file exists (perhaps from a previous Live++ session), it will contain much more info // than necessary and be significantly larger. // we therefore delete leftover files from previous sessions to make the linker write completely new outputs. // additionally, when unloading live modules, the debugger might still have a lock on the PDB file, even // though the corresponding DLL has been unloaded already. // in this case, we increase the counter until we find a PDB file that was either deleted successfully or // did not exist yet. // safety net: in case the PDB path does not exist, we generate the PDB filename from the module name const std::wstring originalPdbPath = (m_linkerDB->pdbPath.GetLength() != 0u) ? string::ToWideString(m_linkerDB->pdbPath) // we have a valid, original PDB path for the module : std::wstring(Filesystem::RemoveExtension(m_moduleName.c_str()).GetString()) + L".pdb"; // create our own PDB path based on the module's name isExeOrPdbFileStillThere = false; pdbPath = string::Replace(originalPdbPath, L".pdb", patchInstanceStr + std::wstring(L".pdb")); exePath = string::Replace(originalPdbPath, L".pdb", patchInstanceStr + std::wstring(L".exe")); const Filesystem::PathAttributes& pdbAttributes = Filesystem::GetAttributes(pdbPath.c_str()); const Filesystem::PathAttributes& exeAttributes = Filesystem::GetAttributes(exePath.c_str()); if (Filesystem::DoesExist(pdbAttributes)) { if (!Filesystem::DeleteIfExists(pdbPath.c_str())) { // PDB file could not be deleted isExeOrPdbFileStillThere = true; } } if (Filesystem::DoesExist(exeAttributes)) { if (!Filesystem::DeleteIfExists(exePath.c_str())) { // EXE file could not be deleted isExeOrPdbFileStillThere = true; } } if (isExeOrPdbFileStillThere) { ++m_patchCounter; } } while (isExeOrPdbFileStillThere); // path of output .exe file linkerOptions += L"/OUT:\""; linkerOptions += exePath; linkerOptions += L"\" "; // path of output .pdb file linkerOptions += L"/PDB:\""; linkerOptions += pdbPath; linkerOptions += L"\"\n"; // add all needed .obj files to the command line { for (auto it = neededCompilands.begin(); it != neededCompilands.end(); ++it) { const symbols::ObjPath& objPath = *it; LC_LOG_DEV("Pulling in OBJ file %s", objPath.c_str()); linkerOptions += L"\""; linkerOptions += string::ToWideString(objPath); linkerOptions += L"\"\n"; } } // add all libraries to the command line { const size_t count = m_libraryDB->libraries.size(); for (size_t i = 0u; i < count; ++i) { const symbols::FilePath& libPath = m_libraryDB->libraries[i]; LC_LOG_DEV("Pulling in LIB file %s", libPath.c_str()); linkerOptions += L"\""; linkerOptions += string::ToWideString(libPath); linkerOptions += L"\"\n"; } } // BEGIN EPIC MOD { const size_t count = additionalLibraries.size(); for (size_t i = 0u; i < count; ++i) { const std::wstring& libPath = additionalLibraries[i]; LC_LOG_DEV("Pulling in additional LIB file %s", libPath.c_str()); linkerOptions += L"\""; linkerOptions += libPath; linkerOptions += L"\"\n"; } } // END EPIC MOD // add UE4-specific helper library to support NatVis visualizers when debugging if (appSettings::g_ue4EnableNatVisSupport->GetValue()) { const std::wstring& lppExePath = Process::Current::GetImagePath().GetString(); std::wstring libPath = Filesystem::GetDirectory(lppExePath.c_str()).GetString(); libPath += L"\\UE4_NatVisHelper.lib"; // check whether the .lib file exists and output a warning if not { const Filesystem::PathAttributes& attributes = Filesystem::GetAttributes(libPath.c_str()); if (!Filesystem::DoesExist(attributes)) { LC_WARNING_USER("Cannot find UE4 NatVis helper library at %S", libPath.c_str()); } } linkerOptions += L"\""; linkerOptions += libPath; linkerOptions += L"\"\n"; // force the linker to include the needed symbols linkerOptions += L"/INCLUDE:"; linkerOptions += string::ToWideString(g_ue4NameTableInLIB); linkerOptions += L"\n"; linkerOptions += L"/INCLUDE:"; linkerOptions += string::ToWideString(g_ue4ObjectArrayInLIB); linkerOptions += L"\n"; } // BEGIN EPIC MOD - Support for UE debug visualizers linkerOptions += L"\""; #if LC_64_BIT linkerOptions += *FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / L"Extras/NatvisHelpers/Win64/NatvisHelpers.lib"); #else linkerOptions += *FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / L"Extras/NatvisHelpers/Win32/NatvisHelpers.lib"); #endif linkerOptions += L"\"\n"; linkerOptions += L"/INCLUDE:InitNatvisHelpers\n"; // END EPIC MOD generateLinkerCommandLine.End(); telemetry::Scope linkScope("Linking"); std::wstring linkerPath = GetLinkerPath(m_linkerDB); std::wstring linkerWorkingDirectory = (m_linkerDB->workingDirectory.GetLength() != 0u) ? string::ToWideString(m_linkerDB->workingDirectory) // we have a valid working directory : Filesystem::GetDirectory(linkerPath.c_str()).GetString(); // no valid working directory, take the linker directory instead // create a temporary file that acts as a so-called response file for the linker, and contains // the whole linker command-line. this is done because the latter can get very long, longer // than the limit of 32k characters. const Filesystem::Path responseFilePath = Filesystem::GenerateTempFilename(); Filesystem::CreateFileWithData(responseFilePath.GetString(), linkerOptions.c_str(), linkerOptions.size() * sizeof(wchar_t)); // BEGIN EPIC MOD const Process::Environment linkerEnvironment = compiler::GetEnvironmentFromCache(linkerPath.c_str()); const void* linkerEnvironmentData = linkerEnvironment.data; std::wstring linkerCommandLine; // If there are vfs entries, we need to run the linker through ubacli with -vfs options to make sure linker result is the same as when running through UBT if (!Filesystem::GetVfsEntries().empty()) { for (auto& entry : Filesystem::GetVfsEntries()) { linkerCommandLine += L" -vfs=\"" + entry.virtualPath + L";" + entry.localPath + L"\""; } linkerCommandLine += L" local \""; linkerCommandLine += linkerPath.c_str(); linkerCommandLine += '\"'; linkerPath = *FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / L"Binaries/Win64/UnrealBuildAccelerator/x64/UbaCli.exe"); linkerWorkingDirectory = Filesystem::Devirtualize(linkerWorkingDirectory); } else { linkerCommandLine = Filesystem::GetFilename(linkerPath.c_str()).GetString(); } linkerCommandLine += L" @\""; linkerCommandLine += responseFilePath.GetString(); linkerCommandLine += L"\""; GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Linking patch..."); // END EPIC MOD Process::Context* linkerProcessContext = Process::Spawn(linkerPath.c_str(), linkerWorkingDirectory.c_str(), linkerCommandLine.c_str(), linkerEnvironmentData, Process::SpawnFlags::REDIRECT_STDOUT | Process::SpawnFlags::NO_WINDOW); const unsigned int linkerExitCode = Process::Wait(linkerProcessContext); const double linkerTime = linkScope.ReadSeconds(); // for all the following operations, make sure to restore the original .obj files from their backup location for (auto compilandIt = neededCompilands.begin(); compilandIt != neededCompilands.end(); ++compilandIt) { const symbols::ObjPath& objPath = *compilandIt; const std::wstring originalPath(string::ToWideString(objPath)); const std::wstring bakPath = originalPath + L".bak"; const Filesystem::PathAttributes& attributes = Filesystem::GetAttributes(bakPath.c_str()); if (Filesystem::DoesExist(attributes)) { Filesystem::Delete(originalPath.c_str()); Filesystem::Move(bakPath.c_str(), originalPath.c_str()); } // END EPIC MOD } const std::wstring linkerProcessStdout = Process::GetStdOutData(linkerProcessContext); const wchar_t* linkerOutput = linkerProcessStdout.c_str(); // send linker output to main executable { Logging::LogNoFormat(linkerOutput); const size_t outputLength = linkerProcessStdout.length(); if (outputLength != 0u) { if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { for (size_t p = 0u; p < processCount; ++p) { const DuplexPipe* pipe = processData[p].liveProcess->GetPipe(); commands::LogOutput cmd {}; pipe->SendCommandAndWaitForAck(cmd, linkerOutput, (outputLength + 1u) * sizeof(wchar_t)); } } } } Process::Destroy(linkerProcessContext); Filesystem::Delete(responseFilePath.GetString()); linkScope.End(); if (linkerExitCode != 0u) { LC_ERROR_USER("Failed to link patch (%.3fs) (Exit code: 0x%X)", linkerTime, linkerExitCode); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::LINK_ERROR; } LC_SUCCESS_USER("Successfully linked patch (%.3fs)", linkerTime); // linking was successful, clear the compiled compilands' status and bump the patch version for the next patch for (auto compilandIt = m_compiledCompilands.begin(); compilandIt != m_compiledCompilands.end(); ++compilandIt) { symbols::Compiland* compiland = compilandIt->second; if (compiland) { symbols::ClearCompilandAsRecompiled(compiland); } } ++m_patchCounter; // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Preparing patch image..."); // END EPIC MOD // try to load patch image executable::Image* image = executable::OpenImage(exePath.c_str(), Filesystem::OpenMode::READ_WRITE); if (!image) { LC_ERROR_USER("Cannot load patch executable %S", exePath.c_str()); // clear the set for the next update m_modifiedFiles.clear(); m_compiledCompilands.clear(); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::LOAD_PATCH_ERROR; } executable::ImageSectionDB* imageSections = executable::GatherImageSectionDB(image); // before loading the DLL, disable its entry point so we can load it without initializing anything. // we first want to reconstruct symbol information and patch dynamic initializers, only then do // we want to call the entry point. LC_LOG_DEV("Patching entry point"); ExecutablePatcher executablePatcher(image, imageSections); const uint32_t entryPointRva = executablePatcher.DisableEntryPointInImage(image, imageSections); executable::DestroyImageSectionDB(imageSections); // note that the image needs to be closed before it can be loaded into a process const uint32_t patchImageSize = executable::GetSize(image); executable::CloseImage(image); // the patch's entry point is disabled. tell the processes to load the patch LC_LOG_DEV("Loading code into process"); types::vector loadedPatches; { #if LC_64_BIT executable::PreferredBase currentPreferredImageBase = 0u; #endif for (size_t i = 0u; i < processCount; ++i) { const PerProcessData& data = processData[i]; commands::LoadPatch cmd = {}; wcscpy_s(cmd.path, Filesystem::Devirtualize(exePath).c_str()); #if LC_64_BIT // before doing anything further, we need to ensure that the patch can be loaded into the address space at a suitable location. // for 64-bit applications, this means that the patch must lie in a +/-2GB range of the main executable. // 32-bit executables can reach the whole address space due to modulo addressing. LC_LOG_DEV("Scanning memory for suitable patch location (PID: %d)", data.liveProcess->GetProcessId()); // disable the main process before scanning its memory to ensure that no operation allocates/frees virtual memory concurrently Process::Suspend(data.liveProcess->GetProcessHandle()); // free our page reservations in the +-2GB range of the main module data.liveProcess->FreeVirtualMemoryPages(data.originalModuleBase); const executable::PreferredBase preferredImageBase = FindPreferredImageBase(patchImageSize, data.liveProcess->GetProcessId(), data.liveProcess->GetProcessHandle(), data.originalModuleBase); // rather than constantly copying images for processes, check whether they need to be rebased to a different address for this process const bool imageNeedsToBeRebased = (currentPreferredImageBase != preferredImageBase); const bool imageNeedsToBeCopied = (currentPreferredImageBase == 0u) ? false // this is the first image, so no copying needed : imageNeedsToBeRebased; // image has been rebased and now potentially needs to be rebased to a different address std::wstring rebasedExePath = exePath; if (imageNeedsToBeCopied) { // this image needs to be copied. create a new name based on the process ID, which must be unique rebasedExePath = string::Replace(rebasedExePath, L".exe", std::wstring(L"_PID_") + std::to_wstring(+data.liveProcess->GetProcessId()) + L".exe"); Filesystem::Copy(exePath.c_str(), rebasedExePath.c_str()); wcscpy_s(cmd.path, Filesystem::Devirtualize(rebasedExePath).c_str()); } if (imageNeedsToBeRebased) { // rebase the patch image to its preferred base address executable::Image* rebasedImage = executable::OpenImage(rebasedExePath.c_str(), Filesystem::OpenMode::READ_WRITE); LC_LOG_DEV("Rebasing patch executable to image base 0x%" PRIX64 " (PID: %d)", preferredImageBase, data.liveProcess->GetProcessId()); executable::RebaseImage(rebasedImage, preferredImageBase); executable::CloseImage(rebasedImage); currentPreferredImageBase = preferredImageBase; } // resume the main process so that it can respond to our command. if we're *really* unlucky, a concurrent operation // will allocate virtual memory at the patch's preferred image base, possibly rendering the patch unusable because // it cannot be loaded. // the chances of that happening are *very* rare though, and we can always load the next patch then. Process::Resume(data.liveProcess->GetProcessHandle()); #endif data.liveProcess->GetPipe()->SendCommandAndWaitForAck(cmd, nullptr, 0u); // receive command with patch info CommandMap commandMap; commandMap.RegisterAction(); commandMap.HandleCommands(data.liveProcess->GetPipe(), &loadedPatches); } } #if LC_64_BIT // immediately reserve pages in the +-2GB range of the main module again for (size_t i = 0u; i < processCount; ++i) { const PerProcessData& data = processData[i]; data.liveProcess->ReserveVirtualMemoryPages(data.originalModuleBase); } #endif if (processCount != loadedPatches.size()) { // communication with the client broke down while trying to load the patch, bail out LC_ERROR_USER("Client communication broken, patch could not be loaded."); // clear the set for the next update m_modifiedFiles.clear(); m_compiledCompilands.clear(); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::LOAD_PATCH_ERROR; } bool patchesLoadedSuccessfully = true; for (size_t i = 0u; i < processCount; ++i) { const PerProcessData& data = processData[i]; void* patchBase = loadedPatches[i]; LC_LOG_DEV("Loaded patch at 0x%p (PID: %d)", patchBase, data.liveProcess->GetProcessId()); patchesLoadedSuccessfully = CheckPatchAddressValidity(data.originalModuleBase, patchBase, data.liveProcess->GetProcessHandle()); if (!patchesLoadedSuccessfully) { break; } } if (!patchesLoadedSuccessfully) { LC_ERROR_USER("Patch could not be activated."); // one of the patches cannot be used, unload all of them and bail out for (size_t i = 0u; i < processCount; ++i) { const DuplexPipe* clientPipe = processData[i].liveProcess->GetPipe(); clientPipe->SendCommandAndWaitForAck(commands::UnloadPatch { static_cast(loadedPatches[i]) }, nullptr, 0u); } // clear the set for the next update m_modifiedFiles.clear(); m_compiledCompilands.clear(); CallCompileErrorHooks(m_moduleCache, updateType); return ErrorType::ACTIVATE_PATCH_ERROR; } // enter sync point in all processes if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; data.liveProcess->GetPipe()->SendCommandAndWaitForAck(commands::EnterSyncPoint {}, nullptr, 0u); } } // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Loading patch PDB..."); // END EPIC MOD LC_LOG_DEV("Loading patch PDB"); telemetry::Scope loadPatchPDBScope("Loading PDB database"); symbols::Provider* patchSymbolProvider = symbols::OpenEXE(Filesystem::Devirtualize(exePath).c_str(), symbols::OpenOptions::ACCUMULATE_SIZE); symbols::DiaCompilandDB* patch_diaCompilandDb = symbols::GatherDiaCompilands(patchSymbolProvider); IDiaSymbol* patch_linkerSymbol = symbols::FindLinkerSymbol(patch_diaCompilandDb); auto taskRootPatchLoading = scheduler::CreateEmptyTask(); // similar to the initial reading of PDB files, we open separate providers to enable // multi-threaded loading of PDB data. auto taskPatch_symbolDB = scheduler::CreateTask(taskRootPatchLoading, [patchSymbolProvider]() { return symbols::GatherSymbols(patchSymbolProvider); }); scheduler::RunTask(taskPatch_symbolDB); auto taskPatch_contributionDB = scheduler::CreateTask(taskRootPatchLoading, [exePath]() { symbols::Provider* localProvider = symbols::OpenEXE(Filesystem::Devirtualize(exePath).c_str(), symbols::OpenOptions::NONE); symbols::DiaCompilandDB* localDiaCompilandDb = symbols::GatherDiaCompilands(localProvider); auto db = symbols::GatherContributions(localProvider); symbols::DestroyDiaCompilandDB(localDiaCompilandDb); symbols::Close(localProvider); return db; }); scheduler::RunTask(taskPatch_contributionDB); // note that we only gather symbols from .obj contained in the new patch executable. // therefore we need to extract its compiland database as well, and cannot use the one from // the original executable. auto taskPatch_compilandDB = scheduler::CreateTask(taskRootPatchLoading, [this, exePath]() { symbols::Provider* localProvider = symbols::OpenEXE(Filesystem::Devirtualize(exePath).c_str(), symbols::OpenOptions::NONE); symbols::DiaCompilandDB* localDiaCompilandDb = symbols::GatherDiaCompilands(localProvider); uint32_t options = 0u; if (appSettings::g_enableDevLogCompilands->GetValue()) { options |= symbols::CompilandOptions::GENERATE_LOGS; } if (appSettings::g_compilerForcePchPdbs->GetValue()) { options |= symbols::CompilandOptions::FORCE_PCH_PDBS; } // in case the user wants to use a completely external build system, we track .objs only if (m_runMode == RunMode::EXTERNAL_BUILD_SYSTEM) { options |= symbols::CompilandOptions::TRACK_OBJ_ONLY; } // TODO: what if somebody uses FASTBuild? how to get the new dependencies? // does this step even have to be done? seems like this might not even be needed in ANY CASE. // -> this has to be done to figure out new #include files for existing compilands! // => but it should be split in two: figuring out compilands, and figuring out include files. symbols::CompilandDB* db = symbols::GatherCompilands(localProvider, localDiaCompilandDb, GetAmalgamatedSplitThreshold(), options); symbols::DestroyDiaCompilandDB(localDiaCompilandDb); symbols::Close(localProvider); return db; }); scheduler::RunTask(taskPatch_compilandDB); auto taskPatch_thunkDB = scheduler::CreateTask(taskRootPatchLoading, [patch_linkerSymbol]() { return symbols::GatherThunks(patch_linkerSymbol); }); scheduler::RunTask(taskPatch_thunkDB); auto taskPatch_imageSectionDB = scheduler::CreateTask(taskRootPatchLoading, [patch_linkerSymbol]() { return symbols::GatherImageSections(patch_linkerSymbol); }); scheduler::RunTask(taskPatch_imageSectionDB); // ensure asynchronous operations have finished scheduler::RunTask(taskRootPatchLoading); scheduler::WaitForTask(taskRootPatchLoading); // fetch results symbols::SymbolDB* patch_symbolDB = taskPatch_symbolDB->GetResult(); symbols::ContributionDB* patch_contributionDB = taskPatch_contributionDB->GetResult(); symbols::CompilandDB* patch_compilandDB = taskPatch_compilandDB->GetResult(); symbols::ThunkDB* patch_thunkDB = taskPatch_thunkDB->GetResult(); symbols::ImageSectionDB* patch_imageSectionDB = taskPatch_imageSectionDB->GetResult(); symbols::DestroyLinkerSymbol(patch_linkerSymbol); // destroy tasks scheduler::DestroyTask(taskRootPatchLoading); scheduler::DestroyTask(taskPatch_symbolDB); scheduler::DestroyTask(taskPatch_contributionDB); scheduler::DestroyTask(taskPatch_compilandDB); scheduler::DestroyTask(taskPatch_thunkDB); scheduler::DestroyTask(taskPatch_imageSectionDB); symbols::FinalizeContributions(patch_compilandDB, patch_contributionDB); LC_LOG_DEV("Updating cache of external symbols"); // update the cache that stores all external/public symbols for each compiland { // clear the cache for all files that were compiled, but not the ones that were pulled in for linking only // without them having changed (e.g. a PCH). for (auto it : m_compiledCompilands) { const symbols::ObjPath& objPath = it.first; m_externalSymbolsPerCompilandCache.erase(objPath); } // we only know public symbols at this point, so walk all of them and find their corresponding contribution. // there are two ways to go about this: // 1) walk all symbols, find their contribution // 2) walk all contributions, find their symbol // this needs to be done using 1), otherwise some external symbols cannot be found because their contributions // have been merged. for (auto it : patch_symbolDB->symbolsByRva) { const uint32_t rva = it.first; const symbols::Symbol* symbol = it.second; const symbols::Contribution* contribution = symbols::FindContributionByRVA(patch_contributionDB, rva); if (contribution) { const ImmutableString& compilandName = symbols::GetContributionCompilandName(patch_contributionDB, contribution); m_externalSymbolsPerCompilandCache[compilandName].push_back(symbol); } } } loadPatchPDBScope.End(); // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Updating COFF cache..."); // END EPIC MOD { LC_LOG_DEV("Updating COFF cache for new patch compilands"); // update the COFF cache for new patch compilands. // there may be files for which we don't have a database yet, even though we updated the database for all compiled files. // this can happen when a new .obj that is part of a library is linked in for the first time. types::vector updatedCoffs = UpdateCoffCache(patch_compilandDB->compilands, m_coffCache, CacheUpdate::NON_EXISTANT, modifiedOrNewObjFiles); // similarly, reconstruct symbols and dynamic initializers for new .obj that have been pulled in for the first time. // otherwise, dynamic initializers from these files will never be reconstructed, which would inevitably lead to // symbols being constructed twice. LC_LOG_DEV("Reconstructing symbols from original OBJ"); { LC_LOG_INDENT_DEV; executable::Image* originalImage = executable::OpenImage(m_moduleName.c_str(), Filesystem::OpenMode::READ); executable::ImageSectionDB* originalImageSections = executable::GatherImageSectionDB(originalImage); types::StringSet noSymbolsToIgnore; symbols::Provider* provider = symbols::OpenEXE(m_moduleName.c_str(), symbols::OpenOptions::NONE); { symbols::GatherDynamicInitializers(provider, originalImage, originalImageSections, m_imageSectionDB, m_contributionDB, m_compilandDB, m_coffCache, m_symbolDB); const size_t count = updatedCoffs.size(); for (size_t i = 0u; i < count; ++i) { const symbols::ObjPath& objPath = updatedCoffs[i]; if (m_reconstructedCompilands.find(objPath) == m_reconstructedCompilands.end()) { // no entry yet, must be reconstructed LC_LOG_DEV("COFF %s not in cache yet", objPath.c_str()); const coff::CoffDB* database = m_coffCache->Lookup(objPath); if (!database) { LC_ERROR_USER("COFF database for compiland %s is invalid (lazy reconstruct)", objPath.c_str()); continue; } m_reconstructedCompilands.emplace(objPath); symbols::ReconstructFromExecutableCoff(provider, originalImage, originalImageSections, database, noSymbolsToIgnore, objPath, m_contributionDB, m_thunkDB, m_imageSectionDB, m_symbolDB); } } } symbols::Close(provider); executable::DestroyImageSectionDB(originalImageSections); executable::CloseImage(originalImage); } } // new symbols have been reconstructed, so transform the symbols containing anonymous namespace names symbols::TransformAnonymousNamespaceSymbols(m_symbolDB, m_contributionDB, m_compilandDB, modifiedOrNewObjFiles); // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Reconstructing patch symbols..."); // END EPIC MOD // reconstruct symbols for all compilands that are part of the new patch executable executable::Image* patchImage = executable::OpenImage(exePath.c_str(), Filesystem::OpenMode::READ); executable::ImageSectionDB* patchImageSections = executable::GatherImageSectionDB(patchImage); // gather the dynamic initializers and remaining symbols by walking the module const symbols::DynamicInitializerDB initializerDb = symbols::GatherDynamicInitializers(patchSymbolProvider, patchImage, patchImageSections, patch_imageSectionDB, patch_contributionDB, patch_compilandDB, m_coffCache, patch_symbolDB); { LC_LOG_DEV("Reconstructing patch symbols from OBJ"); LC_LOG_INDENT_DEV; for (auto it = patch_compilandDB->compilands.begin(); it != patch_compilandDB->compilands.end(); ++it) { const symbols::ObjPath& patchObjPath = it->first; const coff::CoffDB* database = m_coffCache->Lookup(patchObjPath); if (!database) { LC_ERROR_USER("COFF database for compiland %s is invalid", patchObjPath.c_str()); continue; } symbols::ReconstructFromExecutableCoff(patchSymbolProvider, patchImage, patchImageSections, database, strippedSymbolsPerCompiland[patchObjPath], patchObjPath, patch_contributionDB, patch_thunkDB, patch_imageSectionDB, patch_symbolDB); } // merge compilands and dependencies with existing ones to account for new files and e.g. new #includes. symbols::MergeCompilandsAndDependencies(m_compilandDB, patch_compilandDB); // update directory cache for new compilands UpdateDirectoryCache(directoryCache); // AMALGAMATION // for files that are part of an amalgamation, we write a new database in case the file compiled successfully. // this ensures that files split once don't need to be recompiled again in case nothing changed, even when // restarting a new Live++ session. // when a file fails to compile, no database exists on disk, so the file will be recompiled next time automatically. for (auto it = patch_compilandDB->compilands.begin(); it != patch_compilandDB->compilands.end(); ++it) { const symbols::ObjPath& patchObjPath = it->first; const bool isPartOfAmalgamation = amalgamation::IsPartOfAmalgamation(patchObjPath.c_str()); if (isPartOfAmalgamation) { auto originalIt = m_compilandDB->compilands.find(patchObjPath); if (originalIt != m_compilandDB->compilands.end()) { // this compiland had its source files updated, write a database const symbols::ObjPath& originalObjPath = originalIt->first; const symbols::Compiland* compiland = originalIt->second; amalgamation::WriteDatabase(originalObjPath, GetCompilerPath(compiland), compiland, appSettings::g_compilerOptions->GetValue()); } } } symbols::DestroyDiaCompilandDB(patch_diaCompilandDb); patch_diaCompilandDb = nullptr; } executable::DestroyImageSectionDB(patchImageSections); executable::CloseImage(patchImage); // BEGIN EPIC MOD uint64_t patchLastModicationTime = patchSymbolProvider->lastModificationTime; // END EPIC MOD symbols::Close(patchSymbolProvider); // now that everything is loaded and reconstructed in the patch, transform the symbols containing anonymous namespace names. // note that we need to used the merged main compiland database here. TransformAnonymousNamespaceSymbols(patch_symbolDB, patch_contributionDB, m_compilandDB, modifiedOrNewObjFiles); // store the new databases into the module cache ModulePatch* compiledModulePatch = nullptr; // BEGIN EPIC MOD const size_t token = m_moduleCache->Insert(patch_symbolDB, patch_contributionDB, patch_compilandDB, patch_thunkDB, patch_imageSectionDB, patchLastModicationTime); // END EPIC MOD { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; m_moduleCache->RegisterProcess(token, data.liveProcess, loadedPatches[p]); } // now that the patch has been loaded, store a new module patch and record the data needed for // loading it into another process at a later time. compiledModulePatch = new ModulePatch(exePath, pdbPath, token); m_compiledModulePatches.push_back(compiledModulePatch); } // record entry point code for patching the entry point when loading this image into a different process later { compiledModulePatch->RegisterEntryPointCode(executablePatcher.GetEntryPointCode()); } { // pre-patch hooks must not be called on the current executable because the hooks want to use the old memory layout of // data structures. if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { const ModuleCache::FindHookData& hookData = m_moduleCache->FindHooksInSectionBackwards(token, ImmutableString(LPP_PREPATCH_SECTION)); if ((hookData.firstRva != 0u) && (hookData.lastRva != 0u)) { const size_t count = hookData.data->processes.size(); for (size_t p = 0u; p < count; ++p) { const ModuleCache::ProcessData& hookProcessData = hookData.data->processes[p]; const Process::Id pid = hookProcessData.processId; void* moduleBase = hookProcessData.moduleBase; const DuplexPipe* pipe = hookProcessData.pipe; LC_LOG_USER("Calling pre-patch hooks (PID: %d)", +pid); pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::PREPATCH, moduleBase, hookData), nullptr, 0u); } compiledModulePatch->RegisterPrePatchHooks(hookData.data->index, hookData.firstRva, hookData.lastRva); } } } // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Patching relocations..."); // END EPIC MOD // BEGIN EPIC MOD auto patchRelocationsPreEntryPoint = [&](bool forceBackwards) { // END EPIC MOD LC_LOG_DEV("Patching relocations before calling entry point"); // walk all relocations in the .OBJ files, find their current locations in the .exe, // and patch the relocations to point to the original symbols in the original .exe. // we need to patch relocations *before* calling the DLL entry point, because global // initializer code might refer to symbols that have been stripped by us. // note that we only patch relocations to data symbols at this time, because functions haven't been // hooked yet, and we need to ensure that dynamic initializers end up using new code paths (if available), while // still referring to existing data symbols. { telemetry::Scope patchingRelocationsScope("Patching relocations"); uint32_t relocationsHandledCount = 0u; size_t relocationsCount = 0u; for (auto it = patch_compilandDB->compilands.begin(); it != patch_compilandDB->compilands.end(); ++it) { const symbols::ObjPath& objPath = it->first; LC_LOG_DEV("Patching relocations for file %s", objPath.c_str()); LC_LOG_INDENT_DEV; const coff::CoffDB* coffDb = m_coffCache->Lookup(objPath); if (!coffDb) { LC_ERROR_USER("Could not find COFF database for file %s", objPath.c_str()); continue; } const std::wstring wideObjPath = string::ToWideString(objPath); // note that we need to take the original compiland here, to make sure that single split-files get assigned // the same unique ID as the corresponding amalgamated file. symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); const types::StringSet& strippedSymbols = strippedSymbolsPerCompiland[objPath]; const types::StringSet& forceStrippedSymbols = forceStrippedSymbolsPerCompiland[objPath]; const size_t symbolCount = coffDb->symbols.size(); for (size_t i = 0u; i < symbolCount; ++i) { const coff::Symbol* symbol = coffDb->symbols[i]; relocationsCount += symbol->relocations.size(); // check if the patch knows this symbol. // if not, it has probably been stripped and there is no need to walk all its relocations. const ImmutableString& symbolName = symbols::TransformAnonymousNamespacePattern(coff::GetSymbolName(coffDb, symbol), compilandUniqueId); const symbols::Symbol* realSymbol = symbols::FindSymbolByName(patch_symbolDB, symbolName); if (!realSymbol) { // this symbol has been stripped from the executable. // in optimized builds, the compiler will sometimes e.g. leave a static function in an OBJ file, // which will be kicked out by the linker. continue; } // before patching relocations, check whether the symbol which relocations we want to patch originated from // a compiland that is the same as the file we're working on. // this might not be the case, especially when using static libraries, COMDATs, and compilands that use the // same inline function but have slightly different compiler options (/hotpatch vs. no /hotpatch, e.g. // __local_stdio_printf_options in the main module vs. in the dynamic runtime) const symbols::Contribution* originalContribution = symbols::FindContributionByRVA(patch_contributionDB, realSymbol->rva); if (originalContribution) { const ImmutableString& compilandName = symbols::GetContributionCompilandName(patch_contributionDB, originalContribution); if (compilandName != objPath) { LC_LOG_DEV("Ignoring relocations for symbol %s in file %s (original compiland: %s)", symbolName.c_str(), objPath.c_str(), compilandName.c_str()); continue; } } const size_t relocationCount = symbol->relocations.size(); for (size_t j = 0u; j < relocationCount; ++j) { const coff::Relocation* relocation = symbol->relocations[j]; if (relocation->dstIsSection) { // don't patch relocations to sections continue; } // ignore relocations to symbols in .msvcjmc (MSVC JustMyCode) sections if (relocation->dstSectionIndex >= 0) { const uint32_t index = static_cast(relocation->dstSectionIndex); const coff::Section& section = coffDb->sections[index]; if (coff::IsMSVCJustMyCodeSection(section.name.c_str())) { LC_LOG_DEV("Ignoring relocation to symbol in section %s", section.name.c_str()); continue; } } ImmutableString dstSymbolName = symbols::TransformAnonymousNamespacePattern(GetRelocationDstSymbolName(coffDb, relocation), compilandUniqueId); const bool refersToDataSymbol = !coff::IsFunctionSymbol(coff::GetRelocationDstSymbolType(relocation)); const bool refersToStrippedSymbol = (strippedSymbols.find(dstSymbolName) != strippedSymbols.end()); if (refersToDataSymbol || refersToStrippedSymbol) { // BEGIN EPIC MOD const relocations::Record& relocationRecord = relocations::PatchRelocation(relocation, coffDb, forceStrippedSymbols, m_moduleCache, symbolName, dstSymbolName, realSymbol, token, &loadedPatches[0], forceBackwards); if (!forceBackwards && relocations::IsValidRecord(relocationRecord)) { compiledModulePatch->RegisterPreEntryPointRelocation(relocationRecord); } // END EPIC MOD ++relocationsHandledCount; } } } } LC_LOG_TELEMETRY("Handled %d of %d relocations in %.3fms (avg: %.3fus)", relocationsHandledCount, relocationsCount, patchingRelocationsScope.ReadMilliSeconds(), (patchingRelocationsScope.ReadMicroSeconds() / relocationsHandledCount)); } // BEGIN EPIC MOD }; // END EPIC MOD // BEGIN EPIC MOD patchRelocationsPreEntryPoint(enableReinstancingFlow); // END EPIC MOD // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Patching public functions in lib compilands..."); // END EPIC MOD LC_LOG_DEV("Patching public functions in lib compilands"); { telemetry::Scope patchingFunctionsScope("Patching functions"); uint32_t functionsPatchedCount = 0u; size_t functionsCount = patch_symbolDB->patchableFunctionSymbols.size(); // functions in lib compilands cannot have changed, per definition. but there can be code linked in from libraries // that calls these functions, therefore they need to be patched to their original function, otherwise // there would be functions working on new data. this can be the case when linking with static libraries we don't have // the .obj files for. LC_LOG_INDENT_DEV; for (auto it : patch_symbolDB->patchableFunctionSymbols) { const symbols::Symbol* symbol = it; const ImmutableString& functionName = symbol->name; // never patch the entry point, we need to call that later on if (symbol->rva == entryPointRva) { LC_LOG_DEV("Ignoring entry point function %s", functionName.c_str()); continue; } // note that when patching new functions to original ones, the same rules as for patching relocations apply, // i.e. not all functions should be patched. if (symbols::IsExceptionRelatedSymbol(functionName)) { LC_LOG_DEV("Ignoring exception-related function %s", functionName.c_str()); continue; } else if (symbols::IsRuntimeCheckRelatedSymbol(functionName)) { LC_LOG_DEV("Ignoring runtime check function %s", functionName.c_str()); continue; } else if (symbols::IsSdlCheckRelatedSymbol(functionName)) { LC_LOG_DEV("Ignoring SDL check function %s", functionName.c_str()); continue; } // check whether the function is at least 5 bytes long to consider it for patching const symbols::Contribution* contribution = symbols::FindContributionByRVA(patch_contributionDB, symbol->rva); if (!contribution) { LC_ERROR_DEV("Ignoring function %s because its contribution cannot be found", functionName.c_str()); continue; } if (contribution->size < 5u) { LC_LOG_DEV("Ignoring function %s that is only %d bytes long", functionName.c_str(), contribution->size); continue; } const ModuleCache::FindSymbolData& originalData = m_moduleCache->FindSymbolByName(token, functionName); if (!originalData.symbol) { LC_LOG_DEV("Ignoring new function %s", functionName.c_str()); continue; } // if the original function to be patched did not come from a compiland, it cannot possibly have changed and // therefore needs to be patched. const symbols::Contribution* originalContribution = symbols::FindContributionByRVA(originalData.data->contributionDb, originalData.symbol->rva); if (originalContribution) { const ImmutableString& compilandName = symbols::GetContributionCompilandName(originalData.data->contributionDb, originalContribution); const symbols::Compiland* originalCompiland = symbols::FindCompiland(originalData.data->compilandDb, compilandName); if (!originalCompiland) { const size_t moduleProcessCount = originalData.data->processes.size(); for (size_t p = 0u; p < moduleProcessCount; ++p) { ++functionsPatchedCount; const Process::Id pid = originalData.data->processes[p].processId; void* moduleBase = originalData.data->processes[p].moduleBase; Process::Handle processHandle = originalData.data->processes[p].processHandle; const symbols::Symbol* patchSymbol = symbol; char* srcAddress = pointer::Offset(loadedPatches[p], patchSymbol->rva); char* destAddress = pointer::Offset(moduleBase, originalData.symbol->rva); LC_LOG_DEV("Patching library function %s at 0x%p (0x%X) (PID: %d)", functionName.c_str(), moduleBase, patchSymbol->rva, +pid); const functions::LibraryRecord& record = functions::PatchLibraryFunction(srcAddress, destAddress, patchSymbol->rva, originalData.symbol->rva, contribution, processHandle, originalData.data->index); if (functions::IsValidRecord(record)) { compiledModulePatch->RegisterLibraryFunctionPatch(record); } } } } } LC_LOG_TELEMETRY("Patched %d of %d functions in %.3fms (avg: %.3fus)", functionsPatchedCount, functionsCount, patchingFunctionsScope.ReadMilliSeconds(), (patchingFunctionsScope.ReadMicroSeconds() / functionsPatchedCount)); } // now that the .dll is loaded and symbols have been relocated, finally patch the dynamic initializers // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Patching dynamic initializers..."); // END EPIC MOD { const size_t count = initializerDb.dynamicInitializers.size(); LC_LOG_DEV("Scanning %d dynamic initializer candidates", count); LC_LOG_INDENT_DEV; for (size_t i = 0u; i < count; ++i) { const symbols::Symbol* initializerSymbol = initializerDb.dynamicInitializers[i]; const ImmutableString& name = initializerSymbol->name; // BEGIN EPIC MOD if (symbols::IsUEInitializerSymbol(name)) { LC_LOG_DEV("Skipping dynamic initializer symbol %s", name.c_str()); continue; } // END EPIC MOD const ModuleCache::FindSymbolData& originalData = m_moduleCache->FindSymbolByName(token, name); if (originalData.symbol) { // this initializer has been called already, overwrite it in all processes const uint32_t rva = initializerSymbol->rva; for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; LC_LOG_DEV("Patching dynamic initializer symbol %s at RVA 0x%X (PID: %d)", name.c_str(), rva, data.liveProcess->GetProcessId()); void* initializerAddress = pointer::Offset(loadedPatches[p], rva); Process::WriteProcessMemory(data.liveProcess->GetProcessHandle(), initializerAddress, nullptr); } compiledModulePatch->RegisterPatchedDynamicInitializer(rva); } else { LC_WARNING_DEV("Cannot find symbol %s in original executable", name.c_str()); } } // on Clang, CRT sections that store individual dynamic initializers don't expose any symbols in the COFF, // but only relocations to the initializers. therefore, the initializer database is empty. // however, the dynamic initializers should have been reconstructed at this point, so we know their RVA. // in this case, we scan all symbols from __xc_a to __xc_z, check to which symbol RVA they refer, fetch the symbol at this RVA, // and check if this symbol is already known to us. if so, we need to disable the corresponding initializer. if (usesClang) { const symbols::Symbol* firstInitializerSymbol = symbols::FindSymbolByName(patch_symbolDB, ImmutableString(LC_IDENTIFIER("__xc_a"))); const symbols::Symbol* lastInitializerSymbol = symbols::FindSymbolByName(patch_symbolDB, ImmutableString(LC_IDENTIFIER("__xc_z"))); if (firstInitializerSymbol && lastInitializerSymbol) { executable::Image* thisImage = executable::OpenImage(exePath.c_str(), Filesystem::OpenMode::READ); executable::ImageSectionDB* sectionDb = executable::GatherImageSectionDB(thisImage); // this is defined in the CRT, which also defines all the special sections // TODO: move this to somewhere else, e.g. Windows Internals typedef _PVFV DynamicInitializer; // the first symbol is always __xc_a, which we are not interested in. // similarly, the last symbol is always __xc_z, which we are also not interested in. const uint32_t firstRva = firstInitializerSymbol->rva + sizeof(DynamicInitializer); const uint32_t lastRva = lastInitializerSymbol->rva - sizeof(DynamicInitializer); for (uint32_t rva = firstRva; rva <= lastRva; rva += sizeof(DynamicInitializer)) { #if LC_64_BIT const uint64_t initializerAddress = executable::ReadFromImage(thisImage, sectionDb, rva); #else const uint32_t initializerAddress = executable::ReadFromImage(thisImage, sectionDb, rva); #endif // the relocations from "$initializer$" to a dynamic initializer are always absolute, so its // easy to reconstruct the dynamic initializer's RVA. const uint32_t dynamicInitializerRva = static_cast(initializerAddress - executable::GetPreferredBase(thisImage)); const symbols::Symbol* symbol = symbols::FindSymbolByRVA(patch_symbolDB, dynamicInitializerRva); if (symbol) { // check if we already know a symbol with this name const ModuleCache::FindSymbolData& findData = m_moduleCache->FindSymbolByName(token, symbol->name); const bool symbolFound = (findData.symbol != nullptr); // special case for amalgamated files: Clang assigns dynamic initializer functions based on filenames. // for amalgamated files, the initializers will already have executed, but the initializers in the // split files will get a different name. const bool isClangDynamicInitializer = (string::Contains(symbol->name.c_str(), "_GLOBAL__sub_I_")); if (symbolFound || isClangDynamicInitializer) { // this symbol already exists, so it must have been called during initialization earlier. // patch the dynamic initializer in all processes. for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; void* address = pointer::Offset(loadedPatches[p], rva); Process::WriteProcessMemory(data.liveProcess->GetProcessHandle(), address, nullptr); } compiledModulePatch->RegisterPatchedDynamicInitializer(rva); } } } executable::DestroyImageSectionDB(sectionDb); executable::CloseImage(thisImage); } } } { // patch security cookies in all processes. // when "Buffer Security Checks" (/GS) and/or "Enable Additional Security Checks" (/sdl) are enabled in a build, // the compiler inserts security cookies and a call to "__security_check_cookie" to check whether this cookie has // been overwritten. each EXE and DLL gets its own cookie, and this poses a problem. // when patching relocations, the original version of __security_check_cookie will be called with a check // against the security cookie stored in the patch DLL, which will of course fail. // we could special-case relocations to __security_check_cookie to never touch such relocations, but this doesn't // work under x64. // the reason for that is that under x86, __security_check_cookie will be called by __ehhandler$SomeFunctionName, // which means the call is always "embedded" into the code and we can therefore ignore such relocations. // under x64 however, throwing an exception always calls the GSHandler responsible for doing security checks, // but this handler lives in the original executable and is called by the kernel. // we therefore choose the simpler solution to overwrite patch DLL security cookies with their original values, // ensuring that a call to __security_check_cookie for a patch DLL will never fail. const symbols::Symbol* originalCookie = symbols::FindSymbolByName(m_symbolDB, ImmutableString(LC_IDENTIFIER("__security_cookie"))); const symbols::Symbol* newCookie = symbols::FindSymbolByName(patch_symbolDB, ImmutableString(LC_IDENTIFIER("__security_cookie"))); if (originalCookie && newCookie) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; PatchSecurityCookie(data.originalModuleBase, loadedPatches[p], originalCookie->rva, newCookie->rva, data.liveProcess->GetProcessHandle()); } compiledModulePatch->RegisterSecurityCookie(originalCookie->rva, newCookie->rva); } } // patch UE4-specific symbols needed by NatVis visualizers if (appSettings::g_ue4EnableNatVisSupport->GetValue()) { { const symbols::Symbol* originalNameTable = symbols::FindSymbolByName(m_symbolDB, ImmutableString(g_ue4NameTableInDLL)); const symbols::Symbol* newNameTable = symbols::FindSymbolByName(patch_symbolDB, ImmutableString(g_ue4NameTableInLIB)); if (originalNameTable && newNameTable) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; PatchUE4NatVisSymbols(data.originalModuleBase, loadedPatches[p], originalNameTable->rva, newNameTable->rva, data.liveProcess->GetProcessHandle()); } compiledModulePatch->RegisterUe4NameTable(originalNameTable->rva, newNameTable->rva); } } { const symbols::Symbol* originalObjectArray = symbols::FindSymbolByName(m_symbolDB, ImmutableString(g_ue4ObjectArrayInDLL)); const symbols::Symbol* newObjectArray = symbols::FindSymbolByName(patch_symbolDB, ImmutableString(g_ue4ObjectArrayInLIB)); if (originalObjectArray && newObjectArray) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; PatchUE4NatVisSymbols(data.originalModuleBase, loadedPatches[p], originalObjectArray->rva, newObjectArray->rva, data.liveProcess->GetProcessHandle()); } compiledModulePatch->RegisterUe4ObjectArray(originalObjectArray->rva, newObjectArray->rva); } } } // now that relocations are done, it is safe to call the entry point. // restore the original entry point and tell the process to call it. // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Restoring and calling entry point..."); // END EPIC MOD { // disable user entry point DllMain (if it exists). // the DllMain function is named differently depending on the architecture. #if LC_64_BIT const symbols::Symbol* dllMainSymbol = symbols::FindSymbolByName(patch_symbolDB, ImmutableString("DllMain")); #else const symbols::Symbol* dllMainSymbol = symbols::FindSymbolByName(patch_symbolDB, ImmutableString("_DllMain@12")); #endif if (dllMainSymbol) { // this is a DLL that has a user entry point. disable it in all processes. for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; PatchDllMain(loadedPatches[p], dllMainSymbol->rva, data.liveProcess->GetProcessHandle()); } compiledModulePatch->RegisterDllMain(dllMainSymbol->rva); } LC_LOG_DEV("Restoring original entry point"); // restore entry point in all processes for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; executablePatcher.RestoreEntryPoint(data.liveProcess->GetProcessHandle(), loadedPatches[p], entryPointRva); } LC_LOG_DEV("Calling original entry point"); // call entry points in all processes for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; data.liveProcess->GetPipe()->SendCommandAndWaitForAck(commands::CallEntryPoint { loadedPatches[p], entryPointRva }, nullptr, 0u); } // disable entry point in all processes again. // this is done because otherwise the process would crash when "detaching" the DLL on shutdown. // the reason is that _DllMainCRTStartup is called when detaching the DLL, and somewhere down the callstack, this // function calls __scrt_dllmain_uninitialize_c - which has been patched by us (to point to the original exe) and then // tries to free stuff already freed. instead of trying to handle edge cases like __scrt_dllmain_uninitialize_c manually, // we simply disable this entry point completely. // note that this does NOT disable global destructors of symbols living in patch DLLs to be called! // because we relocate _atexit to the original function, those destructors are all registered with the original // atexit table, meaning they will be properly destroyed. for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; executablePatcher.DisableEntryPoint(data.liveProcess->GetProcessHandle(), loadedPatches[p], entryPointRva); } } // BEGIN EPIC MOD auto patchRelocations = [&](bool forceBackwards) { // END EPIC MOD // dynamic initializers have run, patch the remaining relocations // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Patching remaining relocations..."); // END EPIC MOD { telemetry::Scope patchingRelocationsScope("Patching remaining relocations"); uint32_t relocationsHandledCount = 0u; size_t relocationsCount = 0u; LC_LOG_DEV("Patching relocations after calling entry point"); for (auto it = patch_compilandDB->compilands.begin(); it != patch_compilandDB->compilands.end(); ++it) { const symbols::ObjPath& objPath = it->first; LC_LOG_DEV("Patching relocations for file %s", objPath.c_str()); LC_LOG_INDENT_DEV; const coff::CoffDB* coffDb = m_coffCache->Lookup(objPath); if (!coffDb) { LC_ERROR_USER("Could not find COFF database for file %s", objPath.c_str()); continue; } const std::wstring wideObjPath = string::ToWideString(objPath); // note that we need to take the original compiland here, to make sure that single split-files get assigned // the same unique ID as the corresponding amalgamated file. symbols::Compiland* compiland = symbols::FindCompiland(m_compilandDB, objPath); const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); const types::StringSet& strippedSymbols = strippedSymbolsPerCompiland[objPath]; const types::StringSet& forceStrippedSymbols = forceStrippedSymbolsPerCompiland[objPath]; const size_t symbolCount = coffDb->symbols.size(); for (size_t i = 0u; i < symbolCount; ++i) { const coff::Symbol* symbol = coffDb->symbols[i]; relocationsCount += symbol->relocations.size(); // check if the patch knows this symbol. // if not, it has probably been stripped and there is no need to walk all its relocations. const ImmutableString& symbolName = coff::GetSymbolName(coffDb, symbol); const symbols::Symbol* realSymbol = symbols::FindSymbolByName(patch_symbolDB, symbolName); if (!realSymbol) { // this symbol has been stripped from the executable. // in optimized builds, the compiler will sometimes e.g. leave a static function in an OBJ file, // which will be kicked out by the linker. continue; } // before patching relocations, check whether the symbol which relocations we want to patch originated from // a compiland that is the same as the file we're working on. // this might not be the case, especially when using static libraries, COMDATs, and compilands that use the // same inline function but have slightly different compiler options (/hotpatch vs. no /hotpatch, e.g. // __local_stdio_printf_options in the main module vs. in the dynamic runtime) const symbols::Contribution* originalContribution = symbols::FindContributionByRVA(patch_contributionDB, realSymbol->rva); if (originalContribution) { const ImmutableString& compilandName = symbols::GetContributionCompilandName(patch_contributionDB, originalContribution); if (compilandName != objPath) { LC_LOG_DEV("Ignoring relocations for symbol %s in file %s (original compiland: %s)", symbolName.c_str(), objPath.c_str(), compilandName.c_str()); continue; } } const size_t relocationCount = symbol->relocations.size(); for (size_t j = 0u; j < relocationCount; ++j) { const coff::Relocation* relocation = symbol->relocations[j]; if (relocation->dstIsSection) { // don't patch relocations to sections continue; } // ignore relocations to symbols in .msvcjmc (MSVC JustMyCode) sections if (relocation->dstSectionIndex >= 0) { const uint32_t index = static_cast(relocation->dstSectionIndex); const coff::Section& section = coffDb->sections[index]; if (coff::IsMSVCJustMyCodeSection(section.name.c_str())) { LC_LOG_DEV("Ignoring relocation to symbol in section %s", section.name.c_str()); continue; } } ImmutableString dstSymbolName = symbols::TransformAnonymousNamespacePattern(GetRelocationDstSymbolName(coffDb, relocation), compilandUniqueId); // relocations to data symbols and stripped symbols have already been done const bool refersToFunctionSymbol = coff::IsFunctionSymbol(coff::GetRelocationDstSymbolType(relocation)); const bool refersToStrippedSymbol = (strippedSymbols.find(dstSymbolName) != strippedSymbols.end()); if (refersToFunctionSymbol && !refersToStrippedSymbol) { // BEGIN EPIC MOD const relocations::Record& relocationRecord = relocations::PatchRelocation(relocation, coffDb, forceStrippedSymbols, m_moduleCache, symbolName, dstSymbolName, realSymbol, token, &loadedPatches[0], forceBackwards); if (!forceBackwards && relocations::IsValidRecord(relocationRecord)) { compiledModulePatch->RegisterPostEntryPointRelocation(relocationRecord); } // END EPIC MOD ++relocationsHandledCount; } } } } LC_LOG_TELEMETRY("Handled %d of %d remaining relocations in %.3fms (avg: %.3fus)", relocationsHandledCount, relocationsCount, patchingRelocationsScope.ReadMilliSeconds(), (patchingRelocationsScope.ReadMicroSeconds() / relocationsHandledCount)); } // BEGIN EPIC MOD }; // END EPIC MOD // BEGIN EPIC MOD patchRelocations(enableReinstancingFlow); // END EPIC MOD // BEGIN EPIC MOD auto patchFunctions = [&](bool forceBackwards) // END EPIC MOD { // BEGIN EPIC MOD GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Patching functions..."); // END EPIC MOD // suspend the main processes before patching functions, because they might not use synchronization points. for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; Process::Suspend(data.liveProcess->GetProcessHandle()); } // determining which functions have changed (or lead to a different execution path) would be very hard // to do, therefore we hook all functions. // even though internal functions can only be referenced from external ones, it is not enough to hook // only those. the reason for that is that global/static instances might refer to internal functions // by function-pointer, address, etc., so internal functions must also be hooked. { telemetry::Scope patchingFunctionsScope("Patching functions"); // the processes are all halted. fetch instruction pointers from all their threads. typedef types::vector PerProcessThreadIPs; types::vector processThreadIPs; processThreadIPs.reserve(processCount); for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; processThreadIPs.emplace_back(EnumerateInstructionPointers(data.liveProcess->GetProcessId())); } uint32_t functionsPatchedCount = 0u; size_t functionsCount = 0u; // we deliberately do not hook functions in lib compilands because they cannot have changed, per definition. // they are part of a static library that won't be recompiled during a Live++ session. for (auto it = patch_compilandDB->compilands.begin(); it != patch_compilandDB->compilands.end(); ++it) { const symbols::ObjPath& objPath = it->first; LC_LOG_DEV("Patching functions for file %s", objPath.c_str()); LC_LOG_INDENT_DEV; const coff::CoffDB* coffDb = m_coffCache->Lookup(objPath); if (!coffDb) { LC_ERROR_USER("Could not find COFF database for file %s", objPath.c_str()); continue; } const std::wstring& wideObjPath = string::ToWideString(objPath); const symbols::Compiland* compiland = it->second; const uint32_t compilandUniqueId = symbols::GetCompilandId(compiland, wideObjPath.c_str(), modifiedOrNewObjFiles); for (size_t i = 0u; i < coffDb->symbols.size(); ++i) { const coff::Symbol* symbol = coffDb->symbols[i]; if (!coff::IsFunctionSymbol(symbol->type)) { continue; } ++functionsCount; const ImmutableString& functionName = symbols::TransformAnonymousNamespacePattern(coff::GetSymbolName(coffDb, symbol), compilandUniqueId); if (symbols::IsExceptionRelatedSymbol(functionName)) { LC_LOG_DEV("Ignoring exception-related function %s", functionName.c_str()); continue; } const symbols::Symbol* patchSymbol = symbols::FindSymbolByName(patch_symbolDB, functionName); if (!patchSymbol) { LC_WARNING_DEV("Cannot find function %s in patch, possibly stripped by linker", functionName.c_str()); continue; } const ModuleCache::FindSymbolData& originalData = m_moduleCache->FindSymbolByName(token, functionName); if (!originalData.symbol) { LC_LOG_DEV("Ignoring new function %s", functionName.c_str()); continue; } // if the original function to be patched did not come from a compiland, it cannot possibly have changed and // therefore can be ignored. const symbols::Contribution* originalContribution = symbols::FindContributionByRVA(originalData.data->contributionDb, originalData.symbol->rva); if (originalContribution) { const ImmutableString& compilandName = symbols::GetContributionCompilandName(originalData.data->contributionDb, originalContribution); const symbols::Compiland* originalCompiland = symbols::FindCompiland(originalData.data->compilandDb, compilandName); if (!originalCompiland) { LC_LOG_DEV("Ignoring function %s originally contributed from lib compiland %s", functionName.c_str(), compilandName.c_str()); continue; } } const size_t moduleProcessCount = originalData.data->processes.size(); for (size_t p = 0u; p < moduleProcessCount; ++p) { ++functionsPatchedCount; const ModuleCache::ProcessData& hookProcessData = originalData.data->processes[p]; const Process::Id pid = hookProcessData.processId; void* moduleBase = hookProcessData.moduleBase; Process::Handle processHandle = hookProcessData.processHandle; char* originalAddress = pointer::Offset(moduleBase, originalData.symbol->rva); char* patchAddress = pointer::Offset(loadedPatches[p], patchSymbol->rva); types::unordered_set& patchedAddresses = m_patchedAddressesPerProcess[pid]; const functions::Record& record = functions::PatchFunction(originalAddress, patchAddress, originalData.symbol->rva, patchSymbol->rva, originalData.data->thunkDb, originalContribution, processHandle, moduleBase, originalData.data->index, patchedAddresses, processThreadIPs[p], pid, functionName.c_str()); // BEGIN EPIC MOD if (!forceBackwards && functions::IsValidRecord(record)) { compiledModulePatch->RegisterFunctionPatch(record); } // END EPIC MOD } } } } // resume the main processes again for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; Process::Resume(data.liveProcess->GetProcessHandle()); } // BEGIN EPIC MOD }; // END EPIC MOD // BEGIN EPIC MOD if (!enableReinstancingFlow) { patchFunctions(enableReinstancingFlow); } // END EPIC MOD { // post-patch hooks must be called on the current executable because the hooks want to use the newest memory layout of // data structures. therefore we do not ignore any executables in our search. if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { const ModuleCache::FindHookData& hookData = m_moduleCache->FindHooksInSectionBackwards(ModuleCache::SEARCH_ALL_MODULES, ImmutableString(LPP_POSTPATCH_SECTION)); if ((hookData.firstRva != 0u) && (hookData.lastRva != 0u)) { const size_t count = hookData.data->processes.size(); for (size_t p = 0u; p < count; ++p) { const Process::Id pid = hookData.data->processes[p].processId; void* moduleBase = hookData.data->processes[p].moduleBase; const DuplexPipe* pipe = hookData.data->processes[p].pipe; LC_LOG_USER("Calling post-patch hooks (PID: %d)", +pid); pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::POSTPATCH, moduleBase, hookData), nullptr, 0u); } compiledModulePatch->RegisterPostPatchHooks(hookData.data->index, hookData.firstRva, hookData.lastRva); } } } // BEGIN EPIC MOD // pulse the sync point in all processes if (enableReinstancingFlow) { if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; data.liveProcess->GetPipe()->SendCommandAndWaitForAck(commands::TriggerReload{}, nullptr, 0u); } } // Re-patch everything so that things are patched as normally expected patchRelocationsPreEntryPoint(false); patchRelocations(false); patchFunctions(false); } // END EPIC MOD // leave sync point in all processes if (updateType != LiveModule::UpdateType::NO_CLIENT_COMMUNICATION) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; data.liveProcess->GetPipe()->SendCommandAndWaitForAck(commands::LeaveSyncPoint {}, nullptr, 0u); } } // clear the set for the next update m_modifiedFiles.clear(); m_compiledCompilands.clear(); LC_SUCCESS_USER("Patch creation for module %S successful (%.3fs)", m_moduleName.c_str(), updateScope.ReadSeconds()); // log all processes that were patched in case we have more than one if (processCount > 1u) { for (size_t p = 0u; p < processCount; ++p) { const PerProcessData& data = processData[p]; LC_SUCCESS_USER("Patched process %S (PID: %d)", data.modulePath.c_str(), data.liveProcess->GetProcessId()); } } CallCompileSuccessHooks(m_moduleCache, updateType); return ErrorType::SUCCESS; } bool LiveModule::InstallCompiledPatches(LiveProcess* liveProcess, void* originalModuleBase) { if (!appSettings::g_installCompiledPatchesMultiProcess->GetValue()) { // don't install any patches return true; } if (m_compiledModulePatches.size() == 0u) { // nothing to install return true; } LC_LOG_DEV("LiveModule InstallCompiledPatches: %S", m_moduleName.c_str()); telemetry::Scope wholeScope("Installing patches"); Process::Handle processHandle = liveProcess->GetProcessHandle(); const Process::Id processId = liveProcess->GetProcessId(); const DuplexPipe* pipe = liveProcess->GetPipe(); // BEGIN EPIC MOD #if LC_64_BIT // free our page reservations in the +-2GB range of the main module liveProcess->FreeVirtualMemoryPages(originalModuleBase); #endif // END EPIC MOD for (auto modulePatch : m_compiledModulePatches) { const std::wstring& originalExePath = modulePatch->GetExePath(); LC_LOG_USER("Installing patch %S (PID: %d)", originalExePath.c_str(), +processId); // this image needs to be copied because it is loaded already. // create a new name based on the process ID, which must be unique. std::wstring exePath = originalExePath; { exePath = string::Replace(exePath, L".exe", std::wstring(L"_PID_") + std::to_wstring(+processId) + L".exe"); Filesystem::Copy(originalExePath.c_str(), exePath.c_str()); } const size_t token = modulePatch->GetToken(); const ModulePatch::Data& patchData = modulePatch->GetData(); // note that the image on disk we are trying to load had its entry point patched already when it was // loaded for the first time, so we don't have to do that at this point. executable::Image* image = executable::OpenImage(exePath.c_str(), Filesystem::OpenMode::READ); if (!image) { LC_ERROR_USER("Cannot load patch executable %S", exePath.c_str()); return false; } const uint32_t entryPointRva = executable::GetEntryPointRva(image); const uint32_t patchImageSize = executable::GetSize(image); executable::CloseImage(image); // the patch's entry point is disabled. tell the processes to load the patch LC_LOG_DEV("Loading code into process"); types::vector loadedPatches; { commands::LoadPatch cmd = {}; wcscpy_s(cmd.path, exePath.c_str()); #if LC_64_BIT // before doing anything further, we need to ensure that the patch can be loaded into the address space at a suitable location. // for 64-bit applications, this means that the patch must lie in a +/-2GB range of the main executable. // 32-bit executables can reach the whole address space due to modulo addressing. LC_LOG_DEV("Scanning memory for suitable patch location (PID: %d)", +processId); // disable the main process before scanning its memory to ensure that no operation allocates/frees virtual memory concurrently Process::Suspend(processHandle); const executable::PreferredBase preferredImageBase = FindPreferredImageBase(patchImageSize, processId, processHandle, originalModuleBase); // rebase the patch image to its preferred base address executable::Image* rebasedImage = executable::OpenImage(exePath.c_str(), Filesystem::OpenMode::READ_WRITE); LC_LOG_DEV("Rebasing patch executable to image base 0x%" PRIX64 " (PID: %d)", preferredImageBase, +processId); executable::RebaseImage(rebasedImage, preferredImageBase); executable::CloseImage(rebasedImage); // resume the main process so that it can respond to our command. if we're *really* unlucky, a concurrent operation // will allocate virtual memory at the patch's preferred image base, possibly rendering the patch unusable because // it cannot be loaded. // the chances of that happening are *very* rare though, and we can always load the next patch then. Process::Resume(processHandle); #endif pipe->SendCommandAndWaitForAck(cmd, nullptr, 0u); // receive command with patch info CommandMap commandMap; commandMap.RegisterAction(); commandMap.HandleCommands(pipe, &loadedPatches); } void* moduleBase = loadedPatches[0]; const bool patchesLoadedSuccessfully = CheckPatchAddressValidity(originalModuleBase, moduleBase, processHandle); if (!patchesLoadedSuccessfully) { LC_ERROR_USER("Patch could not be activated."); pipe->SendCommandAndWaitForAck(commands::UnloadPatch { static_cast(moduleBase) }, nullptr, 0u); return false; } // enter sync point pipe->SendCommandAndWaitForAck(commands::EnterSyncPoint {}, nullptr, 0u); // store the new databases into the module cache m_moduleCache->RegisterProcess(token, liveProcess, moduleBase); types::vector processModuleBases = m_moduleCache->GatherModuleBases(processId); LC_LOG_DEV("Calling pre-patch hooks"); { void* hookModule = processModuleBases[patchData.prePatchHookModuleIndex]; pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::PREPATCH, hookModule, patchData.firstPrePatchHook, patchData.lastPrePatchHook), nullptr, 0u); } LC_LOG_DEV("Patching relocations before calling entry point"); for (auto record : patchData.preEntryPointRelocations) { relocations::PatchRelocation(record, processHandle, &processModuleBases[0], moduleBase); } LC_LOG_DEV("Patching dynamic initializers"); for (auto rva : patchData.patchedInitializers) { LC_LOG_DEV("Patching dynamic initializer symbol at RVA 0x%X (PID: %d)", rva, +processId); void* initializerAddress = pointer::Offset(moduleBase, rva); Process::WriteProcessMemory(processHandle, initializerAddress, nullptr); } LC_LOG_DEV("Patching security cookie"); PatchSecurityCookie(originalModuleBase, moduleBase, patchData.originalCookieRva, patchData.patchCookieRva, processHandle); if (appSettings::g_ue4EnableNatVisSupport->GetValue()) { LC_LOG_DEV("Patching symbols for UE4 NatVis visualizers"); PatchUE4NatVisSymbols(originalModuleBase, moduleBase, patchData.originalUe4NameTableRva, patchData.patchUe4NameTableRva, processHandle); PatchUE4NatVisSymbols(originalModuleBase, moduleBase, patchData.originalUe4ObjectArrayRva, patchData.patchUe4ObjectArrayRva, processHandle); } // now that relocations are done, it is safe to call the entry point. // restore the original entry point and tell the process to call it. { // disable user entry point DllMain (if it exists) if (patchData.dllMainRva != 0u) { PatchDllMain(moduleBase, patchData.dllMainRva, processHandle); } LC_LOG_DEV("Restoring original entry point"); // restore entry point in all processes. // the module patch stores the original entry point code from the original image, before it had // its entry point patched. ExecutablePatcher executablePatcher(patchData.entryPointCode); executablePatcher.RestoreEntryPoint(processHandle, moduleBase, entryPointRva); LC_LOG_DEV("Calling original entry point"); pipe->SendCommandAndWaitForAck(commands::CallEntryPoint { moduleBase, entryPointRva }, nullptr, 0u); executablePatcher.DisableEntryPoint(processHandle, moduleBase, entryPointRva); } LC_LOG_DEV("Patching relocations after calling entry point"); for (auto record : patchData.postEntryPointRelocations) { relocations::PatchRelocation(record, processHandle, &processModuleBases[0], moduleBase); } // suspend the main processes before patching functions, because they might not use synchronization points. Process::Suspend(processHandle); // patch all functions types::unordered_set& patchedAddresses = m_patchedAddressesPerProcess[processId]; types::vector threadIPs = EnumerateInstructionPointers(processId); LC_LOG_DEV("Patching functions"); for (auto record : patchData.functionPatches) { functions::PatchFunction(record, processHandle, &processModuleBases[0], moduleBase, patchedAddresses, threadIPs); } LC_LOG_DEV("Patching public functions in lib compilands"); for (auto record : patchData.libraryFunctionPatches) { functions::PatchLibraryFunction(record, processHandle, &processModuleBases[0], moduleBase); } // resume the main processes again Process::Resume(processHandle); LC_LOG_DEV("Calling post-patch hooks"); { void* hookModule = processModuleBases[patchData.postPatchHookModuleIndex]; pipe->SendCommandAndWaitForAck(MakeCallHooksCommand(hook::Type::POSTPATCH, hookModule, patchData.firstPostPatchHook, patchData.lastPostPatchHook), nullptr, 0u); } // leave sync point pipe->SendCommandAndWaitForAck(commands::LeaveSyncPoint {}, nullptr, 0u); } // BEGIN EPIC MOD #if LC_64_BIT // immediately reserve pages in the +-2GB range of the main module again liveProcess->ReserveVirtualMemoryPages(originalModuleBase); #endif // END EPIC MOD LC_SUCCESS_USER("Successfully installed %zu patches (%.3fs)", m_compiledModulePatches.size(), wholeScope.ReadSeconds()); return true; } const std::wstring& LiveModule::GetModuleName(void) const { return m_moduleName; } const executable::Header& LiveModule::GetImageHeader(void) const { return m_imageHeader; } const symbols::CompilandDB* LiveModule::GetCompilandDatabase(void) const { return m_compilandDB; } const symbols::LinkerDB* LiveModule::GetLinkerDatabase(void) const { return m_linkerDB; } bool LiveModule::HasInstalledPatches(void) const { return (m_patchCounter != 0u); } bool LiveModule::actions::LoadPatchInfo::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t) { types::vector* loadedPatches = static_cast*>(context); loadedPatches->emplace_back(command->module); pipe->SendAck(); return false; } void LiveModule::UpdateDirectoryCache(const ImmutableString& path, symbols::Dependency* dependency, DirectoryCache* cache) { const std::wstring directoryOnly = Filesystem::GetDirectory(string::ToWideString(path).c_str()).GetString(); dependency->parentDirectory = cache->AddDirectory(directoryOnly); } void LiveModule::OnCompiledFile(const symbols::ObjPath& objPath, symbols::Compiland* compiland, const CompileResult& compileResult, double compileTime, bool forceAmalgamationPartsLinkage) { if (compileResult.exitCode == 0u) { if (compileResult.wasCompiled) { LC_SUCCESS_USER("Successfully compiled %s (%.3fs)", objPath.c_str(), compileTime); } // AMALGAMATION // files which are part of an amalgamation only need to be linked in when initially splitting the unity file. // this happens the first time some .cpp file is touched during a session. // even though up-to-date .cpp files don't need to be recompiled, they need to be linked in order to // handle inlining across translation units. if (compileResult.wasCompiled || forceAmalgamationPartsLinkage) { // compilation was successful, store this compiland for linking later m_compiledCompilands.emplace(objPath, compiland); symbols::MarkCompilandAsRecompiled(compiland); } // remove this file from the set of modified files. it need not be compiled in the next run, unless // it has been modified again. if so, it will be picked up automatically by checking the modification time. m_modifiedFiles.erase(objPath); } else { // compilation failed. remove the compiland from the set of previously compiled compilands, because it // might have compiled successfully in an earlier call to Update(). // note that we do not remove this file from the set of modified files, so it is automatically compiled again // upon the next call to Update(). m_compiledCompilands.erase(objPath); symbols::ClearCompilandAsRecompiled(compiland); LC_ERROR_USER("Failed to compile %s (%.3fs) (Exit code: 0x%X)", objPath.c_str(), compileTime, compileResult.exitCode); } } // BEGIN EPIC MOD bool LiveModule::IsModifiedSource(const wchar_t* sourceFile) const { if (m_moduleCache == nullptr || m_moduleCache->GetSize() == 0) { return false; } Filesystem::PathAttributes attr = Filesystem::GetAttributes(sourceFile); if (!Filesystem::DoesExist(attr)) { return false; } for (size_t i = 0; i < m_moduleCache->GetSize(); ++i) { const ModuleCache::Data& data = m_moduleCache->GetEntry(i); if (data.lastModificationTime > attr.lastModificationTime) { return false; } } return true; } // END EPIC MOD #endif