Skip to content

Commit a6cab3f

Browse files
committed
Windows hotpatching support
move hotpatch tests to X86 subdir move llvm hotpatch tests to X86 dir switch to strbool attributes cleanup require x86 target for tests
1 parent 6cf656e commit a6cab3f

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 (Arg *A = Args.getLastArg(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)