[clang] [compiler-rt] [llvm] [compiler-rt][SanitizerCoverage] Add LibFuzzer support for trace-args/trace-ret (PR #227612)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 30 02:10:17 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-driver

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Yunseong Kim (yskzalloc)

<details>
<summary>Changes</summary>

Fold every reported value into the value-profile map through TracePC::HandleDataflow. The fold binds the value to the site it was observed at the function, the argument or return position, and the field within a struct - so a value never seen at that site before becomes a new value-profile feature under -use_value_profile: an input that reaches a function with a new argument value counts as new coverage and is kept in the corpus. This is the userspace equivalent of what kcov-dataflow records.
    
A value is folded directly when num_fields is zero, which is the common case and touches no memory at all; only a non-zero num_fields makes the runtime read fields out of the object `val` points at, and at most 32 of them, to bound the work done on every call. A size of zero is the value-unavailable case and is ignored. Returns are folded under a location of their own, because they share a pc with the arguments of the same function.

This is PR 4 of a 5-PR stack. the diff shows `compiler-rt/lib/sanitizer_common/`:

1. Depends on (llvm/): the instrumentation pass and IR tests
2. Depends on (clang/): `-fsanitize-coverage=trace-args,trace-ret` driver/frontend support and documentation https://github.com/llvm/llvm-project/pull/218254
3. Depends on (compiler-rt/lib/sanitizer_common/): Add a runtime for trace-args/trace-ret https://github.com/llvm/llvm-project/pull/218265
4. This PR (compiler-rt/lib/fuzzer/): Add libFuzzer support as a value-profile consumer
5. Follow-up (clang/docs/SanitizerCoverage.md): Documentation update

---

Patch is 82.23 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/227612.diff


22 Files Affected:

- (modified) clang/include/clang/Basic/CodeGenOptions.def (+2) 
- (modified) clang/include/clang/Basic/CodeGenOptions.h (+2-1) 
- (modified) clang/include/clang/Options/Options.td (+10) 
- (modified) clang/lib/CodeGen/BackendUtil.cpp (+2) 
- (modified) clang/lib/Driver/SanitizerArgs.cpp (+11-3) 
- (added) clang/test/CodeGen/sanitizer-coverage-trace-args-ret.c (+38) 
- (modified) clang/test/Driver/fsanitize-coverage.c (+13) 
- (modified) compiler-rt/lib/fuzzer/FuzzerTracePC.cpp (+66) 
- (modified) compiler-rt/lib/fuzzer/FuzzerTracePC.h (+7) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_coverage_interface.inc (+2) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep_new.cpp (+7) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h (+15) 
- (added) compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_trace_args.cpp (+50) 
- (modified) llvm/include/llvm/Transforms/Utils/Instrumentation.h (+2) 
- (modified) llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp (+534-1) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-args-abi.ll (+127) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-args-dominance.ll (+67) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-args-inlined.ll (+52) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-args-no-debug.ll (+35) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-args.ll (+130) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-no-spill.ll (+67) 
- (added) llvm/test/Instrumentation/SanitizerCoverage/trace-ret.ll (+138) 


``````````diff
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 2a04538677005..574f7a6bf8c25 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -328,6 +328,8 @@ CODEGENOPT(SanitizeCoverageStackDepth, 1, 0, Benign) ///< Enable max stack depth
 VALUE_CODEGENOPT(SanitizeCoverageStackDepthCallbackMin , 32, 0, Benign) ///< Enable stack depth tracing callbacks.
 CODEGENOPT(SanitizeCoverageTraceLoads, 1, 0, Benign) ///< Enable tracing of loads.
 CODEGENOPT(SanitizeCoverageTraceStores, 1, 0, Benign) ///< Enable tracing of stores.
+CODEGENOPT(SanitizeCoverageTraceArgs, 1, 0, Benign) ///< Enable tracing of function args.
+CODEGENOPT(SanitizeCoverageTraceRet, 1, 0, Benign) ///< Enable tracing of return values.
 CODEGENOPT(SanitizeBinaryMetadataCovered, 1, 0, Benign) ///< Emit PCs for covered functions.
 CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0, Benign) ///< Emit PCs for atomic operations.
 CODEGENOPT(SanitizeBinaryMetadataUAR, 1, 0, Benign) ///< Emit PCs for start of functions
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 17f367bc02607..b805e069a109b 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -686,7 +686,8 @@ class CodeGenOptions : public CodeGenOptionsBase {
   bool hasSanitizeCoverage() const {
     return SanitizeCoverageType || SanitizeCoverageIndirectCalls ||
            SanitizeCoverageTraceCmp || SanitizeCoverageTraceLoads ||
-           SanitizeCoverageTraceStores || SanitizeCoverageControlFlow;
+           SanitizeCoverageTraceStores || SanitizeCoverageControlFlow ||
+           SanitizeCoverageTraceArgs || SanitizeCoverageTraceRet;
   }
 
   // Check if any one of SanitizeBinaryMetadata* is enabled.
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index e991dba448033..0a97f5c9d342a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -8345,6 +8345,16 @@ def fsanitize_coverage_trace_stores
       Group<fsan_cov_Group>,
       HelpText<"Enable tracing of stores">,
       MarshallingInfoFlag<CodeGenOpts<"SanitizeCoverageTraceStores">>;
+def fsanitize_coverage_trace_args
+    : Flag<["-"], "fsanitize-coverage-trace-args">,
+      Group<fsan_cov_Group>,
+      HelpText<"Enable dataflow tracing of function arguments">,
+      MarshallingInfoFlag<CodeGenOpts<"SanitizeCoverageTraceArgs">>;
+def fsanitize_coverage_trace_ret
+    : Flag<["-"], "fsanitize-coverage-trace-ret">,
+      Group<fsan_cov_Group>,
+      HelpText<"Enable dataflow tracing of return values">,
+      MarshallingInfoFlag<CodeGenOpts<"SanitizeCoverageTraceRet">>;
 def fexperimental_sanitize_metadata_EQ_covered
     : Flag<["-"], "fexperimental-sanitize-metadata=covered">,
       HelpText<"Emit PCs for code covered with binary analysis sanitizers">,
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index c09a8f7c0d679..f221a2a51d82a 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -259,6 +259,8 @@ getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) {
   Opts.StackDepthCallbackMin = CGOpts.SanitizeCoverageStackDepthCallbackMin;
   Opts.TraceLoads = CGOpts.SanitizeCoverageTraceLoads;
   Opts.TraceStores = CGOpts.SanitizeCoverageTraceStores;
+  Opts.TraceArgs = CGOpts.SanitizeCoverageTraceArgs;
+  Opts.TraceRet = CGOpts.SanitizeCoverageTraceRet;
   Opts.CollectControlFlow = CGOpts.SanitizeCoverageControlFlow;
   return Opts;
 }
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 778cde8285aaf..1ea3975c8db03 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -109,6 +109,8 @@ enum CoverageFeature {
   CoverageTraceStores = 1 << 17,
   CoverageControlFlow = 1 << 18,
   CoverageTracePCEntryExit = 1 << 19,
+  CoverageTraceArgs = 1 << 20,
+  CoverageTraceRet = 1 << 21,
 };
 
 enum BinaryMetadataFeature {
@@ -1100,6 +1102,7 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
   int InstrumentationTypes = CoverageTracePC | CoverageTracePCEntryExit |
                              CoverageTracePCGuard | CoverageInline8bitCounters |
                              CoverageTraceLoads | CoverageTraceStores |
+                             CoverageTraceArgs | CoverageTraceRet |
                              CoverageInlineBoolFlag | CoverageControlFlow;
   if ((CoverageFeatures & InsertionPointTypes) &&
       !(CoverageFeatures & InstrumentationTypes) && DiagnoseErrors) {
@@ -1111,9 +1114,10 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
 
   // trace-pc w/o func/bb/edge implies edge.
   if (!(CoverageFeatures & InsertionPointTypes)) {
-    if (CoverageFeatures & (CoverageTracePC | CoverageTracePCEntryExit |
-                            CoverageTracePCGuard | CoverageInline8bitCounters |
-                            CoverageInlineBoolFlag | CoverageControlFlow))
+    if (CoverageFeatures &
+        (CoverageTracePC | CoverageTracePCEntryExit | CoverageTracePCGuard |
+         CoverageInline8bitCounters | CoverageInlineBoolFlag |
+         CoverageControlFlow | CoverageTraceArgs | CoverageTraceRet))
       CoverageFeatures |= CoverageEdge;
 
     if (CoverageFeatures & CoverageStackDepth)
@@ -1474,6 +1478,8 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
       std::make_pair(CoverageStackDepth, "-fsanitize-coverage-stack-depth"),
       std::make_pair(CoverageTraceLoads, "-fsanitize-coverage-trace-loads"),
       std::make_pair(CoverageTraceStores, "-fsanitize-coverage-trace-stores"),
+      std::make_pair(CoverageTraceArgs, "-fsanitize-coverage-trace-args"),
+      std::make_pair(CoverageTraceRet, "-fsanitize-coverage-trace-ret"),
       std::make_pair(CoverageControlFlow, "-fsanitize-coverage-control-flow")};
   for (auto F : CoverageFlags) {
     if (CoverageFeatures & F.first)
@@ -1864,6 +1870,8 @@ int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A,
                 .Case("stack-depth", CoverageStackDepth)
                 .Case("trace-loads", CoverageTraceLoads)
                 .Case("trace-stores", CoverageTraceStores)
+                .Case("trace-args", CoverageTraceArgs)
+                .Case("trace-ret", CoverageTraceRet)
                 .Case("control-flow", CoverageControlFlow)
                 .Default(0);
     if (F == 0 && DiagnoseErrors)
diff --git a/clang/test/CodeGen/sanitizer-coverage-trace-args-ret.c b/clang/test/CodeGen/sanitizer-coverage-trace-args-ret.c
new file mode 100644
index 0000000000000..58482a40035d5
--- /dev/null
+++ b/clang/test/CodeGen/sanitizer-coverage-trace-args-ret.c
@@ -0,0 +1,38 @@
+// Check that -fsanitize-coverage-trace-args and -fsanitize-coverage-trace-ret
+// reach the SanitizerCoverage pass and emit their callbacks.
+//
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -O1 -debug-info-kind=limited \
+// RUN:     -fsanitize-coverage-type=3 -fsanitize-coverage-trace-args %s -o - \
+// RUN:   | FileCheck %s --check-prefix=ARGS
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -O1 -debug-info-kind=limited \
+// RUN:     -fsanitize-coverage-type=3 -fsanitize-coverage-trace-ret %s -o - \
+// RUN:   | FileCheck %s --check-prefix=RET
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -O1 -debug-info-kind=limited \
+// RUN:     -fsanitize-coverage-type=3 %s -o - | FileCheck %s --check-prefix=NONE
+
+struct Foo {
+  int a;
+  long b;
+};
+
+void takes_struct_ptr(struct Foo *f) {}
+
+int returns_scalar(int x) { return x + 1; }
+
+// The struct pointer parameter is reported as the address of the 16-byte struct
+// it points at, with the field layout of that struct. Nothing is spilled.
+// ARGS: @__sancov_offsets_ = private unnamed_addr constant [4 x i64] [i64 0, i64 4, i64 8, i64 8]
+// ARGS-LABEL: define {{.*}} @takes_struct_ptr(
+// ARGS-NOT: alloca
+// ARGS: %[[ADDR:.*]] = ptrtoint ptr %f to i64
+// ARGS: call void @__sanitizer_cov_trace_args({{.*}}, i32 0, i32 16, i64 %[[ADDR]], ptr @__sancov_offsets_, i32 2)
+// ARGS-LABEL: define {{.*}} @returns_scalar(
+// ARGS: call void @__sanitizer_cov_trace_args(
+// ARGS-NOT: call void @__sanitizer_cov_trace_ret(
+
+// RET-LABEL: define {{.*}} @returns_scalar(
+// RET: call void @__sanitizer_cov_trace_ret(
+// RET-NOT: call void @__sanitizer_cov_trace_args(
+
+// NONE-NOT: call void @__sanitizer_cov_trace_args(
+// NONE-NOT: call void @__sanitizer_cov_trace_ret(
diff --git a/clang/test/Driver/fsanitize-coverage.c b/clang/test/Driver/fsanitize-coverage.c
index 21e2c16bfb1b7..9a0c74bc3dda9 100644
--- a/clang/test/Driver/fsanitize-coverage.c
+++ b/clang/test/Driver/fsanitize-coverage.c
@@ -170,3 +170,16 @@
 // CHECK-NO-SHADOWCALLSTACK-NOT: unknown argument
 // CHECK-NO-SHADOWCALLSTACK-NOT: -fsanitize=shadow-call-stack
 // CHECK-NO-SHADOWCALLSTACK: -fsanitize-coverage-trace-pc-guard
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize-coverage=trace-args %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-TRACE-ARGS
+// CHECK-TRACE-ARGS: -fsanitize-coverage-type=3
+// CHECK-TRACE-ARGS: -fsanitize-coverage-trace-args
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize-coverage=trace-ret %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-TRACE-RET
+// CHECK-TRACE-RET: -fsanitize-coverage-type=3
+// CHECK-TRACE-RET: -fsanitize-coverage-trace-ret
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize-coverage=edge,trace-args,trace-ret %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-TRACE-ARGS-RET
+// CHECK-TRACE-ARGS-RET: -fsanitize-coverage-type=3
+// CHECK-TRACE-ARGS-RET: -fsanitize-coverage-trace-args
+// CHECK-TRACE-ARGS-RET: -fsanitize-coverage-trace-ret
diff --git a/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp b/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
index d439ec2f0982d..3bd7e6b76040e 100644
--- a/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
@@ -384,6 +384,48 @@ void TracePC::HandleCmp(uintptr_t PC, T Arg1, T Arg2) {
   ValueProfileMap.AddValue(PC * 128 + 64 + AbsoluteDistance);
 }
 
+// A struct with more fields than this contributes only its first ones, to bound
+// the work done on every call.
+static const uint32_t kMaxDataflowFields = 32;
+
+ATTRIBUTE_NO_SANITIZE_ALL
+void TracePC::HandleDataflow(uintptr_t PC, uint32_t Loc, uint32_t Size,
+                             uint64_t Val, const uint64_t *Offsets,
+                             uint32_t NumFields) {
+  // A parameter the optimizer removed, or one the instrumentation could not
+  // report, carries no value to observe.
+  if (!Size)
+    return;
+  // Bind the value to the site it was observed at - the function, the argument
+  // or return position, and the field - so that the same value seen elsewhere
+  // stays a distinct observation, then fold it into the value profile: a value
+  // never seen at that site before becomes a new feature. Only
+  // -use_value_profile consumes this.
+  auto Fold = [&](uint32_t Field, uint64_t V) {
+    ValueProfileMap.AddValueModPrime((PC * 3 + Loc) ^ (Field * 0x9E3779B1u) ^
+                                     V);
+  };
+
+  if (!NumFields || !Offsets) {
+    Fold(0, Val);
+    return;
+  }
+  // Val is the address of an object and Offsets holds {byte offset, byte size}
+  // pairs, one per field of it. Field numbers start at one to stay distinct
+  // from a whole value.
+  const uint8_t *Object =
+      reinterpret_cast<const uint8_t *>(static_cast<uintptr_t>(Val));
+  for (uint32_t I = 0; I < NumFields && I < kMaxDataflowFields; I++) {
+    uint64_t Bytes = Offsets[I * 2 + 1];
+    if (Bytes > sizeof(uint64_t))
+      Bytes = sizeof(uint64_t);
+    uint64_t Field = 0;
+    __builtin_memcpy(&Field, Object + Offsets[I * 2],
+                     static_cast<size_t>(Bytes));
+    Fold(I + 1, Field);
+  }
+}
+
 ATTRIBUTE_NO_SANITIZE_MEMORY
 static size_t InternalStrnlen(const char *S, size_t MaxLen) {
   size_t Len = 0;
@@ -478,6 +520,30 @@ void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) {
   fuzzer::TPC.HandleCmp(PC, Arg1, Arg2);
 }
 
+// Argument and return value tracing (-fsanitize-coverage=trace-args,trace-ret).
+// Unlike the cmp callbacks these receive the PC from the instrumentation - the
+// address of the instrumented function, not of the call site - and fold the
+// observed value into the value profile.
+ATTRIBUTE_INTERFACE
+ATTRIBUTE_NO_SANITIZE_ALL
+void __sanitizer_cov_trace_args(uint64_t PC, uint32_t ArgIdx, uint32_t Size,
+                                uint64_t Val, const uint64_t *Offsets,
+                                uint32_t NumFields) {
+  fuzzer::TPC.HandleDataflow(static_cast<uintptr_t>(PC), ArgIdx, Size, Val,
+                             Offsets, NumFields);
+}
+
+ATTRIBUTE_INTERFACE
+ATTRIBUTE_NO_SANITIZE_ALL
+void __sanitizer_cov_trace_ret(uint64_t PC, uint32_t Size, uint64_t Val,
+                               const uint64_t *Offsets, uint32_t NumFields) {
+  // Returns share the PC with the arguments of the same function, so they need
+  // a location of their own; no function has this many parameters.
+  const uint32_t kReturnLoc = 0xFFFF;
+  fuzzer::TPC.HandleDataflow(static_cast<uintptr_t>(PC), kReturnLoc, Size, Val,
+                             Offsets, NumFields);
+}
+
 ATTRIBUTE_INTERFACE
 ATTRIBUTE_NO_SANITIZE_ALL
 ATTRIBUTE_TARGET_POPCNT
diff --git a/compiler-rt/lib/fuzzer/FuzzerTracePC.h b/compiler-rt/lib/fuzzer/FuzzerTracePC.h
index af1f9d81e9509..e09b07992c164 100644
--- a/compiler-rt/lib/fuzzer/FuzzerTracePC.h
+++ b/compiler-rt/lib/fuzzer/FuzzerTracePC.h
@@ -73,6 +73,13 @@ class TracePC {
   void HandlePCsInit(const uintptr_t *Start, const uintptr_t *Stop);
   void HandleCallerCallee(uintptr_t Caller, uintptr_t Callee);
   template <class T> void HandleCmp(uintptr_t PC, T Arg1, T Arg2);
+  // Fold an argument or return value observed at \p PC into the value profile
+  // (trace-args / trace-ret). Loc tells apart the arguments and the return of
+  // the same function. Val is the value itself, or, when NumFields is non-zero,
+  // the address of an object whose fields the NumFields {byte offset, byte
+  // size} pairs in Offsets describe.
+  void HandleDataflow(uintptr_t PC, uint32_t Loc, uint32_t Size, uint64_t Val,
+                      const uint64_t *Offsets, uint32_t NumFields);
   size_t GetTotalPCCoverage();
   void SetUseCounters(bool UC) { UseCounters = UC; }
   void SetUseValueProfileMask(uint32_t VPMask) { UseValueProfileMask = VPMask; }
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_interface.inc b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_interface.inc
index 9d36a40270d5a..ff7d971bfe51e 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_interface.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_interface.inc
@@ -38,6 +38,8 @@ INTERFACE_WEAK_FUNCTION(__sanitizer_cov_store4)
 INTERFACE_WEAK_FUNCTION(__sanitizer_cov_store8)
 INTERFACE_WEAK_FUNCTION(__sanitizer_cov_store16)
 INTERFACE_WEAK_FUNCTION(__sanitizer_cov_trace_switch)
+INTERFACE_WEAK_FUNCTION(__sanitizer_cov_trace_args)
+INTERFACE_WEAK_FUNCTION(__sanitizer_cov_trace_ret)
 INTERFACE_WEAK_FUNCTION(__sanitizer_cov_8bit_counters_init)
 INTERFACE_WEAK_FUNCTION(__sanitizer_cov_bool_flag_init)
 INTERFACE_WEAK_FUNCTION(__sanitizer_cov_pcs_init)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep_new.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep_new.cpp
index 506659a58c45e..cdb1f389ea79f 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep_new.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep_new.cpp
@@ -256,6 +256,13 @@ SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_const_cmp2, void) {}
 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_const_cmp4, void) {}
 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_const_cmp8, void) {}
 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_switch, void) {}
+// Argument and return value tracing (trace-args / trace-ret). Weak, empty
+// defaults so that a program built with -fsanitize-coverage=trace-args and
+// trace-ret links without a runtime consuming the values; libFuzzer, or the
+// user, provides a strong definition. Such a definition must tolerate a size of
+// zero, which the instrumentation passes for a value it cannot report.
+SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_args, void) {}
+SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_ret, void) {}
 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_div4, void) {}
 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_div8, void) {}
 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_cov_trace_gep, void) {}
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h b/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
index c424ab1cecf94..45e37bfe88473 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_interface_internal.h
@@ -115,6 +115,21 @@ SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
 __sanitizer_cov_trace_const_cmp8();
 SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
 __sanitizer_cov_trace_switch();
+// Argument and return value tracing: observe a function's parameters on entry
+// and its return values.
+//   __sanitizer_cov_trace_args(u64 pc, u32 arg_idx, u32 size, u64 val,
+//                              u64 *offsets, u32 num_fields)
+//   __sanitizer_cov_trace_ret (u64 pc, u32 size, u64 val,
+//                              u64 *offsets, u32 num_fields)
+// `val` is the reported value itself, or, when num_fields is non-zero, the
+// address of an object whose fields the num_fields {byte offset, byte size}
+// pairs in `offsets` describe. A size of zero means nothing was reported: a
+// parameter the optimizer removed, a void return, a value the instrumentation
+// could not report.
+SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
+__sanitizer_cov_trace_args();
+SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
+__sanitizer_cov_trace_ret();
 SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
 __sanitizer_cov_trace_div4();
 SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
diff --git a/compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_trace_args.cpp b/compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_trace_args.cpp
new file mode 100644
index 0000000000000..d909e1d245d97
--- /dev/null
+++ b/compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_trace_args.cpp
@@ -0,0 +1,50 @@
+// Check that -fsanitize-coverage=trace-args,trace-ret calls the two callbacks
+// with the observed argument and return values, and that a user definition
+// overrides compiler-rt's weak defaults.
+//
+// Arguments are reported in the entry block, before the -O0 prologue stores
+// them to their stack slots, so the values are only meaningful from -O1 on.
+//
+// REQUIRES: has_sancovcc
+// UNSUPPORTED: i386-darwin
+// RUN: %clangxx -O2 -g -fsanitize-coverage=trace-pc-guard,trace-args,trace-ret %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+#include <cstdint>
+#include <cstdio>
+
+extern "C" {
+// Stubs so the program links without a sanitizer runtime; these are weak in
+// compiler-rt, so these definitions win.
+void __sanitizer_cov_trace_pc_guard(uint32_t *) {}
+void __sanitizer_cov_trace_pc_guard_init(uint32_t *, uint32_t *) {}
+
+// The consumers under test. A size of zero means the instrumentation had
+// nothing to report, and a non-zero num_fields would mean `val` is an address
+// rather than a value.
+void __sanitizer_cov_trace_args(uint64_t pc, uint32_t arg_idx, uint32_t size,
+                                uint64_t val, uint64_t *offsets,
+                                uint32_t num_fields) {
+  if (size == sizeof(int) && !num_fields)
+    fprintf(stderr, "ARG idx=%u val=%d\n", arg_idx, (int)val);
+}
+
+void __sanitizer_cov_trace_ret(uint64_t pc, uint32_t size, uint64_t val,
+                               uint64_t *offsets, uint32_t num_fields) {
+  if (size == sizeof(int) && !num_fields)
+    fprintf(stderr, "RET val=%d\n", (int)val);
+}
+}
+
+__attribute__((noinline)) int add(int a, int b) { return a + b; }
+
+int main() {
+  volatile int r =...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/227612


More information about the llvm-commits mailing list