blob: 855c5b4ba9103379c8dc3fdfe2f66b1fc2f9b350 [file] [log] [blame]
// Copyright 2019 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 "tools/fidlcat/lib/interception_workflow.h"
#include <cstring>
#include <regex>
#include <string>
#include <thread>
#include "src/developer/debug/shared/platform_message_loop.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/filter.h"
#include "src/developer/debug/zxdb/client/remote_api.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/expr/expr_parser.h"
#include "tools/fidlcat/lib/decode_options.h"
#include "tools/fidlcat/lib/syscall_decoder_dispatcher.h"
namespace fidlcat {
void InterceptingThreadObserver::OnThreadStopped(zxdb::Thread* thread, const zxdb::StopInfo& info) {
FXL_CHECK(thread) << "Internal error: Stopped in a breakpoint without a thread?";
if (info.exception_type != debug_ipc::ExceptionType::kSoftware) {
FXL_CHECK(info.hit_breakpoints.empty());
if (threads_in_error_.find(thread->GetKoid()) == threads_in_error_.end()) {
threads_in_error_.emplace(thread->GetKoid());
workflow_->syscall_decoder_dispatcher()->DecodeException(workflow_, thread);
}
return;
}
if (info.hit_breakpoints.empty()) {
// This can happen when we are shutting down fidlcat.
// There is nothing to do => we just return.
return;
}
// TODO(bug 47497) Uncomment this and fix the test bugs that create more than one breakpoint
// at the same address.
// FXL_CHECK(info.hit_breakpoints.size() == 1)
// << "Internal error: more than one simultaneous breakpoint for thread " <<
// thread->GetKoid();
// There a two possible breakpoints we can hit:
// - A breakpoint right before a system call (zx_channel_read,
// zx_channel_write, etc)
// - A breakpoint that we hit because we ran the system call to see what the
// result will be.
// This is the breakpoint that we hit after running the system call. The
// initial breakpoint - the one on the system call - registered a callback in
// this per-thread map, so that the next breakpoint on this thread would be
// handled here.
auto entry = breakpoint_map_.find(thread->GetKoid());
if (entry != breakpoint_map_.end()) {
entry->second->LoadSyscallReturnValue();
// Erasing under the assumption that the next step will put it back, if
// necessary.
breakpoint_map_.erase(thread->GetKoid());
return;
}
// If there was no registered breakpoint on this thread, we hit it because we
// encountered a system call. Run the callbacks associated with this system
// call.
for (auto& bp_ptr : info.hit_breakpoints) {
zxdb::BreakpointSettings settings = bp_ptr->GetSettings();
if (settings.locations.size() == 1u &&
settings.locations[0].type == zxdb::InputLocation::Type::kName &&
settings.locations[0].name.components().size() == 1u &&
settings.locations[0].name.components()[0].special() == zxdb::SpecialIdentifier::kPlt) {
threads_in_error_.erase(thread->GetKoid());
for (auto& syscall : workflow_->syscall_decoder_dispatcher()->syscalls()) {
// Compare against the syscall->name() which is the syscall name not including the $plt
// prefix. The Identifier component's name won't include this annotation without running
// GetFullName() which is slower. We already checked that it's a $plt annotation above.
if (settings.locations[0].name.components()[0].name() == syscall->name()) {
workflow_->syscall_decoder_dispatcher()->DecodeSyscall(this, thread, syscall.get());
return;
}
}
FXL_LOG(ERROR) << thread->GetProcess()->GetName() << ' ' << thread->GetProcess()->GetKoid()
<< ':' << thread->GetKoid() << ": Internal error: breakpoint "
<< settings.locations[0].name.components()[0].name() << " not managed";
thread->Continue();
return;
}
}
thread->Continue();
}
void InterceptingThreadObserver::Register(int64_t koid, SyscallDecoder* decoder) {
breakpoint_map_[koid] = decoder;
}
void InterceptingThreadObserver::AddExitBreakpoint(zxdb::Thread* thread,
const std::string& syscall_name,
uint64_t address) {
zxdb::BreakpointSettings settings;
if (one_shot_breakpoints_) {
settings.enabled = true;
settings.name = syscall_name + "-return";
settings.stop_mode = zxdb::BreakpointSettings::StopMode::kThread;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.locations.emplace_back(address);
settings.scope = zxdb::ExecutionScope(thread);
settings.one_shot = true;
} else {
if (exit_breakpoints_.find(address) != exit_breakpoints_.end()) {
return;
}
exit_breakpoints_.emplace(address);
settings.enabled = true;
settings.name = syscall_name + "-return";
settings.stop_mode = zxdb::BreakpointSettings::StopMode::kThread;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.locations.emplace_back(address);
settings.scope = zxdb::ExecutionScope(thread->GetProcess()->GetTarget());
}
FXL_VLOG(2) << "Thread " << thread->GetKoid() << ": creating return value breakpoint for "
<< syscall_name << " at address " << std::hex << address << std::dec;
CreateNewBreakpoint(thread, settings);
}
void InterceptingThreadObserver::CreateNewBreakpoint(zxdb::Thread* thread,
zxdb::BreakpointSettings& settings) {
zxdb::Breakpoint* breakpoint = workflow_->session_->system().CreateNewBreakpoint();
breakpoint->SetSettings(settings);
}
void InterceptingProcessObserver::DidCreateProcess(zxdb::Process* process, bool autoattached) {
workflow_->syscall_decoder_dispatcher()->AddLaunchedProcess(process->GetKoid());
workflow_->SetBreakpoints(process);
}
void InterceptingProcessObserver::WillDestroyProcess(zxdb::Process* process,
ProcessObserver::DestroyReason reason,
int exit_code) {
workflow_->ProcessDetached(process->GetKoid());
}
InterceptionWorkflow::InterceptionWorkflow()
: session_(new zxdb::Session()),
delete_session_(true),
loop_(new debug_ipc::PlatformMessageLoop()),
delete_loop_(true),
process_observer_(this),
thread_observer_(this) {
session_->process_observers().AddObserver(&process_observer_);
session_->thread_observers().AddObserver(&thread_observer_);
}
InterceptionWorkflow::InterceptionWorkflow(zxdb::Session* session, debug_ipc::MessageLoop* loop)
: session_(session),
delete_session_(false),
loop_(loop),
delete_loop_(false),
process_observer_(this),
thread_observer_(this) {
session_->process_observers().AddObserver(&process_observer_);
session_->thread_observers().AddObserver(&thread_observer_);
}
InterceptionWorkflow::~InterceptionWorkflow() {
session_->thread_observers().RemoveObserver(&thread_observer_);
session_->process_observers().RemoveObserver(&process_observer_);
if (delete_session_) {
delete session_;
}
if (delete_loop_) {
delete loop_;
}
}
void InterceptionWorkflow::Initialize(
const std::vector<std::string>& symbol_paths, const std::vector<std::string>& symbol_repo_paths,
const std::string& symbol_cache_path, const std::vector<std::string>& symbol_servers,
std::unique_ptr<SyscallDecoderDispatcher> syscall_decoder_dispatcher, bool quit_agent_on_exit) {
syscall_decoder_dispatcher_ = std::move(syscall_decoder_dispatcher);
if (quit_agent_on_exit) {
session_->system().settings().SetBool(zxdb::ClientSettings::System::kQuitAgentOnExit, true);
}
// 1) Set up symbol index.
// Stolen from console/console_main.cc
std::vector<std::string> paths;
// At this moment, the build index has all the "default" paths.
zxdb::BuildIDIndex& build_id_index = session_->system().GetSymbols()->build_id_index();
for (const auto& build_id_file : build_id_index.build_id_files()) {
paths.push_back(build_id_file);
}
for (const auto& source : build_id_index.sources()) {
paths.push_back(source);
}
// We add the options paths given paths.
paths.insert(paths.end(), symbol_paths.begin(), symbol_paths.end());
if (!symbol_cache_path.empty()) {
session_->system().settings().SetString(zxdb::ClientSettings::System::kSymbolCache,
symbol_cache_path);
}
// Adding it to the settings will trigger the loading of the symbols.
// Redundant adds are ignored.
session_->system().settings().SetList(zxdb::ClientSettings::System::kSymbolPaths,
std::move(paths));
if (!symbol_repo_paths.empty()) {
session_->system().settings().SetList(zxdb::ClientSettings::System::kSymbolRepoPaths,
symbol_repo_paths);
}
// 2) Ensure that the session correctly reads data off of the loop.
buffer_.set_data_available_callback([this]() { session_->OnStreamReadable(); });
// 3) Provide a loop, if none exists.
if (debug_ipc::MessageLoop::Current() == nullptr) {
std::string error_message;
bool success = loop_->Init(&error_message);
FXL_CHECK(success) << error_message;
}
// 4) Initialize the symbol servers.
if (!symbol_servers.empty()) {
session_->system().settings().SetList(zxdb::ClientSettings::System::kSymbolServers,
symbol_servers);
}
}
void InterceptionWorkflow::Connect(const std::string& host, uint16_t port,
const SimpleErrorFunction& and_then) {
session_->Connect(host, port, [and_then](const zxdb::Err& err) { and_then(err); });
}
// Helper function that finds a target for fidlcat to attach itself to. The
// target with |process_koid| must already be running.
zxdb::Target* InterceptionWorkflow::GetTarget(zx_koid_t process_koid) {
for (zxdb::Target* target : session_->system().GetTargets()) {
if (target->GetProcess() && target->GetProcess()->GetKoid() == process_koid) {
return target;
}
}
return session_->system().CreateNewTarget(nullptr);
}
zxdb::Target* InterceptionWorkflow::GetNewTarget() {
for (zxdb::Target* target : session_->system().GetTargets()) {
if (target->GetState() == zxdb::Target::State::kNone) {
return target;
}
}
return session_->system().CreateNewTarget(nullptr);
}
bool InterceptionWorkflow::HasSymbolServers() const {
return !session_->system().GetSymbolServers().empty();
}
std::vector<zxdb::SymbolServer*> InterceptionWorkflow::GetSymbolServers() const {
return session_->system().GetSymbolServers();
}
void InterceptionWorkflow::Attach(const std::vector<zx_koid_t>& process_koids) {
for (zx_koid_t process_koid : process_koids) {
// Get a target for this process.
zxdb::Target* target = GetTarget(process_koid);
// If we are already attached, then we are done.
if (target->GetProcess()) {
FXL_CHECK(target->GetProcess()->GetKoid() == process_koid)
<< "Internal error: target attached to wrong process";
continue;
}
// The debugger is not yet attached to the process. Attach to it.
target->Attach(process_koid, [this, target, process_koid](fxl::WeakPtr<zxdb::Target> /*target*/,
const zxdb::Err& err) {
if (!err.ok()) {
syscall_decoder_dispatcher()->ProcessMonitored("", process_koid, err.msg());
return;
}
SetBreakpoints(target->GetProcess());
});
}
}
void InterceptionWorkflow::ProcessDetached(zx_koid_t koid) {
if (configured_processes_.find(koid) == configured_processes_.end()) {
return;
}
configured_processes_.erase(koid);
syscall_decoder_dispatcher()->StopMonitoring(koid);
Detach();
}
void InterceptionWorkflow::Detach() {
if (configured_processes_.empty()) {
if (!shutdown_done_) {
shutdown_done_ = true;
Shutdown();
}
}
}
void InterceptionWorkflow::Filter(const std::vector<std::string>& filter) {
std::set<std::string> filter_set(filter.begin(), filter.end());
for (auto it = filters_.begin(); it != filters_.end();) {
if (filter_set.find((*it)->pattern()) != filter_set.end()) {
filter_set.erase((*it)->pattern());
++it;
} else {
session_->system().DeleteFilter(*it);
it = filters_.erase(it);
}
}
zxdb::JobContext* default_job = session_->system().GetJobContexts()[0];
for (const auto& pattern : filter_set) {
filters_.push_back(session_->system().CreateNewFilter());
filters_.back()->SetPattern(pattern);
filters_.back()->SetJob(default_job);
}
}
void InterceptionWorkflow::Launch(zxdb::Target* target, const std::vector<std::string>& command) {
FXL_CHECK(!command.empty()) << "No arguments passed to launcher";
auto on_err = [this, command](const zxdb::Err& err) {
std::string cmd;
for (auto& param : command) {
cmd.append(param);
cmd.append(" ");
}
syscall_decoder_dispatcher()->ProcessLaunched(cmd, err.ok() ? "" : err.msg());
};
if (command[0] == "run") {
// The component workflow.
debug_ipc::LaunchRequest request;
request.inferior_type = debug_ipc::InferiorType::kComponent;
request.argv = std::vector<std::string>(command.begin() + 1, command.end());
session_->remote_api()->Launch(
request, [this, target = target->GetWeakPtr(), on_err = std::move(on_err)](
const zxdb::Err& err, debug_ipc::LaunchReply reply) {
if (err.ok() && (reply.status != debug_ipc::kZxOk)) {
zxdb::Err status_err(zxdb::ErrType::kGeneral, fidl_codec::StatusName(reply.status));
on_err(status_err);
} else {
on_err(err);
}
target->session()->ExpectComponent(reply.component_id);
if (target->GetProcess() != nullptr) {
SetBreakpoints(target->GetProcess());
}
});
return;
}
target->SetArgs(command);
target->Launch(
[this, on_err = std::move(on_err)](fxl::WeakPtr<zxdb::Target> target, const zxdb::Err& err) {
on_err(err);
if (target->GetProcess() != nullptr) {
SetBreakpoints(target->GetProcess());
}
});
}
void InterceptionWorkflow::SetBreakpoints(zxdb::Process* process) {
if (configured_processes_.find(process->GetKoid()) != configured_processes_.end()) {
return;
}
configured_processes_.emplace(process->GetKoid());
syscall_decoder_dispatcher()->ProcessMonitored(process->GetName(), process->GetKoid(), "");
for (auto& syscall : syscall_decoder_dispatcher()->syscalls()) {
bool put_breakpoint = true;
if (!syscall->is_function()) {
// Only apply the filters to syscalls. We always want to intercept regular
// functions because they give us the information about the starting handles.
if (!syscall_decoder_dispatcher()->decode_options().syscall_filters.empty()) {
put_breakpoint = false;
for (const auto& syscall_filter :
syscall_decoder_dispatcher()->decode_options().syscall_filters) {
if (regex_match(syscall->name(), syscall_filter)) {
put_breakpoint = true;
break;
}
}
}
if (put_breakpoint) {
for (const auto& syscall_filter :
syscall_decoder_dispatcher()->decode_options().exclude_syscall_filters) {
if (regex_match(syscall->name(), syscall_filter)) {
put_breakpoint = false;
break;
}
}
}
}
if (put_breakpoint) {
zxdb::BreakpointSettings settings;
settings.enabled = true;
settings.name = syscall->name();
settings.stop_mode = zxdb::BreakpointSettings::StopMode::kThread;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.scope = zxdb::ExecutionScope(process->GetTarget());
zxdb::Identifier identifier;
zxdb::Err err = zxdb::ExprParser::ParseIdentifier(syscall->breakpoint_name(), &identifier);
FXL_CHECK(err.ok());
settings.locations.emplace_back(std::move(identifier));
zxdb::Breakpoint* breakpoint = session_->system().CreateNewBreakpoint();
breakpoint->SetSettings(settings);
}
}
}
void InterceptionWorkflow::Go() {
debug_ipc::MessageLoop* current = debug_ipc::MessageLoop::Current();
current->Run();
current->Cleanup();
}
namespace {
// Makes sure we never get stuck in the workflow at a breakpoint.
class AlwaysContinue {
public:
explicit AlwaysContinue(zxdb::Thread* thread) : thread_(thread) {}
~AlwaysContinue() { thread_->Continue(); }
private:
zxdb::Thread* thread_;
};
} // namespace
} // namespace fidlcat