Author: Daniel Paoliello Date: 2026-04-14T08:56:47-07:00 New Revision: 530688456deef791dc294891e82d44ca4e6c307f
URL: https://github.com/llvm/llvm-project/commit/530688456deef791dc294891e82d44ca4e6c307f DIFF: https://github.com/llvm/llvm-project/commit/530688456deef791dc294891e82d44ca4e6c307f.diff LOG: [win] Add a flag to control the Control Flow Guard mechanism on Windows (#176276) Windows Control Flow Guard (CFG) has two different "mechanisms" or "patterns": * Dispatch: the caller calls into the CFG function, which both checks the target callee and then calls it. * Check: the caller calls the CFG function which only checks the target callee and then must separately call the callee. LLVM has followed MSVC's pattern for selecting the mechanism based on the target architecture. These defaults in MSVC are based on tests for performance: Dispatch produces a smaller code size, whereas Check is more friendly to branch predictors. It is possible, however, for a given workload, call pattern or target CPU that someone may want to select a different mechanism to use for their code. This change adds a new Clang and CC1 flag to force a CFG mechanism: `-fwin-cfg-mechanism`. This can be set to `automatic` (lets LLVM choose a mechanism), `force-dispatch` or `force-check`. Also adds the support for the equivalent MSVC flag `/d2guardcfgdispatch`. NOTE: Arm64EC only supports the check mechanism. It should be noted that MSVC emits the "dispatch" name for the call checker (for legacy reasons) but uses the check mechanism. Added: clang/test/CodeGen/cfguard-mechanism.c llvm/test/Linker/cfguard.ll Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Basic/CodeGenOptions.def clang/include/clang/Basic/CodeGenOptions.h clang/include/clang/Options/Options.td clang/lib/CodeGen/CodeGenModule.cpp clang/lib/Driver/ToolChains/Clang.cpp clang/test/Driver/cl-options.c llvm/docs/LangRef.rst llvm/include/llvm/Support/CodeGen.h llvm/include/llvm/Transforms/CFGuard.h llvm/lib/Passes/PassBuilder.cpp llvm/lib/Passes/PassRegistry.def llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp llvm/lib/Target/AArch64/AArch64TargetMachine.cpp llvm/lib/Target/ARM/ARMTargetMachine.cpp llvm/lib/Target/X86/X86CallingConv.td llvm/lib/Target/X86/X86CodeGenPassBuilder.cpp llvm/lib/Target/X86/X86RegisterInfo.cpp llvm/lib/Target/X86/X86TargetMachine.cpp llvm/lib/Transforms/CFGuard/CFGuard.cpp llvm/test/CodeGen/AArch64/cfguard-module-flag.ll llvm/test/CodeGen/ARM/cfguard-module-flag.ll llvm/test/CodeGen/X86/cfguard-module-flag.ll Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 6d7a8631f0d58..c9a470c7748b4 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -217,6 +217,16 @@ New Compiler Flags higher), accurate source locations are used; otherwise, a heuristic fallback is used with a note suggesting how to enable debug info for better accuracy. +- New option ``-fwin-cfg-mechanism=`` added to control the mechanism used by + Control Flow Guard on Windows. Accepted values are ``automatic`` (default), + ``dispatch``, and ``check``. The ``dispatch`` mechanism uses the dispatch + function to perform indirect call checks and can improve performance, while + ``check`` uses the traditional check mechanism. +- New ``-cl`` option ``/d2guardcfgdispatch`` added to match MSVC. This acts as a + shorthand for ``-fwin-cfg-mechanism=dispatch``. +- New ``-cl`` option ``/d2guardcfgdispatch-`` added to match MSVC. This acts as a + shorthand for ``-fwin-cfg-mechanism=check``. + Deprecated Compiler Flags ------------------------- diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 6cee3e8acda3c..0cd8f35339cf7 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -517,6 +517,10 @@ CODEGENOPT(AllResourcesBound, 1, 0, Benign) ENUM_CODEGENOPT(WinX64EHUnwindV2, WinX64EHUnwindV2Mode, 2, WinX64EHUnwindV2Mode::Disabled, Benign) +/// Controls the mechanism used for Control Flow Guard (CFG) on Windows. +ENUM_CODEGENOPT(WinControlFlowGuardMechanism, ControlFlowGuardMechanism, + 2, ControlFlowGuardMechanism::Automatic, Benign) + /// Adds attributes that prevent outlining (`-mno-outline`) CODEGENOPT(DisableOutlining, 1, 0, Benign) diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 9454f7672b7e1..147730bdb33fd 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -67,6 +67,7 @@ class CodeGenOptionsBase { using VectorLibrary = llvm::driver::VectorLibrary; using ZeroCallUsedRegsKind = llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind; using WinX64EHUnwindV2Mode = llvm::WinX64EHUnwindV2Mode; + using ControlFlowGuardMechanism = llvm::ControlFlowGuardMechanism; using DebugCompressionType = llvm::DebugCompressionType; using EmitDwarfUnwindType = llvm::EmitDwarfUnwindType; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 5d2741e7e30e5..96ecc8fbdeb83 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -2342,6 +2342,14 @@ def winx64_eh_unwindv2 NormalizedValues<["Disabled", "BestEffort", "Required"]>, NormalizedValuesScope<"llvm::WinX64EHUnwindV2Mode">, MarshallingInfoEnum<CodeGenOpts<"WinX64EHUnwindV2">, "Disabled">; +def win_cfg_mechanism + : Joined<["-"], "fwin-cfg-mechanism=">, Group<f_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Sets the mechanism to use for Control Flow Guard on Windows">, + Values<"automatic,dispatch,check">, + NormalizedValues<["Automatic", "Dispatch", "Check"]>, + NormalizedValuesScope<"llvm::ControlFlowGuardMechanism">, + MarshallingInfoEnum<CodeGenOpts<"WinControlFlowGuardMechanism">, "Automatic">; def fexcess_precision_EQ : Joined<["-"], "fexcess-precision=">, Group<f_Group>, Visibility<[ClangOption, CLOption]>, HelpText<"Allows control over excess precision on targets where native " @@ -9671,6 +9679,10 @@ def _SLASH_d2epilogunwindrequirev2 : CLFlag<"d2epilogunwindrequirev2">, HelpText<"Require generation of unwind v2 (epilog) information for x64 Windows">; def _SLASH_d2guardnochecks : CLFlag<"d2guardnochecks">, HelpText<"When used with /guard:cf, emits Windows Control Flow Guard tables only (no checks)">; +def _SLASH_d2guardcfgdispatch : CLFlag<"d2guardcfgdispatch">, + HelpText<"Use the dispatch mechanism to call the Control Flow Guard checker">; +def _SLASH_d2guardcfgdispatch_ : CLFlag<"d2guardcfgdispatch-">, + HelpText<"Use the check mechanism to call the Control Flow Guard checker">; def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">; def _SLASH_EP : CLFlag<"EP">, HelpText<"Disable linemarker output and preprocess to stdout">; diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index bb399a71aa047..b5ff40c2bd175 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1163,6 +1163,13 @@ void CodeGenModule::Release() { llvm::Module::Warning, "cfguard", static_cast<unsigned>(llvm::ControlFlowGuardMode::TableOnly)); } + if (CodeGenOpts.getWinControlFlowGuardMechanism() != + llvm::ControlFlowGuardMechanism::Automatic) { + // Specify the Control Flow Guard mechanism to use on Windows. + getModule().addModuleFlag( + llvm::Module::Warning, "cfguard-mechanism", + static_cast<unsigned>(CodeGenOpts.getWinControlFlowGuardMechanism())); + } if (CodeGenOpts.EHContGuard) { // Function ID tables for EH Continuation Guard. getModule().addModuleFlag(llvm::Module::Warning, "ehcontguard", 1); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index f685abe9dad35..267e674441599 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7598,6 +7598,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, // Unwind v2 (epilog) information for x64 Windows. Args.AddLastArg(CmdArgs, options::OPT_winx64_eh_unwindv2); + // Control Flow Guard mechanism for Windows. + Args.AddLastArg(CmdArgs, options::OPT_win_cfg_mechanism); + // C++ "sane" operator new. Args.addOptOutFlag(CmdArgs, options::OPT_fassume_sane_operator_new, options::OPT_fno_assume_sane_operator_new); @@ -8738,6 +8741,12 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType, else if (HasCFGuardNoChecks) CmdArgs.push_back("-cfguard-no-checks"); + // Control Flow Guard mechanism for Windows. + if (Args.hasArg(options::OPT__SLASH_d2guardcfgdispatch_)) + CmdArgs.push_back("-fwin-cfg-mechanism=check"); + else if (Args.hasArg(options::OPT__SLASH_d2guardcfgdispatch)) + CmdArgs.push_back("-fwin-cfg-mechanism=dispatch"); + for (const auto &FuncOverride : Args.getAllArgValues(options::OPT__SLASH_funcoverride)) { CmdArgs.push_back(Args.MakeArgString( diff --git a/clang/test/CodeGen/cfguard-mechanism.c b/clang/test/CodeGen/cfguard-mechanism.c new file mode 100644 index 0000000000000..e3b3a6eae2d7f --- /dev/null +++ b/clang/test/CodeGen/cfguard-mechanism.c @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - | FileCheck %s -check-prefix=AUTOMATIC +// RUN: %clang_cc1 -fwin-cfg-mechanism=automatic -emit-llvm %s -o - | FileCheck %s -check-prefix=AUTOMATIC +// RUN: %clang_cc1 -fwin-cfg-mechanism=dispatch -emit-llvm %s -o - | FileCheck %s -check-prefix=DISPATCH +// RUN: %clang_cc1 -fwin-cfg-mechanism=check -emit-llvm %s -o - | FileCheck %s -check-prefix=CHECK +// RUN: %clang -fwin-cfg-mechanism=dispatch -S -emit-llvm %s -o - | FileCheck %s -check-prefix=DISPATCH + +void f(void) {} + +// CHECK: !"cfguard-mechanism", i32 1} +// DISPATCH: !"cfguard-mechanism", i32 2} +// AUTOMATIC-NOT: "cfguard-mechanism" diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c index 07a09a16170cc..b77a6cd6eb21f 100644 --- a/clang/test/Driver/cl-options.c +++ b/clang/test/Driver/cl-options.c @@ -861,4 +861,10 @@ // FUNCOVERRIDE: -loader-replaceable-function=override_me1 // FUNCOVERRIDE-SAME: -loader-replaceable-function=override_me2 +// RUN: %clang_cl /d2guardcfgdispatch /c -### -- %s 2>&1 | FileCheck %s --check-prefix=GUARDCFGDISPATCH +// GUARDCFGDISPATCH: -fwin-cfg-mechanism=dispatch + +// RUN: %clang_cl /d2guardcfgdispatch- /c -### -- %s 2>&1 | FileCheck %s --check-prefix=GUARDCFGDISPATCHNEG +// GUARDCFGDISPATCHNEG: -fwin-cfg-mechanism=check + void f(void) { } diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 86325ef731381..6f34005f3e945 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -9123,6 +9123,36 @@ For example: This will change the stack alignment to 8B. +Windows Control Flow Guard Metadata +----------------------------------- + +Controls what Control Flow Guard (CFG) checks are performed, how they are +performed, and what metadata is emitted. There are multiple flags that can be +used to control diff erent aspects of CFG. Using two diff erent values for the +same flag will raise a warning when linking. + +To pass this information to the backend, these options are encoded in module +flags metadata, using the following key-value pairs: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Key + - Value + + * - cfguard + - * 0 --- CFG is completely disabled. + * 1 --- The CFG table is emitted, but no checks are performed. + * 2 --- The CFG table is emitted and checks are performed. + + * - cfguard-mechanism + - * 0 --- CFG uses the default mechanism for the architecture. + * 1 --- CFG uses the "check" mechanism. This will result in a separate + call to the checker function and then one to the target. + * 2 --- CFG uses the "dispatch" mechanism. This calls a dispatcher + function which both checks and then calls the target. + Embedded Objects Names Metadata =============================== diff --git a/llvm/include/llvm/Support/CodeGen.h b/llvm/include/llvm/Support/CodeGen.h index 65d262a087378..52f00c3258c0f 100644 --- a/llvm/include/llvm/Support/CodeGen.h +++ b/llvm/include/llvm/Support/CodeGen.h @@ -183,6 +183,13 @@ namespace llvm { Enabled = 2, }; + enum class ControlFlowGuardMechanism { + // Choose the mechanism automatically based on the target. + Automatic = 0, + Check = 1, + Dispatch = 2, + }; + } // namespace llvm #endif diff --git a/llvm/include/llvm/Transforms/CFGuard.h b/llvm/include/llvm/Transforms/CFGuard.h index df5385718becc..175d62851f363 100644 --- a/llvm/include/llvm/Transforms/CFGuard.h +++ b/llvm/include/llvm/Transforms/CFGuard.h @@ -24,18 +24,12 @@ class CFGuardPass : public PassInfoMixin<CFGuardPass> { public: enum class Mechanism { Check, Dispatch }; - CFGuardPass(Mechanism M = Mechanism::Check) : GuardMechanism(M) {} + CFGuardPass() {} LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM); - -private: - Mechanism GuardMechanism; }; -/// Insert Control FLow Guard checks on indirect function calls. -LLVM_ABI FunctionPass *createCFGuardCheckPass(); - -/// Insert Control FLow Guard dispatches on indirect function calls. -LLVM_ABI FunctionPass *createCFGuardDispatchPass(); +/// Insert Control Flow Guard checks on indirect function calls. +LLVM_ABI FunctionPass *createCFGuardPass(); LLVM_ABI bool isCFGuardCall(const CallBase *CB); LLVM_ABI bool isCFGuardFunction(const GlobalValue *GV); diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp index 715f4d2c0abc4..2751961e684d2 100644 --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -884,26 +884,6 @@ Expected<bool> parsePostOrderFunctionAttrsPassOptions(StringRef Params) { Params, "skip-non-recursive-function-attrs", "PostOrderFunctionAttrs"); } -Expected<CFGuardPass::Mechanism> parseCFGuardPassOptions(StringRef Params) { - if (Params.empty()) - return CFGuardPass::Mechanism::Check; - - auto [Param, RHS] = Params.split(';'); - if (!RHS.empty()) - return make_error<StringError>( - formatv("too many CFGuardPass parameters '{}'", Params).str(), - inconvertibleErrorCode()); - - if (Param == "check") - return CFGuardPass::Mechanism::Check; - if (Param == "dispatch") - return CFGuardPass::Mechanism::Dispatch; - - return make_error<StringError>( - formatv("invalid CFGuardPass mechanism: '{}'", Param).str(), - inconvertibleErrorCode()); -} - Expected<bool> parseEarlyCSEPassOptions(StringRef Params) { return PassBuilder::parseSinglePassOption(Params, "memssa", "EarlyCSE"); } diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def index 9fe40d441e0af..9f7b3cf4df77b 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -412,6 +412,7 @@ FUNCTION_PASS("atomic-expand", AtomicExpandPass(*TM)) FUNCTION_PASS("bdce", BDCEPass()) FUNCTION_PASS("break-crit-edges", BreakCriticalEdgesPass()) FUNCTION_PASS("callsite-splitting", CallSiteSplittingPass()) +FUNCTION_PASS("cfguard", CFGuardPass()) FUNCTION_PASS("chr", ControlHeightReductionPass()) FUNCTION_PASS("codegenprepare", CodeGenPreparePass(*TM)) FUNCTION_PASS("complex-deinterleaving", ComplexDeinterleavingPass(*TM)) @@ -571,10 +572,6 @@ FUNCTION_PASS("wasm-eh-prepare", WasmEHPreparePass()) #ifndef FUNCTION_PASS_WITH_PARAMS #define FUNCTION_PASS_WITH_PARAMS(NAME, CLASS, CREATE_PASS, PARSER, PARAMS) #endif -FUNCTION_PASS_WITH_PARAMS( - "cfguard", "CFGuardPass", - [](CFGuardPass::Mechanism M) { return CFGuardPass(M); }, - parseCFGuardPassOptions, "check;dispatch") FUNCTION_PASS_WITH_PARAMS( "early-cse", "EarlyCSEPass", [](bool UseMemorySSA) { return EarlyCSEPass(UseMemorySSA); }, diff --git a/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp b/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp index 866e2f9c4218c..44f76f9e8772b 100644 --- a/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp @@ -21,6 +21,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/IR/CallingConv.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/GlobalAlias.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" @@ -802,6 +803,21 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) { // Check if this module has the cfguard flag and read its value. CFGuardModuleFlag = M->getControlFlowGuardMode(); + // Warn if the module flag requests an unsupported CFGuard mechanism. + if (CFGuardModuleFlag == ControlFlowGuardMode::Enabled) { + if (auto *CI = mdconst::dyn_extract_or_null<ConstantInt>( + Mod.getModuleFlag("cfguard-mechanism"))) { + auto MechanismOverride = + static_cast<ControlFlowGuardMechanism>(CI->getZExtValue()); + if (MechanismOverride != ControlFlowGuardMechanism::Automatic && + MechanismOverride != ControlFlowGuardMechanism::Check) + Mod.getContext().diagnose( + DiagnosticInfoGeneric("only the Check Control Flow Guard mechanism " + "is supported for Arm64EC", + DS_Warning)); + } + } + PtrTy = PointerType::getUnqual(M->getContext()); I64Ty = Type::getInt64Ty(M->getContext()); VoidTy = Type::getVoidTy(M->getContext()); diff --git a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp index 929001ed0ae6b..a2dbdf2cef1e7 100644 --- a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp +++ b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp @@ -691,7 +691,7 @@ void AArch64PassConfig::addIRPasses() { if (TM->getTargetTriple().isWindowsArm64EC()) addPass(createAArch64Arm64ECCallLoweringPass()); else - addPass(createCFGuardCheckPass()); + addPass(createCFGuardPass()); } if (TM->Options.JMCInstrument) diff --git a/llvm/lib/Target/ARM/ARMTargetMachine.cpp b/llvm/lib/Target/ARM/ARMTargetMachine.cpp index f8dbc7907f64b..9e8c3cdd29fe8 100644 --- a/llvm/lib/Target/ARM/ARMTargetMachine.cpp +++ b/llvm/lib/Target/ARM/ARMTargetMachine.cpp @@ -390,7 +390,7 @@ void ARMPassConfig::addIRPasses() { // Add Control Flow Guard checks. if (TM->getTargetTriple().isOSWindows()) - addPass(createCFGuardCheckPass()); + addPass(createCFGuardPass()); if (TM->Options.JMCInstrument) addPass(createJMCInstrumenterPass()); diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td index f020e0b55141c..7d24126f820f8 100644 --- a/llvm/lib/Target/X86/X86CallingConv.td +++ b/llvm/lib/Target/X86/X86CallingConv.td @@ -1217,8 +1217,10 @@ def CSR_Win32_CFGuard_Check_NoSSE : CalleeSavedRegs<(add CSR_32_RegCall_NoSSE, E def CSR_Win32_CFGuard_Check : CalleeSavedRegs<(add CSR_32_RegCall, ECX)>; def CSR_Win64_RegCall_NoSSE : CalleeSavedRegs<(add RBX, RBP, (sequence "R%u", 10, 15))>; -def CSR_Win64_RegCall : CalleeSavedRegs<(add CSR_Win64_RegCall_NoSSE, +def CSR_Win64_RegCall : CalleeSavedRegs<(add CSR_Win64_RegCall_NoSSE, (sequence "XMM%u", 8, 15))>; +def CSR_Win64_CFGuard_Check_NoSSE : CalleeSavedRegs<(add CSR_Win64_RegCall_NoSSE, RCX)>; +def CSR_Win64_CFGuard_Check : CalleeSavedRegs<(add CSR_Win64_RegCall, RCX)>; def CSR_SysV64_RegCall_NoSSE : CalleeSavedRegs<(add RBX, RBP, (sequence "R%u", 12, 15))>; def CSR_SysV64_RegCall : CalleeSavedRegs<(add CSR_SysV64_RegCall_NoSSE, diff --git a/llvm/lib/Target/X86/X86CodeGenPassBuilder.cpp b/llvm/lib/Target/X86/X86CodeGenPassBuilder.cpp index 87dd17bf544f7..7bc009f24a412 100644 --- a/llvm/lib/Target/X86/X86CodeGenPassBuilder.cpp +++ b/llvm/lib/Target/X86/X86CodeGenPassBuilder.cpp @@ -85,9 +85,7 @@ void X86CodeGenPassBuilder::addIRPasses(PassManagerWrapper &PMW) const { // Add Control Flow Guard checks. const Triple &TT = TM.getTargetTriple(); if (TT.isOSWindows()) - addFunctionPass(CFGuardPass(TT.isX86_64() ? CFGuardPass::Mechanism::Dispatch - : CFGuardPass::Mechanism::Check), - PMW); + addFunctionPass(CFGuardPass(), PMW); if (TM.Options.JMCInstrument) { flushFPMsToMPM(PMW); diff --git a/llvm/lib/Target/X86/X86RegisterInfo.cpp b/llvm/lib/Target/X86/X86RegisterInfo.cpp index 83dd6ea287e83..c92e20ad7ef13 100644 --- a/llvm/lib/Target/X86/X86RegisterInfo.cpp +++ b/llvm/lib/Target/X86/X86RegisterInfo.cpp @@ -434,9 +434,13 @@ X86RegisterInfo::getCallPreservedMask(const MachineFunction &MF, CSR_32_RegCall_NoSSE_RegMask); } case CallingConv::CFGuard_Check: - assert(!Is64Bit && "CFGuard check mechanism only used on 32-bit X86"); - return (HasSSE ? CSR_Win32_CFGuard_Check_RegMask - : CSR_Win32_CFGuard_Check_NoSSE_RegMask); + if (Is64Bit) { + return (HasSSE ? CSR_Win64_CFGuard_Check_RegMask + : CSR_Win64_CFGuard_Check_NoSSE_RegMask); + } else { + return (HasSSE ? CSR_Win32_CFGuard_Check_RegMask + : CSR_Win32_CFGuard_Check_NoSSE_RegMask); + } case CallingConv::Cold: if (Is64Bit) return CSR_64_MostRegs_RegMask; diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp index bb7e4d4396305..eba9486745f54 100644 --- a/llvm/lib/Target/X86/X86TargetMachine.cpp +++ b/llvm/lib/Target/X86/X86TargetMachine.cpp @@ -443,11 +443,7 @@ void X86PassConfig::addIRPasses() { // Add Control Flow Guard checks. const Triple &TT = TM->getTargetTriple(); if (TT.isOSWindows()) { - if (TT.isX86_64()) { - addPass(createCFGuardDispatchPass()); - } else { - addPass(createCFGuardCheckPass()); - } + addPass(createCFGuardPass()); } if (TM->Options.JMCInstrument) diff --git a/llvm/lib/Transforms/CFGuard/CFGuard.cpp b/llvm/lib/Transforms/CFGuard/CFGuard.cpp index 28d0eddca7ce2..a9819a54d20e5 100644 --- a/llvm/lib/Transforms/CFGuard/CFGuard.cpp +++ b/llvm/lib/Transforms/CFGuard/CFGuard.cpp @@ -38,24 +38,11 @@ namespace { /// Adds Control Flow Guard (CFG) checks on indirect function calls/invokes. /// These checks ensure that the target address corresponds to the start of an -/// address-taken function. X86_64 targets use the Mechanism::Dispatch -/// mechanism. X86, ARM, and AArch64 targets use the Mechanism::Check machanism. +/// address-taken function. class CFGuardImpl { public: using Mechanism = CFGuardPass::Mechanism; - CFGuardImpl(Mechanism M) : GuardMechanism(M) { - // Get or insert the guard check or dispatch global symbols. - switch (GuardMechanism) { - case Mechanism::Check: - GuardFnName = GuardCheckFunctionName; - break; - case Mechanism::Dispatch: - GuardFnName = GuardDispatchFunctionName; - break; - } - } - /// Inserts a Control Flow Guard (CFG) check on an indirect call using the CFG /// check mechanism. When the image is loaded, the loader puts the appropriate /// guard check function pointer in the __guard_check_icall_fptr global @@ -148,7 +135,6 @@ class CFGuardImpl { private: // Only add checks if the module has them enabled. ControlFlowGuardMode CFGuardModuleFlag = ControlFlowGuardMode::Disabled; - StringRef GuardFnName; Mechanism GuardMechanism = Mechanism::Check; FunctionType *GuardFnType = nullptr; PointerType *GuardFnPtrType = nullptr; @@ -162,7 +148,7 @@ class CFGuard : public FunctionPass { static char ID; // Default constructor required for the INITIALIZE_PASS macro. - CFGuard(CFGuardImpl::Mechanism M) : FunctionPass(ID), Impl(M) {} + CFGuard() : FunctionPass(ID) {} bool doInitialization(Module &M) override { return Impl.doInitialization(M); } bool runOnFunction(Function &F) override { return Impl.runOnFunction(F); } @@ -239,12 +225,36 @@ bool CFGuardImpl::doInitialization(Module &M) { if (CFGuardModuleFlag != ControlFlowGuardMode::Enabled) return false; + // Determine the guard mechanism to use. + ControlFlowGuardMechanism MechanismOverride = + ControlFlowGuardMechanism::Automatic; + if (auto *CI = mdconst::dyn_extract_or_null<ConstantInt>( + M.getModuleFlag("cfguard-mechanism"))) + MechanismOverride = + static_cast<ControlFlowGuardMechanism>(CI->getZExtValue()); + switch (MechanismOverride) { + case ControlFlowGuardMechanism::Check: + GuardMechanism = Mechanism::Check; + break; + case ControlFlowGuardMechanism::Dispatch: + GuardMechanism = Mechanism::Dispatch; + break; + default: + // X86_64 uses dispatch; all other architectures use check. + GuardMechanism = + M.getTargetTriple().isX86_64() ? Mechanism::Dispatch : Mechanism::Check; + break; + } + // Set up prototypes for the guard check and dispatch functions. GuardFnType = FunctionType::get(Type::getVoidTy(M.getContext()), {PointerType::getUnqual(M.getContext())}, false); GuardFnPtrType = PointerType::get(M.getContext(), 0); + StringRef GuardFnName = GuardMechanism == Mechanism::Check + ? GuardCheckFunctionName + : GuardDispatchFunctionName; GuardFnGlobal = M.getOrInsertGlobal(GuardFnName, GuardFnPtrType, [&] { auto *Var = new GlobalVariable(M, GuardFnPtrType, false, GlobalVariable::ExternalLinkage, nullptr, @@ -294,7 +304,7 @@ bool CFGuardImpl::runOnFunction(Function &F) { } PreservedAnalyses CFGuardPass::run(Function &F, FunctionAnalysisManager &FAM) { - CFGuardImpl Impl(GuardMechanism); + CFGuardImpl Impl; bool Changed = Impl.doInitialization(*F.getParent()); Changed |= Impl.runOnFunction(F); return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); @@ -303,13 +313,7 @@ PreservedAnalyses CFGuardPass::run(Function &F, FunctionAnalysisManager &FAM) { char CFGuard::ID = 0; INITIALIZE_PASS(CFGuard, "CFGuard", "CFGuard", false, false) -FunctionPass *llvm::createCFGuardCheckPass() { - return new CFGuard(CFGuardPass::Mechanism::Check); -} - -FunctionPass *llvm::createCFGuardDispatchPass() { - return new CFGuard(CFGuardPass::Mechanism::Dispatch); -} +FunctionPass *llvm::createCFGuardPass() { return new CFGuard(); } bool llvm::isCFGuardCall(const CallBase *CB) { return CB->getCallingConv() == CallingConv::CFGuard_Check || diff --git a/llvm/test/CodeGen/AArch64/cfguard-module-flag.ll b/llvm/test/CodeGen/AArch64/cfguard-module-flag.ll index 7cd9cf1ed2e5c..f279aad41ade5 100644 --- a/llvm/test/CodeGen/AArch64/cfguard-module-flag.ll +++ b/llvm/test/CodeGen/AArch64/cfguard-module-flag.ll @@ -1,13 +1,22 @@ - -; RUN: llc < %s -mtriple=aarch64-pc-windows-msvc | FileCheck %s -; RUN: llc < %s -mtriple=aarch64-w64-windows-gnu | FileCheck %s +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=aarch64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,TABLEONLY +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=aarch64-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,TABLEONLY +; RUN: sed -e s/.normal:// %s | llc -mtriple=aarch64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK +; RUN: sed -e s/.normal:// %s | llc -mtriple=aarch64-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,USECHECK +; RUN: sed -e s/.normal:// %s | llc -mtriple=arm64ec-pc-windows-msvc 2>&1 | FileCheck %s --check-prefixes=CHECK,EC,NOECWARN +; RUN: sed -e s/.check:// %s | llc -mtriple=aarch64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK +; RUN: sed -e s/.check:// %s | llc -mtriple=arm64ec-pc-windows-msvc 2>&1 | FileCheck %s --check-prefixes=CHECK,EC,NOECWARN +; RUN: sed -e s/.dispatch:// %s | llc -mtriple=aarch64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USEDISPATCH +; RUN: sed -e s/.dispatch:// %s | llc -mtriple=arm64ec-pc-windows-msvc 2>&1 | FileCheck %s --check-prefixes=CHECK,EC,ECWARN ; Control Flow Guard is currently only available on Windows -; Test that Control Flow Guard checks are not added in modules with the -; cfguard=1 flag (emit tables but no checks). +; NOECWARN-NOT: warning: +; ECWARN: warning: only the Check Control Flow Guard mechanism is supported for Arm64EC ; If no checks were inserted then the GuardCF bit shouldn't be set in @feat.00. -; CHECK: "@feat.00" = 0 +; TABLEONLY: "@feat.00" = 0 +; USECHECK: "@feat.00" = 2048 +; USEDISPATCH: "@feat.00" = 2048 +; EC: "@feat.00" = 2048 declare void @target_func() @@ -20,9 +29,30 @@ entry: call void %0() ret void - ; CHECK-NOT: __guard_check_icall_fptr - ; CHECK-NOT: __guard_dispatch_icall_fptr + ; CHECK: adrp + + ; USECHECK-SAME: __guard_check_icall_fptr + ; USECHECK-NOT: __guard_dispatch_icall_fptr + + ; USEDISPATCH-SAME: __guard_dispatch_icall_fptr + ; USEDISPATCH-NOT: __guard_check_icall_fptr + + ; TABLEONLY-SAME: target_func + ; TABLEONLY-NOT: __guard_dispatch_icall_fptr + ; TABLEONLY-NOT: __guard_check_icall_fptr + + ; Arm64EC Always uses check + ; EC-SAME: __os_arm64x_check_icall_cfg + ; EC-NOT: _dispatch_icall_ } -!llvm.module.flags = !{!0} +; CHECK: .section .gfids$y,"dr" + !0 = !{i32 2, !"cfguard", i32 1} +!1 = !{i32 2, !"cfguard", i32 2} +!2 = !{i32 2, !"cfguard-mechanism", i32 1} +!3 = !{i32 2, !"cfguard-mechanism", i32 2} +;tableonly: !llvm.module.flags = !{!0} +;normal: !llvm.module.flags = !{!1} +;check: !llvm.module.flags = !{!1, !2} +;dispatch: !llvm.module.flags = !{!1, !3} diff --git a/llvm/test/CodeGen/ARM/cfguard-module-flag.ll b/llvm/test/CodeGen/ARM/cfguard-module-flag.ll index bb3c04a54caff..e17730fb3e777 100644 --- a/llvm/test/CodeGen/ARM/cfguard-module-flag.ll +++ b/llvm/test/CodeGen/ARM/cfguard-module-flag.ll @@ -1,12 +1,11 @@ - -; RUN: llc < %s -mtriple=arm-pc-windows-msvc | FileCheck %s -; RUN: llc < %s -mtriple=arm-w64-windows-gnu | FileCheck %s +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=arm-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,TABLEONLY +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=arm-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,TABLEONLY +; RUN: sed -e s/.normal:// %s | llc -mtriple=arm-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK +; RUN: sed -e s/.normal:// %s | llc -mtriple=arm-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,USECHECK +; RUN: sed -e s/.check:// %s | llc -mtriple=arm-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK +; RUN: sed -e s/.dispatch:// %s | llc -mtriple=arm-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USEDISPATCH ; Control Flow Guard is currently only available on Windows -; Test that Control Flow Guard checks are not added in modules with the -; cfguard=1 flag (emit tables but no checks). - - declare void @target_func() define void @func_in_module_without_cfguard() #0 { @@ -18,10 +17,27 @@ entry: call void %0() ret void - ; CHECK-NOT: __guard_check_icall_fptr - ; CHECK-NOT: __guard_dispatch_icall_fptr + ; CHECK: movw + + ; USECHECK-SAME: __guard_check_icall_fptr + ; USECHECK-NOT: __guard_dispatch_icall_fptr + + ; USEDISPATCH-SAME: __guard_dispatch_icall_fptr + ; USEDISPATCH-NOT: __guard_check_icall_fptr + + ; TABLEONLY-SAME: target_func + ; TABLEONLY-NOT: __guard_dispatch_icall_fptr + ; TABLEONLY-NOT: __guard_check_icall_fptr } attributes #0 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "frame-pointer"="all" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="cortex-a9" "target-features"="+armv7-a,+dsp,+fp16,+neon,+strict-align,+thumb-mode,+vfp3" "use-soft-float"="false"} -!llvm.module.flags = !{!0} +; CHECK: .section .gfids$y,"dr" + !0 = !{i32 2, !"cfguard", i32 1} +!1 = !{i32 2, !"cfguard", i32 2} +!2 = !{i32 2, !"cfguard-mechanism", i32 1} +!3 = !{i32 2, !"cfguard-mechanism", i32 2} +;tableonly: !llvm.module.flags = !{!0} +;normal: !llvm.module.flags = !{!1} +;check: !llvm.module.flags = !{!1, !2} +;dispatch: !llvm.module.flags = !{!1, !3} diff --git a/llvm/test/CodeGen/X86/cfguard-module-flag.ll b/llvm/test/CodeGen/X86/cfguard-module-flag.ll index bf0120781292f..0b3006231663b 100644 --- a/llvm/test/CodeGen/X86/cfguard-module-flag.ll +++ b/llvm/test/CodeGen/X86/cfguard-module-flag.ll @@ -1,18 +1,23 @@ -; RUN: llc < %s -mtriple=i686-pc-windows-msvc | FileCheck %s -check-prefix=X86 -; RUN: llc < %s -mtriple=x86_64-pc-windows-msvc | FileCheck %s -check-prefix=X64 -; RUN: llc < %s -mtriple=i686-w64-windows-gnu | FileCheck %s -check-prefix=X86 -; RUN: llc < %s -mtriple=x86_64-w64-windows-gnu | FileCheck %s -check-prefix=X64 +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=i686-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,TABLEONLY,X86NOFEAT +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=x86_64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,TABLEONLY,X64NOFEAT +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=i686-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,TABLEONLY,X86NOFEAT +; RUN: sed -e s/.tableonly:// %s | llc -mtriple=x86_64-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,TABLEONLY,X64NOFEAT +; RUN: sed -e s/.normal:// %s | llc -mtriple=i686-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK,X86FEAT +; RUN: sed -e s/.normal:// %s | llc -mtriple=x86_64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USEDISPATCH,X64FEAT +; RUN: sed -e s/.normal:// %s | llc -mtriple=i686-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,USECHECK,X86FEAT +; RUN: sed -e s/.normal:// %s | llc -mtriple=x86_64-w64-windows-gnu | FileCheck %s --check-prefixes=CHECK,USEDISPATCH +; RUN: sed -e s/.check:// %s | llc -mtriple=i686-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK,X86FEAT +; RUN: sed -e s/.check:// %s | llc -mtriple=x86_64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USECHECK,X64FEAT +; RUN: sed -e s/.dispatch:// %s | llc -mtriple=i686-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USEDISPATCH,X86FEAT +; RUN: sed -e s/.dispatch:// %s | llc -mtriple=x86_64-pc-windows-msvc | FileCheck %s --check-prefixes=CHECK,USEDISPATCH,X64FEAT ; Control Flow Guard is currently only available on Windows -; Test that Control Flow Guard checks are not added in modules with the -; cfguard=1 flag (emit tables but no checks). - -; If no checks were inserted then the GuardCF bit shouldn't be set in @feat.00. -; CHECK: "@feat.00" = 0 ; i686 has SafeSEH (0x1) but should NOT have GuardCF (0x800). -; X86: @feat.00 = 1 +; X86NOFEAT: @feat.00 = 1 +; X86FEAT: @feat.00 = 2049 ; x86_64 has no SafeSEH and should NOT have GuardCF. -; X64: @feat.00 = 0 +; X64NOFEAT: @feat.00 = 0 +; X64FEAT: @feat.00 = 2048 declare void @target_func() @@ -25,9 +30,26 @@ entry: call void %0() ret void - ; X86-NOT: __guard_check_icall_fptr - ; X64-NOT: __guard_dispatch_icall_fptr + ; CHECK: call + + ; USECHECK-SAME: __guard_check_icall_fptr + ; USECHECK-NOT: __guard_dispatch_icall_fptr + + ; USEDISPATCH-SAME: __guard_dispatch_icall_fptr + ; USEDISPATCH-NOT: __guard_check_icall_fptr + + ; TABLEONLY-SAME: *% + ; TABLEONLY-NOT: __guard_dispatch_icall_fptr + ; TABLEONLY-NOT: __guard_check_icall_fptr } -!llvm.module.flags = !{!0} +; CHECK: .section .gfids$y,"dr" + !0 = !{i32 2, !"cfguard", i32 1} +!1 = !{i32 2, !"cfguard", i32 2} +!2 = !{i32 2, !"cfguard-mechanism", i32 1} +!3 = !{i32 2, !"cfguard-mechanism", i32 2} +;tableonly: !llvm.module.flags = !{!0} +;normal: !llvm.module.flags = !{!1} +;check: !llvm.module.flags = !{!1, !2} +;dispatch: !llvm.module.flags = !{!1, !3} diff --git a/llvm/test/Linker/cfguard.ll b/llvm/test/Linker/cfguard.ll new file mode 100644 index 0000000000000..2ffdb6ed612ab --- /dev/null +++ b/llvm/test/Linker/cfguard.ll @@ -0,0 +1,39 @@ +; RUN: split-file %s %t +; RUN: llvm-link %t/base.ll %t/mode.ll 2>&1 | FileCheck --check-prefix=MODE %s +; RUN: llvm-link %t/base.ll %t/mechanism.ll 2>&1 | FileCheck --check-prefix=MECHANISM %s +; RUN: llvm-link %t/base.ll %t/same.ll + +; MODE: warning: linking module flags 'cfguard': IDs have conflicting values +; MECHANISM: warning: linking module flags 'cfguard-mechanism': IDs have conflicting values + +;--- base.ll +define void @foo() { + ret void +} +!llvm.module.flags = !{!0,!1} +!0 = !{i32 2, !"cfguard", i32 1} +!1 = !{i32 2, !"cfguard-mechanism", i32 1} + +;--- mode.ll +define void @bar() { + ret void +} +!llvm.module.flags = !{!0,!1} +!0 = !{i32 2, !"cfguard", i32 2} +!1 = !{i32 2, !"cfguard-mechanism", i32 1} + +;--- mechanism.ll +define void @bar() { + ret void +} +!llvm.module.flags = !{!0,!1} +!0 = !{i32 2, !"cfguard", i32 1} +!1 = !{i32 2, !"cfguard-mechanism", i32 2} + +;--- same.ll +define void @bar() { + ret void +} +!llvm.module.flags = !{!0,!1} +!0 = !{i32 2, !"cfguard", i32 1} +!1 = !{i32 2, !"cfguard-mechanism", i32 1} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
