diff options
author | sivadeilra <ardavis@microsoft.com> | 2025-06-24 09:22:38 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-06-24 09:22:38 -0700 |
commit | 26d318e4a9437f95b6a2e7abace5f2b867c88a3e (patch) | |
tree | 1b4432f9b26dd01065941f1b83bbc466a2ea01b6 /llvm/lib/CodeGen | |
parent | e175ecff936287823b5443d7b2d443fc6569f31f (diff) | |
download | llvm-26d318e4a9437f95b6a2e7abace5f2b867c88a3e.zip llvm-26d318e4a9437f95b6a2e7abace5f2b867c88a3e.tar.gz llvm-26d318e4a9437f95b6a2e7abace5f2b867c88a3e.tar.bz2 |
Add support for Windows Secure Hot-Patching (#138972)
This PR adds some of the support needed for Windows hot-patching.
Windows implements a form of hot-patching. This allows patches to be
applied to Windows apps, drivers, and the kernel, without rebooting or
restarting any of these components. Hot-patching is a complex technology
and requires coordination between the OS, compilers, linkers, and
additional tools.
This PR adds support to Clang and LLVM for part of the hot-patching
process. It enables LLVM to generate the required code changes and to
generate CodeView symbols which identify hot-patched functions. The PR
provides new command-line arguments to Clang which allow developers to
identify the list of functions that need to be hot-patched. This PR also
allows LLVM to directly receive the list of functions to be modified, so
that language front-ends which have not yet been modified (such as Rust)
can still make use of hot-patching.
This PR:
* Adds a `MarkedForWindowsHotPatching` LLVM function attribute. This
attribute indicates that a function should be _hot-patched_. This
generates a new CodeView symbol, `S_HOTPATCHFUNC`, which identifies any
function that has been hot-patched. This attribute also causes accesses
to global variables to be indirected through a `_ref_*` global variable.
This allows hot-patched functions to access the correct version of a
global variable; the hot-patched code needs to access the variable in
the _original_ image, not the patch image.
* Adds a `AllowDirectAccessInHotPatchFunction` LLVM attribute. This
attribute may be placed on global variable declarations. It indicates
that the variable may be safely accessed without the `_ref_*`
indirection.
* Adds two Clang command-line parameters: `-fms-hotpatch-functions-file`
and `-fms-hotpatch-functions-list`. The `-file` flag may point to a text
file, which contains a list of functions to be hot-patched (one function
name per line). The `-list` flag simply directly identifies functions to
be patched, using a comma-separated list. These two command-line
parameters may also be combined; the final set of functions to be
hot-patched is the union of the two sets.
* Adds similar LLVM command-line parameters:
`--ms-hotpatch-functions-file` and `--ms-hotpatch-functions-list`.
* Adds integration tests for both LLVM and Clang.
* Adds support for dumping the new `S_HOTPATCHFUNC` CodeView symbol.
Although the flags are redundant between Clang and LLVM, this allows
additional languages (such as Rust) to take advantage of hot-patching
support before they have been modified to generate the required
attributes.
Credit to @dpaoliello, who wrote the original form of this patch.
Diffstat (limited to 'llvm/lib/CodeGen')
-rw-r--r-- | llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp | 24 | ||||
-rw-r--r-- | llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h | 2 | ||||
-rw-r--r-- | llvm/lib/CodeGen/CMakeLists.txt | 1 | ||||
-rw-r--r-- | llvm/lib/CodeGen/TargetPassConfig.cpp | 3 | ||||
-rw-r--r-- | llvm/lib/CodeGen/WindowsSecureHotPatching.cpp | 617 |
5 files changed, 647 insertions, 0 deletions
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp index ea57a8f..5e1b313 100644 --- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp @@ -669,6 +669,8 @@ void CodeViewDebug::endModule() { if (!Asm) return; + emitSecureHotPatchInformation(); + emitInlineeLinesSubsection(); // Emit per-function debug information. @@ -823,6 +825,28 @@ void CodeViewDebug::emitObjName() { endSymbolRecord(CompilerEnd); } +void CodeViewDebug::emitSecureHotPatchInformation() { + MCSymbol *hotPatchInfo = nullptr; + + for (const auto &F : MMI->getModule()->functions()) { + if (!F.isDeclarationForLinker() && + F.hasFnAttribute("marked_for_windows_hot_patching")) { + if (hotPatchInfo == nullptr) + hotPatchInfo = beginCVSubsection(DebugSubsectionKind::Symbols); + MCSymbol *HotPatchEnd = beginSymbolRecord(SymbolKind::S_HOTPATCHFUNC); + auto *SP = F.getSubprogram(); + OS.AddComment("Function"); + OS.emitInt32(getFuncIdForSubprogram(SP).getIndex()); + OS.AddComment("Name"); + emitNullTerminatedSymbolName(OS, F.getName()); + endSymbolRecord(HotPatchEnd); + } + } + + if (hotPatchInfo != nullptr) + endCVSubsection(hotPatchInfo); +} + namespace { struct Version { int Part[4]; diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h index 5f4f302..c862802 100644 --- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h +++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h @@ -337,6 +337,8 @@ private: void emitCompilerInformation(); + void emitSecureHotPatchInformation(); + void emitBuildInfo(); void emitInlineeLinesSubsection(); diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt index 5dd6413..f8f9bbb 100644 --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -250,6 +250,7 @@ add_llvm_component_library(LLVMCodeGen VirtRegMap.cpp WasmEHPrepare.cpp WindowScheduler.cpp + WindowsSecureHotPatching.cpp WinEHPrepare.cpp XRayInstrumentation.cpp ${GeneratedMLSources} diff --git a/llvm/lib/CodeGen/TargetPassConfig.cpp b/llvm/lib/CodeGen/TargetPassConfig.cpp index 4ae52b0..7d7c6e7 100644 --- a/llvm/lib/CodeGen/TargetPassConfig.cpp +++ b/llvm/lib/CodeGen/TargetPassConfig.cpp @@ -893,6 +893,9 @@ void TargetPassConfig::addIRPasses() { if (EnableGlobalMergeFunc) addPass(createGlobalMergeFuncPass()); + + if (TM->getTargetTriple().isOSWindows()) + addPass(createWindowsSecureHotPatchingPass()); } /// Turn exception handling constructs into something the code generators can diff --git a/llvm/lib/CodeGen/WindowsSecureHotPatching.cpp b/llvm/lib/CodeGen/WindowsSecureHotPatching.cpp new file mode 100644 index 0000000..6267207 --- /dev/null +++ b/llvm/lib/CodeGen/WindowsSecureHotPatching.cpp @@ -0,0 +1,617 @@ +//===------ WindowsHotPatch.cpp - Support for Windows hotpatching ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Provides support for the Windows "Secure Hot-Patching" feature. +// +// Windows contains technology, called "Secure Hot-Patching" (SHP), for securely +// applying hot-patches to a running system. Hot-patches may be applied to the +// kernel, kernel-mode components, device drivers, user-mode system services, +// etc. +// +// SHP relies on integration between many tools, including compiler, linker, +// hot-patch generation tools, and the Windows kernel. This file implements that +// part of the workflow needed in compilers / code generators. +// +// SHP is not intended for productivity scenarios such as Edit-and-Continue or +// interactive development. SHP is intended to minimize downtime during +// installation of Windows OS patches. +// +// In order to work with SHP, LLVM must do all of the following: +// +// * On some architectures (X86, AMD64), the function prolog must begin with +// hot-patchable instructions. This is handled by the MSVC `/hotpatch` option +// and the equivalent `-fms-hotpatch` function. This is necessary because we +// generally cannot anticipate which functions will need to be patched in the +// future. This option ensures that a function can be hot-patched in the +// future, but does not actually generate any hot-patch for it. +// +// * For a selected set of functions that are being hot-patched (which are +// identified using command-line options), LLVM must generate the +// `S_HOTPATCHFUNC` CodeView record (symbol). This record indicates that a +// function was compiled with hot-patching enabled. +// +// This implementation uses the `MarkedForWindowsHotPatching` attribute to +// annotate those functions that were marked for hot-patching by command-line +// parameters. The attribute may be specified by a language front-end by +// setting an attribute when a function is created in LLVM IR, or it may be +// set by passing LLVM arguments. +// +// * For those functions that are hot-patched, LLVM must rewrite references to +// global variables so that they are indirected through a `__ref_*` pointer +// variable. For each global variable, that is accessed by a hot-patched +// function, e.g. `FOO`, a `__ref_FOO` global pointer variable is created and +// all references to the original `FOO` are rewritten as dereferences of the +// `__ref_FOO` pointer. +// +// Some globals do not need `__ref_*` indirection. The pointer indirection +// behavior can be disabled for these globals by marking them with the +// `AllowDirectAccessInHotPatchFunction`. +// +// Rewriting references to global variables has some complexity. +// +// For ordinary instructions that reference GlobalVariables, we rewrite the +// operand of the instruction to a Load of the __ref_* variable. +// +// For constant expressions, we have to convert the constant expression (and +// transitively all constant expressions in its parent chain) to non-constant +// expressions, i.e. to a sequence of instructions. +// +// Pass 1: +// * Enumerate all instructions in all basic blocks. +// +// * If an instruction references a GlobalVariable (and it is not marked +// as being ignored), then we create (if necessary) the __ref_* variable +// for the GlobalVariable reference. However, we do not yet modify the +// Instruction. +// +// * If an instruction has an operand that is a ConstantExpr and the +// ConstantExpression tree contains a reference to a GlobalVariable, then +// we similarly create __ref_*. Similarly, we do not yet modify the +// Instruction or the ConstantExpr tree. +// +// After Pass 1 completes, we will know whether we found any references to +// globals in this pass. If the function does not use any globals (and most +// functions do not use any globals), then we return immediately. +// +// If a function does reference globals, then we iterate the list of globals +// used by this function and we generate Load instructions for each (unique) +// global. +// +// Next, we do another pass over all instructions: +// +// Pass 2: +// * Re-visit the instructions that were found in Pass 1. +// +// * If an instruction operand is a GlobalVariable, then look up the +// replacement +// __ref_* global variable and the Value that came from the Load instruction +// for it. Replace the operand of the GlobalVariable with the Load Value. +// +// * If an instruction operand is a ConstantExpr, then recursively examine the +// operands of all instructions in the ConstantExpr tree. If an operand is +// a GlobalVariable, then replace the operand with the result of the load +// *and* convert the ConstantExpr to a non-constant instruction. This +// instruction will need to be inserted into the BB of the instruction whose +// operand is being modified, ideally immediately before the instruction +// being modified. +// +// Limitations +// +// This feature is not intended to work in every situation. There are many +// legitimate code changes (patches) for which it is not possible to generate +// a hot-patch. Developers who are writing hot-patches are expected to +// understand the limitations. +// +// Tools which generate hot-patch metadata may also check that certain +// variables are upheld, and some of these invariants may be global (may require +// whole-program knowledge, not available in any single compiland). However, +// such tools are not required to be perfect; they are also best-effort. +// +// For these reasons, the hot-patching support implemented in this file is +// "best effort". It does not recognize every possible code pattern that could +// be patched, nor does it generate diagnostics for certain code patterns that +// could result in a binary that does not work with hot-patching. For example, +// const GlobalVariables that point to other non-const GlobalVariables are not +// compatible with hot-patching because they cannot use __ref_*-based +// redirection. +// +// References +// +// * "Hotpatching on Windows": +// https://techcommunity.microsoft.com/blog/windowsosplatform/hotpatching-on-windows/2959541 +// +// * "Hotpatch for Windows client now available": +// https://techcommunity.microsoft.com/blog/windows-itpro-blog/hotpatch-for-windows-client-now-available/4399808 +// +// * "Get hotpatching for Windows Server": +// https://www.microsoft.com/en-us/windows-server/blog/2025/04/24/tired-of-all-the-restarts-get-hotpatching-for-windows-server/ +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/SmallSet.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/IR/Attributes.h" +#include "llvm/IR/DIBuilder.h" +#include "llvm/IR/DiagnosticInfo.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Module.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" + +using namespace llvm; + +#define DEBUG_TYPE "windows-secure-hot-patch" + +// A file containing list of mangled function names to mark for hot patching. +static cl::opt<std::string> LLVMMSSecureHotPatchFunctionsFile( + "ms-secure-hotpatch-functions-file", cl::value_desc("filename"), + cl::desc("A file containing list of mangled function names to mark for " + "Windows Secure Hot-Patching")); + +// A list of mangled function names to mark for hot patching. +static cl::list<std::string> LLVMMSSecureHotPatchFunctionsList( + "ms-secure-hotpatch-functions-list", cl::value_desc("list"), + cl::desc("A list of mangled function names to mark for Windows Secure " + "Hot-Patching"), + cl::CommaSeparated); + +namespace { + +struct GlobalVariableUse { + // GlobalVariable *GV; + Instruction *User; + unsigned Op; +}; + +class WindowsSecureHotPatching : public ModulePass { +public: + static char ID; + + WindowsSecureHotPatching() : ModulePass(ID) { + initializeWindowsSecureHotPatchingPass(*PassRegistry::getPassRegistry()); + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + } + + bool doInitialization(Module &) override; + bool runOnModule(Module &M) override { return false; } + +private: + bool + runOnFunction(Function &F, + SmallDenseMap<GlobalVariable *, GlobalVariable *> &RefMapping); +}; + +} // end anonymous namespace + +char WindowsSecureHotPatching::ID = 0; + +INITIALIZE_PASS(WindowsSecureHotPatching, "windows-secure-hot-patch", + "Mark functions for Windows hot patch support", false, false) +ModulePass *llvm::createWindowsSecureHotPatchingPass() { + return new WindowsSecureHotPatching(); +} + +// Find functions marked with Attribute::MarkedForWindowsHotPatching and modify +// their code (if necessary) to account for accesses to global variables. +// +// This runs during doInitialization() instead of runOnModule() because it needs +// to run before CodeViewDebug::collectGlobalVariableInfo(). +bool WindowsSecureHotPatching::doInitialization(Module &M) { + // The front end may have already marked functions for hot-patching. However, + // we also allow marking functions by passing -ms-hotpatch-functions-file or + // -ms-hotpatch-functions-list directly to LLVM. This allows hot-patching to + // work with languages that have not yet updated their front-ends. + if (!LLVMMSSecureHotPatchFunctionsFile.empty() || + !LLVMMSSecureHotPatchFunctionsList.empty()) { + std::vector<std::string> HotPatchFunctionsList; + + if (!LLVMMSSecureHotPatchFunctionsFile.empty()) { + auto BufOrErr = MemoryBuffer::getFile(LLVMMSSecureHotPatchFunctionsFile); + if (BufOrErr) { + const MemoryBuffer &FileBuffer = **BufOrErr; + for (line_iterator I(FileBuffer.getMemBufferRef(), true), E; I != E; + ++I) + HotPatchFunctionsList.push_back(std::string{*I}); + } else { + M.getContext().diagnose(DiagnosticInfoGeneric{ + Twine("failed to open hotpatch functions file " + "(--ms-hotpatch-functions-file): ") + + LLVMMSSecureHotPatchFunctionsFile + Twine(" : ") + + BufOrErr.getError().message()}); + } + } + + if (!LLVMMSSecureHotPatchFunctionsList.empty()) + for (const auto &FuncName : LLVMMSSecureHotPatchFunctionsList) + HotPatchFunctionsList.push_back(FuncName); + + // Build a set for quick lookups. This points into HotPatchFunctionsList, so + // HotPatchFunctionsList must live longer than HotPatchFunctionsSet. + SmallSet<StringRef, 16> HotPatchFunctionsSet; + for (const auto &FuncName : HotPatchFunctionsList) + HotPatchFunctionsSet.insert(StringRef{FuncName}); + + // Iterate through all of the functions and check whether they need to be + // marked for hotpatching using the list provided directly to LLVM. + for (auto &F : M.functions()) { + // Ignore declarations that are not definitions. + if (F.isDeclarationForLinker()) + continue; + + if (HotPatchFunctionsSet.contains(F.getName())) + F.addFnAttr("marked_for_windows_hot_patching"); + } + } + + SmallDenseMap<GlobalVariable *, GlobalVariable *> RefMapping; + bool MadeChanges = false; + for (auto &F : M.functions()) { + if (F.hasFnAttribute("marked_for_windows_hot_patching")) { + if (runOnFunction(F, RefMapping)) + MadeChanges = true; + } + } + return MadeChanges; +} + +static bool TypeContainsPointers(Type *ty) { + switch (ty->getTypeID()) { + case Type::PointerTyID: + return true; + + case Type::ArrayTyID: + return TypeContainsPointers(ty->getArrayElementType()); + + case Type::StructTyID: { + unsigned NumElements = ty->getStructNumElements(); + for (unsigned I = 0; I < NumElements; ++I) { + if (TypeContainsPointers(ty->getStructElementType(I))) { + return true; + } + } + return false; + } + + default: + return false; + } +} + +// Returns true if GV needs redirection through a __ref_* variable. +static bool globalVariableNeedsRedirect(GlobalVariable *GV) { + // If a global variable is explictly marked as allowing access in hot-patched + // functions, then do not redirect it. + if (GV->hasAttribute("allow_direct_access_in_hot_patch_function")) + return false; + + // If the global variable is not a constant, then we want to redirect it. + if (!GV->isConstant()) { + if (GV->getName().starts_with("??_R")) { + // This is the name mangling prefix that MSVC uses for RTTI data. + // Clang is currently generating RTTI data that is marked non-constant. + // We override that and treat it like it is constant. + return false; + } + + // In general, if a global variable is not a constant, then redirect it. + return true; + } + + // If the type of GV cannot contain pointers, then it cannot point to + // other global variables. In this case, there is no need for redirects. + // For example, string literals do not contain pointers. + return TypeContainsPointers(GV->getValueType()); +} + +// Get or create a new global variable that points to the old one and whose +// name begins with `__ref_`. +// +// In hot-patched images, the __ref_* variables point to global variables in +// the original (unpatched) image. Hot-patched functions in the hot-patch +// image use these __ref_* variables to access global variables. This ensures +// that all code (both unpatched and patched) is using the same instances of +// global variables. +// +// The Windows hot-patch infrastructure handles modifying these __ref_* +// variables. By default, they are initialized with pointers to the equivalent +// global variables, so when a hot-patch module is loaded *as* a base image +// (such as after a system reboot), hot-patch functions will access the +// instances of global variables that are compiled into the hot-patch image. +// This is the desired outcome, since in this situation (normal boot) the +// hot-patch image *is* the base image. +// +// When we create the GlobalVariable for the __ref_* variable, we must create +// it as a *non-constant* global variable. The __ref_* pointers will not change +// during the runtime of the program, so it is tempting to think that they +// should be constant. However, they still need to be updateable by the +// hot-patching infrastructure. Also, if the GlobalVariable is created as a +// constant, then the LLVM optimizer will assume that it can dereference the +// definition of the __ref_* variable at compile time, which defeats the +// purpose of the indirection (pointer). +// +// The RefMapping table spans the entire module, not just a single function. +static GlobalVariable *getOrCreateRefVariable( + Function &F, SmallDenseMap<GlobalVariable *, GlobalVariable *> &RefMapping, + GlobalVariable *GV) { + GlobalVariable *&ReplaceWithRefGV = RefMapping.try_emplace(GV).first->second; + if (ReplaceWithRefGV != nullptr) { + // We have already created a __ref_* pointer for this GlobalVariable. + return ReplaceWithRefGV; + } + + Module *M = F.getParent(); + + const DISubprogram *Subprogram = F.getSubprogram(); + DICompileUnit *Unit = Subprogram != nullptr ? Subprogram->getUnit() : nullptr; + DIFile *File = Subprogram != nullptr ? Subprogram->getFile() : nullptr; + DIBuilder DebugInfo{*F.getParent(), true, Unit}; + + auto PtrTy = PointerType::get(M->getContext(), 0); + + Constant *AddrOfOldGV = + ConstantExpr::getGetElementPtr(PtrTy, GV, ArrayRef<Value *>{}); + + GlobalVariable *RefGV = + new GlobalVariable(*M, PtrTy, false, GlobalValue::LinkOnceAnyLinkage, + AddrOfOldGV, Twine("__ref_").concat(GV->getName()), + nullptr, GlobalVariable::NotThreadLocal); + + // Create debug info for the replacement global variable. + DataLayout Layout = M->getDataLayout(); + DIType *DebugType = DebugInfo.createPointerType( + nullptr, Layout.getTypeSizeInBits(GV->getValueType())); + DIGlobalVariableExpression *GVE = DebugInfo.createGlobalVariableExpression( + Unit, RefGV->getName(), StringRef{}, File, + /*LineNo*/ 0, DebugType, + /*IsLocalToUnit*/ false); + RefGV->addDebugInfo(GVE); + + // Store the __ref_* in RefMapping so that future calls use the same RefGV. + ReplaceWithRefGV = RefGV; + + return RefGV; +} + +// Given a ConstantExpr, this searches for GlobalVariable references within +// the expression tree. If found, it will generate instructions and will +// return a non-null Value* that points to the new root instruction. +// +// If C does not contain any GlobalVariable references, this returns nullptr. +// +// If this function creates new instructions, then it will insert them +// before InsertionPoint. +static Value *rewriteGlobalVariablesInConstant( + Constant *C, SmallDenseMap<GlobalVariable *, Value *> &GVLoadMap, + IRBuilder<> &IRBuilderAtEntry) { + if (C->getValueID() == Value::GlobalVariableVal) { + GlobalVariable *GV = cast<GlobalVariable>(C); + if (globalVariableNeedsRedirect(GV)) { + return GVLoadMap.at(GV); + } else { + return nullptr; + } + } + + // Scan the operands of this expression. + + SmallVector<Value *, 8> ReplacedValues; + bool ReplacedAnyOperands = false; + + unsigned NumOperands = C->getNumOperands(); + for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) { + Value *OldValue = C->getOperand(OpIndex); + Value *ReplacedValue = nullptr; + if (Constant *OldConstant = dyn_cast<Constant>(OldValue)) { + ReplacedValue = rewriteGlobalVariablesInConstant(OldConstant, GVLoadMap, + IRBuilderAtEntry); + } + // Do not use short-circuiting, here. We need to traverse the whole tree. + ReplacedAnyOperands |= ReplacedValue != nullptr; + ReplacedValues.push_back(ReplacedValue); + } + + // If none of our operands were replaced, then don't rewrite this expression. + if (!ReplacedAnyOperands) { + return nullptr; + } + + // We need to rewrite this expression. Convert this constant expression + // to an instruction, then replace any operands as needed. + Instruction *NewInst = cast<ConstantExpr>(C)->getAsInstruction(); + for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) { + Value *ReplacedValue = ReplacedValues[OpIndex]; + if (ReplacedValue != nullptr) { + NewInst->setOperand(OpIndex, ReplacedValue); + } + } + + // Insert the new instruction before the reference instruction. + IRBuilderAtEntry.Insert(NewInst); + + return NewInst; +} + +static bool searchConstantExprForGlobalVariables( + Value *V, SmallDenseMap<GlobalVariable *, Value *> &GVLoadMap, + SmallVector<GlobalVariableUse> &GVUses) { + + SmallVector<Value *, 8> ReplacedOperands; + + if (GlobalVariable *GV = dyn_cast<GlobalVariable>(V)) { + if (globalVariableNeedsRedirect(GV)) { + GVLoadMap[GV] = nullptr; + return true; + } else { + return false; + } + } + + if (User *U = dyn_cast<User>(V)) { + unsigned NumOperands = U->getNumOperands(); + bool FoundAny = false; + for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) { + Value *Op = U->getOperand(OpIndex); + // Do not use short-circuiting, here. We need to traverse the whole tree. + FoundAny |= searchConstantExprForGlobalVariables(Op, GVLoadMap, GVUses); + } + return FoundAny; + } else { + return false; + } +} + +// Processes a function that is marked for hot-patching. +// +// If a function is marked for hot-patching, we generate an S_HOTPATCHFUNC +// CodeView debug symbol. Tools that generate hot-patches look for +// S_HOTPATCHFUNC in final PDBs so that they can find functions that have been +// hot-patched and so that they can distinguish hot-patched functions from +// non-hot-patched functions. +// +// Also, in functions that are hot-patched, we must indirect all access to +// (mutable) global variables through a pointer. This pointer may point into the +// unpatched ("base") binary or may point into the patched image, depending on +// whether a hot-patch was loaded as a patch or as a base image. These +// indirections go through a new global variable, named `__ref_<Foo>` where +// `<Foo>` is the original symbol name of the global variable. +// +// This function handles rewriting accesses to global variables, but the +// generation of S_HOTPATCHFUNC occurs in +// CodeViewDebug::emitHotPatchInformation(). +// +// Returns true if any global variable references were found and rewritten. +bool WindowsSecureHotPatching::runOnFunction( + Function &F, + SmallDenseMap<GlobalVariable *, GlobalVariable *> &RefMapping) { + // Scan the function for references to global variables. If we find such a + // reference, create (if necessary) the __ref_* variable, then add an entry + // to the GVUses table. + // + // We ignore references to global variables if the variable is marked with + // AllowDirectAccessInHotPatchFunction. + + SmallDenseMap<GlobalVariable *, Value *> GVLoadMap; + SmallVector<GlobalVariableUse> GVUses; + + for (auto &I : instructions(F)) { + unsigned NumOperands = I.getNumOperands(); + for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) { + Value *V = I.getOperand(OpIndex); + + bool FoundAnyGVUses = false; + + switch (V->getValueID()) { + case Value::GlobalVariableVal: { + // Discover all uses of GlobalVariable, these will need to be replaced. + GlobalVariable *GV = cast<GlobalVariable>(V); + if (globalVariableNeedsRedirect(GV)) { + GVLoadMap.insert(std::make_pair(GV, nullptr)); + FoundAnyGVUses = true; + } + break; + } + + case Value::ConstantExprVal: { + ConstantExpr *CE = cast<ConstantExpr>(V); + if (searchConstantExprForGlobalVariables(CE, GVLoadMap, GVUses)) { + FoundAnyGVUses = true; + } + break; + } + + default: + break; + } + + if (FoundAnyGVUses) { + GVUses.push_back(GlobalVariableUse{&I, OpIndex}); + } + } + } + + // If this function did not reference any global variables then we have no + // work to do. Most functions do not access global variables. + if (GVUses.empty()) { + return false; + } + + // We know that there is at least one instruction that needs to be rewritten. + // Generate a Load instruction for each unique GlobalVariable used by this + // function. The Load instructions are inserted at the beginning of the + // entry block. Since entry blocks cannot contain PHI instructions, there is + // no need to skip PHI instructions. + + // We use a single IRBuilder for inserting Load instructions as well as the + // constants that we convert to instructions. Because constants do not + // depend on any dynamic values (they're constant, after all!), it is safe + // to move them to the start of entry BB. + + auto &EntryBlock = F.getEntryBlock(); + IRBuilder<> IRBuilderAtEntry(&EntryBlock, EntryBlock.begin()); + + for (auto &[GV, LoadValue] : GVLoadMap) { + assert(LoadValue == nullptr); + GlobalVariable *RefGV = getOrCreateRefVariable(F, RefMapping, GV); + LoadValue = IRBuilderAtEntry.CreateLoad(RefGV->getValueType(), RefGV); + } + + const DISubprogram *Subprogram = F.getSubprogram(); + DICompileUnit *Unit = Subprogram != nullptr ? Subprogram->getUnit() : nullptr; + DIBuilder DebugInfo{*F.getParent(), true, Unit}; + + // Go back to the instructions and rewrite their uses of GlobalVariable. + // Because a ConstantExpr can be a tree, it may reference more than one + // GlobalVariable. + + for (auto &GVUse : GVUses) { + Value *OldOperandValue = GVUse.User->getOperand(GVUse.Op); + Value *NewOperandValue; + + switch (OldOperandValue->getValueID()) { + case Value::GlobalVariableVal: { + // This is easy. Look up the replacement value and store the operand. + Value *OperandValue = GVUse.User->getOperand(GVUse.Op); + GlobalVariable *GV = cast<GlobalVariable>(OperandValue); + NewOperandValue = GVLoadMap.at(GV); + break; + } + + case Value::ConstantExprVal: { + // Walk the recursive tree of the ConstantExpr. If we find a + // GlobalVariable then replace it with the loaded value and rewrite + // the ConstantExpr to an Instruction and insert it before the + // current instruction. + Value *OperandValue = GVUse.User->getOperand(GVUse.Op); + ConstantExpr *CE = cast<ConstantExpr>(OperandValue); + NewOperandValue = + rewriteGlobalVariablesInConstant(CE, GVLoadMap, IRBuilderAtEntry); + assert(NewOperandValue != nullptr); + break; + } + + default: + // We should only ever get here because a GVUse was created in the first + // pass, and this only happens for GlobalVariableVal and ConstantExprVal. + llvm_unreachable_internal( + "unexpected Value in second pass of hot-patching"); + break; + } + + GVUse.User->setOperand(GVUse.Op, NewOperandValue); + } + + return true; +} |