| // Copyright 2020 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 "context.h" |
| |
| #include <cstdint> |
| #include <vector> |
| |
| #include "src/developer/debug/shared/logging/logging.h" |
| #include "src/developer/debug/zxdb/client/breakpoint.h" |
| #include "src/developer/debug/zxdb/client/pretty_stack_manager.h" |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/client/thread.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_attach.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_breakpoint.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_continue.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_evaluate.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_launch.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_next.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_pause.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_scopes.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_stacktrace.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_step_in.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_step_out.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_terminate.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_threads.h" |
| #include "src/developer/debug/zxdb/debug_adapter/handlers/request_variables.h" |
| #include "src/developer/debug/zxdb/debug_adapter/server.h" |
| #include "src/lib/fxl/memory/weak_ptr.h" |
| |
| namespace zxdb { |
| |
| DebugAdapterContext::DebugAdapterContext(Console* console, debug::StreamBuffer* stream) |
| : console_(console), dap_(dap::Session::create()) { |
| reader_ = std::make_shared<DebugAdapterReader>(stream); |
| writer_ = std::make_shared<DebugAdapterWriter>(stream); |
| |
| session()->AddObserver(this); |
| |
| dap_->registerHandler( |
| [this](const dap::InitializeRequest& req, |
| std::function<void(dap::ResponseOrError<dap::InitializeResponse>)> send_resp) { |
| DEBUG_LOG(DebugAdapter) << "InitializeRequest received"; |
| if (req.supportsInvalidatedEvent) { |
| supports_invalidate_event_ = req.supportsInvalidatedEvent.value(); |
| } |
| if (req.supportsRunInTerminalRequest) { |
| supports_run_in_terminal_ = req.supportsRunInTerminalRequest.value(); |
| } |
| send_initialize_response_ = send_resp; |
| // If the session is connected or there's no pending connection, send the response |
| // immediately. Otherwise, defer the response until the connection resolves. |
| if (session()->IsConnected()) { |
| DidResolveConnection(Err()); |
| } else if (!session()->HasPendingConnection()) { |
| Err err = session()->last_connection_error(); |
| if (!err.has_error()) { |
| err = Err("Debugger not connected to device"); |
| } |
| DidResolveConnection(err); |
| } |
| }); |
| |
| dap_->registerSentHandler([this](const dap::ResponseOrError<dap::InitializeResponse>& response) { |
| DEBUG_LOG(DebugAdapter) << "InitializeResponse sent"; |
| // Set up events and handlers now. All messages should be sent only after Initialize response |
| // is sent. Setting up earlier would lead to events and responses being sent before Initialize |
| // request is processed. |
| Init(); |
| dap_->send(dap::InitializedEvent()); |
| }); |
| |
| dap_->onError([](const char* msg) { LOGS(Error) << "dap::Session error:" << msg; }); |
| |
| dap_->connect(reader_, writer_); |
| } |
| |
| DebugAdapterContext::~DebugAdapterContext() { |
| if (init_done_) { |
| session()->thread_observers().RemoveObserver(this); |
| session()->process_observers().RemoveObserver(this); |
| session()->breakpoint_observers().RemoveObserver(this); |
| } |
| DeleteAllBreakpoints(); |
| session()->RemoveObserver(this); |
| } |
| |
| void DebugAdapterContext::DidResolveConnection(const Err& err) { |
| if (!send_initialize_response_) { |
| return; |
| } |
| if (err.has_error()) { |
| send_initialize_response_(dap::Error(err.msg())); |
| return; |
| } |
| dap::InitializeResponse response; |
| response.supportsFunctionBreakpoints = false; |
| response.supportsConfigurationDoneRequest = true; |
| response.supportsEvaluateForHovers = false; |
| response.supportsTerminateRequest = true; |
| send_initialize_response_(response); |
| } |
| |
| void DebugAdapterContext::Init() { |
| session()->analytics().ReportConsoleType(ConsoleType::Type::kDebugAdapter); |
| |
| // Register handlers with dap module. |
| dap_->registerHandler([this](const dap::LaunchRequestZxdb& req) { |
| DEBUG_LOG(DebugAdapter) << "RunBinaryRequest received"; |
| return OnRequestLaunch(this, req); |
| }); |
| |
| dap_->registerHandler([](const dap::SetExceptionBreakpointsRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "SetExceptionBreakpointsRequest received"; |
| dap::SetExceptionBreakpointsResponse response; |
| return response; |
| }); |
| |
| dap_->registerHandler([this](const dap::SetBreakpointsRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "SetBreakpointsRequest received"; |
| return OnRequestBreakpoint(this, req); |
| }); |
| |
| dap_->registerHandler([](const dap::ConfigurationDoneRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "ConfigurationDoneRequest received"; |
| return dap::ConfigurationDoneResponse(); |
| }); |
| |
| dap_->registerHandler([this](const dap::AttachRequestZxdb& req) { |
| DEBUG_LOG(DebugAdapter) << "AttachRequest received"; |
| return OnRequestAttach(this, req); |
| }); |
| |
| dap_->registerHandler([this](const dap::ThreadsRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "ThreadRequest received"; |
| return OnRequestThreads(this, req); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::PauseRequest& req, |
| std::function<void(dap::ResponseOrError<dap::PauseResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "PauseRequest received"; |
| OnRequestPause(this, req, callback); |
| }); |
| |
| dap_->registerHandler([this](const dap::ContinueRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "ContinueRequest received"; |
| return OnRequestContinue(this, req); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::NextRequest& req, |
| std::function<void(dap::ResponseOrError<dap::NextResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "NextRequest received"; |
| OnRequestNext(this, req, callback); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::StepInRequest& req, |
| std::function<void(dap::ResponseOrError<dap::StepInResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "StepInRequest received"; |
| OnRequestStepIn(this, req, callback); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::StepOutRequest& req, |
| std::function<void(dap::ResponseOrError<dap::StepOutResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "StepOutRequest received"; |
| OnRequestStepOut(this, req, callback); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::StackTraceRequest& req, |
| std::function<void(dap::ResponseOrError<dap::StackTraceResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "StackTraceRequest received"; |
| OnRequestStackTrace(this, req, callback); |
| }); |
| |
| dap_->registerHandler([this](const dap::ScopesRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "ScopesRequest received"; |
| return OnRequestScopes(this, req); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::VariablesRequest& req, |
| std::function<void(dap::ResponseOrError<dap::VariablesResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "VariablesRequest received"; |
| OnRequestVariables(this, req, callback); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::EvaluateRequest& req, |
| std::function<void(dap::ResponseOrError<dap::EvaluateResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "EvaluateRequest received"; |
| OnRequestEvaluate(this, req, callback); |
| }); |
| |
| dap_->registerHandler( |
| [this](const dap::TerminateRequest& req, |
| std::function<void(dap::ResponseOrError<dap::TerminateResponse>)> callback) { |
| DEBUG_LOG(DebugAdapter) << "TerminateRequest received"; |
| OnRequestTerminate(this, req, callback); |
| }); |
| |
| dap_->registerHandler([this](const dap::DisconnectRequest& req) { |
| DEBUG_LOG(DebugAdapter) << "DisconnectRequest received"; |
| if (destroy_connection_cb_) { |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [cb = std::move(destroy_connection_cb_)]() mutable { cb(); }); |
| } |
| return dap::DisconnectResponse(); |
| }); |
| |
| // Register to zxdb session events |
| session()->thread_observers().AddObserver(this); |
| session()->process_observers().AddObserver(this); |
| session()->breakpoint_observers().AddObserver(this); |
| |
| init_done_ = true; |
| } |
| |
| void DebugAdapterContext::OnStreamReadable() { |
| while (auto payload = dap_->getPayload()) { |
| payload(); |
| } |
| } |
| |
| void DebugAdapterContext::DidCreateThread(Thread* thread) { |
| dap::ThreadEvent event; |
| event.reason = "started"; |
| event.threadId = thread->GetKoid(); |
| dap_->send(event); |
| } |
| |
| void DebugAdapterContext::WillDestroyThread(Thread* thread) { |
| dap::ThreadEvent event; |
| event.reason = "exited"; |
| event.threadId = thread->GetKoid(); |
| dap_->send(event); |
| } |
| |
| void DebugAdapterContext::OnThreadStopped(Thread* thread, const StopInfo& info) { |
| dap::StoppedEvent event; |
| switch (info.exception_type) { |
| case debug_ipc::ExceptionType::kSoftwareBreakpoint: |
| case debug_ipc::ExceptionType::kHardwareBreakpoint: |
| event.reason = "breakpoint"; |
| event.description = "Breakpoint hit"; |
| break; |
| case debug_ipc::ExceptionType::kSingleStep: |
| event.reason = "step"; |
| break; |
| case debug_ipc::ExceptionType::kPolicyError: |
| event.reason = "exception"; |
| event.description = "Policy error"; |
| break; |
| case debug_ipc::ExceptionType::kPageFault: |
| event.reason = "exception"; |
| event.description = "Page fault"; |
| break; |
| case debug_ipc::ExceptionType::kUndefinedInstruction: |
| event.reason = "exception"; |
| event.description = "Undefined Instruction"; |
| break; |
| case debug_ipc::ExceptionType::kUnalignedAccess: |
| event.reason = "exception"; |
| event.description = "Unaligned Access"; |
| break; |
| default: |
| event.reason = "unknown"; |
| } |
| event.threadId = thread->GetKoid(); |
| |
| // Check whether a process level breakpoint caused this event. |
| // TODO(https://fxbug.dev/442573279): This approach doesn't not work for |
| // `debug_ipc::ExceptionType::kSingleStep` since `StopInfo::hit_breakpoints` will be empty in that |
| // case. |
| for (auto& bp : info.hit_breakpoints) { |
| if (bp->GetSettings().enabled && |
| (bp->GetSettings().stop_mode == BreakpointSettings::StopMode::kProcess || |
| bp->GetSettings().stop_mode == BreakpointSettings::StopMode::kAll)) { |
| event.allThreadsStopped = true; |
| } |
| } |
| dap_->send(event); |
| } |
| |
| void DebugAdapterContext::DidUpdateStackFrames(Thread* thread) { DeleteFrameIdsForThread(thread); } |
| |
| void DebugAdapterContext::DidCreateProcess(Process* process, uint64_t timestamp) { |
| dap::ProcessEvent event; |
| event.name = process->GetName(); |
| event.isLocalProcess = false; |
| |
| switch (process->start_type()) { |
| case Process::StartType::kAttach: |
| event.startMethod = "attach"; |
| break; |
| case Process::StartType::kLaunch: |
| event.startMethod = "launch"; |
| break; |
| } |
| |
| dap_->send(event); |
| } |
| |
| void DebugAdapterContext::WillDestroyProcess(Process* process, DestroyReason reason, int exit_code, |
| uint64_t timestamp) { |
| dap::ExitedEvent exit_event; // Sent when process exits. |
| dap::TerminatedEvent terminated_event; // Sent when process is detached. |
| switch (reason) { |
| case ProcessObserver::DestroyReason::kExit: |
| exit_event.exitCode = exit_code; |
| dap_->send(exit_event); |
| break; |
| case ProcessObserver::DestroyReason::kDetach: |
| dap_->send(terminated_event); |
| break; |
| case ProcessObserver::DestroyReason::kKill: |
| exit_event.exitCode = -1; |
| dap_->send(exit_event); |
| break; |
| } |
| } |
| |
| int64_t DebugAdapterContext::IdForBreakpoint(Breakpoint* breakpoint) { |
| auto item = breakpoint_to_id_.find(breakpoint); |
| if (item != breakpoint_to_id_.end()) { |
| return item->second; |
| } |
| |
| int64_t current_breakpoint_id = next_breakpoint_id_++; |
| breakpoint_to_id_[breakpoint] = current_breakpoint_id; |
| return current_breakpoint_id; |
| } |
| |
| void DebugAdapterContext::OnBreakpointMatched(Breakpoint* breakpoint, bool user_requested) { |
| BreakpointSettings settings = breakpoint->GetSettings(); |
| |
| dap::Breakpoint bp; |
| bp.verified = true; |
| bp.id = IdForBreakpoint(breakpoint); |
| |
| dap::BreakpointEvent breakpoint_event; |
| breakpoint_event.reason = "changed"; |
| breakpoint_event.breakpoint = bp; |
| dap_->send(breakpoint_event); |
| } |
| |
| Thread* DebugAdapterContext::GetThread(uint64_t koid) { |
| Thread* match = nullptr; |
| auto targets = session()->system().GetTargets(); |
| for (auto target : targets) { |
| if (!target) { |
| continue; |
| } |
| auto process = target->GetProcess(); |
| if (!process) { |
| continue; |
| } |
| auto threads = process->GetThreads(); |
| for (auto thread : threads) { |
| if (koid == thread->GetKoid()) { |
| match = thread; |
| break; |
| } |
| } |
| } |
| return match; |
| } |
| |
| Err DebugAdapterContext::CheckStoppedThread(Thread* thread) { |
| if (!thread) { |
| return Err("Invalid thread."); |
| } |
| |
| std::optional<debug_ipc::ThreadRecord::State> state_or = thread->GetState(); |
| if (!state_or) { |
| return Err("Thread should be suspended but thread %llu is in an unknown state.", |
| static_cast<unsigned long long>(thread->GetKoid())); |
| } |
| |
| if (*state_or != debug_ipc::ThreadRecord::State::kBlocked && |
| *state_or != debug_ipc::ThreadRecord::State::kCoreDump && |
| *state_or != debug_ipc::ThreadRecord::State::kSuspended) { |
| return Err("Thread should be suspended but thread %llu is %s.", |
| static_cast<unsigned long long>(thread->GetKoid()), |
| debug_ipc::ThreadRecord::StateToString(*state_or)); |
| } |
| return Err(); |
| } |
| |
| std::vector<PrettyStackManager::Match> DebugAdapterContext::GetElidedFrames(const Stack& stack) { |
| std::vector<PrettyStackManager::Match> result(stack.size()); |
| |
| Process* process = nullptr; |
| if (stack[0]->GetThread()) { |
| process = stack[0]->GetThread()->GetProcess(); |
| } |
| |
| // Elide against PrettyStackManager's default matchers first. |
| for (const auto& frame : console()->context().pretty_stack_manager()->ProcessStack(stack)) { |
| for (uint64_t i = 0; i < frame.frames.size(); i++) { |
| result.at(frame.begin_index + i) = frame.match; |
| } |
| } |
| |
| // Next, elide against TestFailureStackMatcher's matchers. This runs second so the more relevant |
| // "Test assertion implementation" grouping can override any generic matches near the top of the |
| // stack during a test failure. |
| auto test_impl_skipped_frames = console()->context().test_failure_stack_matcher()->Match(stack); |
| for (uint64_t i = 0; i < test_impl_skipped_frames; i++) { |
| result.at(i) = |
| PrettyStackManager::Match(test_impl_skipped_frames, "Test assertion implementation"); |
| } |
| |
| if (test_impl_skipped_frames > 0) { |
| // We think this process is a test, mark it as such. |
| process->set_kind(Process::Kind::kTest); |
| } |
| |
| return result; |
| } |
| |
| int64_t DebugAdapterContext::IdForFrame(uint64_t thread_koid, int64_t stack_index) { |
| FrameRecord record = {}; |
| record.thread_koid = thread_koid; |
| record.stack_index = stack_index; |
| |
| for (auto const& it : id_to_frame_) { |
| if (it.second.thread_koid == record.thread_koid && it.second.stack_index == stack_index) { |
| return it.first; |
| } |
| } |
| |
| int64_t current_frame_id = next_frame_id_++; |
| id_to_frame_[current_frame_id] = record; |
| return current_frame_id; |
| } |
| |
| Frame* DebugAdapterContext::FrameforId(int64_t id) { |
| // id - 0 is invalid |
| if (!id) { |
| return nullptr; |
| } |
| |
| if (auto it = id_to_frame_.find(id); it != id_to_frame_.end()) { |
| Thread* thread = GetThread(it->second.thread_koid); |
| if (!thread) { |
| return nullptr; |
| } |
| if (thread->GetStack().size() <= static_cast<size_t>(it->second.stack_index)) { |
| return nullptr; |
| } |
| return thread->GetStack()[it->second.stack_index]; |
| } |
| // Not found |
| return nullptr; |
| } |
| |
| void DebugAdapterContext::DeleteFrameIdsForThread(Thread* thread) { |
| auto thread_koid = thread->GetKoid(); |
| for (auto it = id_to_frame_.begin(); it != id_to_frame_.end();) { |
| // We don't really know what changed. We don't want to invalidate the frame ID every time |
| // since one of the update cases is that the frames have been appended to (so existing indices |
| // are still valid) or that symbols are loaded (normally this means that the frames are |
| // unchanged, though inline frames can get expanded in some cases). |
| // |
| // As a result, keep the ID unchanged unless it's now out-of-bounds. This avoids resetting any |
| // state in the more common cases. |
| if ((it->second.thread_koid == thread_koid) && |
| (static_cast<size_t>(it->second.stack_index) >= thread->GetStack().size())) { |
| if (supports_invalidate_event_) { |
| dap::InvalidatedEvent event; |
| event.stackFrameId = IdForFrame(thread_koid, it->second.stack_index); |
| dap_->send(event); |
| } |
| DeleteVariablesIdsForFrameId(it->first); |
| it = id_to_frame_.erase(it); |
| } else { |
| it++; |
| } |
| } |
| } |
| |
| int64_t DebugAdapterContext::IdForVariables(int64_t frame_id, VariablesType type, |
| std::unique_ptr<FormatNode> parent, |
| fxl::WeakPtr<FormatNode> child) { |
| // Check if an entry exists already, except for kChildVariable records, as those are always |
| // created newly. |
| if (type != VariablesType::kChildVariable) { |
| for (auto const& it : id_to_variables_) { |
| if (it.second.frame_id == frame_id && it.second.type == type) { |
| return it.first; |
| } |
| } |
| } |
| |
| VariablesRecord record; |
| record.frame_id = frame_id; |
| record.type = type; |
| record.parent = std::move(parent); |
| record.child = std::move(child); |
| |
| int current_variables_id = next_variables_id_++; |
| id_to_variables_[current_variables_id] = std::move(record); |
| return current_variables_id; |
| } |
| |
| VariablesRecord* DebugAdapterContext::VariablesRecordForID(int64_t id) { |
| // id - 0 is invalid |
| if (!id) { |
| return nullptr; |
| } |
| |
| if (auto it = id_to_variables_.find(id); it != id_to_variables_.end()) { |
| return &it->second; |
| } |
| // Not found |
| return nullptr; |
| } |
| |
| void DebugAdapterContext::DeleteVariablesIdsForFrameId(int64_t id) { |
| for (auto it = id_to_variables_.begin(); it != id_to_variables_.end();) { |
| if (it->second.frame_id == id) { |
| it = id_to_variables_.erase(it); |
| } else { |
| it++; |
| } |
| } |
| } |
| |
| void DebugAdapterContext::StoreBreakpointForSource(const std::filesystem::path& source, |
| Breakpoint* bp) { |
| FX_DCHECK(bp); |
| source_to_bp_[source].push_back(bp->GetWeakPtr()); |
| } |
| |
| std::vector<fxl::WeakPtr<Breakpoint>>* DebugAdapterContext::GetBreakpointsForSource( |
| const std::filesystem::path& source) { |
| if (auto it = source_to_bp_.find(source); it != source_to_bp_.end()) { |
| return &it->second; |
| } |
| // Not found |
| return nullptr; |
| } |
| |
| void DebugAdapterContext::DeleteBreakpointsForSource(const std::filesystem::path& source) { |
| auto it = source_to_bp_.find(source); |
| if (it == source_to_bp_.end()) { |
| return; |
| } |
| |
| for (auto& bp : it->second) { |
| if (bp) { |
| breakpoint_to_id_.erase(bp.get()); |
| session()->system().DeleteBreakpoint(bp.get()); |
| } |
| } |
| source_to_bp_.erase(it); |
| } |
| |
| void DebugAdapterContext::DeleteAllBreakpoints() { |
| for (auto& it : source_to_bp_) { |
| for (auto& bp : it.second) { |
| if (bp) { |
| session()->system().DeleteBreakpoint(bp.get()); |
| } |
| } |
| } |
| breakpoint_to_id_.clear(); |
| source_to_bp_.clear(); |
| } |
| |
| } // namespace zxdb |