Add support for Windows Secure Hot-Patching (redo) (#145565)
(This is a re-do of #138972, which had a minor warning in `Clang.cpp`.) 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.
This commit is contained in:
@@ -495,6 +495,13 @@ public:
|
||||
|
||||
/// A list of functions that are replacable by the loader.
|
||||
std::vector<std::string> LoaderReplaceableFunctionNames;
|
||||
/// The name of a file that contains functions which will be compiled for
|
||||
/// hotpatching. See -fms-secure-hotpatch-functions-file.
|
||||
std::string MSSecureHotPatchFunctionsFile;
|
||||
|
||||
/// A list of functions which will be compiled for hotpatching.
|
||||
/// See -fms-secure-hotpatch-functions-list.
|
||||
std::vector<std::string> MSSecureHotPatchFunctionsList;
|
||||
|
||||
public:
|
||||
// Define accessors/mutators for code generation options of enumeration type.
|
||||
|
||||
@@ -3838,6 +3838,24 @@ def fms_hotpatch : Flag<["-"], "fms-hotpatch">, Group<f_Group>,
|
||||
Visibility<[ClangOption, CC1Option, CLOption]>,
|
||||
HelpText<"Ensure that all functions can be hotpatched at runtime">,
|
||||
MarshallingInfoFlag<CodeGenOpts<"HotPatch">>;
|
||||
|
||||
// See llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
|
||||
def fms_secure_hotpatch_functions_file
|
||||
: Joined<["-"], "fms-secure-hotpatch-functions-file=">,
|
||||
Group<f_Group>,
|
||||
Visibility<[ClangOption, CC1Option, CLOption]>,
|
||||
MarshallingInfoString<CodeGenOpts<"MSSecureHotPatchFunctionsFile">>,
|
||||
HelpText<"Path to a file that contains a list of mangled names of "
|
||||
"functions that should be hot-patched for Windows Secure "
|
||||
"Hot-Patching">;
|
||||
def fms_secure_hotpatch_functions_list
|
||||
: CommaJoined<["-"], "fms-secure-hotpatch-functions-list=">,
|
||||
Group<f_Group>,
|
||||
Visibility<[ClangOption, CC1Option, CLOption]>,
|
||||
MarshallingInfoStringVector<CodeGenOpts<"MSSecureHotPatchFunctionsList">>,
|
||||
HelpText<"List of mangled symbol names of functions that should be "
|
||||
"hot-patched for Windows Secure Hot-Patching">;
|
||||
|
||||
def fpcc_struct_return : Flag<["-"], "fpcc-struct-return">, Group<f_Group>,
|
||||
Visibility<[ClangOption, CC1Option]>,
|
||||
HelpText<"Override the default ABI to return all structs on the stack">;
|
||||
|
||||
@@ -2660,6 +2660,13 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
|
||||
// CPU/feature overrides. addDefaultFunctionDefinitionAttributes
|
||||
// handles these separately to set them based on the global defaults.
|
||||
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
|
||||
|
||||
// Windows hotpatching support
|
||||
if (!MSHotPatchFunctions.empty()) {
|
||||
bool IsHotPatched = llvm::binary_search(MSHotPatchFunctions, Name);
|
||||
if (IsHotPatched)
|
||||
FuncAttrs.addAttribute("marked_for_windows_hot_patching");
|
||||
}
|
||||
}
|
||||
|
||||
// Mark functions that are replaceable by the loader.
|
||||
|
||||
@@ -458,6 +458,35 @@ CodeGenModule::CodeGenModule(ASTContext &C,
|
||||
if (Context.getTargetInfo().getTriple().getArch() == llvm::Triple::x86)
|
||||
getModule().addModuleFlag(llvm::Module::Error, "NumRegisterParameters",
|
||||
CodeGenOpts.NumRegisterParameters);
|
||||
|
||||
// If there are any functions that are marked for Windows secure hot-patching,
|
||||
// then build the list of functions now.
|
||||
if (!CGO.MSSecureHotPatchFunctionsFile.empty() ||
|
||||
!CGO.MSSecureHotPatchFunctionsList.empty()) {
|
||||
if (!CGO.MSSecureHotPatchFunctionsFile.empty()) {
|
||||
auto BufOrErr =
|
||||
llvm::MemoryBuffer::getFile(CGO.MSSecureHotPatchFunctionsFile);
|
||||
if (BufOrErr) {
|
||||
const llvm::MemoryBuffer &FileBuffer = **BufOrErr;
|
||||
for (llvm::line_iterator I(FileBuffer.getMemBufferRef(), true), E;
|
||||
I != E; ++I)
|
||||
this->MSHotPatchFunctions.push_back(std::string{*I});
|
||||
} else {
|
||||
auto &DE = Context.getDiagnostics();
|
||||
unsigned DiagID =
|
||||
DE.getCustomDiagID(DiagnosticsEngine::Error,
|
||||
"failed to open hotpatch functions file "
|
||||
"(-fms-hotpatch-functions-file): %0 : %1");
|
||||
DE.Report(DiagID) << CGO.MSSecureHotPatchFunctionsFile
|
||||
<< BufOrErr.getError().message();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &FuncName : CGO.MSSecureHotPatchFunctionsList)
|
||||
this->MSHotPatchFunctions.push_back(FuncName);
|
||||
|
||||
llvm::sort(this->MSHotPatchFunctions);
|
||||
}
|
||||
}
|
||||
|
||||
CodeGenModule::~CodeGenModule() {}
|
||||
|
||||
@@ -678,6 +678,11 @@ private:
|
||||
|
||||
AtomicOptions AtomicOpts;
|
||||
|
||||
// A set of functions which should be hot-patched; see
|
||||
// -fms-hotpatch-functions-file (and -list). This will nearly always be empty.
|
||||
// The list is sorted for binary-searching.
|
||||
std::vector<std::string> MSHotPatchFunctions;
|
||||
|
||||
public:
|
||||
CodeGenModule(ASTContext &C, IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
|
||||
const HeaderSearchOptions &headersearchopts,
|
||||
|
||||
@@ -6803,6 +6803,14 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
|
||||
|
||||
Args.AddLastArg(CmdArgs, options::OPT_fms_hotpatch);
|
||||
|
||||
if (Args.hasArg(options::OPT_fms_secure_hotpatch_functions_file))
|
||||
Args.AddLastArg(CmdArgs, options::OPT_fms_secure_hotpatch_functions_file);
|
||||
|
||||
for (const auto &A :
|
||||
Args.getAllArgValues(options::OPT_fms_secure_hotpatch_functions_list))
|
||||
CmdArgs.push_back(
|
||||
Args.MakeArgString("-fms-secure-hotpatch-functions-list=" + Twine(A)));
|
||||
|
||||
if (TC.SupportsProfiling()) {
|
||||
Args.AddLastArg(CmdArgs, options::OPT_pg);
|
||||
|
||||
|
||||
18
clang/test/CodeGen/X86/ms-secure-hotpatch-bad-file.c
Normal file
18
clang/test/CodeGen/X86/ms-secure-hotpatch-bad-file.c
Normal file
@@ -0,0 +1,18 @@
|
||||
// REQUIRES: x86-registered-target
|
||||
|
||||
// This verifies that we correctly handle a -fms-secure-hotpatch-functions-file argument that points
|
||||
// to a missing file.
|
||||
//
|
||||
// RUN: not %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-file=%S/this-file-is-intentionally-missing-do-not-create-it.txt /Fo%t.obj %s 2>&1 | FileCheck %s
|
||||
// CHECK: failed to open hotpatch functions file
|
||||
|
||||
void this_might_have_side_effects();
|
||||
|
||||
int __declspec(noinline) this_gets_hotpatched() {
|
||||
this_might_have_side_effects();
|
||||
return 42;
|
||||
}
|
||||
|
||||
int __declspec(noinline) this_does_not_get_hotpatched() {
|
||||
return this_gets_hotpatched() + 100;
|
||||
}
|
||||
24
clang/test/CodeGen/X86/ms-secure-hotpatch-cpp.cpp
Normal file
24
clang/test/CodeGen/X86/ms-secure-hotpatch-cpp.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// REQUIRES: x86-registered-target
|
||||
|
||||
// This verifies that hotpatch function attributes are correctly propagated when compiling directly to OBJ,
|
||||
// and that name mangling works as expected.
|
||||
//
|
||||
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=?this_gets_hotpatched@@YAHXZ /Fo%t.obj %s
|
||||
// RUN: llvm-readobj --codeview %t.obj | FileCheck %s
|
||||
|
||||
void this_might_have_side_effects();
|
||||
|
||||
int __declspec(noinline) this_gets_hotpatched() {
|
||||
this_might_have_side_effects();
|
||||
return 42;
|
||||
}
|
||||
|
||||
// CHECK: Kind: S_HOTPATCHFUNC (0x1169)
|
||||
// CHECK-NEXT: Function: this_gets_hotpatched
|
||||
// CHECK-NEXT: Name: ?this_gets_hotpatched@@YAHXZ
|
||||
|
||||
extern "C" int __declspec(noinline) this_does_not_get_hotpatched() {
|
||||
return this_gets_hotpatched() + 100;
|
||||
}
|
||||
|
||||
// CHECK-NOT: S_HOTPATCHFUNC
|
||||
26
clang/test/CodeGen/X86/ms-secure-hotpatch-eh.cpp
Normal file
26
clang/test/CodeGen/X86/ms-secure-hotpatch-eh.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// REQUIRES: x86-registered-target
|
||||
|
||||
// Global constant data such as exception handler tables should not be redirected by Windows Secure Hot-Patching
|
||||
//
|
||||
// RUN: %clang_cl -c --target=x86_64-windows-msvc /EHsc -O2 -fms-secure-hotpatch-functions-list=this_gets_hotpatched /Fo%t.obj /clang:-S /clang:-o- %s 2>& 1 | FileCheck %s
|
||||
|
||||
class Foo {
|
||||
public:
|
||||
int x;
|
||||
};
|
||||
|
||||
void this_might_throw();
|
||||
|
||||
extern "C" int this_gets_hotpatched(int k) {
|
||||
int ret;
|
||||
try {
|
||||
this_might_throw();
|
||||
ret = 1;
|
||||
} catch (Foo& f) {
|
||||
ret = 2;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// We expect that RTTI data is not redirected.
|
||||
// CHECK-NOT: "__ref_??_R0?AVFoo@@@8"
|
||||
135
clang/test/CodeGen/X86/ms-secure-hotpatch-globals.c
Normal file
135
clang/test/CodeGen/X86/ms-secure-hotpatch-globals.c
Normal file
@@ -0,0 +1,135 @@
|
||||
// REQUIRES: x86-registered-target
|
||||
|
||||
// This verifies that global variable redirection works correctly when using hotpatching.
|
||||
//
|
||||
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 \
|
||||
// RUN: -fms-secure-hotpatch-functions-list=hp1,hp2,hp3,hp4,hp5_phi_ptr_mixed,hp_phi_ptr_both,hp_const_ptr_sub \
|
||||
// RUN: /clang:-S /clang:-o- %s | FileCheck %s
|
||||
|
||||
#ifdef __clang__
|
||||
#define NO_TAIL __attribute__((disable_tail_calls))
|
||||
#else
|
||||
#define NO_TAIL
|
||||
#endif
|
||||
|
||||
extern int g_data[10];
|
||||
|
||||
struct SomeData {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
const struct SomeData g_this_is_const = { 100, 200 };
|
||||
|
||||
struct HasPointers {
|
||||
int* ptr;
|
||||
int x;
|
||||
};
|
||||
|
||||
extern struct HasPointers g_has_pointers;
|
||||
|
||||
void take_data(const void* p);
|
||||
|
||||
void do_side_effects();
|
||||
void do_other_side_effects();
|
||||
|
||||
void hp1() NO_TAIL {
|
||||
take_data(&g_data[5]);
|
||||
}
|
||||
|
||||
// CHECK: hp1:
|
||||
// CHECK: mov rcx, qword ptr [rip + __ref_g_data]
|
||||
// CHECK: add rcx, 20
|
||||
// CHECK: call take_data
|
||||
// CHECK: .seh_endproc
|
||||
|
||||
void hp2() NO_TAIL {
|
||||
// We do not expect string literals to be redirected.
|
||||
take_data("hello, world!");
|
||||
}
|
||||
|
||||
// CHECK: hp2:
|
||||
// CHECK: lea rcx, [rip + "??_C@_0O@KJBLMJCB@hello?0?5world?$CB?$AA@"]
|
||||
// CHECK: call take_data
|
||||
// CHECK: .seh_endproc
|
||||
|
||||
void hp3() NO_TAIL {
|
||||
// We do not expect g_this_is_const to be redirected because it is const
|
||||
// and contains no pointers.
|
||||
take_data(&g_this_is_const);
|
||||
}
|
||||
|
||||
// CHECK: hp3:
|
||||
// CHECK: lea rcx, [rip + g_this_is_const]
|
||||
// CHECK: call take_data
|
||||
// CHECK-NOT: __ref_g_this_is_const
|
||||
// CHECK: .seh_endproc
|
||||
|
||||
void hp4() NO_TAIL {
|
||||
take_data(&g_has_pointers);
|
||||
// We expect &g_has_pointers to be redirected.
|
||||
}
|
||||
|
||||
// CHECK: hp4:
|
||||
// CHECK: mov rcx, qword ptr [rip + __ref_g_has_pointers]
|
||||
// CHECK: call take_data
|
||||
// CHECK: .seh_endproc
|
||||
|
||||
// This case checks that global variable redirection interacts correctly with PHI nodes.
|
||||
// The IR for this generates a "phi ptr g_has_pointers, g_this_is_const" node.
|
||||
// We expect g_has_pointers to be redirected, but not g_this_is_const.
|
||||
void hp5_phi_ptr_mixed(int x) NO_TAIL {
|
||||
const void* y;
|
||||
if (x) {
|
||||
y = &g_has_pointers;
|
||||
do_side_effects();
|
||||
} else {
|
||||
y = &g_this_is_const;
|
||||
do_other_side_effects();
|
||||
}
|
||||
take_data(y);
|
||||
}
|
||||
|
||||
// CHECK: hp5_phi_ptr_mixed
|
||||
// CHECK: .seh_endprologue
|
||||
// CHECK: test ecx, ecx
|
||||
// CHECK: mov rsi, qword ptr [rip + __ref_g_has_pointers]
|
||||
// CHECK: call do_side_effects
|
||||
// CHECK: jmp
|
||||
// CHECK: call do_other_side_effects
|
||||
// CHECK: lea rsi, [rip + g_this_is_const]
|
||||
// CHECK: mov rcx, rsi
|
||||
// CHECK: call take_data
|
||||
// CHECK: .seh_endproc
|
||||
|
||||
// This case tests that global variable redirection interacts correctly with PHI nodes,
|
||||
// where two (all) operands of a given PHI node are globabl variables that redirect.
|
||||
void hp_phi_ptr_both(int x) NO_TAIL {
|
||||
const void* y;
|
||||
if (x) {
|
||||
y = &g_has_pointers;
|
||||
do_side_effects();
|
||||
} else {
|
||||
y = &g_data[5];
|
||||
do_other_side_effects();
|
||||
}
|
||||
take_data(y);
|
||||
}
|
||||
|
||||
// CHECK: hp_phi_ptr_both:
|
||||
// CHECK: .seh_endprologue
|
||||
// CHECK: test ecx, ecx
|
||||
// CHECK: mov rsi, qword ptr [rip + __ref_g_has_pointers]
|
||||
// CHECK: mov rsi, qword ptr [rip + __ref_g_data]
|
||||
// CHECK: take_data
|
||||
// CHECK: .seh_endproc
|
||||
|
||||
// Test a constant expression which references global variable addresses.
|
||||
size_t hp_const_ptr_sub() NO_TAIL {
|
||||
return (unsigned char*)&g_has_pointers - (unsigned char*)&g_data;
|
||||
}
|
||||
|
||||
// CHECK: hp_const_ptr_sub:
|
||||
// CHECK: mov rax, qword ptr [rip + __ref_g_has_pointers]
|
||||
// CHECK: sub rax, qword ptr [rip + __ref_g_data]
|
||||
// CHECK: ret
|
||||
26
clang/test/CodeGen/X86/ms-secure-hotpatch-lto.c
Normal file
26
clang/test/CodeGen/X86/ms-secure-hotpatch-lto.c
Normal file
@@ -0,0 +1,26 @@
|
||||
// REQUIRES: x86-registered-target
|
||||
|
||||
// This verifies that hotpatch function attributes are correctly propagated through LLVM IR when compiling with LTO.
|
||||
//
|
||||
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=this_gets_hotpatched -flto /Fo%t.bc %s
|
||||
// RUN: llvm-dis %t.bc -o - | FileCheck %s
|
||||
//
|
||||
// CHECK-LABEL: define dso_local noundef i32 @this_gets_hotpatched()
|
||||
// CHECK-SAME: #0
|
||||
//
|
||||
// CHECK-LABEL: define dso_local noundef i32 @this_does_not_get_hotpatched()
|
||||
// CHECK-SAME: #1
|
||||
|
||||
// CHECK: attributes #0
|
||||
// CHECK-SAME: "marked_for_windows_hot_patching"
|
||||
|
||||
// CHECK: attributes #1
|
||||
// CHECK-NOT: "marked_for_windows_hot_patching"
|
||||
|
||||
int __declspec(noinline) this_gets_hotpatched() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
int __declspec(noinline) this_does_not_get_hotpatched() {
|
||||
return this_gets_hotpatched() + 100;
|
||||
}
|
||||
23
clang/test/CodeGen/X86/ms-secure-hotpatch.c
Normal file
23
clang/test/CodeGen/X86/ms-secure-hotpatch.c
Normal file
@@ -0,0 +1,23 @@
|
||||
// REQUIRES: x86-registered-target
|
||||
|
||||
// This verifies that hotpatch function attributes are correctly propagated when compiling directly to OBJ.
|
||||
//
|
||||
// RUN: echo this_gets_hotpatched > %t.patch-functions.txt
|
||||
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-file=%t.patch-functions.txt /Fo%t.obj %s
|
||||
// RUN: llvm-readobj --codeview %t.obj | FileCheck %s
|
||||
|
||||
void this_might_have_side_effects();
|
||||
|
||||
int __declspec(noinline) this_gets_hotpatched() {
|
||||
this_might_have_side_effects();
|
||||
return 42;
|
||||
}
|
||||
|
||||
// CHECK: Kind: S_HOTPATCHFUNC (0x1169)
|
||||
// CHECK-NEXT: Function: this_gets_hotpatched
|
||||
|
||||
int __declspec(noinline) this_does_not_get_hotpatched() {
|
||||
return this_gets_hotpatched() + 100;
|
||||
}
|
||||
|
||||
// CHECK-NOT: S_HOTPATCHFUNC
|
||||
@@ -618,6 +618,9 @@ LLVM_ABI FunctionPass *createSelectOptimizePass();
|
||||
|
||||
LLVM_ABI FunctionPass *createCallBrPass();
|
||||
|
||||
/// Creates Windows Secure Hot Patch pass. \see WindowsSecureHotPatching.cpp
|
||||
ModulePass *createWindowsSecureHotPatchingPass();
|
||||
|
||||
/// Lowers KCFI operand bundles for indirect calls.
|
||||
LLVM_ABI FunctionPass *createKCFIPass();
|
||||
} // namespace llvm
|
||||
|
||||
@@ -256,6 +256,8 @@ SYMBOL_RECORD_ALIAS(S_GTHREAD32 , 0x1113, GlobalTLS, ThreadLocalDataSym)
|
||||
SYMBOL_RECORD(S_UNAMESPACE , 0x1124, UsingNamespaceSym)
|
||||
SYMBOL_RECORD(S_ANNOTATION , 0x1019, AnnotationSym)
|
||||
|
||||
SYMBOL_RECORD(S_HOTPATCHFUNC , 0x1169, HotPatchFuncSym)
|
||||
|
||||
#undef CV_SYMBOL
|
||||
#undef SYMBOL_RECORD
|
||||
#undef SYMBOL_RECORD_ALIAS
|
||||
|
||||
@@ -177,6 +177,21 @@ public:
|
||||
uint32_t RecordOffset = 0;
|
||||
};
|
||||
|
||||
class HotPatchFuncSym : public SymbolRecord {
|
||||
public:
|
||||
explicit HotPatchFuncSym(SymbolRecordKind Kind) : SymbolRecord(Kind) {}
|
||||
HotPatchFuncSym(uint32_t RecordOffset)
|
||||
: SymbolRecord(SymbolRecordKind::HotPatchFuncSym),
|
||||
RecordOffset(RecordOffset) {}
|
||||
|
||||
// This is an ItemID in the IPI stream, which points to an LF_FUNC_ID or
|
||||
// LF_MFUNC_ID record.
|
||||
TypeIndex Function;
|
||||
StringRef Name;
|
||||
|
||||
uint32_t RecordOffset = 0;
|
||||
};
|
||||
|
||||
struct DecodedAnnotation {
|
||||
StringRef Name;
|
||||
ArrayRef<uint8_t> Bytes;
|
||||
|
||||
@@ -389,6 +389,16 @@ def CoroDestroyOnlyWhenComplete : EnumAttr<"coro_only_destroy_when_complete", In
|
||||
/// pipeline to perform elide on the call or invoke instruction.
|
||||
def CoroElideSafe : EnumAttr<"coro_elide_safe", IntersectPreserve, [FnAttr]>;
|
||||
|
||||
/// Function is marked for Windows Hot Patching
|
||||
def MarkedForWindowsSecureHotPatching
|
||||
: StrBoolAttr<"marked_for_windows_hot_patching">;
|
||||
|
||||
/// Global variable should not be accessed through a "__ref_" global variable in
|
||||
/// a hot patching function This attribute is applied to the global variable
|
||||
/// decl, not the hotpatched function.
|
||||
def AllowDirectAccessInHotPatchFunction
|
||||
: StrBoolAttr<"allow_direct_access_in_hot_patch_function">;
|
||||
|
||||
/// Target-independent string attributes.
|
||||
def LessPreciseFPMAD : StrBoolAttr<"less-precise-fpmad">;
|
||||
def NoInfsFPMath : StrBoolAttr<"no-infs-fp-math">;
|
||||
|
||||
@@ -336,6 +336,7 @@ LLVM_ABI void initializeVerifierLegacyPassPass(PassRegistry &);
|
||||
LLVM_ABI void initializeVirtRegMapWrapperLegacyPass(PassRegistry &);
|
||||
LLVM_ABI void initializeVirtRegRewriterLegacyPass(PassRegistry &);
|
||||
LLVM_ABI void initializeWasmEHPreparePass(PassRegistry &);
|
||||
LLVM_ABI void initializeWindowsSecureHotPatchingPass(PassRegistry &);
|
||||
LLVM_ABI void initializeWinEHPreparePass(PassRegistry &);
|
||||
LLVM_ABI void initializeWriteBitcodePassPass(PassRegistry &);
|
||||
LLVM_ABI void initializeXRayInstrumentationLegacyPass(PassRegistry &);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -337,6 +337,8 @@ private:
|
||||
|
||||
void emitCompilerInformation();
|
||||
|
||||
void emitSecureHotPatchInformation();
|
||||
|
||||
void emitBuildInfo();
|
||||
|
||||
void emitInlineeLinesSubsection();
|
||||
|
||||
@@ -250,6 +250,7 @@ add_llvm_component_library(LLVMCodeGen
|
||||
VirtRegMap.cpp
|
||||
WasmEHPrepare.cpp
|
||||
WindowScheduler.cpp
|
||||
WindowsSecureHotPatching.cpp
|
||||
WinEHPrepare.cpp
|
||||
XRayInstrumentation.cpp
|
||||
${GeneratedMLSources}
|
||||
|
||||
@@ -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
|
||||
|
||||
617
llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
Normal file
617
llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
Normal file
@@ -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;
|
||||
}
|
||||
@@ -672,6 +672,13 @@ Error CVSymbolDumperImpl::visitKnownRecord(CVSymbol &CVR,
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error CVSymbolDumperImpl::visitKnownRecord(CVSymbol &CVR,
|
||||
HotPatchFuncSym &HotPatchFunc) {
|
||||
printTypeIndex("Function", HotPatchFunc.Function);
|
||||
W.printString("Name", HotPatchFunc.Name);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error CVSymbolDumperImpl::visitUnknownSymbol(CVSymbol &CVR) {
|
||||
W.printNumber("Length", CVR.length());
|
||||
return Error::success();
|
||||
|
||||
@@ -496,6 +496,13 @@ Error SymbolRecordMapping::visitKnownRecord(CVSymbol &CVR,
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error SymbolRecordMapping::visitKnownRecord(CVSymbol &CVR,
|
||||
HotPatchFuncSym &HotPatchFunc) {
|
||||
error(IO.mapInteger(HotPatchFunc.Function));
|
||||
error(IO.mapStringZ(HotPatchFunc.Name));
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
RegisterId codeview::decodeFramePtrReg(EncodedFramePtrReg EncodedReg,
|
||||
CPUType CPU) {
|
||||
assert(unsigned(EncodedReg) < 4);
|
||||
|
||||
@@ -605,6 +605,11 @@ template <> void SymbolRecordImpl<JumpTableSym>::map(IO &IO) {
|
||||
IO.mapRequired("EntriesCount", Symbol.EntriesCount);
|
||||
}
|
||||
|
||||
template <> void SymbolRecordImpl<HotPatchFuncSym>::map(IO &IO) {
|
||||
IO.mapRequired("Function", Symbol.Function);
|
||||
IO.mapRequired("Name", Symbol.Name);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
} // end namespace CodeViewYAML
|
||||
} // end namespace llvm
|
||||
|
||||
38
llvm/test/CodeGen/X86/ms-secure-hotpatch-attr.ll
Normal file
38
llvm/test/CodeGen/X86/ms-secure-hotpatch-attr.ll
Normal file
@@ -0,0 +1,38 @@
|
||||
; This tests directly annotating a function with marked_for_windows_hot_patching.
|
||||
;
|
||||
; RUN: llc -mtriple=x86_64-windows < %s | FileCheck %s
|
||||
|
||||
source_filename = ".\\ms-secure-hotpatch-attr.ll"
|
||||
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-windows-msvc19.36.32537"
|
||||
|
||||
@some_global_var = external global i32
|
||||
|
||||
define noundef i32 @this_gets_hotpatched() #0 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #0 = { "marked_for_windows_hot_patching" mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_gets_hotpatched: # @this_gets_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movq __ref_some_global_var(%rip), %rax
|
||||
; CHECK-NEXT: movl (%rax), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
|
||||
define noundef i32 @this_does_not_get_hotpatched() #1 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #1 = { mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_does_not_get_hotpatched: # @this_does_not_get_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movl some_global_var(%rip), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
16
llvm/test/CodeGen/X86/ms-secure-hotpatch-bad-file.ll
Normal file
16
llvm/test/CodeGen/X86/ms-secure-hotpatch-bad-file.ll
Normal file
@@ -0,0 +1,16 @@
|
||||
; RUN: not llc -mtriple=x86_64-windows --ms-secure-hotpatch-functions-file=%S/this-file-is-intentionally-missing-do-not-create-it.txt < %s 2>&1 | FileCheck %s
|
||||
; CHECK: failed to open hotpatch functions file
|
||||
|
||||
source_filename = ".\\ms-secure-hotpatch.ll"
|
||||
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-windows-msvc19.36.32537"
|
||||
|
||||
@some_global_var = external global i32
|
||||
|
||||
define noundef i32 @this_gets_hotpatched() #0 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #0 = { "marked_for_windows_hot_patching" mustprogress noinline nounwind optnone uwtable }
|
||||
@@ -0,0 +1,39 @@
|
||||
; This tests hotpatching functions that bypass double-indirection for global variables.
|
||||
;
|
||||
; RUN: llc -mtriple=x86_64-windows < %s | FileCheck %s
|
||||
|
||||
source_filename = ".\\ms-secure-hotpatch-direct-global-access.ll"
|
||||
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-windows-msvc19.36.32537"
|
||||
|
||||
@some_global_var = external global i32 #2
|
||||
|
||||
define noundef i32 @this_gets_hotpatched() #0 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #0 = { "marked_for_windows_hot_patching" mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_gets_hotpatched: # @this_gets_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movl some_global_var(%rip), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
|
||||
define noundef i32 @this_does_not_get_hotpatched() #1 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #1 = { mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
attributes #2 = { "allow_direct_access_in_hot_patch_function" }
|
||||
|
||||
; CHECK: this_does_not_get_hotpatched: # @this_does_not_get_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movl some_global_var(%rip), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
39
llvm/test/CodeGen/X86/ms-secure-hotpatch-functions-file.ll
Normal file
39
llvm/test/CodeGen/X86/ms-secure-hotpatch-functions-file.ll
Normal file
@@ -0,0 +1,39 @@
|
||||
; This tests annotating a function with marked_for_windows_hot_patching by using --ms-hotpatch-functions-file.
|
||||
;
|
||||
; RUN: echo this_gets_hotpatched > %t.patch-functions.txt
|
||||
; RUN: llc -mtriple=x86_64-windows --ms-secure-hotpatch-functions-file=%t.patch-functions.txt < %s | FileCheck %s
|
||||
|
||||
source_filename = ".\\ms-secure-hotpatch-functions-file.ll"
|
||||
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-windows-msvc19.36.32537"
|
||||
|
||||
@some_global_var = external global i32
|
||||
|
||||
define noundef i32 @this_gets_hotpatched() #0 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #0 = { mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_gets_hotpatched: # @this_gets_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movq __ref_some_global_var(%rip), %rax
|
||||
; CHECK-NEXT: movl (%rax), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
|
||||
define noundef i32 @this_does_not_get_hotpatched() #1 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #1 = { mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_does_not_get_hotpatched: # @this_does_not_get_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movl some_global_var(%rip), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
38
llvm/test/CodeGen/X86/ms-secure-hotpatch-functions-list.ll
Normal file
38
llvm/test/CodeGen/X86/ms-secure-hotpatch-functions-list.ll
Normal file
@@ -0,0 +1,38 @@
|
||||
; This tests annotating a function with marked_for_windows_hot_patching by using --ms-hotpatch-functions-list.
|
||||
;
|
||||
; RUN: llc -mtriple=x86_64-windows --ms-secure-hotpatch-functions-list=this_gets_hotpatched < %s | FileCheck %s
|
||||
|
||||
source_filename = ".\\ms-secure-hotpatch-functions-list.ll"
|
||||
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-windows-msvc19.36.32537"
|
||||
|
||||
@some_global_var = external global i32
|
||||
|
||||
define noundef i32 @this_gets_hotpatched() #0 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #0 = { mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_gets_hotpatched: # @this_gets_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movq __ref_some_global_var(%rip), %rax
|
||||
; CHECK-NEXT: movl (%rax), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
|
||||
define noundef i32 @this_does_not_get_hotpatched() #1 {
|
||||
%1 = load i32, ptr @some_global_var
|
||||
%2 = add i32 %1, 1
|
||||
ret i32 %2
|
||||
}
|
||||
|
||||
attributes #1 = { mustprogress noinline nounwind optnone uwtable }
|
||||
|
||||
; CHECK: this_does_not_get_hotpatched: # @this_does_not_get_hotpatched
|
||||
; CHECK-NEXT: bb.0:
|
||||
; CHECK-NEXT: movl some_global_var(%rip), %eax
|
||||
; CHECK-NEXT: addl $1, %eax
|
||||
; CHECK-NEXT: retq
|
||||
@@ -955,3 +955,11 @@ Error MinimalSymbolDumper::visitKnownRecord(CVSymbol &CVR,
|
||||
JumpTable.EntriesCount);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Error MinimalSymbolDumper::visitKnownRecord(CVSymbol &CVR,
|
||||
HotPatchFuncSym &JumpTable) {
|
||||
AutoIndent Indent(P, 7);
|
||||
P.formatLine("function = {0}, name = {1}", typeIndex(JumpTable.Function),
|
||||
JumpTable.Name);
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user