Index: compiler-rt/lib/fuzzer/CMakeLists.txt =================================================================== --- compiler-rt/lib/fuzzer/CMakeLists.txt +++ compiler-rt/lib/fuzzer/CMakeLists.txt @@ -13,6 +13,7 @@ FuzzerIOWindows.cpp FuzzerLoop.cpp FuzzerMerge.cpp + FuzzerMonitor.cpp FuzzerMutate.cpp FuzzerSHA1.cpp FuzzerTracePC.cpp Index: compiler-rt/lib/fuzzer/FuzzerDriver.cpp =================================================================== --- compiler-rt/lib/fuzzer/FuzzerDriver.cpp +++ compiler-rt/lib/fuzzer/FuzzerDriver.cpp @@ -301,22 +301,6 @@ return HasErrors ? 1 : 0; } -static void RssThread(size_t RssLimitMb) { - while (true) { - SleepSeconds(1); - size_t Peak = GetPeakRSSMb(); - if (Peak > RssLimitMb) - FuzzerRssLimitCallback(0); - } -} - -static void StartRssThread(size_t RssLimitMb) { - if (!RssLimitMb) - return; - std::thread T(RssThread, RssLimitMb); - T.detach(); -} - int RunOneTest(Fuzzer *F, const char *InputFilePath, size_t MaxLen) { Unit U = FileToVector(InputFilePath); if (MaxLen && MaxLen < U.size()) Index: compiler-rt/lib/fuzzer/FuzzerLoop.cpp =================================================================== --- compiler-rt/lib/fuzzer/FuzzerLoop.cpp +++ compiler-rt/lib/fuzzer/FuzzerLoop.cpp @@ -47,96 +47,6 @@ // Only one Fuzzer per process. static Fuzzer *F; -// Leak detection is expensive, so we first check if there were more mallocs -// than frees (using the sanitizer malloc hooks) and only then try to call lsan. -struct MallocFreeTracer { - void Start(int TraceLevel) { - this->TraceLevel = TraceLevel; - if (TraceLevel) - Printf("MallocFreeTracer: START\n"); - Mallocs = 0; - Frees = 0; - } - // Returns true if there were more mallocs than frees. - bool Stop() { - if (TraceLevel) - Printf("MallocFreeTracer: STOP %zd %zd (%s)\n", Mallocs.load(), - Frees.load(), Mallocs == Frees ? "same" : "DIFFERENT"); - bool Result = Mallocs > Frees; - Mallocs = 0; - Frees = 0; - TraceLevel = 0; - return Result; - } - std::atomic Mallocs; - std::atomic Frees; - int TraceLevel = 0; - - std::recursive_mutex TraceMutex; - bool TraceDisabled = false; -}; - -static MallocFreeTracer AllocTracer; - -// Locks printing and avoids nested hooks triggered from mallocs/frees in -// sanitizer. -class TraceLock { -public: - TraceLock() : Lock(AllocTracer.TraceMutex) { - AllocTracer.TraceDisabled = !AllocTracer.TraceDisabled; - } - ~TraceLock() { AllocTracer.TraceDisabled = !AllocTracer.TraceDisabled; } - - bool IsDisabled() const { - // This is already inverted value. - return !AllocTracer.TraceDisabled; - } - -private: - std::lock_guard Lock; -}; - -ATTRIBUTE_NO_SANITIZE_MEMORY -void MallocHook(const volatile void *ptr, size_t size) { - size_t N = AllocTracer.Mallocs++; - F->HandleMalloc(size); - if (int TraceLevel = AllocTracer.TraceLevel) { - TraceLock Lock; - if (Lock.IsDisabled()) - return; - Printf("MALLOC[%zd] %p %zd\n", N, ptr, size); - if (TraceLevel >= 2 && EF) - PrintStackTrace(); - } -} - -ATTRIBUTE_NO_SANITIZE_MEMORY -void FreeHook(const volatile void *ptr) { - size_t N = AllocTracer.Frees++; - if (int TraceLevel = AllocTracer.TraceLevel) { - TraceLock Lock; - if (Lock.IsDisabled()) - return; - Printf("FREE[%zd] %p\n", N, ptr); - if (TraceLevel >= 2 && EF) - PrintStackTrace(); - } -} - -// Crash on a single malloc that exceeds the rss limit. -void Fuzzer::HandleMalloc(size_t Size) { - if (!Options.MallocLimitMb || (Size >> 20) < (size_t)Options.MallocLimitMb) - return; - Printf("==%d== ERROR: libFuzzer: out-of-memory (malloc(%zd))\n", GetPid(), - Size); - Printf(" To change the out-of-memory limit use -rss_limit_mb=\n\n"); - PrintStackTrace(); - DumpCurrentUnit("oom-"); - Printf("SUMMARY: libFuzzer: out-of-memory\n"); - PrintFinalStats(); - _Exit(Options.OOMExitCode); // Stop right now. -} - Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, FuzzingOptions Options) : CB(CB), Corpus(Corpus), MD(MD), Options(Options) { @@ -146,8 +56,7 @@ F = this; TPC.ResetMaps(); IsMyThread = true; - if (Options.DetectLeaks && EF->__sanitizer_install_malloc_and_free_hooks) - EF->__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook); + AllocMonitorConfigure(Options); TPC.SetUseCounters(Options.UseCounters); TPC.SetUseValueProfileMask(Options.UseValueProfile); if (Options.Verbosity) @@ -289,7 +198,7 @@ if (LastMalloc) { Printf("(malloc(%zd))\n", LastMalloc); Printf(" To change the out-of-memory limit use -rss_limit_mb=\n\n"); - PrintStackTrace(); + PrintStackTrace(PID); } else { Printf("(used: %zdMb; limit: %zdMb)\n", GetPeakRSSMb(), Options.RssLimitMb); Printf(" To change the out-of-memory limit use -rss_limit_mb=\n\n"); @@ -596,7 +505,7 @@ CurrentUnitSize = Size; { ScopedEnableMsanInterceptorChecks S; - AllocTracer.Start(Options.TraceMalloc); + AllocMonitorStartTracing(Options.TraceMalloc); UnitStartTime = system_clock::now(); TPC.ResetMaps(); RunningUserCallback = true; @@ -605,7 +514,7 @@ UnitStopTime = system_clock::now(); (void)Res; assert(Res == 0); - HasMoreMallocsThanFrees = AllocTracer.Stop(); + HasMoreMallocsThanFrees = AllocMonitorStopTracing(); } if (!LooseMemeq(DataCopy, Data, Size)) CrashOnOverwrittenData(); @@ -882,7 +791,7 @@ // Perform several mutations and runs. MutateAndTestOne(); - PurgeAllocator(); + AllocMonitorPurge(); } PrintStats("DONE ", "\n"); Index: compiler-rt/lib/fuzzer/FuzzerMonitor.h =================================================================== --- compiler-rt/lib/fuzzer/FuzzerMonitor.h +++ compiler-rt/lib/fuzzer/FuzzerMonitor.h @@ -11,9 +11,20 @@ #ifndef LLVM_FUZZER_MONITOR_H #define LLVM_FUZZER_MONITOR_H +#include "FuzzerOptions.h" #include "FuzzerPlatform.h" #include +namespace fuzzer { + +void StartRssThread(size_t RssLimitMb); +void AllocMonitorConfigure(const FuzzingOptions &Options); +void AllocMonitorStartTracing(int TraceLevel); +bool AllocMonitorStopTracing(); +void AllocMonitorPurge(); + +} // namespace fuzzer + extern "C" { ATTRIBUTE_INTERFACE void FuzzerAlarmCallback(); Index: compiler-rt/lib/fuzzer/FuzzerMonitor.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/FuzzerMonitor.cpp @@ -0,0 +1,164 @@ +//===- FuzzerMonitor.cpp ----------------------------------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Defines an interface for tracing mallocs and frees. +//===----------------------------------------------------------------------===// + +#include "FuzzerMonitor.h" +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include "FuzzerPlatform.h" +#include "FuzzerUtil.h" +#include +#include +#include +#include + +namespace fuzzer { +namespace { + +using namespace std::chrono; + +void MallocHook(const volatile void *ptr, size_t size); +void FreeHook(const volatile void *ptr); + +// Leak detection is expensive, so we first check if there were more mallocs +// than frees (using the sanitizer malloc hooks) and only then try to call lsan. +static struct { + void Configure(const FuzzingOptions &Options) { + MallocLimit = Options.MallocLimitMb << 20; + TraceLevel = Options.TraceMalloc; + if (Options.DetectLeaks && EF->__sanitizer_install_malloc_and_free_hooks) + EF->__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook); + RssLimitMb = Options.RssLimitMb; + PurgeIntervalSec = EF->__sanitizer_purge_allocator + ? Options.PurgeAllocatorIntervalSec + : -1; + } + + void StartTracing(int TraceLevel) { + this->TraceLevel = TraceLevel; + if (TraceLevel) + Printf("MallocFreeTracer: START\n"); + Mallocs = 0; + Frees = 0; + } + + // Returns true if there were more mallocs than frees. + bool StopTracing() { + if (TraceLevel) + Printf("MallocFreeTracer: STOP %zd %zd (%s)\n", Mallocs.load(), + Frees.load(), Mallocs == Frees ? "same" : "DIFFERENT"); + bool Result = Mallocs > Frees; + Mallocs = 0; + Frees = 0; + TraceLevel = 0; + return Result; + } + + void Purge() { + if (PurgeIntervalSec < 0 || !EF->__sanitizer_purge_allocator) + return; + auto T = duration_cast(system_clock::now() - LastPurgeAttempt); + if (T.count() < PurgeIntervalSec) + return; + if (RssLimitMb <= 0 || GetPeakRSSMb() > static_cast(RssLimitMb / 2)) + EF->__sanitizer_purge_allocator(); + LastPurgeAttempt = system_clock::now(); + } + + size_t MallocLimit = 0; + std::atomic Mallocs; + std::atomic Frees; + int TraceLevel = 0; + + std::recursive_mutex TraceMutex; + bool TraceDisabled = false; + + int RssLimitMb = 0; + int PurgeIntervalSec = -1; + system_clock::time_point LastPurgeAttempt = system_clock::now(); + +} AllocMonitor; + +// Locks printing and avoids nested hooks triggered from mallocs/frees in +// sanitizer. +class TraceLock { +public: + TraceLock() : Lock(AllocMonitor.TraceMutex) { + AllocMonitor.TraceDisabled = !AllocMonitor.TraceDisabled; + } + + ~TraceLock() { AllocMonitor.TraceDisabled = !AllocMonitor.TraceDisabled; } + + bool IsDisabled() const { + // This is already inverted value. + return !AllocMonitor.TraceDisabled; + } + +private: + std::lock_guard Lock; +}; + +ATTRIBUTE_NO_SANITIZE_MEMORY +void MallocHook(const volatile void *ptr, size_t size) { + size_t N = AllocMonitor.Mallocs++; + if (AllocMonitor.MallocLimit && size >= AllocMonitor.MallocLimit) + FuzzerMallocLimitCallback(0, size); + if (int TraceLevel = AllocMonitor.TraceLevel) { + TraceLock Lock; + if (Lock.IsDisabled()) + return; + if (ptr) + Printf("MALLOC[%zd] %p %zd\n", N, ptr, size); + if (TraceLevel >= 2 && EF) + PrintStackTrace(); + } +} + +ATTRIBUTE_NO_SANITIZE_MEMORY +void FreeHook(const volatile void *ptr) { + size_t N = AllocMonitor.Frees++; + if (int TraceLevel = AllocMonitor.TraceLevel) { + TraceLock Lock; + if (Lock.IsDisabled()) + return; + Printf("FREE[%zd] %p\n", N, ptr); + if (TraceLevel >= 2 && EF) + PrintStackTrace(); + } +} + +} // namespace + +void StartRssThread(size_t RssLimitMb) { + if (!RssLimitMb) + return; + std::thread T([RssLimitMb]() { + while (true) { + SleepSeconds(1); + size_t Peak = GetPeakRSSMb(); + if (Peak > RssLimitMb) + FuzzerRssLimitCallback(0); + } + }); + T.detach(); +} + +void AllocMonitorConfigure(const FuzzingOptions &Options) { + AllocMonitor.Configure(Options); +} + +void AllocMonitorStartTracing(int TraceLevel) { + AllocMonitor.StartTracing(TraceLevel); +} + +bool AllocMonitorStopTracing() { return AllocMonitor.StopTracing(); } + +void AllocMonitorPurge() { AllocMonitor.Purge(); } + +} // namespace fuzzer