blob: 5aeee8e07cda237c8fdf86d1c369cf9e50ce4842 [file] [log] [blame] [edit]
// 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