
This is a refinment of #145565 . That PR added support for "Windows Secure Hot-patching". In this design, functions that are compiled for hot-patching need to be modified when they access mutable global variables. The modification is to insert a level of indirection, the so-called `__ref_*` variables. Ref variables are supposed to be inserted into the `.rdata` section, not `.data`. This provides a degree of protection against modification (accidental or malicious) of ref variables during program execution. When the Windows hot-patch subsystem loads a module as a hot-patch, it finds all ref variables and changes the page protections for the pages containing them to read/write. Then it sets the ref variables to point to the real variable locations within the base image. Then it changes page protections back to read-only. This relies on the variables being placed in the `.rdata` section, not `.data`. However, it is still important that the LLVM `GlobalVariable` that is created for the ref variable be created with `isConstant = false`. This prevents LLVM from optimizing accesses to the `GlobalVariable`, i.e. assuming that the variable can never change and thus inlining its value into expressions that would ordinarily dereference it. That optimization would defeat the purpose of hot-patching, so `isConstant = false` is still the correct value for these ref variables.
631 lines
25 KiB
C++
631 lines
25 KiB
C++
//===------ 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);
|
|
|
|
// RefGV is created with isConstant = false, but we want to place RefGV into
|
|
// .rdata, not .data. It is important that the GlobalVariable be mutable
|
|
// from the compiler's point of view, so that the optimizer does not remove
|
|
// the global variable entirely and replace all references to it with its
|
|
// initial value.
|
|
//
|
|
// When the Windows hot-patch loader applies a hot-patch, it maps the
|
|
// pages of .rdata as read/write so that it can set each __ref_* variable
|
|
// to point to the original variable in the base image. Afterward, pages in
|
|
// .rdata are remapped as read-only. This protects the __ref_* variables from
|
|
// being overwritten during execution.
|
|
RefGV->setSection(".rdata");
|
|
|
|
// 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;
|
|
}
|