| // 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. | |
| #include "./centipede/binary_info.h" | |
| #include <cstdlib> | |
| #include <filesystem> // NOLINT | |
| #include <sstream> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_split.h" | |
| #include "./centipede/command.h" | |
| #include "./centipede/control_flow.h" | |
| #include "./centipede/pc_info.h" | |
| #include "./centipede/util.h" | |
| #include "./common/logging.h" | |
| #include "./common/remote_file.h" | |
| namespace fuzztest::internal { | |
| namespace { | |
| constexpr std::string_view kSymbolTableFileName = "symbol-table"; | |
| constexpr std::string_view kPCTableFileName = "pc-table"; | |
| constexpr std::string_view kCfTableFileName = "cf-table"; | |
| } // namespace | |
| void BinaryInfo::InitializeFromSanCovBinary( | |
| std::string_view binary_path_with_args, std::vector<std::string> env_diff, | |
| std::string_view objdump_path, std::string_view symbolizer_path, | |
| std::string_view tmp_dir_path) { | |
| if (binary_path_with_args.empty()) { | |
| // This usually happens in tests. | |
| FUZZTEST_LOG(INFO) << __func__ << ": binary_path_with_args is empty"; | |
| return; | |
| } | |
| // Compute names for temp files. | |
| const std::filesystem::path tmp_dir = tmp_dir_path; | |
| FUZZTEST_CHECK(std::filesystem::exists(tmp_dir) && | |
| std::filesystem::is_directory(tmp_dir)); | |
| ScopedFile pc_table_path(tmp_dir_path, "pc_table_tmp"); | |
| ScopedFile cf_table_path(tmp_dir_path, "cf_table_tmp"); | |
| ScopedFile dso_table_path(tmp_dir_path, "dso_table_tmp"); | |
| const auto log_prefix = | |
| std::filesystem::path{tmp_dir_path} / "binary_info_log_tmp"; | |
| FUZZTEST_LOG(INFO) << __func__ << ": tmp_dir: " << tmp_dir; | |
| env_diff.push_back( | |
| absl::StrCat("CENTIPEDE_RUNNER_FLAGS=:dump_binary_info:arg1=", | |
| EscapeEngineFlag(pc_table_path.path()), | |
| ":arg2=", EscapeEngineFlag(cf_table_path.path()), | |
| ":arg3=", EscapeEngineFlag(dso_table_path.path()), ":")); | |
| Command::Options cmd_options; | |
| cmd_options.env_diff = std::move(env_diff); | |
| cmd_options.stdout_file_prefix = log_prefix; | |
| Command cmd{binary_path_with_args, std::move(cmd_options)}; | |
| int exit_code = cmd.Execute(); | |
| if (exit_code != EXIT_SUCCESS) { | |
| FUZZTEST_LOG(INFO) << __func__ << ": exit_code: " << exit_code; | |
| } | |
| // Load PC Table. | |
| pc_table = ReadPcTableFromFile(pc_table_path.path()); | |
| // Load CF Table. | |
| if (std::filesystem::exists(cf_table_path.path())) | |
| cf_table = ReadCfTable(cf_table_path.path()); | |
| // Load the DSO Table. | |
| dso_table = ReadDsoTableFromFile(dso_table_path.path()); | |
| if (pc_table.empty()) { | |
| FUZZTEST_CHECK(dso_table.empty()); | |
| // Fallback to GetPcTableFromBinaryWithTracePC(). | |
| FUZZTEST_LOG(WARNING) | |
| << "Failed to dump PC table directly from binary using linked-in " | |
| "runner; see target execution logs above; falling back to legacy PC " | |
| "table extraction using trace-pc and objdump"; | |
| pc_table = GetPcTableFromBinaryWithTracePC( | |
| binary_path_with_args, objdump_path, pc_table_path.path()); | |
| if (pc_table.empty()) { | |
| FUZZTEST_LOG(ERROR) | |
| << "Failed to extract PC table from binary using objdump; see " | |
| "objdump execution logs above"; | |
| } | |
| // For the legacy trace-pc instrumentation, set the dso_table | |
| // to 1-element array consisting of the binary name | |
| const std::vector<std::string> args = | |
| absl::StrSplit(binary_path_with_args, absl::ByAnyChar{" \t\n"}, | |
| absl::SkipWhitespace{}); | |
| FUZZTEST_CHECK(!args.empty()); | |
| dso_table.push_back({args[0], pc_table.size()}); | |
| uses_legacy_trace_pc_instrumentation = true; | |
| } else { | |
| uses_legacy_trace_pc_instrumentation = false; | |
| } | |
| if (!uses_legacy_trace_pc_instrumentation) { | |
| // The number of instrumented PCs in the DSO table should match pc_table. | |
| size_t num_instrumened_pcs_in_all_dsos = 0; | |
| for (const auto& dso : dso_table) { | |
| num_instrumened_pcs_in_all_dsos += dso.num_instrumented_pcs; | |
| } | |
| FUZZTEST_CHECK_EQ(num_instrumened_pcs_in_all_dsos, pc_table.size()); | |
| } | |
| // Load symbols, if there is a PC table. | |
| if (!pc_table.empty()) { | |
| ScopedFile sym_tmp1_path(tmp_dir_path, "symbols_tmp1"); | |
| ScopedFile sym_tmp2_path(tmp_dir_path, "symbols_tmp2"); | |
| symbols.GetSymbolsFromBinary(pc_table, dso_table, symbolizer_path, | |
| tmp_dir_path); | |
| } | |
| } | |
| void BinaryInfo::Read(std::string_view dir) { | |
| std::string symbol_table_contents; | |
| // TODO(b/295978603): move calculation of paths into WorkDir class. | |
| FUZZTEST_CHECK_OK(RemoteFileGetContents( | |
| (std::filesystem::path(dir) / kSymbolTableFileName).c_str(), | |
| symbol_table_contents)); | |
| std::istringstream symbol_table_stream(symbol_table_contents); | |
| symbols.ReadFromLLVMSymbolizer(symbol_table_stream); | |
| std::string pc_table_contents; | |
| FUZZTEST_CHECK_OK(RemoteFileGetContents( | |
| (std::filesystem::path(dir) / kPCTableFileName).c_str(), | |
| pc_table_contents)); | |
| std::istringstream pc_table_stream(pc_table_contents); | |
| pc_table = ReadPcTable(pc_table_stream); | |
| cf_table = | |
| ReadCfTable((std::filesystem::path(dir) / kCfTableFileName).c_str()); | |
| } | |
| void BinaryInfo::Write(std::string_view dir) { | |
| std::ostringstream symbol_table_stream; | |
| symbols.WriteToLLVMSymbolizer(symbol_table_stream); | |
| // TODO(b/295978603): move calculation of paths into WorkDir class. | |
| FUZZTEST_CHECK_OK(RemoteFileSetContents( | |
| (std::filesystem::path(dir) / kSymbolTableFileName).c_str(), | |
| symbol_table_stream.str())); | |
| std::ostringstream pc_table_stream; | |
| WritePcTable(pc_table, pc_table_stream); | |
| FUZZTEST_CHECK_OK(RemoteFileSetContents( | |
| (std::filesystem::path(dir) / kPCTableFileName).c_str(), | |
| pc_table_stream.str())); | |
| std::ostringstream cf_table_stream; | |
| WriteCfTable(cf_table, cf_table_stream); | |
| FUZZTEST_CHECK_OK(RemoteFileSetContents( | |
| (std::filesystem::path(dir) / kCfTableFileName).c_str(), | |
| cf_table_stream.str())); | |
| } | |
| } // namespace fuzztest::internal |