Skip to content

Commit 54196a3

Browse files
committed
Windows hotpatching support
1 parent 8957e64 commit 54196a3

35 files changed

+1114
-0
lines changed

clang/include/clang/Basic/CodeGenOptions.h

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

506506
/// A list of functions that are replacable by the loader.
507507
std::vector<std::string> LoaderReplaceableFunctionNames;
508+
/// The name of a file that contains functions which will be compiled for
509+
/// hotpatching. See -fms-secure-hotpatch-functions-file.
510+
std::string MSSecureHotPatchFunctionsFile;
511+
512+
/// A list of functions which will be compiled for hotpatching.
513+
/// See -fms-secure-hotpatch-functions-list.
514+
std::vector<std::string> MSSecureHotPatchFunctionsList;
508515

509516
public:
510517
// 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
@@ -3830,6 +3830,24 @@ def fms_hotpatch : Flag<["-"], "fms-hotpatch">, Group<f_Group>,
38303830
Visibility<[ClangOption, CC1Option, CLOption]>,
38313831
HelpText<"Ensure that all functions can be hotpatched at runtime">,
38323832
MarshallingInfoFlag<CodeGenOpts<"HotPatch">>;
3833+
3834+
// See llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
3835+
def fms_secure_hotpatch_functions_file
3836+
: Joined<["-"], "fms-secure-hotpatch-functions-file=">,
3837+
Group<f_Group>,
3838+
Visibility<[ClangOption, CC1Option, CLOption]>,
3839+
MarshallingInfoString<CodeGenOpts<"MSSecureHotPatchFunctionsFile">>,
3840+
HelpText<"Path to a file that contains a list of mangled names of "
3841+
"functions that should be hot-patched for Windows Secure "
3842+
"Hot-Patching">;
3843+
def fms_secure_hotpatch_functions_list
3844+
: CommaJoined<["-"], "fms-secure-hotpatch-functions-list=">,
3845+
Group<f_Group>,
3846+
Visibility<[ClangOption, CC1Option, CLOption]>,
3847+
MarshallingInfoStringVector<CodeGenOpts<"MSSecureHotPatchFunctionsList">>,
3848+
HelpText<"List of mangled symbol names of functions that should be "
3849+
"hot-patched for Windows Secure Hot-Patching">;
3850+
38333851
def fpcc_struct_return : Flag<["-"], "fpcc-struct-return">, Group<f_Group>,
38343852
Visibility<[ClangOption, CC1Option]>,
38353853
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
@@ -2669,6 +2669,13 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
26692669
// CPU/feature overrides. addDefaultFunctionDefinitionAttributes
26702670
// handles these separately to set them based on the global defaults.
26712671
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
2672+
2673+
// Windows hotpatching support
2674+
if (!MSHotPatchFunctions.empty()) {
2675+
bool IsHotPatched = llvm::binary_search(MSHotPatchFunctions, Name);
2676+
if (IsHotPatched)
2677+
FuncAttrs.addAttribute(llvm::Attribute::MarkedForWindowsHotPatching);
2678+
}
26722679
}
26732680

26742681
// 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
@@ -453,6 +453,35 @@ CodeGenModule::CodeGenModule(ASTContext &C,
453453
if (Context.getTargetInfo().getTriple().getArch() == llvm::Triple::x86)
454454
getModule().addModuleFlag(llvm::Module::Error, "NumRegisterParameters",
455455
CodeGenOpts.NumRegisterParameters);
456+
457+
// If there are any functions that are marked for Windows secure hot-patching,
458+
// then build the list of functions now.
459+
if (!CGO.MSSecureHotPatchFunctionsFile.empty() ||
460+
!CGO.MSSecureHotPatchFunctionsList.empty()) {
461+
if (!CGO.MSSecureHotPatchFunctionsFile.empty()) {
462+
auto BufOrErr =
463+
llvm::MemoryBuffer::getFile(CGO.MSSecureHotPatchFunctionsFile);
464+
if (BufOrErr) {
465+
const llvm::MemoryBuffer &FileBuffer = **BufOrErr;
466+
for (llvm::line_iterator I(FileBuffer.getMemBufferRef(), true), E;
467+
I != E; ++I)
468+
this->MSHotPatchFunctions.push_back(std::string{*I});
469+
} else {
470+
auto &DE = Context.getDiagnostics();
471+
unsigned DiagID =
472+
DE.getCustomDiagID(DiagnosticsEngine::Error,
473+
"failed to open hotpatch functions file "
474+
"(-fms-hotpatch-functions-file): %0 : %1");
475+
DE.Report(DiagID) << CGO.MSSecureHotPatchFunctionsFile
476+
<< BufOrErr.getError().message();
477+
}
478+
}
479+
480+
for (const auto &FuncName : CGO.MSSecureHotPatchFunctionsList)
481+
this->MSHotPatchFunctions.push_back(FuncName);
482+
483+
llvm::sort(this->MSHotPatchFunctions);
484+
}
456485
}
457486

458487
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
@@ -6775,6 +6775,14 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
67756775

67766776
Args.AddLastArg(CmdArgs, options::OPT_fms_hotpatch);
67776777

6778+
if (Arg *A = Args.getLastArg(options::OPT_fms_secure_hotpatch_functions_file))
6779+
Args.AddLastArg(CmdArgs, options::OPT_fms_secure_hotpatch_functions_file);
6780+
6781+
for (const auto &A :
6782+
Args.getAllArgValues(options::OPT_fms_secure_hotpatch_functions_list))
6783+
CmdArgs.push_back(
6784+
Args.MakeArgString("-fms-secure-hotpatch-functions-list=" + Twine(A)));
6785+
67786786
if (TC.SupportsProfiling()) {
67796787
Args.AddLastArg(CmdArgs, options::OPT_pg);
67806788

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This verifies that we correctly handle a -fms-secure-hotpatch-functions-file argument that points
2+
// to a missing file.
3+
//
4+
// 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
5+
// CHECK: failed to open hotpatch functions file
6+
7+
void this_might_have_side_effects();
8+
9+
int __declspec(noinline) this_gets_hotpatched() {
10+
this_might_have_side_effects();
11+
return 42;
12+
}
13+
14+
int __declspec(noinline) this_does_not_get_hotpatched() {
15+
return this_gets_hotpatched() + 100;
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This verifies that hotpatch function attributes are correctly propagated when compiling directly to OBJ,
2+
// and that name mangling works as expected.
3+
//
4+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=?this_gets_hotpatched@@YAHXZ /Fo%t.obj %s
5+
// RUN: llvm-readobj --codeview %t.obj | FileCheck %s
6+
7+
void this_might_have_side_effects();
8+
9+
int __declspec(noinline) this_gets_hotpatched() {
10+
this_might_have_side_effects();
11+
return 42;
12+
}
13+
14+
// CHECK: Kind: S_HOTPATCHFUNC (0x1169)
15+
// CHECK-NEXT: Function: this_gets_hotpatched
16+
// CHECK-NEXT: Name: ?this_gets_hotpatched@@YAHXZ
17+
18+
extern "C" int __declspec(noinline) this_does_not_get_hotpatched() {
19+
return this_gets_hotpatched() + 100;
20+
}
21+
22+
// CHECK-NOT: S_HOTPATCHFUNC
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Global constant data such as exception handler tables should not be redirected by Windows Secure Hot-Patching
2+
//
3+
// 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
4+
5+
class Foo {
6+
public:
7+
int x;
8+
};
9+
10+
void this_might_throw();
11+
12+
extern "C" int this_gets_hotpatched(int k) {
13+
int ret;
14+
try {
15+
this_might_throw();
16+
ret = 1;
17+
} catch (Foo& f) {
18+
ret = 2;
19+
}
20+
return ret;
21+
}
22+
23+
// We expect that RTTI data is not redirected.
24+
// CHECK-NOT: "__ref_??_R0?AVFoo@@@8"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// This verifies that global variable redirection works correctly when using hotpatching.
2+
//
3+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=hp1,hp2,hp3,hp4 /clang:-S /clang:-o- %s | FileCheck %s
4+
5+
#ifdef __clang__
6+
#define NO_TAIL __attribute__((disable_tail_calls))
7+
#else
8+
#define NO_TAIL
9+
#endif
10+
11+
extern int g_data[10];
12+
13+
struct SomeData {
14+
int x;
15+
int y;
16+
};
17+
18+
const struct SomeData g_this_is_const = { 100, 200 };
19+
20+
struct HasPointers {
21+
int* ptr;
22+
int x;
23+
};
24+
25+
extern struct HasPointers g_has_pointers;
26+
27+
void take_data(const void* p);
28+
29+
void hp1() NO_TAIL {
30+
take_data(&g_data[5]);
31+
}
32+
33+
// CHECK: hp1:
34+
// CHECK: mov rcx, qword ptr [rip + __ref_g_data]
35+
// CHECK: add rcx, 20
36+
// CHECK: call take_data
37+
// CHECK: .seh_endproc
38+
39+
void hp2() NO_TAIL {
40+
// We do not expect string literals to be redirected.
41+
take_data("hello, world!");
42+
}
43+
44+
// CHECK: hp2:
45+
// CHECK: lea rcx, [rip + "??_C@_0O@KJBLMJCB@hello?0?5world?$CB?$AA@"]
46+
// CHECK: call take_data
47+
// CHECK: .seh_endproc
48+
49+
void hp3() NO_TAIL {
50+
// We do not expect g_this_is_const to be redirected because it is const
51+
// and contains no pointers.
52+
take_data(&g_this_is_const);
53+
}
54+
55+
// CHECK: hp3:
56+
// CHECK: lea rcx, [rip + g_this_is_const]
57+
// CHECK: call take_data
58+
// CHECK-NOT: __ref_g_this_is_const
59+
// CHECK: .seh_endproc
60+
61+
void hp4() NO_TAIL {
62+
take_data(&g_has_pointers);
63+
// We expect &g_has_pointers to be redirected.
64+
}
65+
66+
// CHECK: hp4:
67+
// CHECK: mov rcx, qword ptr [rip + __ref_g_has_pointers]
68+
// CHECK: call take_data
69+
// CHECK: .seh_endproc
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// This verifies that hotpatch function attributes are correctly propagated through LLVM IR when compiling with LTO.
2+
//
3+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-list=this_gets_hotpatched -flto /Fo%t.bc %s
4+
// RUN: llvm-dis %t.bc -o - | FileCheck %s
5+
//
6+
// CHECK: ; Function Attrs: marked_for_windows_hot_patching mustprogress nofree noinline norecurse nosync nounwind sspstrong willreturn memory(none) uwtable
7+
// CHECK-NEXT: define dso_local noundef i32 @this_gets_hotpatched() local_unnamed_addr #0 !dbg !13 {
8+
//
9+
// CHECK: ; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind sspstrong willreturn memory(none) uwtable
10+
// CHECK-NEXT: define dso_local noundef i32 @this_does_not_get_hotpatched() local_unnamed_addr #1 !dbg !19 {
11+
12+
int __declspec(noinline) this_gets_hotpatched() {
13+
return 42;
14+
}
15+
16+
int __declspec(noinline) this_does_not_get_hotpatched() {
17+
return this_gets_hotpatched() + 100;
18+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This verifies that hotpatch function attributes are correctly propagated when compiling directly to OBJ.
2+
//
3+
// RUN: echo this_gets_hotpatched > %t.patch-functions.txt
4+
// RUN: %clang_cl -c --target=x86_64-windows-msvc -O2 /Z7 -fms-secure-hotpatch-functions-file=%t.patch-functions.txt /Fo%t.obj %s
5+
// RUN: llvm-readobj --codeview %t.obj | FileCheck %s
6+
7+
void this_might_have_side_effects();
8+
9+
int __declspec(noinline) this_gets_hotpatched() {
10+
this_might_have_side_effects();
11+
return 42;
12+
}
13+
14+
// CHECK: Kind: S_HOTPATCHFUNC (0x1169)
15+
// CHECK-NEXT: Function: this_gets_hotpatched
16+
17+
int __declspec(noinline) this_does_not_get_hotpatched() {
18+
return this_gets_hotpatched() + 100;
19+
}
20+
21+
// CHECK-NOT: S_HOTPATCHFUNC

llvm/include/llvm/Bitcode/LLVMBitCodes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,8 @@ enum AttributeKindCodes {
798798
ATTR_KIND_NO_DIVERGENCE_SOURCE = 100,
799799
ATTR_KIND_SANITIZE_TYPE = 101,
800800
ATTR_KIND_CAPTURES = 102,
801+
ATTR_KIND_ALLOW_DIRECT_ACCESS_IN_HOT_PATCH_FUNCTION = 103,
802+
ATTR_KIND_MARKED_FOR_WINDOWS_HOT_PATCHING = 104,
801803
};
802804

803805
enum ComdatSelectionKindCodes {

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

llvm/include/llvm/DebugInfo/CodeView/SymbolRecord.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@ class CallerSym : public SymbolRecord {
177177
uint32_t RecordOffset = 0;
178178
};
179179

180+
class HotPatchFuncSym : public SymbolRecord {
181+
public:
182+
explicit HotPatchFuncSym(SymbolRecordKind Kind) : SymbolRecord(Kind) {}
183+
HotPatchFuncSym(uint32_t RecordOffset)
184+
: SymbolRecord(SymbolRecordKind::HotPatchFuncSym),
185+
RecordOffset(RecordOffset) {}
186+
187+
// This is an ItemID in the IPI stream, which points to an LF_FUNC_ID or
188+
// LF_MFUNC_ID record.
189+
TypeIndex Function;
190+
StringRef Name;
191+
192+
uint32_t RecordOffset = 0;
193+
};
194+
180195
struct DecodedAnnotation {
181196
StringRef Name;
182197
ArrayRef<uint8_t> Bytes;

llvm/include/llvm/IR/Attributes.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,17 @@ def CoroDestroyOnlyWhenComplete : EnumAttr<"coro_only_destroy_when_complete", In
389389
/// pipeline to perform elide on the call or invoke instruction.
390390
def CoroElideSafe : EnumAttr<"coro_elide_safe", IntersectPreserve, [FnAttr]>;
391391

392+
/// Function is marked for Windows Hot Patching
393+
def MarkedForWindowsHotPatching
394+
: EnumAttr<"marked_for_windows_hot_patching", IntersectPreserve, [FnAttr]>;
395+
396+
/// Global variable should not be accessed through a "__ref_" global variable in
397+
/// a hot patching function This attribute is applied to the global variable
398+
/// decl, not the hotpatched function.
399+
def AllowDirectAccessInHotPatchFunction
400+
: EnumAttr<"allow_direct_access_in_hot_patch_function",
401+
IntersectPreserve, [FnAttr]>;
402+
392403
/// Target-independent string attributes.
393404
def LessPreciseFPMAD : StrBoolAttr<"less-precise-fpmad">;
394405
def NoInfsFPMath : StrBoolAttr<"no-infs-fp-math">;

llvm/include/llvm/InitializePasses.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ void initializeVirtRegMapWrapperLegacyPass(PassRegistry &);
324324
void initializeVirtRegRewriterLegacyPass(PassRegistry &);
325325
void initializeWasmEHPreparePass(PassRegistry &);
326326
void initializeWinEHPreparePass(PassRegistry &);
327+
void initializeWindowsSecureHotPatchingPass(PassRegistry &);
327328
void initializeWriteBitcodePassPass(PassRegistry &);
328329
void initializeXRayInstrumentationLegacyPass(PassRegistry &);
329330

llvm/lib/Bitcode/Reader/BitcodeReader.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,6 +2244,10 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
22442244
return Attribute::NoExt;
22452245
case bitc::ATTR_KIND_CAPTURES:
22462246
return Attribute::Captures;
2247+
case bitc::ATTR_KIND_ALLOW_DIRECT_ACCESS_IN_HOT_PATCH_FUNCTION:
2248+
return Attribute::AllowDirectAccessInHotPatchFunction;
2249+
case bitc::ATTR_KIND_MARKED_FOR_WINDOWS_HOT_PATCHING:
2250+
return Attribute::MarkedForWindowsHotPatching;
22472251
}
22482252
}
22492253

llvm/lib/Bitcode/Writer/BitcodeWriter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,10 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
938938
return bitc::ATTR_KIND_NO_EXT;
939939
case Attribute::Captures:
940940
return bitc::ATTR_KIND_CAPTURES;
941+
case Attribute::AllowDirectAccessInHotPatchFunction:
942+
return bitc::ATTR_KIND_ALLOW_DIRECT_ACCESS_IN_HOT_PATCH_FUNCTION;
943+
case Attribute::MarkedForWindowsHotPatching:
944+
return bitc::ATTR_KIND_MARKED_FOR_WINDOWS_HOT_PATCHING;
941945
case Attribute::EndAttrKinds:
942946
llvm_unreachable("Can not encode end-attribute kinds marker.");
943947
case Attribute::None:

0 commit comments

Comments
 (0)