Skip to content

Commit 0a3c5c4

Browse files
authored
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.
1 parent ebdd5a0 commit 0a3c5c4

31 files changed

+1201
-0
lines changed

clang/include/clang/Basic/CodeGenOptions.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,13 @@ class CodeGenOptions : public CodeGenOptionsBase {
495495

496496
/// A list of functions that are replacable by the loader.
497497
std::vector<std::string> LoaderReplaceableFunctionNames;
498+
/// The name of a file that contains functions which will be compiled for
499+
/// hotpatching. See -fms-secure-hotpatch-functions-file.
500+
std::string MSSecureHotPatchFunctionsFile;
501+
502+
/// A list of functions which will be compiled for hotpatching.
503+
/// See -fms-secure-hotpatch-functions-list.
504+
std::vector<std::string> MSSecureHotPatchFunctionsList;
498505

499506
public:
500507
// Define accessors/mutators for code generation options of enumeration type.

clang/include/clang/Driver/Options.td

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3838,6 +3838,24 @@ def fms_hotpatch : Flag<["-"], "fms-hotpatch">, Group<f_Group>,
38383838
Visibility<[ClangOption, CC1Option, CLOption]>,
38393839
HelpText<"Ensure that all functions can be hotpatched at runtime">,
38403840
MarshallingInfoFlag<CodeGenOpts<"HotPatch">>;
3841+
3842+
// See llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
3843+
def fms_secure_hotpatch_functions_file
3844+
: Joined<["-"], "fms-secure-hotpatch-functions-file=">,
3845+
Group<f_Group>,
3846+
Visibility<[ClangOption, CC1Option, CLOption]>,
3847+
MarshallingInfoString<CodeGenOpts<"MSSecureHotPatchFunctionsFile">>,
3848+
HelpText<"Path to a file that contains a list of mangled names of "
3849+
"functions that should be hot-patched for Windows Secure "
3850+
"Hot-Patching">;
3851+
def fms_secure_hotpatch_functions_list
3852+
: CommaJoined<["-"], "fms-secure-hotpatch-functions-list=">,
3853+
Group<f_Group>,
3854+
Visibility<[ClangOption, CC1Option, CLOption]>,
3855+
MarshallingInfoStringVector<CodeGenOpts<"MSSecureHotPatchFunctionsList">>,
3856+
HelpText<"List of mangled symbol names of functions that should be "
3857+
"hot-patched for Windows Secure Hot-Patching">;
3858+
38413859
def fpcc_struct_return : Flag<["-"], "fpcc-struct-return">, Group<f_Group>,
38423860
Visibility<[ClangOption, CC1Option]>,
38433861
HelpText<"Override the default ABI to return all structs on the stack">;

clang/lib/CodeGen/CGCall.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,13 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
26602660
// CPU/feature overrides. addDefaultFunctionDefinitionAttributes
26612661
// handles these separately to set them based on the global defaults.
26622662
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
2663+
2664+
// Windows hotpatching support
2665+
if (!MSHotPatchFunctions.empty()) {
2666+
bool IsHotPatched = llvm::binary_search(MSHotPatchFunctions, Name);
2667+
if (IsHotPatched)
2668+
FuncAttrs.addAttribute("marked_for_windows_hot_patching");
2669+
}
26632670
}
26642671

26652672
// Mark functions that are replaceable by the loader.

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,35 @@ CodeGenModule::CodeGenModule(ASTContext &C,
458458
if (Context.getTargetInfo().getTriple().getArch() == llvm::Triple::x86)
459459
getModule().addModuleFlag(llvm::Module::Error, "NumRegisterParameters",
460460
CodeGenOpts.NumRegisterParameters);
461+
462+
// If there are any functions that are marked for Windows secure hot-patching,
463+
// then build the list of functions now.
464+
if (!CGO.MSSecureHotPatchFunctionsFile.empty() ||
465+
!CGO.MSSecureHotPatchFunctionsList.empty()) {
466+
if (!CGO.MSSecureHotPatchFunctionsFile.empty()) {
467+
auto BufOrErr =
468+
llvm::MemoryBuffer::getFile(CGO.MSSecureHotPatchFunctionsFile);
469+
if (BufOrErr) {
470+
const llvm::MemoryBuffer &FileBuffer = **BufOrErr;
471+
for (llvm::line_iterator I(FileBuffer.getMemBufferRef(), true), E;
472+
I != E; ++I)
473+
this->MSHotPatchFunctions.push_back(std::string{*I});
474+
} else {
475+
auto &DE = Context.getDiagnostics();
476+
unsigned DiagID =
477+
DE.getCustomDiagID(DiagnosticsEngine::Error,
478+
"failed to open hotpatch functions file "
479+
"(-fms-hotpatch-functions-file): %0 : %1");
480+
DE.Report(DiagID) << CGO.MSSecureHotPatchFunctionsFile
481+
<< BufOrErr.getError().message();
482+
}
483+
}
484+
485+
for (const auto &FuncName : CGO.MSSecureHotPatchFunctionsList)
486+
this->MSHotPatchFunctions.push_back(FuncName);
487+
488+
llvm::sort(this->MSHotPatchFunctions);
489+
}
461490
}
462491

463492
CodeGenModule::~CodeGenModule() {}

clang/lib/CodeGen/CodeGenModule.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,11 @@ class CodeGenModule : public CodeGenTypeCache {
678678

679679
AtomicOptions AtomicOpts;
680680

681+
// A set of functions which should be hot-patched; see
682+
// -fms-hotpatch-functions-file (and -list). This will nearly always be empty.
683+
// The list is sorted for binary-searching.
684+
std::vector<std::string> MSHotPatchFunctions;
685+
681686
public:
682687
CodeGenModule(ASTContext &C, IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
683688
const HeaderSearchOptions &headersearchopts,

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6803,6 +6803,14 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
68036803

68046804
Args.AddLastArg(CmdArgs, options::OPT_fms_hotpatch);
68056805

6806+
if (Args.hasArg(options::OPT_fms_secure_hotpatch_functions_file))
6807+
Args.AddLastArg(CmdArgs, options::OPT_fms_secure_hotpatch_functions_file);
6808+
6809+
for (const auto &A :
6810+
Args.getAllArgValues(options::OPT_fms_secure_hotpatch_functions_list))
6811+
CmdArgs.push_back(
6812+
Args.MakeArgString("-fms-secure-hotpatch-functions-list=" + Twine(A)));
6813+
68066814
if (TC.SupportsProfiling()) {
68076815
Args.AddLastArg(CmdArgs, options::OPT_pg);
68086816

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// REQUIRES: x86-registered-target
2+
3+
// This verifies that we correctly handle a -fms-secure-hotpatch-functions-file argument that points
4+
// to a missing file.
5+
//
6+
// 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
7+
// CHECK: failed to open hotpatch functions file
8+
9+
void this_might_have_side_effects();
10+
11+
int __declspec(noinline) this_gets_hotpatched() {
12+
this_might_have_side_effects();
13+
return 42;
14+
}
15+
16+
int __declspec(noinline) this_does_not_get_hotpatched() {
17+
return this_gets_hotpatched() + 100;
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// REQUIRES: x86-registered-target
2+
3+
// This verifies that hotpatch function attributes are correctly propagated when compiling directly to OBJ,
4+
// and that name mangling works as expected.
5+
//
6+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=?this_gets_hotpatched@@YAHXZ /Fo%t.obj %s
7+
// RUN: llvm-readobj --codeview %t.obj | FileCheck %s
8+
9+
void this_might_have_side_effects();
10+
11+
int __declspec(noinline) this_gets_hotpatched() {
12+
this_might_have_side_effects();
13+
return 42;
14+
}
15+
16+
// CHECK: Kind: S_HOTPATCHFUNC (0x1169)
17+
// CHECK-NEXT: Function: this_gets_hotpatched
18+
// CHECK-NEXT: Name: ?this_gets_hotpatched@@YAHXZ
19+
20+
extern "C" int __declspec(noinline) this_does_not_get_hotpatched() {
21+
return this_gets_hotpatched() + 100;
22+
}
23+
24+
// CHECK-NOT: S_HOTPATCHFUNC
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// REQUIRES: x86-registered-target
2+
3+
// Global constant data such as exception handler tables should not be redirected by Windows Secure Hot-Patching
4+
//
5+
// 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
6+
7+
class Foo {
8+
public:
9+
int x;
10+
};
11+
12+
void this_might_throw();
13+
14+
extern "C" int this_gets_hotpatched(int k) {
15+
int ret;
16+
try {
17+
this_might_throw();
18+
ret = 1;
19+
} catch (Foo& f) {
20+
ret = 2;
21+
}
22+
return ret;
23+
}
24+
25+
// We expect that RTTI data is not redirected.
26+
// CHECK-NOT: "__ref_??_R0?AVFoo@@@8"
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// REQUIRES: x86-registered-target
2+
3+
// This verifies that global variable redirection works correctly when using hotpatching.
4+
//
5+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 \
6+
// RUN: -fms-secure-hotpatch-functions-list=hp1,hp2,hp3,hp4,hp5_phi_ptr_mixed,hp_phi_ptr_both,hp_const_ptr_sub \
7+
// RUN: /clang:-S /clang:-o- %s | FileCheck %s
8+
9+
#ifdef __clang__
10+
#define NO_TAIL __attribute__((disable_tail_calls))
11+
#else
12+
#define NO_TAIL
13+
#endif
14+
15+
extern int g_data[10];
16+
17+
struct SomeData {
18+
int x;
19+
int y;
20+
};
21+
22+
const struct SomeData g_this_is_const = { 100, 200 };
23+
24+
struct HasPointers {
25+
int* ptr;
26+
int x;
27+
};
28+
29+
extern struct HasPointers g_has_pointers;
30+
31+
void take_data(const void* p);
32+
33+
void do_side_effects();
34+
void do_other_side_effects();
35+
36+
void hp1() NO_TAIL {
37+
take_data(&g_data[5]);
38+
}
39+
40+
// CHECK: hp1:
41+
// CHECK: mov rcx, qword ptr [rip + __ref_g_data]
42+
// CHECK: add rcx, 20
43+
// CHECK: call take_data
44+
// CHECK: .seh_endproc
45+
46+
void hp2() NO_TAIL {
47+
// We do not expect string literals to be redirected.
48+
take_data("hello, world!");
49+
}
50+
51+
// CHECK: hp2:
52+
// CHECK: lea rcx, [rip + "??_C@_0O@KJBLMJCB@hello?0?5world?$CB?$AA@"]
53+
// CHECK: call take_data
54+
// CHECK: .seh_endproc
55+
56+
void hp3() NO_TAIL {
57+
// We do not expect g_this_is_const to be redirected because it is const
58+
// and contains no pointers.
59+
take_data(&g_this_is_const);
60+
}
61+
62+
// CHECK: hp3:
63+
// CHECK: lea rcx, [rip + g_this_is_const]
64+
// CHECK: call take_data
65+
// CHECK-NOT: __ref_g_this_is_const
66+
// CHECK: .seh_endproc
67+
68+
void hp4() NO_TAIL {
69+
take_data(&g_has_pointers);
70+
// We expect &g_has_pointers to be redirected.
71+
}
72+
73+
// CHECK: hp4:
74+
// CHECK: mov rcx, qword ptr [rip + __ref_g_has_pointers]
75+
// CHECK: call take_data
76+
// CHECK: .seh_endproc
77+
78+
// This case checks that global variable redirection interacts correctly with PHI nodes.
79+
// The IR for this generates a "phi ptr g_has_pointers, g_this_is_const" node.
80+
// We expect g_has_pointers to be redirected, but not g_this_is_const.
81+
void hp5_phi_ptr_mixed(int x) NO_TAIL {
82+
const void* y;
83+
if (x) {
84+
y = &g_has_pointers;
85+
do_side_effects();
86+
} else {
87+
y = &g_this_is_const;
88+
do_other_side_effects();
89+
}
90+
take_data(y);
91+
}
92+
93+
// CHECK: hp5_phi_ptr_mixed
94+
// CHECK: .seh_endprologue
95+
// CHECK: test ecx, ecx
96+
// CHECK: mov rsi, qword ptr [rip + __ref_g_has_pointers]
97+
// CHECK: call do_side_effects
98+
// CHECK: jmp
99+
// CHECK: call do_other_side_effects
100+
// CHECK: lea rsi, [rip + g_this_is_const]
101+
// CHECK: mov rcx, rsi
102+
// CHECK: call take_data
103+
// CHECK: .seh_endproc
104+
105+
// This case tests that global variable redirection interacts correctly with PHI nodes,
106+
// where two (all) operands of a given PHI node are globabl variables that redirect.
107+
void hp_phi_ptr_both(int x) NO_TAIL {
108+
const void* y;
109+
if (x) {
110+
y = &g_has_pointers;
111+
do_side_effects();
112+
} else {
113+
y = &g_data[5];
114+
do_other_side_effects();
115+
}
116+
take_data(y);
117+
}
118+
119+
// CHECK: hp_phi_ptr_both:
120+
// CHECK: .seh_endprologue
121+
// CHECK: test ecx, ecx
122+
// CHECK: mov rsi, qword ptr [rip + __ref_g_has_pointers]
123+
// CHECK: mov rsi, qword ptr [rip + __ref_g_data]
124+
// CHECK: take_data
125+
// CHECK: .seh_endproc
126+
127+
// Test a constant expression which references global variable addresses.
128+
size_t hp_const_ptr_sub() NO_TAIL {
129+
return (unsigned char*)&g_has_pointers - (unsigned char*)&g_data;
130+
}
131+
132+
// CHECK: hp_const_ptr_sub:
133+
// CHECK: mov rax, qword ptr [rip + __ref_g_has_pointers]
134+
// CHECK: sub rax, qword ptr [rip + __ref_g_data]
135+
// CHECK: ret
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// REQUIRES: x86-registered-target
2+
3+
// This verifies that hotpatch function attributes are correctly propagated through LLVM IR when compiling with LTO.
4+
//
5+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=this_gets_hotpatched -flto /Fo%t.bc %s
6+
// RUN: llvm-dis %t.bc -o - | FileCheck %s
7+
//
8+
// CHECK-LABEL: define dso_local noundef i32 @this_gets_hotpatched()
9+
// CHECK-SAME: #0
10+
//
11+
// CHECK-LABEL: define dso_local noundef i32 @this_does_not_get_hotpatched()
12+
// CHECK-SAME: #1
13+
14+
// CHECK: attributes #0
15+
// CHECK-SAME: "marked_for_windows_hot_patching"
16+
17+
// CHECK: attributes #1
18+
// CHECK-NOT: "marked_for_windows_hot_patching"
19+
20+
int __declspec(noinline) this_gets_hotpatched() {
21+
return 42;
22+
}
23+
24+
int __declspec(noinline) this_does_not_get_hotpatched() {
25+
return this_gets_hotpatched() + 100;
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// REQUIRES: x86-registered-target
2+
3+
// This verifies that hotpatch function attributes are correctly propagated when compiling directly to OBJ.
4+
//
5+
// RUN: echo this_gets_hotpatched > %t.patch-functions.txt
6+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-file=%t.patch-functions.txt /Fo%t.obj %s
7+
// RUN: llvm-readobj --codeview %t.obj | FileCheck %s
8+
9+
void this_might_have_side_effects();
10+
11+
int __declspec(noinline) this_gets_hotpatched() {
12+
this_might_have_side_effects();
13+
return 42;
14+
}
15+
16+
// CHECK: Kind: S_HOTPATCHFUNC (0x1169)
17+
// CHECK-NEXT: Function: this_gets_hotpatched
18+
19+
int __declspec(noinline) this_does_not_get_hotpatched() {
20+
return this_gets_hotpatched() + 100;
21+
}
22+
23+
// CHECK-NOT: S_HOTPATCHFUNC

llvm/include/llvm/CodeGen/Passes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@ LLVM_ABI FunctionPass *createSelectOptimizePass();
618618

619619
LLVM_ABI FunctionPass *createCallBrPass();
620620

621+
/// Creates Windows Secure Hot Patch pass. \see WindowsSecureHotPatching.cpp
622+
ModulePass *createWindowsSecureHotPatchingPass();
623+
621624
/// Lowers KCFI operand bundles for indirect calls.
622625
LLVM_ABI FunctionPass *createKCFIPass();
623626
} // namespace llvm

llvm/include/llvm/DebugInfo/CodeView/CodeViewSymbols.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ SYMBOL_RECORD_ALIAS(S_GTHREAD32 , 0x1113, GlobalTLS, ThreadLocalDataSym)
256256
SYMBOL_RECORD(S_UNAMESPACE , 0x1124, UsingNamespaceSym)
257257
SYMBOL_RECORD(S_ANNOTATION , 0x1019, AnnotationSym)
258258

259+
SYMBOL_RECORD(S_HOTPATCHFUNC , 0x1169, HotPatchFuncSym)
260+
259261
#undef CV_SYMBOL
260262
#undef SYMBOL_RECORD
261263
#undef SYMBOL_RECORD_ALIAS

0 commit comments

Comments
 (0)