|  | // 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) { | 
|  | 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, nullptr, 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(), | 
|  | process->GetWeakPtr(), ""); | 
|  |  | 
|  | 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 |