blob: c98f1bedefde4c03d9c8ac7c1155699427338964 [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/client/system.h"
#include <lib/syslog/cpp/log_settings.h>
#include <filesystem>
#include <set>
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/zxdb/client/breakpoint_impl.h"
#include "src/developer/debug/zxdb/client/download_observer.h"
#include "src/developer/debug/zxdb/client/exception_settings.h"
#include "src/developer/debug/zxdb/client/filter.h"
#include "src/developer/debug/zxdb/client/job.h"
#include "src/developer/debug/zxdb/client/process_impl.h"
#include "src/developer/debug/zxdb/client/remote_api.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/symbol_server.h"
#include "src/developer/debug/zxdb/client/system_observer.h"
#include "src/developer/debug/zxdb/client/target_impl.h"
#include "src/developer/debug/zxdb/client/thread_impl.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/expr/vector_register_format.h"
#include "src/developer/debug/zxdb/symbols/loaded_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/module_symbol_status.h"
#include "src/developer/debug/zxdb/symbols/module_symbols.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
// Schema definition -------------------------------------------------------------------------------
const char* ClientSettings::System::kAutoCastToDerived = "auto-cast-to-derived";
static const char* kAutoCastToDerivedDescription =
R"( Automatically cast pointers and references to the final derived class when
possible.
When a class has virtual members, zxdb can use the vtable information to
deduce the specific derived class for the object. This affects printing and
the resolution of class/struct members in expressions.)";
const char* ClientSettings::System::kDebugMode = "debug-mode";
static const char* kDebugModeDescription =
R"( Output debug information about zxdb.
In general should only be useful for people developing zxdb.)";
const char* ClientSettings::System::kAutoAttachLimbo = "auto-attach-limbo";
static const char* kAutoAttachLimboDescription =
R"( Automatically attach to processes found in Process Limbo.)";
const char* ClientSettings::System::kPauseOnLaunch = "pause-on-launch";
static const char* kPauseOnLaunchDescription =
R"( Whether a process launched through zxdb should be stopped on startup.
This will also affect components launched through zxdb.)";
const char* ClientSettings::System::kPauseOnAttach = "pause-on-attach";
static const char* kPauseOnAttachDescription =
R"( Whether the process should be paused when zxdb attached to it.
This will also affect when zxdb attached a process through a filter.)";
const char* ClientSettings::System::kShowFilePaths = "show-file-paths";
static const char* kShowFilePathsDescription =
R"( Displays full path information when file names are displayed. Otherwise
file names will be shortened to the shortest unique name in the current
process.)";
const char* ClientSettings::System::kShowStdout = "show-stdout";
static const char* kShowStdoutDescription =
R"( Whether newly debugged process (either launched or attached) should
output it's stdout/stderr to zxdb. This setting is global but can be overridden
by each individual process.)";
const char* ClientSettings::System::kQuitAgentOnExit = "quit-agent-on-exit";
static const char* kQuitAgentOnExitDescription =
R"( Whether the client will shutdown the connected agent upon exiting.")";
const char* ClientSettings::System::kLanguage = "language";
static const char* kLanguageDescription =
R"( Programming language for expressions given to commands such as print.
Valid values are "c++", "rust", and "auto". Most of the time you'll want to
set this to "auto" and let zxdb determine the language of the current unit.)";
const char* ClientSettings::System::kLanguage_Cpp = "c++";
const char* ClientSettings::System::kLanguage_Rust = "rust";
const char* ClientSettings::System::kLanguage_Auto = "auto";
const char* ClientSettings::System::kSecondChanceExceptions = "second-chance-exceptions";
static const char* kSecondChanceExceptionsDescription =
R"( List of exception types that should be handled as second-chance by
default; anything not in this list will be handled as first-chance. For
brevity two-to-three letter shorthands are used represent the types; valid
shorthands are:
"gen": general
"pf": page faults
"ui": undefined instruction
"ua": unaligned access
"pe": policy error)";
const char* ClientSettings::System::kSkipUnsymbolized = "skip-unsymbolized";
static const char* kSkipUnsymbolizedDescription =
R"( When true, the "step" command will automatically skip over unsymbolized
function calls. When false, it will stop.)";
// Symbol lookup.
const char* ClientSettings::System::kSymbolIndexFiles = "symbol-index-files";
static const char* kSymbolIndexFilesDescription =
R"( List of symbol-index files for symbol lookup. The content will be used
to populate the "ids-txts" and "build-id-dirs" settings. Check the
"symbol-index" host tool for more information.)";
const char* ClientSettings::System::kSymbolPaths = "symbol-paths";
static const char* kSymbolPathsDescription =
R"( List of ELF files or directories for symbol lookup. When a directory
path is passed, the directory will be enumerated non-recursively to index all
ELF files within. When a file is passed, it will be loaded as an ELF file.)";
const char* ClientSettings::System::kBuildIdDirs = "build-id-dirs";
static const char* kBuildIdDirsDescription =
R"( List of ".build-id" directories for symbol lookup. Each directory is assumed to
contain a ".build-id"-style index of symbol files, that is, each symbol file
lives at xx/yyyyyyyy.debug where xx is the first two characters of the build
ID and yyyyyyyy is the rest. However, the name of the directory doesn't need
to be .build-id.)";
const char* ClientSettings::System::kIdsTxts = "ids-txts";
static const char* kIdsTxtsDescription =
R"( List of "ids.txt" files for symbol lookup. Each file, typically named
"ids.txt", serves as a mapping from build ID to symbol file path and should
contain multiple lines in the format of "<build ID> <file path>".)";
const char* ClientSettings::System::kSymbolServers = "symbol-servers";
static const char* kSymbolServersDescription = R"( List of symbol server URLs.)";
const char* ClientSettings::System::kSymbolCache = "symbol-cache";
static const char* kSymbolCacheDescription =
R"( Directory where we can keep a symbol cache. If a symbol server has been
specified, downloaded symbols will be stored in this directory. The directory
structure will be the same as a .build-id directory.)";
const char* ClientSettings::System::kEnableAnalytics = "enable-analytics";
static const char* kEnableAnalyticsDescription = R"( Whether collection of analytics is enabled.)";
namespace {
fxl::RefPtr<SettingSchema> CreateSchema() {
auto schema = fxl::MakeRefCounted<SettingSchema>();
schema->AddBool(ClientSettings::System::kAutoCastToDerived, kAutoCastToDerivedDescription, true);
schema->AddBool(ClientSettings::System::kDebugMode, kDebugModeDescription, false);
schema->AddBool(ClientSettings::System::kAutoAttachLimbo, kAutoAttachLimboDescription, true);
schema->AddBool(ClientSettings::System::kPauseOnLaunch, kPauseOnLaunchDescription, false);
schema->AddBool(ClientSettings::System::kPauseOnAttach, kPauseOnAttachDescription, false);
schema->AddBool(ClientSettings::System::kQuitAgentOnExit, kQuitAgentOnExitDescription, false);
schema->AddBool(ClientSettings::System::kShowFilePaths, kShowFilePathsDescription, false);
schema->AddBool(ClientSettings::System::kShowStdout, kShowStdoutDescription, true);
schema->AddString(ClientSettings::System::kLanguage, kLanguageDescription, "auto",
{"rust", "c++", "auto"});
schema->AddList(ClientSettings::System::kSecondChanceExceptions,
kSecondChanceExceptionsDescription,
{kPageFaultExcpTypeShorthand, kPolicyErrorExcpTypeShorthand},
{
kGeneralExcpTypeShorthand,
kPageFaultExcpTypeShorthand,
kUndefinedInstructionExcpTypeShorthand,
kUnalignedAccessExcpTypeShorthand,
kPolicyErrorExcpTypeShorthand,
});
schema->AddBool(ClientSettings::System::kSkipUnsymbolized, kSkipUnsymbolizedDescription, true);
// Symbol lookup.
schema->AddList(ClientSettings::System::kSymbolIndexFiles, kSymbolIndexFilesDescription, {});
schema->AddList(ClientSettings::System::kSymbolPaths, kSymbolPathsDescription, {});
schema->AddList(ClientSettings::System::kBuildIdDirs, kBuildIdDirsDescription, {});
schema->AddList(ClientSettings::System::kIdsTxts, kIdsTxtsDescription, {});
schema->AddList(ClientSettings::System::kSymbolServers, kSymbolServersDescription, {});
schema->AddString(ClientSettings::System::kSymbolCache, kSymbolCacheDescription, "");
// Target ones.
schema->AddList(ClientSettings::Target::kBuildDirs, ClientSettings::Target::kBuildDirsDescription,
{});
schema->AddString(
ClientSettings::Target::kVectorFormat, ClientSettings::Target::kVectorFormatDescription,
kVectorRegisterFormatStr_Double, ClientSettings::Target::GetVectorFormatOptions());
// Thread ones.
schema->AddBool(ClientSettings::Thread::kDebugStepping,
ClientSettings::Thread::kDebugSteppingDescription, false);
schema->AddList(ClientSettings::Thread::kDisplay, ClientSettings::Thread::kDisplayDescription);
// The code that handles opt-in/out will set this explicitly.
schema->AddBool(ClientSettings::System::kEnableAnalytics, kEnableAnalyticsDescription, false);
return schema;
}
} // namespace
// Download ----------------------------------------------------------------------------------------
// When we want to download symbols for a build ID, we create a Download object. We then fire off
// requests to all symbol servers we know about asking whether they have the symbols we need. These
// requests are async, and the callbacks each own a shared_ptr to the Download object. If all the
// callbacks run and none of them are informed the request was successful, all of the shared_ptrs
// are dropped and the Download object is freed. The destructor of the Download object calls a
// callback that handles notifying the rest of the system of those results.
//
// If one of the callbacks does report that the symbols were found, a transaction to actually start
// the download is initiated, and its reply callback is again given a shared_ptr to the download. If
// we receive more notifications that other servers also have the symbol in the meantime, they are
// queued and will be tried as a fallback if the download fails. Again, once the download callback
// runs the shared_ptr is dropped, and when the Download object dies the destructor handles
// notifying the system.
class Download {
public:
Download(const std::string& build_id, DebugSymbolFileType file_type,
SymbolServer::FetchCallback result_cb)
: build_id_(build_id), file_type_(file_type), result_cb_(std::move(result_cb)) {}
~Download() { Finish(); }
bool active() { return !!result_cb_; }
// Add a symbol server to this download.
void AddServer(std::shared_ptr<Download> self, SymbolServer* server);
private:
// FetchFunction is a function that downloads the symbol file from one server.
// Multiple fetches are queued in server_fetches_ and tried in sequence.
using FetchFunction = fit::callback<void(SymbolServer::FetchCallback)>;
// Notify this download object that we have gotten the symbols if we're going to get them.
void Finish();
// Notify this Download object that one of the servers has the symbols available.
void Found(std::shared_ptr<Download> self, FetchFunction fetch);
// Notify this Download object that a transaction failed.
void Error(std::shared_ptr<Download> self, const Err& err);
void RunFetch(std::shared_ptr<Download> self, FetchFunction& fetch);
std::string build_id_;
DebugSymbolFileType file_type_;
Err err_;
std::string path_;
SymbolServer::FetchCallback result_cb_;
std::vector<FetchFunction> server_fetches_;
bool trying_ = false;
};
void Download::Finish() {
if (!result_cb_)
return;
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [result_cb = std::move(result_cb_), err = std::move(err_),
path = std::move(path_)]() mutable { result_cb(err, path); });
result_cb_ = nullptr;
}
void Download::AddServer(std::shared_ptr<Download> self, SymbolServer* server) {
FX_DCHECK(self.get() == this);
if (!result_cb_)
return;
server->CheckFetch(build_id_, file_type_, [self](const Err& err, FetchFunction fetch) {
if (!fetch)
self->Error(self, err);
else
self->Found(self, std::move(fetch));
});
}
void Download::Found(std::shared_ptr<Download> self, FetchFunction fetch) {
FX_DCHECK(self.get() == this);
if (!result_cb_)
return;
if (trying_) {
server_fetches_.push_back(std::move(fetch));
return;
}
RunFetch(self, fetch);
}
void Download::Error(std::shared_ptr<Download> self, const Err& err) {
FX_DCHECK(self.get() == this);
if (!result_cb_)
return;
if (!err_.has_error()) {
err_ = err;
} else if (err.has_error()) {
err_ = Err("Multiple servers could not be reached.");
}
if (!trying_ && !server_fetches_.empty()) {
RunFetch(self, server_fetches_.back());
server_fetches_.pop_back();
}
}
void Download::RunFetch(std::shared_ptr<Download> self, FetchFunction& fetch) {
FX_DCHECK(!trying_);
trying_ = true;
fetch([self](const Err& err, const std::string& path) {
self->trying_ = false;
if (path.empty()) {
self->Error(self, err);
} else {
self->err_ = err;
self->path_ = path;
self->Finish();
}
});
}
// System Implementation ---------------------------------------------------------------------------
System::System(Session* session)
: ClientObject(session), symbols_(this), settings_(GetSchema(), nullptr), weak_factory_(this) {
// Create the default job and target.
AddNewJob(std::make_unique<Job>(session, true));
AddNewTarget(std::make_unique<TargetImpl>(this));
// The system is the one holding the system symbols and is the one who will be updating the
// symbols once we get a symbol change, so the System will be listening to its own options. We
// don't use SystemSymbols because they live in the symbols library and we don't want it to have a
// client dependency.
settings_.AddObserver(ClientSettings::System::kDebugMode, this);
settings_.AddObserver(ClientSettings::System::kSymbolIndexFiles, this);
settings_.AddObserver(ClientSettings::System::kSymbolCache, this);
settings_.AddObserver(ClientSettings::System::kSymbolPaths, this);
settings_.AddObserver(ClientSettings::System::kBuildIdDirs, this);
settings_.AddObserver(ClientSettings::System::kIdsTxts, this);
settings_.AddObserver(ClientSettings::System::kSymbolServers, this);
settings_.AddObserver(ClientSettings::System::kSecondChanceExceptions, this);
// Observe the session for filter matches and attach to any process koid that the system is not
// already attached to.
session->AddFilterObserver(this);
}
System::~System() {
// Target destruction may depend on the symbol system. Ensure the targets get cleaned up first.
for (auto& target : targets_) {
// It's better if process destruction notifications are sent before target ones because the
// target owns the process. Because this class sends the target notifications, force the process
// destruction before doing anything.
target->ImplicitlyDetach();
for (auto& observer : session()->target_observers())
observer.WillDestroyTarget(target.get());
}
targets_.clear();
// Filters list may be iterated as we clean them up. Move its contents here first then let it
// drop so the dying objects are out of the system.
auto filters = std::move(filters_);
}
fxl::RefPtr<SettingSchema> System::GetSchema() {
// Will only run initialization once.
InitializeSchemas();
static fxl::RefPtr<SettingSchema> schema = CreateSchema();
return schema;
}
ProcessImpl* System::ProcessImplFromKoid(uint64_t koid) const {
for (const auto& target : targets_) {
ProcessImpl* process = target->process();
if (process && process->GetKoid() == koid)
return process;
}
return nullptr;
}
std::vector<TargetImpl*> System::GetTargetImpls() const {
std::vector<TargetImpl*> result;
for (const auto& t : targets_)
result.push_back(t.get());
return result;
}
TargetImpl* System::CreateNewTargetImpl(TargetImpl* clone) {
auto target = clone ? clone->Clone(this) : std::make_unique<TargetImpl>(this);
TargetImpl* to_return = target.get();
AddNewTarget(std::move(target));
return to_return;
}
SystemSymbols* System::GetSymbols() { return &symbols_; }
std::vector<Target*> System::GetTargets() const {
std::vector<Target*> result;
result.reserve(targets_.size());
for (const auto& t : targets_)
result.push_back(t.get());
return result;
}
std::vector<Job*> System::GetJobs() const {
std::vector<Job*> result;
result.reserve(jobs_.size());
for (const auto& t : jobs_)
result.push_back(t.get());
return result;
}
std::vector<Breakpoint*> System::GetBreakpoints() const {
std::vector<Breakpoint*> result;
result.reserve(breakpoints_.size());
for (const auto& pair : breakpoints_) {
if (!pair.second->is_internal())
result.push_back(pair.second.get());
}
return result;
}
std::vector<Filter*> System::GetFilters() const {
std::vector<Filter*> result;
result.reserve(filters_.size());
for (const auto& filter : filters_) {
result.push_back(filter.get());
}
return result;
}
std::vector<SymbolServer*> System::GetSymbolServers() const {
std::vector<SymbolServer*> result;
result.reserve(symbol_servers_.size());
for (const auto& item : symbol_servers_) {
result.push_back(item.get());
}
return result;
}
std::shared_ptr<Download> System::GetDownload(std::string build_id, DebugSymbolFileType file_type,
bool quiet) {
if (auto existing = downloads_[{build_id, file_type}].lock()) {
return existing;
}
DownloadStarted();
auto download = std::make_shared<Download>(
build_id, file_type,
[build_id, file_type, weak_this = weak_factory_.GetWeakPtr(), quiet](
const Err& err, const std::string& path) {
if (!weak_this) {
return;
}
if (!path.empty()) {
weak_this->download_success_count_++;
// Adds the file manually since the build_id could already be marked as missing in the
// build_id_index.
weak_this->symbols_.build_id_index().AddOneFile(path);
for (const auto& target : weak_this->targets_) {
if (auto process = target->process()) {
// Don't need local symbol lookup when retrying a download, so can pass an empty
// module name.
process->GetSymbols()->RetryLoadBuildID(std::string(), build_id, file_type);
}
}
} else {
weak_this->download_fail_count_++;
if (!quiet) {
weak_this->NotifyFailedToFindDebugSymbols(err, build_id, file_type);
}
}
weak_this->DownloadFinished();
});
downloads_[{build_id, file_type}] = download;
if (servers_initializing_) {
suspended_downloads_.push_back(download);
}
return download;
}
void System::DownloadStarted() {
if (download_count_ == 0) {
for (auto& observer : session()->download_observers()) {
observer.OnDownloadsStarted();
}
}
download_count_++;
}
void System::DownloadFinished() {
download_count_--;
if (download_count_ == 0) {
for (auto& observer : session()->download_observers()) {
observer.OnDownloadsStopped(download_success_count_, download_fail_count_);
}
download_success_count_ = download_fail_count_ = 0;
}
}
void System::RequestDownload(const std::string& build_id, DebugSymbolFileType file_type,
bool quiet) {
auto download = GetDownload(build_id, file_type, quiet);
for (auto& server : symbol_servers_) {
if (server->state() != SymbolServer::State::kReady) {
continue;
}
download->AddServer(download, server.get());
}
}
void System::NotifyFailedToFindDebugSymbols(const Err& err, const std::string& build_id,
DebugSymbolFileType file_type) {
for (const auto& target : targets_) {
// Notify only those targets which are processes and which have attempted and failed to load
// symbols for this build ID previously.
auto process = target->process();
if (!process)
continue;
for (const auto& status : process->GetSymbols()->GetStatus()) {
if (status.build_id != build_id) {
continue;
}
if (!err.has_error()) {
if (file_type == DebugSymbolFileType::kDebugInfo) {
process->OnSymbolLoadFailure(Err(
fxl::StringPrintf("Could not load symbols for \"%s\" because there was no mapping "
"for build ID \"%s\".",
status.name.c_str(), status.build_id.c_str())));
} else {
process->OnSymbolLoadFailure(
Err(fxl::StringPrintf("Could not load binary for \"%s\" because there was no mapping "
"for build ID \"%s\".",
status.name.c_str(), status.build_id.c_str())));
}
} else {
process->OnSymbolLoadFailure(err);
}
}
}
}
void System::OnSymbolServerBecomesReady(SymbolServer* server) {
for (const auto& target : targets_) {
auto process = target->process();
if (!process)
continue;
for (const auto& mod : process->GetSymbols()->GetStatus()) {
if (!mod.symbols || !mod.symbols->module_symbols()) {
auto download = GetDownload(mod.build_id, DebugSymbolFileType::kDebugInfo, true);
download->AddServer(download, server);
} else if (!mod.symbols->module_symbols()->HasBinary()) {
auto download = GetDownload(mod.build_id, DebugSymbolFileType::kBinary, true);
download->AddServer(download, server);
}
}
}
}
Process* System::ProcessFromKoid(uint64_t koid) const { return ProcessImplFromKoid(koid); }
void System::GetProcessTree(ProcessTreeCallback callback) {
session()->remote_api()->ProcessTree(debug_ipc::ProcessTreeRequest(), std::move(callback));
}
Target* System::CreateNewTarget(Target* clone) {
return CreateNewTargetImpl(static_cast<TargetImpl*>(clone));
}
Err System::DeleteTarget(Target* t) {
if (targets_.size() == 1)
return Err("Can't delete the last target.");
if (t->GetState() != Target::kNone)
return Err("Can't delete a process that's currently attached, detached, or starting.");
for (auto& observer : session()->target_observers())
observer.WillDestroyTarget(t);
auto found =
std::find_if(targets_.begin(), targets_.end(), [t](const auto& a) { return t == a.get(); });
FX_DCHECK(found != targets_.end());
targets_.erase(found);
return Err();
}
Job* System::CreateNewJob() {
auto job = std::make_unique<Job>(session(), false);
Job* to_return = job.get();
AddNewJob(std::move(job));
return to_return;
}
void System::DeleteJob(Job* job) {
auto found =
std::find_if(jobs_.begin(), jobs_.end(), [job](auto& cur) { return job == cur.get(); });
if (found == jobs_.end()) {
FX_NOTREACHED(); // Should always be found.
return;
}
for (auto& observer : observers_)
observer.WillDestroyJob(job);
// Delete all filters that reference this job. While it might be nice if the filter
// registered for a notification or used a weak pointer for the job, this would imply having a
// filter enabled/disabled state independent of the other settings which we don't have and don't
// currently need. Without a disabled state, clearing the job on the filter will make it apply to
// all jobs which the user does not want.
std::vector<Filter*> filters_to_remove;
for (auto& f : filters_) {
if (f->job() == job)
filters_to_remove.push_back(f.get());
}
for (auto& f : filters_to_remove)
DeleteFilter(f);
jobs_.erase(found);
}
Breakpoint* System::CreateNewBreakpoint() {
auto owning = std::make_unique<BreakpointImpl>(session(), false);
uint32_t id = owning->backend_id();
Breakpoint* to_return = owning.get();
breakpoints_[id] = std::move(owning);
// Notify observers (may mutate breakpoint list).
for (auto& observer : observers_)
observer.DidCreateBreakpoint(to_return);
return to_return;
}
Breakpoint* System::CreateNewInternalBreakpoint() {
auto owning = std::make_unique<BreakpointImpl>(session(), true);
uint32_t id = owning->backend_id();
Breakpoint* to_return = owning.get();
breakpoints_[id] = std::move(owning);
return to_return;
}
void System::DeleteBreakpoint(Breakpoint* breakpoint) {
BreakpointImpl* impl = static_cast<BreakpointImpl*>(breakpoint);
auto found = breakpoints_.find(impl->backend_id());
if (found == breakpoints_.end()) {
// Should always have found the breakpoint.
FX_NOTREACHED();
return;
}
// Only notify observers for non-internal breakpoints.
if (!found->second->is_internal()) {
for (auto& observer : observers_)
observer.WillDestroyBreakpoint(breakpoint);
}
breakpoints_.erase(found);
}
Filter* System::CreateNewFilter() {
Filter* to_return = filters_.emplace_back(std::make_unique<Filter>(session())).get();
// Notify observers (may mutate filter list).
for (auto& observer : observers_)
observer.DidCreateFilter(to_return);
return to_return;
}
void System::DeleteFilter(Filter* filter) {
auto found = filters_.begin();
for (; found != filters_.end(); ++found) {
if (found->get() == filter) {
break;
}
}
if (found == filters_.end()) {
// Should always have found the filter.
FX_NOTREACHED();
return;
}
for (auto& observer : observers_)
observer.WillDestroyFilter(filter);
// Move this aside while we modify the list, then let it drop at the end of the function. That way
// the destructor doesn't see itself in the list of active filters when it emits
// WillDestroyFilter.
auto filter_ptr = std::move(*found);
filters_.erase(found);
}
void System::Pause(fit::callback<void()> on_paused) {
debug_ipc::PauseRequest request; // Unset process/thread means everything.
session()->remote_api()->Pause(
request, [weak_system = weak_factory_.GetWeakPtr(), on_paused = std::move(on_paused)](
const Err&, debug_ipc::PauseReply reply) mutable {
if (weak_system) {
// Save the newly paused thread metadata. This may need to be
// generalized if we add other messages that update thread metadata.
for (const auto& record : reply.threads) {
if (auto* process = weak_system->ProcessImplFromKoid(record.id.process)) {
if (auto* thread = process->GetThreadImplFromKoid(record.id.thread))
thread->SetMetadata(record);
}
}
}
on_paused();
});
}
void System::Continue(bool forward) {
// Tell each process to continue as it desires.
//
// It would be more efficient to tell the backend to resume all threads in all processes but the
// Thread client objects have state which needs to be updated (like the current stack) and the
// thread could have a controller that wants to continue in a specific way (like single-step or
// step in a range).
for (const auto& target : targets_) {
if (Process* process = target->GetProcess())
process->Continue(forward);
}
}
void System::CancelAllThreadControllers() {
for (const auto& target : targets_) {
if (Process* process = target->GetProcess())
process->CancelAllThreadControllers();
}
}
bool System::HasDownload(const std::string& build_id) {
auto download = downloads_.find({build_id, DebugSymbolFileType::kDebugInfo});
if (download == downloads_.end()) {
download = downloads_.find({build_id, DebugSymbolFileType::kBinary});
}
if (download == downloads_.end()) {
return false;
}
auto ptr = download->second.lock();
return ptr && ptr->active();
}
std::shared_ptr<Download> System::InjectDownloadForTesting(const std::string& build_id) {
return GetDownload(build_id, DebugSymbolFileType::kDebugInfo, true);
}
void System::DidConnect(bool is_local) {
// Force reload the symbol mappings after connection. This needs to be done for every connection
// since a new image could have been compiled and launched which will have a different build ID
// file.
symbols_.build_id_index().ClearCache();
// Implicitly attach a job to the root. If there was already an implicit job created (from a
// previous connection) re-use it since there will be settings on it about what processes to
// attach to that we want to preserve.
Job* implicit_job = nullptr;
for (auto& job : jobs_) {
if (job->is_implicit_root()) {
implicit_job = job.get();
break;
}
}
if (!implicit_job) {
// No previous one, create a new implicit job.
auto new_job = std::make_unique<Job>(session(), true);
implicit_job = new_job.get();
AddNewJob(std::move(new_job));
}
implicit_job->AttachToSystemRoot([](fxl::WeakPtr<Job>, const Err& e) {
if (e.has_error())
LOGS(Warn) << "Failed to attach to the root job. Maybe another debugger is running?";
});
// Force the debug agent to reload its second-chance exception handling policy.
OnSettingChanged(settings(), ClientSettings::System::kSecondChanceExceptions);
// When debugging locally, fall back to using symbols from the local modules themselves if not
// found in the normal symbol locations.
GetSymbols()->set_enable_local_fallback(is_local);
}
void System::DidDisconnect() {
// The logic here should be consistent with debug_agent::DebugAgent::Disconnect().
for (auto& target : targets_)
target->ImplicitlyDetach();
for (auto& job : jobs_)
job->ImplicitlyDetach();
}
BreakpointImpl* System::BreakpointImplForId(uint32_t id) {
auto found = breakpoints_.find(id);
if (found == breakpoints_.end())
return nullptr;
return found->second.get();
}
void System::AddNewTarget(std::unique_ptr<TargetImpl> target) {
Target* for_observers = target.get();
targets_.push_back(std::move(target));
for (auto& observer : session()->target_observers())
observer.DidCreateTarget(for_observers);
}
void System::AddNewJob(std::unique_ptr<Job> job) {
Job* for_observers = job.get();
jobs_.push_back(std::move(job));
for (auto& observer : observers_)
observer.DidCreateJob(for_observers);
}
void System::OnSettingChanged(const SettingStore& store, const std::string& setting_name) {
// If any of them change, we have to reinitialize the build_id_index.
if (setting_name == ClientSettings::System::kSymbolIndexFiles ||
setting_name == ClientSettings::System::kSymbolPaths ||
setting_name == ClientSettings::System::kBuildIdDirs ||
setting_name == ClientSettings::System::kIdsTxts ||
setting_name == ClientSettings::System::kSymbolCache ||
setting_name == ClientSettings::System::kSymbolServers) {
// Clear the symbol sources and add them back to sync the index with the setting.
BuildIDIndex& build_id_index = GetSymbols()->build_id_index();
build_id_index.ClearAll();
// Add symbol-index files first. Because they might encode extra information, e.g., build_dir
// and require_authentication.
for (const std::string& path : store.GetList(ClientSettings::System::kSymbolIndexFiles)) {
build_id_index.AddSymbolIndexFile(path);
}
for (const std::string& path : store.GetList(ClientSettings::System::kSymbolPaths)) {
build_id_index.AddPlainFileOrDir(path);
}
for (const std::string& path : store.GetList(ClientSettings::System::kBuildIdDirs)) {
build_id_index.AddBuildIdDir(path);
}
for (const std::string& path : store.GetList(ClientSettings::System::kIdsTxts)) {
build_id_index.AddIdsTxt(path);
}
for (const std::string& url : store.GetList(ClientSettings::System::kSymbolServers)) {
build_id_index.AddSymbolServer(url);
}
// Cache directory.
auto symbol_cache = store.GetString(ClientSettings::System::kSymbolCache);
if (!symbol_cache.empty()) {
std::error_code ec;
std::filesystem::create_directories(std::filesystem::path(symbol_cache), ec);
build_id_index.SetCacheDir(symbol_cache);
}
// Symbol servers.
// TODO(dangyi): We don't support the removal of an existing symbol server yet.
std::set<std::string> existing;
for (const auto& symbol_server : symbol_servers_) {
existing.insert(symbol_server->name());
}
// TODO(dangyi): Separate symbol downloading from System into a new DownloadManager which is to
// be owned by BuildIDIndex.
for (const auto& server : build_id_index.symbol_servers()) {
if (existing.find(server.url) == existing.end()) {
if (auto symbol_server =
SymbolServer::FromURL(session(), server.url, server.require_authentication)) {
AddSymbolServer(std::move(symbol_server));
}
}
}
} else if (setting_name == ClientSettings::System::kDebugMode) {
bool debug_mode = store.GetBool(setting_name);
debug::SetDebugLogging(debug_mode);
syslog::SetLogSettings(
syslog::LogSettings{.min_log_level = debug_mode ? syslog::LOG_DEBUG : syslog::LOG_INFO});
} else if (setting_name == ClientSettings::System::kSecondChanceExceptions) {
debug_ipc::UpdateGlobalSettingsRequest request;
auto updates = ParseExceptionStrategyUpdates(store.GetList(setting_name));
if (updates.has_error()) {
// TODO: handle me.
return;
}
request.exception_strategies = updates.value();
session()->remote_api()->UpdateGlobalSettings(
request, [](const Err& err, debug_ipc::UpdateGlobalSettingsReply reply) {
if (reply.status.has_error()) {
// TODO: handle me.
}
});
} else {
LOGS(Warn) << "Unhandled setting change: " << setting_name;
}
}
void System::InjectSymbolServerForTesting(std::unique_ptr<SymbolServer> server) {
AddSymbolServer(std::move(server));
}
void System::OnFilterMatches(Job* job, const std::vector<uint64_t>& matched_pids) {
// Go over the targets and see if we find a valid one for each pid.
for (uint64_t matched_pid : matched_pids) {
bool found = false;
for (auto& target : targets_) {
Process* process = target->GetProcess();
if (process && process->GetKoid() == matched_pid) {
found = true;
break;
}
}
// If we found an already attached process, we don't care about this match.
if (found)
continue;
AttachToProcess(matched_pid, [matched_pid](fxl::WeakPtr<Target> target, const Err& err,
uint64_t timestamp) {
if (err.has_error()) {
LOGS(Error) << "Could not attach to process " << matched_pid << ": " << err.msg();
return;
}
});
}
}
void System::AttachToProcess(uint64_t pid, Target::CallbackWithTimestamp callback) {
// Don't allow attaching to a process more than once.
if (Process* process = ProcessFromKoid(pid)) {
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [callback = std::move(callback),
weak_target = process->GetTarget()->GetWeakPtr(), pid]() mutable {
callback(weak_target,
Err("Process " + std::to_string(pid) + " is already being debugged."), 0);
});
return;
}
// See if there is a target that is not attached.
Target* open_slot = nullptr;
for (auto& target : targets_) {
if (target->GetState() == zxdb::Target::State::kNone) {
open_slot = target.get();
break;
}
}
// If no slot was found, we create a new target.
if (!open_slot)
open_slot = CreateNewTarget(nullptr);
open_slot->Attach(pid, std::move(callback));
}
void System::AddSymbolServer(std::unique_ptr<SymbolServer> unique_server) {
SymbolServer* server = unique_server.get();
symbol_servers_.push_back(std::move(unique_server));
for (auto& observer : observers_) {
observer.DidCreateSymbolServer(server);
}
bool initializing = false;
if (server->state() == SymbolServer::State::kInitializing ||
server->state() == SymbolServer::State::kBusy) {
initializing = true;
servers_initializing_++;
}
server->set_state_change_callback([weak_this = weak_factory_.GetWeakPtr(), initializing](
SymbolServer* server, SymbolServer::State state) mutable {
if (!weak_this) {
return;
}
for (auto& observer : weak_this->observers_)
observer.OnSymbolServerStatusChanged(server);
if (state == SymbolServer::State::kReady)
weak_this->OnSymbolServerBecomesReady(server);
if (initializing && state != SymbolServer::State::kBusy &&
state != SymbolServer::State::kInitializing) {
initializing = false;
weak_this->servers_initializing_--;
if (!weak_this->servers_initializing_) {
weak_this->suspended_downloads_.clear();
}
}
});
if (server->state() == SymbolServer::State::kReady) {
OnSymbolServerBecomesReady(server);
}
}
} // namespace zxdb