blob: 0834e3b2f23b794b793e84bd87ee47ea5eeb2f74 [file] [log] [blame]
// 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.
// Instrumentation callbacks for SanitizerCoverage (sancov).
// https://clang.llvm.org/docs/SanitizerCoverage.html
#include "./centipede/sancov_object_array.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <vector>
#include "absl/base/nullability.h"
#include "./centipede/foreach_nonzero.h"
#include "./centipede/pc_info.h"
#include "./centipede/runner_dl_info.h"
#include "./centipede/runner_utils.h"
namespace fuzztest::internal {
void SanCovObjectArray::PCGuardInit(PCGuard *absl_nullable start,
PCGuard *stop) {
RunnerCheck((start != nullptr) == (stop != nullptr),
"invalid PC guard table");
skipping_no_code_dso_ = start == stop;
if (skipping_no_code_dso_) return;
// Ignore repeated calls with the same arguments.
if (size_ != 0 && objects_[size_ - 1].pc_guard_start == start) return;
RunnerCheck(size_ < kMaxSize, "too many sancov objects");
auto &sancov_object = objects_[size_++];
sancov_object.pc_guard_start = start;
sancov_object.pc_guard_stop = stop;
for (PCGuard *guard = start; guard != stop; ++guard) {
guard->pc_index = num_instrumented_pcs_;
++num_instrumented_pcs_;
}
}
void SanCovObjectArray::Inline8BitCountersInit(
uint8_t *inline_8bit_counters_start, uint8_t *inline_8bit_counters_stop) {
RunnerCheck((inline_8bit_counters_start != nullptr) ==
(inline_8bit_counters_stop != nullptr),
"invalid 8-bit counter table");
skipping_no_code_dso_ =
inline_8bit_counters_start == inline_8bit_counters_stop;
if (skipping_no_code_dso_) return;
// Ignore repeated calls with the same arguments.
if (size_ != 0 && objects_[size_ - 1].inline_8bit_counters_start ==
inline_8bit_counters_start) {
return;
}
RunnerCheck(size_ < kMaxSize, "too many sancov objects");
auto &sancov_object = objects_[size_++];
sancov_object.inline_8bit_counters_start = inline_8bit_counters_start;
sancov_object.inline_8bit_counters_stop = inline_8bit_counters_stop;
}
void SanCovObjectArray::PCInfoInit(const PCInfo *absl_nullable pcs_beg,
const PCInfo *pcs_end) {
RunnerCheck((pcs_beg != nullptr) == (pcs_end != nullptr), "invalid PC table");
if (skipping_no_code_dso_) {
RunnerCheck(pcs_beg == pcs_end,
"unexpected non-empty PC table for no-code DSO");
return;
}
const char *called_early =
"__sanitizer_cov_pcs_init is called before either of "
"__sanitizer_cov_trace_pc_guard_init or "
"__sanitizer_cov_8bit_counters_init";
RunnerCheck(size_ != 0, called_early);
// Assumes either __sanitizer_cov_trace_pc_guard_init or
// sanitizer_cov_8bit_counters_init was already called on this object.
auto &sancov_object = objects_[size_ - 1];
const size_t guard_size =
sancov_object.pc_guard_stop - sancov_object.pc_guard_start;
const size_t counter_size = sancov_object.inline_8bit_counters_stop -
sancov_object.inline_8bit_counters_start;
RunnerCheck(guard_size != 0 || counter_size != 0, called_early);
RunnerCheck(std::max(guard_size, counter_size) == pcs_end - pcs_beg,
"__sanitizer_cov_pcs_init: mismatch between guard/counter size"
" and pc table size");
sancov_object.pcs_beg = pcs_beg;
sancov_object.pcs_end = pcs_end;
sancov_object.dl_info = GetDlInfo(pcs_beg->pc);
RunnerCheck(sancov_object.dl_info.IsSet(), "failed to compute dl_info");
if (sancov_object.pc_guard_start != nullptr) {
// Set is_function_entry for all the guards.
for (size_t i = 0, n = pcs_end - pcs_beg; i < n; ++i) {
sancov_object.pc_guard_start[i].is_function_entry =
pcs_beg[i].has_flag(PCInfo::kFuncEntry);
}
}
}
void SanCovObjectArray::CFSInit(const uintptr_t *cfs_beg,
const uintptr_t *cfs_end) {
RunnerCheck((cfs_beg != nullptr) == (cfs_end != nullptr),
"invalid control-flow table");
if (skipping_no_code_dso_) {
RunnerCheck(cfs_beg == cfs_end,
"unexpected non-empty control-flow table for no-code DSO");
return;
}
// Assumes __sanitizer_cov_pcs_init has been called.
const char *called_early =
"__sanitizer_cov_cfs_init is called before __sanitizer_cov_pcs_init";
RunnerCheck(size_ != 0, called_early);
auto &sancov_object = objects_[size_ - 1];
RunnerCheck(sancov_object.pcs_beg != nullptr, called_early);
sancov_object.cfs_beg = cfs_beg;
sancov_object.cfs_end = cfs_end;
}
std::vector<PCInfo> SanCovObjectArray::CreatePCTable() const {
// Populate the result.
std::vector<PCInfo> result;
for (size_t i = 0; i < size(); ++i) {
const auto &object = objects_[i];
for (const auto *ptr = object.pcs_beg; ptr != object.pcs_end; ++ptr) {
auto pc_info = *ptr;
// Convert into the link-time address
pc_info.pc -= object.dl_info.link_offset;
result.push_back(pc_info);
}
}
return result;
}
std::vector<uintptr_t> SanCovObjectArray::CreateCfTable() const {
// Compute the CF table.
std::vector<uintptr_t> result;
for (size_t i = 0; i < size(); ++i) {
const auto &object = objects_[i];
for (const auto *ptr = object.cfs_beg; ptr != object.cfs_end; ++ptr) {
uintptr_t data = *ptr;
// CF table is an array of PCs, except for delimiter (Null) and indirect
// call indicator (-1). Convert into link-time address.
if (data != 0 && data != -1ULL) data -= object.dl_info.link_offset;
result.push_back(data);
}
}
return result;
}
DsoTable SanCovObjectArray::CreateDsoTable() const {
DsoTable result;
result.reserve(size());
for (size_t i = 0; i < size(); ++i) {
const auto &object = objects_[i];
size_t num_instrumented_pcs = object.pcs_end - object.pcs_beg;
result.push_back({object.dl_info.path, num_instrumented_pcs});
}
return result;
}
void SanCovObjectArray::ClearInlineCounters() {
for (size_t i = 0; i < size(); ++i) {
const auto &object = objects_[i];
if (object.inline_8bit_counters_start == nullptr) continue;
const size_t num_counters =
object.inline_8bit_counters_stop - object.inline_8bit_counters_start;
memset(object.inline_8bit_counters_start, 0, num_counters);
}
}
void SanCovObjectArray::ForEachNonZeroInlineCounter(
const std::function<void(size_t idx, uint8_t counter_value)> &callback)
const {
size_t process_wide_idx = 0;
for (size_t i = 0; i < size(); ++i) {
const auto &object = objects_[i];
if (object.inline_8bit_counters_start == nullptr) continue;
const size_t num_counters =
object.inline_8bit_counters_stop - object.inline_8bit_counters_start;
ForEachNonZeroByte(object.inline_8bit_counters_start, num_counters,
[&](size_t idx, uint8_t counter_value) {
callback(idx + process_wide_idx, counter_value);
});
process_wide_idx += num_counters;
}
}
} // namespace fuzztest::internal