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,47 @@ return "\1section$end$__DATA$__" + Section; return "__stop___" + Section; } + +void ModuleSanitizerCoverage::CollectFunctionControlFlow(Function &F) { + LLVM_DEBUG(dbgs() << "I am here! " << F.getName() << "\n"); + SmallVector CFs; + size_t N; + IRBuilder<> IRB(&*F.getEntryBlock().getFirstInsertionPt()); + + for (auto &BB: F) { + // blockaddress may 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)) { + if (SuccBB == &F.getEntryBlock()) + CFs.push_back((Constant *)IRB.CreatePointerCast(&F, IntptrPtrTy)); + else + 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) { + CallBase *CB = dyn_cast(&Inst); + if (CB) { + 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)); + } + + N = CFs.size(); + auto CFArray = CreateFunctionLocalArrayInSection(N, F, IntptrPtrTy, SanCovCFsSectionName); + CFArray->setInitializer(ConstantArray::get(ArrayType::get(IntptrPtrTy, N), 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,21 @@ +; 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 + br label %if.end + + if.end: ; preds = %entry, %if.then + ret void +} + +; CHECK: private constant [16 x i64*] [{{.*}}@foo{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}blockaddress{{.*}}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*