[Lldb-commits] [lldb] [lldb-dap] Refactor remaining request handlers (NFC)Remaining request handlers (PR #128551)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Mon Feb 24 11:10:37 PST 2025
https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/128551
Continuation of the work started in https://github.com/llvm/llvm-project/pull/128262. Builds on top of https://github.com/llvm/llvm-project/pull/128550.
>From 0bf14ccd21f06ee3c53d64b5139760072d6405b1 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sun, 23 Feb 2025 21:07:55 -0600
Subject: [PATCH 1/4] [lldb-dap] Refactor stepping related request handlers
(NFC)
Continuation of the work started in #128262.
---
lldb/tools/lldb-dap/CMakeLists.txt | 4 +
.../lldb-dap/Handler/NextRequestHandler.cpp | 79 ++++
.../tools/lldb-dap/Handler/RequestHandler.cpp | 7 +
lldb/tools/lldb-dap/Handler/RequestHandler.h | 33 +-
.../lldb-dap/Handler/StepInRequestHandler.cpp | 96 +++++
.../Handler/StepInTargetsRequestHandler.cpp | 149 ++++++++
.../Handler/StepOutRequestHandler.cpp | 68 ++++
lldb/tools/lldb-dap/lldb-dap.cpp | 344 +-----------------
8 files changed, 440 insertions(+), 340 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/NextRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/StepInRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/StepOutRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 73762af5c2fd7..61271e1a9f2a6 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -47,8 +47,12 @@ add_lldb_tool(lldb-dap
Handler/ExceptionInfoRequestHandler.cpp
Handler/InitializeRequestHandler.cpp
Handler/LaunchRequestHandler.cpp
+ Handler/NextRequestHandler.cpp
Handler/RequestHandler.cpp
Handler/RestartRequestHandler.cpp
+ Handler/StepInRequestHandler.cpp
+ Handler/StepInTargetsRequestHandler.cpp
+ Handler/StepOutRequestHandler.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/Handler/NextRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/NextRequestHandler.cpp
new file mode 100644
index 0000000000000..695703fe301b3
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/NextRequestHandler.cpp
@@ -0,0 +1,79 @@
+//===-- NextRequestHandler.cpp --------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "NextRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Next request; value of command field is 'next'. The
+// request starts the debuggee to run again for one step.
+// The debug adapter first sends the NextResponse and then
+// a StoppedEvent (event type 'step') after the step has
+// completed.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "next" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/NextArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "NextArguments": {
+// "type": "object",
+// "description": "Arguments for 'next' request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Execute 'next' for this thread."
+// },
+// "granularity": {
+// "$ref": "#/definitions/SteppingGranularity",
+// "description": "Stepping granularity. If no granularity is specified, a
+// granularity of `statement` is assumed."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "NextResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'next' request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// }
+void NextRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ lldb::SBThread thread = dap.GetLLDBThread(*arguments);
+ if (thread.IsValid()) {
+ // Remember the thread ID that caused the resume so we can set the
+ // "threadCausedFocus" boolean value in the "stopped" events.
+ dap.focus_tid = thread.GetThreadID();
+ if (HasInstructionGranularity(*arguments)) {
+ thread.StepInstruction(/*step_over=*/true);
+ } else {
+ thread.StepOver();
+ }
+ } else {
+ response["success"] = llvm::json::Value(false);
+ }
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index c09ddf55dd5e9..3b1c2b0dc7e31 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -225,4 +225,11 @@ void RequestHandler::PrintWelcomeMessage() {
#endif
}
+bool RequestHandler::HasInstructionGranularity(
+ const llvm::json::Object &request) {
+ if (std::optional<llvm::StringRef> value = request.getString("granularity"))
+ return value == "instruction";
+ return false;
+}
+
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 9bc8e60dbb858..2610a3d21ebc4 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -30,6 +30,7 @@ class RequestHandler {
virtual void operator()(const llvm::json::Object &request) = 0;
+protected:
/// Helpers used by multiple request handlers.
/// FIXME: Move these into the DAP class?
/// @{
@@ -48,9 +49,11 @@ class RequestHandler {
// This way we can reuse the process launching logic for RestartRequest too.
lldb::SBError LaunchProcess(const llvm::json::Object &request);
+ // Check if the step-granularity is `instruction`.
+ bool HasInstructionGranularity(const llvm::json::Object &request);
+
/// @}
-protected:
DAP &dap;
};
@@ -131,6 +134,34 @@ class RestartRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class NextRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "next"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class StepInRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "stepIn"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class StepInTargetsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "stepInTargets"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class StepOutRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "stepOut"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/Handler/StepInRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StepInRequestHandler.cpp
new file mode 100644
index 0000000000000..f435436734538
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/StepInRequestHandler.cpp
@@ -0,0 +1,96 @@
+//===-- StepInRequestHandler.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "StepInRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "StepIn request; value of command field is 'stepIn'. The
+// request starts the debuggee to step into a function/method if possible.
+// If it cannot step into a target, 'stepIn' behaves like 'next'. The debug
+// adapter first sends the StepInResponse and then a StoppedEvent (event
+// type 'step') after the step has completed. If there are multiple
+// function/method calls (or other targets) on the source line, the optional
+// argument 'targetId' can be used to control into which target the 'stepIn'
+// should occur. The list of possible targets for a given source line can be
+// retrieved via the 'stepInTargets' request.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "stepIn" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/StepInArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "StepInArguments": {
+// "type": "object",
+// "description": "Arguments for 'stepIn' request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Execute 'stepIn' for this thread."
+// },
+// "targetId": {
+// "type": "integer",
+// "description": "Optional id of the target to step into."
+// },
+// "granularity": {
+// "$ref": "#/definitions/SteppingGranularity",
+// "description": "Stepping granularity. If no granularity is specified, a
+// granularity of `statement` is assumed."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "StepInResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'stepIn' request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// }
+void StepInRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+
+ std::string step_in_target;
+ uint64_t target_id = GetUnsigned(arguments, "targetId", 0);
+ auto it = dap.step_in_targets.find(target_id);
+ if (it != dap.step_in_targets.end())
+ step_in_target = it->second;
+
+ const bool single_thread = GetBoolean(arguments, "singleThread", false);
+ lldb::RunMode run_mode =
+ single_thread ? lldb::eOnlyThisThread : lldb::eOnlyDuringStepping;
+ lldb::SBThread thread = dap.GetLLDBThread(*arguments);
+ if (thread.IsValid()) {
+ // Remember the thread ID that caused the resume so we can set the
+ // "threadCausedFocus" boolean value in the "stopped" events.
+ dap.focus_tid = thread.GetThreadID();
+ if (HasInstructionGranularity(*arguments)) {
+ thread.StepInstruction(/*step_over=*/false);
+ } else {
+ thread.StepInto(step_in_target.c_str(), run_mode);
+ }
+ } else {
+ response["success"] = llvm::json::Value(false);
+ }
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp
new file mode 100644
index 0000000000000..e771780711ae9
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp
@@ -0,0 +1,149 @@
+//===-- StepInTargetsRequestHandler.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBInstruction.h"
+
+namespace lldb_dap {
+
+// "StepInTargetsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "This request retrieves the possible step-in targets for
+// the specified stack frame.\nThese targets can be used in the `stepIn`
+// request.\nClients should only call this request if the corresponding
+// capability `supportsStepInTargetsRequest` is true.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "stepInTargets" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/StepInTargetsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "StepInTargetsArguments": {
+// "type": "object",
+// "description": "Arguments for `stepInTargets` request.",
+// "properties": {
+// "frameId": {
+// "type": "integer",
+// "description": "The stack frame for which to retrieve the possible
+// step-in targets."
+// }
+// },
+// "required": [ "frameId" ]
+// },
+// "StepInTargetsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `stepInTargets` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "targets": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/StepInTarget"
+// },
+// "description": "The possible step-in targets of the specified
+// source location."
+// }
+// },
+// "required": [ "targets" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void StepInTargetsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+
+ dap.step_in_targets.clear();
+ lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+ if (frame.IsValid()) {
+ lldb::SBAddress pc_addr = frame.GetPCAddress();
+ lldb::SBAddress line_end_addr =
+ pc_addr.GetLineEntry().GetSameLineContiguousAddressRangeEnd(true);
+ lldb::SBInstructionList insts = dap.target.ReadInstructions(
+ pc_addr, line_end_addr, /*flavor_string=*/nullptr);
+
+ if (!insts.IsValid()) {
+ response["success"] = false;
+ response["message"] = "Failed to get instructions for frame.";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ llvm::json::Array step_in_targets;
+ const auto num_insts = insts.GetSize();
+ for (size_t i = 0; i < num_insts; ++i) {
+ lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
+ if (!inst.IsValid())
+ break;
+
+ lldb::addr_t inst_addr = inst.GetAddress().GetLoadAddress(dap.target);
+
+ // Note: currently only x86/x64 supports flow kind.
+ lldb::InstructionControlFlowKind flow_kind =
+ inst.GetControlFlowKind(dap.target);
+ if (flow_kind == lldb::eInstructionControlFlowKindCall) {
+ // Use call site instruction address as id which is easy to debug.
+ llvm::json::Object step_in_target;
+ step_in_target["id"] = inst_addr;
+
+ llvm::StringRef call_operand_name = inst.GetOperands(dap.target);
+ lldb::addr_t call_target_addr;
+ if (call_operand_name.getAsInteger(0, call_target_addr))
+ continue;
+
+ lldb::SBAddress call_target_load_addr =
+ dap.target.ResolveLoadAddress(call_target_addr);
+ if (!call_target_load_addr.IsValid())
+ continue;
+
+ // The existing ThreadPlanStepInRange only accept step in target
+ // function with debug info.
+ lldb::SBSymbolContext sc = dap.target.ResolveSymbolContextForAddress(
+ call_target_load_addr, lldb::eSymbolContextFunction);
+
+ // The existing ThreadPlanStepInRange only accept step in target
+ // function with debug info.
+ std::string step_in_target_name;
+ if (sc.IsValid() && sc.GetFunction().IsValid())
+ step_in_target_name = sc.GetFunction().GetDisplayName();
+
+ // Skip call sites if we fail to resolve its symbol name.
+ if (step_in_target_name.empty())
+ continue;
+
+ dap.step_in_targets.try_emplace(inst_addr, step_in_target_name);
+ step_in_target.try_emplace("label", step_in_target_name);
+ step_in_targets.emplace_back(std::move(step_in_target));
+ }
+ }
+ llvm::json::Object body;
+ body.try_emplace("targets", std::move(step_in_targets));
+ response.try_emplace("body", std::move(body));
+ } else {
+ response["success"] = llvm::json::Value(false);
+ response["message"] = "Failed to get frame for input frameId.";
+ }
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/StepOutRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StepOutRequestHandler.cpp
new file mode 100644
index 0000000000000..d71547d579f1f
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/StepOutRequestHandler.cpp
@@ -0,0 +1,68 @@
+//===-- StepOutRequestHandler.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "StepOutRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "StepOut request; value of command field is 'stepOut'. The
+// request starts the debuggee to run again for one step. The debug adapter
+// first sends the StepOutResponse and then a StoppedEvent (event type
+// 'step') after the step has completed.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "stepOut" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/StepOutArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "StepOutArguments": {
+// "type": "object",
+// "description": "Arguments for 'stepOut' request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Execute 'stepOut' for this thread."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "StepOutResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'stepOut' request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// }
+void StepOutRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ lldb::SBThread thread = dap.GetLLDBThread(*arguments);
+ if (thread.IsValid()) {
+ // Remember the thread ID that caused the resume so we can set the
+ // "threadCausedFocus" boolean value in the "stopped" events.
+ dap.focus_tid = thread.GetThreadID();
+ thread.StepOut();
+ } else {
+ response["success"] = llvm::json::Value(false);
+ }
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 7935e88dba71a..22fff86066659 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -367,77 +367,6 @@ void request_modules(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// Check if the step-granularity is `instruction`
-static bool hasInstructionGranularity(const llvm::json::Object &requestArgs) {
- if (std::optional<llvm::StringRef> value =
- requestArgs.getString("granularity"))
- return value == "instruction";
- return false;
-}
-
-// "NextRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Next request; value of command field is 'next'. The
-// request starts the debuggee to run again for one step.
-// The debug adapter first sends the NextResponse and then
-// a StoppedEvent (event type 'step') after the step has
-// completed.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "next" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/NextArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "NextArguments": {
-// "type": "object",
-// "description": "Arguments for 'next' request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Execute 'next' for this thread."
-// },
-// "granularity": {
-// "$ref": "#/definitions/SteppingGranularity",
-// "description": "Stepping granularity. If no granularity is specified, a
-// granularity of `statement` is assumed."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "NextResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'next' request. This is just an
-// acknowledgement, so no body field is required."
-// }]
-// }
-void request_next(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- lldb::SBThread thread = dap.GetLLDBThread(*arguments);
- if (thread.IsValid()) {
- // Remember the thread ID that caused the resume so we can set the
- // "threadCausedFocus" boolean value in the "stopped" events.
- dap.focus_tid = thread.GetThreadID();
- if (hasInstructionGranularity(*arguments)) {
- thread.StepInstruction(/*step_over=*/true);
- } else {
- thread.StepOver();
- }
- } else {
- response["success"] = llvm::json::Value(false);
- }
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "PauseRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -1372,269 +1301,6 @@ void request_stackTrace(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// "StepInRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "StepIn request; value of command field is 'stepIn'. The
-// request starts the debuggee to step into a function/method if possible.
-// If it cannot step into a target, 'stepIn' behaves like 'next'. The debug
-// adapter first sends the StepInResponse and then a StoppedEvent (event
-// type 'step') after the step has completed. If there are multiple
-// function/method calls (or other targets) on the source line, the optional
-// argument 'targetId' can be used to control into which target the 'stepIn'
-// should occur. The list of possible targets for a given source line can be
-// retrieved via the 'stepInTargets' request.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "stepIn" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/StepInArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "StepInArguments": {
-// "type": "object",
-// "description": "Arguments for 'stepIn' request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Execute 'stepIn' for this thread."
-// },
-// "targetId": {
-// "type": "integer",
-// "description": "Optional id of the target to step into."
-// },
-// "granularity": {
-// "$ref": "#/definitions/SteppingGranularity",
-// "description": "Stepping granularity. If no granularity is specified, a
-// granularity of `statement` is assumed."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "StepInResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'stepIn' request. This is just an
-// acknowledgement, so no body field is required."
-// }]
-// }
-void request_stepIn(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
-
- std::string step_in_target;
- uint64_t target_id = GetUnsigned(arguments, "targetId", 0);
- auto it = dap.step_in_targets.find(target_id);
- if (it != dap.step_in_targets.end())
- step_in_target = it->second;
-
- const bool single_thread = GetBoolean(arguments, "singleThread", false);
- lldb::RunMode run_mode =
- single_thread ? lldb::eOnlyThisThread : lldb::eOnlyDuringStepping;
- lldb::SBThread thread = dap.GetLLDBThread(*arguments);
- if (thread.IsValid()) {
- // Remember the thread ID that caused the resume so we can set the
- // "threadCausedFocus" boolean value in the "stopped" events.
- dap.focus_tid = thread.GetThreadID();
- if (hasInstructionGranularity(*arguments)) {
- thread.StepInstruction(/*step_over=*/false);
- } else {
- thread.StepInto(step_in_target.c_str(), run_mode);
- }
- } else {
- response["success"] = llvm::json::Value(false);
- }
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "StepInTargetsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "This request retrieves the possible step-in targets for
-// the specified stack frame.\nThese targets can be used in the `stepIn`
-// request.\nClients should only call this request if the corresponding
-// capability `supportsStepInTargetsRequest` is true.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "stepInTargets" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/StepInTargetsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "StepInTargetsArguments": {
-// "type": "object",
-// "description": "Arguments for `stepInTargets` request.",
-// "properties": {
-// "frameId": {
-// "type": "integer",
-// "description": "The stack frame for which to retrieve the possible
-// step-in targets."
-// }
-// },
-// "required": [ "frameId" ]
-// },
-// "StepInTargetsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `stepInTargets` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "targets": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/StepInTarget"
-// },
-// "description": "The possible step-in targets of the specified
-// source location."
-// }
-// },
-// "required": [ "targets" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_stepInTargets(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
-
- dap.step_in_targets.clear();
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
- if (frame.IsValid()) {
- lldb::SBAddress pc_addr = frame.GetPCAddress();
- lldb::SBAddress line_end_addr =
- pc_addr.GetLineEntry().GetSameLineContiguousAddressRangeEnd(true);
- lldb::SBInstructionList insts = dap.target.ReadInstructions(
- pc_addr, line_end_addr, /*flavor_string=*/nullptr);
-
- if (!insts.IsValid()) {
- response["success"] = false;
- response["message"] = "Failed to get instructions for frame.";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- llvm::json::Array step_in_targets;
- const auto num_insts = insts.GetSize();
- for (size_t i = 0; i < num_insts; ++i) {
- lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
- if (!inst.IsValid())
- break;
-
- lldb::addr_t inst_addr = inst.GetAddress().GetLoadAddress(dap.target);
-
- // Note: currently only x86/x64 supports flow kind.
- lldb::InstructionControlFlowKind flow_kind =
- inst.GetControlFlowKind(dap.target);
- if (flow_kind == lldb::eInstructionControlFlowKindCall) {
- // Use call site instruction address as id which is easy to debug.
- llvm::json::Object step_in_target;
- step_in_target["id"] = inst_addr;
-
- llvm::StringRef call_operand_name = inst.GetOperands(dap.target);
- lldb::addr_t call_target_addr;
- if (call_operand_name.getAsInteger(0, call_target_addr))
- continue;
-
- lldb::SBAddress call_target_load_addr =
- dap.target.ResolveLoadAddress(call_target_addr);
- if (!call_target_load_addr.IsValid())
- continue;
-
- // The existing ThreadPlanStepInRange only accept step in target
- // function with debug info.
- lldb::SBSymbolContext sc = dap.target.ResolveSymbolContextForAddress(
- call_target_load_addr, lldb::eSymbolContextFunction);
-
- // The existing ThreadPlanStepInRange only accept step in target
- // function with debug info.
- std::string step_in_target_name;
- if (sc.IsValid() && sc.GetFunction().IsValid())
- step_in_target_name = sc.GetFunction().GetDisplayName();
-
- // Skip call sites if we fail to resolve its symbol name.
- if (step_in_target_name.empty())
- continue;
-
- dap.step_in_targets.try_emplace(inst_addr, step_in_target_name);
- step_in_target.try_emplace("label", step_in_target_name);
- step_in_targets.emplace_back(std::move(step_in_target));
- }
- }
- llvm::json::Object body;
- body.try_emplace("targets", std::move(step_in_targets));
- response.try_emplace("body", std::move(body));
- } else {
- response["success"] = llvm::json::Value(false);
- response["message"] = "Failed to get frame for input frameId.";
- }
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "StepOutRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "StepOut request; value of command field is 'stepOut'. The
-// request starts the debuggee to run again for one step. The debug adapter
-// first sends the StepOutResponse and then a StoppedEvent (event type
-// 'step') after the step has completed.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "stepOut" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/StepOutArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "StepOutArguments": {
-// "type": "object",
-// "description": "Arguments for 'stepOut' request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Execute 'stepOut' for this thread."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "StepOutResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'stepOut' request. This is just an
-// acknowledgement, so no body field is required."
-// }]
-// }
-void request_stepOut(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- lldb::SBThread thread = dap.GetLLDBThread(*arguments);
- if (thread.IsValid()) {
- // Remember the thread ID that caused the resume so we can set the
- // "threadCausedFocus" boolean value in the "stopped" events.
- dap.focus_tid = thread.GetThreadID();
- thread.StepOut();
- } else {
- response["success"] = llvm::json::Value(false);
- }
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "ThreadsRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -2777,16 +2443,19 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<AttachRequestHandler>();
dap.RegisterRequest<BreakpointLocationsRequestHandler>();
dap.RegisterRequest<CompletionsRequestHandler>();
- dap.RegisterRequest<ContinueRequestHandler>();
dap.RegisterRequest<ConfigurationDoneRequestHandler>();
+ dap.RegisterRequest<ContinueRequestHandler>();
dap.RegisterRequest<DisconnectRequestHandler>();
dap.RegisterRequest<EvaluateRequestHandler>();
dap.RegisterRequest<ExceptionInfoRequestHandler>();
dap.RegisterRequest<InitializeRequestHandler>();
dap.RegisterRequest<LaunchRequestHandler>();
+ dap.RegisterRequest<NextRequestHandler>();
dap.RegisterRequest<RestartRequestHandler>();
+ dap.RegisterRequest<StepInRequestHandler>();
+ dap.RegisterRequest<StepInTargetsRequestHandler>();
+ dap.RegisterRequest<StepOutRequestHandler>();
- dap.RegisterRequestCallback("next", request_next);
dap.RegisterRequestCallback("pause", request_pause);
dap.RegisterRequestCallback("scopes", request_scopes);
dap.RegisterRequestCallback("setBreakpoints", request_setBreakpoints);
@@ -2799,9 +2468,6 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("setVariable", request_setVariable);
dap.RegisterRequestCallback("source", request_source);
dap.RegisterRequestCallback("stackTrace", request_stackTrace);
- dap.RegisterRequestCallback("stepIn", request_stepIn);
- dap.RegisterRequestCallback("stepInTargets", request_stepInTargets);
- dap.RegisterRequestCallback("stepOut", request_stepOut);
dap.RegisterRequestCallback("threads", request_threads);
dap.RegisterRequestCallback("variables", request_variables);
dap.RegisterRequestCallback("locations", request_locations);
>From d4c15e9c4c1f1822985c25a979f3e872cbaae57c Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 24 Feb 2025 11:28:17 -0600
Subject: [PATCH 2/4] [lldb-dap] Refactor custom & testing related request
handlers (NFC)
---
lldb/tools/lldb-dap/CMakeLists.txt | 3 +
.../Handler/CompileUnitsRequestHandler.cpp | 80 ++++++++++
.../Handler/ModulesRequestHandler.cpp | 58 ++++++++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 27 ++++
...TestGetTargetBreakpointsRequestHandler.cpp | 31 ++++
lldb/tools/lldb-dap/lldb-dap.cpp | 138 +-----------------
6 files changed, 206 insertions(+), 131 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/TestGetTargetBreakpointsRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 61271e1a9f2a6..688a2e448f71d 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -39,6 +39,7 @@ add_lldb_tool(lldb-dap
Handler/AttachRequestHandler.cpp
Handler/BreakpointLocationsHandler.cpp
+ Handler/CompileUnitsRequestHandler.cpp
Handler/CompletionsHandler.cpp
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
@@ -47,11 +48,13 @@ add_lldb_tool(lldb-dap
Handler/ExceptionInfoRequestHandler.cpp
Handler/InitializeRequestHandler.cpp
Handler/LaunchRequestHandler.cpp
+ Handler/ModulesRequestHandler.cpp
Handler/NextRequestHandler.cpp
Handler/RequestHandler.cpp
Handler/RestartRequestHandler.cpp
Handler/StepInRequestHandler.cpp
Handler/StepInTargetsRequestHandler.cpp
+ Handler/TestGetTargetBreakpointsRequestHandler.cpp
Handler/StepOutRequestHandler.cpp
LINK_LIBS
diff --git a/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp
new file mode 100644
index 0000000000000..c541d1cd039c8
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp
@@ -0,0 +1,80 @@
+//===-- CompileUnitsRequestHandler.cpp ------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "compileUnitsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Compile Unit request; value of command field is
+// 'compileUnits'.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "compileUnits" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/compileUnitRequestArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "compileUnitsRequestArguments": {
+// "type": "object",
+// "description": "Arguments for 'compileUnits' request.",
+// "properties": {
+// "moduleId": {
+// "type": "string",
+// "description": "The ID of the module."
+// }
+// },
+// "required": [ "moduleId" ]
+// },
+// "compileUnitsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'compileUnits' request.",
+// "properties": {
+// "body": {
+// "description": "Response to 'compileUnits' request. Array of
+// paths of compile units."
+// }
+// }
+// }]
+// }
+void CompileUnitsRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+ llvm::json::Array units;
+ const auto *arguments = request.getObject("arguments");
+ std::string module_id = std::string(GetString(arguments, "moduleId"));
+ int num_modules = dap.target.GetNumModules();
+ for (int i = 0; i < num_modules; i++) {
+ auto curr_module = dap.target.GetModuleAtIndex(i);
+ if (module_id == curr_module.GetUUIDString()) {
+ int num_units = curr_module.GetNumCompileUnits();
+ for (int j = 0; j < num_units; j++) {
+ auto curr_unit = curr_module.GetCompileUnitAtIndex(j);
+ units.emplace_back(CreateCompileUnit(curr_unit));
+ }
+ body.try_emplace("compileUnits", std::move(units));
+ break;
+ }
+ }
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
new file mode 100644
index 0000000000000..f72faa7be8963
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
@@ -0,0 +1,58 @@
+//===-- ModulesRequestHandler.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "modulesRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Modules request; value of command field is
+// 'modules'.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "modules" ]
+// },
+// },
+// "required": [ "command" ]
+// }]
+// },
+// "modulesResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'modules' request.",
+// "properties": {
+// "body": {
+// "description": "Response to 'modules' request. Array of
+// module objects."
+// }
+// }
+// }]
+// }
+void ModulesRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+
+ llvm::json::Array modules;
+ for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
+ lldb::SBModule module = dap.target.GetModuleAtIndex(i);
+ modules.emplace_back(CreateModule(dap.target, module));
+ }
+
+ llvm::json::Object body;
+ body.try_emplace("modules", std::move(modules));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 2610a3d21ebc4..874b600181f43 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -162,6 +162,33 @@ class StepOutRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class CompileUnitsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "compileUnits"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class ModulesRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "modules"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+/// A request used in testing to get the details on all breakpoints that are
+/// currently set in the target. This helps us to test "setBreakpoints" and
+/// "setFunctionBreakpoints" requests to verify we have the correct set of
+/// breakpoints currently set in LLDB.
+class TestGetTargetBreakpointsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() {
+ return "_testGetTargetBreakpoints";
+ }
+ void operator()(const llvm::json::Object &request) override;
+};
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/Handler/TestGetTargetBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/TestGetTargetBreakpointsRequestHandler.cpp
new file mode 100644
index 0000000000000..ad012d75f9059
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/TestGetTargetBreakpointsRequestHandler.cpp
@@ -0,0 +1,31 @@
+//===-- TestGetTargetBreakpointsRequestHandler.cpp ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+void TestGetTargetBreakpointsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Array response_breakpoints;
+ for (uint32_t i = 0; dap.target.GetBreakpointAtIndex(i).IsValid(); ++i) {
+ auto bp = Breakpoint(dap, dap.target.GetBreakpointAtIndex(i));
+ AppendBreakpoint(&bp, response_breakpoints);
+ }
+ llvm::json::Object body;
+ body.try_emplace("breakpoints", std::move(response_breakpoints));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 22fff86066659..632629d56232c 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -261,112 +261,6 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
return reached_end_of_stack;
}
-// "compileUnitsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Compile Unit request; value of command field is
-// 'compileUnits'.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "compileUnits" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/compileUnitRequestArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "compileUnitsRequestArguments": {
-// "type": "object",
-// "description": "Arguments for 'compileUnits' request.",
-// "properties": {
-// "moduleId": {
-// "type": "string",
-// "description": "The ID of the module."
-// }
-// },
-// "required": [ "moduleId" ]
-// },
-// "compileUnitsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'compileUnits' request.",
-// "properties": {
-// "body": {
-// "description": "Response to 'compileUnits' request. Array of
-// paths of compile units."
-// }
-// }
-// }]
-// }
-void request_compileUnits(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- llvm::json::Array units;
- const auto *arguments = request.getObject("arguments");
- std::string module_id = std::string(GetString(arguments, "moduleId"));
- int num_modules = dap.target.GetNumModules();
- for (int i = 0; i < num_modules; i++) {
- auto curr_module = dap.target.GetModuleAtIndex(i);
- if (module_id == curr_module.GetUUIDString()) {
- int num_units = curr_module.GetNumCompileUnits();
- for (int j = 0; j < num_units; j++) {
- auto curr_unit = curr_module.GetCompileUnitAtIndex(j);
- units.emplace_back(CreateCompileUnit(curr_unit));
- }
- body.try_emplace("compileUnits", std::move(units));
- break;
- }
- }
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "modulesRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Modules request; value of command field is
-// 'modules'.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "modules" ]
-// },
-// },
-// "required": [ "command" ]
-// }]
-// },
-// "modulesResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'modules' request.",
-// "properties": {
-// "body": {
-// "description": "Response to 'modules' request. Array of
-// module objects."
-// }
-// }
-// }]
-// }
-void request_modules(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
-
- llvm::json::Array modules;
- for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
- lldb::SBModule module = dap.target.GetModuleAtIndex(i);
- modules.emplace_back(CreateModule(dap.target, module));
- }
-
- llvm::json::Object body;
- body.try_emplace("modules", std::move(modules));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "PauseRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -2187,25 +2081,6 @@ void request_readMemory(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// A request used in testing to get the details on all breakpoints that are
-// currently set in the target. This helps us to test "setBreakpoints" and
-// "setFunctionBreakpoints" requests to verify we have the correct set of
-// breakpoints currently set in LLDB.
-void request__testGetTargetBreakpoints(DAP &dap,
- const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Array response_breakpoints;
- for (uint32_t i = 0; dap.target.GetBreakpointAtIndex(i).IsValid(); ++i) {
- auto bp = Breakpoint(dap, dap.target.GetBreakpointAtIndex(i));
- AppendBreakpoint(&bp, response_breakpoints);
- }
- llvm::json::Object body;
- body.try_emplace("breakpoints", std::move(response_breakpoints));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "SetInstructionBreakpointsRequest": {
// "allOf": [
// {"$ref": "#/definitions/Request"},
@@ -2456,6 +2331,13 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<StepInTargetsRequestHandler>();
dap.RegisterRequest<StepOutRequestHandler>();
+ // Custom requests
+ dap.RegisterRequest<CompileUnitsRequestHandler>();
+ dap.RegisterRequest<ModulesRequestHandler>();
+
+ // Testing requests
+ dap.RegisterRequest<TestGetTargetBreakpointsRequestHandler>();
+
dap.RegisterRequestCallback("pause", request_pause);
dap.RegisterRequestCallback("scopes", request_scopes);
dap.RegisterRequestCallback("setBreakpoints", request_setBreakpoints);
@@ -2475,12 +2357,6 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("readMemory", request_readMemory);
dap.RegisterRequestCallback("setInstructionBreakpoints",
request_setInstructionBreakpoints);
- // Custom requests
- dap.RegisterRequestCallback("compileUnits", request_compileUnits);
- dap.RegisterRequestCallback("modules", request_modules);
- // Testing requests
- dap.RegisterRequestCallback("_testGetTargetBreakpoints",
- request__testGetTargetBreakpoints);
}
} // anonymous namespace
>From e1f4611f708bc704c6a0a956ce0d8d1e6b9787dc Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 24 Feb 2025 11:51:28 -0600
Subject: [PATCH 3/4] [lldb-dap] Refactor breakpoint related request handlers
(NFC)
---
lldb/tools/lldb-dap/CMakeLists.txt | 7 +-
.../DataBreakpointInfoRequestHandler.cpp | 190 ++++
.../tools/lldb-dap/Handler/RequestHandler.cpp | 55 ++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 47 +
.../Handler/SetBreakpointsRequestHandler.cpp | 182 ++++
.../SetDataBreakpointsRequestHandler.cpp | 114 +++
.../SetExceptionBreakpointsRequestHandler.cpp | 93 ++
.../SetFunctionBreakpointsRequestHandler.cpp | 139 +++
...etInstructionBreakpointsRequestHandler.cpp | 249 +++++
lldb/tools/lldb-dap/lldb-dap.cpp | 880 +-----------------
10 files changed, 1081 insertions(+), 875 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SetDataBreakpointsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SetExceptionBreakpointsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SetFunctionBreakpointsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SetInstructionBreakpointsRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 688a2e448f71d..c04b10861a4c5 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -43,6 +43,7 @@ add_lldb_tool(lldb-dap
Handler/CompletionsHandler.cpp
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
+ Handler/DataBreakpointInfoRequestHandler.cpp
Handler/DisconnectRequestHandler.cpp
Handler/EvaluateRequestHandler.cpp
Handler/ExceptionInfoRequestHandler.cpp
@@ -52,10 +53,14 @@ add_lldb_tool(lldb-dap
Handler/NextRequestHandler.cpp
Handler/RequestHandler.cpp
Handler/RestartRequestHandler.cpp
+ Handler/SetBreakpointsRequestHandler.cpp
+ Handler/SetDataBreakpointsRequestHandler.cpp
+ Handler/SetExceptionBreakpointsRequestHandler.cpp
+ Handler/SetFunctionBreakpointsRequestHandler.cpp
+ Handler/SetInstructionBreakpointsRequestHandler.cpp Handler/StepOutRequestHandler.cpp
Handler/StepInRequestHandler.cpp
Handler/StepInTargetsRequestHandler.cpp
Handler/TestGetTargetBreakpointsRequestHandler.cpp
- Handler/StepOutRequestHandler.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
new file mode 100644
index 0000000000000..519a9c728e4b3
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
@@ -0,0 +1,190 @@
+//===-- DataBreakpointInfoRequestHandler.cpp ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBMemoryRegionInfo.h"
+#include "llvm/ADT/StringExtras.h"
+
+namespace lldb_dap {
+
+// "DataBreakpointInfoRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Obtains information on a possible data breakpoint that
+// could be set on an expression or variable.\nClients should only call this
+// request if the corresponding capability `supportsDataBreakpoints` is
+// true.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "dataBreakpointInfo" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/DataBreakpointInfoArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "DataBreakpointInfoArguments": {
+// "type": "object",
+// "description": "Arguments for `dataBreakpointInfo` request.",
+// "properties": {
+// "variablesReference": {
+// "type": "integer",
+// "description": "Reference to the variable container if the data
+// breakpoint is requested for a child of the container. The
+// `variablesReference` must have been obtained in the current suspended
+// state. See 'Lifetime of Object References' in the Overview section for
+// details."
+// },
+// "name": {
+// "type": "string",
+// "description": "The name of the variable's child to obtain data
+// breakpoint information for.\nIf `variablesReference` isn't specified,
+// this can be an expression."
+// },
+// "frameId": {
+// "type": "integer",
+// "description": "When `name` is an expression, evaluate it in the scope
+// of this stack frame. If not specified, the expression is evaluated in
+// the global scope. When `variablesReference` is specified, this property
+// has no effect."
+// }
+// },
+// "required": [ "name" ]
+// },
+// "DataBreakpointInfoResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `dataBreakpointInfo` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "dataId": {
+// "type": [ "string", "null" ],
+// "description": "An identifier for the data on which a data
+// breakpoint can be registered with the `setDataBreakpoints`
+// request or null if no data breakpoint is available. If a
+// `variablesReference` or `frameId` is passed, the `dataId` is
+// valid in the current suspended state, otherwise it's valid
+// indefinitely. See 'Lifetime of Object References' in the Overview
+// section for details. Breakpoints set using the `dataId` in the
+// `setDataBreakpoints` request may outlive the lifetime of the
+// associated `dataId`."
+// },
+// "description": {
+// "type": "string",
+// "description": "UI string that describes on what data the
+// breakpoint is set on or why a data breakpoint is not available."
+// },
+// "accessTypes": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/DataBreakpointAccessType"
+// },
+// "description": "Attribute lists the available access types for a
+// potential data breakpoint. A UI client could surface this
+// information."
+// },
+// "canPersist": {
+// "type": "boolean",
+// "description": "Attribute indicates that a potential data
+// breakpoint could be persisted across sessions."
+// }
+// },
+// "required": [ "dataId", "description" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void DataBreakpointInfoRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+ lldb::SBError error;
+ llvm::json::Array accessTypes{"read", "write", "readWrite"};
+ const auto *arguments = request.getObject("arguments");
+ const auto variablesReference =
+ GetUnsigned(arguments, "variablesReference", 0);
+ llvm::StringRef name = GetString(arguments, "name");
+ lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+ lldb::SBValue variable = FindVariable(variablesReference, name);
+ std::string addr, size;
+
+ if (variable.IsValid()) {
+ lldb::addr_t load_addr = variable.GetLoadAddress();
+ size_t byte_size = variable.GetByteSize();
+ if (load_addr == LLDB_INVALID_ADDRESS) {
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description",
+ "does not exist in memory, its location is " +
+ std::string(variable.GetLocation()));
+ } else if (byte_size == 0) {
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description", "variable size is 0");
+ } else {
+ addr = llvm::utohexstr(load_addr);
+ size = llvm::utostr(byte_size);
+ }
+ } else if (variablesReference == 0 && frame.IsValid()) {
+ lldb::SBValue value = frame.EvaluateExpression(name.data());
+ if (value.GetError().Fail()) {
+ lldb::SBError error = value.GetError();
+ const char *error_cstr = error.GetCString();
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description", error_cstr && error_cstr[0]
+ ? std::string(error_cstr)
+ : "evaluation failed");
+ } else {
+ uint64_t load_addr = value.GetValueAsUnsigned();
+ lldb::SBData data = value.GetPointeeData();
+ if (data.IsValid()) {
+ size = llvm::utostr(data.GetByteSize());
+ addr = llvm::utohexstr(load_addr);
+ lldb::SBMemoryRegionInfo region;
+ lldb::SBError err =
+ dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
+ // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
+ // request if SBProcess::GetMemoryRegionInfo returns error.
+ if (err.Success()) {
+ if (!(region.IsReadable() || region.IsWritable())) {
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description",
+ "memory region for address " + addr +
+ " has no read or write permissions");
+ }
+ }
+ } else {
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description",
+ "unable to get byte size for expression: " +
+ name.str());
+ }
+ }
+ } else {
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description", "variable not found: " + name.str());
+ }
+
+ if (!body.getObject("dataId")) {
+ body.try_emplace("dataId", addr + "/" + size);
+ body.try_emplace("accessTypes", std::move(accessTypes));
+ body.try_emplace("description",
+ size + " bytes at " + addr + " " + name.str());
+ }
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 3b1c2b0dc7e31..5d7d00bec9bd1 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -232,4 +232,59 @@ bool RequestHandler::HasInstructionGranularity(
return false;
}
+lldb::SBValueList *
+RequestHandler::GetTopLevelScope(int64_t variablesReference) {
+ switch (variablesReference) {
+ case VARREF_LOCALS:
+ return &dap.variables.locals;
+ case VARREF_GLOBALS:
+ return &dap.variables.globals;
+ case VARREF_REGS:
+ return &dap.variables.registers;
+ default:
+ return nullptr;
+ }
+}
+
+lldb::SBValue RequestHandler::FindVariable(uint64_t variablesReference,
+ llvm::StringRef name) {
+ lldb::SBValue variable;
+ if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) {
+ bool is_duplicated_variable_name = name.contains(" @");
+ // variablesReference is one of our scopes, not an actual variable it is
+ // asking for a variable in locals or globals or registers
+ int64_t end_idx = top_scope->GetSize();
+ // Searching backward so that we choose the variable in closest scope
+ // among variables of the same name.
+ for (int64_t i = end_idx - 1; i >= 0; --i) {
+ lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
+ std::string variable_name = CreateUniqueVariableNameForDisplay(
+ curr_variable, is_duplicated_variable_name);
+ if (variable_name == name) {
+ variable = curr_variable;
+ break;
+ }
+ }
+ } else {
+ // This is not under the globals or locals scope, so there are no duplicated
+ // names.
+
+ // We have a named item within an actual variable so we need to find it
+ // withing the container variable by name.
+ lldb::SBValue container = dap.variables.GetVariable(variablesReference);
+ variable = container.GetChildMemberWithName(name.data());
+ if (!variable.IsValid()) {
+ if (name.starts_with("[")) {
+ llvm::StringRef index_str(name.drop_front(1));
+ uint64_t index = 0;
+ if (!index_str.consumeInteger(0, index)) {
+ if (index_str == "]")
+ variable = container.GetChildAtIndex(index);
+ }
+ }
+ }
+ }
+ return variable;
+}
+
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 874b600181f43..a30e0dcc2bd04 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -52,6 +52,9 @@ class RequestHandler {
// Check if the step-granularity is `instruction`.
bool HasInstructionGranularity(const llvm::json::Object &request);
+ lldb::SBValueList *GetTopLevelScope(int64_t variablesReference);
+ lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
+
/// @}
DAP &dap;
@@ -162,6 +165,50 @@ class StepOutRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class SetBreakpointsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "setBreakpoints"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class SetExceptionBreakpointsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "setExceptionBreakpoints"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class SetFunctionBreakpointsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "setFunctionBreakpoints"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class DataBreakpointInfoRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "dataBreakpointInfo"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class SetDataBreakpointsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "setDataBreakpoints"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class SetInstructionBreakpointsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() {
+ return "setInstructionBreakpoints";
+ }
+ void operator()(const llvm::json::Object &request) override;
+};
+
class CompileUnitsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
diff --git a/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp
new file mode 100644
index 0000000000000..6dbd24c130db6
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp
@@ -0,0 +1,182 @@
+//===-- SetBreakpointsRequestHandler.cpp ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "SetBreakpointsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "SetBreakpoints request; value of command field is
+// 'setBreakpoints'. Sets multiple breakpoints for a single source and
+// clears all previous breakpoints in that source. To clear all breakpoint
+// for a source, specify an empty array. When a breakpoint is hit, a
+// StoppedEvent (event type 'breakpoint') is generated.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "setBreakpoints" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SetBreakpointsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SetBreakpointsArguments": {
+// "type": "object",
+// "description": "Arguments for 'setBreakpoints' request.",
+// "properties": {
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "The source location of the breakpoints; either
+// source.path or source.reference must be specified."
+// },
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/SourceBreakpoint"
+// },
+// "description": "The code locations of the breakpoints."
+// },
+// "lines": {
+// "type": "array",
+// "items": {
+// "type": "integer"
+// },
+// "description": "Deprecated: The code locations of the breakpoints."
+// },
+// "sourceModified": {
+// "type": "boolean",
+// "description": "A value of true indicates that the underlying source
+// has been modified which results in new breakpoint locations."
+// }
+// },
+// "required": [ "source" ]
+// },
+// "SetBreakpointsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'setBreakpoints' request. Returned is
+// information about each breakpoint created by this request. This includes
+// the actual code location and whether the breakpoint could be verified.
+// The breakpoints returned are in the same order as the elements of the
+// 'breakpoints' (or the deprecated 'lines') in the
+// SetBreakpointsArguments.", "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/Breakpoint"
+// },
+// "description": "Information about the breakpoints. The array
+// elements are in the same order as the elements of the
+// 'breakpoints' (or the deprecated 'lines') in the
+// SetBreakpointsArguments."
+// }
+// },
+// "required": [ "breakpoints" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// },
+// "SourceBreakpoint": {
+// "type": "object",
+// "description": "Properties of a breakpoint or logpoint passed to the
+// setBreakpoints request.", "properties": {
+// "line": {
+// "type": "integer",
+// "description": "The source line of the breakpoint or logpoint."
+// },
+// "column": {
+// "type": "integer",
+// "description": "An optional source column of the breakpoint."
+// },
+// "condition": {
+// "type": "string",
+// "description": "An optional expression for conditional breakpoints."
+// },
+// "hitCondition": {
+// "type": "string",
+// "description": "An optional expression that controls how many hits of
+// the breakpoint are ignored. The backend is expected to interpret the
+// expression as needed."
+// },
+// "logMessage": {
+// "type": "string",
+// "description": "If this attribute exists and is non-empty, the backend
+// must not 'break' (stop) but log the message instead. Expressions within
+// {} are interpolated."
+// }
+// },
+// "required": [ "line" ]
+// }
+void SetBreakpointsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ lldb::SBError error;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ const auto *source = arguments->getObject("source");
+ const auto path = GetString(source, "path");
+ const auto *breakpoints = arguments->getArray("breakpoints");
+ llvm::json::Array response_breakpoints;
+
+ // Decode the source breakpoint infos for this "setBreakpoints" request
+ SourceBreakpointMap request_bps;
+ // "breakpoints" may be unset, in which case we treat it the same as being set
+ // to an empty array.
+ if (breakpoints) {
+ for (const auto &bp : *breakpoints) {
+ const auto *bp_obj = bp.getAsObject();
+ if (bp_obj) {
+ SourceBreakpoint src_bp(dap, *bp_obj);
+ std::pair<uint32_t, uint32_t> bp_pos(src_bp.line, src_bp.column);
+ request_bps.try_emplace(bp_pos, src_bp);
+ const auto [iv, inserted] =
+ dap.source_breakpoints[path].try_emplace(bp_pos, src_bp);
+ // We check if this breakpoint already exists to update it
+ if (inserted)
+ iv->getSecond().SetBreakpoint(path.data());
+ else
+ iv->getSecond().UpdateBreakpoint(src_bp);
+ AppendBreakpoint(&iv->getSecond(), response_breakpoints, path,
+ src_bp.line);
+ }
+ }
+ }
+
+ // Delete any breakpoints in this source file that aren't in the
+ // request_bps set. There is no call to remove breakpoints other than
+ // calling this function with a smaller or empty "breakpoints" list.
+ auto old_src_bp_pos = dap.source_breakpoints.find(path);
+ if (old_src_bp_pos != dap.source_breakpoints.end()) {
+ for (auto &old_bp : old_src_bp_pos->second) {
+ auto request_pos = request_bps.find(old_bp.first);
+ if (request_pos == request_bps.end()) {
+ // This breakpoint no longer exists in this source file, delete it
+ dap.target.BreakpointDelete(old_bp.second.bp.GetID());
+ old_src_bp_pos->second.erase(old_bp.first);
+ }
+ }
+ }
+
+ llvm::json::Object body;
+ body.try_emplace("breakpoints", std::move(response_breakpoints));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/SetDataBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetDataBreakpointsRequestHandler.cpp
new file mode 100644
index 0000000000000..9c2308f7a6bcd
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SetDataBreakpointsRequestHandler.cpp
@@ -0,0 +1,114 @@
+//===-- SetDataBreakpointsRequestHandler.cpp ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "Watchpoint.h"
+#include <set>
+
+namespace lldb_dap {
+
+// "SetDataBreakpointsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Replaces all existing data breakpoints with new data
+// breakpoints.\nTo clear all data breakpoints, specify an empty
+// array.\nWhen a data breakpoint is hit, a `stopped` event (with reason
+// `data breakpoint`) is generated.\nClients should only call this request
+// if the corresponding capability `supportsDataBreakpoints` is true.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "setDataBreakpoints" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SetDataBreakpointsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SetDataBreakpointsArguments": {
+// "type": "object",
+// "description": "Arguments for `setDataBreakpoints` request.",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/DataBreakpoint"
+// },
+// "description": "The contents of this array replaces all existing data
+// breakpoints. An empty array clears all data breakpoints."
+// }
+// },
+// "required": [ "breakpoints" ]
+// },
+// "SetDataBreakpointsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `setDataBreakpoints` request.\nReturned is
+// information about each breakpoint created by this request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/Breakpoint"
+// },
+// "description": "Information about the data breakpoints. The array
+// elements correspond to the elements of the input argument
+// `breakpoints` array."
+// }
+// },
+// "required": [ "breakpoints" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void SetDataBreakpointsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ lldb::SBError error;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ const auto *breakpoints = arguments->getArray("breakpoints");
+ llvm::json::Array response_breakpoints;
+ dap.target.DeleteAllWatchpoints();
+ std::vector<Watchpoint> watchpoints;
+ if (breakpoints) {
+ for (const auto &bp : *breakpoints) {
+ const auto *bp_obj = bp.getAsObject();
+ if (bp_obj)
+ watchpoints.emplace_back(dap, *bp_obj);
+ }
+ }
+ // If two watchpoints start at the same address, the latter overwrite the
+ // former. So, we only enable those at first-seen addresses when iterating
+ // backward.
+ std::set<lldb::addr_t> addresses;
+ for (auto iter = watchpoints.rbegin(); iter != watchpoints.rend(); ++iter) {
+ if (addresses.count(iter->addr) == 0) {
+ iter->SetWatchpoint();
+ addresses.insert(iter->addr);
+ }
+ }
+ for (auto wp : watchpoints)
+ AppendBreakpoint(&wp, response_breakpoints);
+
+ llvm::json::Object body;
+ body.try_emplace("breakpoints", std::move(response_breakpoints));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/SetExceptionBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetExceptionBreakpointsRequestHandler.cpp
new file mode 100644
index 0000000000000..d208525385094
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SetExceptionBreakpointsRequestHandler.cpp
@@ -0,0 +1,93 @@
+//===-- SetExceptionBreakpointsRequestHandler.cpp -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include <set>
+
+namespace lldb_dap {
+
+// "SetExceptionBreakpointsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "SetExceptionBreakpoints request; value of command field
+// is 'setExceptionBreakpoints'. The request configures the debuggers
+// response to thrown exceptions. If an exception is configured to break, a
+// StoppedEvent is fired (event type 'exception').", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "setExceptionBreakpoints" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SetExceptionBreakpointsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SetExceptionBreakpointsArguments": {
+// "type": "object",
+// "description": "Arguments for 'setExceptionBreakpoints' request.",
+// "properties": {
+// "filters": {
+// "type": "array",
+// "items": {
+// "type": "string"
+// },
+// "description": "IDs of checked exception options. The set of IDs is
+// returned via the 'exceptionBreakpointFilters' capability."
+// },
+// "exceptionOptions": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/ExceptionOptions"
+// },
+// "description": "Configuration options for selected exceptions."
+// }
+// },
+// "required": [ "filters" ]
+// },
+// "SetExceptionBreakpointsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'setExceptionBreakpoints' request. This is
+// just an acknowledgement, so no body field is required."
+// }]
+// }
+void SetExceptionBreakpointsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ lldb::SBError error;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ const auto *filters = arguments->getArray("filters");
+ // Keep a list of any exception breakpoint filter names that weren't set
+ // so we can clear any exception breakpoints if needed.
+ std::set<std::string> unset_filters;
+ for (const auto &bp : *dap.exception_breakpoints)
+ unset_filters.insert(bp.filter);
+
+ for (const auto &value : *filters) {
+ const auto filter = GetAsString(value);
+ auto *exc_bp = dap.GetExceptionBreakpoint(std::string(filter));
+ if (exc_bp) {
+ exc_bp->SetBreakpoint();
+ unset_filters.erase(std::string(filter));
+ }
+ }
+ for (const auto &filter : unset_filters) {
+ auto *exc_bp = dap.GetExceptionBreakpoint(filter);
+ if (exc_bp)
+ exc_bp->ClearBreakpoint();
+ }
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/SetFunctionBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetFunctionBreakpointsRequestHandler.cpp
new file mode 100644
index 0000000000000..e55cfaef8c897
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SetFunctionBreakpointsRequestHandler.cpp
@@ -0,0 +1,139 @@
+//===-- SetFunctionBreakpointsRequestHandler.cpp --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "SetFunctionBreakpointsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "SetFunctionBreakpoints request; value of command field is
+// 'setFunctionBreakpoints'. Sets multiple function breakpoints and clears
+// all previous function breakpoints. To clear all function breakpoint,
+// specify an empty array. When a function breakpoint is hit, a StoppedEvent
+// (event type 'function breakpoint') is generated.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "setFunctionBreakpoints" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SetFunctionBreakpointsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SetFunctionBreakpointsArguments": {
+// "type": "object",
+// "description": "Arguments for 'setFunctionBreakpoints' request.",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/FunctionBreakpoint"
+// },
+// "description": "The function names of the breakpoints."
+// }
+// },
+// "required": [ "breakpoints" ]
+// },
+// "FunctionBreakpoint": {
+// "type": "object",
+// "description": "Properties of a breakpoint passed to the
+// setFunctionBreakpoints request.", "properties": {
+// "name": {
+// "type": "string",
+// "description": "The name of the function."
+// },
+// "condition": {
+// "type": "string",
+// "description": "An optional expression for conditional breakpoints."
+// },
+// "hitCondition": {
+// "type": "string",
+// "description": "An optional expression that controls how many hits of
+// the breakpoint are ignored. The backend is expected to interpret the
+// expression as needed."
+// }
+// },
+// "required": [ "name" ]
+// },
+// "SetFunctionBreakpointsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'setFunctionBreakpoints' request. Returned is
+// information about each breakpoint created by this request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/Breakpoint"
+// },
+// "description": "Information about the breakpoints. The array
+// elements correspond to the elements of the 'breakpoints' array."
+// }
+// },
+// "required": [ "breakpoints" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void SetFunctionBreakpointsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ lldb::SBError error;
+ FillResponse(request, response);
+ const auto *arguments = request.getObject("arguments");
+ const auto *breakpoints = arguments->getArray("breakpoints");
+ llvm::json::Array response_breakpoints;
+
+ // Disable any function breakpoints that aren't in this request.
+ // There is no call to remove function breakpoints other than calling this
+ // function with a smaller or empty "breakpoints" list.
+ const auto name_iter = dap.function_breakpoints.keys();
+ llvm::DenseSet<llvm::StringRef> seen(name_iter.begin(), name_iter.end());
+ for (const auto &value : *breakpoints) {
+ const auto *bp_obj = value.getAsObject();
+ if (!bp_obj)
+ continue;
+ FunctionBreakpoint fn_bp(dap, *bp_obj);
+ const auto [it, inserted] =
+ dap.function_breakpoints.try_emplace(fn_bp.functionName, dap, *bp_obj);
+ if (inserted)
+ it->second.SetBreakpoint();
+ else
+ it->second.UpdateBreakpoint(fn_bp);
+
+ AppendBreakpoint(&it->second, response_breakpoints);
+ seen.erase(fn_bp.functionName);
+ }
+
+ // Remove any breakpoints that are no longer in our list
+ for (const auto &name : seen) {
+ auto fn_bp = dap.function_breakpoints.find(name);
+ if (fn_bp == dap.function_breakpoints.end())
+ continue;
+ dap.target.BreakpointDelete(fn_bp->second.bp.GetID());
+ dap.function_breakpoints.erase(name);
+ }
+
+ llvm::json::Object body;
+ body.try_emplace("breakpoints", std::move(response_breakpoints));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/SetInstructionBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetInstructionBreakpointsRequestHandler.cpp
new file mode 100644
index 0000000000000..636d9b814ab76
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SetInstructionBreakpointsRequestHandler.cpp
@@ -0,0 +1,249 @@
+//===-- SetInstructionBreakpointsRequestHandler.cpp -----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "SetInstructionBreakpointsRequest": {
+// "allOf": [
+// {"$ref": "#/definitions/Request"},
+// {
+// "type": "object",
+// "description" :
+// "Replaces all existing instruction breakpoints. Typically, "
+// "instruction breakpoints would be set from a disassembly window. "
+// "\nTo clear all instruction breakpoints, specify an empty "
+// "array.\nWhen an instruction breakpoint is hit, a `stopped` event "
+// "(with reason `instruction breakpoint`) is generated.\nClients "
+// "should only call this request if the corresponding capability "
+// "`supportsInstructionBreakpoints` is true.",
+// "properties": {
+// "command": { "type": "string", "enum": ["setInstructionBreakpoints"]
+// }, "arguments": {"$ref":
+// "#/definitions/SetInstructionBreakpointsArguments"}
+// },
+// "required": [ "command", "arguments" ]
+// }
+// ]
+// },
+// "SetInstructionBreakpointsArguments": {
+// "type": "object",
+// "description": "Arguments for `setInstructionBreakpoints` request",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {"$ref": "#/definitions/InstructionBreakpoint"},
+// "description": "The instruction references of the breakpoints"
+// }
+// },
+// "required": ["breakpoints"]
+// },
+// "SetInstructionBreakpointsResponse": {
+// "allOf": [
+// {"$ref": "#/definitions/Response"},
+// {
+// "type": "object",
+// "description": "Response to `setInstructionBreakpoints` request",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "breakpoints": {
+// "type": "array",
+// "items": {"$ref": "#/definitions/Breakpoint"},
+// "description":
+// "Information about the breakpoints. The array elements
+// " "correspond to the elements of the `breakpoints`
+// array."
+// }
+// },
+// "required": ["breakpoints"]
+// }
+// },
+// "required": ["body"]
+// }
+// ]
+// },
+// "InstructionBreakpoint": {
+// "type": "object",
+// "description": "Properties of a breakpoint passed to the "
+// "`setInstructionBreakpoints` request",
+// "properties": {
+// "instructionReference": {
+// "type": "string",
+// "description" :
+// "The instruction reference of the breakpoint.\nThis should be a "
+// "memory or instruction pointer reference from an
+// `EvaluateResponse`, "
+// "`Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`."
+// },
+// "offset": {
+// "type": "integer",
+// "description": "The offset from the instruction reference in "
+// "bytes.\nThis can be negative."
+// },
+// "condition": {
+// "type": "string",
+// "description": "An expression for conditional breakpoints.\nIt is only
+// "
+// "honored by a debug adapter if the corresponding "
+// "capability `supportsConditionalBreakpoints` is true."
+// },
+// "hitCondition": {
+// "type": "string",
+// "description": "An expression that controls how many hits of the "
+// "breakpoint are ignored.\nThe debug adapter is expected
+// " "to interpret the expression as needed.\nThe
+// attribute " "is only honored by a debug adapter if the
+// corresponding " "capability
+// `supportsHitConditionalBreakpoints` is true."
+// },
+// "mode": {
+// "type": "string",
+// "description": "The mode of this breakpoint. If defined, this must be
+// "
+// "one of the `breakpointModes` the debug adapter "
+// "advertised in its `Capabilities`."
+// }
+// },
+// "required": ["instructionReference"]
+// },
+// "Breakpoint": {
+// "type": "object",
+// "description" :
+// "Information about a breakpoint created in `setBreakpoints`, "
+// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or "
+// "`setDataBreakpoints` requests.",
+// "properties": {
+// "id": {
+// "type": "integer",
+// "description" :
+// "The identifier for the breakpoint. It is needed if breakpoint
+// " "events are used to update or remove breakpoints."
+// },
+// "verified": {
+// "type": "boolean",
+// "description": "If true, the breakpoint could be set (but not "
+// "necessarily at the desired location)."
+// },
+// "message": {
+// "type": "string",
+// "description": "A message about the state of the breakpoint.\nThis
+// "
+// "is shown to the user and can be used to explain
+// why " "a breakpoint could not be verified."
+// },
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "The source where the breakpoint is located."
+// },
+// "line": {
+// "type": "integer",
+// "description" :
+// "The start line of the actual range covered by the breakpoint."
+// },
+// "column": {
+// "type": "integer",
+// "description" :
+// "Start position of the source range covered by the breakpoint.
+// " "It is measured in UTF-16 code units and the client
+// capability "
+// "`columnsStartAt1` determines whether it is 0- or 1-based."
+// },
+// "endLine": {
+// "type": "integer",
+// "description" :
+// "The end line of the actual range covered by the breakpoint."
+// },
+// "endColumn": {
+// "type": "integer",
+// "description" :
+// "End position of the source range covered by the breakpoint. It
+// " "is measured in UTF-16 code units and the client capability "
+// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf
+// " "no end line is given, then the end column is assumed to be
+// in " "the start line."
+// },
+// "instructionReference": {
+// "type": "string",
+// "description": "A memory reference to where the breakpoint is
+// set."
+// },
+// "offset": {
+// "type": "integer",
+// "description": "The offset from the instruction reference.\nThis "
+// "can be negative."
+// },
+// "reason": {
+// "type": "string",
+// "description" :
+// "A machine-readable explanation of why a breakpoint may not be
+// " "verified. If a breakpoint is verified or a specific reason
+// is " "not known, the adapter should omit this property.
+// Possible " "values include:\n\n- `pending`: Indicates a
+// breakpoint might be " "verified in the future, but the adapter
+// cannot verify it in the " "current state.\n - `failed`:
+// Indicates a breakpoint was not " "able to be verified, and the
+// adapter does not believe it can be " "verified without
+// intervention.",
+// "enum": [ "pending", "failed" ]
+// }
+// },
+// "required": ["verified"]
+// },
+void SetInstructionBreakpointsRequestHandler::operator()(
+ const llvm::json::Object &request) {
+ llvm::json::Object response;
+ llvm::json::Array response_breakpoints;
+ llvm::json::Object body;
+ FillResponse(request, response);
+
+ const auto *arguments = request.getObject("arguments");
+ const auto *breakpoints = arguments->getArray("breakpoints");
+
+ // Disable any instruction breakpoints that aren't in this request.
+ // There is no call to remove instruction breakpoints other than calling this
+ // function with a smaller or empty "breakpoints" list.
+ llvm::DenseSet<lldb::addr_t> seen;
+ for (const auto &addr : dap.instruction_breakpoints)
+ seen.insert(addr.first);
+
+ for (const auto &bp : *breakpoints) {
+ const auto *bp_obj = bp.getAsObject();
+ if (!bp_obj)
+ continue;
+ // Read instruction breakpoint request.
+ InstructionBreakpoint inst_bp(dap, *bp_obj);
+ const auto [iv, inserted] = dap.instruction_breakpoints.try_emplace(
+ inst_bp.instructionAddressReference, dap, *bp_obj);
+ if (inserted)
+ iv->second.SetBreakpoint();
+ else
+ iv->second.UpdateBreakpoint(inst_bp);
+ AppendBreakpoint(&iv->second, response_breakpoints);
+ seen.erase(inst_bp.instructionAddressReference);
+ }
+
+ for (const auto &addr : seen) {
+ auto inst_bp = dap.instruction_breakpoints.find(addr);
+ if (inst_bp == dap.instruction_breakpoints.end())
+ continue;
+ dap.target.BreakpointDelete(inst_bp->second.bp.GetID());
+ dap.instruction_breakpoints.erase(addr);
+ }
+
+ body.try_emplace("breakpoints", std::move(response_breakpoints));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 632629d56232c..fd4615897841c 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -18,8 +18,6 @@
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBFile.h"
#include "lldb/API/SBInstruction.h"
-#include "lldb/API/SBListener.h"
-#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/MainLoop.h"
@@ -395,636 +393,6 @@ void request_scopes(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// "SetBreakpointsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "SetBreakpoints request; value of command field is
-// 'setBreakpoints'. Sets multiple breakpoints for a single source and
-// clears all previous breakpoints in that source. To clear all breakpoint
-// for a source, specify an empty array. When a breakpoint is hit, a
-// StoppedEvent (event type 'breakpoint') is generated.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "setBreakpoints" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/SetBreakpointsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "SetBreakpointsArguments": {
-// "type": "object",
-// "description": "Arguments for 'setBreakpoints' request.",
-// "properties": {
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "The source location of the breakpoints; either
-// source.path or source.reference must be specified."
-// },
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/SourceBreakpoint"
-// },
-// "description": "The code locations of the breakpoints."
-// },
-// "lines": {
-// "type": "array",
-// "items": {
-// "type": "integer"
-// },
-// "description": "Deprecated: The code locations of the breakpoints."
-// },
-// "sourceModified": {
-// "type": "boolean",
-// "description": "A value of true indicates that the underlying source
-// has been modified which results in new breakpoint locations."
-// }
-// },
-// "required": [ "source" ]
-// },
-// "SetBreakpointsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'setBreakpoints' request. Returned is
-// information about each breakpoint created by this request. This includes
-// the actual code location and whether the breakpoint could be verified.
-// The breakpoints returned are in the same order as the elements of the
-// 'breakpoints' (or the deprecated 'lines') in the
-// SetBreakpointsArguments.", "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Breakpoint"
-// },
-// "description": "Information about the breakpoints. The array
-// elements are in the same order as the elements of the
-// 'breakpoints' (or the deprecated 'lines') in the
-// SetBreakpointsArguments."
-// }
-// },
-// "required": [ "breakpoints" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// },
-// "SourceBreakpoint": {
-// "type": "object",
-// "description": "Properties of a breakpoint or logpoint passed to the
-// setBreakpoints request.", "properties": {
-// "line": {
-// "type": "integer",
-// "description": "The source line of the breakpoint or logpoint."
-// },
-// "column": {
-// "type": "integer",
-// "description": "An optional source column of the breakpoint."
-// },
-// "condition": {
-// "type": "string",
-// "description": "An optional expression for conditional breakpoints."
-// },
-// "hitCondition": {
-// "type": "string",
-// "description": "An optional expression that controls how many hits of
-// the breakpoint are ignored. The backend is expected to interpret the
-// expression as needed."
-// },
-// "logMessage": {
-// "type": "string",
-// "description": "If this attribute exists and is non-empty, the backend
-// must not 'break' (stop) but log the message instead. Expressions within
-// {} are interpolated."
-// }
-// },
-// "required": [ "line" ]
-// }
-void request_setBreakpoints(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- lldb::SBError error;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- const auto *source = arguments->getObject("source");
- const auto path = GetString(source, "path");
- const auto *breakpoints = arguments->getArray("breakpoints");
- llvm::json::Array response_breakpoints;
-
- // Decode the source breakpoint infos for this "setBreakpoints" request
- SourceBreakpointMap request_bps;
- // "breakpoints" may be unset, in which case we treat it the same as being set
- // to an empty array.
- if (breakpoints) {
- for (const auto &bp : *breakpoints) {
- const auto *bp_obj = bp.getAsObject();
- if (bp_obj) {
- SourceBreakpoint src_bp(dap, *bp_obj);
- std::pair<uint32_t, uint32_t> bp_pos(src_bp.line, src_bp.column);
- request_bps.try_emplace(bp_pos, src_bp);
- const auto [iv, inserted] =
- dap.source_breakpoints[path].try_emplace(bp_pos, src_bp);
- // We check if this breakpoint already exists to update it
- if (inserted)
- iv->getSecond().SetBreakpoint(path.data());
- else
- iv->getSecond().UpdateBreakpoint(src_bp);
- AppendBreakpoint(&iv->getSecond(), response_breakpoints, path,
- src_bp.line);
- }
- }
- }
-
- // Delete any breakpoints in this source file that aren't in the
- // request_bps set. There is no call to remove breakpoints other than
- // calling this function with a smaller or empty "breakpoints" list.
- auto old_src_bp_pos = dap.source_breakpoints.find(path);
- if (old_src_bp_pos != dap.source_breakpoints.end()) {
- for (auto &old_bp : old_src_bp_pos->second) {
- auto request_pos = request_bps.find(old_bp.first);
- if (request_pos == request_bps.end()) {
- // This breakpoint no longer exists in this source file, delete it
- dap.target.BreakpointDelete(old_bp.second.bp.GetID());
- old_src_bp_pos->second.erase(old_bp.first);
- }
- }
- }
-
- llvm::json::Object body;
- body.try_emplace("breakpoints", std::move(response_breakpoints));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "SetExceptionBreakpointsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "SetExceptionBreakpoints request; value of command field
-// is 'setExceptionBreakpoints'. The request configures the debuggers
-// response to thrown exceptions. If an exception is configured to break, a
-// StoppedEvent is fired (event type 'exception').", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "setExceptionBreakpoints" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/SetExceptionBreakpointsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "SetExceptionBreakpointsArguments": {
-// "type": "object",
-// "description": "Arguments for 'setExceptionBreakpoints' request.",
-// "properties": {
-// "filters": {
-// "type": "array",
-// "items": {
-// "type": "string"
-// },
-// "description": "IDs of checked exception options. The set of IDs is
-// returned via the 'exceptionBreakpointFilters' capability."
-// },
-// "exceptionOptions": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/ExceptionOptions"
-// },
-// "description": "Configuration options for selected exceptions."
-// }
-// },
-// "required": [ "filters" ]
-// },
-// "SetExceptionBreakpointsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'setExceptionBreakpoints' request. This is
-// just an acknowledgement, so no body field is required."
-// }]
-// }
-void request_setExceptionBreakpoints(DAP &dap,
- const llvm::json::Object &request) {
- llvm::json::Object response;
- lldb::SBError error;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- const auto *filters = arguments->getArray("filters");
- // Keep a list of any exception breakpoint filter names that weren't set
- // so we can clear any exception breakpoints if needed.
- std::set<std::string> unset_filters;
- for (const auto &bp : *dap.exception_breakpoints)
- unset_filters.insert(bp.filter);
-
- for (const auto &value : *filters) {
- const auto filter = GetAsString(value);
- auto *exc_bp = dap.GetExceptionBreakpoint(std::string(filter));
- if (exc_bp) {
- exc_bp->SetBreakpoint();
- unset_filters.erase(std::string(filter));
- }
- }
- for (const auto &filter : unset_filters) {
- auto *exc_bp = dap.GetExceptionBreakpoint(filter);
- if (exc_bp)
- exc_bp->ClearBreakpoint();
- }
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "SetFunctionBreakpointsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "SetFunctionBreakpoints request; value of command field is
-// 'setFunctionBreakpoints'. Sets multiple function breakpoints and clears
-// all previous function breakpoints. To clear all function breakpoint,
-// specify an empty array. When a function breakpoint is hit, a StoppedEvent
-// (event type 'function breakpoint') is generated.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "setFunctionBreakpoints" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/SetFunctionBreakpointsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "SetFunctionBreakpointsArguments": {
-// "type": "object",
-// "description": "Arguments for 'setFunctionBreakpoints' request.",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/FunctionBreakpoint"
-// },
-// "description": "The function names of the breakpoints."
-// }
-// },
-// "required": [ "breakpoints" ]
-// },
-// "FunctionBreakpoint": {
-// "type": "object",
-// "description": "Properties of a breakpoint passed to the
-// setFunctionBreakpoints request.", "properties": {
-// "name": {
-// "type": "string",
-// "description": "The name of the function."
-// },
-// "condition": {
-// "type": "string",
-// "description": "An optional expression for conditional breakpoints."
-// },
-// "hitCondition": {
-// "type": "string",
-// "description": "An optional expression that controls how many hits of
-// the breakpoint are ignored. The backend is expected to interpret the
-// expression as needed."
-// }
-// },
-// "required": [ "name" ]
-// },
-// "SetFunctionBreakpointsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'setFunctionBreakpoints' request. Returned is
-// information about each breakpoint created by this request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Breakpoint"
-// },
-// "description": "Information about the breakpoints. The array
-// elements correspond to the elements of the 'breakpoints' array."
-// }
-// },
-// "required": [ "breakpoints" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_setFunctionBreakpoints(DAP &dap,
- const llvm::json::Object &request) {
- llvm::json::Object response;
- lldb::SBError error;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- const auto *breakpoints = arguments->getArray("breakpoints");
- llvm::json::Array response_breakpoints;
-
- // Disable any function breakpoints that aren't in this request.
- // There is no call to remove function breakpoints other than calling this
- // function with a smaller or empty "breakpoints" list.
- const auto name_iter = dap.function_breakpoints.keys();
- llvm::DenseSet<llvm::StringRef> seen(name_iter.begin(), name_iter.end());
- for (const auto &value : *breakpoints) {
- const auto *bp_obj = value.getAsObject();
- if (!bp_obj)
- continue;
- FunctionBreakpoint fn_bp(dap, *bp_obj);
- const auto [it, inserted] =
- dap.function_breakpoints.try_emplace(fn_bp.functionName, dap, *bp_obj);
- if (inserted)
- it->second.SetBreakpoint();
- else
- it->second.UpdateBreakpoint(fn_bp);
-
- AppendBreakpoint(&it->second, response_breakpoints);
- seen.erase(fn_bp.functionName);
- }
-
- // Remove any breakpoints that are no longer in our list
- for (const auto &name : seen) {
- auto fn_bp = dap.function_breakpoints.find(name);
- if (fn_bp == dap.function_breakpoints.end())
- continue;
- dap.target.BreakpointDelete(fn_bp->second.bp.GetID());
- dap.function_breakpoints.erase(name);
- }
-
- llvm::json::Object body;
- body.try_emplace("breakpoints", std::move(response_breakpoints));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "DataBreakpointInfoRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Obtains information on a possible data breakpoint that
-// could be set on an expression or variable.\nClients should only call this
-// request if the corresponding capability `supportsDataBreakpoints` is
-// true.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "dataBreakpointInfo" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/DataBreakpointInfoArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "DataBreakpointInfoArguments": {
-// "type": "object",
-// "description": "Arguments for `dataBreakpointInfo` request.",
-// "properties": {
-// "variablesReference": {
-// "type": "integer",
-// "description": "Reference to the variable container if the data
-// breakpoint is requested for a child of the container. The
-// `variablesReference` must have been obtained in the current suspended
-// state. See 'Lifetime of Object References' in the Overview section for
-// details."
-// },
-// "name": {
-// "type": "string",
-// "description": "The name of the variable's child to obtain data
-// breakpoint information for.\nIf `variablesReference` isn't specified,
-// this can be an expression."
-// },
-// "frameId": {
-// "type": "integer",
-// "description": "When `name` is an expression, evaluate it in the scope
-// of this stack frame. If not specified, the expression is evaluated in
-// the global scope. When `variablesReference` is specified, this property
-// has no effect."
-// }
-// },
-// "required": [ "name" ]
-// },
-// "DataBreakpointInfoResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `dataBreakpointInfo` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "dataId": {
-// "type": [ "string", "null" ],
-// "description": "An identifier for the data on which a data
-// breakpoint can be registered with the `setDataBreakpoints`
-// request or null if no data breakpoint is available. If a
-// `variablesReference` or `frameId` is passed, the `dataId` is
-// valid in the current suspended state, otherwise it's valid
-// indefinitely. See 'Lifetime of Object References' in the Overview
-// section for details. Breakpoints set using the `dataId` in the
-// `setDataBreakpoints` request may outlive the lifetime of the
-// associated `dataId`."
-// },
-// "description": {
-// "type": "string",
-// "description": "UI string that describes on what data the
-// breakpoint is set on or why a data breakpoint is not available."
-// },
-// "accessTypes": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/DataBreakpointAccessType"
-// },
-// "description": "Attribute lists the available access types for a
-// potential data breakpoint. A UI client could surface this
-// information."
-// },
-// "canPersist": {
-// "type": "boolean",
-// "description": "Attribute indicates that a potential data
-// breakpoint could be persisted across sessions."
-// }
-// },
-// "required": [ "dataId", "description" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_dataBreakpointInfo(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- lldb::SBError error;
- llvm::json::Array accessTypes{"read", "write", "readWrite"};
- const auto *arguments = request.getObject("arguments");
- const auto variablesReference =
- GetUnsigned(arguments, "variablesReference", 0);
- llvm::StringRef name = GetString(arguments, "name");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
- lldb::SBValue variable = FindVariable(dap, variablesReference, name);
- std::string addr, size;
-
- if (variable.IsValid()) {
- lldb::addr_t load_addr = variable.GetLoadAddress();
- size_t byte_size = variable.GetByteSize();
- if (load_addr == LLDB_INVALID_ADDRESS) {
- body.try_emplace("dataId", nullptr);
- body.try_emplace("description",
- "does not exist in memory, its location is " +
- std::string(variable.GetLocation()));
- } else if (byte_size == 0) {
- body.try_emplace("dataId", nullptr);
- body.try_emplace("description", "variable size is 0");
- } else {
- addr = llvm::utohexstr(load_addr);
- size = llvm::utostr(byte_size);
- }
- } else if (variablesReference == 0 && frame.IsValid()) {
- lldb::SBValue value = frame.EvaluateExpression(name.data());
- if (value.GetError().Fail()) {
- lldb::SBError error = value.GetError();
- const char *error_cstr = error.GetCString();
- body.try_emplace("dataId", nullptr);
- body.try_emplace("description", error_cstr && error_cstr[0]
- ? std::string(error_cstr)
- : "evaluation failed");
- } else {
- uint64_t load_addr = value.GetValueAsUnsigned();
- lldb::SBData data = value.GetPointeeData();
- if (data.IsValid()) {
- size = llvm::utostr(data.GetByteSize());
- addr = llvm::utohexstr(load_addr);
- lldb::SBMemoryRegionInfo region;
- lldb::SBError err =
- dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
- // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
- // request if SBProcess::GetMemoryRegionInfo returns error.
- if (err.Success()) {
- if (!(region.IsReadable() || region.IsWritable())) {
- body.try_emplace("dataId", nullptr);
- body.try_emplace("description",
- "memory region for address " + addr +
- " has no read or write permissions");
- }
- }
- } else {
- body.try_emplace("dataId", nullptr);
- body.try_emplace("description",
- "unable to get byte size for expression: " +
- name.str());
- }
- }
- } else {
- body.try_emplace("dataId", nullptr);
- body.try_emplace("description", "variable not found: " + name.str());
- }
-
- if (!body.getObject("dataId")) {
- body.try_emplace("dataId", addr + "/" + size);
- body.try_emplace("accessTypes", std::move(accessTypes));
- body.try_emplace("description",
- size + " bytes at " + addr + " " + name.str());
- }
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "SetDataBreakpointsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Replaces all existing data breakpoints with new data
-// breakpoints.\nTo clear all data breakpoints, specify an empty
-// array.\nWhen a data breakpoint is hit, a `stopped` event (with reason
-// `data breakpoint`) is generated.\nClients should only call this request
-// if the corresponding capability `supportsDataBreakpoints` is true.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "setDataBreakpoints" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/SetDataBreakpointsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "SetDataBreakpointsArguments": {
-// "type": "object",
-// "description": "Arguments for `setDataBreakpoints` request.",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/DataBreakpoint"
-// },
-// "description": "The contents of this array replaces all existing data
-// breakpoints. An empty array clears all data breakpoints."
-// }
-// },
-// "required": [ "breakpoints" ]
-// },
-// "SetDataBreakpointsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `setDataBreakpoints` request.\nReturned is
-// information about each breakpoint created by this request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Breakpoint"
-// },
-// "description": "Information about the data breakpoints. The array
-// elements correspond to the elements of the input argument
-// `breakpoints` array."
-// }
-// },
-// "required": [ "breakpoints" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_setDataBreakpoints(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- lldb::SBError error;
- FillResponse(request, response);
- const auto *arguments = request.getObject("arguments");
- const auto *breakpoints = arguments->getArray("breakpoints");
- llvm::json::Array response_breakpoints;
- dap.target.DeleteAllWatchpoints();
- std::vector<Watchpoint> watchpoints;
- if (breakpoints) {
- for (const auto &bp : *breakpoints) {
- const auto *bp_obj = bp.getAsObject();
- if (bp_obj)
- watchpoints.emplace_back(dap, *bp_obj);
- }
- }
- // If two watchpoints start at the same address, the latter overwrite the
- // former. So, we only enable those at first-seen addresses when iterating
- // backward.
- std::set<lldb::addr_t> addresses;
- for (auto iter = watchpoints.rbegin(); iter != watchpoints.rend(); ++iter) {
- if (addresses.count(iter->addr) == 0) {
- iter->SetWatchpoint();
- addresses.insert(iter->addr);
- }
- }
- for (auto wp : watchpoints)
- AppendBreakpoint(&wp, response_breakpoints);
-
- llvm::json::Object body;
- body.try_emplace("breakpoints", std::move(response_breakpoints));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
// "SourceRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -2081,245 +1449,13 @@ void request_readMemory(DAP &dap, const llvm::json::Object &request) {
dap.SendJSON(llvm::json::Value(std::move(response)));
}
-// "SetInstructionBreakpointsRequest": {
-// "allOf": [
-// {"$ref": "#/definitions/Request"},
-// {
-// "type": "object",
-// "description" :
-// "Replaces all existing instruction breakpoints. Typically, "
-// "instruction breakpoints would be set from a disassembly window. "
-// "\nTo clear all instruction breakpoints, specify an empty "
-// "array.\nWhen an instruction breakpoint is hit, a `stopped` event "
-// "(with reason `instruction breakpoint`) is generated.\nClients "
-// "should only call this request if the corresponding capability "
-// "`supportsInstructionBreakpoints` is true.",
-// "properties": {
-// "command": { "type": "string", "enum": ["setInstructionBreakpoints"]
-// }, "arguments": {"$ref":
-// "#/definitions/SetInstructionBreakpointsArguments"}
-// },
-// "required": [ "command", "arguments" ]
-// }
-// ]
-// },
-// "SetInstructionBreakpointsArguments": {
-// "type": "object",
-// "description": "Arguments for `setInstructionBreakpoints` request",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {"$ref": "#/definitions/InstructionBreakpoint"},
-// "description": "The instruction references of the breakpoints"
-// }
-// },
-// "required": ["breakpoints"]
-// },
-// "SetInstructionBreakpointsResponse": {
-// "allOf": [
-// {"$ref": "#/definitions/Response"},
-// {
-// "type": "object",
-// "description": "Response to `setInstructionBreakpoints` request",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "breakpoints": {
-// "type": "array",
-// "items": {"$ref": "#/definitions/Breakpoint"},
-// "description":
-// "Information about the breakpoints. The array elements
-// " "correspond to the elements of the `breakpoints`
-// array."
-// }
-// },
-// "required": ["breakpoints"]
-// }
-// },
-// "required": ["body"]
-// }
-// ]
-// },
-// "InstructionBreakpoint": {
-// "type": "object",
-// "description": "Properties of a breakpoint passed to the "
-// "`setInstructionBreakpoints` request",
-// "properties": {
-// "instructionReference": {
-// "type": "string",
-// "description" :
-// "The instruction reference of the breakpoint.\nThis should be a "
-// "memory or instruction pointer reference from an
-// `EvaluateResponse`, "
-// "`Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`."
-// },
-// "offset": {
-// "type": "integer",
-// "description": "The offset from the instruction reference in "
-// "bytes.\nThis can be negative."
-// },
-// "condition": {
-// "type": "string",
-// "description": "An expression for conditional breakpoints.\nIt is only
-// "
-// "honored by a debug adapter if the corresponding "
-// "capability `supportsConditionalBreakpoints` is true."
-// },
-// "hitCondition": {
-// "type": "string",
-// "description": "An expression that controls how many hits of the "
-// "breakpoint are ignored.\nThe debug adapter is expected
-// " "to interpret the expression as needed.\nThe
-// attribute " "is only honored by a debug adapter if the
-// corresponding " "capability
-// `supportsHitConditionalBreakpoints` is true."
-// },
-// "mode": {
-// "type": "string",
-// "description": "The mode of this breakpoint. If defined, this must be
-// "
-// "one of the `breakpointModes` the debug adapter "
-// "advertised in its `Capabilities`."
-// }
-// },
-// "required": ["instructionReference"]
-// },
-// "Breakpoint": {
-// "type": "object",
-// "description" :
-// "Information about a breakpoint created in `setBreakpoints`, "
-// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or "
-// "`setDataBreakpoints` requests.",
-// "properties": {
-// "id": {
-// "type": "integer",
-// "description" :
-// "The identifier for the breakpoint. It is needed if breakpoint
-// " "events are used to update or remove breakpoints."
-// },
-// "verified": {
-// "type": "boolean",
-// "description": "If true, the breakpoint could be set (but not "
-// "necessarily at the desired location)."
-// },
-// "message": {
-// "type": "string",
-// "description": "A message about the state of the breakpoint.\nThis
-// "
-// "is shown to the user and can be used to explain
-// why " "a breakpoint could not be verified."
-// },
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "The source where the breakpoint is located."
-// },
-// "line": {
-// "type": "integer",
-// "description" :
-// "The start line of the actual range covered by the breakpoint."
-// },
-// "column": {
-// "type": "integer",
-// "description" :
-// "Start position of the source range covered by the breakpoint.
-// " "It is measured in UTF-16 code units and the client
-// capability "
-// "`columnsStartAt1` determines whether it is 0- or 1-based."
-// },
-// "endLine": {
-// "type": "integer",
-// "description" :
-// "The end line of the actual range covered by the breakpoint."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description" :
-// "End position of the source range covered by the breakpoint. It
-// " "is measured in UTF-16 code units and the client capability "
-// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf
-// " "no end line is given, then the end column is assumed to be
-// in " "the start line."
-// },
-// "instructionReference": {
-// "type": "string",
-// "description": "A memory reference to where the breakpoint is
-// set."
-// },
-// "offset": {
-// "type": "integer",
-// "description": "The offset from the instruction reference.\nThis "
-// "can be negative."
-// },
-// "reason": {
-// "type": "string",
-// "description" :
-// "A machine-readable explanation of why a breakpoint may not be
-// " "verified. If a breakpoint is verified or a specific reason
-// is " "not known, the adapter should omit this property.
-// Possible " "values include:\n\n- `pending`: Indicates a
-// breakpoint might be " "verified in the future, but the adapter
-// cannot verify it in the " "current state.\n - `failed`:
-// Indicates a breakpoint was not " "able to be verified, and the
-// adapter does not believe it can be " "verified without
-// intervention.",
-// "enum": [ "pending", "failed" ]
-// }
-// },
-// "required": ["verified"]
-// },
-void request_setInstructionBreakpoints(DAP &dap,
- const llvm::json::Object &request) {
- llvm::json::Object response;
- llvm::json::Array response_breakpoints;
- llvm::json::Object body;
- FillResponse(request, response);
-
- const auto *arguments = request.getObject("arguments");
- const auto *breakpoints = arguments->getArray("breakpoints");
-
- // Disable any instruction breakpoints that aren't in this request.
- // There is no call to remove instruction breakpoints other than calling this
- // function with a smaller or empty "breakpoints" list.
- llvm::DenseSet<lldb::addr_t> seen;
- for (const auto &addr : dap.instruction_breakpoints)
- seen.insert(addr.first);
-
- for (const auto &bp : *breakpoints) {
- const auto *bp_obj = bp.getAsObject();
- if (!bp_obj)
- continue;
- // Read instruction breakpoint request.
- InstructionBreakpoint inst_bp(dap, *bp_obj);
- const auto [iv, inserted] = dap.instruction_breakpoints.try_emplace(
- inst_bp.instructionAddressReference, dap, *bp_obj);
- if (inserted)
- iv->second.SetBreakpoint();
- else
- iv->second.UpdateBreakpoint(inst_bp);
- AppendBreakpoint(&iv->second, response_breakpoints);
- seen.erase(inst_bp.instructionAddressReference);
- }
-
- for (const auto &addr : seen) {
- auto inst_bp = dap.instruction_breakpoints.find(addr);
- if (inst_bp == dap.instruction_breakpoints.end())
- continue;
- dap.target.BreakpointDelete(inst_bp->second.bp.GetID());
- dap.instruction_breakpoints.erase(addr);
- }
-
- body.try_emplace("breakpoints", std::move(response_breakpoints));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<AttachRequestHandler>();
dap.RegisterRequest<BreakpointLocationsRequestHandler>();
dap.RegisterRequest<CompletionsRequestHandler>();
dap.RegisterRequest<ConfigurationDoneRequestHandler>();
dap.RegisterRequest<ContinueRequestHandler>();
+ dap.RegisterRequest<DataBreakpointInfoRequestHandler>();
dap.RegisterRequest<DisconnectRequestHandler>();
dap.RegisterRequest<EvaluateRequestHandler>();
dap.RegisterRequest<ExceptionInfoRequestHandler>();
@@ -2327,6 +1463,11 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<LaunchRequestHandler>();
dap.RegisterRequest<NextRequestHandler>();
dap.RegisterRequest<RestartRequestHandler>();
+ dap.RegisterRequest<SetBreakpointsRequestHandler>();
+ dap.RegisterRequest<SetDataBreakpointsRequestHandler>();
+ dap.RegisterRequest<SetExceptionBreakpointsRequestHandler>();
+ dap.RegisterRequest<SetFunctionBreakpointsRequestHandler>();
+ dap.RegisterRequest<SetInstructionBreakpointsRequestHandler>();
dap.RegisterRequest<StepInRequestHandler>();
dap.RegisterRequest<StepInTargetsRequestHandler>();
dap.RegisterRequest<StepOutRequestHandler>();
@@ -2340,13 +1481,6 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("pause", request_pause);
dap.RegisterRequestCallback("scopes", request_scopes);
- dap.RegisterRequestCallback("setBreakpoints", request_setBreakpoints);
- dap.RegisterRequestCallback("setExceptionBreakpoints",
- request_setExceptionBreakpoints);
- dap.RegisterRequestCallback("setFunctionBreakpoints",
- request_setFunctionBreakpoints);
- dap.RegisterRequestCallback("dataBreakpointInfo", request_dataBreakpointInfo);
- dap.RegisterRequestCallback("setDataBreakpoints", request_setDataBreakpoints);
dap.RegisterRequestCallback("setVariable", request_setVariable);
dap.RegisterRequestCallback("source", request_source);
dap.RegisterRequestCallback("stackTrace", request_stackTrace);
@@ -2355,8 +1489,6 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("locations", request_locations);
dap.RegisterRequestCallback("disassemble", request_disassemble);
dap.RegisterRequestCallback("readMemory", request_readMemory);
- dap.RegisterRequestCallback("setInstructionBreakpoints",
- request_setInstructionBreakpoints);
}
} // anonymous namespace
>From 0f33f54a4001b783eb4baa8a55e2c8687611cb67 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 24 Feb 2025 12:43:34 -0600
Subject: [PATCH 4/4] [lldb-dap] Refactor remaining request handlers (NFC)
---
lldb/tools/lldb-dap/CMakeLists.txt | 13 +-
.../Handler/DisassembleRequestHandler.cpp | 222 +++
.../Handler/LocationsRequestHandler.cpp | 160 ++
.../lldb-dap/Handler/PauseRequestHandler.cpp | 60 +
.../Handler/ReadMemoryRequestHandler.cpp | 139 ++
lldb/tools/lldb-dap/Handler/RequestHandler.h | 70 +
.../lldb-dap/Handler/ScopesRequestHandler.cpp | 106 ++
.../Handler/SetVariableRequestHandler.cpp | 177 +++
.../lldb-dap/Handler/SourceRequestHandler.cpp | 82 +
.../Handler/StackTraceRequestHandler.cpp | 197 +++
.../Handler/ThreadsRequestHandler.cpp | 71 +
.../Handler/VariablesRequestHandler.cpp | 217 +++
lldb/tools/lldb-dap/lldb-dap.cpp | 1345 +----------------
13 files changed, 1525 insertions(+), 1334 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Handler/DisassembleRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/LocationsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/ReadMemoryRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp
create mode 100644 lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index c04b10861a4c5..804dd8e4cc2a0 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -44,23 +44,34 @@ add_lldb_tool(lldb-dap
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
Handler/DataBreakpointInfoRequestHandler.cpp
+ Handler/DisassembleRequestHandler.cpp
Handler/DisconnectRequestHandler.cpp
Handler/EvaluateRequestHandler.cpp
Handler/ExceptionInfoRequestHandler.cpp
Handler/InitializeRequestHandler.cpp
Handler/LaunchRequestHandler.cpp
+ Handler/LocationsRequestHandler.cpp
Handler/ModulesRequestHandler.cpp
Handler/NextRequestHandler.cpp
+ Handler/PauseRequestHandler.cpp
+ Handler/ReadMemoryRequestHandler.cpp
Handler/RequestHandler.cpp
Handler/RestartRequestHandler.cpp
+ Handler/ScopesRequestHandler.cpp
Handler/SetBreakpointsRequestHandler.cpp
Handler/SetDataBreakpointsRequestHandler.cpp
Handler/SetExceptionBreakpointsRequestHandler.cpp
Handler/SetFunctionBreakpointsRequestHandler.cpp
- Handler/SetInstructionBreakpointsRequestHandler.cpp Handler/StepOutRequestHandler.cpp
+ Handler/SetInstructionBreakpointsRequestHandler.cpp
+ Handler/SetVariableRequestHandler.cpp
+ Handler/SourceRequestHandler.cpp
+ Handler/StackTraceRequestHandler.cpp
Handler/StepInRequestHandler.cpp
Handler/StepInTargetsRequestHandler.cpp
+ Handler/StepOutRequestHandler.cpp
Handler/TestGetTargetBreakpointsRequestHandler.cpp
+ Handler/ThreadsRequestHandler.cpp
+ Handler/VariablesRequestHandler.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/Handler/DisassembleRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DisassembleRequestHandler.cpp
new file mode 100644
index 0000000000000..6d25ef0fc5d74
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/DisassembleRequestHandler.cpp
@@ -0,0 +1,222 @@
+//===-- DisassembleRequestHandler.cpp -------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBInstruction.h"
+#include "llvm/ADT/StringExtras.h"
+
+namespace lldb_dap {
+
+// "DisassembleRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Disassembles code stored at the provided
+// location.\nClients should only call this request if the corresponding
+// capability `supportsDisassembleRequest` is true.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "disassemble" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/DisassembleArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "DisassembleArguments": {
+// "type": "object",
+// "description": "Arguments for `disassemble` request.",
+// "properties": {
+// "memoryReference": {
+// "type": "string",
+// "description": "Memory reference to the base location containing the
+// instructions to disassemble."
+// },
+// "offset": {
+// "type": "integer",
+// "description": "Offset (in bytes) to be applied to the reference
+// location before disassembling. Can be negative."
+// },
+// "instructionOffset": {
+// "type": "integer",
+// "description": "Offset (in instructions) to be applied after the byte
+// offset (if any) before disassembling. Can be negative."
+// },
+// "instructionCount": {
+// "type": "integer",
+// "description": "Number of instructions to disassemble starting at the
+// specified location and offset.\nAn adapter must return exactly this
+// number of instructions - any unavailable instructions should be
+// replaced with an implementation-defined 'invalid instruction' value."
+// },
+// "resolveSymbols": {
+// "type": "boolean",
+// "description": "If true, the adapter should attempt to resolve memory
+// addresses and other values to symbolic names."
+// }
+// },
+// "required": [ "memoryReference", "instructionCount" ]
+// },
+// "DisassembleResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `disassemble` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "instructions": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/DisassembledInstruction"
+// },
+// "description": "The list of disassembled instructions."
+// }
+// },
+// "required": [ "instructions" ]
+// }
+// }
+// }]
+// }
+void DisassembleRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ auto *arguments = request.getObject("arguments");
+
+ llvm::StringRef memoryReference = GetString(arguments, "memoryReference");
+ auto addr_opt = DecodeMemoryReference(memoryReference);
+ if (!addr_opt.has_value()) {
+ response["success"] = false;
+ response["message"] =
+ "Malformed memory reference: " + memoryReference.str();
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ lldb::addr_t addr_ptr = *addr_opt;
+
+ addr_ptr += GetSigned(arguments, "instructionOffset", 0);
+ lldb::SBAddress addr(addr_ptr, dap.target);
+ if (!addr.IsValid()) {
+ response["success"] = false;
+ response["message"] = "Memory reference not found in the current binary.";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ const auto inst_count = GetUnsigned(arguments, "instructionCount", 0);
+ lldb::SBInstructionList insts = dap.target.ReadInstructions(addr, inst_count);
+
+ if (!insts.IsValid()) {
+ response["success"] = false;
+ response["message"] = "Failed to find instructions for memory address.";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ const bool resolveSymbols = GetBoolean(arguments, "resolveSymbols", false);
+ llvm::json::Array instructions;
+ const auto num_insts = insts.GetSize();
+ for (size_t i = 0; i < num_insts; ++i) {
+ lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
+ auto addr = inst.GetAddress();
+ const auto inst_addr = addr.GetLoadAddress(dap.target);
+ const char *m = inst.GetMnemonic(dap.target);
+ const char *o = inst.GetOperands(dap.target);
+ const char *c = inst.GetComment(dap.target);
+ auto d = inst.GetData(dap.target);
+
+ std::string bytes;
+ llvm::raw_string_ostream sb(bytes);
+ for (unsigned i = 0; i < inst.GetByteSize(); i++) {
+ lldb::SBError error;
+ uint8_t b = d.GetUnsignedInt8(error, i);
+ if (error.Success()) {
+ sb << llvm::format("%2.2x ", b);
+ }
+ }
+
+ llvm::json::Object disassembled_inst{
+ {"address", "0x" + llvm::utohexstr(inst_addr)},
+ {"instructionBytes",
+ bytes.size() > 0 ? bytes.substr(0, bytes.size() - 1) : ""},
+ };
+
+ std::string instruction;
+ llvm::raw_string_ostream si(instruction);
+
+ lldb::SBSymbol symbol = addr.GetSymbol();
+ // Only add the symbol on the first line of the function.
+ if (symbol.IsValid() && symbol.GetStartAddress() == addr) {
+ // If we have a valid symbol, append it as a label prefix for the first
+ // instruction. This is so you can see the start of a function/callsite
+ // in the assembly, at the moment VS Code (1.80) does not visualize the
+ // symbol associated with the assembly instruction.
+ si << (symbol.GetMangledName() != nullptr ? symbol.GetMangledName()
+ : symbol.GetName())
+ << ": ";
+
+ if (resolveSymbols) {
+ disassembled_inst.try_emplace("symbol", symbol.GetDisplayName());
+ }
+ }
+
+ si << llvm::formatv("{0,7} {1,12}", m, o);
+ if (c && c[0]) {
+ si << " ; " << c;
+ }
+
+ disassembled_inst.try_emplace("instruction", instruction);
+
+ auto line_entry = addr.GetLineEntry();
+ // If the line number is 0 then the entry represents a compiler generated
+ // location.
+ if (line_entry.GetStartAddress() == addr && line_entry.IsValid() &&
+ line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0) {
+ auto source = CreateSource(line_entry);
+ disassembled_inst.try_emplace("location", source);
+
+ const auto line = line_entry.GetLine();
+ if (line && line != LLDB_INVALID_LINE_NUMBER) {
+ disassembled_inst.try_emplace("line", line);
+ }
+ const auto column = line_entry.GetColumn();
+ if (column && column != LLDB_INVALID_COLUMN_NUMBER) {
+ disassembled_inst.try_emplace("column", column);
+ }
+
+ auto end_line_entry = line_entry.GetEndAddress().GetLineEntry();
+ if (end_line_entry.IsValid() &&
+ end_line_entry.GetFileSpec() == line_entry.GetFileSpec()) {
+ const auto end_line = end_line_entry.GetLine();
+ if (end_line && end_line != LLDB_INVALID_LINE_NUMBER &&
+ end_line != line) {
+ disassembled_inst.try_emplace("endLine", end_line);
+
+ const auto end_column = end_line_entry.GetColumn();
+ if (end_column && end_column != LLDB_INVALID_COLUMN_NUMBER &&
+ end_column != column) {
+ disassembled_inst.try_emplace("endColumn", end_column - 1);
+ }
+ }
+ }
+ }
+
+ instructions.emplace_back(std::move(disassembled_inst));
+ }
+
+ llvm::json::Object body;
+ body.try_emplace("instructions", std::move(instructions));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/LocationsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LocationsRequestHandler.cpp
new file mode 100644
index 0000000000000..81ec42689b13f
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/LocationsRequestHandler.cpp
@@ -0,0 +1,160 @@
+//===-- LocationsRequestHandler.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "lldb/API/SBDeclaration.h"
+
+namespace lldb_dap {
+
+// "LocationsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Looks up information about a location reference
+// previously returned by the debug adapter.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "locations" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/LocationsArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "LocationsArguments": {
+// "type": "object",
+// "description": "Arguments for `locations` request.",
+// "properties": {
+// "locationReference": {
+// "type": "integer",
+// "description": "Location reference to resolve."
+// }
+// },
+// "required": [ "locationReference" ]
+// },
+// "LocationsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `locations` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "The source containing the location; either
+// `source.path` or `source.sourceReference` must be
+// specified."
+// },
+// "line": {
+// "type": "integer",
+// "description": "The line number of the location. The client
+// capability `linesStartAt1` determines whether it
+// is 0- or 1-based."
+// },
+// "column": {
+// "type": "integer",
+// "description": "Position of the location within the `line`. It is
+// measured in UTF-16 code units and the client
+// capability `columnsStartAt1` determines whether
+// it is 0- or 1-based. If no column is given, the
+// first position in the start line is assumed."
+// },
+// "endLine": {
+// "type": "integer",
+// "description": "End line of the location, present if the location
+// refers to a range. The client capability
+// `linesStartAt1` determines whether it is 0- or
+// 1-based."
+// },
+// "endColumn": {
+// "type": "integer",
+// "description": "End position of the location within `endLine`,
+// present if the location refers to a range. It is
+// measured in UTF-16 code units and the client
+// capability `columnsStartAt1` determines whether
+// it is 0- or 1-based."
+// }
+// },
+// "required": [ "source", "line" ]
+// }
+// }
+// }]
+// },
+void LocationsRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ auto *arguments = request.getObject("arguments");
+
+ uint64_t location_id = GetUnsigned(arguments, "locationReference", 0);
+ // We use the lowest bit to distinguish between value location and declaration
+ // location
+ auto [var_ref, is_value_location] = UnpackLocation(location_id);
+ lldb::SBValue variable = dap.variables.GetVariable(var_ref);
+ if (!variable.IsValid()) {
+ response["success"] = false;
+ response["message"] = "Invalid variable reference";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ llvm::json::Object body;
+ if (is_value_location) {
+ // Get the value location
+ if (!variable.GetType().IsPointerType() &&
+ !variable.GetType().IsReferenceType()) {
+ response["success"] = false;
+ response["message"] =
+ "Value locations are only available for pointers and references";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ lldb::addr_t addr = variable.GetValueAsAddress();
+ lldb::SBLineEntry line_entry =
+ dap.target.ResolveLoadAddress(addr).GetLineEntry();
+
+ if (!line_entry.IsValid()) {
+ response["success"] = false;
+ response["message"] = "Failed to resolve line entry for location";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ body.try_emplace("source", CreateSource(line_entry.GetFileSpec()));
+ if (int line = line_entry.GetLine())
+ body.try_emplace("line", line);
+ if (int column = line_entry.GetColumn())
+ body.try_emplace("column", column);
+ } else {
+ // Get the declaration location
+ lldb::SBDeclaration decl = variable.GetDeclaration();
+ if (!decl.IsValid()) {
+ response["success"] = false;
+ response["message"] = "No declaration location available";
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
+ body.try_emplace("source", CreateSource(decl.GetFileSpec()));
+ if (int line = decl.GetLine())
+ body.try_emplace("line", line);
+ if (int column = decl.GetColumn())
+ body.try_emplace("column", column);
+ }
+
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp
new file mode 100644
index 0000000000000..69b5d60655a1d
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp
@@ -0,0 +1,60 @@
+//===-- PauseRequestHandler.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "PauseRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Pause request; value of command field is 'pause'. The
+// request suspenses the debuggee. The debug adapter first sends the
+// PauseResponse and then a StoppedEvent (event type 'pause') after the
+// thread has been paused successfully.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "pause" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/PauseArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "PauseArguments": {
+// "type": "object",
+// "description": "Arguments for 'pause' request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Pause execution for this thread."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "PauseResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'pause' request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// }
+void PauseRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ lldb::SBProcess process = dap.target.GetProcess();
+ lldb::SBError error = process.Stop();
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/ReadMemoryRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ReadMemoryRequestHandler.cpp
new file mode 100644
index 0000000000000..f258dae3e4a22
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ReadMemoryRequestHandler.cpp
@@ -0,0 +1,139 @@
+//===-- ReadMemoryRequestHandler.cpp --------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Base64.h"
+
+namespace lldb_dap {
+
+// "ReadMemoryRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Reads bytes from memory at the provided location. Clients
+// should only call this request if the corresponding
+// capability `supportsReadMemoryRequest` is true.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "readMemory" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/ReadMemoryArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "ReadMemoryArguments": {
+// "type": "object",
+// "description": "Arguments for `readMemory` request.",
+// "properties": {
+// "memoryReference": {
+// "type": "string",
+// "description": "Memory reference to the base location from which data
+// should be read."
+// },
+// "offset": {
+// "type": "integer",
+// "description": "Offset (in bytes) to be applied to the reference
+// location before reading data. Can be negative."
+// },
+// "count": {
+// "type": "integer",
+// "description": "Number of bytes to read at the specified location and
+// offset."
+// }
+// },
+// "required": [ "memoryReference", "count" ]
+// },
+// "ReadMemoryResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `readMemory` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "address": {
+// "type": "string",
+// "description": "The address of the first byte of data returned.
+// Treated as a hex value if prefixed with `0x`, or
+// as a decimal value otherwise."
+// },
+// "unreadableBytes": {
+// "type": "integer",
+// "description": "The number of unreadable bytes encountered after
+// the last successfully read byte.\nThis can be
+// used to determine the number of bytes that should
+// be skipped before a subsequent
+// `readMemory` request succeeds."
+// },
+// "data": {
+// "type": "string",
+// "description": "The bytes read from memory, encoded using base64.
+// If the decoded length of `data` is less than the
+// requested `count` in the original `readMemory`
+// request, and `unreadableBytes` is zero or
+// omitted, then the client should assume it's
+// reached the end of readable memory."
+// }
+// },
+// "required": [ "address" ]
+// }
+// }
+// }]
+// },
+void ReadMemoryRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ auto *arguments = request.getObject("arguments");
+
+ llvm::StringRef memoryReference = GetString(arguments, "memoryReference");
+ auto addr_opt = DecodeMemoryReference(memoryReference);
+ if (!addr_opt.has_value()) {
+ response["success"] = false;
+ response["message"] =
+ "Malformed memory reference: " + memoryReference.str();
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ lldb::addr_t addr_int = *addr_opt;
+ addr_int += GetSigned(arguments, "offset", 0);
+ const uint64_t count_requested = GetUnsigned(arguments, "count", 0);
+
+ // We also need support reading 0 bytes
+ // VS Code sends those requests to check if a `memoryReference`
+ // can be dereferenced.
+ const uint64_t count_read = std::max<uint64_t>(count_requested, 1);
+ std::vector<uint8_t> buf;
+ buf.resize(count_read);
+ lldb::SBError error;
+ lldb::SBAddress addr{addr_int, dap.target};
+ size_t count_result =
+ dap.target.ReadMemory(addr, buf.data(), count_read, error);
+ if (count_result == 0) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", error.GetCString());
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+ buf.resize(std::min<size_t>(count_result, count_requested));
+
+ llvm::json::Object body;
+ std::string formatted_addr = "0x" + llvm::utohexstr(addr_int);
+ body.try_emplace("address", formatted_addr);
+ body.try_emplace("data", llvm::encodeBase64(buf));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index a30e0dcc2bd04..d73b6c8520659 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -223,6 +223,76 @@ class ModulesRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};
+class PauseRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "pause"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class ScopesRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "scopes"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class SetVariableRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "setVariable"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class SourceRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "source"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class StackTraceRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "stackTrace"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class ThreadsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "threads"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class VariablesRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "variables"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class LocationsRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "locations"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class DisassembleRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "disassemble"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
+class ReadMemoryRequestHandler : public RequestHandler {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral getCommand() { return "readMemory"; }
+ void operator()(const llvm::json::Object &request) override;
+};
+
/// A request used in testing to get the details on all breakpoints that are
/// currently set in the target. This helps us to test "setBreakpoints" and
/// "setFunctionBreakpoints" requests to verify we have the correct set of
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
new file mode 100644
index 0000000000000..d9a831436ec0e
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -0,0 +1,106 @@
+//===-- ScopesRequestHandler.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "ScopesRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Scopes request; value of command field is 'scopes'. The
+// request returns the variable scopes for a given stackframe ID.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "scopes" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/ScopesArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "ScopesArguments": {
+// "type": "object",
+// "description": "Arguments for 'scopes' request.",
+// "properties": {
+// "frameId": {
+// "type": "integer",
+// "description": "Retrieve the scopes for this stackframe."
+// }
+// },
+// "required": [ "frameId" ]
+// },
+// "ScopesResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'scopes' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "scopes": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/Scope"
+// },
+// "description": "The scopes of the stackframe. If the array has
+// length zero, there are no scopes available."
+// }
+// },
+// "required": [ "scopes" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void ScopesRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+ const auto *arguments = request.getObject("arguments");
+ lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+ // As the user selects different stack frames in the GUI, a "scopes" request
+ // will be sent to the DAP. This is the only way we know that the user has
+ // selected a frame in a thread. There are no other notifications that are
+ // sent and VS code doesn't allow multiple frames to show variables
+ // concurrently. If we select the thread and frame as the "scopes" requests
+ // are sent, this allows users to type commands in the debugger console
+ // with a backtick character to run lldb commands and these lldb commands
+ // will now have the right context selected as they are run. If the user
+ // types "`bt" into the debugger console and we had another thread selected
+ // in the LLDB library, we would show the wrong thing to the user. If the
+ // users switches threads with a lldb command like "`thread select 14", the
+ // GUI will not update as there are no "event" notification packets that
+ // allow us to change the currently selected thread or frame in the GUI that
+ // I am aware of.
+ if (frame.IsValid()) {
+ frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
+ frame.GetThread().SetSelectedFrame(frame.GetFrameID());
+ }
+
+ dap.variables.locals = frame.GetVariables(/*arguments=*/true,
+ /*locals=*/true,
+ /*statics=*/false,
+ /*in_scope_only=*/true);
+ dap.variables.globals = frame.GetVariables(/*arguments=*/false,
+ /*locals=*/false,
+ /*statics=*/true,
+ /*in_scope_only=*/true);
+ dap.variables.registers = frame.GetRegisters();
+ body.try_emplace("scopes", dap.CreateTopLevelScopes());
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
new file mode 100644
index 0000000000000..943e3a8a5060e
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
@@ -0,0 +1,177 @@
+//===-- SetVariableRequestHandler.cpp -------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "SetVariableRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "setVariable request; value of command field is
+// 'setVariable'. Set the variable with the given name in the variable
+// container to a new value.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "setVariable" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SetVariableArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SetVariableArguments": {
+// "type": "object",
+// "description": "Arguments for 'setVariable' request.",
+// "properties": {
+// "variablesReference": {
+// "type": "integer",
+// "description": "The reference of the variable container."
+// },
+// "name": {
+// "type": "string",
+// "description": "The name of the variable."
+// },
+// "value": {
+// "type": "string",
+// "description": "The value of the variable."
+// },
+// "format": {
+// "$ref": "#/definitions/ValueFormat",
+// "description": "Specifies details on how to format the response value."
+// }
+// },
+// "required": [ "variablesReference", "name", "value" ]
+// },
+// "SetVariableResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'setVariable' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "value": {
+// "type": "string",
+// "description": "The new value of the variable."
+// },
+// "type": {
+// "type": "string",
+// "description": "The type of the new value. Typically shown in the
+// UI when hovering over the value."
+// },
+// "variablesReference": {
+// "type": "number",
+// "description": "If variablesReference is > 0, the new value is
+// structured and its children can be retrieved by passing
+// variablesReference to the VariablesRequest."
+// },
+// "namedVariables": {
+// "type": "number",
+// "description": "The number of named child variables. The client
+// can use this optional information to present the variables in a
+// paged UI and fetch them in chunks."
+// },
+// "indexedVariables": {
+// "type": "number",
+// "description": "The number of indexed child variables. The client
+// can use this optional information to present the variables in a
+// paged UI and fetch them in chunks."
+// },
+// "valueLocationReference": {
+// "type": "integer",
+// "description": "A reference that allows the client to request the
+// location where the new value is declared. For example, if the new
+// value is function pointer, the adapter may be able to look up the
+// function's location. This should be present only if the adapter
+// is likely to be able to resolve the location.\n\nThis reference
+// shares the same lifetime as the `variablesReference`. See
+// 'Lifetime of Object References' in the Overview section for
+// details."
+// }
+// },
+// "required": [ "value" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void SetVariableRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Array variables;
+ llvm::json::Object body;
+ const auto *arguments = request.getObject("arguments");
+ // This is a reference to the containing variable/scope
+ const auto variablesReference =
+ GetUnsigned(arguments, "variablesReference", 0);
+ llvm::StringRef name = GetString(arguments, "name");
+
+ const auto value = GetString(arguments, "value");
+ // Set success to false just in case we don't find the variable by name
+ response.try_emplace("success", false);
+
+ lldb::SBValue variable;
+
+ // The "id" is the unique integer ID that is unique within the enclosing
+ // variablesReference. It is optionally added to any "interface Variable"
+ // objects to uniquely identify a variable within an enclosing
+ // variablesReference. It helps to disambiguate between two variables that
+ // have the same name within the same scope since the "setVariables" request
+ // only specifies the variable reference of the enclosing scope/variable, and
+ // the name of the variable. We could have two shadowed variables with the
+ // same name in "Locals" or "Globals". In our case the "id" absolute index
+ // of the variable within the dap.variables list.
+ const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX);
+ if (id_value != UINT64_MAX) {
+ variable = dap.variables.GetVariable(id_value);
+ } else {
+ variable = FindVariable(variablesReference, name);
+ }
+
+ if (variable.IsValid()) {
+ lldb::SBError error;
+ bool success = variable.SetValueFromCString(value.data(), error);
+ if (success) {
+ VariableDescription desc(variable, dap.enable_auto_variable_summaries);
+ EmplaceSafeString(body, "result", desc.display_value);
+ EmplaceSafeString(body, "type", desc.display_type_name);
+
+ // We don't know the index of the variable in our dap.variables
+ // so always insert a new one to get its variablesReference.
+ // is_permanent is false because debug console does not support
+ // setVariable request.
+ int64_t new_var_ref =
+ dap.variables.InsertVariable(variable, /*is_permanent=*/false);
+ if (variable.MightHaveChildren())
+ body.try_emplace("variablesReference", new_var_ref);
+ else
+ body.try_emplace("variablesReference", 0);
+ if (lldb::addr_t addr = variable.GetLoadAddress();
+ addr != LLDB_INVALID_ADDRESS)
+ body.try_emplace("memoryReference", EncodeMemoryReference(addr));
+ if (ValuePointsToCode(variable))
+ body.try_emplace("valueLocationReference", new_var_ref);
+ } else {
+ EmplaceSafeString(body, "message", std::string(error.GetCString()));
+ }
+ response["success"] = llvm::json::Value(success);
+ } else {
+ response["success"] = llvm::json::Value(false);
+ }
+
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
new file mode 100644
index 0000000000000..03561c9e64922
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
@@ -0,0 +1,82 @@
+//===-- SourceRequestHandler.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "SourceRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Source request; value of command field is 'source'. The
+// request retrieves the source code for a given source reference.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "source" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SourceArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SourceArguments": {
+// "type": "object",
+// "description": "Arguments for 'source' request.",
+// "properties": {
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "Specifies the source content to load. Either
+// source.path or source.sourceReference must be specified."
+// },
+// "sourceReference": {
+// "type": "integer",
+// "description": "The reference to the source. This is the same as
+// source.sourceReference. This is provided for backward compatibility
+// since old backends do not understand the 'source' attribute."
+// }
+// },
+// "required": [ "sourceReference" ]
+// },
+// "SourceResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'source' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "content": {
+// "type": "string",
+// "description": "Content of the source reference."
+// },
+// "mimeType": {
+// "type": "string",
+// "description": "Optional content type (mime type) of the source."
+// }
+// },
+// "required": [ "content" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void SourceRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body{{"content", ""}};
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp
new file mode 100644
index 0000000000000..c7c8bbf6902e9
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp
@@ -0,0 +1,197 @@
+//===-- StackTraceRequestHandler.cpp --------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+/// Page size used for reporting addtional frames in the 'stackTrace' request.
+static constexpr int StackPageSize = 20;
+
+// Fill in the stack frames of the thread.
+//
+// Threads stacks may contain runtime specific extended backtraces, when
+// constructing a stack trace first report the full thread stack trace then
+// perform a breadth first traversal of any extended backtrace frames.
+//
+// For example:
+//
+// Thread (id=th0) stack=[s0, s1, s2, s3]
+// \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1]
+// \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1]
+// \ Extended backtrace "Application Specific Backtrace" Thread (id=th3)
+// stack=[s0, s1, s2]
+//
+// Which will flatten into:
+//
+// 0. th0->s0
+// 1. th0->s1
+// 2. th0->s2
+// 3. th0->s3
+// 4. label - Enqueued from th1, sf=-1, i=-4
+// 5. th1->s0
+// 6. th1->s1
+// 7. label - Enqueued from th2
+// 8. th2->s0
+// 9. th2->s1
+// 10. label - Application Specific Backtrace
+// 11. th3->s0
+// 12. th3->s1
+// 13. th3->s2
+//
+// s=3,l=3 = [th0->s3, label1, th1->s0]
+static bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
+ llvm::json::Array &stack_frames, int64_t &offset,
+ const int64_t start_frame, const int64_t levels) {
+ bool reached_end_of_stack = false;
+ for (int64_t i = start_frame;
+ static_cast<int64_t>(stack_frames.size()) < levels; i++) {
+ if (i == -1) {
+ stack_frames.emplace_back(
+ CreateExtendedStackFrameLabel(thread, dap.frame_format));
+ continue;
+ }
+
+ lldb::SBFrame frame = thread.GetFrameAtIndex(i);
+ if (!frame.IsValid()) {
+ offset += thread.GetNumFrames() + 1 /* label between threads */;
+ reached_end_of_stack = true;
+ break;
+ }
+
+ stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format));
+ }
+
+ if (dap.display_extended_backtrace && reached_end_of_stack) {
+ // Check for any extended backtraces.
+ for (uint32_t bt = 0;
+ bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) {
+ lldb::SBThread backtrace = thread.GetExtendedBacktraceThread(
+ thread.GetProcess().GetExtendedBacktraceTypeAtIndex(bt));
+ if (!backtrace.IsValid())
+ continue;
+
+ reached_end_of_stack = FillStackFrames(
+ dap, backtrace, stack_frames, offset,
+ (start_frame - offset) > 0 ? start_frame - offset : -1, levels);
+ if (static_cast<int64_t>(stack_frames.size()) >= levels)
+ break;
+ }
+ }
+
+ return reached_end_of_stack;
+}
+
+// "StackTraceRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "StackTrace request; value of command field is
+// 'stackTrace'. The request returns a stacktrace from the current execution
+// state.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "stackTrace" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/StackTraceArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "StackTraceArguments": {
+// "type": "object",
+// "description": "Arguments for 'stackTrace' request.",
+// "properties": {
+// "threadId": {
+// "type": "integer",
+// "description": "Retrieve the stacktrace for this thread."
+// },
+// "startFrame": {
+// "type": "integer",
+// "description": "The index of the first frame to return; if omitted
+// frames start at 0."
+// },
+// "levels": {
+// "type": "integer",
+// "description": "The maximum number of frames to return. If levels is
+// not specified or 0, all frames are returned."
+// },
+// "format": {
+// "$ref": "#/definitions/StackFrameFormat",
+// "description": "Specifies details on how to format the stack frames.
+// The attribute is only honored by a debug adapter if the corresponding
+// capability `supportsValueFormattingOptions` is true."
+// }
+// },
+// "required": [ "threadId" ]
+// },
+// "StackTraceResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `stackTrace` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "stackFrames": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/StackFrame"
+// },
+// "description": "The frames of the stackframe. If the array has
+// length zero, there are no stackframes available. This means that
+// there is no location information available."
+// },
+// "totalFrames": {
+// "type": "integer",
+// "description": "The total number of frames available in the
+// stack. If omitted or if `totalFrames` is larger than the
+// available frames, a client is expected to request frames until
+// a request returns less frames than requested (which indicates
+// the end of the stack). Returning monotonically increasing
+// `totalFrames` values for subsequent requests can be used to
+// enforce paging in the client."
+// }
+// },
+// "required": [ "stackFrames" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void StackTraceRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ lldb::SBError error;
+ const auto *arguments = request.getObject("arguments");
+ lldb::SBThread thread = dap.GetLLDBThread(*arguments);
+ llvm::json::Array stack_frames;
+ llvm::json::Object body;
+
+ if (thread.IsValid()) {
+ const auto start_frame = GetUnsigned(arguments, "startFrame", 0);
+ const auto levels = GetUnsigned(arguments, "levels", 0);
+ int64_t offset = 0;
+ bool reached_end_of_stack =
+ FillStackFrames(dap, thread, stack_frames, offset, start_frame,
+ levels == 0 ? INT64_MAX : levels);
+ body.try_emplace("totalFrames",
+ start_frame + stack_frames.size() +
+ (reached_end_of_stack ? 0 : StackPageSize));
+ }
+
+ body.try_emplace("stackFrames", std::move(stack_frames));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp
new file mode 100644
index 0000000000000..a70722b11fb29
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp
@@ -0,0 +1,71 @@
+//===-- ThreadsRequestHandler.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "ThreadsRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Thread request; value of command field is 'threads'. The
+// request retrieves a list of all threads.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "threads" ]
+// }
+// },
+// "required": [ "command" ]
+// }]
+// },
+// "ThreadsResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'threads' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "threads": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/Thread"
+// },
+// "description": "All threads."
+// }
+// },
+// "required": [ "threads" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void ThreadsRequestHandler::operator()(const llvm::json::Object &request) {
+ lldb::SBProcess process = dap.target.GetProcess();
+ llvm::json::Object response;
+ FillResponse(request, response);
+
+ const uint32_t num_threads = process.GetNumThreads();
+ llvm::json::Array threads;
+ for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
+ lldb::SBThread thread = process.GetThreadAtIndex(thread_idx);
+ threads.emplace_back(CreateThread(thread, dap.thread_format));
+ }
+ if (threads.size() == 0) {
+ response["success"] = llvm::json::Value(false);
+ }
+ llvm::json::Object body;
+ body.try_emplace("threads", std::move(threads));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
new file mode 100644
index 0000000000000..641e27189fa18
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -0,0 +1,217 @@
+//===-- VariablesRequestHandler.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "EventHelper.h"
+#include "JSONUtils.h"
+#include "RequestHandler.h"
+
+namespace lldb_dap {
+
+// "VariablesRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Variables request; value of command field is 'variables'.
+// Retrieves all child variables for the given variable reference. An
+// optional filter can be used to limit the fetched children to either named
+// or indexed children.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "variables" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/VariablesArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "VariablesArguments": {
+// "type": "object",
+// "description": "Arguments for 'variables' request.",
+// "properties": {
+// "variablesReference": {
+// "type": "integer",
+// "description": "The Variable reference."
+// },
+// "filter": {
+// "type": "string",
+// "enum": [ "indexed", "named" ],
+// "description": "Optional filter to limit the child variables to either
+// named or indexed. If ommited, both types are fetched."
+// },
+// "start": {
+// "type": "integer",
+// "description": "The index of the first variable to return; if omitted
+// children start at 0."
+// },
+// "count": {
+// "type": "integer",
+// "description": "The number of variables to return. If count is missing
+// or 0, all variables are returned."
+// },
+// "format": {
+// "$ref": "#/definitions/ValueFormat",
+// "description": "Specifies details on how to format the Variable
+// values."
+// }
+// },
+// "required": [ "variablesReference" ]
+// },
+// "VariablesResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'variables' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "variables": {
+// "type": "array",
+// "items": {
+// "$ref": "#/definitions/Variable"
+// },
+// "description": "All (or a range) of variables for the given
+// variable reference."
+// }
+// },
+// "required": [ "variables" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+void VariablesRequestHandler::operator()(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Array variables;
+ const auto *arguments = request.getObject("arguments");
+ const auto variablesReference =
+ GetUnsigned(arguments, "variablesReference", 0);
+ const int64_t start = GetSigned(arguments, "start", 0);
+ const int64_t count = GetSigned(arguments, "count", 0);
+ bool hex = false;
+ const auto *format = arguments->getObject("format");
+ if (format)
+ hex = GetBoolean(format, "hex", false);
+
+ if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) {
+ // variablesReference is one of our scopes, not an actual variable it is
+ // asking for the list of args, locals or globals.
+ int64_t start_idx = 0;
+ int64_t num_children = 0;
+
+ if (variablesReference == VARREF_REGS) {
+ // Change the default format of any pointer sized registers in the first
+ // register set to be the lldb::eFormatAddressInfo so we show the pointer
+ // and resolve what the pointer resolves to. Only change the format if the
+ // format was set to the default format or if it was hex as some registers
+ // have formats set for them.
+ const uint32_t addr_size = dap.target.GetProcess().GetAddressByteSize();
+ lldb::SBValue reg_set = dap.variables.registers.GetValueAtIndex(0);
+ const uint32_t num_regs = reg_set.GetNumChildren();
+ for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) {
+ lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx);
+ const lldb::Format format = reg.GetFormat();
+ if (format == lldb::eFormatDefault || format == lldb::eFormatHex) {
+ if (reg.GetByteSize() == addr_size)
+ reg.SetFormat(lldb::eFormatAddressInfo);
+ }
+ }
+ }
+
+ num_children = top_scope->GetSize();
+ if (num_children == 0 && variablesReference == VARREF_LOCALS) {
+ // Check for an error in the SBValueList that might explain why we don't
+ // have locals. If we have an error display it as the sole value in the
+ // the locals.
+
+ // "error" owns the error string so we must keep it alive as long as we
+ // want to use the returns "const char *"
+ lldb::SBError error = top_scope->GetError();
+ const char *var_err = error.GetCString();
+ if (var_err) {
+ // Create a fake variable named "error" to explain why variables were
+ // not available. This new error will help let users know when there was
+ // a problem that kept variables from being available for display and
+ // allow users to fix this issue instead of seeing no variables. The
+ // errors are only set when there is a problem that the user could
+ // fix, so no error will show up when you have no debug info, only when
+ // we do have debug info and something that is fixable can be done.
+ llvm::json::Object object;
+ EmplaceSafeString(object, "name", "<error>");
+ EmplaceSafeString(object, "type", "const char *");
+ EmplaceSafeString(object, "value", var_err);
+ object.try_emplace("variablesReference", (int64_t)0);
+ variables.emplace_back(std::move(object));
+ }
+ }
+ const int64_t end_idx = start_idx + ((count == 0) ? num_children : count);
+
+ // We first find out which variable names are duplicated
+ std::map<std::string, int> variable_name_counts;
+ for (auto i = start_idx; i < end_idx; ++i) {
+ lldb::SBValue variable = top_scope->GetValueAtIndex(i);
+ if (!variable.IsValid())
+ break;
+ variable_name_counts[GetNonNullVariableName(variable)]++;
+ }
+
+ // Now we construct the result with unique display variable names
+ for (auto i = start_idx; i < end_idx; ++i) {
+ lldb::SBValue variable = top_scope->GetValueAtIndex(i);
+
+ if (!variable.IsValid())
+ break;
+
+ int64_t var_ref =
+ dap.variables.InsertVariable(variable, /*is_permanent=*/false);
+ variables.emplace_back(CreateVariable(
+ variable, var_ref, hex, dap.enable_auto_variable_summaries,
+ dap.enable_synthetic_child_debugging,
+ variable_name_counts[GetNonNullVariableName(variable)] > 1));
+ }
+ } else {
+ // We are expanding a variable that has children, so we will return its
+ // children.
+ lldb::SBValue variable = dap.variables.GetVariable(variablesReference);
+ if (variable.IsValid()) {
+ auto addChild = [&](lldb::SBValue child,
+ std::optional<std::string> custom_name = {}) {
+ if (!child.IsValid())
+ return;
+ bool is_permanent =
+ dap.variables.IsPermanentVariableReference(variablesReference);
+ int64_t var_ref = dap.variables.InsertVariable(child, is_permanent);
+ variables.emplace_back(CreateVariable(
+ child, var_ref, hex, dap.enable_auto_variable_summaries,
+ dap.enable_synthetic_child_debugging,
+ /*is_name_duplicated=*/false, custom_name));
+ };
+ const int64_t num_children = variable.GetNumChildren();
+ int64_t end_idx = start + ((count == 0) ? num_children : count);
+ int64_t i = start;
+ for (; i < end_idx && i < num_children; ++i)
+ addChild(variable.GetChildAtIndex(i));
+
+ // If we haven't filled the count quota from the request, we insert a new
+ // "[raw]" child that can be used to inspect the raw version of a
+ // synthetic member. That eliminates the need for the user to go to the
+ // debug console and type `frame var <variable> to get these values.
+ if (dap.enable_synthetic_child_debugging && variable.IsSynthetic() &&
+ i == num_children)
+ addChild(variable.GetNonSyntheticValue(), "[raw]");
+ }
+ }
+ llvm::json::Object body;
+ body.try_emplace("variables", std::move(variables));
+ response.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index fd4615897841c..fcf2d122e5313 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -14,7 +14,6 @@
#include "LLDBUtils.h"
#include "RunInTerminal.h"
#include "Watchpoint.h"
-#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBFile.h"
#include "lldb/API/SBInstruction.h"
@@ -36,7 +35,6 @@
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
-#include "llvm/Support/Base64.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
@@ -127,1328 +125,6 @@ class LLDBDAPOptTable : public llvm::opt::GenericOptTable {
typedef void (*RequestCallback)(const llvm::json::Object &command);
-/// Page size used for reporting addtional frames in the 'stackTrace' request.
-constexpr int StackPageSize = 20;
-
-lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) {
- switch (variablesReference) {
- case VARREF_LOCALS:
- return &dap.variables.locals;
- case VARREF_GLOBALS:
- return &dap.variables.globals;
- case VARREF_REGS:
- return &dap.variables.registers;
- default:
- return nullptr;
- }
-}
-
-lldb::SBValue FindVariable(DAP &dap, uint64_t variablesReference,
- llvm::StringRef name) {
- lldb::SBValue variable;
- if (lldb::SBValueList *top_scope =
- GetTopLevelScope(dap, variablesReference)) {
- bool is_duplicated_variable_name = name.contains(" @");
- // variablesReference is one of our scopes, not an actual variable it is
- // asking for a variable in locals or globals or registers
- int64_t end_idx = top_scope->GetSize();
- // Searching backward so that we choose the variable in closest scope
- // among variables of the same name.
- for (int64_t i = end_idx - 1; i >= 0; --i) {
- lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
- std::string variable_name = CreateUniqueVariableNameForDisplay(
- curr_variable, is_duplicated_variable_name);
- if (variable_name == name) {
- variable = curr_variable;
- break;
- }
- }
- } else {
- // This is not under the globals or locals scope, so there are no duplicated
- // names.
-
- // We have a named item within an actual variable so we need to find it
- // withing the container variable by name.
- lldb::SBValue container = dap.variables.GetVariable(variablesReference);
- variable = container.GetChildMemberWithName(name.data());
- if (!variable.IsValid()) {
- if (name.starts_with("[")) {
- llvm::StringRef index_str(name.drop_front(1));
- uint64_t index = 0;
- if (!index_str.consumeInteger(0, index)) {
- if (index_str == "]")
- variable = container.GetChildAtIndex(index);
- }
- }
- }
- }
- return variable;
-}
-
-// Fill in the stack frames of the thread.
-//
-// Threads stacks may contain runtime specific extended backtraces, when
-// constructing a stack trace first report the full thread stack trace then
-// perform a breadth first traversal of any extended backtrace frames.
-//
-// For example:
-//
-// Thread (id=th0) stack=[s0, s1, s2, s3]
-// \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1]
-// \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1]
-// \ Extended backtrace "Application Specific Backtrace" Thread (id=th3)
-// stack=[s0, s1, s2]
-//
-// Which will flatten into:
-//
-// 0. th0->s0
-// 1. th0->s1
-// 2. th0->s2
-// 3. th0->s3
-// 4. label - Enqueued from th1, sf=-1, i=-4
-// 5. th1->s0
-// 6. th1->s1
-// 7. label - Enqueued from th2
-// 8. th2->s0
-// 9. th2->s1
-// 10. label - Application Specific Backtrace
-// 11. th3->s0
-// 12. th3->s1
-// 13. th3->s2
-//
-// s=3,l=3 = [th0->s3, label1, th1->s0]
-bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
- llvm::json::Array &stack_frames, int64_t &offset,
- const int64_t start_frame, const int64_t levels) {
- bool reached_end_of_stack = false;
- for (int64_t i = start_frame;
- static_cast<int64_t>(stack_frames.size()) < levels; i++) {
- if (i == -1) {
- stack_frames.emplace_back(
- CreateExtendedStackFrameLabel(thread, dap.frame_format));
- continue;
- }
-
- lldb::SBFrame frame = thread.GetFrameAtIndex(i);
- if (!frame.IsValid()) {
- offset += thread.GetNumFrames() + 1 /* label between threads */;
- reached_end_of_stack = true;
- break;
- }
-
- stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format));
- }
-
- if (dap.display_extended_backtrace && reached_end_of_stack) {
- // Check for any extended backtraces.
- for (uint32_t bt = 0;
- bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) {
- lldb::SBThread backtrace = thread.GetExtendedBacktraceThread(
- thread.GetProcess().GetExtendedBacktraceTypeAtIndex(bt));
- if (!backtrace.IsValid())
- continue;
-
- reached_end_of_stack = FillStackFrames(
- dap, backtrace, stack_frames, offset,
- (start_frame - offset) > 0 ? start_frame - offset : -1, levels);
- if (static_cast<int64_t>(stack_frames.size()) >= levels)
- break;
- }
- }
-
- return reached_end_of_stack;
-}
-
-// "PauseRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Pause request; value of command field is 'pause'. The
-// request suspenses the debuggee. The debug adapter first sends the
-// PauseResponse and then a StoppedEvent (event type 'pause') after the
-// thread has been paused successfully.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "pause" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/PauseArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "PauseArguments": {
-// "type": "object",
-// "description": "Arguments for 'pause' request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Pause execution for this thread."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "PauseResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'pause' request. This is just an
-// acknowledgement, so no body field is required."
-// }]
-// }
-void request_pause(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- lldb::SBProcess process = dap.target.GetProcess();
- lldb::SBError error = process.Stop();
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "ScopesRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Scopes request; value of command field is 'scopes'. The
-// request returns the variable scopes for a given stackframe ID.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "scopes" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/ScopesArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "ScopesArguments": {
-// "type": "object",
-// "description": "Arguments for 'scopes' request.",
-// "properties": {
-// "frameId": {
-// "type": "integer",
-// "description": "Retrieve the scopes for this stackframe."
-// }
-// },
-// "required": [ "frameId" ]
-// },
-// "ScopesResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'scopes' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "scopes": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Scope"
-// },
-// "description": "The scopes of the stackframe. If the array has
-// length zero, there are no scopes available."
-// }
-// },
-// "required": [ "scopes" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_scopes(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- const auto *arguments = request.getObject("arguments");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
- // As the user selects different stack frames in the GUI, a "scopes" request
- // will be sent to the DAP. This is the only way we know that the user has
- // selected a frame in a thread. There are no other notifications that are
- // sent and VS code doesn't allow multiple frames to show variables
- // concurrently. If we select the thread and frame as the "scopes" requests
- // are sent, this allows users to type commands in the debugger console
- // with a backtick character to run lldb commands and these lldb commands
- // will now have the right context selected as they are run. If the user
- // types "`bt" into the debugger console and we had another thread selected
- // in the LLDB library, we would show the wrong thing to the user. If the
- // users switches threads with a lldb command like "`thread select 14", the
- // GUI will not update as there are no "event" notification packets that
- // allow us to change the currently selected thread or frame in the GUI that
- // I am aware of.
- if (frame.IsValid()) {
- frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
- frame.GetThread().SetSelectedFrame(frame.GetFrameID());
- }
-
- dap.variables.locals = frame.GetVariables(/*arguments=*/true,
- /*locals=*/true,
- /*statics=*/false,
- /*in_scope_only=*/true);
- dap.variables.globals = frame.GetVariables(/*arguments=*/false,
- /*locals=*/false,
- /*statics=*/true,
- /*in_scope_only=*/true);
- dap.variables.registers = frame.GetRegisters();
- body.try_emplace("scopes", dap.CreateTopLevelScopes());
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "SourceRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Source request; value of command field is 'source'. The
-// request retrieves the source code for a given source reference.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "source" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/SourceArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "SourceArguments": {
-// "type": "object",
-// "description": "Arguments for 'source' request.",
-// "properties": {
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "Specifies the source content to load. Either
-// source.path or source.sourceReference must be specified."
-// },
-// "sourceReference": {
-// "type": "integer",
-// "description": "The reference to the source. This is the same as
-// source.sourceReference. This is provided for backward compatibility
-// since old backends do not understand the 'source' attribute."
-// }
-// },
-// "required": [ "sourceReference" ]
-// },
-// "SourceResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'source' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "content": {
-// "type": "string",
-// "description": "Content of the source reference."
-// },
-// "mimeType": {
-// "type": "string",
-// "description": "Optional content type (mime type) of the source."
-// }
-// },
-// "required": [ "content" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_source(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body{{"content", ""}};
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "StackTraceRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "StackTrace request; value of command field is
-// 'stackTrace'. The request returns a stacktrace from the current execution
-// state.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "stackTrace" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/StackTraceArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "StackTraceArguments": {
-// "type": "object",
-// "description": "Arguments for 'stackTrace' request.",
-// "properties": {
-// "threadId": {
-// "type": "integer",
-// "description": "Retrieve the stacktrace for this thread."
-// },
-// "startFrame": {
-// "type": "integer",
-// "description": "The index of the first frame to return; if omitted
-// frames start at 0."
-// },
-// "levels": {
-// "type": "integer",
-// "description": "The maximum number of frames to return. If levels is
-// not specified or 0, all frames are returned."
-// },
-// "format": {
-// "$ref": "#/definitions/StackFrameFormat",
-// "description": "Specifies details on how to format the stack frames.
-// The attribute is only honored by a debug adapter if the corresponding
-// capability `supportsValueFormattingOptions` is true."
-// }
-// },
-// "required": [ "threadId" ]
-// },
-// "StackTraceResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `stackTrace` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "stackFrames": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/StackFrame"
-// },
-// "description": "The frames of the stackframe. If the array has
-// length zero, there are no stackframes available. This means that
-// there is no location information available."
-// },
-// "totalFrames": {
-// "type": "integer",
-// "description": "The total number of frames available in the
-// stack. If omitted or if `totalFrames` is larger than the
-// available frames, a client is expected to request frames until
-// a request returns less frames than requested (which indicates
-// the end of the stack). Returning monotonically increasing
-// `totalFrames` values for subsequent requests can be used to
-// enforce paging in the client."
-// }
-// },
-// "required": [ "stackFrames" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_stackTrace(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- lldb::SBError error;
- const auto *arguments = request.getObject("arguments");
- lldb::SBThread thread = dap.GetLLDBThread(*arguments);
- llvm::json::Array stack_frames;
- llvm::json::Object body;
-
- if (thread.IsValid()) {
- const auto start_frame = GetUnsigned(arguments, "startFrame", 0);
- const auto levels = GetUnsigned(arguments, "levels", 0);
- int64_t offset = 0;
- bool reached_end_of_stack =
- FillStackFrames(dap, thread, stack_frames, offset, start_frame,
- levels == 0 ? INT64_MAX : levels);
- body.try_emplace("totalFrames",
- start_frame + stack_frames.size() +
- (reached_end_of_stack ? 0 : StackPageSize));
- }
-
- body.try_emplace("stackFrames", std::move(stack_frames));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "ThreadsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Thread request; value of command field is 'threads'. The
-// request retrieves a list of all threads.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "threads" ]
-// }
-// },
-// "required": [ "command" ]
-// }]
-// },
-// "ThreadsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'threads' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "threads": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Thread"
-// },
-// "description": "All threads."
-// }
-// },
-// "required": [ "threads" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_threads(DAP &dap, const llvm::json::Object &request) {
- lldb::SBProcess process = dap.target.GetProcess();
- llvm::json::Object response;
- FillResponse(request, response);
-
- const uint32_t num_threads = process.GetNumThreads();
- llvm::json::Array threads;
- for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
- lldb::SBThread thread = process.GetThreadAtIndex(thread_idx);
- threads.emplace_back(CreateThread(thread, dap.thread_format));
- }
- if (threads.size() == 0) {
- response["success"] = llvm::json::Value(false);
- }
- llvm::json::Object body;
- body.try_emplace("threads", std::move(threads));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "SetVariableRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "setVariable request; value of command field is
-// 'setVariable'. Set the variable with the given name in the variable
-// container to a new value.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "setVariable" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/SetVariableArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "SetVariableArguments": {
-// "type": "object",
-// "description": "Arguments for 'setVariable' request.",
-// "properties": {
-// "variablesReference": {
-// "type": "integer",
-// "description": "The reference of the variable container."
-// },
-// "name": {
-// "type": "string",
-// "description": "The name of the variable."
-// },
-// "value": {
-// "type": "string",
-// "description": "The value of the variable."
-// },
-// "format": {
-// "$ref": "#/definitions/ValueFormat",
-// "description": "Specifies details on how to format the response value."
-// }
-// },
-// "required": [ "variablesReference", "name", "value" ]
-// },
-// "SetVariableResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'setVariable' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "value": {
-// "type": "string",
-// "description": "The new value of the variable."
-// },
-// "type": {
-// "type": "string",
-// "description": "The type of the new value. Typically shown in the
-// UI when hovering over the value."
-// },
-// "variablesReference": {
-// "type": "number",
-// "description": "If variablesReference is > 0, the new value is
-// structured and its children can be retrieved by passing
-// variablesReference to the VariablesRequest."
-// },
-// "namedVariables": {
-// "type": "number",
-// "description": "The number of named child variables. The client
-// can use this optional information to present the variables in a
-// paged UI and fetch them in chunks."
-// },
-// "indexedVariables": {
-// "type": "number",
-// "description": "The number of indexed child variables. The client
-// can use this optional information to present the variables in a
-// paged UI and fetch them in chunks."
-// },
-// "valueLocationReference": {
-// "type": "integer",
-// "description": "A reference that allows the client to request the
-// location where the new value is declared. For example, if the new
-// value is function pointer, the adapter may be able to look up the
-// function's location. This should be present only if the adapter
-// is likely to be able to resolve the location.\n\nThis reference
-// shares the same lifetime as the `variablesReference`. See
-// 'Lifetime of Object References' in the Overview section for
-// details."
-// }
-// },
-// "required": [ "value" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_setVariable(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Array variables;
- llvm::json::Object body;
- const auto *arguments = request.getObject("arguments");
- // This is a reference to the containing variable/scope
- const auto variablesReference =
- GetUnsigned(arguments, "variablesReference", 0);
- llvm::StringRef name = GetString(arguments, "name");
-
- const auto value = GetString(arguments, "value");
- // Set success to false just in case we don't find the variable by name
- response.try_emplace("success", false);
-
- lldb::SBValue variable;
-
- // The "id" is the unique integer ID that is unique within the enclosing
- // variablesReference. It is optionally added to any "interface Variable"
- // objects to uniquely identify a variable within an enclosing
- // variablesReference. It helps to disambiguate between two variables that
- // have the same name within the same scope since the "setVariables" request
- // only specifies the variable reference of the enclosing scope/variable, and
- // the name of the variable. We could have two shadowed variables with the
- // same name in "Locals" or "Globals". In our case the "id" absolute index
- // of the variable within the dap.variables list.
- const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX);
- if (id_value != UINT64_MAX) {
- variable = dap.variables.GetVariable(id_value);
- } else {
- variable = FindVariable(dap, variablesReference, name);
- }
-
- if (variable.IsValid()) {
- lldb::SBError error;
- bool success = variable.SetValueFromCString(value.data(), error);
- if (success) {
- VariableDescription desc(variable, dap.enable_auto_variable_summaries);
- EmplaceSafeString(body, "result", desc.display_value);
- EmplaceSafeString(body, "type", desc.display_type_name);
-
- // We don't know the index of the variable in our dap.variables
- // so always insert a new one to get its variablesReference.
- // is_permanent is false because debug console does not support
- // setVariable request.
- int64_t new_var_ref =
- dap.variables.InsertVariable(variable, /*is_permanent=*/false);
- if (variable.MightHaveChildren())
- body.try_emplace("variablesReference", new_var_ref);
- else
- body.try_emplace("variablesReference", 0);
- if (lldb::addr_t addr = variable.GetLoadAddress();
- addr != LLDB_INVALID_ADDRESS)
- body.try_emplace("memoryReference", EncodeMemoryReference(addr));
- if (ValuePointsToCode(variable))
- body.try_emplace("valueLocationReference", new_var_ref);
- } else {
- EmplaceSafeString(body, "message", std::string(error.GetCString()));
- }
- response["success"] = llvm::json::Value(success);
- } else {
- response["success"] = llvm::json::Value(false);
- }
-
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "VariablesRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Variables request; value of command field is 'variables'.
-// Retrieves all child variables for the given variable reference. An
-// optional filter can be used to limit the fetched children to either named
-// or indexed children.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "variables" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/VariablesArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "VariablesArguments": {
-// "type": "object",
-// "description": "Arguments for 'variables' request.",
-// "properties": {
-// "variablesReference": {
-// "type": "integer",
-// "description": "The Variable reference."
-// },
-// "filter": {
-// "type": "string",
-// "enum": [ "indexed", "named" ],
-// "description": "Optional filter to limit the child variables to either
-// named or indexed. If ommited, both types are fetched."
-// },
-// "start": {
-// "type": "integer",
-// "description": "The index of the first variable to return; if omitted
-// children start at 0."
-// },
-// "count": {
-// "type": "integer",
-// "description": "The number of variables to return. If count is missing
-// or 0, all variables are returned."
-// },
-// "format": {
-// "$ref": "#/definitions/ValueFormat",
-// "description": "Specifies details on how to format the Variable
-// values."
-// }
-// },
-// "required": [ "variablesReference" ]
-// },
-// "VariablesResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'variables' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "variables": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Variable"
-// },
-// "description": "All (or a range) of variables for the given
-// variable reference."
-// }
-// },
-// "required": [ "variables" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-void request_variables(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Array variables;
- const auto *arguments = request.getObject("arguments");
- const auto variablesReference =
- GetUnsigned(arguments, "variablesReference", 0);
- const int64_t start = GetSigned(arguments, "start", 0);
- const int64_t count = GetSigned(arguments, "count", 0);
- bool hex = false;
- const auto *format = arguments->getObject("format");
- if (format)
- hex = GetBoolean(format, "hex", false);
-
- if (lldb::SBValueList *top_scope =
- GetTopLevelScope(dap, variablesReference)) {
- // variablesReference is one of our scopes, not an actual variable it is
- // asking for the list of args, locals or globals.
- int64_t start_idx = 0;
- int64_t num_children = 0;
-
- if (variablesReference == VARREF_REGS) {
- // Change the default format of any pointer sized registers in the first
- // register set to be the lldb::eFormatAddressInfo so we show the pointer
- // and resolve what the pointer resolves to. Only change the format if the
- // format was set to the default format or if it was hex as some registers
- // have formats set for them.
- const uint32_t addr_size = dap.target.GetProcess().GetAddressByteSize();
- lldb::SBValue reg_set = dap.variables.registers.GetValueAtIndex(0);
- const uint32_t num_regs = reg_set.GetNumChildren();
- for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) {
- lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx);
- const lldb::Format format = reg.GetFormat();
- if (format == lldb::eFormatDefault || format == lldb::eFormatHex) {
- if (reg.GetByteSize() == addr_size)
- reg.SetFormat(lldb::eFormatAddressInfo);
- }
- }
- }
-
- num_children = top_scope->GetSize();
- if (num_children == 0 && variablesReference == VARREF_LOCALS) {
- // Check for an error in the SBValueList that might explain why we don't
- // have locals. If we have an error display it as the sole value in the
- // the locals.
-
- // "error" owns the error string so we must keep it alive as long as we
- // want to use the returns "const char *"
- lldb::SBError error = top_scope->GetError();
- const char *var_err = error.GetCString();
- if (var_err) {
- // Create a fake variable named "error" to explain why variables were
- // not available. This new error will help let users know when there was
- // a problem that kept variables from being available for display and
- // allow users to fix this issue instead of seeing no variables. The
- // errors are only set when there is a problem that the user could
- // fix, so no error will show up when you have no debug info, only when
- // we do have debug info and something that is fixable can be done.
- llvm::json::Object object;
- EmplaceSafeString(object, "name", "<error>");
- EmplaceSafeString(object, "type", "const char *");
- EmplaceSafeString(object, "value", var_err);
- object.try_emplace("variablesReference", (int64_t)0);
- variables.emplace_back(std::move(object));
- }
- }
- const int64_t end_idx = start_idx + ((count == 0) ? num_children : count);
-
- // We first find out which variable names are duplicated
- std::map<std::string, int> variable_name_counts;
- for (auto i = start_idx; i < end_idx; ++i) {
- lldb::SBValue variable = top_scope->GetValueAtIndex(i);
- if (!variable.IsValid())
- break;
- variable_name_counts[GetNonNullVariableName(variable)]++;
- }
-
- // Now we construct the result with unique display variable names
- for (auto i = start_idx; i < end_idx; ++i) {
- lldb::SBValue variable = top_scope->GetValueAtIndex(i);
-
- if (!variable.IsValid())
- break;
-
- int64_t var_ref =
- dap.variables.InsertVariable(variable, /*is_permanent=*/false);
- variables.emplace_back(CreateVariable(
- variable, var_ref, hex, dap.enable_auto_variable_summaries,
- dap.enable_synthetic_child_debugging,
- variable_name_counts[GetNonNullVariableName(variable)] > 1));
- }
- } else {
- // We are expanding a variable that has children, so we will return its
- // children.
- lldb::SBValue variable = dap.variables.GetVariable(variablesReference);
- if (variable.IsValid()) {
- auto addChild = [&](lldb::SBValue child,
- std::optional<std::string> custom_name = {}) {
- if (!child.IsValid())
- return;
- bool is_permanent =
- dap.variables.IsPermanentVariableReference(variablesReference);
- int64_t var_ref = dap.variables.InsertVariable(child, is_permanent);
- variables.emplace_back(CreateVariable(
- child, var_ref, hex, dap.enable_auto_variable_summaries,
- dap.enable_synthetic_child_debugging,
- /*is_name_duplicated=*/false, custom_name));
- };
- const int64_t num_children = variable.GetNumChildren();
- int64_t end_idx = start + ((count == 0) ? num_children : count);
- int64_t i = start;
- for (; i < end_idx && i < num_children; ++i)
- addChild(variable.GetChildAtIndex(i));
-
- // If we haven't filled the count quota from the request, we insert a new
- // "[raw]" child that can be used to inspect the raw version of a
- // synthetic member. That eliminates the need for the user to go to the
- // debug console and type `frame var <variable> to get these values.
- if (dap.enable_synthetic_child_debugging && variable.IsSynthetic() &&
- i == num_children)
- addChild(variable.GetNonSyntheticValue(), "[raw]");
- }
- }
- llvm::json::Object body;
- body.try_emplace("variables", std::move(variables));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "LocationsRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Looks up information about a location reference
-// previously returned by the debug adapter.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "locations" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/LocationsArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "LocationsArguments": {
-// "type": "object",
-// "description": "Arguments for `locations` request.",
-// "properties": {
-// "locationReference": {
-// "type": "integer",
-// "description": "Location reference to resolve."
-// }
-// },
-// "required": [ "locationReference" ]
-// },
-// "LocationsResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `locations` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "The source containing the location; either
-// `source.path` or `source.sourceReference` must be
-// specified."
-// },
-// "line": {
-// "type": "integer",
-// "description": "The line number of the location. The client
-// capability `linesStartAt1` determines whether it
-// is 0- or 1-based."
-// },
-// "column": {
-// "type": "integer",
-// "description": "Position of the location within the `line`. It is
-// measured in UTF-16 code units and the client
-// capability `columnsStartAt1` determines whether
-// it is 0- or 1-based. If no column is given, the
-// first position in the start line is assumed."
-// },
-// "endLine": {
-// "type": "integer",
-// "description": "End line of the location, present if the location
-// refers to a range. The client capability
-// `linesStartAt1` determines whether it is 0- or
-// 1-based."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description": "End position of the location within `endLine`,
-// present if the location refers to a range. It is
-// measured in UTF-16 code units and the client
-// capability `columnsStartAt1` determines whether
-// it is 0- or 1-based."
-// }
-// },
-// "required": [ "source", "line" ]
-// }
-// }
-// }]
-// },
-void request_locations(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- auto *arguments = request.getObject("arguments");
-
- uint64_t location_id = GetUnsigned(arguments, "locationReference", 0);
- // We use the lowest bit to distinguish between value location and declaration
- // location
- auto [var_ref, is_value_location] = UnpackLocation(location_id);
- lldb::SBValue variable = dap.variables.GetVariable(var_ref);
- if (!variable.IsValid()) {
- response["success"] = false;
- response["message"] = "Invalid variable reference";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- llvm::json::Object body;
- if (is_value_location) {
- // Get the value location
- if (!variable.GetType().IsPointerType() &&
- !variable.GetType().IsReferenceType()) {
- response["success"] = false;
- response["message"] =
- "Value locations are only available for pointers and references";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- lldb::addr_t addr = variable.GetValueAsAddress();
- lldb::SBLineEntry line_entry =
- dap.target.ResolveLoadAddress(addr).GetLineEntry();
-
- if (!line_entry.IsValid()) {
- response["success"] = false;
- response["message"] = "Failed to resolve line entry for location";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- body.try_emplace("source", CreateSource(line_entry.GetFileSpec()));
- if (int line = line_entry.GetLine())
- body.try_emplace("line", line);
- if (int column = line_entry.GetColumn())
- body.try_emplace("column", column);
- } else {
- // Get the declaration location
- lldb::SBDeclaration decl = variable.GetDeclaration();
- if (!decl.IsValid()) {
- response["success"] = false;
- response["message"] = "No declaration location available";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- body.try_emplace("source", CreateSource(decl.GetFileSpec()));
- if (int line = decl.GetLine())
- body.try_emplace("line", line);
- if (int column = decl.GetColumn())
- body.try_emplace("column", column);
- }
-
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "DisassembleRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Disassembles code stored at the provided
-// location.\nClients should only call this request if the corresponding
-// capability `supportsDisassembleRequest` is true.", "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "disassemble" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/DisassembleArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "DisassembleArguments": {
-// "type": "object",
-// "description": "Arguments for `disassemble` request.",
-// "properties": {
-// "memoryReference": {
-// "type": "string",
-// "description": "Memory reference to the base location containing the
-// instructions to disassemble."
-// },
-// "offset": {
-// "type": "integer",
-// "description": "Offset (in bytes) to be applied to the reference
-// location before disassembling. Can be negative."
-// },
-// "instructionOffset": {
-// "type": "integer",
-// "description": "Offset (in instructions) to be applied after the byte
-// offset (if any) before disassembling. Can be negative."
-// },
-// "instructionCount": {
-// "type": "integer",
-// "description": "Number of instructions to disassemble starting at the
-// specified location and offset.\nAn adapter must return exactly this
-// number of instructions - any unavailable instructions should be
-// replaced with an implementation-defined 'invalid instruction' value."
-// },
-// "resolveSymbols": {
-// "type": "boolean",
-// "description": "If true, the adapter should attempt to resolve memory
-// addresses and other values to symbolic names."
-// }
-// },
-// "required": [ "memoryReference", "instructionCount" ]
-// },
-// "DisassembleResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `disassemble` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "instructions": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/DisassembledInstruction"
-// },
-// "description": "The list of disassembled instructions."
-// }
-// },
-// "required": [ "instructions" ]
-// }
-// }
-// }]
-// }
-void request_disassemble(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- auto *arguments = request.getObject("arguments");
-
- llvm::StringRef memoryReference = GetString(arguments, "memoryReference");
- auto addr_opt = DecodeMemoryReference(memoryReference);
- if (!addr_opt.has_value()) {
- response["success"] = false;
- response["message"] =
- "Malformed memory reference: " + memoryReference.str();
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- lldb::addr_t addr_ptr = *addr_opt;
-
- addr_ptr += GetSigned(arguments, "instructionOffset", 0);
- lldb::SBAddress addr(addr_ptr, dap.target);
- if (!addr.IsValid()) {
- response["success"] = false;
- response["message"] = "Memory reference not found in the current binary.";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- const auto inst_count = GetUnsigned(arguments, "instructionCount", 0);
- lldb::SBInstructionList insts = dap.target.ReadInstructions(addr, inst_count);
-
- if (!insts.IsValid()) {
- response["success"] = false;
- response["message"] = "Failed to find instructions for memory address.";
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
-
- const bool resolveSymbols = GetBoolean(arguments, "resolveSymbols", false);
- llvm::json::Array instructions;
- const auto num_insts = insts.GetSize();
- for (size_t i = 0; i < num_insts; ++i) {
- lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
- auto addr = inst.GetAddress();
- const auto inst_addr = addr.GetLoadAddress(dap.target);
- const char *m = inst.GetMnemonic(dap.target);
- const char *o = inst.GetOperands(dap.target);
- const char *c = inst.GetComment(dap.target);
- auto d = inst.GetData(dap.target);
-
- std::string bytes;
- llvm::raw_string_ostream sb(bytes);
- for (unsigned i = 0; i < inst.GetByteSize(); i++) {
- lldb::SBError error;
- uint8_t b = d.GetUnsignedInt8(error, i);
- if (error.Success()) {
- sb << llvm::format("%2.2x ", b);
- }
- }
-
- llvm::json::Object disassembled_inst{
- {"address", "0x" + llvm::utohexstr(inst_addr)},
- {"instructionBytes",
- bytes.size() > 0 ? bytes.substr(0, bytes.size() - 1) : ""},
- };
-
- std::string instruction;
- llvm::raw_string_ostream si(instruction);
-
- lldb::SBSymbol symbol = addr.GetSymbol();
- // Only add the symbol on the first line of the function.
- if (symbol.IsValid() && symbol.GetStartAddress() == addr) {
- // If we have a valid symbol, append it as a label prefix for the first
- // instruction. This is so you can see the start of a function/callsite
- // in the assembly, at the moment VS Code (1.80) does not visualize the
- // symbol associated with the assembly instruction.
- si << (symbol.GetMangledName() != nullptr ? symbol.GetMangledName()
- : symbol.GetName())
- << ": ";
-
- if (resolveSymbols) {
- disassembled_inst.try_emplace("symbol", symbol.GetDisplayName());
- }
- }
-
- si << llvm::formatv("{0,7} {1,12}", m, o);
- if (c && c[0]) {
- si << " ; " << c;
- }
-
- disassembled_inst.try_emplace("instruction", instruction);
-
- auto line_entry = addr.GetLineEntry();
- // If the line number is 0 then the entry represents a compiler generated
- // location.
- if (line_entry.GetStartAddress() == addr && line_entry.IsValid() &&
- line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0) {
- auto source = CreateSource(line_entry);
- disassembled_inst.try_emplace("location", source);
-
- const auto line = line_entry.GetLine();
- if (line && line != LLDB_INVALID_LINE_NUMBER) {
- disassembled_inst.try_emplace("line", line);
- }
- const auto column = line_entry.GetColumn();
- if (column && column != LLDB_INVALID_COLUMN_NUMBER) {
- disassembled_inst.try_emplace("column", column);
- }
-
- auto end_line_entry = line_entry.GetEndAddress().GetLineEntry();
- if (end_line_entry.IsValid() &&
- end_line_entry.GetFileSpec() == line_entry.GetFileSpec()) {
- const auto end_line = end_line_entry.GetLine();
- if (end_line && end_line != LLDB_INVALID_LINE_NUMBER &&
- end_line != line) {
- disassembled_inst.try_emplace("endLine", end_line);
-
- const auto end_column = end_line_entry.GetColumn();
- if (end_column && end_column != LLDB_INVALID_COLUMN_NUMBER &&
- end_column != column) {
- disassembled_inst.try_emplace("endColumn", end_column - 1);
- }
- }
- }
- }
-
- instructions.emplace_back(std::move(disassembled_inst));
- }
-
- llvm::json::Object body;
- body.try_emplace("instructions", std::move(instructions));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
-// "ReadMemoryRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Reads bytes from memory at the provided location. Clients
-// should only call this request if the corresponding
-// capability `supportsReadMemoryRequest` is true.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "readMemory" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/ReadMemoryArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "ReadMemoryArguments": {
-// "type": "object",
-// "description": "Arguments for `readMemory` request.",
-// "properties": {
-// "memoryReference": {
-// "type": "string",
-// "description": "Memory reference to the base location from which data
-// should be read."
-// },
-// "offset": {
-// "type": "integer",
-// "description": "Offset (in bytes) to be applied to the reference
-// location before reading data. Can be negative."
-// },
-// "count": {
-// "type": "integer",
-// "description": "Number of bytes to read at the specified location and
-// offset."
-// }
-// },
-// "required": [ "memoryReference", "count" ]
-// },
-// "ReadMemoryResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to `readMemory` request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "address": {
-// "type": "string",
-// "description": "The address of the first byte of data returned.
-// Treated as a hex value if prefixed with `0x`, or
-// as a decimal value otherwise."
-// },
-// "unreadableBytes": {
-// "type": "integer",
-// "description": "The number of unreadable bytes encountered after
-// the last successfully read byte.\nThis can be
-// used to determine the number of bytes that should
-// be skipped before a subsequent
-// `readMemory` request succeeds."
-// },
-// "data": {
-// "type": "string",
-// "description": "The bytes read from memory, encoded using base64.
-// If the decoded length of `data` is less than the
-// requested `count` in the original `readMemory`
-// request, and `unreadableBytes` is zero or
-// omitted, then the client should assume it's
-// reached the end of readable memory."
-// }
-// },
-// "required": [ "address" ]
-// }
-// }
-// }]
-// },
-void request_readMemory(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- auto *arguments = request.getObject("arguments");
-
- llvm::StringRef memoryReference = GetString(arguments, "memoryReference");
- auto addr_opt = DecodeMemoryReference(memoryReference);
- if (!addr_opt.has_value()) {
- response["success"] = false;
- response["message"] =
- "Malformed memory reference: " + memoryReference.str();
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- lldb::addr_t addr_int = *addr_opt;
- addr_int += GetSigned(arguments, "offset", 0);
- const uint64_t count_requested = GetUnsigned(arguments, "count", 0);
-
- // We also need support reading 0 bytes
- // VS Code sends those requests to check if a `memoryReference`
- // can be dereferenced.
- const uint64_t count_read = std::max<uint64_t>(count_requested, 1);
- std::vector<uint8_t> buf;
- buf.resize(count_read);
- lldb::SBError error;
- lldb::SBAddress addr{addr_int, dap.target};
- size_t count_result =
- dap.target.ReadMemory(addr, buf.data(), count_read, error);
- if (count_result == 0) {
- response["success"] = false;
- EmplaceSafeString(response, "message", error.GetCString());
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
- buf.resize(std::min<size_t>(count_result, count_requested));
-
- llvm::json::Object body;
- std::string formatted_addr = "0x" + llvm::utohexstr(addr_int);
- body.try_emplace("address", formatted_addr);
- body.try_emplace("data", llvm::encodeBase64(buf));
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
-}
-
void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<AttachRequestHandler>();
dap.RegisterRequest<BreakpointLocationsRequestHandler>();
@@ -1456,21 +132,31 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequest<ConfigurationDoneRequestHandler>();
dap.RegisterRequest<ContinueRequestHandler>();
dap.RegisterRequest<DataBreakpointInfoRequestHandler>();
+ dap.RegisterRequest<DisassembleRequestHandler>();
dap.RegisterRequest<DisconnectRequestHandler>();
dap.RegisterRequest<EvaluateRequestHandler>();
dap.RegisterRequest<ExceptionInfoRequestHandler>();
dap.RegisterRequest<InitializeRequestHandler>();
dap.RegisterRequest<LaunchRequestHandler>();
+ dap.RegisterRequest<LocationsRequestHandler>();
dap.RegisterRequest<NextRequestHandler>();
+ dap.RegisterRequest<PauseRequestHandler>();
+ dap.RegisterRequest<ReadMemoryRequestHandler>();
dap.RegisterRequest<RestartRequestHandler>();
+ dap.RegisterRequest<ScopesRequestHandler>();
dap.RegisterRequest<SetBreakpointsRequestHandler>();
dap.RegisterRequest<SetDataBreakpointsRequestHandler>();
dap.RegisterRequest<SetExceptionBreakpointsRequestHandler>();
dap.RegisterRequest<SetFunctionBreakpointsRequestHandler>();
dap.RegisterRequest<SetInstructionBreakpointsRequestHandler>();
+ dap.RegisterRequest<SetVariableRequestHandler>();
+ dap.RegisterRequest<SourceRequestHandler>();
+ dap.RegisterRequest<StackTraceRequestHandler>();
dap.RegisterRequest<StepInRequestHandler>();
dap.RegisterRequest<StepInTargetsRequestHandler>();
dap.RegisterRequest<StepOutRequestHandler>();
+ dap.RegisterRequest<ThreadsRequestHandler>();
+ dap.RegisterRequest<VariablesRequestHandler>();
// Custom requests
dap.RegisterRequest<CompileUnitsRequestHandler>();
@@ -1479,16 +165,9 @@ void RegisterRequestCallbacks(DAP &dap) {
// Testing requests
dap.RegisterRequest<TestGetTargetBreakpointsRequestHandler>();
- dap.RegisterRequestCallback("pause", request_pause);
- dap.RegisterRequestCallback("scopes", request_scopes);
- dap.RegisterRequestCallback("setVariable", request_setVariable);
- dap.RegisterRequestCallback("source", request_source);
- dap.RegisterRequestCallback("stackTrace", request_stackTrace);
- dap.RegisterRequestCallback("threads", request_threads);
- dap.RegisterRequestCallback("variables", request_variables);
- dap.RegisterRequestCallback("locations", request_locations);
- dap.RegisterRequestCallback("disassemble", request_disassemble);
+#if 0
dap.RegisterRequestCallback("readMemory", request_readMemory);
+#endif
}
} // anonymous namespace
More information about the lldb-commits
mailing list