You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Allows fast iteration of C++ changes without restarting the application. To use, select the "Live Coding (Experimental)" mode from the drop down menu next to the editor's compile button, or type "LiveCoding" into the console for a monolithic build. Press Ctrl+Alt+F11 to find changes and compile.
Changes vs standalone Live++ version:
* UBT is used to execute builds. This allows standard UE4 adaptive unity mode, allows us to reuse object files when we do regular builds, supports using any build executor allowed by UBT (XGE, SNDBS, etc..).
* Adding new source files is supported.
* Custom visualizer for FNames is supported via a weakly linked symbol in a static library (Engine/Extras/NatvisHelpers).
* Settings are exposed in the editor's project settings dialog.
* Standalone application has been rewritten as a Slate app ("LiveCodingConsole"). There is an additional option to start the program as hidden, where it will not be visible until Ctrl+Alt+F11 is hit. Similarly, closing the window will hide it instead of closing the application.
* Does not require a standalone licensed version of Live++.
Known issues:
* Does not currently support class layout changes / object reinstancing
#rb none
#fyi Marc.Audy, Stefan.Boberg, Nick.Penwarden
#jira
[CL 5304722 by Ben Marsh in 4.22 branch]
437 lines
16 KiB
C++
437 lines
16 KiB
C++
// Copyright 2011-2019 Molecular Matters GmbH, all rights reserved.
|
|
|
|
#include "LC_FunctionPatcher.h"
|
|
#include "LC_Patch.h"
|
|
#include "LC_Disassembler.h"
|
|
#include "LC_NameMangling.h"
|
|
#include "LC_AppSettings.h"
|
|
#include "LC_Logging.h"
|
|
|
|
|
|
namespace
|
|
{
|
|
struct PatchTechnique
|
|
{
|
|
enum Enum
|
|
{
|
|
DIRECT_RELATIVE_JUMP,
|
|
HOTPATCH_INDIRECTION
|
|
};
|
|
};
|
|
|
|
|
|
static const uint8_t INT3_PADDING[16u] = { 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC };
|
|
|
|
|
|
// helper function that checks whether a certain number of bytes in memory are available for patching
|
|
template <size_t N>
|
|
static inline bool AreBytesAvailableForPatching(process::Handle processHandle, const void* address)
|
|
{
|
|
// available bytes are either 0xCC (int 3) or 0x0 (page padding)
|
|
uint8_t memory[N] = {};
|
|
process::ReadProcessMemory(processHandle, address, memory, N);
|
|
|
|
for (size_t i = 0u; i < N; ++i)
|
|
{
|
|
if ((memory[i] != 0xCC) && (memory[i] != 0x0))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
functions::Record functions::PatchFunction
|
|
(
|
|
char* originalAddress,
|
|
char* patchAddress,
|
|
uint32_t functionRva,
|
|
uint32_t patchFunctionRva,
|
|
const symbols::ThunkDB* thunkDb,
|
|
const symbols::Contribution* contribution,
|
|
process::Handle processHandle,
|
|
void* moduleBase,
|
|
uint16_t moduleIndex,
|
|
types::unordered_set<const void*>& patchedAddresses,
|
|
const types::vector<const void*>& threadIPs,
|
|
|
|
// debug only
|
|
unsigned int processId,
|
|
const char* functionName
|
|
)
|
|
{
|
|
Record record = { thunkDb, functionRva, patchFunctionRva, moduleIndex, 0u };
|
|
|
|
// there are three ways to patch the old function so that it redirects to the new one:
|
|
// 1) patch the incremental linking table to point to the new function. this is easiest because it
|
|
// doesn't need any disassembling of instructions or instruction pointer checks.
|
|
// 2) install a relative jump to the new function at the start of the old function.
|
|
// this relative jump needs 5 bytes and can only be installed if no thread of this process is currently
|
|
// reading instructions from the location in question.
|
|
// 3) install a relative jump to the new function 5 bytes in front of the old function.
|
|
// install a short jump to this relative jump at the start of the old function.
|
|
// this needs support from the compiler (/hotpatch) to ensure that the first instruction of a
|
|
// function is always at least 2 bytes long, and support from the linker (/FUNCTIONPADMIN) to ensure
|
|
// that there are 5 unused bytes in front of each function.
|
|
unsigned int patchTechniqueUsed = PatchTechnique::HOTPATCH_INDIRECTION;
|
|
size_t wholeInstructionSize = 0u;
|
|
|
|
// first check whether we can find an incremental linking thunk for this function.
|
|
bool installedPatchToILT = false;
|
|
const types::vector<uint32_t>& thunkTableEntries = symbols::FindThunkTableEntriesByRVA(thunkDb, functionRva);
|
|
if (thunkTableEntries.size() != 0u)
|
|
{
|
|
// patch the ILTs directly, but keep installing patches into the real function too.
|
|
// this acts as a safety net, should any relocation or any function ever point to the real function
|
|
// instead of the ILT.
|
|
for (auto thunkRva : thunkTableEntries)
|
|
{
|
|
LC_LOG_DEV("Patching ILT 0x%X of function %s at 0x%p (0x%X) (PID: %d)", thunkRva, functionName, moduleBase, functionRva, processId);
|
|
|
|
void* incrementalLinkingThunk = pointer::Offset<char*>(moduleBase, thunkRva);
|
|
patch::InstallRelativeNearJump(processHandle, incrementalLinkingThunk, patchAddress);
|
|
}
|
|
|
|
installedPatchToILT = true;
|
|
}
|
|
|
|
// second check whether the function is at least 5 bytes long to consider it for direct patching using
|
|
// a single relative jump.
|
|
if (contribution && contribution->size >= 5u)
|
|
{
|
|
// the function seems to be long enough.
|
|
// disassemble the first instructions to see how many bytes we can patch.
|
|
while (wholeInstructionSize < 5u)
|
|
{
|
|
const size_t instructionSize = disassembler::FindInstructionSize(processHandle, originalAddress + wholeInstructionSize);
|
|
if (instructionSize == 0u)
|
|
{
|
|
// dump raw code in case it could not be decoded
|
|
LC_ERROR_DEV("Failed to disassemble code for function %s at 0x%p (0x%X) (PID: %d)", functionName, moduleBase, functionRva, processId);
|
|
process::DumpMemory(processHandle, originalAddress, contribution->size);
|
|
break;
|
|
}
|
|
|
|
wholeInstructionSize += instructionSize;
|
|
}
|
|
|
|
// did we disassemble at least 5 bytes worth of instructions?
|
|
if (wholeInstructionSize >= 5u)
|
|
{
|
|
record.directJumpInstructionSize = static_cast<uint8_t>(wholeInstructionSize);
|
|
|
|
// yes, now check if a thread is currently reading from memory at the location where we
|
|
// want to install a relative jump.
|
|
bool anyThreadInside = false;
|
|
const size_t threadCount = threadIPs.size();
|
|
for (size_t t = 0u; t < threadCount; ++t)
|
|
{
|
|
const void* instructionPointer = threadIPs[t];
|
|
if ((instructionPointer >= originalAddress) && (instructionPointer < originalAddress + wholeInstructionSize))
|
|
{
|
|
anyThreadInside = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!anyThreadInside)
|
|
{
|
|
// no thread currently reads from there, install a direct relative jump
|
|
patchTechniqueUsed = PatchTechnique::DIRECT_RELATIVE_JUMP;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now install a patch using the selected technique
|
|
if (patchTechniqueUsed == PatchTechnique::DIRECT_RELATIVE_JUMP)
|
|
{
|
|
LC_LOG_DEV("Patching function %s directly at 0x%p (0x%X) (PID: %d)", functionName, moduleBase, functionRva, processId);
|
|
|
|
patch::InstallRelativeNearJump(processHandle, originalAddress, patchAddress);
|
|
|
|
// are there remaining bytes straddling the last instruction?
|
|
if (wholeInstructionSize > 5u)
|
|
{
|
|
// yes, overwrite those with int 3
|
|
process::WriteProcessMemory(processHandle, originalAddress + 5u, INT3_PADDING, wholeInstructionSize - 5u);
|
|
}
|
|
}
|
|
else if (patchTechniqueUsed == PatchTechnique::HOTPATCH_INDIRECTION)
|
|
{
|
|
const size_t instructionSize = disassembler::FindInstructionSize(processHandle, originalAddress);
|
|
if (instructionSize >= 2u)
|
|
{
|
|
// we need to go via an indirection, and install the relative jump to the patch
|
|
// address right before the original function. this means we need 5 bytes to be
|
|
// available in front of the function.
|
|
const bool isAvailable = AreBytesAvailableForPatching<5u>(processHandle, originalAddress - 5u);
|
|
const bool installedPatch = (patchedAddresses.find(originalAddress - 5u) != patchedAddresses.end());
|
|
if (isAvailable || installedPatch)
|
|
{
|
|
LC_LOG_DEV("Hot-patching function %s at 0x%p (0x%X) (PID: %d)", functionName, moduleBase, functionRva, processId);
|
|
|
|
// it is safe to install the relative jump right in front of the function
|
|
patch::InstallRelativeNearJump(processHandle, originalAddress - 5u, patchAddress);
|
|
|
|
// note that in very, very rare cases, the memory region in front of the function might not
|
|
// be executable pages. this can only happen for the function right at the start of the
|
|
// code segment, but it can happen.
|
|
process::MakePagesExecutable(processHandle, originalAddress - 5u, 5u);
|
|
|
|
// jump to the relative jump we just installed using a short jump, using 2 bytes.
|
|
// this memory region must always be executable already.
|
|
patch::InstallRelativeShortJump(processHandle, originalAddress, originalAddress - 5u);
|
|
|
|
patchedAddresses.insert(originalAddress - 5u);
|
|
}
|
|
else
|
|
{
|
|
// there is not enough space.
|
|
// only emit a warning if the ILT also couldn't be patched.
|
|
if (!installedPatchToILT)
|
|
{
|
|
if (appSettings::g_showUndecoratedNames->GetValue())
|
|
{
|
|
LC_WARNING_USER("Not enough space near function '%s' at 0x%X to install patch (PID: %d). Changes to this function will not be observable.", nameMangling::UndecorateSymbol(functionName, 0u).c_str(), originalAddress, processId);
|
|
}
|
|
else
|
|
{
|
|
LC_WARNING_USER("Not enough space near function '%s' at 0x%X to install patch (PID: %d). Changes to this function will not be observable.", functionName, originalAddress, processId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the instruction is too short.
|
|
// only emit a warning if the ILT also couldn't be patched.
|
|
if (!installedPatchToILT)
|
|
{
|
|
if (appSettings::g_showUndecoratedNames->GetValue())
|
|
{
|
|
LC_WARNING_USER("Instruction in function '%s' at 0x%X is too short to install patch (PID: %d). Changes to this function will not be observable.", nameMangling::UndecorateSymbol(functionName, 0u).c_str(), originalAddress, processId);
|
|
}
|
|
else
|
|
{
|
|
LC_WARNING_USER("Instruction in function '%s' at 0x%X is too short to install patch (PID: %d). Changes to this function will not be observable.", functionName, originalAddress, processId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
|
|
void functions::PatchFunction
|
|
(
|
|
const Record& record,
|
|
process::Handle processHandle,
|
|
void* processModuleBases[],
|
|
void* newModuleBase,
|
|
types::unordered_set<const void*>& patchedAddresses,
|
|
const types::vector<const void*>& threadIPs
|
|
)
|
|
{
|
|
void* originalModuleBase = processModuleBases[record.patchIndex];
|
|
if (!originalModuleBase)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char* originalAddress = pointer::Offset<char*>(originalModuleBase, record.functionRva);
|
|
char* patchAddress = pointer::Offset<char*>(newModuleBase, record.patchFunctionRva);
|
|
|
|
unsigned int patchTechniqueUsed = PatchTechnique::HOTPATCH_INDIRECTION;
|
|
size_t wholeInstructionSize = 0u;
|
|
|
|
// first check whether we can find an incremental linking thunk for this function.
|
|
bool installedPatchToILT = false;
|
|
const types::vector<uint32_t>& thunkTableEntries = symbols::FindThunkTableEntriesByRVA(record.thunkDb, record.functionRva);
|
|
if (thunkTableEntries.size() != 0u)
|
|
{
|
|
// patch the ILTs directly, but keep installing patches into the real function too.
|
|
// this acts as a safety net, should any relocation or any function ever point to the real function
|
|
// instead of the ILT.
|
|
for (auto thunkRva : thunkTableEntries)
|
|
{
|
|
void* incrementalLinkingThunk = pointer::Offset<char*>(originalModuleBase, thunkRva);
|
|
patch::InstallRelativeNearJump(processHandle, incrementalLinkingThunk, patchAddress);
|
|
}
|
|
|
|
installedPatchToILT = true;
|
|
}
|
|
|
|
// second check whether the function is at least 5 bytes long to consider it for direct patching using
|
|
// a single relative jump.
|
|
if (record.directJumpInstructionSize >= 5u)
|
|
{
|
|
// check if a thread is currently reading from memory at the location where we
|
|
// want to install a relative jump.
|
|
bool anyThreadInside = false;
|
|
const size_t threadCount = threadIPs.size();
|
|
for (size_t t = 0u; t < threadCount; ++t)
|
|
{
|
|
const void* instructionPointer = threadIPs[t];
|
|
if ((instructionPointer >= originalAddress) && (instructionPointer < originalAddress + wholeInstructionSize))
|
|
{
|
|
anyThreadInside = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!anyThreadInside)
|
|
{
|
|
// no thread currently reads from there, install a direct relative jump
|
|
patchTechniqueUsed = PatchTechnique::DIRECT_RELATIVE_JUMP;
|
|
}
|
|
}
|
|
|
|
// now install a patch using the selected technique
|
|
if (patchTechniqueUsed == PatchTechnique::DIRECT_RELATIVE_JUMP)
|
|
{
|
|
patch::InstallRelativeNearJump(processHandle, originalAddress, patchAddress);
|
|
|
|
// are there remaining bytes straddling the last instruction?
|
|
if (record.directJumpInstructionSize > 5u)
|
|
{
|
|
// yes, overwrite those with int 3
|
|
process::WriteProcessMemory(processHandle, originalAddress + 5u, INT3_PADDING, record.directJumpInstructionSize - 5u);
|
|
}
|
|
}
|
|
else if (patchTechniqueUsed == PatchTechnique::HOTPATCH_INDIRECTION)
|
|
{
|
|
const size_t instructionSize = disassembler::FindInstructionSize(processHandle, originalAddress);
|
|
if (instructionSize >= 2u)
|
|
{
|
|
// we need to go via an indirection, and install the relative jump to the patch
|
|
// address right before the original function. this means we need 5 bytes to be
|
|
// available in front of the function.
|
|
const bool isAvailable = AreBytesAvailableForPatching<5u>(processHandle, originalAddress - 5u);
|
|
const bool installedPatch = (patchedAddresses.find(originalAddress - 5u) != patchedAddresses.end());
|
|
if (isAvailable || installedPatch)
|
|
{
|
|
// it is safe to install the relative jump right in front of the function
|
|
patch::InstallRelativeNearJump(processHandle, originalAddress - 5u, patchAddress);
|
|
|
|
// note that in very, very rare cases, the memory region in front of the function might not
|
|
// be executable pages. this can only happen for the function right at the start of the
|
|
// code segment, but it can happen.
|
|
process::MakePagesExecutable(processHandle, originalAddress - 5u, 5u);
|
|
|
|
// jump to the relative jump we just installed using a short jump, using 2 bytes.
|
|
// this memory region must always be executable already.
|
|
patch::InstallRelativeShortJump(processHandle, originalAddress, originalAddress - 5u);
|
|
|
|
patchedAddresses.insert(originalAddress - 5u);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
functions::LibraryRecord functions::PatchLibraryFunction
|
|
(
|
|
char* srcAddress,
|
|
char* destAddress,
|
|
uint32_t srcRva,
|
|
uint32_t destRva,
|
|
const symbols::Contribution* contribution,
|
|
process::Handle processHandle,
|
|
uint16_t moduleIndex
|
|
)
|
|
{
|
|
LibraryRecord record = { srcRva, destRva, moduleIndex, 0u };
|
|
|
|
// patching of public functions that were pulled in from libraries is a bit different because those libraries
|
|
// were probably not built with the /hotpatch and /FUNCTIONPADMIN switches.
|
|
// therefore, we need to install a relative jump to the original function directly, without any indirection.
|
|
// such a jump needs 5 bytes but is actually easier to install in this case due to the following constraints:
|
|
// - if the function is shorter than 5 bytes it cannot contain a jump or a relocation to another symbol, because
|
|
// both would need at least (1 + 4) bytes. the function therefore cannot access any data or other function, and
|
|
// hence is of no relevance to us.
|
|
// - the instruction pointer cannot be in any of these functions currently, because no code calling these functions
|
|
// could have possibly been run at this point (the process is still suspended).
|
|
// therefore, we analyze the instructions in the function until we have found at least 5 bytes.
|
|
// these 5 bytes are then patched with a relative jump to the original function, and the remaining bytes (if any)
|
|
// are patched with int 3.
|
|
size_t wholeInstructionSize = 0u;
|
|
while (wholeInstructionSize < 5u)
|
|
{
|
|
const size_t instructionSize = disassembler::FindInstructionSize(processHandle, srcAddress + wholeInstructionSize);
|
|
if (instructionSize == 0u)
|
|
{
|
|
// dump raw code in case it could not be decoded
|
|
process::DumpMemory(processHandle, srcAddress, contribution->size);
|
|
break;
|
|
}
|
|
|
|
wholeInstructionSize += instructionSize;
|
|
}
|
|
|
|
// did we disassemble at least 5 bytes worth of instructions?
|
|
if (wholeInstructionSize >= 5u)
|
|
{
|
|
record.wholeInstructionSize = static_cast<uint16_t>(wholeInstructionSize);
|
|
|
|
// install a relative jump to the destination right here
|
|
patch::InstallRelativeNearJump(processHandle, srcAddress, destAddress);
|
|
|
|
// are there remaining bytes straddling the last instruction?
|
|
if (wholeInstructionSize > 5u)
|
|
{
|
|
// yes, overwrite those with int 3
|
|
process::WriteProcessMemory(processHandle, srcAddress + 5u, INT3_PADDING, wholeInstructionSize - 5u);
|
|
}
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
|
|
void functions::PatchLibraryFunction
|
|
(
|
|
const LibraryRecord& record,
|
|
process::Handle processHandle,
|
|
void* processModuleBases[],
|
|
void* newModuleBase
|
|
)
|
|
{
|
|
void* originalModuleBase = processModuleBases[record.patchIndex];
|
|
if (!originalModuleBase)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char* srcAddress = pointer::Offset<char*>(newModuleBase, record.srcRva);
|
|
char* destAddress = pointer::Offset<char*>(originalModuleBase, record.destRva);
|
|
|
|
if (record.wholeInstructionSize >= 5u)
|
|
{
|
|
// install a relative jump to the destination right here
|
|
patch::InstallRelativeNearJump(processHandle, srcAddress, destAddress);
|
|
|
|
// are there remaining bytes straddling the last instruction?
|
|
if (record.wholeInstructionSize > 5u)
|
|
{
|
|
// yes, overwrite those with int 3
|
|
process::WriteProcessMemory(processHandle, srcAddress + 5u, INT3_PADDING, record.wholeInstructionSize - 5u);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool functions::IsValidRecord(const Record& record)
|
|
{
|
|
return (record.thunkDb != nullptr);
|
|
}
|
|
|
|
|
|
bool functions::IsValidRecord(const LibraryRecord& record)
|
|
{
|
|
return (record.wholeInstructionSize != 0u);
|
|
}
|