blob: e053a8029407d40f878f992632145183e2595584 [file] [log] [blame]
// Copyright 2022 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.
#include "./centipede/control_flow.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <filesystem> // NOLINT
#include <fstream>
#include <istream>
#include <iterator>
#include <ostream>
#include <queue>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "./centipede/command.h"
#include "./centipede/pc_info.h"
#include "./centipede/util.h"
#include "./common/defs.h"
#include "./common/logging.h"
#include "./common/remote_file.h"
namespace fuzztest::internal {
PCTable ReadPcTableFromFile(std::string_view file_path) {
ByteArray pc_infos_as_bytes;
ReadFromLocalFile(file_path, pc_infos_as_bytes);
FUZZTEST_CHECK_EQ(pc_infos_as_bytes.size() % sizeof(PCInfo), 0);
size_t pc_table_size = pc_infos_as_bytes.size() / sizeof(PCInfo);
const auto *pc_infos = reinterpret_cast<PCInfo *>(pc_infos_as_bytes.data());
PCTable pc_table{pc_infos, pc_infos + pc_table_size};
FUZZTEST_CHECK_EQ(pc_table.size(), pc_table_size);
return pc_table;
}
PCTable GetPcTableFromBinaryWithTracePC(std::string_view binary_path,
std::string_view objdump_path,
std::string_view tmp_path) {
const std::string stderr_path = absl::StrCat(tmp_path, ".log");
Command::Options cmd_options;
cmd_options.args = {"-d", std::string(binary_path)};
cmd_options.stdout_file = std::string(tmp_path);
cmd_options.stderr_file = stderr_path;
Command cmd{objdump_path, std::move(cmd_options)};
int exit_code = cmd.Execute();
if (exit_code != EXIT_SUCCESS) {
std::string log_text;
ReadFromLocalFile(stderr_path, log_text);
FUZZTEST_LOG(ERROR) << "Failed to use objdump to get PC table; stderr is:";
for (const auto &line : absl::StrSplit(log_text, '\n')) {
FUZZTEST_LOG(ERROR).NoPrefix() << line;
}
std::filesystem::remove(tmp_path);
std::filesystem::remove(stderr_path);
return {};
}
std::filesystem::remove(stderr_path);
PCTable pc_table;
std::ifstream in(std::string{tmp_path});
FUZZTEST_CHECK(in.good()) << VV(tmp_path);
bool saw_new_function = false;
// Read the objdump output, find lines that start a function
// and lines that have a call to __sanitizer_cov_trace_pc.
// Reconstruct the PCTable from those.
for (std::string line; std::getline(in, line);) {
if (absl::EndsWith(line, ">:")) { // new function.
saw_new_function = true;
continue;
}
// 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;
saw_new_function = false; // next trace_pc will be in the same function.
pc_table.push_back({pc, flags});
}
std::filesystem::remove(tmp_path);
return pc_table;
}
CFTable ReadCfTable(std::istream &in) {
const std::string input_string(std::istreambuf_iterator<char>(in), {});
const ByteArray cf_table_as_bytes(input_string.begin(), input_string.end());
FUZZTEST_CHECK_EQ(cf_table_as_bytes.size() % sizeof(CFTable::value_type), 0);
const size_t cf_table_size =
cf_table_as_bytes.size() / sizeof(CFTable::value_type);
const auto *cf_entries =
reinterpret_cast<const CFTable::value_type *>(cf_table_as_bytes.data());
return CFTable{cf_entries, cf_entries + cf_table_size};
}
CFTable ReadCfTable(std::string_view file_path) {
std::string cf_table_contents;
FUZZTEST_CHECK_OK(RemoteFileGetContents(file_path, cf_table_contents));
std::istringstream cf_table_stream(cf_table_contents);
return ReadCfTable(cf_table_stream);
}
void WriteCfTable(const CFTable &cf_table, std::ostream &out) {
out.write(reinterpret_cast<const char *>(cf_table.data()),
sizeof(CFTable::value_type) * cf_table.size());
}
DsoTable ReadDsoTableFromFile(std::string_view file_path) {
DsoTable result;
std::string data;
ReadFromLocalFile(file_path, data);
for (const auto &line : absl::StrSplit(data, '\n', absl::SkipEmpty())) {
// Use std::string; there is no std::stoul for std::string_view.
const std::vector<std::string> tokens =
absl::StrSplit(line, ' ', absl::SkipEmpty());
FUZZTEST_CHECK_EQ(tokens.size(), 2) << VV(line);
result.push_back(DsoInfo{tokens[0], std::stoul(tokens[1])});
}
return result;
}
void ControlFlowGraph::InitializeControlFlowGraph(const CFTable &cf_table,
const PCTable &pc_table) {
FUZZTEST_CHECK(!cf_table.empty());
func_entries_.resize(pc_table.size());
reachability_.resize(pc_table.size());
for (size_t j = 0; j < cf_table.size();) {
std::vector<uintptr_t> successors;
auto curr_pc = cf_table[j];
++j;
// Iterate over successors.
while (cf_table[j]) {
successors.push_back(cf_table[j]);
++j;
}
++j; // Step over the delimiter.
// Record the list of successors
graph_[curr_pc] = std::move(successors);
// TODO(ussuri): Remove after debugging.
FUZZTEST_VLOG(100) << "Added PC: " << curr_pc;
// Iterate over callees.
while (cf_table[j]) {
++j;
}
++j; // Step over the delimiter.
FUZZTEST_CHECK_LE(j, cf_table.size());
}
// Calculate cyclomatic complexity for all functions.
for (PCIndex i = 0; i < pc_table.size(); ++i) {
pc_index_map_[pc_table[i].pc] = i;
if (pc_table[i].has_flag(PCInfo::kFuncEntry)) {
func_entries_[i] = true;
uintptr_t func_pc = pc_table[i].pc;
auto func_comp = ComputeFunctionCyclomaticComplexity(func_pc, *this);
function_complexities_[func_pc] = func_comp;
}
}
}
const std::vector<uintptr_t> &ControlFlowGraph::GetSuccessors(
uintptr_t basic_block) const {
auto it = graph_.find(basic_block);
FUZZTEST_CHECK(it != graph_.end()) << VV(basic_block);
return it->second;
}
std::vector<uintptr_t> ControlFlowGraph::ComputeReachabilityForPc(
uintptr_t pc) const {
absl::flat_hash_set<uintptr_t> visited_pcs;
std::queue<uintptr_t> worklist;
worklist.push(pc);
while (!worklist.empty()) {
auto current_pc = worklist.front();
worklist.pop();
if (!visited_pcs.insert(current_pc).second) continue;
for (const auto &successor : graph_.at(current_pc)) {
if (!exists(successor)) continue;
worklist.push(successor);
}
}
return {visited_pcs.begin(), visited_pcs.end()};
}
uint32_t ComputeFunctionCyclomaticComplexity(uintptr_t pc,
const ControlFlowGraph &cfg) {
size_t edge_num = 0, node_num = 0;
absl::flat_hash_set<uintptr_t> visited_pcs;
std::queue<uintptr_t> worklist;
worklist.push(pc);
while (!worklist.empty()) {
auto current_pc = worklist.front();
worklist.pop();
if (!visited_pcs.insert(current_pc).second) continue;
++node_num;
for (auto &successor : cfg.GetSuccessors(current_pc)) {
if (!cfg.exists(successor)) continue;
++edge_num;
worklist.push(successor);
}
}
return edge_num - node_num + 2;
}
} // namespace fuzztest::internal