diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index a77232c281f7f..17f0a0794616f 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -505,6 +505,13 @@ class CodeGenOptions : public CodeGenOptionsBase { /// A list of functions that are replacable by the loader. std::vector 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 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 152df89118a6a..3217c96ed5903 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, Visibility<[ClangOption, CC1Option, CLOption]>, HelpText<"Ensure that all functions can be hotpatched at runtime">, MarshallingInfoFlag>; + +// See llvm/lib/CodeGen/WindowsSecureHotPatching.cpp +def fms_secure_hotpatch_functions_file + : Joined<["-"], "fms-secure-hotpatch-functions-file=">, + Group, + Visibility<[ClangOption, CC1Option, CLOption]>, + MarshallingInfoString>, + 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, + Visibility<[ClangOption, CC1Option, CLOption]>, + MarshallingInfoStringVector>, + 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, 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 16e49aab4fe61..fbbef6e69fda0 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 MSHotPatchFunctions; + public: CodeGenModule(ASTContext &C, IntrusiveRefCntPtr FS, const HeaderSearchOptions &headersearchopts, diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index a74fa81f3cf5b..f51ac4b8fdfa6 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6775,6 +6775,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..af0f141dfb039 --- /dev/null +++ b/clang/test/CodeGen/ms-secure-hotpatch-globals.c @@ -0,0 +1,69 @@ +// This verifies that global variable redirection works correctly when using hotpatching. +// +// 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 + +#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 hp1() NO_TAIL { + take_data(&g_data[5]); +} + +// CHECK: hp1: +// CHECK: mov rcx, qword ptr [rip + __ref_g_data] +// CHECK: add rcx, 20 +// CHECK: call take_data +// CHECK: .seh_endproc + +void hp2() NO_TAIL { + // We do not expect string literals to be redirected. + take_data("hello, world!"); +} + +// CHECK: hp2: +// CHECK: lea rcx, [rip + "??_C@_0O@KJBLMJCB@hello?0?5world?$CB?$AA@"] +// CHECK: call take_data +// CHECK: .seh_endproc + +void hp3() NO_TAIL { + // We do not expect g_this_is_const to be redirected because it is const + // and contains no pointers. + take_data(&g_this_is_const); +} + +// CHECK: hp3: +// CHECK: lea rcx, [rip + g_this_is_const] +// CHECK: call take_data +// CHECK-NOT: __ref_g_this_is_const +// CHECK: .seh_endproc + +void hp4() NO_TAIL { + take_data(&g_has_pointers); + // We expect &g_has_pointers to be redirected. +} + +// CHECK: hp4: +// CHECK: mov rcx, qword ptr [rip + __ref_g_has_pointers] +// CHECK: call take_data +// CHECK: .seh_endproc 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 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 31129b7e5cf77..600bdec77c6c2 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..0f5fd37c67c2e --- /dev/null +++ b/llvm/lib/CodeGen/WindowsSecureHotPatching.cpp @@ -0,0 +1,596 @@ +//===------ 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. +// +// 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 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 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 &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 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 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 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 &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{}); + + 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, Instruction *InsertionPoint, + SmallDenseMap &GVLoadMap) { + if (C->getValueID() == Value::GlobalVariableVal) { + GlobalVariable *GV = cast(C); + if (globalVariableNeedsRedirect(GV)) { + return GVLoadMap.at(GV); + } else { + return nullptr; + } + } + + // Scan the operands of this expression. + + SmallVector 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(OldValue)) { + ReplacedValue = rewriteGlobalVariablesInConstant( + OldConstant, InsertionPoint, GVLoadMap); + } + // 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(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. + IRBuilder<> Builder(InsertionPoint); + Builder.Insert(NewInst); + + return NewInst; +} + +static bool searchConstantExprForGlobalVariables( + Value *V, SmallDenseMap &GVLoadMap, + SmallVector &GVUses) { + + SmallVector ReplacedOperands; + + if (V->getValueID() == Value::GlobalVariableVal) { + GlobalVariable *GV = cast(V); + if (globalVariableNeedsRedirect(GV)) { + GVLoadMap[GV] = nullptr; + return true; + } else { + return false; + } + } + + if (User *U = dyn_cast(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_` where +// `` 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 &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 GVLoadMap; + SmallVector 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(V); + if (globalVariableNeedsRedirect(GV)) { + GVLoadMap.insert(std::make_pair(GV, nullptr)); + FoundAnyGVUses = true; + } + break; + } + + case Value::ConstantExprVal: { + ConstantExpr *CE = cast(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. + { + auto &EntryBlock = F.getEntryBlock(); + IRBuilder<> Builder(&EntryBlock, EntryBlock.begin()); + + for (auto &[GV, LoadValue] : GVLoadMap) { + assert(LoadValue == nullptr); + GlobalVariable *RefGV = getOrCreateRefVariable(F, RefMapping, GV); + LoadValue = Builder.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(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(OperandValue); + NewOperandValue = + rewriteGlobalVariablesInConstant(CE, GVUse.User, GVLoadMap); + 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::map(IO &IO) { IO.mapRequired("EntriesCount", Symbol.EntriesCount); } +template <> void SymbolRecordImpl::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 c4894c90c127f..8d90c57d6b371 100644 --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -938,6 +938,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" + +@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" + +@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" + +@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" + +@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" + +@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(); +}