diff --git a/centipede/BUILD b/centipede/BUILD index 7aa5030a..6835c2ed 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -380,10 +380,10 @@ cc_library( hdrs = ["minimize_crash.h"], deps = [ ":centipede_callbacks", - ":early_exit", ":environment", ":mutation_input", ":runner_result", + ":stop", ":thread_pool", ":util", ":workdir", @@ -619,7 +619,7 @@ cc_library( hdrs = ["command.h"], visibility = EXTENDED_API_VISIBILITY, deps = [ - ":early_exit", + ":stop", ":util", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/log", @@ -711,9 +711,9 @@ cc_library( ) cc_library( - name = "early_exit", - srcs = ["early_exit.cc"], - hdrs = ["early_exit.h"], + name = "stop", + srcs = ["stop.cc"], + hdrs = ["stop.h"], linkopts = select({ "@platforms//os:macos": [], "//conditions:default": [ @@ -721,6 +721,7 @@ cc_library( ], }), visibility = PUBLIC_API_VISIBILITY, + deps = ["@com_google_absl//absl/time"], ) cc_library( @@ -761,7 +762,6 @@ cc_library( ":corpus", ":corpus_io", ":coverage", - ":early_exit", ":environment", ":feature", ":feature_set", @@ -771,6 +771,7 @@ cc_library( ":rusage_profiler", ":rusage_stats", ":stats", + ":stop", ":symbol_table", ":util", ":workdir", @@ -810,7 +811,6 @@ cc_library( ":command", ":coverage", ":distill", - ":early_exit", ":environment", ":minimize_crash", ":pc_info", @@ -818,6 +818,7 @@ cc_library( ":runner_result", ":seed_corpus_maker_lib", ":stats", + ":stop", ":thread_pool", ":util", ":workdir", @@ -1615,7 +1616,7 @@ cc_test( data = [":command_test_helper"], deps = [ ":command", - ":early_exit", + ":stop", ":util", "@com_google_absl//absl/log", "@com_google_absl//absl/strings", diff --git a/centipede/centipede.cc b/centipede/centipede.cc index e5b5eb58..181343d5 100644 --- a/centipede/centipede.cc +++ b/centipede/centipede.cc @@ -80,7 +80,6 @@ #include "./centipede/control_flow.h" #include "./centipede/corpus_io.h" #include "./centipede/coverage.h" -#include "./centipede/early_exit.h" #include "./centipede/environment.h" #include "./centipede/feature.h" #include "./centipede/feature_set.h" @@ -89,6 +88,7 @@ #include "./centipede/rusage_profiler.h" #include "./centipede/rusage_stats.h" #include "./centipede/stats.h" +#include "./centipede/stop.h" #include "./centipede/util.h" #include "./centipede/workdir.h" #include "./common/blob_file.h" @@ -377,14 +377,14 @@ bool Centipede::RunBatch( } if (!success && env_.exit_on_crash) { LOG(INFO) << "--exit_on_crash is enabled; exiting soon"; - RequestEarlyExit(EXIT_FAILURE); + RequestEarlyStop(EXIT_FAILURE); return false; } CHECK_EQ(batch_result.results().size(), input_vec.size()); num_runs_ += input_vec.size(); bool batch_gained_new_coverage = false; for (size_t i = 0; i < input_vec.size(); i++) { - if (EarlyExitRequested()) break; + if (ShouldStop()) break; FeatureVec &fv = batch_result.results()[i].mutable_features(); bool function_filter_passed = function_filter_.filter(fv); bool input_gained_new_coverage = fs_.PruneFeaturesAndCountUnseen(fv) != 0; @@ -429,7 +429,7 @@ void Centipede::LoadShard(const Environment &load_env, size_t shard_index, std::vector inputs_to_rerun; auto input_features_callback = [&](ByteArray input, FeatureVec input_features) { - if (EarlyExitRequested()) return; + if (ShouldStop()) return; if (input_features.empty()) { if (rerun) { inputs_to_rerun.emplace_back(std::move(input)); @@ -498,7 +498,7 @@ void Centipede::Rerun(std::vector &to_rerun) { // Re-run all inputs for which we don't know their features. // Run in batches of at most env_.batch_size inputs each. while (!to_rerun.empty()) { - if (EarlyExitRequested()) break; + if (ShouldStop()) break; size_t batch_size = std::min(to_rerun.size(), env_.batch_size); std::vector batch(to_rerun.end() - batch_size, to_rerun.end()); to_rerun.resize(to_rerun.size() - batch_size); @@ -748,7 +748,7 @@ void Centipede::FuzzingLoop() { size_t new_runs = 0; size_t corpus_size_at_last_prune = corpus_.NumActive(); for (size_t batch_index = 0; batch_index < number_of_batches; batch_index++) { - if (EarlyExitRequested()) break; + if (ShouldStop()) break; CHECK_LT(new_runs, env_.num_runs); auto remaining_runs = env_.num_runs - new_runs; auto batch_size = std::min(env_.batch_size, remaining_runs); @@ -809,7 +809,7 @@ void Centipede::ReportCrash(std::string_view binary, const std::vector &input_vec, const BatchResult &batch_result) { CHECK_EQ(input_vec.size(), batch_result.results().size()); - if (EarlyExitRequested()) return; + if (ShouldStop()) return; if (++num_crashes_ > env_.max_num_crash_reports) return; @@ -867,7 +867,7 @@ void Centipede::ReportCrash(std::string_view binary, LOG(INFO) << log_prefix << "Executing inputs one-by-one, trying to find the reproducer"; for (auto input_idx : input_idxs_to_try) { - if (EarlyExitRequested()) return; + if (ShouldStop()) return; const auto &one_input = input_vec[input_idx]; BatchResult one_input_batch_result; if (!user_callbacks_.Execute(binary, {one_input}, one_input_batch_result)) { diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc index 3e0b7f22..ce360f24 100644 --- a/centipede/centipede_interface.cc +++ b/centipede/centipede_interface.cc @@ -51,7 +51,6 @@ #include "./centipede/command.h" #include "./centipede/coverage.h" #include "./centipede/distill.h" -#include "./centipede/early_exit.h" #include "./centipede/environment.h" #include "./centipede/minimize_crash.h" #include "./centipede/pc_info.h" @@ -59,6 +58,7 @@ #include "./centipede/runner_result.h" #include "./centipede/seed_corpus_maker_lib.h" #include "./centipede/stats.h" +#include "./centipede/stop.h" #include "./centipede/thread_pool.h" #include "./centipede/util.h" #include "./centipede/workdir.h" @@ -74,37 +74,20 @@ namespace centipede { namespace { -// Sets signal handler for SIGINT and SIGALRM. -void SetSignalHandlers(absl::Time stop_at) { - for (int signum : {SIGINT, SIGALRM}) { - struct sigaction sigact = {}; - sigact.sa_handler = [](int received_signum) { - if (received_signum == SIGINT) { - ABSL_RAW_LOG(INFO, "Ctrl-C pressed: winding down"); - RequestEarlyExit(EXIT_FAILURE); // => abnormal outcome - } else if (received_signum == SIGALRM) { - ABSL_RAW_LOG(INFO, "Reached --stop_at time: winding down"); - RequestEarlyExit(EXIT_SUCCESS); // => expected outcome - } else { - ABSL_UNREACHABLE(); - } - }; - sigaction(signum, &sigact, nullptr); - } - - if (stop_at != absl::InfiniteFuture()) { - const absl::Duration stop_in = stop_at - absl::Now(); - // Setting an alarm works only if the delay is longer than 1 second. - if (stop_in >= absl::Seconds(1)) { - LOG(INFO) << "Setting alarm for --stop_at time " << stop_at << " (in " - << stop_in << ")"; - PCHECK(alarm(absl::ToInt64Seconds(stop_in)) == 0) << "Alarm already set"; - } else { - LOG(WARNING) << "Already reached --stop_at time " << stop_at - << " upon starting: winding down immediately"; - RequestEarlyExit(EXIT_SUCCESS); // => expected outcome +// Sets signal handler for SIGINT. +// TODO(b/378532202): Replace this with a more generic mechanism that allows +// the called or `CentipedeMain()` to indicate when to stop. +void SetSignalHandlers() { + struct sigaction sigact = {}; + sigact.sa_handler = [](int received_signum) { + if (received_signum == SIGINT) { + LOG(INFO) << "Ctrl-C pressed: winding down"; + RequestEarlyStop(EXIT_FAILURE); + return; } - } + ABSL_UNREACHABLE(); + }; + sigaction(SIGINT, &sigact, nullptr); } // Runs env.for_each_blob on every blob extracted from env.args. @@ -135,7 +118,7 @@ int ForEachBlob(const Environment &env) { // If this flag gets active use, we may want to define special cases, // e.g. if for_each_blob=="cp %P /some/where" we can do it in-process. cmd.Execute(); - if (EarlyExitRequested()) return ExitCode(); + if (ShouldStop()) return ExitCode(); } } return EXIT_SUCCESS; @@ -577,8 +560,8 @@ int UpdateCorpusDatabaseForFuzzTests( LOG(INFO) << "Fuzzing " << fuzztest_config.fuzz_tests[i] << "\n\tTest binary: " << env.binary; - ClearEarlyExitRequest(); - alarm(absl::ToInt64Seconds(fuzztest_config.GetTimeLimitPerTest())); + ClearEarlyStopRequestAndSetStopTime(/*stop_time=*/absl::Now() + + fuzztest_config.GetTimeLimitPerTest()); Fuzz(env, binary_info, pcs_file_path, callbacks_factory); if (!stats_root_path.empty()) { const auto stats_dir = stats_root_path / fuzztest_config.fuzz_tests[i]; @@ -624,8 +607,8 @@ int UpdateCorpusDatabaseForFuzzTests( int CentipedeMain(const Environment &env, CentipedeCallbacksFactory &callbacks_factory) { - ClearEarlyExitRequest(); - SetSignalHandlers(env.stop_at); + ClearEarlyStopRequestAndSetStopTime(env.stop_at); + SetSignalHandlers(); if (!env.corpus_to_files.empty()) { Centipede::CorpusToFiles(env, env.corpus_to_files); diff --git a/centipede/command.cc b/centipede/command.cc index 7c34c41f..8b570ca0 100644 --- a/centipede/command.cc +++ b/centipede/command.cc @@ -51,7 +51,7 @@ #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "./centipede/early_exit.h" +#include "./centipede/stop.h" #include "./centipede/util.h" #include "./common/logging.h" @@ -399,7 +399,7 @@ int Command::Execute() { } else if (WIFSIGNALED(exit_code)) { const auto signal = WTERMSIG(exit_code); if (signal == SIGINT) { - RequestEarlyExit(EXIT_FAILURE); + RequestEarlyStop(EXIT_FAILURE); // When the user kills Centipede via ^C, they are unlikely to be // interested in any of the subprocesses' outputs. Also, ^C terminates all // the subprocesses, including all the runners, so all their outputs would diff --git a/centipede/command_test.cc b/centipede/command_test.cc index 1aa53b04..2578e0ad 100644 --- a/centipede/command_test.cc +++ b/centipede/command_test.cc @@ -26,7 +26,7 @@ #include "absl/log/log.h" #include "absl/strings/substitute.h" #include "absl/time/time.h" -#include "./centipede/early_exit.h" +#include "./centipede/stop.h" #include "./centipede/util.h" #include "./common/test_util.h" @@ -51,12 +51,12 @@ TEST(CommandTest, Execute) { // Check for default exit code. Command echo("echo"); EXPECT_EQ(echo.Execute(), 0); - EXPECT_FALSE(EarlyExitRequested()); + EXPECT_FALSE(ShouldStop()); // Check for exit code 7. Command exit7("bash -c 'exit 7'"); EXPECT_EQ(exit7.Execute(), 7); - EXPECT_FALSE(EarlyExitRequested()); + EXPECT_FALSE(ShouldStop()); } TEST(CommandDeathTest, Execute) { @@ -65,12 +65,12 @@ TEST(CommandDeathTest, Execute) { const auto self_sigint_lambda = []() { Command self_sigint("bash -c 'kill -SIGINT $$'"); self_sigint.Execute(); - if (EarlyExitRequested()) { - LOG(INFO) << "Early exit requested"; + if (ShouldStop()) { + LOG(INFO) << "Early stop requested"; exit(ExitCode()); } }; - EXPECT_DEATH(self_sigint_lambda(), "Early exit requested"); + EXPECT_DEATH(self_sigint_lambda(), "Early stop requested"); } TEST(CommandTest, InputFileWildCard) { diff --git a/centipede/early_exit.h b/centipede/early_exit.h deleted file mode 100644 index 1895fd9c..00000000 --- a/centipede/early_exit.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 The Centipede Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef THIRD_PARTY_CENTIPEDE_EARLY_EXIT_H_ -#define THIRD_PARTY_CENTIPEDE_EARLY_EXIT_H_ - -namespace centipede { - -// Requests that the process exits soon, with `exit_code`. -// Async-signal-safe. -void RequestEarlyExit(int exit_code); - -// Clears the request to exit early. -// -// Note: Typically it doesn't make much sense to concurrently both request early -// exit and clear the request, so the normal usage is to invoke this function -// before starting concurrent threads that invoke the other related functions. -// Otherwise, a specific problem that may arise is that this function clears the -// request between checking `EarlyExitRequested()` and obtaining `ExitCode()`. -void ClearEarlyExitRequest(); - -// Returns true iff `RequestEarlyExit()` was called since the most recent call -// to `ClearEarlyExitRequest()` (if any). -bool EarlyExitRequested(); - -// Returns the value most recently passed to `RequestEarlyExit()` or 0 if -// `RequestEarlyExit()` was not called since the most recent call to -// `ClearEarlyExitRequest()` (if any). -int ExitCode(); - -} // namespace centipede - -#endif diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc index 10207d83..54ca3b61 100644 --- a/centipede/minimize_crash.cc +++ b/centipede/minimize_crash.cc @@ -27,10 +27,10 @@ #include "absl/log/log.h" #include "absl/synchronization/mutex.h" #include "./centipede/centipede_callbacks.h" -#include "./centipede/early_exit.h" #include "./centipede/environment.h" #include "./centipede/mutation_input.h" #include "./centipede/runner_result.h" +#include "./centipede/stop.h" #include "./centipede/thread_pool.h" #include "./centipede/util.h" #include "./centipede/workdir.h" @@ -97,7 +97,7 @@ static void MinimizeCrash(const Environment &env, size_t num_batches = env.num_runs / env.batch_size; for (size_t i = 0; i < num_batches; ++i) { LOG_EVERY_POW_2(INFO) << "[" << i << "] Minimizing... Interrupt to stop"; - if (EarlyExitRequested()) break; + if (ShouldStop()) break; // Get up to kMaxNumCrashersToGet most recent crashers. We don't want just // the most recent crasher to avoid being stuck in local minimum. constexpr size_t kMaxNumCrashersToGet = 20; diff --git a/centipede/early_exit.cc b/centipede/stop.cc similarity index 54% rename from centipede/early_exit.cc rename to centipede/stop.cc index 88c6060b..6ab69f06 100644 --- a/centipede/early_exit.cc +++ b/centipede/stop.cc @@ -12,34 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "./centipede/early_exit.h" +#include "./centipede/stop.h" #include #include +#include "absl/time/clock.h" +#include "absl/time/time.h" + namespace centipede { namespace { -struct EarlyExit { +struct EarlyStop { int exit_code = EXIT_SUCCESS; bool is_requested = false; }; -std::atomic early_exit; +std::atomic early_stop; -} // namespace +absl::Time stop_time = absl::InfiniteFuture(); -void RequestEarlyExit(int exit_code) { - early_exit.store({exit_code, true}, std::memory_order_release); +bool EarlyStopRequested() { + return early_stop.load(std::memory_order_acquire).is_requested; } -void ClearEarlyExitRequest() { - early_exit.store({}, std::memory_order_release); +} // namespace + +void ClearEarlyStopRequestAndSetStopTime(absl::Time stop_time) { + early_stop.store({}, std::memory_order_release); + ::centipede::stop_time = stop_time; } -bool EarlyExitRequested() { - return early_exit.load(std::memory_order_acquire).is_requested; +void RequestEarlyStop(int exit_code) { + early_stop.store({exit_code, true}, std::memory_order_release); } -int ExitCode() { return early_exit.load(std::memory_order_acquire).exit_code; } +bool ShouldStop() { return EarlyStopRequested() || stop_time < absl::Now(); } + +int ExitCode() { return early_stop.load(std::memory_order_acquire).exit_code; } } // namespace centipede diff --git a/centipede/stop.h b/centipede/stop.h new file mode 100644 index 00000000..f1c09dc2 --- /dev/null +++ b/centipede/stop.h @@ -0,0 +1,52 @@ +// Copyright 2023 The Centipede Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CENTIPEDE_STOP_H_ +#define THIRD_PARTY_CENTIPEDE_STOP_H_ + +#include "absl/time/time.h" + +namespace centipede { + +// Clears the request to stop early and sets the stop time. +// +// REQUIRES: Must be called before starting concurrent threads that may invoke +// the functions defined in this header. In particular, calling this function +// concurrently with `ShouldStop()` is not thread-safe. +void ClearEarlyStopRequestAndSetStopTime(absl::Time stop_time); + +// Requests that Centipede soon stops whatever it is doing (fuzzing, minimizing +// reproducer, etc.), with `exit_code` indicating success (zero) or failure +// (non-zero). +// +// ENSURES: Thread-safe and safe to call from signal handlers. +void RequestEarlyStop(int exit_code); + +// Returns true iff it is time to stop, either because the stopping time has +// been reached or `RequestEarlyStop()` was called since the most recent call to +// `ClearEarlyStopRequestAndSetStopTime()` (if any). +// +// ENSURES: Thread-safe. +bool ShouldStop(); + +// Returns the value most recently passed to `RequestEarlyStop()` or 0 if +// `RequestEarlyStop()` was not called since the most recent call to +// `ClearEarlyStopRequestAndSetStopTime()` (if any). +// +// ENSURES: Thread-safe. +int ExitCode(); + +} // namespace centipede + +#endif // THIRD_PARTY_CENTIPEDE_STOP_H_ diff --git a/fuzztest/BUILD b/fuzztest/BUILD index c6f58888..45c2f2fa 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD @@ -229,11 +229,11 @@ cc_library( "@com_google_fuzztest//centipede:centipede_callbacks", "@com_google_fuzztest//centipede:centipede_interface", "@com_google_fuzztest//centipede:centipede_runner_no_main", - "@com_google_fuzztest//centipede:early_exit", "@com_google_fuzztest//centipede:environment", "@com_google_fuzztest//centipede:mutation_input", "@com_google_fuzztest//centipede:runner_result", "@com_google_fuzztest//centipede:shared_memory_blob_sequence", + "@com_google_fuzztest//centipede:stop", "@com_google_fuzztest//centipede:workdir", "@com_google_fuzztest//common:defs", ], diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index a962fe9f..292e450c 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc @@ -51,12 +51,12 @@ #include "absl/types/span.h" #include "./centipede/centipede_callbacks.h" #include "./centipede/centipede_interface.h" -#include "./centipede/early_exit.h" #include "./centipede/environment.h" #include "./centipede/mutation_input.h" #include "./centipede/runner_interface.h" #include "./centipede/runner_result.h" #include "./centipede/shared_memory_blob_sequence.h" +#include "./centipede/stop.h" #include "./centipede/workdir.h" #include "./common/defs.h" #include "./fuzztest/internal/any.h" @@ -352,9 +352,9 @@ class CentipedeAdaptorEngineCallbacks : public centipede::CentipedeCallbacks { buffer_offset); batch_result.Read(batch_result_blobseq); } - if (runtime_.termination_requested() && !centipede::EarlyExitRequested()) { + if (runtime_.termination_requested() && !centipede::ShouldStop()) { absl::FPrintF(GetStderr(), "[.] Early termination requested.\n"); - centipede::RequestEarlyExit(0); + centipede::RequestEarlyStop(0); } return true; } @@ -384,9 +384,9 @@ class CentipedeAdaptorEngineCallbacks : public centipede::CentipedeCallbacks { inputs, num_mutants, [&](centipede::ByteSpan mutant) { mutants.emplace_back(mutant.begin(), mutant.end()); }); - if (runtime_.termination_requested() && !centipede::EarlyExitRequested()) { + if (runtime_.termination_requested() && !centipede::ShouldStop()) { absl::FPrintF(GetStderr(), "[.] Early termination requested.\n"); - centipede::RequestEarlyExit(0); + centipede::RequestEarlyStop(0); } }