diff --git a/compiler-rt/lib/fuzzer/FuzzerDriver.cpp b/compiler-rt/lib/fuzzer/FuzzerDriver.cpp index ff2a639ac4a3..9375925b2c94 100644 --- a/compiler-rt/lib/fuzzer/FuzzerDriver.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerDriver.cpp @@ -617,6 +617,7 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) { Options.PrintFinalStats = Flags.print_final_stats; Options.PrintCorpusStats = Flags.print_corpus_stats; Options.PrintCoverage = Flags.print_coverage; + Options.PrintUnstableStats = Flags.print_unstable_stats; Options.DumpCoverage = Flags.dump_coverage; if (Flags.exit_on_src_pos) Options.ExitOnSrcPos = Flags.exit_on_src_pos; diff --git a/compiler-rt/lib/fuzzer/FuzzerFlags.def b/compiler-rt/lib/fuzzer/FuzzerFlags.def index aaa172716b5a..e50b82ab70a5 100644 --- a/compiler-rt/lib/fuzzer/FuzzerFlags.def +++ b/compiler-rt/lib/fuzzer/FuzzerFlags.def @@ -110,6 +110,8 @@ FUZZER_FLAG_INT(print_coverage, 0, "If 1, print coverage information as text" FUZZER_FLAG_INT(dump_coverage, 0, "Deprecated." " If 1, dump coverage information as a" " .sancov file at exit.") +FUZZER_FLAG_INT(print_unstable_stats, 0, "Experimental." + " If 1, print unstable statistics at exit.") FUZZER_FLAG_INT(handle_segv, 1, "If 1, try to intercept SIGSEGV.") FUZZER_FLAG_INT(handle_bus, 1, "If 1, try to intercept SIGBUS.") FUZZER_FLAG_INT(handle_abrt, 1, "If 1, try to intercept SIGABRT.") diff --git a/compiler-rt/lib/fuzzer/FuzzerInternal.h b/compiler-rt/lib/fuzzer/FuzzerInternal.h index b420596b4e3a..0eb42895548c 100644 --- a/compiler-rt/lib/fuzzer/FuzzerInternal.h +++ b/compiler-rt/lib/fuzzer/FuzzerInternal.h @@ -67,6 +67,7 @@ public: static void StaticGracefulExitCallback(); void ExecuteCallback(const uint8_t *Data, size_t Size); + void CheckForUnstableCounters(const uint8_t *Data, size_t Size); bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false, InputInfo *II = nullptr, bool *FoundUniqFeatures = nullptr); diff --git a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp index 08b54553792f..ba61c15f01bf 100644 --- a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp @@ -352,6 +352,8 @@ void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units) { void Fuzzer::PrintFinalStats() { if (Options.PrintCoverage) TPC.PrintCoverage(); + if (Options.PrintUnstableStats) + TPC.PrintUnstableStats(); if (Options.DumpCoverage) TPC.DumpCoverage(); if (Options.PrintCorpusStats) @@ -444,6 +446,29 @@ void Fuzzer::PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size) { } } +void Fuzzer::CheckForUnstableCounters(const uint8_t *Data, size_t Size) { + auto CBSetupAndRun = [&]() { + ScopedEnableMsanInterceptorChecks S; + UnitStartTime = system_clock::now(); + TPC.ResetMaps(); + RunningCB = true; + CB(Data, Size); + RunningCB = false; + UnitStopTime = system_clock::now(); + }; + + // Copy original run counters into our unstable counters + TPC.InitializeUnstableCounters(); + + // First Rerun + CBSetupAndRun(); + TPC.UpdateUnstableCounters(); + + // Second Rerun + CBSetupAndRun(); + TPC.UpdateUnstableCounters(); +} + bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile, InputInfo *II, bool *FoundUniqFeatures) { if (!Size) @@ -466,6 +491,12 @@ bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile, *FoundUniqFeatures = FoundUniqFeaturesOfII; PrintPulseAndReportSlowInput(Data, Size); size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore; + + // If print_unstable_stats, execute the same input two more times to detect + // unstable edges. + if (NumNewFeatures && Options.PrintUnstableStats) + CheckForUnstableCounters(Data, Size); + if (NumNewFeatures) { TPC.UpdateObservedPCs(); Corpus.AddToCorpus({Data, Data + Size}, NumNewFeatures, MayDeleteFile, diff --git a/compiler-rt/lib/fuzzer/FuzzerOptions.h b/compiler-rt/lib/fuzzer/FuzzerOptions.h index ab90df82a63d..e32b7d59b853 100644 --- a/compiler-rt/lib/fuzzer/FuzzerOptions.h +++ b/compiler-rt/lib/fuzzer/FuzzerOptions.h @@ -54,6 +54,7 @@ struct FuzzingOptions { bool PrintFinalStats = false; bool PrintCorpusStats = false; bool PrintCoverage = false; + bool PrintUnstableStats = false; bool DumpCoverage = false; bool DetectLeaks = true; int PurgeAllocatorIntervalSec = 1; diff --git a/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp b/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp index 4e0ff149df6d..ed920b8e59a7 100644 --- a/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp @@ -59,6 +59,37 @@ size_t TracePC::GetTotalPCCoverage() { return Res; } +// Initializes unstable counters by copying Inline8bitCounters to unstable +// counters. +void TracePC::InitializeUnstableCounters() { + if (NumInline8bitCounters && NumInline8bitCounters == NumPCsInPCTables) { + size_t UnstableIdx = 0; + for (size_t i = 0; i < NumModulesWithInline8bitCounters; i++) { + uint8_t *Beg = ModuleCounters[i].Start; + size_t Size = ModuleCounters[i].Stop - Beg; + assert(Size == (size_t)(ModulePCTable[i].Stop - ModulePCTable[i].Start)); + for (size_t j = 0; j < Size; j++, UnstableIdx++) + if (UnstableCounters[UnstableIdx] != kUnstableCounter) + UnstableCounters[UnstableIdx] = Beg[j]; + } + } +} + +// Compares the current counters with counters from previous runs +// and records differences as unstable edges. +void TracePC::UpdateUnstableCounters() { + if (NumInline8bitCounters && NumInline8bitCounters == NumPCsInPCTables) { + size_t UnstableIdx = 0; + for (size_t i = 0; i < NumModulesWithInline8bitCounters; i++) { + uint8_t *Beg = ModuleCounters[i].Start; + size_t Size = ModuleCounters[i].Stop - Beg; + assert(Size == (size_t)(ModulePCTable[i].Stop - ModulePCTable[i].Start)); + for (size_t j = 0; j < Size; j++, UnstableIdx++) + if (Beg[j] != UnstableCounters[UnstableIdx]) + UnstableCounters[UnstableIdx] = kUnstableCounter; + } + } +} void TracePC::HandleInline8bitCountersInit(uint8_t *Start, uint8_t *Stop) { if (Start == Stop) return; @@ -310,6 +341,15 @@ void TracePC::DumpCoverage() { } } +void TracePC::PrintUnstableStats() { + size_t count = 0; + for (size_t i = 0; i < NumInline8bitCounters; i++) + if (UnstableCounters[i] == kUnstableCounter) + count++; + Printf("stat::stability_rate: %.2f\n", + 100 - static_cast(count * 100) / NumInline8bitCounters); +} + // Value profile. // We keep track of various values that affect control flow. // These values are inserted into a bit-set-based hash map. diff --git a/compiler-rt/lib/fuzzer/FuzzerTracePC.h b/compiler-rt/lib/fuzzer/FuzzerTracePC.h index d397bedf8b51..424c20722a18 100644 --- a/compiler-rt/lib/fuzzer/FuzzerTracePC.h +++ b/compiler-rt/lib/fuzzer/FuzzerTracePC.h @@ -103,6 +103,7 @@ class TracePC { void PrintCoverage(); void DumpCoverage(); + void PrintUnstableStats(); template void IterateCoveredFunctions(CallBack CB); @@ -135,7 +136,17 @@ class TracePC { void SetFocusFunction(const std::string &FuncName); bool ObservedFocusFunction(); + void InitializeUnstableCounters(); + void UpdateUnstableCounters(); + private: + // Value used to represent unstable edge. + static constexpr int16_t kUnstableCounter = -1; + + // Uses 16-bit signed type to be able to accommodate any possible value from + // uint8_t counter and -1 constant as well. + int16_t UnstableCounters[kNumPCs]; + bool UseCounters = false; uint32_t UseValueProfileMask = false; bool DoPrintNewPCs = false; diff --git a/compiler-rt/test/fuzzer/PrintUnstableStatsTest.cpp b/compiler-rt/test/fuzzer/PrintUnstableStatsTest.cpp new file mode 100644 index 000000000000..078eb4c3d971 --- /dev/null +++ b/compiler-rt/test/fuzzer/PrintUnstableStatsTest.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +int x = 0; +bool skip0 = false; +bool skip1 = false; +bool skip2 = false; + +__attribute__((noinline)) void det0() { x++; } +__attribute__((noinline)) void det1() { x++; } +__attribute__((noinline)) void det2() { x++; } +__attribute__((noinline)) void det3() { x++; } +__attribute__((noinline)) void det4() { x++; } + +__attribute__((noinline)) void ini0() { x++; } +__attribute__((noinline)) void ini1() { x++; } +__attribute__((noinline)) void ini2() { x++; } + +__attribute__((noinline)) void t0() { x++; } +__attribute__((noinline)) void t1() { x++; } +__attribute__((noinline)) void t2() { x++; } +__attribute__((noinline)) void t3() { x++; } +__attribute__((noinline)) void t4() { x++; } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size == 1 && Data[0] == 'A' && !skip0) { + skip0 = true; + ini0(); + } + if (Size == 1 && Data[0] == 'B' && !skip1) { + skip1 = true; + ini1(); + } + if (Size == 1 && Data[0] == 'C' && !skip2) { + skip2 = true; + ini2(); + } + + det0(); + det1(); + int a = rand(); + det2(); + + switch (a % 5) { + case 0: + t0(); + break; + case 1: + t1(); + break; + case 2: + t2(); + break; + case 3: + t3(); + break; + case 4: + t4(); + break; + default: + assert(false); + } + + det3(); + det4(); + return 0; +} diff --git a/compiler-rt/test/fuzzer/print_unstable_stats.test b/compiler-rt/test/fuzzer/print_unstable_stats.test new file mode 100644 index 000000000000..bba99aecc838 --- /dev/null +++ b/compiler-rt/test/fuzzer/print_unstable_stats.test @@ -0,0 +1,3 @@ +RUN: %cpp_compiler %S/PrintUnstableStatsTest.cpp -o %t-PrintUnstableStatsTest +RUN: %run %t-PrintUnstableStatsTest -print_unstable_stats=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=LONG +LONG: stat::stability_rate: 27.59