| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/symbols/build_id_index.h" |
| |
| #include <algorithm> |
| |
| #include "src/developer/debug/zxdb/common/string_util.h" |
| #include "src/lib/elflib/elflib.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "src/lib/fxl/strings/string_view.h" |
| #include "src/lib/fxl/strings/trim.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| std::optional<std::string> FindInRepoFolder(const std::string& build_id, |
| const std::filesystem::path& path, |
| DebugSymbolFileType file_type) { |
| if (build_id.size() <= 2) { |
| return std::nullopt; |
| } |
| |
| auto prefix = build_id.substr(0, 2); |
| auto tail = build_id.substr(2); |
| auto name = tail; |
| |
| if (file_type == DebugSymbolFileType::kDebugInfo) { |
| name += ".debug"; |
| } |
| |
| std::error_code ec; |
| auto direct = path / prefix / name; |
| if (std::filesystem::exists(direct, ec)) { |
| return direct; |
| } |
| |
| return std::nullopt; |
| } |
| |
| } // namespace |
| |
| BuildIDIndex::BuildIDIndex() = default; |
| BuildIDIndex::~BuildIDIndex() = default; |
| |
| std::string BuildIDIndex::FileForBuildID(const std::string& build_id, |
| DebugSymbolFileType file_type) { |
| EnsureCacheClean(); |
| |
| const std::string* to_find = &build_id; |
| |
| auto found = build_id_to_file_.find(*to_find); |
| if (found == build_id_to_file_.end()) |
| return SearchRepoSources(*to_find, file_type); |
| return found->second; |
| } |
| |
| std::string BuildIDIndex::SearchRepoSources(const std::string& build_id, |
| DebugSymbolFileType file_type) { |
| for (const auto& source : repo_sources_) { |
| const auto& path = std::filesystem::path(source) / ".build-id"; |
| |
| auto got = FindInRepoFolder(build_id, path, file_type); |
| if (got) { |
| return *got; |
| } |
| } |
| |
| return std::string(); |
| } |
| |
| void BuildIDIndex::AddBuildIDMapping(const std::string& build_id, |
| const std::string& file_name) { |
| // This map saves the manual mapping across cache updates. |
| manual_mappings_[build_id] = file_name; |
| |
| // Don't bother marking the cache dirty since we can just add it. |
| build_id_to_file_[build_id] = file_name; |
| } |
| |
| void BuildIDIndex::AddBuildIDMappingFile(const std::string& id_file_name) { |
| // If the file is already loaded, ignore it. |
| if (std::find(build_id_files_.begin(), build_id_files_.end(), id_file_name) != |
| build_id_files_.end()) |
| return; |
| |
| build_id_files_.emplace_back(id_file_name); |
| ClearCache(); |
| } |
| |
| void BuildIDIndex::AddSymbolSource(const std::string& path) { |
| // If the file is already loaded, ignore it. |
| if (std::find(sources_.begin(), sources_.end(), path) != sources_.end()) |
| return; |
| |
| sources_.emplace_back(path); |
| ClearCache(); |
| } |
| |
| void BuildIDIndex::AddRepoSymbolSource(const std::string& path) { |
| repo_sources_.emplace_back(path); |
| EnsureCacheClean(); |
| } |
| |
| BuildIDIndex::StatusList BuildIDIndex::GetStatus() { |
| EnsureCacheClean(); |
| return status_; |
| } |
| |
| void BuildIDIndex::ClearCache() { |
| build_id_to_file_.clear(); |
| status_.clear(); |
| cache_dirty_ = true; |
| } |
| |
| // static |
| int BuildIDIndex::ParseIDs(const std::string& input, |
| const std::filesystem::path& containing_dir, |
| IDMap* output) { |
| int added = 0; |
| for (size_t line_begin = 0; line_begin < input.size(); line_begin++) { |
| size_t newline = input.find('\n', line_begin); |
| if (newline == std::string::npos) |
| newline = input.size(); |
| |
| fxl::StringView line(&input[line_begin], newline - line_begin); |
| if (!line.empty()) { |
| // Format is <buildid> <space> <filename> |
| size_t first_space = line.find(' '); |
| if (first_space != std::string::npos && first_space > 0 && |
| first_space + 1 < line.size()) { |
| // There is a space and it separates two nonempty things. |
| fxl::StringView to_trim(" \t\r\n"); |
| fxl::StringView build_id = |
| fxl::TrimString(line.substr(0, first_space), to_trim); |
| fxl::StringView path_data = fxl::TrimString( |
| line.substr(first_space + 1, line.size() - first_space - 1), |
| to_trim); |
| |
| std::filesystem::path path(path_data.ToString()); |
| |
| if (path.is_relative()) { |
| path = containing_dir / path; |
| } |
| |
| added++; |
| output->emplace(std::piecewise_construct, |
| std::forward_as_tuple(build_id.data(), build_id.size()), |
| std::forward_as_tuple(path)); |
| } |
| } |
| |
| line_begin = newline; // The for loop will advance past this. |
| } |
| return added; |
| } |
| |
| void BuildIDIndex::LogMessage(const std::string& msg) const { |
| if (information_callback_) |
| information_callback_(msg); |
| } |
| |
| void BuildIDIndex::LoadOneBuildIDFile(const std::string& file_name) { |
| std::error_code err; |
| |
| auto path = std::filesystem::canonical(file_name, err); |
| |
| if (err) { |
| status_.emplace_back(file_name, 0); |
| LogMessage("Can't open build ID file: " + file_name); |
| return; |
| } |
| |
| auto containing_dir = path.parent_path(); |
| |
| FILE* id_file = fopen(file_name.c_str(), "r"); |
| if (!id_file) { |
| status_.emplace_back(file_name, 0); |
| LogMessage("Can't open build ID file: " + file_name); |
| return; |
| } |
| |
| fseek(id_file, 0, SEEK_END); |
| long length = ftell(id_file); |
| if (length <= 0) { |
| status_.emplace_back(file_name, 0); |
| LogMessage("Can't load build ID file: " + file_name); |
| return; |
| } |
| |
| std::string contents; |
| contents.resize(length); |
| |
| fseek(id_file, 0, SEEK_SET); |
| if (fread(&contents[0], 1, contents.size(), id_file) != |
| static_cast<size_t>(length)) { |
| status_.emplace_back(file_name, 0); |
| LogMessage("Can't read build ID file: " + file_name); |
| return; |
| } |
| |
| fclose(id_file); |
| |
| int added = ParseIDs(contents, containing_dir, &build_id_to_file_); |
| status_.emplace_back(file_name, added); |
| if (!added) |
| LogMessage("No mappings found in build ID file: " + file_name); |
| } |
| |
| void BuildIDIndex::IndexOneSourcePath(const std::string& path) { |
| std::error_code ec; |
| if (std::filesystem::is_directory(path, ec)) { |
| auto build_id_path = std::filesystem::path(path) / ".build-id"; |
| |
| if (std::filesystem::is_directory(build_id_path, ec)) { |
| repo_sources_.emplace_back(path); |
| status_.emplace_back(path, BuildIDIndex::kStatusIsFolder); |
| return; |
| } |
| |
| // Iterate through all files in this directory, but don't recurse. |
| int indexed = 0; |
| for (const auto& child : std::filesystem::directory_iterator(path, ec)) { |
| if (IndexOneSourceFile(child.path())) |
| indexed++; |
| } |
| |
| if (!ec) { |
| status_.emplace_back(path, indexed); |
| } |
| } else if (!ec) { |
| if (IndexOneSourceFile(path)) { |
| status_.emplace_back(path, 1); |
| } else { |
| status_.emplace_back(path, 0); |
| LogMessage(fxl::StringPrintf("Symbol file could not be loaded: %s", |
| path.c_str())); |
| } |
| } |
| } |
| |
| bool BuildIDIndex::IndexOneSourceFile(const std::string& file_path) { |
| auto elf = elflib::ElfLib::Create(file_path); |
| if (!elf) |
| return false; |
| |
| std::string build_id = elf->GetGNUBuildID(); |
| if (!build_id.empty()) { |
| build_id_to_file_[build_id] = file_path; |
| return true; |
| } |
| return false; |
| } |
| |
| void BuildIDIndex::EnsureCacheClean() { |
| if (!cache_dirty_) |
| return; |
| |
| for (const auto& build_id_file : build_id_files_) |
| LoadOneBuildIDFile(build_id_file); |
| |
| for (const auto& source : sources_) |
| IndexOneSourcePath(source); |
| |
| for (const auto& mapping : manual_mappings_) |
| build_id_to_file_.insert(mapping); |
| |
| for (const auto& path : repo_sources_) { |
| std::error_code ec; |
| auto buildid_path = std::filesystem::path(path) / ".build-id"; |
| |
| if (std::filesystem::is_directory(buildid_path, ec)) { |
| status_.emplace_back(path, BuildIDIndex::kStatusIsFolder); |
| } |
| } |
| |
| cache_dirty_ = false; |
| } |
| |
| } // namespace zxdb |