blob: c32325bf262a3cd81a46d15de1824319008542fd [file]
// Copyright 2026 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/debug_adapter/handlers/request_tracepoint.h"
#include <limits>
#include <set>
#include <vector>
#include "src/developer/debug/shared/arch.h"
#include "src/developer/debug/zxdb/client/breakpoint_settings.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/developer/debug/zxdb/client/tracepoint.h"
#include "src/developer/debug/zxdb/common/err_or.h"
#include "src/developer/debug/zxdb/debug_adapter/context.h"
#include "src/developer/debug/zxdb/debug_adapter/data_id.h"
namespace dap {
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointWatchSpec, "", DAP_FIELD(data_id, "dataId"),
DAP_FIELD(address, "address"), DAP_FIELD(expression, "expression"),
DAP_FIELD(byte_size, "byteSize"),
DAP_FIELD(access_type, "accessType"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointSpec, "", DAP_FIELD(id, "id"),
DAP_FIELD(function, "function"), DAP_FIELD(file, "file"),
DAP_FIELD(line, "line"), DAP_FIELD(address, "address"),
DAP_FIELD(watch, "watch"), DAP_FIELD(condition, "condition"),
DAP_FIELD(frame, "frame"), DAP_FIELD(backtrace, "backtrace"),
DAP_FIELD(raw_backtrace, "rawBacktrace"), DAP_FIELD(locals, "locals"),
DAP_FIELD(registers, "registers"),
DAP_FIELD(expressions, "expressions"),
DAP_FIELD(max_matching_frames, "maxMatchingFrames"),
DAP_FIELD(max_hits, "maxHits"), DAP_FIELD(enabled, "enabled"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointInfo, "", DAP_FIELD(id, "id"),
DAP_FIELD(verified, "verified"),
DAP_FIELD(max_matching_frames, "maxMatchingFrames"),
DAP_FIELD(hit_count, "hitCount"), DAP_FIELD(max_hits, "maxHits"),
DAP_FIELD(enabled, "enabled"),
DAP_FIELD(auto_disabled, "autoDisabled"),
DAP_FIELD(message, "message"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbSetTracepointsResponse, "", DAP_FIELD(tracepoints, "tracepoints"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbSetTracepointsRequest, "zxdb.SetTracepoints",
DAP_FIELD(tracepoints, "tracepoints"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointFrameSummary, "", DAP_FIELD(name, "name"),
DAP_FIELD(file, "file"), DAP_FIELD(line, "line"), DAP_FIELD(ip, "ip"),
DAP_FIELD(sp, "sp"), DAP_FIELD(trust, "trust"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointFrameRecord, "", DAP_FIELD(frame_index, "frameIndex"),
DAP_FIELD(name, "name"), DAP_FIELD(file, "file"),
DAP_FIELD(line, "line"), DAP_FIELD(ip, "ip"), DAP_FIELD(sp, "sp"),
DAP_FIELD(trust, "trust"), DAP_FIELD(elided, "elided"),
DAP_FIELD(elide_description, "elideDescription"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointFrameCapture, "", DAP_FIELD(matched, "matched"),
DAP_FIELD(frame, "frame"), DAP_FIELD(backtrace, "backtrace"),
DAP_FIELD(locals, "locals"), DAP_FIELD(registers, "registers"),
DAP_FIELD(evaluations, "evaluations"), DAP_FIELD(errors, "errors"))
DAP_IMPLEMENT_STRUCT_TYPEINFO(ZxdbTracepointEvent, "zxdb.Tracepoint",
DAP_FIELD(tracepoint_id, "tracepointId"),
DAP_FIELD(location, "location"), DAP_FIELD(hit_count, "hitCount"),
DAP_FIELD(max_hits, "maxHits"),
DAP_FIELD(auto_disabled, "autoDisabled"),
DAP_FIELD(truncated, "truncated"),
DAP_FIELD(thread_koid, "threadKoid"), DAP_FIELD(dwell_ms, "dwellMs"),
DAP_FIELD(capture, "capture"))
} // namespace dap
namespace zxdb {
namespace {
struct AssignedClientIds {
std::vector<int64_t> assigned_ids;
std::set<int64_t> incoming_ids;
};
ErrOr<AssignedClientIds> AssignAndValidateClientIds(
const dap::array<dap::ZxdbTracepointSpec>& specs,
const std::map<int64_t, fxl::WeakPtr<Tracepoint>>& existing) {
int64_t next_id = 1;
for (const auto& [cid, _] : existing) {
if (cid >= next_id) {
next_id = cid + 1;
}
}
AssignedClientIds result;
result.assigned_ids.reserve(specs.size());
for (const auto& dap_spec : specs) {
if (dap_spec.id.has_value()) {
if (*dap_spec.id <= 0) {
return Err("Tracepoint id must be positive.");
}
if (!result.incoming_ids.insert(*dap_spec.id).second) {
return Err("Duplicate tracepoint id in zxdb.SetTracepoints request.");
}
next_id = std::max(next_id, *dap_spec.id + 1);
}
if (dap_spec.line.has_value() && *dap_spec.line <= 0) {
return Err("Line number must be positive.");
}
if (dap_spec.max_matching_frames.has_value() && *dap_spec.max_matching_frames <= 0) {
return Err("Tracepoint maxMatchingFrames must be a positive integer.");
}
if (dap_spec.max_hits.has_value() && *dap_spec.max_hits <= 0) {
return Err("maxHits must be positive.");
}
if (dap_spec.watch.has_value() && dap_spec.watch->byte_size.has_value() &&
*dap_spec.watch->byte_size <= 0) {
return Err("watch byteSize must be positive.");
}
}
for (const auto& dap_spec : specs) {
if (dap_spec.id.has_value()) {
result.assigned_ids.push_back(*dap_spec.id);
} else {
while (existing.contains(next_id) || result.incoming_ids.contains(next_id)) {
next_id++;
}
result.assigned_ids.push_back(next_id);
result.incoming_ids.insert(next_id);
next_id++;
}
}
return result;
}
void PruneRemovedTracepoints(Session* session, const std::set<int64_t>& incoming_ids,
std::map<int64_t, fxl::WeakPtr<Tracepoint>>& existing) {
std::vector<Tracepoint*> to_delete;
for (const auto& [cid, tp] : existing) {
if (!incoming_ids.contains(cid) && tp) {
to_delete.push_back(tp.get());
}
}
for (Tracepoint* tp : to_delete) {
if (session) {
session->system().DeleteTracepoint(tp);
}
}
for (auto it = existing.begin(); it != existing.end();) {
if (!incoming_ids.contains(it->first)) {
it = existing.erase(it);
} else {
++it;
}
}
}
ErrOr<TracepointWatchSpec> BuildWatchSpec(const dap::ZxdbTracepointWatchSpec& dap_watch,
debug::Arch arch) {
TracepointWatchSpec watch;
if (dap_watch.expression.has_value()) {
watch.expression = *dap_watch.expression;
}
if (dap_watch.data_id.has_value()) {
auto parse_res = DataId::Parse(*dap_watch.data_id);
if (parse_res.has_error()) {
return parse_res.err();
}
watch.address = parse_res.value().address();
watch.byte_size = parse_res.value().byte_size();
}
if (dap_watch.address.has_value()) {
if (*dap_watch.address < 0) {
return Err("Invalid negative watch address.");
}
watch.address = static_cast<uint64_t>(*dap_watch.address);
}
if (dap_watch.byte_size.has_value()) {
watch.byte_size = static_cast<uint32_t>(*dap_watch.byte_size);
}
if (dap_watch.access_type.has_value()) {
const std::string& access = *dap_watch.access_type;
if (access == "read") {
return Err("Read-only data breakpoints are not supported on this platform.");
}
if (access == "readWrite" || access == "read_write") {
watch.access_type = debug_ipc::BreakpointType::kReadWrite;
} else if (access == "write") {
watch.access_type = debug_ipc::BreakpointType::kWrite;
} else {
return Err("Unknown accessType: %s", access.c_str());
}
}
if (watch.address.has_value() &&
*watch.address > std::numeric_limits<uint64_t>::max() - watch.byte_size) {
return Err("Watch address range overflows 64-bit address space.");
}
if (Err err = BreakpointSettings::ValidateSize(arch, watch.access_type, watch.byte_size);
err.has_error()) {
return err;
}
return watch;
}
ErrOr<TracepointSpec> BuildTracepointSpec(const dap::ZxdbTracepointSpec& dap_spec,
debug::Arch arch) {
if (dap_spec.file.has_value() != dap_spec.line.has_value()) {
return Err("Both file and line must be specified together for a line tracepoint.");
}
TracepointSpec spec;
if (dap_spec.function.has_value()) {
spec.function = *dap_spec.function;
}
if (dap_spec.address.has_value()) {
if (*dap_spec.address < 0) {
return Err("Invalid negative tracepoint address.");
}
spec.address = static_cast<uint64_t>(*dap_spec.address);
}
if (dap_spec.file.has_value() && dap_spec.line.has_value()) {
spec.file_line = FileLine(*dap_spec.file, static_cast<int>(*dap_spec.line));
}
if (dap_spec.watch.has_value()) {
auto watch_res = BuildWatchSpec(*dap_spec.watch, arch);
if (watch_res.has_error()) {
return watch_res.err();
}
spec.watch = watch_res.value();
}
if (dap_spec.condition.has_value()) {
spec.condition = *dap_spec.condition;
}
if (dap_spec.frame.has_value()) {
spec.frame = *dap_spec.frame;
}
if (dap_spec.backtrace.has_value()) {
spec.backtrace = *dap_spec.backtrace;
}
if (dap_spec.raw_backtrace.has_value()) {
spec.raw_backtrace = *dap_spec.raw_backtrace;
if (spec.raw_backtrace) {
spec.backtrace = true;
}
}
if (dap_spec.locals.has_value()) {
spec.locals = *dap_spec.locals;
}
if (dap_spec.registers.has_value()) {
spec.registers = *dap_spec.registers;
}
if (dap_spec.expressions.has_value()) {
spec.expressions = *dap_spec.expressions;
}
if (dap_spec.max_matching_frames.has_value()) {
spec.max_matching_frames = static_cast<uint32_t>(*dap_spec.max_matching_frames);
}
return spec;
}
dap::ZxdbTracepointInfo CreateUnverifiedTracepointInfo(int64_t client_id,
const dap::ZxdbTracepointSpec& dap_spec,
const std::string& error_message) {
dap::ZxdbTracepointInfo info;
info.id = client_id;
info.verified = false;
info.message = error_message;
if (dap_spec.max_matching_frames.has_value()) {
info.max_matching_frames = *dap_spec.max_matching_frames;
}
if (dap_spec.enabled.has_value()) {
info.enabled = *dap_spec.enabled;
}
if (dap_spec.max_hits.has_value()) {
info.max_hits = *dap_spec.max_hits;
}
return info;
}
dap::ZxdbTracepointInfo UpsertTracepoint(DebugAdapterContext* ctx, int64_t client_id,
const dap::ZxdbTracepointSpec& dap_spec,
const TracepointSpec& spec) {
auto& existing = ctx->dap_tracepoints();
auto existing_it = existing.find(client_id);
bool is_new = (existing_it == existing.end() || !existing_it->second);
Tracepoint* tp = nullptr;
if (!is_new) {
tp = existing_it->second.get();
} else {
tp = ctx->session()->system().CreateNewTracepoint();
existing[client_id] = tp->GetWeakPtr();
}
if (dap_spec.max_hits.has_value()) {
tp->set_max_hits(static_cast<uint32_t>(*dap_spec.max_hits));
}
if (dap_spec.enabled.has_value() && *dap_spec.enabled != tp->enabled()) {
tp->SetEnabled(*dap_spec.enabled);
}
if (is_new || !(tp->spec() == spec)) {
tp->SetSpec(spec);
}
dap::ZxdbTracepointInfo info;
info.id = client_id;
info.verified = tp->verified();
if (tp->spec().max_matching_frames.has_value()) {
info.max_matching_frames = static_cast<int64_t>(*tp->spec().max_matching_frames);
}
info.hit_count = tp->hit_count();
info.max_hits = tp->max_hits();
info.enabled = tp->enabled();
info.auto_disabled = tp->auto_disabled();
return info;
}
} // namespace
dap::ResponseOrError<dap::ZxdbSetTracepointsResponse> OnRequestZxdbSetTracepoints(
DebugAdapterContext* ctx, const dap::ZxdbSetTracepointsRequest& req) {
if (!ctx || !ctx->session()) {
return dap::Error("No active debug session.");
}
auto& existing = ctx->dap_tracepoints();
auto assign_res = AssignAndValidateClientIds(req.tracepoints, existing);
if (assign_res.has_error()) {
return dap::Error(assign_res.err().msg());
}
const auto& assigned_ids = assign_res.value().assigned_ids;
const auto& incoming_ids = assign_res.value().incoming_ids;
PruneRemovedTracepoints(ctx->session(), incoming_ids, existing);
dap::ZxdbSetTracepointsResponse response;
response.tracepoints.reserve(req.tracepoints.size());
for (size_t i = 0; i < req.tracepoints.size(); ++i) {
const auto& dap_spec = req.tracepoints[i];
int64_t client_id = assigned_ids[i];
auto cleanup_existing = [&]() {
auto existing_it = existing.find(client_id);
if (existing_it != existing.end()) {
if (existing_it->second) {
ctx->session()->system().DeleteTracepoint(existing_it->second.get());
}
existing.erase(client_id);
}
};
auto spec_or_err = BuildTracepointSpec(dap_spec, ctx->session()->arch());
if (spec_or_err.has_error()) {
cleanup_existing();
response.tracepoints.push_back(
CreateUnverifiedTracepointInfo(client_id, dap_spec, spec_or_err.err().msg()));
continue;
}
const TracepointSpec& spec = spec_or_err.value();
Err valid_err = spec.Validate();
if (valid_err.has_error()) {
cleanup_existing();
response.tracepoints.push_back(
CreateUnverifiedTracepointInfo(client_id, dap_spec, valid_err.msg()));
continue;
}
response.tracepoints.push_back(UpsertTracepoint(ctx, client_id, dap_spec, spec));
}
return response;
}
} // namespace zxdb