Index: clang/include/clang/Driver/CC1Options.td =================================================================== --- clang/include/clang/Driver/CC1Options.td +++ clang/include/clang/Driver/CC1Options.td @@ -307,6 +307,9 @@ def fsanitize_coverage_no_prune : Flag<["-"], "fsanitize-coverage-no-prune">, HelpText<"Disable coverage pruning (i.e. instrument all blocks/edges)">; +def fsanitize_coverage_stack_depth + : Flag<["-"], "fsanitize-coverage-stack-depth">, + HelpText<"Enable max stack depth tracing">; def fprofile_instrument_EQ : Joined<["-"], "fprofile-instrument=">, HelpText<"Enable PGO instrumentation. The accepted value is clang, llvm, " "or none">, Values<"none,clang,llvm">; Index: clang/include/clang/Frontend/CodeGenOptions.def =================================================================== --- clang/include/clang/Frontend/CodeGenOptions.def +++ clang/include/clang/Frontend/CodeGenOptions.def @@ -169,6 +169,7 @@ CODEGENOPT(SanitizeCoverageInline8bitCounters, 1, 0) ///< Use inline 8bit counters. CODEGENOPT(SanitizeCoveragePCTable, 1, 0) ///< Create a PC Table. CODEGENOPT(SanitizeCoverageNoPrune, 1, 0) ///< Disable coverage pruning. +CODEGENOPT(SanitizeCoverageStackDepth, 1, 0) ///< Enable max stack depth tracing CODEGENOPT(SanitizeStats , 1, 0) ///< Collect statistics for sanitizers. CODEGENOPT(SimplifyLibCalls , 1, 1) ///< Set when -fbuiltin is enabled. CODEGENOPT(SoftFloat , 1, 0) ///< -soft-float. Index: clang/lib/CodeGen/BackendUtil.cpp =================================================================== --- clang/lib/CodeGen/BackendUtil.cpp +++ clang/lib/CodeGen/BackendUtil.cpp @@ -190,6 +190,7 @@ Opts.NoPrune = CGOpts.SanitizeCoverageNoPrune; Opts.Inline8bitCounters = CGOpts.SanitizeCoverageInline8bitCounters; Opts.PCTable = CGOpts.SanitizeCoveragePCTable; + Opts.StackDepth = CGOpts.SanitizeCoverageStackDepth; PM.add(createSanitizerCoverageModulePass(Opts)); } Index: clang/lib/Driver/SanitizerArgs.cpp =================================================================== --- clang/lib/Driver/SanitizerArgs.cpp +++ clang/lib/Driver/SanitizerArgs.cpp @@ -58,6 +58,7 @@ CoverageNoPrune = 1 << 11, CoverageInline8bitCounters = 1 << 12, CoveragePCTable = 1 << 13, + CoverageStackDepth = 1 << 14, }; /// Parse a -fsanitize= or -fno-sanitize= argument's values, diagnosing any @@ -561,6 +562,10 @@ !(CoverageFeatures & InsertionPointTypes)) CoverageFeatures |= CoverageEdge; + if ((CoverageFeatures & CoverageStackDepth) && + !(CoverageFeatures & InsertionPointTypes)) + CoverageFeatures |= CoverageFunc; + if (AllAddedKinds & Address) { AsanSharedRuntime = Args.hasArg(options::OPT_shared_libasan) || @@ -667,7 +672,8 @@ std::make_pair(CoverageTracePCGuard, "-fsanitize-coverage-trace-pc-guard"), std::make_pair(CoverageInline8bitCounters, "-fsanitize-coverage-inline-8bit-counters"), std::make_pair(CoveragePCTable, "-fsanitize-coverage-pc-table"), - std::make_pair(CoverageNoPrune, "-fsanitize-coverage-no-prune")}; + std::make_pair(CoverageNoPrune, "-fsanitize-coverage-no-prune"), + std::make_pair(CoverageStackDepth, "-fsanitize-coverage-stack-depth")}; for (auto F : CoverageFlags) { if (CoverageFeatures & F.first) CmdArgs.push_back(F.second); @@ -830,6 +836,7 @@ .Case("no-prune", CoverageNoPrune) .Case("inline-8bit-counters", CoverageInline8bitCounters) .Case("pc-table", CoveragePCTable) + .Case("stack-depth", CoverageStackDepth) .Default(0); if (F == 0) D.Diag(clang::diag::err_drv_unsupported_option_argument) Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -795,6 +795,8 @@ Opts.SanitizeCoverageInline8bitCounters = Args.hasArg(OPT_fsanitize_coverage_inline_8bit_counters); Opts.SanitizeCoveragePCTable = Args.hasArg(OPT_fsanitize_coverage_pc_table); + Opts.SanitizeCoverageStackDepth = + Args.hasArg(OPT_fsanitize_coverage_stack_depth); Opts.SanitizeMemoryTrackOrigins = getLastArgIntValue(Args, OPT_fsanitize_memory_track_origins_EQ, 0, Diags); Opts.SanitizeMemoryUseAfterDtor = Index: compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_stack_depth.cc =================================================================== --- /dev/null +++ compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_stack_depth.cc @@ -0,0 +1,32 @@ +// Tests -fsanitize-coverage=stack-depth +// +// XFAIL: tsan +// +// RUN: %clangxx -O0 -std=c++11 -fsanitize-coverage=stack-depth %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not Assertion{{.*}}failed +// RUN: %clangxx -O0 -std=c++11 -fsanitize-coverage=trace-pc-guard,stack-depth \ +// RUN: %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not Assertion{{.*}}failed + +#include +#include +#include + +thread_local uintptr_t __sanitizer_cov_lowest_stack; +uintptr_t last_stack; + +void foo(int recurse) { + assert(__sanitizer_cov_lowest_stack < last_stack); + last_stack = __sanitizer_cov_lowest_stack; + if (recurse <= 0) return; + foo(recurse - 1); +} + +int main() { + last_stack = __sanitizer_cov_lowest_stack; + foo(100); + printf("Success!\n"); + return 0; +} + +// CHECK: Success! Index: llvm/include/llvm/Transforms/Instrumentation.h =================================================================== --- llvm/include/llvm/Transforms/Instrumentation.h +++ llvm/include/llvm/Transforms/Instrumentation.h @@ -185,6 +185,7 @@ bool Inline8bitCounters = false; bool PCTable = false; bool NoPrune = false; + bool StackDepth = false; SanitizerCoverageOptions() = default; }; Index: llvm/lib/Fuzzer/FuzzerTracePC.h =================================================================== --- llvm/lib/Fuzzer/FuzzerTracePC.h +++ llvm/lib/Fuzzer/FuzzerTracePC.h @@ -120,19 +120,8 @@ return PCs()[Idx]; } - void RecordCurrentStack() { - uintptr_t Stack = GetCurrentStack(); - if (Stack < LowestStack) - LowestStack = Stack; - } - void RecordInitialStack() { - InitialStack = GetCurrentStack(); - LowestStack = InitialStack; - } - uintptr_t GetCurrentStack() const { - return reinterpret_cast(__builtin_frame_address(0)); - } - uintptr_t GetMaxStackOffset() const { return InitialStack - LowestStack; } + void RecordInitialStack(); + uintptr_t GetMaxStackOffset() const; template void ForEachObservedPC(CallBack CB) { @@ -167,7 +156,7 @@ std::set ObservedPCs; ValueBitMap ValueProfileMap; - uintptr_t InitialStack, LowestStack; // Assume stack grows down. + uintptr_t InitialStack; }; template Index: llvm/lib/Fuzzer/FuzzerTracePC.cpp =================================================================== --- llvm/lib/Fuzzer/FuzzerTracePC.cpp +++ llvm/lib/Fuzzer/FuzzerTracePC.cpp @@ -31,6 +31,9 @@ ATTRIBUTE_INTERFACE uintptr_t __sancov_trace_pc_pcs[fuzzer::TracePC::kNumPCs]; +// Used by -fsanitize-coverage=stack-depth to track stack depth +thread_local uintptr_t __sanitizer_cov_lowest_stack; + namespace fuzzer { TracePC TPC; @@ -340,6 +343,14 @@ } } +void TracePC::RecordInitialStack() { + InitialStack = __sanitizer_cov_lowest_stack; +} + +uintptr_t TracePC::GetMaxStackOffset() const { + return InitialStack - __sanitizer_cov_lowest_stack; // Stack grows down +} + } // namespace fuzzer extern "C" { @@ -350,8 +361,6 @@ uint32_t Idx = *Guard; __sancov_trace_pc_pcs[Idx] = PC; __sancov_trace_pc_guard_8bit_counters[Idx]++; - // Uncomment the following line to get stack-depth profiling. - // fuzzer::TPC.RecordCurrentStack(); } // Best-effort support for -fsanitize-coverage=trace-pc, which is available Index: llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp =================================================================== --- llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp +++ llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp @@ -17,12 +17,15 @@ #include "llvm/Analysis/PostDominators.h" #include "llvm/IR/CFG.h" #include "llvm/IR/CallSite.h" +#include "llvm/IR/Constant.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" +#include "llvm/IR/GlobalVariable.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InlineAsm.h" +#include "llvm/IR/Intrinsics.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/Module.h" @@ -73,6 +76,10 @@ static const char *const SanCovCountersSectionName = "sancov_cntrs"; static const char *const SanCovPCsSectionName = "sancov_pcs"; +static const char *const SanCovLowestStackName = "__sanitizer_cov_lowest_stack"; +static const char *const SanCovLowestStackTLSWrapperName = + "_ZTW28__sanitizer_cov_lowest_stack"; + static cl::opt ClCoverageLevel( "sanitizer-coverage-level", cl::desc("Sanitizer Coverage. 0: none, 1: entry block, 2: all blocks, " @@ -119,6 +126,10 @@ cl::desc("Reduce the number of instrumented blocks"), cl::Hidden, cl::init(true)); +static cl::opt ClStackDepth("sanitizer-coverage-stack-depth", + cl::desc("max stack depth tracing"), + cl::Hidden, cl::init(false)); + namespace { SanitizerCoverageOptions getOptions(int LegacyCoverageLevel) { @@ -156,9 +167,11 @@ Options.TracePCGuard |= ClTracePCGuard; Options.Inline8bitCounters |= ClInline8bitCounters; Options.PCTable |= ClCreatePCTable; - if (!Options.TracePCGuard && !Options.TracePC && !Options.Inline8bitCounters) - Options.TracePCGuard = true; // TracePCGuard is default. Options.NoPrune |= !ClPruneBlocks; + Options.StackDepth |= ClStackDepth; + if (!Options.TracePCGuard && !Options.TracePC && + !Options.Inline8bitCounters && !Options.StackDepth) + Options.TracePCGuard = true; // TracePCGuard is default. return Options; } @@ -216,6 +229,8 @@ Function *SanCovTraceDivFunction[2]; Function *SanCovTraceGepFunction; Function *SanCovTraceSwitchFunction; + Function *SanCovLowestStackTLSWrapper; + GlobalVariable *SanCovLowestStack; InlineAsm *EmptyAsm; Type *IntptrTy, *IntptrPtrTy, *Int64Ty, *Int64PtrTy, *Int32Ty, *Int32PtrTy, *Int16Ty, *Int8Ty, *Int8PtrTy; @@ -333,6 +348,24 @@ SanCovTraceSwitchFunction = checkSanitizerInterfaceFunction(M.getOrInsertFunction( SanCovTraceSwitchName, VoidTy, Int64Ty, Int64PtrTy)); + + Constant *SanCovLowestStackConstant = + M.getOrInsertGlobal(SanCovLowestStackName, IntptrTy); + SanCovLowestStackTLSWrapper = + checkSanitizerInterfaceFunction(M.getOrInsertFunction( + SanCovLowestStackTLSWrapperName, IntptrTy->getPointerTo())); + if (Options.StackDepth) { + assert(isa(SanCovLowestStackConstant)); + SanCovLowestStack = cast(SanCovLowestStackConstant); + if (!SanCovLowestStack->isDeclaration()) { + // Check that the user has correctly defined: + // thread_local uintptr_t __sanitizer_cov_lowest_stack + // and initialize it. + assert(SanCovLowestStack->isThreadLocal()); + SanCovLowestStack->setInitializer(Constant::getAllOnesValue(IntptrTy)); + } + } + // Make sure smaller parameters are zero-extended to i64 as required by the // x86_64 ABI. if (TargetTriple.getArch() == Triple::x86_64) { @@ -451,6 +484,9 @@ if (F.getName() == "__local_stdio_printf_options" || F.getName() == "__local_stdio_scanf_options") return false; + // Avoid infinite recursion by not instrumenting stack depth TLS wrapper + if (F.getName() == SanCovLowestStackTLSWrapperName) + return false; // Don't instrument functions using SEH for now. Splitting basic blocks like // we do for coverage breaks WinEHPrepare. // FIXME: Remove this when SEH no longer uses landingpad pattern matching. @@ -728,6 +764,20 @@ SetNoSanitizeMetadata(Load); SetNoSanitizeMetadata(Store); } + if (Options.StackDepth && IsEntryBB) { + // Check stack depth. If it's the deepest so far, record it. + Function *GetFrameAddr = + Intrinsic::getDeclaration(F.getParent(), Intrinsic::frameaddress); + auto FrameAddrPtr = + IRB.CreateCall(GetFrameAddr, {Constant::getNullValue(Int32Ty)}); + auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy); + auto LowestStackPtr = IRB.CreateCall(SanCovLowestStackTLSWrapper); + auto LowestStack = IRB.CreateLoad(LowestStackPtr); + auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack); + auto ThenTerm = SplitBlockAndInsertIfThen(IsStackLower, &*IP, false); + IRBuilder<> ThenIRB(ThenTerm); + ThenIRB.CreateStore(FrameAddrInt, LowestStackPtr); + } } std::string Index: llvm/test/Instrumentation/SanitizerCoverage/stack-depth.ll =================================================================== --- /dev/null +++ llvm/test/Instrumentation/SanitizerCoverage/stack-depth.ll @@ -0,0 +1,50 @@ +; This check verifies that stack depth instrumentation works correctly. +; RUN: opt < %s -sancov -sanitizer-coverage-level=1 \ +; RUN: -sanitizer-coverage-stack-depth -S | FileCheck %s --enable-var-scope +; RUN: opt < %s -sancov -sanitizer-coverage-level=3 \ +; RUN: -sanitizer-coverage-stack-depth -sanitizer-coverage-trace-pc-guard \ +; RUN: -S | FileCheck %s --enable-var-scope + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK: @__sanitizer_cov_lowest_stack = thread_local global i64 -1 +@__sanitizer_cov_lowest_stack = thread_local global i64 0, align 8 + +define i32 @foo() { +entry: +; CHECK-LABEL: define i32 @foo +; CHECK: [[framePtr:%[^ \t]+]] = call i8* @llvm.frameaddress(i32 0) +; CHECK: [[frameInt:%[^ \t]+]] = ptrtoint i8* [[framePtr]] to [[$intType:i[0-9]+]] +; CHECK: [[lowestPtr:%[^ \t]+]] = call [[$intType]]* @_ZTW28__sanitizer_cov_lowest_stack +; CHECK: [[lowestInt:%[^ \t]+]] = load [[$intType]], [[$intType]]* [[lowestPtr]] +; CHECK: [[cmp:%[^ \t]+]] = icmp ult [[$intType]] [[frameInt]], [[lowestInt]] +; CHECK: br i1 [[cmp]], label %[[ifLabel:[^ \t]+]], label +; CHECK: