Skip to content

Commit

Permalink
#Centipede MacOS support 6/6: fix runner and runner/engine interaction.
Browse files Browse the repository at this point in the history
Unfortunlately it is hard to split the change further as our tests require the engine/runner to work as a whole.

PiperOrigin-RevId: 659581896
  • Loading branch information
xinhaoyuan committed Oct 1, 2024
1 parent 7b8958d commit 3ef60b8
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 131 deletions.
2 changes: 2 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ build:linux --copt=-mcrc32
build --define absl=1

build:macos --macos_minimum_os=10.15
# For compatible behavior on weak symbols.
build:macos --linkopt=-Wl,-undefined,dynamic_lookup

# To create this file, please run:
#
Expand Down
37 changes: 15 additions & 22 deletions .github/workflows/bazel_test_centipede.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,29 +107,22 @@ jobs:
- name: Run unit tests
if: ${{ !cancelled() }}
run: |
bazel test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli \
centipede:binary_info_test centipede:byte_array_mutator_test \
centipede:call_graph_test centipede:callstack_test \
centipede:centipede_binary_test centipede:concurrent_bitset_test \
centipede:command_test \
centipede:concurrent_byteset_test centipede:config_file_test \
centipede:config_util_test centipede:corpus_io_test \
centipede:corpus_test centipede:distill_test \
centipede:environment_test centipede:execution_metadata_test \
centipede:feature_set_test centipede:feature_test \
centipede:foreach_nonzero_test \
centipede:hashed_ring_buffer_test centipede:int_utils_test \
centipede:knobs_test centipede:mutation_input_test \
centipede:pc_info_test centipede:resource_pool_test \
centipede:reverse_pc_table_test centipede:rolling_hash_test \
centipede:runner_cmp_trace_test centipede:runner_flags_test \
centipede:runner_result_test centipede:rusage_stats_test \
centipede:seed_corpus_maker_lib_test \
centipede:seed_corpus_maker_proto_lib_test \
centipede:shared_memory_blob_sequence_test \
centipede:stats_test centipede:symbol_table_test \
centipede:util_test centipede:workdir_test && \
# Timed wait is delayed for unknown reasons in MacOS for a few testing
# environments, thus disabling a few related non-critical tests.
bazel test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli -- centipede:all -centipede:periodic_action_test -centipede:rusage_profiler_test && \
bazel test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli --test_filter='-*ValidateTimelapseSnapshots' -- centipede:rusage_profiler_test
- name: Run e2e tests without Riegeli
if: ${{ !cancelled() }}
run: |
bazel test --test_output=errors --no//fuzztest:use_riegeli centipede/testing:instrumentation_test centipede/testing:runner_test centipede/testing:multi_dso_test
- name: Run puzzles without Riegeli
if: ${{ !cancelled() }}
run: |
bazel test --test_output=errors --no//fuzztest:use_riegeli centipede/puzzles:all
- name: Run puzzles without Riegeli with ASAN
if: ${{ !cancelled() }}
run: |
bazel test --test_output=errors --no//fuzztest:use_riegeli --linkopt=-fsanitize=address --copt=-fsanitize=address --platform_suffix=asan centipede/puzzles:all
- name: Save new cache based on main
if: github.ref == 'refs/heads/main'
uses: actions/cache/save@v4
Expand Down
10 changes: 6 additions & 4 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1032,9 +1032,13 @@ RUNNER_SANITIZED_COPTS = [

RUNNER_LINKOPTS = [
"-ldl", # for dlsym
"-lrt", # for shm_open
"-lpthread", # for pthread_once
]
] + select({
"@platforms//os:macos": [],
"//conditions:default": [
"-lrt", # for shm_open
],
})

# WARNING: be careful with more deps here. Use only the most trivial ones.
RUNNER_DEPS = [
Expand Down Expand Up @@ -1200,8 +1204,6 @@ sh_library(
sh_library(
name = "test_util_sh",
srcs = ["test_util.sh"],
data = [
],
)

################################################################################
Expand Down
4 changes: 2 additions & 2 deletions centipede/centipede_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ class CentipedeCallbacks {
std::filesystem::path(temp_dir_).append("log");
std::string failure_description_path_ =
std::filesystem::path(temp_dir_).append("failure_description");
const std::string shmem_name1_ = ProcessAndThreadUniqueID("/centipede-shm1-");
const std::string shmem_name2_ = ProcessAndThreadUniqueID("/centipede-shm2-");
const std::string shmem_name1_ = ProcessAndThreadUniqueID("/ctpd-shm1-");
const std::string shmem_name2_ = ProcessAndThreadUniqueID("/ctpd-shm2-");

SharedMemoryBlobSequence inputs_blobseq_;
SharedMemoryBlobSequence outputs_blobseq_;
Expand Down
130 changes: 71 additions & 59 deletions centipede/centipede_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,23 +175,29 @@ TEST(Centipede, ReadFirstCorpusDir) {
env.require_pc_table = false; // No PC table here.
env.corpus_dir.push_back(corpus_dir.path());

// First, generate corpus files in corpus_dir.
CentipedeMock mock_1(env);
MockFactory factory_1(mock_1);
CentipedeMain(env, factory_1);
ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs.
ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs.
ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]),
512); // All 1-byte and 2-byte inputs.

// Second, run without fuzzing using the same corpus_dir.
env.workdir = workdir_2.path();
env.num_runs = 0;
CentipedeMock mock_2(env);
MockFactory factory_2(mock_2);
CentipedeMain(env, factory_2);
// Should observe all inputs in corpus_dir.
EXPECT_EQ(mock_2.num_inputs_, 512);
// Need to wrap each CentipedeMain in a scope to make sure the shmem is
// released before the next call. Otherwise it may fail in MacOS.
{
// First, generate corpus files in corpus_dir.
CentipedeMock mock_1(env);
MockFactory factory_1(mock_1);
CentipedeMain(env, factory_1);
ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs.
ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs.
ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]),
512); // All 1-byte and 2-byte inputs.
}

{
// Second, run without fuzzing using the same corpus_dir.
env.workdir = workdir_2.path();
env.num_runs = 0;
CentipedeMock mock_2(env);
MockFactory factory_2(mock_2);
CentipedeMain(env, factory_2);
// Should observe all inputs in corpus_dir.
EXPECT_EQ(mock_2.num_inputs_, 512);
}
}

TEST(Centipede, DoesNotReadFirstCorpusDirIfOutputOnly) {
Expand All @@ -206,24 +212,29 @@ TEST(Centipede, DoesNotReadFirstCorpusDirIfOutputOnly) {
env.require_pc_table = false; // No PC table here.
env.corpus_dir.push_back(corpus_dir.path());

// First, generate corpus files in corpus_dir.
CentipedeMock mock_1(env);
MockFactory factory_1(mock_1);
CentipedeMain(env, factory_1);
ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs.
ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs.
ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]),
512); // All 1-byte and 2-byte inputs.

// Second, run without fuzzing using the same corpus_dir, but as output-only.
env.workdir = workdir_2.path();
env.num_runs = 0;
env.first_corpus_dir_output_only = true;
CentipedeMock mock_2(env);
MockFactory factory_2(mock_2);
CentipedeMain(env, factory_2);
// Should observe no inputs other than the seed input {0}.
EXPECT_EQ(mock_2.num_inputs_, 1);
{
// First, generate corpus files in corpus_dir.
CentipedeMock mock_1(env);
MockFactory factory_1(mock_1);
CentipedeMain(env, factory_1);
ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs.
ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs.
ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]),
512); // All 1-byte and 2-byte inputs.
}

{
// Second, run without fuzzing using the same corpus_dir, but as
// output-only.
env.workdir = workdir_2.path();
env.num_runs = 0;
env.first_corpus_dir_output_only = true;
CentipedeMock mock_2(env);
MockFactory factory_2(mock_2);
CentipedeMain(env, factory_2);
// Should observe no inputs other than the seed input {0}.
EXPECT_EQ(mock_2.num_inputs_, 1);
}
}

TEST(Centipede, SkipsOutputIfFirstCorpusDirIsEmptyPath) {
Expand Down Expand Up @@ -834,31 +845,32 @@ TEST(Centipede, UndetectedCrashingInput) {
env.require_pc_table = false;
env.exit_on_crash = true;

UndetectedCrashingInputMock mock(env, kCrashingInputIdx);
MockFactory factory(mock);
CentipedeMain(env, factory);
{
UndetectedCrashingInputMock mock(env, kCrashingInputIdx);
MockFactory factory(mock);
CentipedeMain(env, factory);

// Verify that we see the expected inputs from the batch.
// The "crashes/unreliable_batch-<HASH>" dir must contain all inputs from the
// batch that were executing during the session.
// We simply verify the number of saved inputs matches the number of executed
// inputs.
const auto crashing_input_hash = Hash(mock.crashing_input());
const auto crashes_dir_path = std::filesystem::path(temp_dir.path())
.append("crashes")
.append("crashing_batch-")
.concat(crashing_input_hash);
EXPECT_TRUE(std::filesystem::exists(crashes_dir_path)) << crashes_dir_path;
std::vector<std::string> found_crash_file_names;
for (auto const &dir_ent :
std::filesystem::directory_iterator(crashes_dir_path)) {
found_crash_file_names.push_back(dir_ent.path().filename());
}
// TODO(ussuri): Verify exact names/contents of the files, not just count.
EXPECT_EQ(found_crash_file_names.size(), kCrashingInputIdxInBatch + 1);
// Suspected input first, then every input in the batch (including the
// suspected input again).
EXPECT_EQ(mock.num_inputs_triaged(), kBatchSize + 1);
// Verify that we see the expected inputs from the batch.
// The "crashes/unreliable_batch-<HASH>" dir must contain all inputs from
// the batch that were executing during the session. We simply verify the
// number of saved inputs matches the number of executed inputs.
const auto crashing_input_hash = Hash(mock.crashing_input());
const auto crashes_dir_path = std::filesystem::path(temp_dir.path())
.append("crashes")
.append("crashing_batch-")
.concat(crashing_input_hash);
EXPECT_TRUE(std::filesystem::exists(crashes_dir_path)) << crashes_dir_path;
std::vector<std::string> found_crash_file_names;
for (auto const &dir_ent :
std::filesystem::directory_iterator(crashes_dir_path)) {
found_crash_file_names.push_back(dir_ent.path().filename());
}
// TODO(ussuri): Verify exact names/contents of the files, not just count.
EXPECT_EQ(found_crash_file_names.size(), kCrashingInputIdxInBatch + 1);
// Suspected input first, then every input in the batch (including the
// suspected input again).
EXPECT_EQ(mock.num_inputs_triaged(), kBatchSize + 1);
}

// Verify that when `env.batch_triage_suspect_only` is set, only triage the
// suspect.
Expand Down
6 changes: 4 additions & 2 deletions centipede/control_flow.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ PCTable GetPcTableFromBinaryWithTracePC(std::string_view binary_path,
saw_new_function = true;
continue;
}
if (!absl::EndsWith(line, "<__sanitizer_cov_trace_pc>") &&
!absl::EndsWith(line, "<__sanitizer_cov_trace_pc@plt>"))
// On MacOS there is an extra underscope before the symbols, so not sealing
// the symbol with `<`.
if (!absl::EndsWith(line, "__sanitizer_cov_trace_pc>") &&
!absl::EndsWith(line, "__sanitizer_cov_trace_pc@plt>"))
continue;
uintptr_t pc = std::stoul(line, nullptr, 16);
uintptr_t flags = saw_new_function ? PCInfo::kFuncEntry : 0;
Expand Down
2 changes: 1 addition & 1 deletion centipede/control_flow_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ static void SymbolizeBinary(std::string_view test_dir,
has_llvm_fuzzer_test_one_input = true;
EXPECT_THAT(
symbols.location(i),
testing::HasSubstr("centipede/testing/test_fuzz_target.cc:70"));
testing::HasSubstr("centipede/testing/test_fuzz_target.cc:71"));
}
}
EXPECT_TRUE(has_llvm_fuzzer_test_one_input);
Expand Down
6 changes: 6 additions & 0 deletions centipede/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ struct Environment {
bool serialize_shard_loads = false;
size_t seed = 0;
size_t prune_frequency = 100;
#ifdef __APPLE__
// Address space limit is ignored on MacOS.
// Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=853873#c2
size_t address_space_limit_mb = 0;
#else // __APPLE__
size_t address_space_limit_mb = 8192;
#endif // __APPLE__
size_t rss_limit_mb = 4096;
size_t timeout_per_input = 60;
size_t timeout_per_batch = 0;
Expand Down
28 changes: 18 additions & 10 deletions centipede/runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
#include "./centipede/runner.h"

#include <pthread.h> // NOLINT: use pthread to avoid extra dependencies.
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
Expand Down Expand Up @@ -95,10 +93,6 @@ thread_local ThreadTerminationDetector termination_detector;

} // namespace

// Use of the fixed init priority allows to call CentipedeRunnerMain
// from constructor functions (CentipedeRunnerMain needs to run after
// state constructor).
// Note: it must run after ForkServerCallMeVeryEarly, see comment there.
GlobalRunnerState state __attribute__((init_priority(200)));
// We use __thread instead of thread_local so that the compiler warns if
// the initializer for `tls` is not a constant expression.
Expand Down Expand Up @@ -193,8 +187,14 @@ void ThreadLocalRunnerState::OnThreadStop() {
static size_t GetPeakRSSMb() {
struct rusage usage = {};
if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
#ifdef __APPLE__
// On MacOS, the unit seems to be byte according to experiment, while some
// documents mentioned KiB. This could depend on OS variants.
return usage.ru_maxrss >> 20;
#else // __APPLE__
// On Linux, ru_maxrss is in KiB
return usage.ru_maxrss >> 10;
#endif // __APPLE__
}

// Returns the current time in microseconds.
Expand Down Expand Up @@ -281,7 +281,8 @@ __attribute__((noinline)) void CheckStackLimit(uintptr_t sp) {
tls.top_frame_sp - sp > stack_limit) {
if (stack_limit_exceeded.test_and_set()) return;
fprintf(stderr,
"========= Stack limit exceeded: %" PRIuPTR " > %" PRIu64
"========= Stack limit exceeded: %" PRIuPTR
" > %zu"
" (byte); aborting\n",
tls.top_frame_sp - sp, stack_limit);
centipede::WriteFailureDescription(
Expand Down Expand Up @@ -951,13 +952,17 @@ static size_t GetVmSizeInBytes() {
// NOTE: Ignore any (unlikely) failures to suppress a compiler warning.
(void)fscanf(f, "%zd", &vm_size);
fclose(f);
return vm_size * getauxval(AT_PAGESZ); // proc gives VmSize in pages.
return vm_size * getpagesize(); // proc gives VmSize in pages.
}

// Sets RLIMIT_CORE, RLIMIT_AS
static void SetLimits() {
// no core files anywhere.
prctl(PR_SET_DUMPABLE, 0);
// Disable core dumping.
struct rlimit core_limits;
getrlimit(RLIMIT_CORE, &core_limits);
core_limits.rlim_cur = 0;
core_limits.rlim_max = 0;
setrlimit(RLIMIT_CORE, &core_limits);

// ASAN/TSAN/MSAN can not be used with RLIMIT_AS.
// We get the current VmSize, if it is greater than 1Tb, we assume we
Expand Down Expand Up @@ -1013,6 +1018,9 @@ extern void RunnerInterceptor();
&RunnerInterceptor;

GlobalRunnerState::GlobalRunnerState() {
// Make sure fork server is started if needed.
ForkServerCallMeVeryEarly();

// TODO(kcc): move some code from CentipedeRunnerMain() here so that it works
// even if CentipedeRunnerMain() is not called.
tls.OnThreadStart();
Expand Down
2 changes: 1 addition & 1 deletion centipede/runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct ThreadLocalRunnerState {

// Paths are thread-local, so we maintain the current bounded path here.
// We allow paths of up to 100, controlled at run-time via the "path_level".
static constexpr size_t kBoundedPathLength = 100;
static constexpr uint64_t kBoundedPathLength = 100;
HashedRingBuffer<kBoundedPathLength> path_ring_buffer;

// Value of SP in the top call frame of the thread, computed in OnThreadStart.
Expand Down
Loading

0 comments on commit 3ef60b8

Please sign in to comment.