Index: lib/fuzzer/FuzzerDriver.cpp =================================================================== --- lib/fuzzer/FuzzerDriver.cpp +++ lib/fuzzer/FuzzerDriver.cpp @@ -615,6 +615,7 @@ 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; Index: lib/fuzzer/FuzzerFlags.def =================================================================== --- lib/fuzzer/FuzzerFlags.def +++ lib/fuzzer/FuzzerFlags.def @@ -110,6 +110,8 @@ 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.") Index: lib/fuzzer/FuzzerInternal.h =================================================================== --- lib/fuzzer/FuzzerInternal.h +++ lib/fuzzer/FuzzerInternal.h @@ -67,6 +67,7 @@ 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); Index: lib/fuzzer/FuzzerLoop.cpp =================================================================== --- lib/fuzzer/FuzzerLoop.cpp +++ lib/fuzzer/FuzzerLoop.cpp @@ -351,6 +351,8 @@ void Fuzzer::PrintFinalStats() { if (Options.PrintCoverage) TPC.PrintCoverage(); + if (Options.PrintUnstableStats) + TPC.PrintUnstableStats(); if (Options.DumpCoverage) TPC.DumpCoverage(); if (Options.PrintCorpusStats) @@ -443,6 +445,30 @@ } } +void Fuzzer::CheckForUnstableCounters(const uint8_t *Data, size_t Size) { + auto CBSetupAndRun = [&]() { + 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) @@ -465,6 +491,12 @@ *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, Index: lib/fuzzer/FuzzerOptions.h =================================================================== --- lib/fuzzer/FuzzerOptions.h +++ lib/fuzzer/FuzzerOptions.h @@ -54,6 +54,7 @@ bool PrintFinalStats = false; bool PrintCorpusStats = false; bool PrintCoverage = false; + bool PrintUnstableStats = false; bool DumpCoverage = false; bool DetectLeaks = true; int PurgeAllocatorIntervalSec = 1; Index: lib/fuzzer/FuzzerTracePC.h =================================================================== --- lib/fuzzer/FuzzerTracePC.h +++ lib/fuzzer/FuzzerTracePC.h @@ -103,6 +103,7 @@ void PrintCoverage(); void DumpCoverage(); + void PrintUnstableStats(); template void IterateCoveredFunctions(CallBack CB); @@ -135,7 +136,17 @@ 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; bool UseValueProfile = false; bool DoPrintNewPCs = false; Index: lib/fuzzer/FuzzerTracePC.cpp =================================================================== --- lib/fuzzer/FuzzerTracePC.cpp +++ lib/fuzzer/FuzzerTracePC.cpp @@ -59,6 +59,38 @@ return Res; } +// Initializes unstable counters by copying Inline8bitCounters to unstable +// counters. +void TracePC::InitializeUnstableCounters() { + if (NumInline8bitCounters == NumPCsInPCTables) { + size_t UnstableIdx = 1; + 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 == NumPCsInPCTables) { + size_t UnstableIdx = 1; + 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) + UnstableCounters[UnstableIdx] = kUnstableCounter; + } + } +} void TracePC::HandleInline8bitCountersInit(uint8_t *Start, uint8_t *Stop) { if (Start == Stop) return; @@ -307,6 +339,15 @@ } } +void TracePC::PrintUnstableStats() { + size_t count = 0; + for (size_t i = 1; 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. Index: test/fuzzer/PrintUnstableStatsTest.cpp =================================================================== --- test/fuzzer/PrintUnstableStatsTest.cpp +++ 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; +} Index: test/fuzzer/print_unstable_stats.test =================================================================== --- test/fuzzer/print_unstable_stats.test +++ test/fuzzer/print_unstable_stats.test @@ -0,0 +1,6 @@ +RUN: %cpp_compiler -fsanitize=fuzzer %S/PrintUnstableStatsTest.cpp -o %t-PrintUnstableStatsTest + +RUN: rm -rf %t_workdir && mkdir -p %t_workdir + +RUN: env ASAN_OPTIONS=coverage_dir='"%t_workdir"' %run %t-PrintUnstableStatsTest -print_unstable_stats=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=LONG +LONG: stat::stability_rate: 27.59%