blob: 21e29a0c0735bf9d148d372c263965d99d109cca [file] [log] [blame]
// 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 <filesystem>
#include <fstream>
#include <memory>
#include <set>
#include <string>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include "lib/syslog/cpp/macros.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/lib/elflib/elflib.h"
#include "src/lib/files/glob.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/fxl/strings/trim.h"
namespace zxdb {
BuildIDIndex::Entry BuildIDIndex::EntryForBuildID(const std::string& build_id) {
EnsureCacheClean();
if (build_id_to_files_.find(build_id) == build_id_to_files_.end())
SearchBuildIdDirs(build_id);
// No matter whether SearchBuildIdDirs found the symbol or not, build_id_to_files_[build_id] will
// always create the entry so next time no search will be performed.
return build_id_to_files_[build_id];
}
void BuildIDIndex::SearchBuildIdDirs(const std::string& build_id) {
if (build_id.size() <= 2) {
return;
}
auto path = build_id.substr(0, 2) + "/" + build_id.substr(2);
for (const auto& build_id_dir : build_id_dirs_) {
// There are potentially two files, one with just the build ID, one with a ".debug" suffix. The
// ".debug" suffix one is supposed to contain either just the DWARF symbols, or the full
// unstripped binary. The plain one is supposed to be either a stripped or unstripped binary.
//
// Since we're looking for DWARF information, look in the ".debug" one first.
IndexSourceFile(build_id_dir.path + "/" + path + ".debug", build_id_dir.build_dir);
IndexSourceFile(build_id_dir.path + "/" + path, build_id_dir.build_dir);
}
}
void BuildIDIndex::AddBuildIDMappingForTest(const std::string& build_id,
const std::string& file_name) {
// This map saves the manual mapping across cache updates.
manual_mappings_[build_id].debug_info = file_name;
manual_mappings_[build_id].binary = file_name;
// Don't bother marking the cache dirty since we can just add it.
build_id_to_files_[build_id].debug_info = file_name;
build_id_to_files_[build_id].binary = file_name;
}
void BuildIDIndex::ClearAll() {
ids_txts_.clear();
build_id_dirs_.clear();
sources_.clear();
ClearCache();
}
bool BuildIDIndex::AddOneFile(const std::string& file_name) {
return IndexSourceFile(file_name, "", true);
}
void BuildIDIndex::AddIdsTxt(const std::string& ids_txt, const std::string& build_dir) {
// If the file is already loaded, ignore it.
if (std::find_if(ids_txts_.begin(), ids_txts_.end(),
[&ids_txt](const auto& it) { return it.path == ids_txt; }) != ids_txts_.end())
return;
ids_txts_.push_back({ids_txt, build_dir});
ClearCache();
}
void BuildIDIndex::AddBuildIdDir(const std::string& dir, const std::string& build_dir) {
if (std::find_if(build_id_dirs_.begin(), build_id_dirs_.end(),
[&dir](const auto& it) { return it.path == dir; }) != build_id_dirs_.end())
return;
build_id_dirs_.push_back({dir, build_dir});
ClearCache();
}
void BuildIDIndex::AddSymbolServer(const std::string& url, bool require_authentication) {
if (std::find_if(symbol_servers_.begin(), symbol_servers_.end(),
[&url](const auto& it) { return it.url == url; }) != symbol_servers_.end())
return;
symbol_servers_.push_back({url, require_authentication});
}
void BuildIDIndex::SetCacheDir(const std::string& cache_dir) {
AddBuildIdDir(cache_dir);
cache_dir_ = std::make_unique<CacheDir>(cache_dir);
}
void BuildIDIndex::AddSymbolIndexFile(const std::string& path) {
if (StringEndsWith(path, ".json")) {
LoadSymbolIndexFileJSON(path);
} else {
LoadSymbolIndexFilePlain(path);
}
}
void BuildIDIndex::AddPlainFileOrDir(const std::string& path) {
if (std::find(sources_.begin(), sources_.end(), path) != sources_.end())
return;
sources_.push_back(path);
ClearCache();
}
BuildIDIndex::StatusList BuildIDIndex::GetStatus() {
EnsureCacheClean();
return status_;
}
void BuildIDIndex::ClearCache() { cache_dirty_ = true; }
// static
int BuildIDIndex::ParseIDs(const std::string& input, const std::filesystem::path& containing_dir,
const std::string& build_dir, BuildIDMap* 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();
std::string_view 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.
std::string_view to_trim(" \t\r\n");
std::string_view build_id = fxl::TrimString(line.substr(0, first_space), to_trim);
std::string_view path_data =
fxl::TrimString(line.substr(first_space + 1, line.size() - first_space - 1), to_trim);
std::filesystem::path path(path_data);
if (path.is_relative()) {
path = containing_dir / path;
}
BuildIDIndex::Entry entry;
// Assume the file contains both debug info and program bits.
entry.debug_info = path;
entry.binary = path;
entry.build_dir = build_dir;
added++;
output->emplace(std::piecewise_construct,
std::forward_as_tuple(build_id.data(), build_id.size()),
std::forward_as_tuple(entry));
}
}
line_begin = newline; // The for loop will advance past this.
}
return added;
}
void BuildIDIndex::LoadIdsTxt(const IdsTxt& ids_txt) {
std::error_code err;
auto path = std::filesystem::canonical(ids_txt.path, err);
if (err) {
status_.emplace_back(ids_txt.path, 0);
LOGS(Warn) << "Can't open build ID file: " << ids_txt.path;
return;
}
auto containing_dir = path.parent_path();
FILE* id_file = fopen(ids_txt.path.c_str(), "r");
if (!id_file) {
status_.emplace_back(ids_txt.path, 0);
LOGS(Warn) << "Can't open build ID file: " << ids_txt.path;
return;
}
fseek(id_file, 0, SEEK_END);
long length = ftell(id_file);
if (length <= 0) {
status_.emplace_back(ids_txt.path, 0);
LOGS(Warn) << "Can't load build ID file: " << ids_txt.path;
return;
}
std::string contents;
contents.resize(length);
fseek(id_file, 0, SEEK_SET);
if (fread(contents.data(), 1, contents.size(), id_file) != static_cast<size_t>(length)) {
status_.emplace_back(ids_txt.path, 0);
LOGS(Warn) << "Can't read build ID file: " << ids_txt.path;
return;
}
fclose(id_file);
int added = ParseIDs(contents, containing_dir, ids_txt.build_dir, &build_id_to_files_);
status_.emplace_back(ids_txt.path, added);
if (!added)
LOGS(Warn) << "No mappings found in build ID file: " << ids_txt.path;
}
void BuildIDIndex::LoadSymbolIndexFilePlain(const std::string& file_name) {
std::ifstream file(file_name);
if (file.fail()) {
LOGS(Warn) << "Cannot read symbol-index file: " << file_name;
return;
}
while (!file.eof()) {
std::string line;
std::string symbol_path;
std::string build_dir;
std::getline(file, line);
if (file.fail()) {
// If the file ends with \n, we will get failbit, eofbit and line == "".
if (file.eof())
break;
LOGS(Warn) << "Error reading " << file_name;
return;
}
if (auto tab_index = line.find('\t'); tab_index != std::string::npos) {
symbol_path = line.substr(0, tab_index);
build_dir = line.substr(tab_index + 1);
} else {
symbol_path = line;
build_dir.clear();
}
// Both paths must be absolute.
if (symbol_path.empty() || symbol_path[0] != '/' ||
(!build_dir.empty() && build_dir[0] != '/')) {
LOGS(Warn) << "Invalid line in " << file_name << ": " << line.c_str();
continue;
}
std::error_code ec;
if (std::filesystem::is_directory(symbol_path, ec)) {
AddBuildIdDir(symbol_path, build_dir);
} else if (std::filesystem::exists(symbol_path, ec)) {
AddIdsTxt(symbol_path, build_dir);
}
}
}
void BuildIDIndex::LoadSymbolIndexFileJSON(const std::string& file_name) {
std::vector<std::string> files_to_load{file_name};
std::set<std::string> visited;
while (!files_to_load.empty()) {
auto file_name = std::move(files_to_load.back());
files_to_load.pop_back();
// Avoid recursive includes.
if (visited.find(file_name) != visited.end()) {
continue;
}
visited.insert(file_name);
std::ifstream file(file_name);
if (!file) {
LOGS(Warn) << "Can't open " << file_name;
return;
}
rapidjson::IStreamWrapper input_stream(file);
rapidjson::Document document;
document.ParseStream(input_stream);
if (document.HasParseError() || !document.IsObject()) {
LOGS(Warn) << file_name << " is not a valid symbol-index.json";
return;
}
auto resolve_path = [base = std::filesystem::path(file_name).parent_path()](const char* path) {
// "/abc/def/..".lexically_normal() => "/abc/", while we want "/abc"
auto res = (base / path).lexically_normal();
if (!res.has_filename()) {
res = res.parent_path();
}
return res;
};
if (document.HasMember("includes") && document["includes"].IsArray()) {
for (auto& value : document["includes"].GetArray()) {
if (value.IsString() && strlen(value.GetString())) {
for (auto path : files::Glob(resolve_path(value.GetString()))) {
files_to_load.push_back(path);
}
}
}
}
if (document.HasMember("build_id_dirs") && document["build_id_dirs"].IsArray()) {
for (auto& value : document["build_id_dirs"].GetArray()) {
if (value.IsObject() && value.HasMember("path") && value["path"].IsString() &&
strlen(value["path"].GetString())) {
std::string build_dir;
if (value.HasMember("build_dir") && value["build_dir"].IsString()) {
build_dir = resolve_path(value["build_dir"].GetString());
}
for (auto path : files::Glob(resolve_path(value["path"].GetString()))) {
AddBuildIdDir(path, build_dir);
}
}
}
}
if (document.HasMember("ids_txts") && document["ids_txts"].IsArray()) {
for (auto& value : document["ids_txts"].GetArray()) {
if (value.IsObject() && value.HasMember("path") && value["path"].IsString() &&
strlen(value["path"].GetString())) {
std::string build_dir;
if (value.HasMember("build_dir") && value["build_dir"].IsString()) {
build_dir = resolve_path(value["build_dir"].GetString());
}
for (auto path : files::Glob(resolve_path(value["path"].GetString()))) {
AddIdsTxt(path, build_dir);
}
}
}
}
if (document.HasMember("gcs_flat") && document["gcs_flat"].IsArray()) {
for (auto& value : document["gcs_flat"].GetArray()) {
if (value.IsObject() && value.HasMember("url") && value["url"].IsString() &&
strlen(value["url"].GetString())) {
bool require_authentication = false;
if (value.HasMember("require_authentication") &&
value["require_authentication"].IsBool()) {
require_authentication = value["require_authentication"].GetBool();
}
AddSymbolServer(value["url"].GetString(), require_authentication);
}
}
}
}
}
void BuildIDIndex::IndexSourcePath(const std::string& path) {
std::error_code ec;
if (std::filesystem::is_directory(path, ec)) {
// 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 (IndexSourceFile(child.path()))
indexed++;
}
status_.emplace_back(path, indexed);
} else if (!ec && IndexSourceFile(path)) {
status_.emplace_back(path, 1);
} else {
status_.emplace_back(path, 0);
LOGS(Warn) << "Symbol file could not be loaded: " << path;
}
}
bool BuildIDIndex::IndexSourceFile(const std::string& file_path, const std::string& build_dir,
bool preserve) {
auto elf = elflib::ElfLib::Create(file_path);
if (!elf)
return false;
std::string build_id = elf->GetGNUBuildID();
if (build_id.empty())
return false;
if (cache_dir_)
cache_dir_->NotifyFileAccess(file_path);
auto ret = false;
if (elf->ProbeHasDebugInfo() && build_id_to_files_[build_id].debug_info.empty()) {
build_id_to_files_[build_id].debug_info = file_path;
ret = true;
}
if (elf->ProbeHasProgramBits() && build_id_to_files_[build_id].binary.empty()) {
build_id_to_files_[build_id].binary = file_path;
ret = true;
}
if (ret && !build_dir.empty()) {
build_id_to_files_[build_id].build_dir = build_dir;
}
if (ret && preserve) {
manual_mappings_[build_id] = build_id_to_files_[build_id];
}
return ret;
}
void BuildIDIndex::EnsureCacheClean() {
if (!cache_dirty_)
return;
status_.clear();
build_id_to_files_ = manual_mappings_;
for (const auto& source : sources_)
IndexSourcePath(source);
for (const auto& ids_txt : ids_txts_)
LoadIdsTxt(ids_txt);
for (const auto& build_id_dir : build_id_dirs_)
status_.emplace_back(build_id_dir.path, BuildIDIndex::kStatusIsFolder);
cache_dirty_ = false;
}
} // namespace zxdb