https://github.com/kees updated https://github.com/llvm/llvm-project/pull/138323

>From 03e2eead7c4034c81c79619c1507dd27145aaa43 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     |  86 ++++--
 .../SanitizerCoverage/stack-depth-callback.ll | 253 ++++++++++++++++++
 9 files changed, 365 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..b48274fa7015d 100644
--- a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp
+++ b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp
@@ -86,6 +86,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 +153,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 +209,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 +280,7 @@ class ModuleSanitizerCoverage {
   DomTreeCallback DTCallback;
   PostDomTreeCallback PDTCallback;
 
+  FunctionCallee SanCovStackDepthCallback;
   FunctionCallee SanCovTracePCIndir;
   FunctionCallee SanCovTracePC, SanCovTracePCGuard;
   std::array<FunctionCallee, 4> SanCovTraceCmpFunction;
@@ -514,6 +524,9 @@ bool ModuleSanitizerCoverage::instrumentModule() {
   SanCovTracePCGuard =
       M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, PtrTy);
 
+  SanCovStackDepthCallback =
+      M.getOrInsertFunction(SanCovStackDepthCallbackName, VoidTy);
+
   for (auto &F : M)
     instrumentFunction(F);
 
@@ -1078,22 +1091,65 @@ 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();
+    const DataLayout &DL = M->getDataLayout();
+
+    if (Options.StackDepthCallbackMin) {
+      // In callback mode, only add call when stack depth reaches minimum.
+      uint32_t EstimatedStackSize = 0;
+      // If dynamic alloca found, always add call.
+      bool dynamic_alloca = false;
+      // Find an insertion point after last "alloca".
+      llvm::Instruction *InsertBefore = NULL;
+
+      // Examine all allocas in the basic block. since we're too early
+      // to have results from Intrinsic::frameaddress, we have to manually
+      // estimate the stack size.
+      for (auto &I : BB) {
+        if (auto *AI = dyn_cast<AllocaInst>(&I)) {
+          // Move potential insertion point past the "alloca".
+          InsertBefore = I.getNextNode();
+
+          // Make an estimate on the stack usage.
+          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();
+              } else {
+                dynamic_alloca = true;
+              }
+            }
+            EstimatedStackSize += Bytes;
+          } else {
+            dynamic_alloca = true;
+          }
+        }
+      }
+
+      if (dynamic_alloca ||
+          EstimatedStackSize >= Options.StackDepthCallbackMin) {
+        if (InsertBefore)
+          IRB.SetInsertPoint(InsertBefore);
+        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(DL.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..e95b69c4c0402
--- /dev/null
+++ b/llvm/test/Instrumentation/SanitizerCoverage/stack-depth-callback.ll
@@ -0,0 +1,253 @@
+; 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:
+; COMMON-NEXT:    [[VAR:%.*]] = alloca i8, i32 %input, 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-NEXT:     call void @__sanitizer_cov_stack_depth()
+; 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 {
+  %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
+; COMMON-LABEL:   %7 = call ptr @llvm.stacksave
+; COMMON-NEXT:    store ptr %7, ptr %3, align 8
+; COMMON-NEXT:    [[VAR:%.*]] = alloca i32, i64 %6, align 16
+; 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()
+  %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 7b1eff16a499ce1b69007f805cae7e59d480c104 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 | 43 ++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst
index f952198295ebc..42aceeb0f95da 100644
--- a/clang/docs/SanitizerCoverage.rst
+++ b/clang/docs/SanitizerCoverage.rst
@@ -385,6 +385,49 @@ 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 every
+non-leaf function to check the stack pointer against this variable,
+and if it is lower, store the current stack pointer. This effectively
+inserts the following:
+
+.. code-block:: c++
+
+  thread_local uintptr_t __sancov_lowest_stack;
+
+  uintptr_t stack = (uintptr_t)__builtin_frame_address(0);
+  if (stack < __sancov_lowest_stack)
+    __sancov_lowest_stack = stack;
+
+If ``-fsanitize-coverage-stack-depth-callback-min=N`` is also used, the
+tracking is delegated to a callback, ``__sanitizer_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 estimated to be 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. A roughtly
+equivalent implementation of ``__sancov_lowest_stack`` using the callback
+would look like this:
+
+.. code-block:: c++
+
+  void __sanitizer_cov_stack_depth(void) {
+    uintptr_t stack = (uintptr_t)__builtin_frame_address(0);
+
+    if (stack < __sancov_lowest_stack)
+      __sancov_lowest_stack = stack;
+  }
+
 Gated Trace Callbacks
 =====================
 

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to