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,60 @@ 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. + +**TODO:** in the current implementation, indirect calls are not tracked +and are only marked with special value (-1) in the list. + +Each table row consists of the basic block address +followed by ``null``-ended lists of successors and callees. +The table is encoded in a special section named ``sancov_cfs`` + +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.`` + +Users need to implement a single function to capture the CF table at startup: + +.. code-block:: c++ + + extern "C" + void __sanitizer_cov_cfs_init(const uintptr_t *cfs_beg, + const uintptr_t *cfs_end) { + // [cfs_beg,cfs_end) is the array of ptr-sized integers representing + // the collected control flow. + } + + 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,63 @@ +// Tests -fsanitize-coverage=control-flow. + +// REQUIRES: has_sancovcc,stable-runtime +// UNSUPPORTED: i386-darwin, x86_64-darwin + +// RUN: %clangxx -O0 -std=c++11 -fsanitize-coverage=control-flow %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include +#include + +uintptr_t *CFS_BEG, *CFS_END; + +extern "C" void __sanitizer_cov_cfs_init(const uintptr_t *cfs_beg, + const uintptr_t *cfs_end) { + CFS_BEG = (uintptr_t *)cfs_beg; + CFS_END = (uintptr_t *)cfs_end; +} + +__attribute__((noinline)) void foo(int x) { /* empty body */ +} + +int main() { + int (*main_ptr)() = &main; + void (*foo_ptr)(int) = &foo; + int x = 10; + + if (x > 0) + foo(x); + else + (*foo_ptr)(x); + + printf("Control Flow section boundaries: [%p %p)\n", CFS_BEG, CFS_END); + uintptr_t *pt = CFS_BEG; + uintptr_t currBB; + + while (pt < CFS_END) { + currBB = *pt; + pt++; + // Iterate over successors. + while (*pt) { + pt++; + } + pt++; + // Iterate over callees. + while (*pt) { + if (*pt == (uintptr_t)(*foo_ptr) && currBB != (uintptr_t)(*main_ptr)) + printf("Direct call matched.\n"); + if (*pt == -1 && currBB != (uintptr_t)(*main_ptr)) + printf("Indirect call matched.\n"); + pt++; + } + pt++; + } + + printf("Finished!\n"); + return 0; +} + +// CHECK: Control Flow section boundaries +// CHECK: Direct call matched. +// CHECK: Indirect call matched. +// CHECK: Finished! \ 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 @@ -75,11 +75,13 @@ const char SanCov8bitCountersInitName[] = "__sanitizer_cov_8bit_counters_init"; const char SanCovBoolFlagInitName[] = "__sanitizer_cov_bool_flag_init"; const char SanCovPCsInitName[] = "__sanitizer_cov_pcs_init"; +const char SanCovCFsInitName[] = "__sanitizer_cov_cfs_init"; const char SanCovGuardsSectionName[] = "sancov_guards"; 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 +149,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 +199,7 @@ !Options.Inline8bitCounters && !Options.StackDepth && !Options.InlineBoolFlag && !Options.TraceLoads && !Options.TraceStores) Options.TracePCGuard = true; // TracePCGuard is default. + Options.CollectControlFlow |= ClCollectCF; return Options; } @@ -212,6 +219,7 @@ PostDomTreeCallback PDTCallback); private: + void createFunctionControlFlow(Function &F); void instrumentFunction(Function &F, DomTreeCallback DTCallback, PostDomTreeCallback PDTCallback); void InjectCoverageForIndirectCalls(Function &F, @@ -270,6 +278,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 +387,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 +512,15 @@ IRBuilder<> IRBCtor(Ctor->getEntryBlock().getTerminator()); IRBCtor.CreateCall(InitFunction, {SecStartEnd.first, SecStartEnd.second}); } + + if (Ctor && Options.CollectControlFlow) { + auto SecStartEnd = CreateSecStartEnd(M, SanCovCFsSectionName, IntptrPtrTy); + FunctionCallee InitFunction = declareSanitizerInitFunction( + M, SanCovCFsInitName, {IntptrPtrTy, IntptrPtrTy}); + IRBuilder<> IRBCtor(Ctor->getEntryBlock().getTerminator()); + IRBCtor.CreateCall(InitFunction, {SecStartEnd.first, SecStartEnd.second}); + } + appendToUsed(M, GlobalsToAppendToUsed); appendToCompilerUsed(M, GlobalsToAppendToCompilerUsed); return true; @@ -671,6 +690,9 @@ } } + if (Options.CollectControlFlow) + createFunctionControlFlow(F); + InjectCoverage(F, BlocksToInstrument, IsLeafFunc); InjectCoverageForIndirectCalls(F, IndirCalls); InjectTraceForCmp(F, CmpTraceTargets); @@ -1028,3 +1050,43 @@ return "\1section$end$__DATA$__" + Section; return "__stop___" + Section; } + +void ModuleSanitizerCoverage::createFunctionControlFlow(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 *)Constant::getNullValue(IntptrPtrTy)); + + for (auto &Inst: BB) { + if (CallBase *CB = dyn_cast(&Inst)) { + if (CB->isIndirectCall()) { + // TODO(navidem): handle indirect calls, for now mark its existence. + CFs.push_back((Constant *)IRB.CreateIntToPtr(ConstantInt::get(IntptrTy, -1), IntptrPtrTy)); + } + else { + auto CalledF = CB->getCalledFunction(); + if (CalledF && !CalledF->isIntrinsic()) + CFs.push_back((Constant *)IRB.CreatePointerCast(CalledF, IntptrPtrTy)); + } + } + } + + CFs.push_back((Constant *)Constant::getNullValue(IntptrPtrTy)); + } + + FunctionCFsArray = CreateFunctionLocalArrayInSection(CFs.size(), F, IntptrPtrTy, SanCovCFsSectionName); + FunctionCFsArray->setInitializer(ConstantArray::get(ArrayType::get(IntptrPtrTy, CFs.size()), CFs)); + FunctionCFsArray->setConstant(true); +} 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(ptr %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 ptr] [{{.*}}@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 +; CHECK-NEXT: @__stop___sancov_cfs = extern_weak hidden global