diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst --- a/clang/docs/SanitizerCoverage.rst +++ b/clang/docs/SanitizerCoverage.rst @@ -331,6 +331,43 @@ void __sanitizer_cov_store8(uint64_t *addr); void __sanitizer_cov_store16(__int128 *addr); + +Tracing control flow +==================== + +With ``-fsanitize-coverage=control-flow`` the compiler will create a table to collect +control flow for each function. More specifically, for each basic block in the function, +two lists are populated. one list for successors of the basic block and another list for +non-intrinsic called functions. Each table row consists of the basic block address +followed by ``null``-separated lists of successors and callees. + +Example: + +.. code-block:: c++ + int foo (int x) { + if (x > 0) + bar(x); + else + x = 0; + return x; + } + +The code above contains 4 basic blocks, let's name them A, B, C, D: + +.. code-block:: none + + A + |\ + | \ + B C + | / + |/ + D + +The collected control flow table is as follows: +``A, B, C, null, null, B, D, null, @bar, null, C, D, null, null, D, null, null.`` + + Disabling instrumentation with ``__attribute__((no_sanitize("coverage")))`` =========================================================================== diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -483,7 +483,7 @@ bool hasSanitizeCoverage() const { return SanitizeCoverageType || SanitizeCoverageIndirectCalls || SanitizeCoverageTraceCmp || SanitizeCoverageTraceLoads || - SanitizeCoverageTraceStores; + SanitizeCoverageTraceStores || SanitizeCoverageControlFlow; } }; diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -281,6 +281,7 @@ CODEGENOPT(SanitizeCoverageInline8bitCounters, 1, 0) ///< Use inline 8bit counters. CODEGENOPT(SanitizeCoverageInlineBoolFlag, 1, 0) ///< Use inline bool flag. 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 CODEGENOPT(SanitizeCoverageTraceLoads, 1, 0) ///< Enable tracing of loads. diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -5472,6 +5472,10 @@ : Flag<["-"], "fsanitize-coverage-pc-table">, HelpText<"Create a table of coverage-instrumented PCs">, MarshallingInfoFlag>; +def fsanitize_coverage_control_flow + : Flag<["-"], "fsanitize-coverage-control-flow">, + HelpText<"Collect control flow of function">, + MarshallingInfoFlag>; def fsanitize_coverage_trace_pc : Flag<["-"], "fsanitize-coverage-trace-pc">, HelpText<"Enable PC tracing in sanitizer coverage">, diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -215,6 +215,7 @@ Opts.StackDepth = CGOpts.SanitizeCoverageStackDepth; Opts.TraceLoads = CGOpts.SanitizeCoverageTraceLoads; Opts.TraceStores = CGOpts.SanitizeCoverageTraceStores; + Opts.CollectControlFlow = CGOpts.SanitizeCoverageControlFlow; return Opts; } diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -98,6 +98,7 @@ CoverageInlineBoolFlag = 1 << 15, CoverageTraceLoads = 1 << 16, CoverageTraceStores = 1 << 17, + CoverageControlFlow = 1 << 18, }; /// Parse a -fsanitize= or -fno-sanitize= argument's values, diagnosing any @@ -798,19 +799,19 @@ int InsertionPointTypes = CoverageFunc | CoverageBB | CoverageEdge; int InstrumentationTypes = CoverageTracePC | CoverageTracePCGuard | CoverageInline8bitCounters | CoverageTraceLoads | - CoverageTraceStores | CoverageInlineBoolFlag; + CoverageTraceStores | CoverageInlineBoolFlag | CoverageControlFlow; if ((CoverageFeatures & InsertionPointTypes) && !(CoverageFeatures & InstrumentationTypes) && DiagnoseErrors) { D.Diag(clang::diag::warn_drv_deprecated_arg) << "-fsanitize-coverage=[func|bb|edge]" - << "-fsanitize-coverage=[func|bb|edge],[trace-pc-guard|trace-pc]"; + << "-fsanitize-coverage=[func|bb|edge],[trace-pc-guard|trace-pc|control-flow]"; } // trace-pc w/o func/bb/edge implies edge. if (!(CoverageFeatures & InsertionPointTypes)) { if (CoverageFeatures & (CoverageTracePC | CoverageTracePCGuard | CoverageInline8bitCounters | - CoverageInlineBoolFlag)) + CoverageInlineBoolFlag | CoverageControlFlow)) CoverageFeatures |= CoverageEdge; if (CoverageFeatures & CoverageStackDepth) @@ -1085,7 +1086,8 @@ std::make_pair(CoverageNoPrune, "-fsanitize-coverage-no-prune"), 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(CoverageTraceStores, "-fsanitize-coverage-trace-stores"), + std::make_pair(CoverageControlFlow, "-fsanitize-coverage-control-flow")}; for (auto F : CoverageFlags) { if (CoverageFeatures & F.first) CmdArgs.push_back(F.second); @@ -1332,6 +1334,7 @@ .Case("stack-depth", CoverageStackDepth) .Case("trace-loads", CoverageTraceLoads) .Case("trace-stores", CoverageTraceStores) + .Case("control-flow", CoverageControlFlow) .Default(0); if (F == 0 && DiagnoseErrors) D.Diag(clang::diag::err_drv_unsupported_option_argument) diff --git a/compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_control_flow.cpp b/compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_control_flow.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/sanitizer_common/TestCases/sanitizer_coverage_control_flow.cpp @@ -0,0 +1,37 @@ +// Tests -fsanitize-coverage=control-flow +// +// RUN: %clangxx -O0 -std=c++11 -fsanitize-coverage=control-flow %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not Assertion{{.*}}failed + +#include + +void foo(int recurse) { + if (recurse > 0) + foo(recurse - 1); +} + +int main() { + int x = 10; + for (int i = 0; i < 10; i++) { + if (x > 5) + foo(x); + } + + switch (x) { + case 5: + foo(x); + break; + case -5: + foo(-x); + break; + + default: + break; + } + printf("Success!\n"); + return 0; +} + +// CHECK: Success! +// +// TODO(navidem): make sure this is all to be checked for the collected control flow \ No newline at end of file diff --git a/llvm/include/llvm/Transforms/Instrumentation.h b/llvm/include/llvm/Transforms/Instrumentation.h --- a/llvm/include/llvm/Transforms/Instrumentation.h +++ b/llvm/include/llvm/Transforms/Instrumentation.h @@ -150,6 +150,7 @@ bool StackDepth = false; bool TraceLoads = false; bool TraceStores = false; + bool CollectControlFlow = false; SanitizerCoverageOptions() = default; }; diff --git a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp --- a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp +++ b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp @@ -80,6 +80,7 @@ const char SanCovCountersSectionName[] = "sancov_cntrs"; const char SanCovBoolFlagSectionName[] = "sancov_bools"; const char SanCovPCsSectionName[] = "sancov_pcs"; +const char SanCovCFsSectionName[] = "sancov_cfs"; const char SanCovLowestStackName[] = "__sancov_lowest_stack"; @@ -147,6 +148,10 @@ cl::desc("max stack depth tracing"), cl::Hidden, cl::init(false)); +static cl::opt ClCollectCF("sanitizer-coverage-control-flow", + cl::desc("collect control flow for each function"), + cl::Hidden, cl::init(false)); + namespace { SanitizerCoverageOptions getOptions(int LegacyCoverageLevel) { @@ -193,6 +198,7 @@ !Options.Inline8bitCounters && !Options.StackDepth && !Options.InlineBoolFlag && !Options.TraceLoads && !Options.TraceStores) Options.TracePCGuard = true; // TracePCGuard is default. + Options.CollectControlFlow |= ClCollectCF; return Options; } @@ -212,6 +218,7 @@ PostDomTreeCallback PDTCallback); private: + void CollectFunctionControlFlow(Function &F); void instrumentFunction(Function &F, DomTreeCallback DTCallback, PostDomTreeCallback PDTCallback); void InjectCoverageForIndirectCalls(Function &F, @@ -270,6 +277,7 @@ GlobalVariable *Function8bitCounterArray; // for inline-8bit-counters. GlobalVariable *FunctionBoolArray; // for inline-bool-flag. GlobalVariable *FunctionPCsArray; // for pc-table. + GlobalVariable *FunctionCFsArray; // for control flow table SmallVector GlobalsToAppendToUsed; SmallVector GlobalsToAppendToCompilerUsed; @@ -378,6 +386,7 @@ Function8bitCounterArray = nullptr; FunctionBoolArray = nullptr; FunctionPCsArray = nullptr; + FunctionCFsArray = nullptr; IntptrTy = Type::getIntNTy(*C, DL->getPointerSizeInBits()); IntptrPtrTy = PointerType::getUnqual(IntptrTy); Type *VoidTy = Type::getVoidTy(*C); @@ -502,6 +511,10 @@ IRBuilder<> IRBCtor(Ctor->getEntryBlock().getTerminator()); IRBCtor.CreateCall(InitFunction, {SecStartEnd.first, SecStartEnd.second}); } + + if (Options.CollectControlFlow) + CreateSecStartEnd(M, SanCovCFsSectionName, IntptrPtrTy); + appendToUsed(M, GlobalsToAppendToUsed); appendToCompilerUsed(M, GlobalsToAppendToCompilerUsed); return true; @@ -671,6 +684,9 @@ } } + if (Options.CollectControlFlow) + CollectFunctionControlFlow(F); + InjectCoverage(F, BlocksToInstrument, IsLeafFunc); InjectCoverageForIndirectCalls(F, IndirCalls); InjectTraceForCmp(F, CmpTraceTargets); @@ -1028,3 +1044,42 @@ return "\1section$end$__DATA$__" + Section; return "__stop___" + Section; } + +void ModuleSanitizerCoverage::CollectFunctionControlFlow(Function &F) { + SmallVector CFs; + IRBuilder<> IRB(&*F.getEntryBlock().getFirstInsertionPt()); + + for (auto &BB: F) { + // blockaddress can not be used on function's entry block. + if (&BB == &F.getEntryBlock()) + CFs.push_back((Constant *)IRB.CreatePointerCast(&F, IntptrPtrTy)); + else + CFs.push_back((Constant *)IRB.CreatePointerCast(BlockAddress::get(&BB), IntptrPtrTy)); + + for (auto SuccBB : successors(&BB)) { + assert(SuccBB != &F.getEntryBlock()); + CFs.push_back((Constant *)IRB.CreatePointerCast(BlockAddress::get(SuccBB), IntptrPtrTy)); + } + + CFs.push_back((Constant *)IRB.CreateIntToPtr(ConstantInt::get(IntptrTy, 0), IntptrPtrTy)); + + for (auto &Inst: BB) { + if (CallBase *CB = dyn_cast(&Inst)) { + if (!CB->isIndirectCall()) { + auto CalledF = CB->getCalledFunction(); + if (CalledF && !CalledF->isIntrinsic()) + CFs.push_back((Constant *)IRB.CreatePointerCast(CalledF, IntptrPtrTy)); + } + // TODO(navidem): handle indirect call. + } + } + + CFs.push_back((Constant *)IRB.CreateIntToPtr(ConstantInt::get(IntptrTy, 0), IntptrPtrTy)); + } + + size_t N = CFs.size(); + auto CFArray = CreateFunctionLocalArrayInSection(CFs.size(), F, IntptrPtrTy, SanCovCFsSectionName); + CFArray->setInitializer(ConstantArray::get(ArrayType::get(IntptrPtrTy, CFs.size()), CFs)); + CFArray->setConstant(true); + FunctionCFsArray = CFArray; +} diff --git a/llvm/test/Instrumentation/SanitizerCoverage/control-flow.ll b/llvm/test/Instrumentation/SanitizerCoverage/control-flow.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Instrumentation/SanitizerCoverage/control-flow.ll @@ -0,0 +1,22 @@ +; Test -sanitizer-coverage-control-flow +; RUN: opt < %s -passes='module(sancov-module)' -sanitizer-coverage-level=3 -sanitizer-coverage-control-flow -S | FileCheck %s + +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" +define void @foo(i32* %a) sanitize_address { +entry: + %tobool = icmp eq i32* %a, null + br i1 %tobool, label %if.end, label %if.then + + if.then: ; preds = %entry + store i32 0, i32* %a, align 4 + call void @foo(i32* %a) + br label %if.end + + if.end: ; preds = %entry, %if.then + ret void +} + +; CHECK: private constant [17 x i64*] [{{.*}}@foo{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}@foo{{.*}}null{{.*}}null], section "__sancov_cfs", comdat($foo), align 8 +; CHECK: @__start___sancov_cfs = extern_weak hidden global i64* +; CHECK-NEXT: @__stop___sancov_cfs = extern_weak hidden global i64*