aboutsummaryrefslogtreecommitdiff
path: root/lldb/tools
diff options
context:
space:
mode:
authorZequan Wu <zequanwu@google.com>2024-02-13 16:38:56 -0500
committerGitHub <noreply@github.com>2024-02-13 16:38:56 -0500
commit8c56e78ec531f0e2460213c20fff869b6b7add99 (patch)
tree00b2d9f07bc58fa97153403ec5761069d69165f3 /lldb/tools
parent99c5a66c62ae6b818fcc62a4d9c936ba9d82bdce (diff)
downloadllvm-8c56e78ec531f0e2460213c20fff869b6b7add99.zip
llvm-8c56e78ec531f0e2460213c20fff869b6b7add99.tar.gz
llvm-8c56e78ec531f0e2460213c20fff869b6b7add99.tar.bz2
[lldb-dap] Add support for data breakpoint. (#81541)
This implements functionality to handle `DataBreakpointInfo` request and `SetDataBreakpoints` request. If variablesReference is 0 or not provided, interpret name as ${number of bytes}@${expression} to set data breakpoint at the given expression because the spec https://microsoft.github.io/debug-adapter-protocol/specification#Requests_DataBreakpointInfo doesn't say how the client could specify the number of bytes to watch. This is based on top of https://github.com/llvm/llvm-project/pull/80753.
Diffstat (limited to 'lldb/tools')
-rw-r--r--lldb/tools/lldb-dap/CMakeLists.txt1
-rw-r--r--lldb/tools/lldb-dap/DAPForward.h2
-rw-r--r--lldb/tools/lldb-dap/Watchpoint.cpp48
-rw-r--r--lldb/tools/lldb-dap/Watchpoint.h34
-rw-r--r--lldb/tools/lldb-dap/lldb-dap.cpp307
5 files changed, 358 insertions, 34 deletions
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index f8c0e4e..f8f0d86 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -37,6 +37,7 @@ add_lldb_tool(lldb-dap
RunInTerminal.cpp
SourceBreakpoint.cpp
DAP.cpp
+ Watchpoint.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index fffff1e..8c79488 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -14,6 +14,7 @@ struct BreakpointBase;
struct ExceptionBreakpoint;
struct FunctionBreakpoint;
struct SourceBreakpoint;
+struct Watchpoint;
} // namespace lldb_dap
namespace lldb {
@@ -39,6 +40,7 @@ class SBStringList;
class SBTarget;
class SBThread;
class SBValue;
+class SBWatchpoint;
} // namespace lldb
#endif
diff --git a/lldb/tools/lldb-dap/Watchpoint.cpp b/lldb/tools/lldb-dap/Watchpoint.cpp
new file mode 100644
index 0000000..2f176e0
--- /dev/null
+++ b/lldb/tools/lldb-dap/Watchpoint.cpp
@@ -0,0 +1,48 @@
+//===-- Watchpoint.cpp ------------------------------------------*- C++ -*-===//
+//
+// 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 "Watchpoint.h"
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "llvm/ADT/StringExtras.h"
+
+namespace lldb_dap {
+Watchpoint::Watchpoint(const llvm::json::Object &obj) : BreakpointBase(obj) {
+ llvm::StringRef dataId = GetString(obj, "dataId");
+ std::string accessType = GetString(obj, "accessType").str();
+ auto [addr_str, size_str] = dataId.split('/');
+ lldb::addr_t addr;
+ size_t size;
+ llvm::to_integer(addr_str, addr, 16);
+ llvm::to_integer(size_str, size);
+ lldb::SBWatchpointOptions options;
+ options.SetWatchpointTypeRead(accessType != "write");
+ if (accessType != "read")
+ options.SetWatchpointTypeWrite(lldb::eWatchpointWriteTypeOnModify);
+ wp = g_dap.target.WatchpointCreateByAddress(addr, size, options, error);
+ SetCondition();
+ SetHitCondition();
+}
+
+void Watchpoint::SetCondition() { wp.SetCondition(condition.c_str()); }
+
+void Watchpoint::SetHitCondition() {
+ uint64_t hitCount = 0;
+ if (llvm::to_integer(hitCondition, hitCount))
+ wp.SetIgnoreCount(hitCount - 1);
+}
+
+void Watchpoint::CreateJsonObject(llvm::json::Object &object) {
+ if (error.Success()) {
+ object.try_emplace("verified", true);
+ } else {
+ object.try_emplace("verified", false);
+ EmplaceSafeString(object, "message", error.GetCString());
+ }
+}
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Watchpoint.h b/lldb/tools/lldb-dap/Watchpoint.h
new file mode 100644
index 0000000..026b07d
--- /dev/null
+++ b/lldb/tools/lldb-dap/Watchpoint.h
@@ -0,0 +1,34 @@
+//===-- Watchpoint.h --------------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
+#define LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
+
+#include "BreakpointBase.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBWatchpoint.h"
+#include "lldb/API/SBWatchpointOptions.h"
+
+namespace lldb_dap {
+
+struct Watchpoint : public BreakpointBase {
+ // The LLDB breakpoint associated wit this watchpoint.
+ lldb::SBWatchpoint wp;
+ lldb::SBError error;
+
+ Watchpoint() = default;
+ Watchpoint(const llvm::json::Object &obj);
+ Watchpoint(lldb::SBWatchpoint wp) : wp(wp) {}
+
+ void SetCondition() override;
+ void SetHitCondition() override;
+ void CreateJsonObject(llvm::json::Object &object) override;
+};
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 6702234..6bf2ec2 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
+#include "Watchpoint.h"
#include <cassert>
#include <climits>
@@ -560,6 +561,46 @@ void EventThreadFunction() {
}
}
+lldb::SBValue 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 = g_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;
+}
+
// Both attach and launch take a either a sourcePath or sourceMap
// argument (or neither), from which we need to set the target.source-map.
void SetSourceMapFromArguments(const llvm::json::Object &arguments) {
@@ -1647,6 +1688,8 @@ void request_initialize(const llvm::json::Object &request) {
body.try_emplace("supportsProgressReporting", true);
// The debug adapter supports 'logMessage' in breakpoint.
body.try_emplace("supportsLogPoints", true);
+ // The debug adapter supports data watchpoints.
+ body.try_emplace("supportsDataBreakpoints", true);
response.try_emplace("body", std::move(body));
g_dap.SendJSON(llvm::json::Value(std::move(response)));
@@ -2591,6 +2634,231 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) {
g_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(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 = g_dap.GetLLDBFrame(*arguments);
+ lldb::SBValue variable = FindVariable(variablesReference, name);
+ std::string addr, size;
+
+ if (variable.IsValid()) {
+ addr = llvm::utohexstr(variable.GetLoadAddress());
+ size = llvm::utostr(variable.GetByteSize());
+ } else if (variablesReference == 0 && frame.IsValid()) {
+ // Name might be an expression. In this case we assume that name is composed
+ // of the number of bytes to watch and expression, separated by '@':
+ // "${size}@${expression}"
+ llvm::StringRef expr;
+ std::tie(size, expr) = name.split('@');
+ lldb::SBValue value = frame.EvaluateExpression(expr.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
+ addr = llvm::utohexstr(value.GetValueAsUnsigned());
+ } else {
+ auto state = g_dap.target.GetProcess().GetState();
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description",
+ "variable not found: " + llvm::utostr(state));
+ }
+
+ 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));
+ g_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(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;
+ g_dap.target.DeleteAllWatchpoints();
+ if (breakpoints) {
+ for (const auto &bp : *breakpoints) {
+ const auto *bp_obj = bp.getAsObject();
+ if (bp_obj) {
+ Watchpoint wp(*bp_obj);
+ AppendBreakpoint(&wp, response_breakpoints);
+ }
+ }
+ }
+ llvm::json::Object body;
+ body.try_emplace("breakpoints", std::move(response_breakpoints));
+ response.try_emplace("body", std::move(body));
+ g_dap.SendJSON(llvm::json::Value(std::move(response)));
+}
+
// "SourceRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -3074,7 +3342,6 @@ void request_setVariable(const llvm::json::Object &request) {
const auto variablesReference =
GetUnsigned(arguments, "variablesReference", 0);
llvm::StringRef name = GetString(arguments, "name");
- bool is_duplicated_variable_name = name.contains(" @");
const auto value = GetString(arguments, "value");
// Set success to false just in case we don't find the variable by name
@@ -3095,40 +3362,8 @@ void request_setVariable(const llvm::json::Object &request) {
const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX);
if (id_value != UINT64_MAX) {
variable = g_dap.variables.GetVariable(id_value);
- } else if (lldb::SBValueList *top_scope =
- GetTopLevelScope(variablesReference)) {
- // 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 = g_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);
- }
- }
- }
+ variable = FindVariable(variablesReference, name);
}
if (variable.IsValid()) {
@@ -3611,6 +3846,10 @@ void RegisterRequestCallbacks() {
request_setExceptionBreakpoints);
g_dap.RegisterRequestCallback("setFunctionBreakpoints",
request_setFunctionBreakpoints);
+ g_dap.RegisterRequestCallback("dataBreakpointInfo",
+ request_dataBreakpointInfo);
+ g_dap.RegisterRequestCallback("setDataBreakpoints",
+ request_setDataBreakpoints);
g_dap.RegisterRequestCallback("setVariable", request_setVariable);
g_dap.RegisterRequestCallback("source", request_source);
g_dap.RegisterRequestCallback("stackTrace", request_stackTrace);