diff --git a/llvm/include/llvm/CodeGen/MachineBasicBlock.h b/llvm/include/llvm/CodeGen/MachineBasicBlock.h --- a/llvm/include/llvm/CodeGen/MachineBasicBlock.h +++ b/llvm/include/llvm/CodeGen/MachineBasicBlock.h @@ -153,6 +153,9 @@ /// LLVM IR. bool IsEHScopeEntry = false; + /// Indicates if this is a target block of a catchret. + bool IsEHCatchRetTarget = false; + /// Indicate that this basic block is the entry block of an EH funclet. bool IsEHFuncletEntry = false; @@ -445,6 +448,12 @@ /// that used to have a catchpad or cleanuppad instruction in the LLVM IR. void setIsEHScopeEntry(bool V = true) { IsEHScopeEntry = V; } + /// Returns true if this is a target block of a catchret. + bool isEHCatchRetTarget() const { return IsEHCatchRetTarget; } + + /// Indicates if this is a target block of a catchret. + void setIsEHCatchRetTarget(bool V = true) { IsEHCatchRetTarget = V; } + /// Returns true if this is the entry block of an EH funclet. bool isEHFuncletEntry() const { return IsEHFuncletEntry; } diff --git a/llvm/include/llvm/CodeGen/MachineFunction.h b/llvm/include/llvm/CodeGen/MachineFunction.h --- a/llvm/include/llvm/CodeGen/MachineFunction.h +++ b/llvm/include/llvm/CodeGen/MachineFunction.h @@ -321,6 +321,10 @@ /// construct a table of valid longjmp targets for Windows Control Flow Guard. std::vector LongjmpTargets; + /// List of basic blocks that are the target of catchrets. Used to construct + /// a table of valid targets for Windows EHCont Guard. + std::vector CatchretTargets; + /// \name Exception Handling /// \{ @@ -341,6 +345,7 @@ bool CallsEHReturn = false; bool CallsUnwindInit = false; + bool HasEHCatchRet = false; bool HasEHScopes = false; bool HasEHFunclets = false; @@ -930,6 +935,18 @@ /// Control Flow Guard. void addLongjmpTarget(MCSymbol *Target) { LongjmpTargets.push_back(Target); } + /// Returns a reference to a list of symbols that we have catchrets. + /// Used to construct the longjmp target table used by Windows EHCont Guard. + const std::vector &getCatchretTargets() const { + return CatchretTargets; + } + + /// Add the specified symbol to the list of valid catchret targets for Windows + /// EHCont Guard. + void addCatchretTarget(MCSymbol *Target) { + CatchretTargets.push_back(Target); + } + /// \name Exception Handling /// \{ @@ -939,6 +956,9 @@ bool callsUnwindInit() const { return CallsUnwindInit; } void setCallsUnwindInit(bool b) { CallsUnwindInit = b; } + bool hasEHCatchRet() const { return HasEHCatchRet; } + void setHasEHCatchRet(bool V) { HasEHCatchRet = V; } + bool hasEHScopes() const { return HasEHScopes; } void setHasEHScopes(bool V) { HasEHScopes = V; } diff --git a/llvm/include/llvm/CodeGen/Passes.h b/llvm/include/llvm/CodeGen/Passes.h --- a/llvm/include/llvm/CodeGen/Passes.h +++ b/llvm/include/llvm/CodeGen/Passes.h @@ -464,6 +464,10 @@ /// \see CFGuardLongjmp.cpp FunctionPass *createCFGuardLongjmpPass(); + /// Creates EHContGuard catchret target identification pass. + /// \see EHContGuardCatchret.cpp + FunctionPass *createEHContGuardCatchretPass(); + /// Create Hardware Loop pass. \see HardwareLoops.cpp FunctionPass *createHardwareLoopsPass(); diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -148,6 +148,7 @@ void initializeEarlyMachineLICMPass(PassRegistry&); void initializeEarlyTailDuplicatePass(PassRegistry&); void initializeEdgeBundlesPass(PassRegistry&); +void initializeEHContGuardCatchretPass(PassRegistry &); void initializeEliminateAvailableExternallyLegacyPassPass(PassRegistry&); void initializeEntryExitInstrumenterPass(PassRegistry&); void initializeExpandMemCmpPassPass(PassRegistry&); diff --git a/llvm/include/llvm/MC/MCObjectFileInfo.h b/llvm/include/llvm/MC/MCObjectFileInfo.h --- a/llvm/include/llvm/MC/MCObjectFileInfo.h +++ b/llvm/include/llvm/MC/MCObjectFileInfo.h @@ -218,6 +218,7 @@ MCSection *PDataSection = nullptr; MCSection *XDataSection = nullptr; MCSection *SXDataSection = nullptr; + MCSection *GEHContSection = nullptr; MCSection *GFIDsSection = nullptr; MCSection *GIATsSection = nullptr; MCSection *GLJMPSection = nullptr; @@ -405,6 +406,7 @@ MCSection *getPDataSection() const { return PDataSection; } MCSection *getXDataSection() const { return XDataSection; } MCSection *getSXDataSection() const { return SXDataSection; } + MCSection *getGEHContSection() const { return GEHContSection; } MCSection *getGFIDsSection() const { return GFIDsSection; } MCSection *getGIATsSection() const { return GIATsSection; } MCSection *getGLJMPSection() const { return GLJMPSection; } diff --git a/llvm/lib/CodeGen/AsmPrinter/WinException.h b/llvm/lib/CodeGen/AsmPrinter/WinException.h --- a/llvm/lib/CodeGen/AsmPrinter/WinException.h +++ b/llvm/lib/CodeGen/AsmPrinter/WinException.h @@ -44,6 +44,9 @@ /// The section of the last funclet start. MCSection *CurrentFuncletTextSection = nullptr; + /// The list of symbols to add to the ehcont section + std::vector EHContTargets; + void emitCSpecificHandlerTable(const MachineFunction *MF); void emitSEHActionsForRange(const WinEHFuncInfo &FuncInfo, diff --git a/llvm/lib/CodeGen/AsmPrinter/WinException.cpp b/llvm/lib/CodeGen/AsmPrinter/WinException.cpp --- a/llvm/lib/CodeGen/AsmPrinter/WinException.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/WinException.cpp @@ -55,6 +55,14 @@ for (const Function &F : *M) if (F.hasFnAttribute("safeseh")) OS.EmitCOFFSafeSEH(Asm->getSymbol(&F)); + + if (M->getModuleFlag("ehcontguard") && !EHContTargets.empty()) { + // Emit the symbol index of each ehcont target. + OS.SwitchSection(Asm->OutContext.getObjectFileInfo()->getGEHContSection()); + for (const MCSymbol *S : EHContTargets) { + OS.EmitCOFFSymbolIndex(S); + } + } } void WinException::beginFunction(const MachineFunction *MF) { @@ -164,6 +172,12 @@ Asm->OutStreamer->PopSection(); } + + if (!MF->getCatchretTargets().empty()) { + // Copy the function's catchret targets to a module-level list. + EHContTargets.insert(EHContTargets.end(), MF->getCatchretTargets().begin(), + MF->getCatchretTargets().end()); + } } /// Retrieve the MCSymbol for a GlobalValue or MachineBasicBlock. @@ -217,6 +231,10 @@ Asm->OutStreamer->EmitWinCFIStartProc(Sym); } + // Add 'Sym' as an ehcont target + if (CurrentFuncletEntry->isEHFuncletEntry()) + EHContTargets.push_back(Sym); + if (shouldEmitPersonality) { const TargetLoweringObjectFile &TLOF = Asm->getObjFileLowering(); const Function *PerFn = nullptr; diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -24,6 +24,7 @@ DwarfEHPrepare.cpp EarlyIfConversion.cpp EdgeBundles.cpp + EHContGuardCatchret.cpp ExecutionDomainFix.cpp ExpandMemCmp.cpp ExpandPostRAPseudos.cpp diff --git a/llvm/lib/CodeGen/EHContGuardCatchret.cpp b/llvm/lib/CodeGen/EHContGuardCatchret.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/CodeGen/EHContGuardCatchret.cpp @@ -0,0 +1,90 @@ +//===-- EHContGuardCatchret.cpp - Catchret target symbols -------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains a machine function pass to insert a symbol before each +/// valid catchret target and store this in the MachineFunction's +/// CatchRetTargets vector. This will be used to emit the table of valid targets +/// used by EHCont Guard. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/Statistic.h" +#include "llvm/CodeGen/MachineBasicBlock.h" +#include "llvm/CodeGen/MachineFunctionPass.h" +#include "llvm/CodeGen/MachineInstr.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/MachineOperand.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/InitializePasses.h" + +using namespace llvm; + +#define DEBUG_TYPE "ehcontguard-catchret" + +STATISTIC(EHContGuardCatchretTargets, + "Number of EHCont Guard catchret targets"); + +namespace { + +/// MachineFunction pass to insert a symbol before each valid catchret target +/// and store these in the MachineFunction's CatchRetTargets vector. +class EHContGuardCatchret : public MachineFunctionPass { +public: + static char ID; + + EHContGuardCatchret() : MachineFunctionPass(ID) { + initializeEHContGuardCatchretPass(*PassRegistry::getPassRegistry()); + } + + StringRef getPassName() const override { + return "EH Cont Guard catchret targets"; + } + + bool runOnMachineFunction(MachineFunction &MF) override; +}; + +} // end anonymous namespace + +char EHContGuardCatchret::ID = 0; + +INITIALIZE_PASS(EHContGuardCatchret, "EHContGuardCatchret", + "Insert symbols at valid catchret targets for /guard:ehcont", + false, false) +FunctionPass *llvm::createEHContGuardCatchretPass() { + return new EHContGuardCatchret(); +} + +bool EHContGuardCatchret::runOnMachineFunction(MachineFunction &MF) { + + // Skip modules for which the ehcontguard flag is not set. + if (!MF.getMMI().getModule()->getModuleFlag("ehcontguard")) + return false; + + // Skip functions that do not have catchret + if (!MF.hasEHCatchRet()) + return false; + + bool result = false; + + for (MachineBasicBlock &MBB : MF) { + if (MBB.isEHCatchRetTarget() && !MBB.isEHFuncletEntry()) { + auto MBBI = MBB.begin(); + SmallString<128> SymbolName; + raw_svector_ostream(SymbolName) + << "$ehgcr_" << MF.getName() << MBB.getNumber(); + MCSymbol *CrSymbol = MF.getContext().getOrCreateSymbol(SymbolName); + MBBI->setPreInstrSymbol(MF, CrSymbol); + MF.addCatchretTarget(CrSymbol); + EHContGuardCatchretTargets++; + result = true; + } + } + + return result; +} diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -1592,6 +1592,8 @@ // Update machine-CFG edge. MachineBasicBlock *TargetMBB = FuncInfo.MBBMap[I.getSuccessor()]; FuncInfo.MBB->addSuccessor(TargetMBB); + TargetMBB->setIsEHCatchRetTarget(true); + DAG.getMachineFunction().setHasEHCatchRet(true); auto Pers = classifyEHPersonality(FuncInfo.Fn->getPersonalityFn()); bool IsSEH = isAsynchronousEHPersonality(Pers); diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp --- a/llvm/lib/MC/MCObjectFileInfo.cpp +++ b/llvm/lib/MC/MCObjectFileInfo.cpp @@ -753,6 +753,11 @@ SXDataSection = Ctx->getCOFFSection(".sxdata", COFF::IMAGE_SCN_LNK_INFO, SectionKind::getMetadata()); + GEHContSection = Ctx->getCOFFSection(".gehcont$y", + COFF::IMAGE_SCN_CNT_INITIALIZED_DATA | + COFF::IMAGE_SCN_MEM_READ, + SectionKind::getMetadata()); + GFIDsSection = Ctx->getCOFFSection(".gfids$y", COFF::IMAGE_SCN_CNT_INITIALIZED_DATA | COFF::IMAGE_SCN_MEM_READ, diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp --- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp +++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp @@ -191,7 +191,32 @@ } // end anonymous namespace void AArch64AsmPrinter::emitStartOfAsmFile(Module &M) { - if (!TM.getTargetTriple().isOSBinFormatELF()) + const Triple &TT = TM.getTargetTriple(); + + if (TT.isOSBinFormatCOFF()) { + // Emit an absolute @feat.00 symbol. This appears to be some kind of + // compiler features bitfield read by link.exe. + MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00")); + OutStreamer->BeginCOFFSymbolDef(S); + OutStreamer->EmitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC); + OutStreamer->EmitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL); + OutStreamer->EndCOFFSymbolDef(); + int64_t Feat00Flags = 0; + + if (M.getModuleFlag("cfguard")) { + Feat00Flags |= 0x800; // Object is CFG-aware. + } + + if (M.getModuleFlag("ehcontguard")) { + Feat00Flags |= 0x4000; // Object also has EHCont. + } + + OutStreamer->emitSymbolAttribute(S, MCSA_Global); + OutStreamer->emitAssignment( + S, MCConstantExpr::create(Feat00Flags, MMI->getContext())); + } + + if (!TT.isOSBinFormatELF()) return; // Assemble feature flags that may require creation of a note section. diff --git a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp --- a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp +++ b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp @@ -676,9 +676,12 @@ if (BranchRelaxation) addPass(&BranchRelaxationPassID); - // Identify valid longjmp targets for Windows Control Flow Guard. - if (TM->getTargetTriple().isOSWindows()) + if (TM->getTargetTriple().isOSWindows()) { + // Identify valid longjmp targets for Windows Control Flow Guard. addPass(createCFGuardLongjmpPass()); + // Identify valid eh continuation targets for Windows EHCont Guard. + addPass(createEHContGuardCatchretPass()); + } if (TM->getOptLevel() != CodeGenOpt::None && EnableCompressJumpTables) addPass(createAArch64CompressJumpTablesPass()); diff --git a/llvm/lib/Target/ARM/ARMTargetMachine.cpp b/llvm/lib/Target/ARM/ARMTargetMachine.cpp --- a/llvm/lib/Target/ARM/ARMTargetMachine.cpp +++ b/llvm/lib/Target/ARM/ARMTargetMachine.cpp @@ -564,7 +564,10 @@ addPass(createARMConstantIslandPass()); addPass(createARMLowOverheadLoopsPass()); - // Identify valid longjmp targets for Windows Control Flow Guard. - if (TM->getTargetTriple().isOSWindows()) + if (TM->getTargetTriple().isOSWindows()) { + // Identify valid longjmp targets for Windows Control Flow Guard. addPass(createCFGuardLongjmpPass()); + // Identify valid eh continuation targets for Windows EHCont Guard. + addPass(createEHContGuardCatchretPass()); + } } diff --git a/llvm/lib/Target/X86/X86AsmPrinter.cpp b/llvm/lib/Target/X86/X86AsmPrinter.cpp --- a/llvm/lib/Target/X86/X86AsmPrinter.cpp +++ b/llvm/lib/Target/X86/X86AsmPrinter.cpp @@ -683,8 +683,13 @@ Feat00Flags |= 1; } - if (M.getModuleFlag("cfguard")) + if (M.getModuleFlag("cfguard")) { Feat00Flags |= 0x800; // Object is CFG-aware. + } + + if (M.getModuleFlag("ehcontguard")) { + Feat00Flags |= 0x4000; // Object also has EHCont. + } OutStreamer->emitSymbolAttribute(S, MCSA_Global); OutStreamer->emitAssignment( diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp --- a/llvm/lib/Target/X86/X86TargetMachine.cpp +++ b/llvm/lib/Target/X86/X86TargetMachine.cpp @@ -568,9 +568,13 @@ (!TT.isOSWindows() || MAI->getExceptionHandlingType() == ExceptionHandling::DwarfCFI)) addPass(createCFIInstrInserter()); - // Identify valid longjmp targets for Windows Control Flow Guard. - if (TT.isOSWindows()) + + if (TT.isOSWindows()) { + // Identify valid longjmp targets for Windows Control Flow Guard. addPass(createCFGuardLongjmpPass()); + // Identify valid eh continuation targets for Windows EHCont Guard. + addPass(createEHContGuardCatchretPass()); + } addPass(createX86LoadValueInjectionRetHardeningPass()); } diff --git a/llvm/test/CodeGen/AArch64/ehcontguard.ll b/llvm/test/CodeGen/AArch64/ehcontguard.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/ehcontguard.ll @@ -0,0 +1,48 @@ +; RUN: llc < %s -mtriple=aarch64-windows | FileCheck %s +; EHCont Guard is currently only available on Windows + +; CHECK: .set @feat.00, 16384 + +; CHECK: .section .gehcont$y + +; // struct with dtor +; struct A +; { +; ~A(); +; }; +; +; // function we call that might raise an exception +; void func2(); +; +; // function which has to call the dtor on unwind +; void func1() +; { +; A a; +; func2(); +; } + +%struct.A = type { [4 x i32], [16 x i8] } +declare dso_local %struct.A* @"??0A@@QEAA@XZ"(%struct.A* returned %0) +declare dso_local void @"??1A@@QEAA@XZ"(%struct.A* %0) +declare dso_local i32 @__CxxFrameHandler3(...) +declare dso_local void @"?func2@@YAXXZ"() + +; Function Attrs: noinline optnone uwtable +define dso_local void @"?func1@@YAXXZ"() #0 personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) { + %1 = alloca %struct.A + %2 = call %struct.A* @"??0A@@QEAA@XZ"(%struct.A* %1) #3 + invoke void @"?func2@@YAXXZ"() + to label %3 unwind label %4 + +3: ; preds = %0 + call void @"??1A@@QEAA@XZ"(%struct.A* %1) #3 + ret void + +4: ; preds = %0 + %5 = cleanuppad within none [] + call void @"??1A@@QEAA@XZ"(%struct.A* %1) #3 [ "funclet"(token %5) ] + cleanupret from %5 unwind to caller +} + +!llvm.module.flags = !{!0} +!0 = !{i32 1, !"ehcontguard", i32 1} \ No newline at end of file diff --git a/llvm/test/CodeGen/X86/ehcontguard.ll b/llvm/test/CodeGen/X86/ehcontguard.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/ehcontguard.ll @@ -0,0 +1,48 @@ +; RUN: llc < %s -mtriple=x86_64-pc-windows-msvc | FileCheck %s +; EHCont Guard is currently only available on Windows + +; CHECK: .set @feat.00, 16384 + +; CHECK: .section .gehcont$y + +; // struct with dtor +; struct A +; { +; ~A(); +; }; +; +; // function we call that might raise an exception +; void func2(); +; +; // function which has to call the dtor on unwind +; void func1() +; { +; A a; +; func2(); +; } + +%struct.A = type { [4 x i32], [16 x i8] } +declare dso_local %struct.A* @"??0A@@QEAA@XZ"(%struct.A* returned %0) +declare dso_local void @"??1A@@QEAA@XZ"(%struct.A* %0) +declare dso_local i32 @__CxxFrameHandler3(...) +declare dso_local void @"?func2@@YAXXZ"() + +; Function Attrs: noinline optnone uwtable +define dso_local void @"?func1@@YAXXZ"() #0 personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) { + %1 = alloca %struct.A + %2 = call %struct.A* @"??0A@@QEAA@XZ"(%struct.A* %1) #3 + invoke void @"?func2@@YAXXZ"() + to label %3 unwind label %4 + +3: ; preds = %0 + call void @"??1A@@QEAA@XZ"(%struct.A* %1) #3 + ret void + +4: ; preds = %0 + %5 = cleanuppad within none [] + call void @"??1A@@QEAA@XZ"(%struct.A* %1) #3 [ "funclet"(token %5) ] + cleanupret from %5 unwind to caller +} + +!llvm.module.flags = !{!0} +!0 = !{i32 1, !"ehcontguard", i32 1} \ No newline at end of file diff --git a/llvm/utils/gn/secondary/llvm/lib/CodeGen/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/CodeGen/BUILD.gn --- a/llvm/utils/gn/secondary/llvm/lib/CodeGen/BUILD.gn +++ b/llvm/utils/gn/secondary/llvm/lib/CodeGen/BUILD.gn @@ -42,6 +42,7 @@ "DwarfEHPrepare.cpp", "EarlyIfConversion.cpp", "EdgeBundles.cpp", + "EHContGuardCatchret.cpp", "ExecutionDomainFix.cpp", "ExpandMemCmp.cpp", "ExpandPostRAPseudos.cpp",