No public description PiperOrigin-RevId: 933237643
diff --git a/centipede/BUILD b/centipede/BUILD index f16df04..37d2a1d 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -1074,6 +1074,7 @@ ":knobs", ":mutation_data", ":rolling_hash", + ":engine_abi", ":runner_cmp_trace", ":runner_fork_server", ":runner_request", @@ -1231,6 +1232,7 @@ deps = [ ":callstack", ":dispatcher_flag_helper", + ":engine_abi", ":execution_metadata", ":feature", ":foreach_nonzero",
diff --git a/centipede/engine_abi.h b/centipede/engine_abi.h index 793e707..6361665 100644 --- a/centipede/engine_abi.h +++ b/centipede/engine_abi.h
@@ -87,21 +87,32 @@ size_t size; } FuzzTestUint64sView; +// Constants for the layout of the coverage feature as a 64-bit unsigned +// integer: +// +// - Bits 63..59: 5-bit domain ID of the feature. Each domain is a +// logically independent feature namespace registered in +// `FuzzTestAdapter::SetUpCoverageDomains`. +// - Bits 58..32: 27-bit feature ID within the domain. +// - Bits 31..0: 32-bit counter value of the feature. +// +typedef enum { + kFuzzTestCoverageCounterStartBit = 0, + kFuzzTestCoverageCounterBitSize = 32, + kFuzzTestCoverageFeatureIdStartBit = 32, + kFuzzTestCoverageFeatureIdBitSize = 27, + kFuzzTestCoverageDomainIdStartBit = 59, + kFuzzTestCoverageDomainIdBitSize = 5, +} FuzzTestCoverageFeatureLayout; + // Sink for execution feedback. typedef struct FuzzTestFeedbackSinkCtx FuzzTestFeedbackSinkCtx; typedef struct { FuzzTestFeedbackSinkCtx* ctx; // Emits an array of coverage features captured from the execution - // inside `Execute` call. - // - // Each feature is a 64-bit unsigned integer with the following layout: - // - // - Bits 63..59: 5-bit domain ID of the feature. Each domain is a - // logically independent feature namespace registered in - // `FuzzTestAdapter::SetUpCoverageDomains`. - // - Bits 58..32: 27-bit feature ID within the domain. - // - Bits 31..0: 32-bit counter value of the feature. + // inside `Execute` call. See `FuzzTestCoverageFeatureLayout` for the feature + // layout. // // Multiple emissions are concatenated. void (*EmitCoverageFeatures)(FuzzTestFeedbackSinkCtx* ctx,
diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc index c647eda..19886f8 100644 --- a/centipede/engine_worker.cc +++ b/centipede/engine_worker.cc
@@ -542,13 +542,6 @@ } } -constexpr size_t kCounterStartBit = 0; -constexpr size_t kCounterBitSize = 32; -constexpr size_t kFeatureIdStartBit = 32; -constexpr size_t kFeatureIdBitSize = 27; -constexpr size_t kDomainIdStartBit = 59; -constexpr size_t kDomainIdBitSize = 5; - template <typename T> constexpr T Bits(T v, size_t begin, size_t size) { return (v >> begin) & ((T{1} << size) - 1); @@ -560,7 +553,8 @@ uint8_t feature_id_bit_size; uint8_t counter_bit_size; }; -std::array<CoverageDomainConfiguration, 1 << kDomainIdBitSize> coverage_domains; +std::array<CoverageDomainConfiguration, 1 << kFuzzTestCoverageDomainIdBitSize> + coverage_domains; void WorkerDoExecute(const FuzzTestAdapter& adapter) { auto* inputs_blobseq = GetInputsBlobSequence(); @@ -579,12 +573,15 @@ /*ctx=*/nullptr, /*Register=*/[](FuzzTestCoverageDomainRegistryCtx* ctx, const FuzzTestCoverageDomain* domain) { - WorkerCheck(domain->domain_id < (1 << kDomainIdBitSize), - "domain ID is too large"); - WorkerCheck(domain->feature_id_bit_size <= kFeatureIdBitSize, - "domain feature id bit size is too large"); - WorkerCheck(domain->counter_bit_size <= kCounterBitSize, - "domain counter bit size is too large"); + WorkerCheck( + domain->domain_id < (1 << kFuzzTestCoverageDomainIdBitSize), + "domain ID is too large"); + WorkerCheck( + domain->feature_id_bit_size <= kFuzzTestCoverageFeatureIdBitSize, + "domain feature id bit size is too large"); + WorkerCheck( + domain->counter_bit_size <= kFuzzTestCoverageCounterBitSize, + "domain counter bit size is too large"); WorkerCheck(!coverage_domains[domain->domain_id].registered, "domain ID is already registered"); coverage_domains[domain->domain_id].registered = true; @@ -651,14 +648,15 @@ // Convert to the Centipede feature layout with possible loss. for (auto& feature : features) { const uint64_t domain_id = - Bits(feature, kDomainIdStartBit, kDomainIdBitSize); + Bits(feature, kFuzzTestCoverageDomainIdStartBit, + kFuzzTestCoverageDomainIdBitSize); WorkerCheck(coverage_domains[domain_id].registered, "Emitted features in unregistered domain"); const auto& domain = coverage_domains[domain_id]; - uint64_t feature_id = - Bits(feature, kFeatureIdStartBit, domain.feature_id_bit_size); - uint64_t counter = - Bits(feature, kCounterStartBit, domain.counter_bit_size); + uint64_t feature_id = Bits(feature, kFuzzTestCoverageFeatureIdStartBit, + domain.feature_id_bit_size); + uint64_t counter = Bits(feature, kFuzzTestCoverageCounterStartBit, + domain.counter_bit_size); if (coverage_domains[domain_id].counter_bit_size > 0) { // Assume that `domain_id` is one of the scoring domains in Centipede, // which uses the lower 6 bits for the counter value. The conversion is
diff --git a/centipede/sancov_runtime.h b/centipede/sancov_runtime.h index ce3dbab..7368e8b 100644 --- a/centipede/sancov_runtime.h +++ b/centipede/sancov_runtime.h
@@ -23,6 +23,8 @@ #include <stddef.h> #include <stdint.h> +#include "./centipede/engine_abi.h" + #ifdef __cplusplus extern "C" { #endif @@ -48,8 +50,28 @@ // the process. // // If `reject_input==true`, then it will simply empty the feature array. +// +// DEPRECATED: the features here is in the raw Centipede layout instead of +// the standard engine layout. Use `SanCovRuntimePostProcessCoverage` + +// `SanCovRuntimeEmitFeatures` to work with the engine. struct SanCovRuntimeRawFeatureParts SanCovRuntimeGetCoverage(bool reject_input); +// Registers the SanCov coverage domains. Returns next available domain ID. +size_t SanCovRuntimeSetUpCoverageDomains( + const FuzzTestCoverageDomainRegistry* registry); + +// Post-processes all coverage data, and saves it internally for later emission +// and retrival. +// +// If `reject_input==true`, no coverage would be saved. +void SanCovRuntimePostProcessCoverage(bool reject_input); + +// Emits post-processed SanCov coverage features into the engine `sink`. +// +// Should be called at most once after `SanCovPostProcessCoverage` - later calls +// will emit empty coverage. +void SanCovRuntimeEmitFeatures(const FuzzTestFeedbackSink* sink); + #ifdef __cplusplus } // extern "C" #endif
diff --git a/centipede/sancov_state.cc b/centipede/sancov_state.cc index 0718616..1bda588 100644 --- a/centipede/sancov_state.cc +++ b/centipede/sancov_state.cc
@@ -25,6 +25,7 @@ #include "absl/base/nullability.h" #include "./centipede/dispatcher_flag_helper.h" +#include "./centipede/engine_abi.h" #include "./centipede/execution_metadata.h" #include "./centipede/feature.h" #include "./centipede/int_utils.h" @@ -512,6 +513,41 @@ } } +// Left-shifts the first `size` bits of `value` by `shift_by`. +template <typename T> +constexpr T GetShiftedBits(T value, size_t size, size_t shift_by) { + return (value & ((T{1} << size) - 1)) << shift_by; +} + +void SanCovRuntimeConvertToEngineFeatures(feature_t* start, size_t size) { + using ::fuzztest::internal::feature_domains::CMPScoreFeatureIndex; + using ::fuzztest::internal::feature_domains::Domain; + using ::fuzztest::internal::feature_domains::IsComparisonScoreFeature; + using ::fuzztest::internal::feature_domains::kCMPScoreBitmask; + using ::fuzztest::internal::feature_domains::kCMPScoreBits; + + for (size_t i = 0; i < size; ++i) { + auto& feature = start[i]; + const auto domain_id = Domain::FeatureToDomainId(feature); + auto feature_id = Domain::FeatureToIndexInDomain(feature); + if (IsComparisonScoreFeature(feature)) { + const auto counter = feature_id & kCMPScoreBitmask; + feature_id >>= kCMPScoreBits; + feature = GetShiftedBits(domain_id, kFuzzTestCoverageDomainIdBitSize, + kFuzzTestCoverageDomainIdStartBit) | + GetShiftedBits(feature_id, kFuzzTestCoverageFeatureIdBitSize, + kFuzzTestCoverageFeatureIdStartBit) | + GetShiftedBits(counter, kFuzzTestCoverageCounterBitSize, + kFuzzTestCoverageCounterStartBit); + continue; + } + feature = GetShiftedBits(domain_id, kFuzzTestCoverageDomainIdBitSize, + kFuzzTestCoverageDomainIdStartBit) | + GetShiftedBits(feature_id, kFuzzTestCoverageFeatureIdBitSize, + kFuzzTestCoverageFeatureIdStartBit); + } +} + SanCovRuntimeRawFeatureParts SanCovRuntimeGetFeatures() { return {fuzztest::internal::sancov_state->g_features.data(), fuzztest::internal::sancov_state->g_features.size()}; @@ -541,3 +577,56 @@ return fuzztest::internal::SanCovRuntimeGetFeatures(); } + +size_t SanCovRuntimeSetUpCoverageDomains( + const FuzzTestCoverageDomainRegistry* registry) { + using ::fuzztest::internal::feature_domains::Domain; + using ::fuzztest::internal::feature_domains::kCMPScoreBits; + using ::fuzztest::internal::feature_domains::kCMPScoreDomains; + using ::fuzztest::internal::feature_domains::kNumDomains; + + static_assert(Domain::kDomainSize == 1 << 27); + static_assert(kNumDomains <= 100); + static_assert(kCMPScoreBits <= 27); + static_assert(kCMPScoreBits <= kFuzzTestCoverageCounterBitSize); + + for (size_t domain_id = 0; domain_id < kNumDomains; ++domain_id) { + const bool is_scoring_domain = + domain_id >= kCMPScoreDomains.front().domain_id() && + domain_id <= kCMPScoreDomains.back().domain_id(); + std::string domain_name = "dom"; + domain_name += '0' + domain_id / 10; + domain_name += '0' + domain_id % 10; + const FuzzTestCoverageDomain domain = { + static_cast<uint8_t>(domain_id), + FuzzTestBytesView{ + reinterpret_cast<const uint8_t*>(domain_name.data()), + domain_name.size(), + }, + /*feature_id_bit_size=*/ + static_cast<uint8_t>(is_scoring_domain ? 27 - kCMPScoreBits : 27), + /*counter_bit_size=*/ + static_cast<uint8_t>(is_scoring_domain ? kCMPScoreBits : 0), + }; + registry->Register(registry->ctx, &domain); + } + + return kNumDomains; +} + +void SanCovRuntimePostProcessCoverage(bool reject_input) { + fuzztest::internal::PostProcessSancov(reject_input); +} + +void SanCovRuntimeEmitFeatures(const FuzzTestFeedbackSink* sink) { + using ::fuzztest::internal::sancov_state; + + fuzztest::internal::SanCovRuntimeConvertToEngineFeatures( + sancov_state->g_features.data(), sancov_state->g_features.size()); + const FuzzTestUint64sView features = { + sancov_state->g_features.data(), + sancov_state->g_features.size(), + }; + sink->EmitCoverageFeatures(sink->ctx, &features); + sancov_state->g_features.clear(); +}
diff --git a/centipede/sancov_state.h b/centipede/sancov_state.h index 5ff8d27..bde30e1 100644 --- a/centipede/sancov_state.h +++ b/centipede/sancov_state.h
@@ -313,6 +313,10 @@ // Gets the execution metadata gathered in `PostProcessSancov`. const ExecutionMetadata& SanCovRuntimeGetExecutionMetadata(); +// Convert Centipede features in `{start, start + size}` to the engine feature +// layout as in `engine_abi.h`. +void SanCovRuntimeConvertToEngineFeatures(feature_t* start, size_t size); + // Check for stack limit for `stack_usage`, with `is_current_stack` set if it // is for the current calling stack. __attribute__((weak)) void CheckStackLimit(size_t stack_usage,