[libFuzzer] refactoring to make -shrink=1 work for value profile, added a test.

llvm-svn: 283409
This commit is contained in:
Kostya Serebryany 2016-10-05 22:56:21 +00:00
parent ae34c56ee7
commit 1c73f1bf27
12 changed files with 145 additions and 150 deletions

View File

@ -34,9 +34,11 @@ struct InputInfo {
class InputCorpus {
public:
static const size_t kFeatureSetSize = 1 << 16;
InputCorpus() {
Inputs.reserve(1 << 14); // Avoid too many resizes.
memset(FeatureSet, 0, sizeof(FeatureSet));
memset(InputSizesPerFeature, 0, sizeof(InputSizesPerFeature));
memset(SmallestElementPerFeature, 0, sizeof(SmallestElementPerFeature));
}
size_t size() const { return Inputs.size(); }
size_t SizeInBytes() const {
@ -53,17 +55,20 @@ class InputCorpus {
}
bool empty() const { return Inputs.empty(); }
const Unit &operator[] (size_t Idx) const { return Inputs[Idx].U; }
void AddToCorpus(const Unit &U) {
void AddToCorpus(const Unit &U, size_t NumFeatures) {
assert(!U.empty());
uint8_t Hash[kSHA1NumBytes];
if (FeatureDebug)
Printf("ADD_TO_CORPUS %zd NF %zd\n", Inputs.size(), NumFeatures);
ComputeSHA1(U.data(), U.size(), Hash);
if (!Hashes.insert(Sha1ToString(Hash)).second) return;
Hashes.insert(Sha1ToString(Hash));
Inputs.push_back(InputInfo());
InputInfo &II = Inputs.back();
II.U = U;
II.NumFeatures = NumFeatures;
memcpy(II.Sha1, Hash, kSHA1NumBytes);
UpdateFeatureSet(Inputs.size() - 1);
UpdateCorpusDistribution();
ValidateFeatureSet();
}
typedef const std::vector<InputInfo>::const_iterator ConstIter;
@ -97,12 +102,9 @@ class InputCorpus {
}
void PrintFeatureSet() {
Printf("Features [id: idx sz] ");
for (size_t i = 0; i < kFeatureSetSize; i++) {
auto &Fe = FeatureSet[i];
if (!Fe.Count) continue;
Printf("[%zd: %zd %zd] ", i, Fe.SmallestElementIdx,
Fe.SmallestElementSize);
if(size_t Sz = GetFeature(i))
Printf("[%zd: id %zd sz%zd] ", i, SmallestElementPerFeature[i], Sz);
}
Printf("\n\t");
for (size_t i = 0; i < Inputs.size(); i++)
@ -111,57 +113,59 @@ class InputCorpus {
Printf("\n");
}
bool AddFeature(size_t Idx, uint32_t NewSize, bool Shrink) {
assert(NewSize);
Idx = Idx % kFeatureSetSize;
uint32_t OldSize = GetFeature(Idx);
if (OldSize == 0 || (Shrink && OldSize > NewSize)) {
if (OldSize > 0) {
InputInfo &II = Inputs[SmallestElementPerFeature[Idx]];
assert(II.NumFeatures > 0);
II.NumFeatures--;
if (II.NumFeatures == 0) {
II.U.clear();
if (FeatureDebug)
Printf("EVICTED %zd\n", SmallestElementPerFeature[Idx]);
}
}
if (FeatureDebug)
Printf("ADD FEATURE %zd sz %d\n", Idx, NewSize);
SmallestElementPerFeature[Idx] = Inputs.size();
InputSizesPerFeature[Idx] = NewSize;
CountingFeatures = true;
return true;
}
return false;
}
size_t NumFeatures() const {
size_t Res = 0;
for (size_t i = 0; i < kFeatureSetSize; i++)
Res += GetFeature(i) != 0;
return Res;
}
private:
static const bool FeatureDebug = false;
static const size_t kFeatureSetSize = TracePC::kFeatureSetSize;
size_t GetFeature(size_t Idx) const { return InputSizesPerFeature[Idx]; }
void ValidateFeatureSet() {
for (size_t Idx = 0; Idx < kFeatureSetSize; Idx++) {
Feature &Fe = FeatureSet[Idx];
if(Fe.Count && Fe.SmallestElementSize)
Inputs[Fe.SmallestElementIdx].Tmp++;
}
if (!CountingFeatures) return;
if (FeatureDebug)
PrintFeatureSet();
for (size_t Idx = 0; Idx < kFeatureSetSize; Idx++)
if (GetFeature(Idx))
Inputs[SmallestElementPerFeature[Idx]].Tmp++;
for (auto &II: Inputs) {
if (II.Tmp != II.NumFeatures)
Printf("ZZZ %zd %zd\n", II.Tmp, II.NumFeatures);
assert(II.Tmp == II.NumFeatures);
II.Tmp = 0;
}
}
void UpdateFeatureSet(size_t CurrentElementIdx) {
auto &II = Inputs[CurrentElementIdx];
size_t Size = II.U.size();
if (!Size)
return;
bool Updated = false;
for (size_t Idx = 0; Idx < kFeatureSetSize; Idx++) {
if (!TPC.HasFeature(Idx))
continue;
Feature &Fe = FeatureSet[Idx];
Fe.Count++;
if (!Fe.SmallestElementSize ||
Fe.SmallestElementSize > Size) {
II.NumFeatures++;
CountingFeatures = true;
if (Fe.SmallestElementSize > Size) {
auto &OlderII = Inputs[Fe.SmallestElementIdx];
assert(OlderII.NumFeatures > 0);
OlderII.NumFeatures--;
if (!OlderII.NumFeatures) {
OlderII.U.clear(); // Will be never used again.
if (FeatureDebug)
Printf("EVICTED %zd\n", Fe.SmallestElementIdx);
}
}
Fe.SmallestElementIdx = CurrentElementIdx;
Fe.SmallestElementSize = Size;
Updated = true;
}
}
if (Updated && FeatureDebug) PrintFeatureSet();
ValidateFeatureSet();
}
// Updates the probability distribution for the units in the corpus.
// Must be called whenever the corpus or unit weights are changed.
void UpdateCorpusDistribution() {
@ -185,13 +189,9 @@ private:
std::unordered_set<std::string> Hashes;
std::vector<InputInfo> Inputs;
struct Feature {
size_t Count;
size_t SmallestElementIdx;
size_t SmallestElementSize;
};
bool CountingFeatures = false;
Feature FeatureSet[kFeatureSetSize];
uint32_t InputSizesPerFeature[kFeatureSetSize];
uint32_t SmallestElementPerFeature[kFeatureSetSize];
};
} // namespace fuzzer

View File

@ -342,7 +342,7 @@ int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) {
Unit U = FileToVector(InputFilePath);
assert(U.size() > 2);
Printf("INFO: Starting MinimizeCrashInputInternalStep: %zd\n", U.size());
Corpus->AddToCorpus(U);
Corpus->AddToCorpus(U, 0);
F->SetMaxInputLen(U.size());
F->SetMaxMutationLen(U.size() - 1);
F->Loop();

View File

@ -42,17 +42,13 @@ public:
CounterBitmapBits = 0;
CounterBitmap.clear();
VPMap.Reset();
TPCMap.Reset();
}
std::string DebugString() const;
size_t BlockCoverage;
size_t CallerCalleeCoverage;
// Precalculated number of bits in CounterBitmap.
size_t CounterBitmapBits;
std::vector<uint8_t> CounterBitmap;
ValueBitMap TPCMap;
ValueBitMap VPMap;
};
@ -80,7 +76,7 @@ public:
static void StaticInterruptCallback();
void ExecuteCallback(const uint8_t *Data, size_t Size);
bool RunOne(const uint8_t *Data, size_t Size);
size_t RunOne(const uint8_t *Data, size_t Size);
// Merge Corpora[1:] into Corpora[0].
void Merge(const std::vector<std::string> &Corpora);
@ -106,7 +102,7 @@ private:
void ReportNewCoverage(InputInfo *II, const Unit &U);
void PrintNewPCs();
void PrintOneNewPC(uintptr_t PC);
bool RunOne(const Unit &U) { return RunOne(U.data(), U.size()); }
size_t RunOne(const Unit &U) { return RunOne(U.data(), U.size()); }
void WriteToOutputCorpus(const Unit &U);
void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix);
void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0);

View File

@ -299,18 +299,18 @@ void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units) {
Printf("#%zd\t%s", TotalNumberOfRuns, Where);
if (MaxCoverage.BlockCoverage)
Printf(" cov: %zd", MaxCoverage.BlockCoverage);
if (size_t N = MaxCoverage.VPMap.GetNumBitsSinceLastMerge())
Printf(" vp: %zd", N);
if (size_t N = TPC.GetTotalPCCoverage())
Printf(" cov: %zd", N);
if (MaxCoverage.VPMap.GetNumBitsSinceLastMerge())
Printf(" vp: %zd", MaxCoverage.VPMap.GetNumBitsSinceLastMerge());
if (auto TB = MaxCoverage.CounterBitmapBits)
Printf(" bits: %zd", TB);
if (auto TB = MaxCoverage.TPCMap.GetNumBitsSinceLastMerge())
Printf(" bits: %zd", MaxCoverage.TPCMap.GetNumBitsSinceLastMerge());
if (size_t N = Corpus.NumFeatures())
Printf( " ft: %zd", N);
if (MaxCoverage.CallerCalleeCoverage)
Printf(" indir: %zd", MaxCoverage.CallerCalleeCoverage);
if (size_t N = Corpus.size()) {
Printf(" corpus: %zd", Corpus.NumActiveUnits());
Printf(" corp: %zd", Corpus.NumActiveUnits());
if (size_t N = Corpus.SizeInBytes()) {
if (N < (1<<14))
Printf("/%zdb", N);
@ -392,8 +392,8 @@ void Fuzzer::RereadOutputCorpus(size_t MaxSize) {
if (U.size() > MaxSize)
U.resize(MaxSize);
if (!Corpus.HasUnit(U)) {
if (RunOne(U)) {
Corpus.AddToCorpus(U);
if (size_t NumFeatures = RunOne(U)) {
Corpus.AddToCorpus(U, NumFeatures);
PrintStats("RELOAD");
}
}
@ -418,8 +418,8 @@ void Fuzzer::ShuffleAndMinimize(UnitVector *InitialCorpus) {
ExecuteCallback(&dummy, 0);
for (const auto &U : *InitialCorpus) {
if (RunOne(U)) {
Corpus.AddToCorpus(U);
if (size_t NumFeatures = RunOne(U)) {
Corpus.AddToCorpus(U, NumFeatures);
if (Options.Verbosity >= 2)
Printf("NEW0: %zd L %zd\n", MaxCoverage.BlockCoverage, U.size());
}
@ -434,27 +434,23 @@ void Fuzzer::ShuffleAndMinimize(UnitVector *InitialCorpus) {
}
}
bool Fuzzer::RunOne(const uint8_t *Data, size_t Size) {
size_t Fuzzer::RunOne(const uint8_t *Data, size_t Size) {
if (!Size) return 0;
TotalNumberOfRuns++;
ExecuteCallback(Data, Size);
bool Res = false;
if (TPC.FinalizeTrace(Size))
if (Options.Shrink)
Res = true;
if (!Res) {
if (TPC.UpdateCounterMap(&MaxCoverage.TPCMap))
Res = true;
size_t Res = 0;
if (size_t NumFeatures = TPC.FinalizeTrace(&Corpus, Size, Options.Shrink))
Res = NumFeatures;
if (!TPC.UsingTracePcGuard()) {
if (TPC.UpdateValueProfileMap(&MaxCoverage.VPMap))
Res = true;
Res = 1;
if (!Res && RecordMaxCoverage(&MaxCoverage))
Res = 1;
}
if (RecordMaxCoverage(&MaxCoverage))
Res = true;
CheckExitOnSrcPos();
auto TimeOfUnit =
duration_cast<seconds>(UnitStopTime - UnitStartTime).count();
@ -500,16 +496,6 @@ void Fuzzer::ExecuteCallback(const uint8_t *Data, size_t Size) {
delete[] DataCopy;
}
std::string Fuzzer::Coverage::DebugString() const {
std::string Result =
std::string("Coverage{") + "BlockCoverage=" +
std::to_string(BlockCoverage) + " CallerCalleeCoverage=" +
std::to_string(CallerCalleeCoverage) + " CounterBitmapBits=" +
std::to_string(CounterBitmapBits) + " VPMapBits " +
std::to_string(VPMap.GetNumBitsSinceLastMerge()) + "}";
return Result;
}
void Fuzzer::WriteToOutputCorpus(const Unit &U) {
if (Options.OnlyASCII)
assert(IsASCII(U));
@ -694,8 +680,9 @@ void Fuzzer::MutateAndTestOne() {
if (i == 0)
StartTraceRecording();
II.NumExecutedMutations++;
if (RunOne(CurrentUnitData, Size)) {
Corpus.AddToCorpus({CurrentUnitData, CurrentUnitData + Size});
if (size_t NumFeatures = RunOne(CurrentUnitData, Size)) {
Corpus.AddToCorpus({CurrentUnitData, CurrentUnitData + Size},
NumFeatures);
ReportNewCoverage(&II, {CurrentUnitData, CurrentUnitData + Size});
CheckExitOnItem();
}

View File

@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//
#include "FuzzerCorpus.h"
#include "FuzzerDefs.h"
#include "FuzzerTracePC.h"
#include "FuzzerValueBitMap.h"
@ -68,45 +69,46 @@ void TracePC::ResetGuards() {
assert(N == NumGuards);
}
bool TracePC::FinalizeTrace(size_t InputSize) {
bool Res = false;
if (TotalPCCoverage) {
const size_t Step = 8;
assert(reinterpret_cast<uintptr_t>(Counters) % Step == 0);
size_t N = Min(kNumCounters, NumGuards + 1);
N = (N + Step - 1) & ~(Step - 1); // Round up.
for (size_t Idx = 0; Idx < N; Idx += Step) {
uint64_t Bundle = *reinterpret_cast<uint64_t*>(&Counters[Idx]);
if (!Bundle) continue;
for (size_t i = Idx; i < Idx + Step; i++) {
uint8_t Counter = (Bundle >> (i * 8)) & 0xff;
if (!Counter) continue;
Counters[i] = 0;
unsigned Bit = 0;
/**/ if (Counter >= 128) Bit = 7;
else if (Counter >= 32) Bit = 6;
else if (Counter >= 16) Bit = 5;
else if (Counter >= 8) Bit = 4;
else if (Counter >= 4) Bit = 3;
else if (Counter >= 3) Bit = 2;
else if (Counter >= 2) Bit = 1;
size_t Feature = i * 8 + Bit;
CounterMap.AddValue(Feature);
uint32_t *SizePtr = &InputSizesPerFeature[Feature % kFeatureSetSize];
if (!*SizePtr || *SizePtr > InputSize) {
*SizePtr = InputSize;
Res = true;
}
}
size_t TracePC::FinalizeTrace(InputCorpus *C, size_t InputSize, bool Shrink) {
if (!UsingTracePcGuard()) return 0;
size_t Res = 0;
const size_t Step = 8;
assert(reinterpret_cast<uintptr_t>(Counters) % Step == 0);
size_t N = Min(kNumCounters, NumGuards + 1);
N = (N + Step - 1) & ~(Step - 1); // Round up.
for (size_t Idx = 0; Idx < N; Idx += Step) {
uint64_t Bundle = *reinterpret_cast<uint64_t*>(&Counters[Idx]);
if (!Bundle) continue;
for (size_t i = Idx; i < Idx + Step; i++) {
uint8_t Counter = (Bundle >> (i * 8)) & 0xff;
if (!Counter) continue;
Counters[i] = 0;
unsigned Bit = 0;
/**/ if (Counter >= 128) Bit = 7;
else if (Counter >= 32) Bit = 6;
else if (Counter >= 16) Bit = 5;
else if (Counter >= 8) Bit = 4;
else if (Counter >= 4) Bit = 3;
else if (Counter >= 3) Bit = 2;
else if (Counter >= 2) Bit = 1;
size_t Feature = (i * 8 + Bit);
if (C->AddFeature(Feature, InputSize, Shrink))
Res++;
}
}
if (UseValueProfile)
ValueProfileMap.ForEach([&](size_t Idx) {
if (C->AddFeature(NumGuards + Idx, InputSize, Shrink))
Res++;
});
return Res;
}
void TracePC::HandleCallerCallee(uintptr_t Caller, uintptr_t Callee) {
const uintptr_t kBits = 12;
const uintptr_t kMask = (1 << kBits) - 1;
CounterMap.AddValue((Caller & kMask) | ((Callee & kMask) << kBits));
uintptr_t Idx = (Caller & kMask) | ((Callee & kMask) << kBits);
HandleValueProfile(Idx);
}
void TracePC::PrintCoverage() {
@ -163,11 +165,9 @@ void TracePC::AddValueForStrcmp(void *caller_pc, const char *s1, const char *s2,
ATTRIBUTE_TARGET_POPCNT
static void AddValueForCmp(void *PCptr, uint64_t Arg1, uint64_t Arg2) {
if (Arg1 == Arg2)
return;
uintptr_t PC = reinterpret_cast<uintptr_t>(PCptr);
uint64_t ArgDistance = __builtin_popcountl(Arg1 ^ Arg2) - 1; // [0,63]
uintptr_t Idx = (PC & 4095) | (ArgDistance << 12);
uint64_t ArgDistance = __builtin_popcountl(Arg1 ^ Arg2) + 1; // [1,65]
uintptr_t Idx = ((PC & 4095) + 1) * ArgDistance;
TPC.HandleValueProfile(Idx);
}

View File

@ -29,13 +29,11 @@ class TracePC {
void ResetTotalPCCoverage() { TotalPCCoverage = 0; }
void SetUseCounters(bool UC) { UseCounters = UC; }
void SetUseValueProfile(bool VP) { UseValueProfile = VP; }
bool UpdateCounterMap(ValueBitMap *MaxCounterMap) {
return MaxCounterMap->MergeFrom(CounterMap);
}
size_t FinalizeTrace(InputCorpus *C, size_t InputSize, bool Shrink);
bool UpdateValueProfileMap(ValueBitMap *MaxValueProfileMap) {
return UseValueProfile && MaxValueProfileMap->MergeFrom(ValueProfileMap);
}
bool FinalizeTrace(size_t InputSize);
}
size_t GetNewPCIDs(uintptr_t **NewPCIDsPtr) {
*NewPCIDsPtr = NewPCIDs;
@ -46,7 +44,6 @@ class TracePC {
void ResetMaps() {
NumNewPCIDs = 0;
CounterMap.Reset();
ValueProfileMap.Reset();
memset(Counters, 0, sizeof(Counters));
}
@ -60,13 +57,13 @@ class TracePC {
void PrintCoverage();
bool HasFeature(size_t Idx) { return CounterMap.Get(Idx); }
void AddValueForMemcmp(void *caller_pc, const void *s1, const void *s2,
size_t n);
void AddValueForStrcmp(void *caller_pc, const char *s1, const char *s2,
size_t n);
bool UsingTracePcGuard() const {return NumModules; }
private:
bool UseCounters = false;
bool UseValueProfile = false;
@ -93,9 +90,7 @@ private:
static const size_t kNumPCs = 1 << 20;
uintptr_t PCs[kNumPCs];
ValueBitMap CounterMap;
ValueBitMap ValueProfileMap;
uint32_t InputSizesPerFeature[kFeatureSetSize];
};
extern TracePC TPC;

View File

@ -68,6 +68,15 @@ struct ValueBitMap {
return OldNumBits < NumBits;
}
template <class Callback>
void ForEach(Callback CB) {
for (size_t i = 0; i < kMapSizeInWords; i++)
if (uintptr_t M = Map[i])
for (size_t j = 0; j < sizeof(M) * 8; j++)
if (M & ((uintptr_t)1 << j))
CB(i * sizeof(M) * 8 + j);
}
private:
size_t NumBits = 0;
uintptr_t Map[kMapSizeInWords] __attribute__((aligned(512)));

View File

@ -583,7 +583,7 @@ TEST(Corpus, Distribution) {
size_t N = 10;
size_t TriesPerUnit = 1<<20;
for (size_t i = 0; i < N; i++)
C.AddToCorpus(Unit{ static_cast<uint8_t>(i) });
C.AddToCorpus(Unit{ static_cast<uint8_t>(i) }, 0);
std::vector<size_t> Hist(N);
for (size_t i = 0; i < N * TriesPerUnit; i++) {

View File

@ -21,7 +21,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
int Z = Ids[(unsigned char)'Z'];
if (F >= 0 && U > F && Z > U) {
Sink++;
// fprintf(stderr, "IDS: %d %d %d\n", F, U, Z);
//fprintf(stderr, "IDS: %d %d %d\n", F, U, Z);
}
return 0;
}

View File

@ -12,10 +12,11 @@ static volatile uint32_t Sink;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if (Size < sizeof(uint32_t)) return 0;
uint32_t X;
uint32_t X, Y;
size_t Offset = Size < 8 ? 0 : Size / 2;
memcpy(&X, Data + Offset, sizeof(uint32_t));
Sink = X == 0xAABBCCDD;
memcpy(&Y, "FUZZ", sizeof(uint32_t));
Sink = X == Y;
return 0;
}

View File

@ -27,13 +27,13 @@ NULL_DEREF_ON_EMPTY: stat::number_of_executed_units:
RUN: not LLVMFuzzer-CounterTest -use_counters=1 -max_len=6 -seed=1 -timeout=15 2>&1 | FileCheck %s --check-prefix=COUNTERS
RUN: not LLVMFuzzer-CounterTest-TracePC -use_counters=1 -max_len=6 -seed=1 -timeout=15 2>&1 | FileCheck %s --check-prefix=COUNTERS
COUNTERS: INITED {{.*}} bits:
COUNTERS: NEW {{.*}} bits: {{[1-9]*}}
COUNTERS: NEW {{.*}} bits: {{[1-9]*}}
COUNTERS: INITED {{.*}} {{bits:|ft:}}
COUNTERS: NEW {{.*}} {{bits:|ft:}} {{[1-9]*}}
COUNTERS: NEW {{.*}} {{bits:|ft:}} {{[1-9]*}}
COUNTERS: BINGO
RUN: not LLVMFuzzer-CallerCalleeTest -cross_over=0 -max_len=6 -seed=1 -max_total_time=15 2>&1 | FileCheck %s
RUN: not LLVMFuzzer-CallerCalleeTest-TracePC -cross_over=0 -max_len=6 -seed=1 -max_total_time=15 2>&1 | FileCheck %s
RUN: not LLVMFuzzer-CallerCalleeTest -use_value_profile=1 -cross_over=0 -max_len=6 -seed=1 -max_total_time=15 2>&1 | FileCheck %s
RUN: not LLVMFuzzer-CallerCalleeTest-TracePC -use_value_profile=1 -cross_over=0 -max_len=6 -seed=1 -max_total_time=15 2>&1 | FileCheck %s
# This one is flaky, may actually find the goal even w/o use_indir_calls.
# LLVMFuzzer-CallerCalleeTest -use_indir_calls=0 -cross_over=0 -max_len=6 -seed=1 -runs=1000000 2>&1 | FileCheck %s --check-prefix=Done1000000

View File

@ -0,0 +1,7 @@
RUN: LLVMFuzzer-ShrinkControlFlowTest-TracePC -seed=1 -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60 -runs=1000000 -shrink=1 2>&1 | FileCheck %s --check-prefix=SHRINK1
RUN: LLVMFuzzer-ShrinkControlFlowTest-TracePC -seed=1 -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60 -runs=1000000 -shrink=0 2>&1 | FileCheck %s --check-prefix=SHRINK0
RUN: LLVMFuzzer-ShrinkValueProfileTest-TracePC -seed=1 -exit_on_item=aea2e3923af219a8956f626558ef32f30a914ebc -runs=100000 -shrink=1 -use_value_profile=1 2>&1 | FileCheck %s --check-prefix=SHRINK1_VP
SHRINK0: Done 1000000 runs in
SHRINK1: INFO: found item with checksum '0eb8e4ed029b774d80f2b66408203801cb982a60', exiting.
SHRINK1_VP: INFO: found item with checksum 'aea2e3923af219a8956f626558ef32f30a914ebc', exiting