Author: Jann Horn Date: 2026-03-17T14:08:53+01:00 New Revision: dc5c6d008f487eea8f5d646011f9b3dca6caebd7
URL: https://github.com/llvm/llvm-project/commit/dc5c6d008f487eea8f5d646011f9b3dca6caebd7 DIFF: https://github.com/llvm/llvm-project/commit/dc5c6d008f487eea8f5d646011f9b3dca6caebd7.diff LOG: [sancov] add -fsanitize-coverage=trace-pc-entry-exit (#185972) Add a SanCov flag for calling dedicated hook functions on function entry and exit. This flag can be used either in combination with -fsanitize-coverage=trace-pc (in which case this patch changes which hook is called for the entry BB, and generates an additional hook call before return), or it can be used by itself (in which case only the dedicated entry/exit callbacks are invoked). This can be used to track the call stack throughout a sancov trace. cc @vitalybuka @dvyukov Added: llvm/test/Instrumentation/SanitizerCoverage/trace-pc-entry-exit.ll Modified: clang/docs/SanitizerCoverage.rst clang/include/clang/Basic/CodeGenOptions.def clang/include/clang/Options/Options.td clang/lib/CodeGen/BackendUtil.cpp clang/lib/Driver/SanitizerArgs.cpp clang/test/Driver/autocomplete.c llvm/include/llvm/Transforms/Utils/Instrumentation.h llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp Removed: ################################################################################ diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst index 4ab2d09366f4f..c01863adebb2d 100644 --- a/clang/docs/SanitizerCoverage.rst +++ b/clang/docs/SanitizerCoverage.rst @@ -201,6 +201,22 @@ With ``-fsanitize-coverage=trace-pc`` the compiler will insert ``__sanitizer_cov_trace_pc()`` on every edge. With an additional ``...=trace-pc,indirect-calls`` flag ``__sanitizer_cov_trace_pc_indirect(void *callee)`` will be inserted on every indirect call. + +With ``-fsanitize-coverage=trace-pc-entry-exit`` the compiler will insert +``__sanitizer_cov_trace_pc_entry()`` on function entry, and insert +``__sanitizer_cov_trace_pc_exit()`` on function return; +``-fsanitize-coverage=trace-pc`` or ``-fsanitize-coverage=trace-pc-guard`` must +still be passed to instrument all basic blocks. + +With the combination ``-fsanitize-coverage=trace-pc-entry-exit,trace-pc``, +``__sanitizer_cov_trace_pc()`` will be omitted in the entry basic block because +the block is already covered by ``__sanitizer_cov_trace_pc_entry()``, which can +be used to both record that the function has been entered and record coverage of +the entry basic block. +However, with ``-fsanitize-coverage=trace-pc-entry-exit,trace-pc-guard``, both +callbacks are called for the entry block because +``__sanitizer_cov_trace_pc_entry()`` does not provide a `guard_variable`. + These callbacks are not implemented in the Sanitizer run-time and should be defined by the user. This mechanism is used for fuzzing the Linux kernel diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 857fe35e12b2b..d883552ea2198 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -308,6 +308,8 @@ CODEGENOPT(SanitizeCoverage8bitCounters, 1, 0, Benign) ///< Use 8-bit frequency ///< in sanitizer coverage. CODEGENOPT(SanitizeCoverageTracePC, 1, 0, Benign) ///< Enable PC tracing ///< in sanitizer coverage. +CODEGENOPT(SanitizeCoverageTracePCEntryExit, 1, 0, Benign) ///< Trace function entry/exit + ///< in sanitizer coverage. CODEGENOPT(SanitizeCoverageTracePCGuard, 1, 0, Benign) ///< Enable PC tracing with guard ///< in sanitizer coverage. CODEGENOPT(SanitizeCoverageInline8bitCounters, 1, 0, Benign) ///< Use inline 8bit counters. diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 03353a31221f3..6fc52384a6d1d 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -2517,7 +2517,7 @@ def fno_sanitize_coverage : CommaJoined<["-"], "fno-sanitize-coverage=">, Group<fsan_cov_Group>, Visibility<[ClangOption, CLOption]>, HelpText<"Disable features of coverage instrumentation for Sanitizers">, Values<"func,bb,edge,indirect-calls,trace-bb,trace-cmp,trace-div,trace-gep," - "8bit-counters,trace-pc,trace-pc-guard,no-prune,inline-8bit-counters," + "8bit-counters,trace-pc,trace-pc-entry-exit,trace-pc-guard,no-prune,inline-8bit-counters," "inline-bool-flag">; def fsanitize_coverage_allowlist : Joined<["-"], "fsanitize-coverage-allowlist=">, Group<fsan_cov_Group>, Visibility<[ClangOption, CLOption]>, @@ -8299,6 +8299,11 @@ def fsanitize_coverage_trace_pc Group<fsan_cov_Group>, HelpText<"Enable PC tracing in sanitizer coverage">, MarshallingInfoFlag<CodeGenOpts<"SanitizeCoverageTracePC">>; +def fsanitize_coverage_trace_pc_entry_exit + : Flag<["-"], "fsanitize-coverage-trace-pc-entry-exit">, + Group<fsan_cov_Group>, + HelpText<"Enable function entry/exit tracing in sanitizer coverage">, + MarshallingInfoFlag<CodeGenOpts<"SanitizeCoverageTracePCEntryExit">>; def fsanitize_coverage_trace_pc_guard : Flag<["-"], "fsanitize-coverage-trace-pc-guard">, Group<fsan_cov_Group>, diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 4b4eaadcf1527..5b8b4083c2ac0 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -241,6 +241,7 @@ getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) { Opts.TraceGep = CGOpts.SanitizeCoverageTraceGep; Opts.Use8bitCounters = CGOpts.SanitizeCoverage8bitCounters; Opts.TracePC = CGOpts.SanitizeCoverageTracePC; + Opts.TracePCEntryExit = CGOpts.SanitizeCoverageTracePCEntryExit; Opts.TracePCGuard = CGOpts.SanitizeCoverageTracePCGuard; Opts.NoPrune = CGOpts.SanitizeCoverageNoPrune; Opts.Inline8bitCounters = CGOpts.SanitizeCoverageInline8bitCounters; diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index ce1e971110caf..78010b9e659d8 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -108,6 +108,7 @@ enum CoverageFeature { CoverageTraceLoads = 1 << 16, CoverageTraceStores = 1 << 17, CoverageControlFlow = 1 << 18, + CoverageTracePCEntryExit = 1 << 19, }; enum BinaryMetadataFeature { @@ -963,10 +964,10 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, } int InsertionPointTypes = CoverageFunc | CoverageBB | CoverageEdge; - int InstrumentationTypes = CoverageTracePC | CoverageTracePCGuard | - CoverageInline8bitCounters | CoverageTraceLoads | - CoverageTraceStores | CoverageInlineBoolFlag | - CoverageControlFlow; + int InstrumentationTypes = CoverageTracePC | CoverageTracePCEntryExit | + CoverageTracePCGuard | CoverageInline8bitCounters | + CoverageTraceLoads | CoverageTraceStores | + CoverageInlineBoolFlag | CoverageControlFlow; if ((CoverageFeatures & InsertionPointTypes) && !(CoverageFeatures & InstrumentationTypes) && DiagnoseErrors) { D.Diag(clang::diag::warn_drv_deprecated_arg) @@ -977,9 +978,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, // trace-pc w/o func/bb/edge implies edge. if (!(CoverageFeatures & InsertionPointTypes)) { - if (CoverageFeatures & - (CoverageTracePC | CoverageTracePCGuard | CoverageInline8bitCounters | - CoverageInlineBoolFlag | CoverageControlFlow)) + if (CoverageFeatures & (CoverageTracePC | CoverageTracePCEntryExit | + CoverageTracePCGuard | CoverageInline8bitCounters | + CoverageInlineBoolFlag | CoverageControlFlow)) CoverageFeatures |= CoverageEdge; if (CoverageFeatures & CoverageStackDepth) @@ -1327,6 +1328,8 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, std::make_pair(CoverageTraceGep, "-fsanitize-coverage-trace-gep"), std::make_pair(Coverage8bitCounters, "-fsanitize-coverage-8bit-counters"), std::make_pair(CoverageTracePC, "-fsanitize-coverage-trace-pc"), + std::make_pair(CoverageTracePCEntryExit, + "-fsanitize-coverage-trace-pc-entry-exit"), std::make_pair(CoverageTracePCGuard, "-fsanitize-coverage-trace-pc-guard"), std::make_pair(CoverageInline8bitCounters, @@ -1716,6 +1719,7 @@ int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A, .Case("trace-gep", CoverageTraceGep) .Case("8bit-counters", Coverage8bitCounters) .Case("trace-pc", CoverageTracePC) + .Case("trace-pc-entry-exit", CoverageTracePCEntryExit) .Case("trace-pc-guard", CoverageTracePCGuard) .Case("no-prune", CoverageNoPrune) .Case("inline-8bit-counters", CoverageInline8bitCounters) diff --git a/clang/test/Driver/autocomplete.c b/clang/test/Driver/autocomplete.c index 1fd60929751ee..297d2e40d6d83 100644 --- a/clang/test/Driver/autocomplete.c +++ b/clang/test/Driver/autocomplete.c @@ -69,6 +69,7 @@ // FNOSANICOVERALL-NEXT: trace-div // FNOSANICOVERALL-NEXT: trace-gep // FNOSANICOVERALL-NEXT: trace-pc +// FNOSANICOVERALL-NEXT: trace-pc-entry-exit // FNOSANICOVERALL-NEXT: trace-pc-guard // RUN: %clang --autocomplete=-ffp-contract= | FileCheck %s -check-prefix=FFPALL // FFPALL: fast diff --git a/llvm/include/llvm/Transforms/Utils/Instrumentation.h b/llvm/include/llvm/Transforms/Utils/Instrumentation.h index 93ab8c693607f..95a985ba3f0c4 100644 --- a/llvm/include/llvm/Transforms/Utils/Instrumentation.h +++ b/llvm/include/llvm/Transforms/Utils/Instrumentation.h @@ -154,6 +154,7 @@ struct SanitizerCoverageOptions { bool TraceGep = false; bool Use8bitCounters = false; bool TracePC = false; + bool TracePCEntryExit = false; bool TracePCGuard = false; bool Inline8bitCounters = false; bool InlineBoolFlag = false; diff --git a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp index c156a1eae58d0..df9675a02824e 100644 --- a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp +++ b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp @@ -35,6 +35,7 @@ #include "llvm/Support/VirtualFileSystem.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/EscapeEnumerator.h" #include "llvm/Transforms/Utils/ModuleUtils.h" using namespace llvm; @@ -43,6 +44,8 @@ using namespace llvm; const char SanCovTracePCIndirName[] = "__sanitizer_cov_trace_pc_indir"; const char SanCovTracePCName[] = "__sanitizer_cov_trace_pc"; +const char SanCovTracePCEntryName[] = "__sanitizer_cov_trace_pc_entry"; +const char SanCovTracePCExitName[] = "__sanitizer_cov_trace_pc_exit"; const char SanCovTraceCmp1[] = "__sanitizer_cov_trace_cmp1"; const char SanCovTraceCmp2[] = "__sanitizer_cov_trace_cmp2"; const char SanCovTraceCmp4[] = "__sanitizer_cov_trace_cmp4"; @@ -99,6 +102,10 @@ static cl::opt<int> ClCoverageLevel( static cl::opt<bool> ClTracePC("sanitizer-coverage-trace-pc", cl::desc("Experimental pc tracing"), cl::Hidden); +static cl::opt<bool> ClTracePCEntryExit( + "sanitizer-coverage-trace-pc-entry-exit", + cl::desc("pc tracing with separate entry/exit callbacks"), cl::Hidden); + static cl::opt<bool> ClTracePCGuard("sanitizer-coverage-trace-pc-guard", cl::desc("pc tracing with a guard"), cl::Hidden); @@ -208,6 +215,7 @@ SanitizerCoverageOptions OverrideFromCL(SanitizerCoverageOptions Options) { Options.TraceDiv |= ClDIVTracing; Options.TraceGep |= ClGEPTracing; Options.TracePC |= ClTracePC; + Options.TracePCEntryExit |= ClTracePCEntryExit; Options.TracePCGuard |= ClTracePCGuard; Options.Inline8bitCounters |= ClInline8bitCounters; Options.InlineBoolFlag |= ClInlineBoolFlag; @@ -219,7 +227,7 @@ SanitizerCoverageOptions OverrideFromCL(SanitizerCoverageOptions Options) { Options.TraceLoads |= ClLoadTracing; Options.TraceStores |= ClStoreTracing; Options.GatedCallbacks |= ClGatedCallbacks; - if (!Options.TracePCGuard && !Options.TracePC && + if (!Options.TracePCGuard && !Options.TracePC && !Options.TracePCEntryExit && !Options.Inline8bitCounters && !Options.StackDepth && !Options.InlineBoolFlag && !Options.TraceLoads && !Options.TraceStores) Options.TracePCGuard = true; // TracePCGuard is default. @@ -256,6 +264,7 @@ class ModuleSanitizerCoverage { ArrayRef<GetElementPtrInst *> GepTraceTargets); void InjectTraceForLoadsAndStores(Function &F, ArrayRef<LoadInst *> Loads, ArrayRef<StoreInst *> Stores); + void InjectTraceForExits(Function &F); void InjectTraceForSwitch(Function &F, ArrayRef<Instruction *> SwitchTraceTargets, Value *&FunctionGateCmp); @@ -288,6 +297,7 @@ class ModuleSanitizerCoverage { FunctionCallee SanCovStackDepthCallback; FunctionCallee SanCovTracePCIndir; FunctionCallee SanCovTracePC, SanCovTracePCGuard; + FunctionCallee SanCovTracePCEntry, SanCovTracePCExit; std::array<FunctionCallee, 4> SanCovTraceCmpFunction; std::array<FunctionCallee, 4> SanCovTraceConstCmpFunction; std::array<FunctionCallee, 5> SanCovLoadFunction; @@ -527,6 +537,8 @@ bool ModuleSanitizerCoverage::instrumentModule() { } SanCovTracePC = M.getOrInsertFunction(SanCovTracePCName, VoidTy); + SanCovTracePCEntry = M.getOrInsertFunction(SanCovTracePCEntryName, VoidTy); + SanCovTracePCExit = M.getOrInsertFunction(SanCovTracePCExitName, VoidTy); SanCovTracePCGuard = M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, PtrTy); @@ -752,6 +764,9 @@ void ModuleSanitizerCoverage::instrumentFunction(Function &F) { InjectTraceForDiv(F, DivTraceTargets); InjectTraceForGep(F, GepTraceTargets); InjectTraceForLoadsAndStores(F, Loads, Stores); + + if (Options.TracePCEntryExit) + InjectTraceForExits(F); } GlobalVariable *ModuleSanitizerCoverage::CreateFunctionLocalArrayInSection( @@ -864,6 +879,7 @@ bool ModuleSanitizerCoverage::InjectCoverage(Function &F, CreateFunctionLocalArrays(F, AllBlocks); for (size_t i = 0, N = AllBlocks.size(); i < N; i++) InjectCoverageAtBlock(F, *AllBlocks[i], i, FunctionGateCmp, IsLeafFunc); + return true; } @@ -878,7 +894,7 @@ void ModuleSanitizerCoverage::InjectCoverageForIndirectCalls( Function &F, ArrayRef<Instruction *> IndirCalls) { if (IndirCalls.empty()) return; - assert(Options.TracePC || Options.TracePCGuard || + assert(Options.TracePC || Options.TracePCEntryExit || Options.TracePCGuard || Options.Inline8bitCounters || Options.InlineBoolFlag); for (auto *I : IndirCalls) { InstrumentationIRBuilder IRB(I); @@ -997,6 +1013,15 @@ void ModuleSanitizerCoverage::InjectTraceForLoadsAndStores( } } +void ModuleSanitizerCoverage::InjectTraceForExits(Function &F) { + EscapeEnumerator EE(F, "sancov_exit"); + while (IRBuilder<> *AtExit = EE.Next()) { + InstrumentationIRBuilder::ensureDebugInfo(*AtExit, F); + AtExit->CreateCall(SanCovTracePCExit, {}) + ->setTailCallKind(CallInst::TCK_NoTail); + } +} + void ModuleSanitizerCoverage::InjectTraceForCmp( Function &F, ArrayRef<Instruction *> CmpTraceTargets, Value *&FunctionGateCmp) { @@ -1062,8 +1087,11 @@ void ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB, InstrumentationIRBuilder IRB(&*IP); if (EntryLoc) IRB.SetCurrentDebugLocation(EntryLoc); - if (Options.TracePC) { - IRB.CreateCall(SanCovTracePC) + if (Options.TracePC || (IsEntryBB && Options.TracePCEntryExit)) { + FunctionCallee Callee = IsEntryBB && Options.TracePCEntryExit + ? SanCovTracePCEntry + : SanCovTracePC; + IRB.CreateCall(Callee) ->setCannotMerge(); // gets the PC using GET_CALLER_PC. } if (Options.TracePCGuard) { diff --git a/llvm/test/Instrumentation/SanitizerCoverage/trace-pc-entry-exit.ll b/llvm/test/Instrumentation/SanitizerCoverage/trace-pc-entry-exit.ll new file mode 100644 index 0000000000000..7766674d2e19a --- /dev/null +++ b/llvm/test/Instrumentation/SanitizerCoverage/trace-pc-entry-exit.ll @@ -0,0 +1,52 @@ +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=2 -sanitizer-coverage-trace-pc-entry-exit -S | FileCheck %s --check-prefix=CHECK +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=2 -sanitizer-coverage-trace-pc-entry-exit -sanitizer-coverage-trace-pc -S | FileCheck %s --check-prefix=CHECK,CHECK_BBCOV + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-unknown-linux-gnu" + +declare i32 @callee_a(i32) +declare i32 @callee_b() + +define i32 @func_multi_return(i32 noundef %a) #0 { +entry: + %tobool.not = icmp eq i32 %a, 0 + br i1 %tobool.not, label %if.else, label %if.then + +if.then: + %call = call i32 @callee_a(i32 noundef %a) + ret i32 %call + +if.else: + %call1 = call i32 @callee_b() + ret i32 %call1 +} + +; CHECK: define i32 @func_multi_return +; CHECK-NOT: call void @__sanitizer_cov_trace_pc() +; CHECK: call void @__sanitizer_cov_trace_pc_entry() +; CHECK-NOT: call void @__sanitizer_cov_trace_pc() +; CHECK: br +; CHECK_BBCOV: call void @__sanitizer_cov_trace_pc() +; CHECK: call i32 @callee_a +; CHECK: notail call void @__sanitizer_cov_trace_pc_exit() +; CHECK: ret +; CHECK_BBCOV: call void @__sanitizer_cov_trace_pc() +; CHECK: call i32 @callee_b +; CHECK: notail call void @__sanitizer_cov_trace_pc_exit() +; CHECK: ret +; CHECK: } + + +define i32 @func_musttail(i32 noundef %a) #0 { + %call = musttail call i32 @callee_a(i32 noundef %a) + ret i32 %call +} + +; CHECK: define i32 @func_musttail +; CHECK: call void @__sanitizer_cov_trace_pc_entry() +; CHECK: notail call void @__sanitizer_cov_trace_pc_exit() +; CHECK: musttail call i32 @callee_a +; CHECK: ret +; CHECK: } + +attributes #0 = { nounwind } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
