https://github.com/kees updated https://github.com/llvm/llvm-project/pull/138323
>From a481c0b96417c63786811e5bf118f3eb2e7dabb1 Mon Sep 17 00:00:00 2001 From: Kees Cook <k...@kernel.org> Date: Fri, 2 May 2025 10:51:02 -0700 Subject: [PATCH 1/2] [sancov] Introduce optional callback for stack-depth tracking Normally -fsanitize-coverage=stack-depth inserts inline arithmetic to update thread_local __sancov_lowest_stack. To support stack depth tracking in the Linux kernel, which does not implement traditional thread_local storage, provide the option to call a function instead. This matches the existing "stackleak" implementation that is supported in Linux via a GCC plugin. To make this coverage more performant, a minimum estimated stack depth can be chosen to enable the callback mode, skipping instrumentation of functions with smaller stacks. With -fsanitize-coverage-stack-depth-callback-min set greater than 0, the __sanitize_cov_stack_depth() callback will be injected when the estimated stack depth is greater than or equal to the given minimum. --- clang/include/clang/Basic/CodeGenOptions.def | 1 + clang/include/clang/Driver/Options.td | 7 + clang/include/clang/Driver/SanitizerArgs.h | 1 + clang/lib/CodeGen/BackendUtil.cpp | 1 + clang/lib/Driver/SanitizerArgs.cpp | 16 ++ clang/test/Driver/fsanitize-coverage.c | 14 + .../llvm/Transforms/Utils/Instrumentation.h | 1 + .../Instrumentation/SanitizerCoverage.cpp | 74 +++-- .../SanitizerCoverage/stack-depth-callback.ll | 254 ++++++++++++++++++ 9 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 llvm/test/Instrumentation/SanitizerCoverage/stack-depth-callback.ll diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 927972015c3dc..452b1e325afb2 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -305,6 +305,7 @@ CODEGENOPT(SanitizeCoveragePCTable, 1, 0) ///< Create a PC Table. CODEGENOPT(SanitizeCoverageControlFlow, 1, 0) ///< Collect control flow CODEGENOPT(SanitizeCoverageNoPrune, 1, 0) ///< Disable coverage pruning. CODEGENOPT(SanitizeCoverageStackDepth, 1, 0) ///< Enable max stack depth tracing +VALUE_CODEGENOPT(SanitizeCoverageStackDepthCallbackMin , 32, 0) ///< Enable stack depth tracing callbacks. CODEGENOPT(SanitizeCoverageTraceLoads, 1, 0) ///< Enable tracing of loads. CODEGENOPT(SanitizeCoverageTraceStores, 1, 0) ///< Enable tracing of stores. CODEGENOPT(SanitizeBinaryMetadataCovered, 1, 0) ///< Emit PCs for covered functions. diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 561b0498c549c..251d20d0b1984 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2361,6 +2361,13 @@ def fsanitize_coverage_ignorelist : Joined<["-"], "fsanitize-coverage-ignorelist HelpText<"Disable sanitizer coverage instrumentation for modules and functions " "that match the provided special case list, even the allowed ones">, MarshallingInfoStringVector<CodeGenOpts<"SanitizeCoverageIgnorelistFiles">>; +def fsanitize_coverage_stack_depth_callback_min_EQ + : Joined<["-"], "fsanitize-coverage-stack-depth-callback-min=">, + Group<f_clang_Group>, + MetaVarName<"<M>">, + HelpText<"Use callback for max stack depth tracing with minimum stack " + "depth M">, + MarshallingInfoInt<CodeGenOpts<"SanitizeCoverageStackDepthCallbackMin">>; def fexperimental_sanitize_metadata_EQ : CommaJoined<["-"], "fexperimental-sanitize-metadata=">, Group<f_Group>, HelpText<"Specify the type of metadata to emit for binary analysis sanitizers">; diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 528e3b400f3dc..513339060f2b2 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -34,6 +34,7 @@ class SanitizerArgs { std::vector<std::string> CoverageIgnorelistFiles; std::vector<std::string> BinaryMetadataIgnorelistFiles; int CoverageFeatures = 0; + int CoverageStackDepthCallbackMin = 0; int BinaryMetadataFeatures = 0; int OverflowPatternExclusions = 0; int MsanTrackOrigins = 0; diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index c9ceb49ce5ceb..42c59377688b2 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -255,6 +255,7 @@ getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) { Opts.InlineBoolFlag = CGOpts.SanitizeCoverageInlineBoolFlag; Opts.PCTable = CGOpts.SanitizeCoveragePCTable; Opts.StackDepth = CGOpts.SanitizeCoverageStackDepth; + Opts.StackDepthCallbackMin = CGOpts.SanitizeCoverageStackDepthCallbackMin; Opts.TraceLoads = CGOpts.SanitizeCoverageTraceLoads; Opts.TraceStores = CGOpts.SanitizeCoverageTraceStores; Opts.CollectControlFlow = CGOpts.SanitizeCoverageControlFlow; diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index ff08bffdbde1f..b29fde92d0722 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -751,6 +751,17 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, options::OPT_fno_sanitize_ignorelist, clang::diag::err_drv_malformed_sanitizer_ignorelist, DiagnoseErrors); + // Verify that -fsanitize-coverage-stack-depth-callback-min is >= 0. + if (Arg *A = Args.getLastArg( + options::OPT_fsanitize_coverage_stack_depth_callback_min_EQ)) { + StringRef S = A->getValue(); + if (S.getAsInteger(0, CoverageStackDepthCallbackMin) || + CoverageStackDepthCallbackMin < 0) { + if (DiagnoseErrors) + D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S; + } + } + // Parse -f[no-]sanitize-memory-track-origins[=level] options. if (AllAddedKinds & SanitizerKind::Memory) { if (Arg *A = @@ -1269,6 +1280,11 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, addSpecialCaseListOpt(Args, CmdArgs, "-fsanitize-coverage-ignorelist=", CoverageIgnorelistFiles); + if (CoverageStackDepthCallbackMin) + CmdArgs.push_back( + Args.MakeArgString("-fsanitize-coverage-stack-depth-callback-min=" + + Twine(CoverageStackDepthCallbackMin))); + if (!GPUSanitize) { // Translate available BinaryMetadataFeatures to corresponding clang-cc1 // flags. Does not depend on any other sanitizers. Unsupported on GPUs. diff --git a/clang/test/Driver/fsanitize-coverage.c b/clang/test/Driver/fsanitize-coverage.c index dc4c39396d45c..2ef452b593774 100644 --- a/clang/test/Driver/fsanitize-coverage.c +++ b/clang/test/Driver/fsanitize-coverage.c @@ -93,6 +93,20 @@ // CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-trace-pc-guard // CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-stack-depth +// RUN: %clang --target=x86_64-linux-gnu \ +// RUN: -fsanitize-coverage-stack-depth-callback-min=100 %s -### 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-CALLBACK +// RUN: %clang --target=x86_64-linux-gnu \ +// RUN: -fsanitize-coverage-stack-depth-callback-min=0 %s -### 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-CALLBACK-ZERO +// RUN: not %clang --target=x86_64-linux-gnu \ +// RUN: -fsanitize-coverage-stack-depth-callback-min=-10 %s -### 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-CALLBACK-NEGATIVE +// CHECK-STACK-DEPTH-CALLBACK-NOT: error: +// CHECK-STACK-DEPTH-CALLBACK: -fsanitize-coverage-stack-depth-callback-min=100 +// CHECK-STACK-DEPTH-CALLBACK-ZERO-NOT: -fsanitize-coverage-stack-depth-callback-min=0 +// CHECK-STACK-DEPTH-CALLBACK-NEGATIVE: error: invalid value '-10' in '-fsanitize-coverage-stack-depth-callback-min=-10' + // RUN: %clang --target=x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=trace-cmp,indirect-calls %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TYPE-NECESSARY // CHECK-NO-TYPE-NECESSARY-NOT: error: // CHECK-NO-TYPE-NECESSARY: -fsanitize-coverage-indirect-calls diff --git a/llvm/include/llvm/Transforms/Utils/Instrumentation.h b/llvm/include/llvm/Transforms/Utils/Instrumentation.h index 0e2c0d9bfa605..f2bc6854ae5b6 100644 --- a/llvm/include/llvm/Transforms/Utils/Instrumentation.h +++ b/llvm/include/llvm/Transforms/Utils/Instrumentation.h @@ -158,6 +158,7 @@ struct SanitizerCoverageOptions { bool PCTable = false; bool NoPrune = false; bool StackDepth = false; + int StackDepthCallbackMin = 0; bool TraceLoads = false; bool TraceStores = false; bool CollectControlFlow = false; diff --git a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp index e52269637b92d..e54afb1fdc8a7 100644 --- a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp +++ b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp @@ -33,6 +33,7 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/SpecialCaseList.h" #include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" #include "llvm/Transforms/Utils/ModuleUtils.h" @@ -86,6 +87,7 @@ const char SanCovPCsSectionName[] = "sancov_pcs"; const char SanCovCFsSectionName[] = "sancov_cfs"; const char SanCovCallbackGateSectionName[] = "sancov_gate"; +const char SanCovStackDepthCallbackName[] = "__sanitizer_cov_stack_depth"; const char SanCovLowestStackName[] = "__sancov_lowest_stack"; const char SanCovCallbackGateName[] = "__sancov_should_track"; @@ -152,6 +154,12 @@ static cl::opt<bool> ClStackDepth("sanitizer-coverage-stack-depth", cl::desc("max stack depth tracing"), cl::Hidden); +static cl::opt<int> ClStackDepthCallbackMin( + "sanitizer-coverage-stack-depth-callback-min", + cl::desc("max stack depth tracing should use callback and only when " + "stack depth more than specified"), + cl::Hidden); + static cl::opt<bool> ClCollectCF("sanitizer-coverage-control-flow", cl::desc("collect control flow for each function"), cl::Hidden); @@ -202,6 +210,8 @@ SanitizerCoverageOptions OverrideFromCL(SanitizerCoverageOptions Options) { Options.PCTable |= ClCreatePCTable; Options.NoPrune |= !ClPruneBlocks; Options.StackDepth |= ClStackDepth; + Options.StackDepthCallbackMin = std::max(Options.StackDepthCallbackMin, + ClStackDepthCallbackMin.getValue()); Options.TraceLoads |= ClLoadTracing; Options.TraceStores |= ClStoreTracing; Options.GatedCallbacks |= ClGatedCallbacks; @@ -271,6 +281,7 @@ class ModuleSanitizerCoverage { DomTreeCallback DTCallback; PostDomTreeCallback PDTCallback; + FunctionCallee SanCovStackDepthCallback; FunctionCallee SanCovTracePCIndir; FunctionCallee SanCovTracePC, SanCovTracePCGuard; std::array<FunctionCallee, 4> SanCovTraceCmpFunction; @@ -514,6 +525,9 @@ bool ModuleSanitizerCoverage::instrumentModule() { SanCovTracePCGuard = M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, PtrTy); + SanCovStackDepthCallback = + M.getOrInsertFunction(SanCovStackDepthCallbackName, VoidTy); + for (auto &F : M) instrumentFunction(F); @@ -1029,6 +1043,8 @@ void ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB, if (IsEntryBB) { if (auto SP = F.getSubprogram()) EntryLoc = DILocation::get(SP->getContext(), SP->getScopeLine(), 0, SP); + // FIXME: stack-depth does not correctly instrument dynamic allocas. + // // Keep static allocas and llvm.localescape calls in the entry block. Even // if we aren't splitting the block, it's nice for allocas to be before // calls. @@ -1078,22 +1094,50 @@ void ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB, Store->setNoSanitizeMetadata(); } if (Options.StackDepth && IsEntryBB && !IsLeafFunc) { - // Check stack depth. If it's the deepest so far, record it. Module *M = F.getParent(); - auto FrameAddrPtr = IRB.CreateIntrinsic( - Intrinsic::frameaddress, - IRB.getPtrTy(M->getDataLayout().getAllocaAddrSpace()), - {Constant::getNullValue(Int32Ty)}); - auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy); - auto LowestStack = IRB.CreateLoad(IntptrTy, SanCovLowestStack); - auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack); - auto ThenTerm = SplitBlockAndInsertIfThen( - IsStackLower, &*IP, false, - MDBuilder(IRB.getContext()).createUnlikelyBranchWeights()); - IRBuilder<> ThenIRB(ThenTerm); - auto Store = ThenIRB.CreateStore(FrameAddrInt, SanCovLowestStack); - LowestStack->setNoSanitizeMetadata(); - Store->setNoSanitizeMetadata(); + if (Options.StackDepthCallbackMin) { + // In callback mode, only add call when stack depth reaches minimum. + const DataLayout &DL = M->getDataLayout(); + uint32_t EstimatedStackSize = 0; + + // Make an estimate on the stack usage. + for (auto &I : F.getEntryBlock()) { + if (auto *AI = dyn_cast<AllocaInst>(&I)) { + if (AI->isStaticAlloca()) { + uint32_t Bytes = DL.getTypeAllocSize(AI->getAllocatedType()); + if (AI->isArrayAllocation()) { + if (const ConstantInt *arraySize = + dyn_cast<ConstantInt>(AI->getArraySize())) + Bytes *= arraySize->getZExtValue(); + } + EstimatedStackSize += Bytes; + } else { + // Dynamic alloca: require we always perform callback. + EstimatedStackSize = Options.StackDepthCallbackMin; + break; + } + } + } + + if (EstimatedStackSize >= Options.StackDepthCallbackMin) + IRB.CreateCall(SanCovStackDepthCallback)->setCannotMerge(); + } else { + // Check stack depth. If it's the deepest so far, record it. + auto FrameAddrPtr = IRB.CreateIntrinsic( + Intrinsic::frameaddress, + IRB.getPtrTy(M->getDataLayout().getAllocaAddrSpace()), + {Constant::getNullValue(Int32Ty)}); + auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy); + auto LowestStack = IRB.CreateLoad(IntptrTy, SanCovLowestStack); + auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack); + auto ThenTerm = SplitBlockAndInsertIfThen( + IsStackLower, &*IP, false, + MDBuilder(IRB.getContext()).createUnlikelyBranchWeights()); + IRBuilder<> ThenIRB(ThenTerm); + auto Store = ThenIRB.CreateStore(FrameAddrInt, SanCovLowestStack); + LowestStack->setNoSanitizeMetadata(); + Store->setNoSanitizeMetadata(); + } } } diff --git a/llvm/test/Instrumentation/SanitizerCoverage/stack-depth-callback.ll b/llvm/test/Instrumentation/SanitizerCoverage/stack-depth-callback.ll new file mode 100644 index 0000000000000..6c9eabbfef894 --- /dev/null +++ b/llvm/test/Instrumentation/SanitizerCoverage/stack-depth-callback.ll @@ -0,0 +1,254 @@ +; This check verifies that stack depth callback instrumentation works correctly. +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth -sanitizer-coverage-stack-depth-callback-min=1 -S | FileCheck %s --check-prefixes=COMMON,CB1 +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth -sanitizer-coverage-stack-depth-callback-min=8 -S | FileCheck %s --check-prefixes=COMMON,CB8 +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth -sanitizer-coverage-stack-depth-callback-min=16 -S | FileCheck %s --check-prefixes=COMMON,CB16 +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth -sanitizer-coverage-stack-depth-callback-min=32 -S | FileCheck %s --check-prefixes=COMMON,CB32 +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth -sanitizer-coverage-stack-depth-callback-min=64 -S | FileCheck %s --check-prefixes=COMMON,CB64 +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth -sanitizer-coverage-stack-depth-callback-min=128 -S | FileCheck %s --check-prefixes=COMMON,CB128 + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; No stack, just return: our leaf function +define i32 @foo() { +; COMMON-LABEL: define i32 @foo() { +; COMMON-NEXT: entry: +; CB1-NOT: call void @__sanitizer_cov_stack_depth() +; CB8-NOT: call void @__sanitizer_cov_stack_depth() +; CB16-NOT: call void @__sanitizer_cov_stack_depth() +; CB32-NOT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: ret i32 7 +; +entry: + + ret i32 7 +} + +; No stack, just function call +define i32 @retcall() { +; COMMON-LABEL: define i32 @retcall() { +; COMMON-NEXT: entry: +; CB1-NOT: call void @__sanitizer_cov_stack_depth() +; CB8-NOT: call void @__sanitizer_cov_stack_depth() +; CB16-NOT: call void @__sanitizer_cov_stack_depth() +; CB32-NOT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + + %call = call i32 @foo() + ret i32 %call +} + +; No stack, just function call, with argument +define i32 @witharg(i32 %input) { +; COMMON-LABEL: define i32 @witharg(i32 %input) { +; COMMON-NEXT: entry: +; CB1-NOT: call void @__sanitizer_cov_stack_depth() +; CB8-NOT: call void @__sanitizer_cov_stack_depth() +; CB16-NOT: call void @__sanitizer_cov_stack_depth() +; CB32-NOT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + + %call = call i32 @foo() + ret i32 %call +} + +; 4 byte stack of scalars +define i32 @alloc4_0() { +; COMMON-LABEL: define i32 @alloc4_0() { +; COMMON-NEXT: entry: +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NOT: call void @__sanitizer_cov_stack_depth() +; CB16-NOT: call void @__sanitizer_cov_stack_depth() +; CB32-NOT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %var1 = alloca i32, align 4 + + %call = call i32 @foo() + ret i32 %call +} + +; 16 byte stack of scalars +define i32 @alloc16_0() { +; COMMON-LABEL: define i32 @alloc16_0() { +; COMMON-NEXT: entry: +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NOT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %var1 = alloca i32, align 4 + %var2 = alloca i32, align 4 + %var3 = alloca i32, align 4 + %var4 = alloca i32, align 4 + + %call = call i32 @foo() + ret i32 %call +} + +; 32 byte stack of scalars +define i32 @alloc32_0() { +; COMMON-LABEL: define i32 @alloc32_0() { +; COMMON-NEXT: entry: +; COMMON-NEXT: [[VAR:%.*]] = alloca i64, align 8 +; COMMON-NEXT: [[VAR:%.*]] = alloca i64, align 8 +; COMMON-NEXT: [[VAR:%.*]] = alloca i64, align 8 +; COMMON-NEXT: [[VAR:%.*]] = alloca i64, align 8 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NEXT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %var1 = alloca i64, align 8 + %var2 = alloca i64, align 8 + %var3 = alloca i64, align 8 + %var4 = alloca i64, align 8 + + %call = call i32 @foo() + ret i32 %call +} + +; 36 byte stack of 1 4 byte scalar and 1 32 byte array +define i32 @alloc4_32x1() { +; COMMON-LABEL: define i32 @alloc4_32x1() { +; COMMON-NEXT: entry: +; COMMON-NEXT: [[VAR:%.*]] = alloca i8, i32 32, align 4 +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NEXT: call void @__sanitizer_cov_stack_depth() +; CB64-NOT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %stack_array1 = alloca i8, i32 32, align 4 + %var1 = alloca i32, align 4 + + %call = call i32 @foo() + ret i32 %call +} + +; 64 byte stack of 2 32 byte arrays +define i32 @alloc0_32x2() { +; COMMON-LABEL: define i32 @alloc0_32x2() { +; COMMON-NEXT: entry: +; COMMON-NEXT: [[VAR:%.*]] = alloca i8, i32 32, align 4 +; COMMON-NEXT: [[VAR:%.*]] = alloca i8, i32 32, align 4 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NEXT: call void @__sanitizer_cov_stack_depth() +; CB64-NEXT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %stack_array1 = alloca i8, i32 32, align 4 + %stack_array2 = alloca i8, i32 32, align 4 + + %call = call i32 @foo() + ret i32 %call +} + +; 64 byte stack of 1 64 byte array +define i32 @alloc0_64x1() { +; COMMON-LABEL: define i32 @alloc0_64x1() { +; COMMON-NEXT: entry: +; COMMON-NEXT: [[VAR:%.*]] = alloca i8, i32 64, align 4 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NEXT: call void @__sanitizer_cov_stack_depth() +; CB64-NEXT: call void @__sanitizer_cov_stack_depth() +; CB128-NOT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %stack_array = alloca i8, i32 64, align 4 + + %call = call i32 @foo() + ret i32 %call +} + +; dynamic stack sized by i32 +define i32 @alloc0_32xDyn(i32 %input) { +; COMMON-LABEL: define i32 @alloc0_32xDyn(i32 %input) { +; COMMON-NEXT: entry: +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NEXT: call void @__sanitizer_cov_stack_depth() +; CB64-NEXT: call void @__sanitizer_cov_stack_depth() +; CB128-NEXT: call void @__sanitizer_cov_stack_depth() +; COMMON-NEXT: [[VAR:%.*]] = alloca i8, i32 %input, align 4 +; COMMON-NEXT: [[CALL:%.*]] = call i32 @foo() +; COMMON-NEXT: ret i32 [[CALL]] +entry: + %stack_array1 = alloca i8, i32 %input, align 4 + + %call = call i32 @foo() + ret i32 %call +} + +; true dynamic stack sized by i32, from C: +; static int dyamic_alloca(int size) +; { +; int array[size]; +; return foo(); +; } +define dso_local i32 @dynamic_alloca(i32 noundef %0) #0 { +; COMMON-LABEL: define dso_local i32 @dynamic_alloca(i32 noundef %0) { +; COMMON-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; COMMON-NEXT: [[VAR:%.*]] = alloca ptr, align 8 +; COMMON-NEXT: [[VAR:%.*]] = alloca i64, align 8 +; CB1-NEXT: call void @__sanitizer_cov_stack_depth() +; CB8-NEXT: call void @__sanitizer_cov_stack_depth() +; CB16-NEXT: call void @__sanitizer_cov_stack_depth() +; CB32-NEXT: call void @__sanitizer_cov_stack_depth() +; CB64-NEXT: call void @__sanitizer_cov_stack_depth() +; CB128-NEXT: call void @__sanitizer_cov_stack_depth() + %2 = alloca i32, align 4 + %3 = alloca ptr, align 8 + %4 = alloca i64, align 8 + store i32 %0, ptr %2, align 4 + %5 = load i32, ptr %2, align 4 + %6 = zext i32 %5 to i64 + %7 = call ptr @llvm.stacksave.p0() + store ptr %7, ptr %3, align 8 + %8 = alloca i32, i64 %6, align 16 + store i64 %6, ptr %4, align 8 + %9 = call i32 @foo() + %10 = load ptr, ptr %3, align 8 +; COMMON-LABEL: call void @llvm.stackrestore +; COMMON-NEXT: ret i32 %9 + call void @llvm.stackrestore.p0(ptr %10) + ret i32 %9 +} >From 18bc755ce1a9880d944655b413ad5764e48e617b Mon Sep 17 00:00:00 2001 From: Kees Cook <k...@kernel.org> Date: Mon, 5 May 2025 19:57:44 -0700 Subject: [PATCH 2/2] [sancov] Document -fsanitize-coverage=stack-depth Add documentation to detail the behaviors of the stack-depth tracer, including the new -fsanitize-coverage-stack-depth-callback-min=N argument. --- clang/docs/SanitizerCoverage.rst | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst index f952198295ebc..c6b624aedd671 100644 --- a/clang/docs/SanitizerCoverage.rst +++ b/clang/docs/SanitizerCoverage.rst @@ -385,6 +385,46 @@ Users need to implement a single function to capture the CF table at startup: // the collected control flow. } +Tracing Stack Depth +=================== + +With ``-fsanitize-coverage=stack-depth`` the compiler will track how much +stack space has been used for a function call chain. Leaf functions are +not included in this tracing. + +The maximum depth of a function call graph is stored in the thread-local +``__sancov_lowest_stack`` variable. Instrumentation is inserted in each +non-leaf function to check the stack pointer against this variable, and +if it is lower, store the current stack pointer. This effectively does: + +.. code-block:: c++ + + thread_local __sancov_lowest_stack; + + if (stack_pointer < __sancov_lowest_stack) + __sancov_lowest_stack = stack_pointer; + +If ``-fsanitize-coverage-stack-depth-callback-min=N`` is also used, +the tracking is delegated to a callback, ``__sanitize_cov_stack_depth``, +instead of adding instrumentation to update ``__sancov_lowest_stack``. +The ``N`` of the argument is used to determine which functions to +instrument. Only functions using ``N`` bytes or more of stack space +will be instrumented to call the tracing callback. In the case of a +dynamically sized stack, the callback is unconditionally added. + +The callback takes no arguments and is responsible for determining +the stack pointer and doing any needed comparisons and storage. The +prototype is: + +.. code-block:: c++ + + extern "C" + void __sanitize_cov_stack_depth(void); + +Note that, currently, dynamically sized stacks are not tracked by +instrumentation correctly, as it is inserted too early. This means +that only constant sized stack allocations are currently tracked. + Gated Trace Callbacks ===================== _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits