[clang] [llvm] Add support for Windows Secure Hot-Patching (PR #138972)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 13 13:36:42 PDT 2025
https://github.com/sivadeilra updated https://github.com/llvm/llvm-project/pull/138972
>From a684405f4986e72d78dcfcf6accf4c3ae26b35f1 Mon Sep 17 00:00:00 2001
From: Arlie Davis <ardavis at microsoft.com>
Date: Thu, 3 Apr 2025 16:10:50 -0700
Subject: [PATCH] Windows hotpatching support
---
clang/include/clang/Basic/CodeGenOptions.h | 7 +
clang/include/clang/Driver/Options.td | 18 +
clang/lib/CodeGen/CGCall.cpp | 8 +
clang/lib/CodeGen/CodeGenModule.cpp | 29 +
clang/lib/CodeGen/CodeGenModule.h | 5 +
clang/lib/Driver/ToolChains/Clang.cpp | 8 +
.../CodeGen/ms-secure-hotpatch-bad-file.c | 16 +
clang/test/CodeGen/ms-secure-hotpatch-cpp.cpp | 22 +
clang/test/CodeGen/ms-secure-hotpatch-eh.cpp | 24 +
.../test/CodeGen/ms-secure-hotpatch-globals.c | 123 ++++
clang/test/CodeGen/ms-secure-hotpatch-lto.c | 18 +
clang/test/CodeGen/ms-secure-hotpatch.c | 21 +
llvm/include/llvm/Bitcode/LLVMBitCodes.h | 2 +
llvm/include/llvm/CodeGen/Passes.h | 3 +
.../DebugInfo/CodeView/CodeViewSymbols.def | 2 +
.../llvm/DebugInfo/CodeView/SymbolRecord.h | 15 +
llvm/include/llvm/IR/Attributes.td | 11 +
llvm/include/llvm/InitializePasses.h | 1 +
llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 4 +
llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 4 +
llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp | 24 +
llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h | 2 +
llvm/lib/CodeGen/CMakeLists.txt | 1 +
llvm/lib/CodeGen/TargetPassConfig.cpp | 3 +
llvm/lib/CodeGen/WindowsSecureHotPatching.cpp | 620 ++++++++++++++++++
llvm/lib/DebugInfo/CodeView/SymbolDumper.cpp | 7 +
.../CodeView/SymbolRecordMapping.cpp | 7 +
llvm/lib/ObjectYAML/CodeViewYAMLSymbols.cpp | 5 +
llvm/lib/Transforms/Utils/CodeExtractor.cpp | 2 +
.../Generic/ms-secure-hotpatch-attr.ll | 38 ++
.../Generic/ms-secure-hotpatch-bad-file.ll | 16 +
...ms-secure-hotpatch-direct-global-access.ll | 39 ++
.../ms-secure-hotpatch-functions-file.ll | 39 ++
.../ms-secure-hotpatch-functions-list.ll | 38 ++
.../llvm-pdbutil/MinimalSymbolDumper.cpp | 8 +
35 files changed, 1190 insertions(+)
create mode 100644 clang/test/CodeGen/ms-secure-hotpatch-bad-file.c
create mode 100644 clang/test/CodeGen/ms-secure-hotpatch-cpp.cpp
create mode 100644 clang/test/CodeGen/ms-secure-hotpatch-eh.cpp
create mode 100644 clang/test/CodeGen/ms-secure-hotpatch-globals.c
create mode 100644 clang/test/CodeGen/ms-secure-hotpatch-lto.c
create mode 100644 clang/test/CodeGen/ms-secure-hotpatch.c
create mode 100644 llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
create mode 100644 llvm/test/CodeGen/Generic/ms-secure-hotpatch-attr.ll
create mode 100644 llvm/test/CodeGen/Generic/ms-secure-hotpatch-bad-file.ll
create mode 100644 llvm/test/CodeGen/Generic/ms-secure-hotpatch-direct-global-access.ll
create mode 100644 llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-file.ll
create mode 100644 llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-list.ll
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 7ba21fca6dd6b..77a0c559f7689 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -495,6 +495,13 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// 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.
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 1b07deb4a8482..dd383969fac7c 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3830,6 +3830,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">;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index a06455d25b1ef..060ca21a4048f 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2669,6 +2669,14 @@ 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(
+ llvm::Attribute::MarkedForWindowsSecureHotPatching);
+ }
}
// Mark functions that are replaceable by the loader.
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 451792dca40c5..091d7c47e30cb 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -453,6 +453,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() {}
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 1b67d4354efc0..cb013feb769fc 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -678,6 +678,11 @@ class CodeGenModule : public CodeGenTypeCache {
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,
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 8556bcadf0915..3547a1222cec1 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6793,6 +6793,14 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddLastArg(CmdArgs, options::OPT_fms_hotpatch);
+ if (Arg *A = Args.getLastArg(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);
diff --git a/clang/test/CodeGen/ms-secure-hotpatch-bad-file.c b/clang/test/CodeGen/ms-secure-hotpatch-bad-file.c
new file mode 100644
index 0000000000000..22f10b34d82fb
--- /dev/null
+++ b/clang/test/CodeGen/ms-secure-hotpatch-bad-file.c
@@ -0,0 +1,16 @@
+// 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;
+}
diff --git a/clang/test/CodeGen/ms-secure-hotpatch-cpp.cpp b/clang/test/CodeGen/ms-secure-hotpatch-cpp.cpp
new file mode 100644
index 0000000000000..c8112ad54baab
--- /dev/null
+++ b/clang/test/CodeGen/ms-secure-hotpatch-cpp.cpp
@@ -0,0 +1,22 @@
+// 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
diff --git a/clang/test/CodeGen/ms-secure-hotpatch-eh.cpp b/clang/test/CodeGen/ms-secure-hotpatch-eh.cpp
new file mode 100644
index 0000000000000..0f11a0fdf6c3e
--- /dev/null
+++ b/clang/test/CodeGen/ms-secure-hotpatch-eh.cpp
@@ -0,0 +1,24 @@
+// 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"
diff --git a/clang/test/CodeGen/ms-secure-hotpatch-globals.c b/clang/test/CodeGen/ms-secure-hotpatch-globals.c
new file mode 100644
index 0000000000000..7d38b6de933b4
--- /dev/null
+++ b/clang/test/CodeGen/ms-secure-hotpatch-globals.c
@@ -0,0 +1,123 @@
+// 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 \
+// 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 at _0O@KJBLMJCB at 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
diff --git a/clang/test/CodeGen/ms-secure-hotpatch-lto.c b/clang/test/CodeGen/ms-secure-hotpatch-lto.c
new file mode 100644
index 0000000000000..6771d3e9f49cf
--- /dev/null
+++ b/clang/test/CodeGen/ms-secure-hotpatch-lto.c
@@ -0,0 +1,18 @@
+// 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: ; Function Attrs: marked_for_windows_hot_patching mustprogress nofree noinline norecurse nosync nounwind sspstrong willreturn memory(none) uwtable
+// CHECK-NEXT: define dso_local noundef i32 @this_gets_hotpatched() local_unnamed_addr #0 !dbg !13 {
+//
+// CHECK: ; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind sspstrong willreturn memory(none) uwtable
+// CHECK-NEXT: define dso_local noundef i32 @this_does_not_get_hotpatched() local_unnamed_addr #1 !dbg !19 {
+
+int __declspec(noinline) this_gets_hotpatched() {
+ return 42;
+}
+
+int __declspec(noinline) this_does_not_get_hotpatched() {
+ return this_gets_hotpatched() + 100;
+}
diff --git a/clang/test/CodeGen/ms-secure-hotpatch.c b/clang/test/CodeGen/ms-secure-hotpatch.c
new file mode 100644
index 0000000000000..f170de851ae51
--- /dev/null
+++ b/clang/test/CodeGen/ms-secure-hotpatch.c
@@ -0,0 +1,21 @@
+// 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
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index b362a88963f6c..b70f0cb27a552 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -798,6 +798,8 @@ enum AttributeKindCodes {
ATTR_KIND_NO_DIVERGENCE_SOURCE = 100,
ATTR_KIND_SANITIZE_TYPE = 101,
ATTR_KIND_CAPTURES = 102,
+ ATTR_KIND_ALLOW_DIRECT_ACCESS_IN_HOT_PATCH_FUNCTION = 103,
+ ATTR_KIND_MARKED_FOR_WINDOWS_SECURE_HOT_PATCHING = 104,
};
enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/CodeGen/Passes.h b/llvm/include/llvm/CodeGen/Passes.h
index 990452fa11fec..18df5d657064a 100644
--- a/llvm/include/llvm/CodeGen/Passes.h
+++ b/llvm/include/llvm/CodeGen/Passes.h
@@ -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
diff --git a/llvm/include/llvm/DebugInfo/CodeView/CodeViewSymbols.def b/llvm/include/llvm/DebugInfo/CodeView/CodeViewSymbols.def
index 9d85acc49fa02..b38bdb482df43 100644
--- a/llvm/include/llvm/DebugInfo/CodeView/CodeViewSymbols.def
+++ b/llvm/include/llvm/DebugInfo/CodeView/CodeViewSymbols.def
@@ -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
diff --git a/llvm/include/llvm/DebugInfo/CodeView/SymbolRecord.h b/llvm/include/llvm/DebugInfo/CodeView/SymbolRecord.h
index 5b4f0d31e6427..f5f6fe69430cc 100644
--- a/llvm/include/llvm/DebugInfo/CodeView/SymbolRecord.h
+++ b/llvm/include/llvm/DebugInfo/CodeView/SymbolRecord.h
@@ -177,6 +177,21 @@ class CallerSym : public SymbolRecord {
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;
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index d488c5f419b82..e204d09e012b6 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -389,6 +389,17 @@ 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
+ : EnumAttr<"marked_for_windows_hot_patching", IntersectPreserve, [FnAttr]>;
+
+/// 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
+ : EnumAttr<"allow_direct_access_in_hot_patch_function",
+ IntersectPreserve, [FnAttr]>;
+
/// Target-independent string attributes.
def LessPreciseFPMAD : StrBoolAttr<"less-precise-fpmad">;
def NoInfsFPMath : StrBoolAttr<"no-infs-fp-math">;
diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h
index 42610d505c2bd..828a23a2695be 100644
--- a/llvm/include/llvm/InitializePasses.h
+++ b/llvm/include/llvm/InitializePasses.h
@@ -324,6 +324,7 @@ void initializeVirtRegMapWrapperLegacyPass(PassRegistry &);
void initializeVirtRegRewriterLegacyPass(PassRegistry &);
void initializeWasmEHPreparePass(PassRegistry &);
void initializeWinEHPreparePass(PassRegistry &);
+void initializeWindowsSecureHotPatchingPass(PassRegistry &);
void initializeWriteBitcodePassPass(PassRegistry &);
void initializeXRayInstrumentationLegacyPass(PassRegistry &);
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index fde934fbb3cf1..2f264e41d0fe4 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2244,6 +2244,10 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
return Attribute::NoExt;
case bitc::ATTR_KIND_CAPTURES:
return Attribute::Captures;
+ case bitc::ATTR_KIND_ALLOW_DIRECT_ACCESS_IN_HOT_PATCH_FUNCTION:
+ return Attribute::AllowDirectAccessInHotPatchFunction;
+ case bitc::ATTR_KIND_MARKED_FOR_WINDOWS_SECURE_HOT_PATCHING:
+ return Attribute::MarkedForWindowsSecureHotPatching;
}
}
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 628b939af19ce..7eab1dda19f3e 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -938,6 +938,10 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
return bitc::ATTR_KIND_NO_EXT;
case Attribute::Captures:
return bitc::ATTR_KIND_CAPTURES;
+ case Attribute::AllowDirectAccessInHotPatchFunction:
+ return bitc::ATTR_KIND_ALLOW_DIRECT_ACCESS_IN_HOT_PATCH_FUNCTION;
+ case Attribute::MarkedForWindowsSecureHotPatching:
+ return bitc::ATTR_KIND_MARKED_FOR_WINDOWS_SECURE_HOT_PATCHING;
case Attribute::EndAttrKinds:
llvm_unreachable("Can not encode end-attribute kinds marker.");
case Attribute::None:
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
index fc43bc6f7776d..c7b29ccb019e8 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
@@ -653,6 +653,8 @@ void CodeViewDebug::endModule() {
emitCompilerInformation();
endCVSubsection(CompilerInfo);
+ emitSecureHotPatchInformation();
+
emitInlineeLinesSubsection();
// Emit per-function debug information.
@@ -807,6 +809,28 @@ void CodeViewDebug::emitObjName() {
endSymbolRecord(CompilerEnd);
}
+void CodeViewDebug::emitSecureHotPatchInformation() {
+ MCSymbol *hotPatchInfo = nullptr;
+
+ for (const auto &F : MMI->getModule()->functions()) {
+ if (!F.isDeclarationForLinker() &&
+ F.hasFnAttribute(Attribute::MarkedForWindowsSecureHotPatching)) {
+ 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 d13b315135ad9..293c27708ed4d 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
@@ -333,6 +333,8 @@ class LLVM_LIBRARY_VISIBILITY CodeViewDebug : public DebugHandlerBase {
void emitCompilerInformation();
+ void emitSecureHotPatchInformation();
+
void emitBuildInfo();
void emitInlineeLinesSubsection();
diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt
index 5dd6413431255..f8f9bbba53e43 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 4ae52b056d844..7d7c6e743fa76 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 0000000000000..546550e438415
--- /dev/null
+++ b/llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
@@ -0,0 +1,620 @@
+//===------ 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(Attribute::MarkedForWindowsSecureHotPatching);
+ }
+ }
+
+ SmallDenseMap<GlobalVariable *, GlobalVariable *> RefMapping;
+ bool MadeChanges = false;
+ for (auto &F : M.functions()) {
+ if (F.hasFnAttribute(Attribute::MarkedForWindowsSecureHotPatching)) {
+ 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(Attribute::AllowDirectAccessInHotPatchFunction)) {
+ 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 (V->getValueID() == Value::GlobalVariableVal) {
+ GlobalVariable *GV = 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;
+}
diff --git a/llvm/lib/DebugInfo/CodeView/SymbolDumper.cpp b/llvm/lib/DebugInfo/CodeView/SymbolDumper.cpp
index f56739db7c75f..9cb3bca8d6e5e 100644
--- a/llvm/lib/DebugInfo/CodeView/SymbolDumper.cpp
+++ b/llvm/lib/DebugInfo/CodeView/SymbolDumper.cpp
@@ -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();
diff --git a/llvm/lib/DebugInfo/CodeView/SymbolRecordMapping.cpp b/llvm/lib/DebugInfo/CodeView/SymbolRecordMapping.cpp
index b5e366b965a95..525343b90a3ae 100644
--- a/llvm/lib/DebugInfo/CodeView/SymbolRecordMapping.cpp
+++ b/llvm/lib/DebugInfo/CodeView/SymbolRecordMapping.cpp
@@ -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);
diff --git a/llvm/lib/ObjectYAML/CodeViewYAMLSymbols.cpp b/llvm/lib/ObjectYAML/CodeViewYAMLSymbols.cpp
index b15919f68725f..3056251809308 100644
--- a/llvm/lib/ObjectYAML/CodeViewYAMLSymbols.cpp
+++ b/llvm/lib/ObjectYAML/CodeViewYAMLSymbols.cpp
@@ -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
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 1210bdf4a1c98..91091b06c2a65 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -937,6 +937,8 @@ Function *CodeExtractor::constructFunctionDeclaration(
case Attribute::CoroDestroyOnlyWhenComplete:
case Attribute::CoroElideSafe:
case Attribute::NoDivergenceSource:
+ case Attribute::MarkedForWindowsSecureHotPatching:
+ case Attribute::AllowDirectAccessInHotPatchFunction:
continue;
// Those attributes should be safe to propagate to the extracted function.
case Attribute::AlwaysInline:
diff --git a/llvm/test/CodeGen/Generic/ms-secure-hotpatch-attr.ll b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-attr.ll
new file mode 100644
index 0000000000000..11d99bae1ca8c
--- /dev/null
+++ b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-attr.ll
@@ -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"
+
+ at 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
diff --git a/llvm/test/CodeGen/Generic/ms-secure-hotpatch-bad-file.ll b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-bad-file.ll
new file mode 100644
index 0000000000000..8f8624ea0de49
--- /dev/null
+++ b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-bad-file.ll
@@ -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"
+
+ at 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 }
diff --git a/llvm/test/CodeGen/Generic/ms-secure-hotpatch-direct-global-access.ll b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-direct-global-access.ll
new file mode 100644
index 0000000000000..960a76fe43f12
--- /dev/null
+++ b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-direct-global-access.ll
@@ -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"
+
+ at 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
diff --git a/llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-file.ll b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-file.ll
new file mode 100644
index 0000000000000..bb79e08683aa7
--- /dev/null
+++ b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-file.ll
@@ -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"
+
+ at 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
diff --git a/llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-list.ll b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-list.ll
new file mode 100644
index 0000000000000..b1da1a2db66dc
--- /dev/null
+++ b/llvm/test/CodeGen/Generic/ms-secure-hotpatch-functions-list.ll
@@ -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"
+
+ at 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
diff --git a/llvm/tools/llvm-pdbutil/MinimalSymbolDumper.cpp b/llvm/tools/llvm-pdbutil/MinimalSymbolDumper.cpp
index 479d025835188..66a091f50d6b2 100644
--- a/llvm/tools/llvm-pdbutil/MinimalSymbolDumper.cpp
+++ b/llvm/tools/llvm-pdbutil/MinimalSymbolDumper.cpp
@@ -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();
+}
More information about the llvm-commits
mailing list