[SanitizerCoverage] Add stack depth tracing instrumentation.

Summary:
Augment SanitizerCoverage to insert maximum stack depth tracing for
use by libFuzzer.  The new instrumentation is enabled by the flag
-fsanitize-coverage=stack-depth and is compatible with the existing
trace-pc-guard coverage.  The user must also declare the following
global variable in their code:
  thread_local uintptr_t __sancov_lowest_stack

https://bugs.llvm.org/show_bug.cgi?id=33857

Reviewers: vitalybuka, kcc

Reviewed By: vitalybuka

Subscribers: kubamracek, hiraditya, cfe-commits, llvm-commits

Differential Revision: https://reviews.llvm.org/D36839

llvm-svn: 311186
This commit is contained in:
Matt Morehouse 2017-08-18 18:43:30 +00:00
parent 6178cfaf7b
commit 5c7fc76983
12 changed files with 179 additions and 23 deletions

View File

@ -307,6 +307,9 @@ def fsanitize_coverage_trace_pc_guard
def fsanitize_coverage_no_prune
: Flag<["-"], "fsanitize-coverage-no-prune">,
HelpText<"Disable coverage pruning (i.e. instrument all blocks/edges)">;
def fsanitize_coverage_stack_depth
: Flag<["-"], "fsanitize-coverage-stack-depth">,
HelpText<"Enable max stack depth tracing">;
def fprofile_instrument_EQ : Joined<["-"], "fprofile-instrument=">,
HelpText<"Enable PGO instrumentation. The accepted value is clang, llvm, "
"or none">, Values<"none,clang,llvm">;

View File

@ -169,6 +169,7 @@ CODEGENOPT(SanitizeCoverageTracePCGuard, 1, 0) ///< Enable PC tracing with guard
CODEGENOPT(SanitizeCoverageInline8bitCounters, 1, 0) ///< Use inline 8bit counters.
CODEGENOPT(SanitizeCoveragePCTable, 1, 0) ///< Create a PC Table.
CODEGENOPT(SanitizeCoverageNoPrune, 1, 0) ///< Disable coverage pruning.
CODEGENOPT(SanitizeCoverageStackDepth, 1, 0) ///< Enable max stack depth tracing
CODEGENOPT(SanitizeStats , 1, 0) ///< Collect statistics for sanitizers.
CODEGENOPT(SimplifyLibCalls , 1, 1) ///< Set when -fbuiltin is enabled.
CODEGENOPT(SoftFloat , 1, 0) ///< -soft-float.

View File

@ -190,6 +190,7 @@ static void addSanitizerCoveragePass(const PassManagerBuilder &Builder,
Opts.NoPrune = CGOpts.SanitizeCoverageNoPrune;
Opts.Inline8bitCounters = CGOpts.SanitizeCoverageInline8bitCounters;
Opts.PCTable = CGOpts.SanitizeCoveragePCTable;
Opts.StackDepth = CGOpts.SanitizeCoverageStackDepth;
PM.add(createSanitizerCoverageModulePass(Opts));
}

View File

@ -58,6 +58,7 @@ enum CoverageFeature {
CoverageNoPrune = 1 << 11,
CoverageInline8bitCounters = 1 << 12,
CoveragePCTable = 1 << 13,
CoverageStackDepth = 1 << 14,
};
/// Parse a -fsanitize= or -fno-sanitize= argument's values, diagnosing any
@ -556,11 +557,15 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
}
// trace-pc w/o func/bb/edge implies edge.
if ((CoverageFeatures &
(CoverageTracePC | CoverageTracePCGuard | CoverageInline8bitCounters)) &&
!(CoverageFeatures & InsertionPointTypes))
if (!(CoverageFeatures & InsertionPointTypes)) {
if (CoverageFeatures &
(CoverageTracePC | CoverageTracePCGuard | CoverageInline8bitCounters))
CoverageFeatures |= CoverageEdge;
if (CoverageFeatures & CoverageStackDepth)
CoverageFeatures |= CoverageFunc;
}
if (AllAddedKinds & Address) {
AsanSharedRuntime =
Args.hasArg(options::OPT_shared_libasan) ||
@ -672,7 +677,8 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
std::make_pair(CoverageTracePCGuard, "-fsanitize-coverage-trace-pc-guard"),
std::make_pair(CoverageInline8bitCounters, "-fsanitize-coverage-inline-8bit-counters"),
std::make_pair(CoveragePCTable, "-fsanitize-coverage-pc-table"),
std::make_pair(CoverageNoPrune, "-fsanitize-coverage-no-prune")};
std::make_pair(CoverageNoPrune, "-fsanitize-coverage-no-prune"),
std::make_pair(CoverageStackDepth, "-fsanitize-coverage-stack-depth")};
for (auto F : CoverageFlags) {
if (CoverageFeatures & F.first)
CmdArgs.push_back(F.second);
@ -835,6 +841,7 @@ int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A) {
.Case("no-prune", CoverageNoPrune)
.Case("inline-8bit-counters", CoverageInline8bitCounters)
.Case("pc-table", CoveragePCTable)
.Case("stack-depth", CoverageStackDepth)
.Default(0);
if (F == 0)
D.Diag(clang::diag::err_drv_unsupported_option_argument)

View File

@ -795,6 +795,8 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK,
Opts.SanitizeCoverageInline8bitCounters =
Args.hasArg(OPT_fsanitize_coverage_inline_8bit_counters);
Opts.SanitizeCoveragePCTable = Args.hasArg(OPT_fsanitize_coverage_pc_table);
Opts.SanitizeCoverageStackDepth =
Args.hasArg(OPT_fsanitize_coverage_stack_depth);
Opts.SanitizeMemoryTrackOrigins =
getLastArgIntValue(Args, OPT_fsanitize_memory_track_origins_EQ, 0, Diags);
Opts.SanitizeMemoryUseAfterDtor =

View File

@ -72,6 +72,17 @@
// CHECK-TRACE_PC_GUARD_FUNC: -fsanitize-coverage-type=1
// CHECK-TRACE_PC_GUARD_FUNC: -fsanitize-coverage-trace-pc-guard
// RUN: %clang -target x86_64-linux-gnu -fsanitize-coverage=stack-depth %s \
// RUN: -### 2>&1 | FileCheck %s --check-prefix=CHECK-STACK-DEPTH
// RUN: %clang -target x86_64-linux-gnu \
// RUN: -fsanitize-coverage=trace-pc-guard,stack-depth %s -### 2>&1 | \
// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-PC-GUARD
// CHECK-STACK-DEPTH: -fsanitize-coverage-type=1
// CHECK-STACK-DEPTH: -fsanitize-coverage-stack-depth
// CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-type=3
// CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-trace-pc-guard
// CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-stack-depth
// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=trace-cmp,indirect-calls %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TYPE-NECESSARY
// CHECK-NO-TYPE-NECESSARY-NOT: error:
// CHECK-NO-TYPE-NECESSARY: -fsanitize-coverage-indirect-calls

View File

@ -0,0 +1,32 @@
// Tests -fsanitize-coverage=stack-depth
//
// XFAIL: tsan
//
// RUN: %clangxx -O0 -std=c++11 -fsanitize-coverage=stack-depth %s -o %t
// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not Assertion{{.*}}failed
// RUN: %clangxx -O0 -std=c++11 -fsanitize-coverage=trace-pc-guard,stack-depth \
// RUN: %s -o %t
// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not Assertion{{.*}}failed
#include <cstdint>
#include <cstdio>
#include <cassert>
thread_local uintptr_t __sancov_lowest_stack;
uintptr_t last_stack;
void foo(int recurse) {
assert(__sancov_lowest_stack < last_stack);
last_stack = __sancov_lowest_stack;
if (recurse <= 0) return;
foo(recurse - 1);
}
int main() {
last_stack = __sancov_lowest_stack;
foo(100);
printf("Success!\n");
return 0;
}
// CHECK: Success!

View File

@ -185,6 +185,7 @@ struct SanitizerCoverageOptions {
bool Inline8bitCounters = false;
bool PCTable = false;
bool NoPrune = false;
bool StackDepth = false;
SanitizerCoverageOptions() = default;
};

View File

@ -31,6 +31,9 @@ uint8_t __sancov_trace_pc_guard_8bit_counters[fuzzer::TracePC::kNumPCs];
ATTRIBUTE_INTERFACE
uintptr_t __sancov_trace_pc_pcs[fuzzer::TracePC::kNumPCs];
// Used by -fsanitize-coverage=stack-depth to track stack depth
ATTRIBUTE_INTERFACE thread_local uintptr_t __sancov_lowest_stack;
namespace fuzzer {
TracePC TPC;
@ -340,6 +343,14 @@ void TracePC::ClearInlineCounters() {
}
}
void TracePC::RecordInitialStack() {
InitialStack = __sancov_lowest_stack;
}
uintptr_t TracePC::GetMaxStackOffset() const {
return InitialStack - __sancov_lowest_stack; // Stack grows down
}
} // namespace fuzzer
extern "C" {
@ -350,8 +361,6 @@ void __sanitizer_cov_trace_pc_guard(uint32_t *Guard) {
uint32_t Idx = *Guard;
__sancov_trace_pc_pcs[Idx] = PC;
__sancov_trace_pc_guard_8bit_counters[Idx]++;
// Uncomment the following line to get stack-depth profiling.
// fuzzer::TPC.RecordCurrentStack();
}
// Best-effort support for -fsanitize-coverage=trace-pc, which is available

View File

@ -120,19 +120,8 @@ class TracePC {
return PCs()[Idx];
}
void RecordCurrentStack() {
uintptr_t Stack = GetCurrentStack();
if (Stack < LowestStack)
LowestStack = Stack;
}
void RecordInitialStack() {
InitialStack = GetCurrentStack();
LowestStack = InitialStack;
}
uintptr_t GetCurrentStack() const {
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
}
uintptr_t GetMaxStackOffset() const { return InitialStack - LowestStack; }
void RecordInitialStack();
uintptr_t GetMaxStackOffset() const;
template<class CallBack>
void ForEachObservedPC(CallBack CB) {
@ -167,7 +156,7 @@ private:
std::set<uintptr_t> ObservedPCs;
ValueBitMap ValueProfileMap;
uintptr_t InitialStack, LowestStack; // Assume stack grows down.
uintptr_t InitialStack;
};
template <class Callback>

View File

@ -17,12 +17,15 @@
#include "llvm/Analysis/PostDominators.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/CallSite.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/IR/Module.h"
@ -73,6 +76,10 @@ static const char *const SanCovGuardsSectionName = "sancov_guards";
static const char *const SanCovCountersSectionName = "sancov_cntrs";
static const char *const SanCovPCsSectionName = "sancov_pcs";
static const char *const SanCovLowestStackName = "__sancov_lowest_stack";
static const char *const SanCovLowestStackTLSWrapperName =
"_ZTW21__sancov_lowest_stack";
static cl::opt<int> ClCoverageLevel(
"sanitizer-coverage-level",
cl::desc("Sanitizer Coverage. 0: none, 1: entry block, 2: all blocks, "
@ -119,6 +126,10 @@ static cl::opt<bool>
cl::desc("Reduce the number of instrumented blocks"),
cl::Hidden, cl::init(true));
static cl::opt<bool> ClStackDepth("sanitizer-coverage-stack-depth",
cl::desc("max stack depth tracing"),
cl::Hidden, cl::init(false));
namespace {
SanitizerCoverageOptions getOptions(int LegacyCoverageLevel) {
@ -156,9 +167,11 @@ SanitizerCoverageOptions OverrideFromCL(SanitizerCoverageOptions Options) {
Options.TracePCGuard |= ClTracePCGuard;
Options.Inline8bitCounters |= ClInline8bitCounters;
Options.PCTable |= ClCreatePCTable;
if (!Options.TracePCGuard && !Options.TracePC && !Options.Inline8bitCounters)
Options.TracePCGuard = true; // TracePCGuard is default.
Options.NoPrune |= !ClPruneBlocks;
Options.StackDepth |= ClStackDepth;
if (!Options.TracePCGuard && !Options.TracePC &&
!Options.Inline8bitCounters && !Options.StackDepth)
Options.TracePCGuard = true; // TracePCGuard is default.
return Options;
}
@ -216,6 +229,8 @@ private:
Function *SanCovTraceDivFunction[2];
Function *SanCovTraceGepFunction;
Function *SanCovTraceSwitchFunction;
Function *SanCovLowestStackTLSWrapper;
GlobalVariable *SanCovLowestStack;
InlineAsm *EmptyAsm;
Type *IntptrTy, *IntptrPtrTy, *Int64Ty, *Int64PtrTy, *Int32Ty, *Int32PtrTy,
*Int16Ty, *Int8Ty, *Int8PtrTy;
@ -333,6 +348,24 @@ bool SanitizerCoverageModule::runOnModule(Module &M) {
SanCovTraceSwitchFunction =
checkSanitizerInterfaceFunction(M.getOrInsertFunction(
SanCovTraceSwitchName, VoidTy, Int64Ty, Int64PtrTy));
Constant *SanCovLowestStackConstant =
M.getOrInsertGlobal(SanCovLowestStackName, IntptrTy);
SanCovLowestStackTLSWrapper =
checkSanitizerInterfaceFunction(M.getOrInsertFunction(
SanCovLowestStackTLSWrapperName, IntptrTy->getPointerTo()));
if (Options.StackDepth) {
assert(isa<GlobalVariable>(SanCovLowestStackConstant));
SanCovLowestStack = cast<GlobalVariable>(SanCovLowestStackConstant);
if (!SanCovLowestStack->isDeclaration()) {
// Check that the user has correctly defined:
// thread_local uintptr_t __sancov_lowest_stack
// and initialize it.
assert(SanCovLowestStack->isThreadLocal());
SanCovLowestStack->setInitializer(Constant::getAllOnesValue(IntptrTy));
}
}
// Make sure smaller parameters are zero-extended to i64 as required by the
// x86_64 ABI.
if (TargetTriple.getArch() == Triple::x86_64) {
@ -451,6 +484,9 @@ bool SanitizerCoverageModule::runOnFunction(Function &F) {
if (F.getName() == "__local_stdio_printf_options" ||
F.getName() == "__local_stdio_scanf_options")
return false;
// Avoid infinite recursion by not instrumenting stack depth TLS wrapper
if (F.getName() == SanCovLowestStackTLSWrapperName)
return false;
// Don't instrument functions using SEH for now. Splitting basic blocks like
// we do for coverage breaks WinEHPrepare.
// FIXME: Remove this when SEH no longer uses landingpad pattern matching.
@ -728,6 +764,20 @@ void SanitizerCoverageModule::InjectCoverageAtBlock(Function &F, BasicBlock &BB,
SetNoSanitizeMetadata(Load);
SetNoSanitizeMetadata(Store);
}
if (Options.StackDepth && IsEntryBB) {
// Check stack depth. If it's the deepest so far, record it.
Function *GetFrameAddr =
Intrinsic::getDeclaration(F.getParent(), Intrinsic::frameaddress);
auto FrameAddrPtr =
IRB.CreateCall(GetFrameAddr, {Constant::getNullValue(Int32Ty)});
auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy);
auto LowestStackPtr = IRB.CreateCall(SanCovLowestStackTLSWrapper);
auto LowestStack = IRB.CreateLoad(LowestStackPtr);
auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack);
auto ThenTerm = SplitBlockAndInsertIfThen(IsStackLower, &*IP, false);
IRBuilder<> ThenIRB(ThenTerm);
ThenIRB.CreateStore(FrameAddrInt, LowestStackPtr);
}
}
std::string

View File

@ -0,0 +1,50 @@
; This check verifies that stack depth instrumentation works correctly.
; RUN: opt < %s -sancov -sanitizer-coverage-level=1 \
; RUN: -sanitizer-coverage-stack-depth -S | FileCheck %s --enable-var-scope
; RUN: opt < %s -sancov -sanitizer-coverage-level=3 \
; RUN: -sanitizer-coverage-stack-depth -sanitizer-coverage-trace-pc-guard \
; RUN: -S | FileCheck %s --enable-var-scope
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
; CHECK: @__sancov_lowest_stack = thread_local global i64 -1
@__sancov_lowest_stack = thread_local global i64 0, align 8
define i32 @foo() {
entry:
; CHECK-LABEL: define i32 @foo
; CHECK: [[framePtr:%[^ \t]+]] = call i8* @llvm.frameaddress(i32 0)
; CHECK: [[frameInt:%[^ \t]+]] = ptrtoint i8* [[framePtr]] to [[$intType:i[0-9]+]]
; CHECK: [[lowestPtr:%[^ \t]+]] = call [[$intType]]* @_ZTW21__sancov_lowest_stack
; CHECK: [[lowestInt:%[^ \t]+]] = load [[$intType]], [[$intType]]* [[lowestPtr]]
; CHECK: [[cmp:%[^ \t]+]] = icmp ult [[$intType]] [[frameInt]], [[lowestInt]]
; CHECK: br i1 [[cmp]], label %[[ifLabel:[^ \t]+]], label
; CHECK: <label>:[[ifLabel]]:
; CHECK: store [[$intType]] [[frameInt]], [[$intType]]* [[lowestPtr]]
; CHECK: ret i32 7
ret i32 7
}
define i32 @bar() {
entry:
; CHECK-LABEL: define i32 @bar
; CHECK: [[framePtr:%[^ \t]+]] = call i8* @llvm.frameaddress(i32 0)
; CHECK: [[frameInt:%[^ \t]+]] = ptrtoint i8* [[framePtr]] to [[$intType]]
; CHECK: [[lowestPtr:%[^ \t]+]] = call [[$intType]]* @_ZTW21__sancov_lowest_stack
; CHECK: [[lowestInt:%[^ \t]+]] = load [[$intType]], [[$intType]]* [[lowestPtr]]
; CHECK: [[cmp:%[^ \t]+]] = icmp ult [[$intType]] [[frameInt]], [[lowestInt]]
; CHECK: br i1 [[cmp]], label %[[ifLabel:[^ \t]+]], label
; CHECK: <label>:[[ifLabel]]:
; CHECK: store [[$intType]] [[frameInt]], [[$intType]]* [[lowestPtr]]
; CHECK: %call = call i32 @foo()
; CHECK: ret i32 %call
%call = call i32 @foo()
ret i32 %call
}
define weak_odr hidden i64* @_ZTW21__sancov_lowest_stack() {
ret i64* @__sancov_lowest_stack
}