From 3d50c64c92f5d42862cfd83602626c91bfd5ecd0 Mon Sep 17 00:00:00 2001 From: David Bozier Date: Tue, 28 Feb 2017 16:02:37 +0000 Subject: [PATCH] [Stack Protection] Add diagnostic information for why stack protection was applied to a function Stack Smash Protection is not completely free, so in hot code, the overhead it causes can cause performance issues. By adding diagnostic information for which functions have SSP and why, a user can quickly determine what they can do to stop SSP being applied to a specific hot function. This change adds a remark that is reported by the stack protection code when an instruction or attribute is encountered that causes SSP to be applied. Patch by: James Henderson Differential Revision: https://reviews.llvm.org/D29023 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@296483 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/llvm/IR/DiagnosticInfo.h | 77 +++++++++-------- lib/CodeGen/StackProtector.cpp | 26 +++++- lib/IR/DiagnosticInfo.cpp | 17 +++- test/CodeGen/X86/stack-protector-remarks.ll | 91 +++++++++++++++++++++ 4 files changed, 176 insertions(+), 35 deletions(-) create mode 100644 test/CodeGen/X86/stack-protector-remarks.ll diff --git a/include/llvm/IR/DiagnosticInfo.h b/include/llvm/IR/DiagnosticInfo.h index 956a1ff4342..458c3cf29b0 100644 --- a/include/llvm/IR/DiagnosticInfo.h +++ b/include/llvm/IR/DiagnosticInfo.h @@ -426,10 +426,11 @@ public: }; /// \p PassName is the name of the pass emitting this diagnostic. \p - /// RemarkName is a textual identifier for the remark. \p Fn is the function - /// where the diagnostic is being emitted. \p Loc is the location information - /// to use in the diagnostic. If line table information is available, the - /// diagnostic will include the source code location. + /// RemarkName is a textual identifier for the remark (single-word, + /// camel-case). \p Fn is the function where the diagnostic is being emitted. + /// \p Loc is the location information to use in the diagnostic. If line table + /// information is available, the diagnostic will include the source code + /// location. DiagnosticInfoOptimizationBase(enum DiagnosticKind Kind, enum DiagnosticSeverity Severity, const char *PassName, StringRef RemarkName, @@ -488,8 +489,9 @@ protected: /// be emitted. const char *PassName; - /// Textual identifier for the remark. Can be used by external tools reading - /// the YAML output file for optimization remarks to identify the remark. + /// Textual identifier for the remark (single-word, camel-case). Can be used + /// by external tools reading the YAML output file for optimization remarks to + /// identify the remark. StringRef RemarkName; /// If profile information is available, this is the number of times the @@ -515,12 +517,13 @@ protected: class DiagnosticInfoIROptimization : public DiagnosticInfoOptimizationBase { public: /// \p PassName is the name of the pass emitting this diagnostic. \p - /// RemarkName is a textual identifier for the remark. \p Fn is the function - /// where the diagnostic is being emitted. \p Loc is the location information - /// to use in the diagnostic. If line table information is available, the - /// diagnostic will include the source code location. \p CodeRegion is IR - /// value (currently basic block) that the optimization operates on. This is - /// currently used to provide run-time hotness information with PGO. + /// RemarkName is a textual identifier for the remark (single-word, + /// camel-case). \p Fn is the function where the diagnostic is being emitted. + /// \p Loc is the location information to use in the diagnostic. If line table + /// information is available, the diagnostic will include the source code + /// location. \p CodeRegion is IR value (currently basic block) that the + /// optimization operates on. This is currently used to provide run-time + /// hotness information with PGO. DiagnosticInfoIROptimization(enum DiagnosticKind Kind, enum DiagnosticSeverity Severity, const char *PassName, StringRef RemarkName, @@ -581,16 +584,22 @@ class OptimizationRemark : public DiagnosticInfoIROptimization { public: /// \p PassName is the name of the pass emitting this diagnostic. If this name /// matches the regular expression given in -Rpass=, then the diagnostic will - /// be emitted. \p RemarkName is a textual identifier for the remark. \p - /// Loc is the debug location and \p CodeRegion is the region that the - /// optimization operates on (currently on block is supported). + /// be emitted. \p RemarkName is a textual identifier for the remark (single- + /// word, camel-case). \p Loc is the debug location and \p CodeRegion is the + /// region that the optimization operates on (currently only block is + /// supported). OptimizationRemark(const char *PassName, StringRef RemarkName, const DiagnosticLocation &Loc, const Value *CodeRegion); - /// Same as above but the debug location and code region is derived from \p + /// Same as above, but the debug location and code region are derived from \p /// Instr. OptimizationRemark(const char *PassName, StringRef RemarkName, - Instruction *Inst); + const Instruction *Inst); + + /// Same as above, but the debug location and code region are derived from \p + /// Func. + OptimizationRemark(const char *PassName, StringRef RemarkName, + const Function *Func); static bool classof(const DiagnosticInfo *DI) { return DI->getKind() == DK_OptimizationRemark; @@ -627,9 +636,10 @@ class OptimizationRemarkMissed : public DiagnosticInfoIROptimization { public: /// \p PassName is the name of the pass emitting this diagnostic. If this name /// matches the regular expression given in -Rpass-missed=, then the - /// diagnostic will be emitted. \p RemarkName is a textual identifier for the - /// remark. \p Loc is the debug location and \p CodeRegion is the region - /// that the optimization operates on (currently on block is supported). + /// diagnostic will be emitted. \p RemarkName is a textual identifier for the + /// remark (single-word, camel-case). \p Loc is the debug location and \p + /// CodeRegion is the region that the optimization operates on (currently only + /// block is supported). OptimizationRemarkMissed(const char *PassName, StringRef RemarkName, const DiagnosticLocation &Loc, const Value *CodeRegion); @@ -675,9 +685,10 @@ class OptimizationRemarkAnalysis : public DiagnosticInfoIROptimization { public: /// \p PassName is the name of the pass emitting this diagnostic. If this name /// matches the regular expression given in -Rpass-analysis=, then the - /// diagnostic will be emitted. \p RemarkName is a textual identifier for the - /// remark. \p Loc is the debug location and \p CodeRegion is the region - /// that the optimization operates on (currently on block is supported). + /// diagnostic will be emitted. \p RemarkName is a textual identifier for the + /// remark (single-word, camel-case). \p Loc is the debug location and \p + /// CodeRegion is the region that the optimization operates on (currently only + /// block is supported). OptimizationRemarkAnalysis(const char *PassName, StringRef RemarkName, const DiagnosticLocation &Loc, const Value *CodeRegion); @@ -752,11 +763,11 @@ class OptimizationRemarkAnalysisFPCommute : public OptimizationRemarkAnalysis { public: /// \p PassName is the name of the pass emitting this diagnostic. If this name /// matches the regular expression given in -Rpass-analysis=, then the - /// diagnostic will be emitted. \p RemarkName is a textual identifier for the - /// remark. \p Loc is the debug location and \p CodeRegion is the region - /// that the optimization operates on (currently on block is supported). The - /// front-end will append its own message related to options that address - /// floating-point non-commutativity. + /// diagnostic will be emitted. \p RemarkName is a textual identifier for the + /// remark (single-word, camel-case). \p Loc is the debug location and \p + /// CodeRegion is the region that the optimization operates on (currently only + /// block is supported). The front-end will append its own message related to + /// options that address floating-point non-commutativity. OptimizationRemarkAnalysisFPCommute(const char *PassName, StringRef RemarkName, const DiagnosticLocation &Loc, @@ -796,11 +807,11 @@ class OptimizationRemarkAnalysisAliasing : public OptimizationRemarkAnalysis { public: /// \p PassName is the name of the pass emitting this diagnostic. If this name /// matches the regular expression given in -Rpass-analysis=, then the - /// diagnostic will be emitted. \p RemarkName is a textual identifier for the - /// remark. \p Loc is the debug location and \p CodeRegion is the region - /// that the optimization operates on (currently on block is supported). The - /// front-end will append its own message related to options that address - /// pointer aliasing legality. + /// diagnostic will be emitted. \p RemarkName is a textual identifier for the + /// remark (single-word, camel-case). \p Loc is the debug location and \p + /// CodeRegion is the region that the optimization operates on (currently only + /// block is supported). The front-end will append its own message related to + /// options that address pointer aliasing legality. OptimizationRemarkAnalysisAliasing(const char *PassName, StringRef RemarkName, const DiagnosticLocation &Loc, const Value *CodeRegion) diff --git a/lib/CodeGen/StackProtector.cpp b/lib/CodeGen/StackProtector.cpp index 2fe9c7ed5c6..1753eda56d8 100644 --- a/lib/CodeGen/StackProtector.cpp +++ b/lib/CodeGen/StackProtector.cpp @@ -18,6 +18,7 @@ #include "llvm/ADT/Statistic.h" #include "llvm/Analysis/BranchProbabilityInfo.h" #include "llvm/Analysis/EHPersonalities.h" +#include "llvm/Analysis/OptimizationDiagnosticInfo.h" #include "llvm/CodeGen/Passes.h" #include "llvm/CodeGen/StackProtector.h" #include "llvm/IR/Attributes.h" @@ -58,7 +59,7 @@ static cl::opt EnableSelectionDAGSP("enable-selectiondag-sp", char StackProtector::ID = 0; INITIALIZE_TM_PASS(StackProtector, "stack-protector", "Insert stack protectors", - false, true) + false, true) FunctionPass *llvm::createStackProtectorPass(const TargetMachine *TM) { return new StackProtector(TM); @@ -229,7 +230,17 @@ bool StackProtector::RequiresStackProtector() { if (F->hasFnAttribute(Attribute::SafeStack)) return false; + // We are constructing the OptimizationRemarkEmitter on the fly rather than + // using the analysis pass to avoid building DominatorTree and LoopInfo which + // are not available this late in the IR pipeline. + OptimizationRemarkEmitter ORE(F); + auto ReasonStub = + Twine("Stack protection applied to function " + F->getName() + " due to ") + .str(); + if (F->hasFnAttribute(Attribute::StackProtectReq)) { + ORE.emit(OptimizationRemark(DEBUG_TYPE, "StackProtectorRequested", F) + << ReasonStub << "a function attribute or command-line switch"); NeedsProtector = true; Strong = true; // Use the same heuristic as strong to determine SSPLayout } else if (F->hasFnAttribute(Attribute::StackProtectStrong)) @@ -243,20 +254,27 @@ bool StackProtector::RequiresStackProtector() { for (const Instruction &I : BB) { if (const AllocaInst *AI = dyn_cast(&I)) { if (AI->isArrayAllocation()) { + OptimizationRemark Remark(DEBUG_TYPE, "StackProtectorAllocaOrArray", + &I); + Remark << ReasonStub + << "a call to alloca or use of a variable length array"; if (const auto *CI = dyn_cast(AI->getArraySize())) { if (CI->getLimitedValue(SSPBufferSize) >= SSPBufferSize) { // A call to alloca with size >= SSPBufferSize requires // stack protectors. Layout.insert(std::make_pair(AI, SSPLK_LargeArray)); + ORE.emit(Remark); NeedsProtector = true; } else if (Strong) { // Require protectors for all alloca calls in strong mode. Layout.insert(std::make_pair(AI, SSPLK_SmallArray)); + ORE.emit(Remark); NeedsProtector = true; } } else { // A call to alloca with a variable size requires protectors. Layout.insert(std::make_pair(AI, SSPLK_LargeArray)); + ORE.emit(Remark); NeedsProtector = true; } continue; @@ -266,6 +284,9 @@ bool StackProtector::RequiresStackProtector() { if (ContainsProtectableArray(AI->getAllocatedType(), IsLarge, Strong)) { Layout.insert(std::make_pair(AI, IsLarge ? SSPLK_LargeArray : SSPLK_SmallArray)); + ORE.emit(OptimizationRemark(DEBUG_TYPE, "StackProtectorBuffer", &I) + << ReasonStub + << "a stack allocated buffer or struct containing a buffer"); NeedsProtector = true; continue; } @@ -273,6 +294,9 @@ bool StackProtector::RequiresStackProtector() { if (Strong && HasAddressTaken(AI)) { ++NumAddrTaken; Layout.insert(std::make_pair(AI, SSPLK_AddrOf)); + ORE.emit( + OptimizationRemark(DEBUG_TYPE, "StackProtectorAddressTaken", &I) + << ReasonStub << "the address of a local variable being taken"); NeedsProtector = true; } } diff --git a/lib/IR/DiagnosticInfo.cpp b/lib/IR/DiagnosticInfo.cpp index 8afb547971c..395b6158e0c 100644 --- a/lib/IR/DiagnosticInfo.cpp +++ b/lib/IR/DiagnosticInfo.cpp @@ -228,11 +228,26 @@ OptimizationRemark::OptimizationRemark(const char *PassName, *cast(CodeRegion)->getParent(), Loc, CodeRegion) {} OptimizationRemark::OptimizationRemark(const char *PassName, - StringRef RemarkName, Instruction *Inst) + StringRef RemarkName, + const Instruction *Inst) : DiagnosticInfoIROptimization(DK_OptimizationRemark, DS_Remark, PassName, RemarkName, *Inst->getParent()->getParent(), Inst->getDebugLoc(), Inst->getParent()) {} +// Helper to allow for an assert before attempting to return an invalid +// reference. +static const BasicBlock &getFirstFunctionBlock(const Function *Func) { + assert(!Func->empty() && "Function does not have a body"); + return Func->front(); +} + +OptimizationRemark::OptimizationRemark(const char *PassName, + StringRef RemarkName, + const Function *Func) + : DiagnosticInfoIROptimization(DK_OptimizationRemark, DS_Remark, PassName, + RemarkName, *Func, Func->getSubprogram(), + &getFirstFunctionBlock(Func)) {} + bool OptimizationRemark::isEnabled(StringRef PassName) { return PassRemarksOptLoc.Pattern && PassRemarksOptLoc.Pattern->match(PassName); diff --git a/test/CodeGen/X86/stack-protector-remarks.ll b/test/CodeGen/X86/stack-protector-remarks.ll new file mode 100644 index 00000000000..a86976ddb32 --- /dev/null +++ b/test/CodeGen/X86/stack-protector-remarks.ll @@ -0,0 +1,91 @@ +; RUN: llc %s -pass-remarks=stack-protector -o /dev/null 2>&1 | FileCheck %s +; CHECK-NOT: nossp +; CHECK: function attribute_ssp +; CHECK-SAME: a function attribute or command-line switch +; CHECK-NOT: alloca_fixed_small_nossp +; CHECK: function alloca_fixed_small_ssp +; CHECK-SAME: a call to alloca or use of a variable length array +; CHECK: function alloca_fixed_large_ssp +; CHECK-SAME: a call to alloca or use of a variable length array +; CHECK: function alloca_variable_ssp +; CHECK-SAME: a call to alloca or use of a variable length array +; CHECK: function buffer_ssp +; CHECK-SAME: a stack allocated buffer or struct containing a buffer +; CHECK: function struct_ssp +; CHECK-SAME: a stack allocated buffer or struct containing a buffer +; CHECK: function address_ssp +; CHECK-SAME: the address of a local variable being taken +; CHECK: function multiple_ssp +; CHECK-SAME: a function attribute or command-line switch +; CHECK: function multiple_ssp +; CHECK-SAME: a stack allocated buffer or struct containing a buffer +; CHECK: function multiple_ssp +; CHECK-SAME: a stack allocated buffer or struct containing a buffer +; CHECK: function multiple_ssp +; CHECK-SAME: the address of a local variable being taken +; CHECK: function multiple_ssp +; CHECK-SAME: a call to alloca or use of a variable length array + +; Check that no remark is emitted when the switch is not specified. +; RUN: llc %s -o /dev/null 2>&1 | FileCheck %s -check-prefix=NOREMARK -allow-empty +; NOREMARK-NOT: ssp + +define void @nossp() ssp { + ret void +} + +define void @attribute_ssp() sspreq { + ret void +} + +define void @alloca_fixed_small_nossp() ssp { + %1 = alloca i8, i64 2, align 16 + ret void +} + +define void @alloca_fixed_small_ssp() sspstrong { + %1 = alloca i8, i64 2, align 16 + ret void +} + +define void @alloca_fixed_large_ssp() ssp { + %1 = alloca i8, i64 64, align 16 + ret void +} + +define void @alloca_variable_ssp(i64 %x) ssp { + %1 = alloca i8, i64 %x, align 16 + ret void +} + +define void @buffer_ssp() sspstrong { + %x = alloca [64 x i32], align 16 + ret void +} + +%struct.X = type { [64 x i32] } +define void @struct_ssp() sspstrong { + %x = alloca %struct.X, align 4 + ret void +} + +define void @address_ssp() sspstrong { +entry: + %x = alloca i32, align 4 + %y = alloca i32*, align 8 + store i32 32, i32* %x, align 4 + store i32* %x, i32** %y, align 8 + ret void +} + +define void @multiple_ssp() sspreq { +entry: + %x = alloca %struct.X, align 4 + %y = alloca [64 x i32], align 16 + %a = alloca i32, align 4 + %b = alloca i32*, align 8 + %0 = alloca i8, i64 2, align 16 + store i32 32, i32* %a, align 4 + store i32* %a, i32** %b, align 8 + ret void +} -- 2.50.1