[Lldb-commits] [lldb] [llvm] [WIP][lldb-dap] Add support for data breakpoint. (PR #81541)
Zequan Wu via lldb-commits
lldb-commits at lists.llvm.org
Mon Feb 12 13:52:02 PST 2024
https://github.com/ZequanWu created https://github.com/llvm/llvm-project/pull/81541
This implements functionality to handle `DataBreakpointInfo` request and `SetDataBreakpoints` request.
It doesn't handle the case when `name` is an expression, see Todo comment for details.
This is based on top of https://github.com/llvm/llvm-project/pull/80753.
>From 40fca279bc2939870f5491e36b03742b567a8ab0 Mon Sep 17 00:00:00 2001
From: Zequan Wu <zequanwu at google.com>
Date: Mon, 12 Feb 2024 16:36:08 -0500
Subject: [PATCH 1/2] [lldb-dap][NFC] Add Breakpoint struct to share common
logic.
---
lldb/tools/lldb-dap/Breakpoint.cpp | 76 +++++
lldb/tools/lldb-dap/Breakpoint.h | 33 ++
lldb/tools/lldb-dap/BreakpointBase.cpp | 299 +----------------
lldb/tools/lldb-dap/BreakpointBase.h | 33 +-
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
lldb/tools/lldb-dap/FunctionBreakpoint.cpp | 12 +-
lldb/tools/lldb-dap/FunctionBreakpoint.h | 4 +-
lldb/tools/lldb-dap/JSONUtils.cpp | 46 +--
lldb/tools/lldb-dap/JSONUtils.h | 5 +-
lldb/tools/lldb-dap/SourceBreakpoint.cpp | 304 +++++++++++++++++-
lldb/tools/lldb-dap/SourceBreakpoint.h | 30 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 17 +-
.../gn/secondary/lldb/tools/lldb-dap/BUILD.gn | 1 +
13 files changed, 459 insertions(+), 402 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Breakpoint.cpp
create mode 100644 lldb/tools/lldb-dap/Breakpoint.h
diff --git a/lldb/tools/lldb-dap/Breakpoint.cpp b/lldb/tools/lldb-dap/Breakpoint.cpp
new file mode 100644
index 00000000000000..0c33d4b114d760
--- /dev/null
+++ b/lldb/tools/lldb-dap/Breakpoint.cpp
@@ -0,0 +1,76 @@
+//===-- Breakpoint.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 "Breakpoint.h"
+#include "DAP.h"
+#include "JSONUtils.h"
+#include "llvm/ADT/StringExtras.h"
+
+using namespace lldb_dap;
+
+void Breakpoint::SetCondition() { bp.SetCondition(condition.c_str()); }
+
+void Breakpoint::SetHitCondition() {
+ uint64_t hitCount = 0;
+ if (llvm::to_integer(hitCondition, hitCount))
+ bp.SetIgnoreCount(hitCount - 1);
+}
+
+void Breakpoint::CreateJsonObject(llvm::json::Object &object) {
+ // Each breakpoint location is treated as a separate breakpoint for VS code.
+ // They don't have the notion of a single breakpoint with multiple locations.
+ if (!bp.IsValid())
+ return;
+ object.try_emplace("verified", bp.GetNumResolvedLocations() > 0);
+ object.try_emplace("id", bp.GetID());
+ // VS Code DAP doesn't currently allow one breakpoint to have multiple
+ // locations so we just report the first one. If we report all locations
+ // then the IDE starts showing the wrong line numbers and locations for
+ // other source file and line breakpoints in the same file.
+
+ // Below we search for the first resolved location in a breakpoint and report
+ // this as the breakpoint location since it will have a complete location
+ // that is at least loaded in the current process.
+ lldb::SBBreakpointLocation bp_loc;
+ const auto num_locs = bp.GetNumLocations();
+ for (size_t i = 0; i < num_locs; ++i) {
+ bp_loc = bp.GetLocationAtIndex(i);
+ if (bp_loc.IsResolved())
+ break;
+ }
+ // If not locations are resolved, use the first location.
+ if (!bp_loc.IsResolved())
+ bp_loc = bp.GetLocationAtIndex(0);
+ auto bp_addr = bp_loc.GetAddress();
+
+ if (bp_addr.IsValid()) {
+ std::string formatted_addr =
+ "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_dap.target));
+ object.try_emplace("instructionReference", formatted_addr);
+ auto line_entry = bp_addr.GetLineEntry();
+ const auto line = line_entry.GetLine();
+ if (line != UINT32_MAX)
+ object.try_emplace("line", line);
+ const auto column = line_entry.GetColumn();
+ if (column != 0)
+ object.try_emplace("column", column);
+ object.try_emplace("source", CreateSource(line_entry));
+ }
+}
+
+bool Breakpoint::MatchesName(const char *name) { return bp.MatchesName(name); }
+
+void Breakpoint::SetBreakpoint() {
+ // See comments in BreakpointBase::GetBreakpointLabel() for details of why
+ // we add a label to our breakpoints.
+ bp.AddName(GetBreakpointLabel());
+ if (!condition.empty())
+ SetCondition();
+ if (!hitCondition.empty())
+ SetHitCondition();
+}
diff --git a/lldb/tools/lldb-dap/Breakpoint.h b/lldb/tools/lldb-dap/Breakpoint.h
new file mode 100644
index 00000000000000..47a9d9c59ae2b7
--- /dev/null
+++ b/lldb/tools/lldb-dap/Breakpoint.h
@@ -0,0 +1,33 @@
+//===-- Breakpoint.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_BREAKPOINT_H
+#define LLDB_TOOLS_LLDB_DAP_BREAKPOINT_H
+
+#include "BreakpointBase.h"
+
+namespace lldb_dap {
+
+struct Breakpoint : public BreakpointBase {
+ // The LLDB breakpoint associated wit this source breakpoint
+ lldb::SBBreakpoint bp;
+
+ Breakpoint() = default;
+ Breakpoint(const llvm::json::Object &obj) : BreakpointBase(obj){};
+ Breakpoint(lldb::SBBreakpoint bp) : bp(bp) {}
+
+ void SetCondition() override;
+ void SetHitCondition() override;
+ void CreateJsonObject(llvm::json::Object &object) override;
+
+ bool MatchesName(const char *name);
+ void SetBreakpoint();
+};
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/BreakpointBase.cpp b/lldb/tools/lldb-dap/BreakpointBase.cpp
index fb4b27fbe315fc..519729f5519ffc 100644
--- a/lldb/tools/lldb-dap/BreakpointBase.cpp
+++ b/lldb/tools/lldb-dap/BreakpointBase.cpp
@@ -8,306 +8,13 @@
#include "BreakpointBase.h"
#include "DAP.h"
-#include "JSONUtils.h"
#include "llvm/ADT/StringExtras.h"
using namespace lldb_dap;
BreakpointBase::BreakpointBase(const llvm::json::Object &obj)
: condition(std::string(GetString(obj, "condition"))),
- hitCondition(std::string(GetString(obj, "hitCondition"))),
- logMessage(std::string(GetString(obj, "logMessage"))) {}
-
-void BreakpointBase::SetCondition() { bp.SetCondition(condition.c_str()); }
-
-void BreakpointBase::SetHitCondition() {
- uint64_t hitCount = 0;
- if (llvm::to_integer(hitCondition, hitCount))
- bp.SetIgnoreCount(hitCount - 1);
-}
-
-lldb::SBError BreakpointBase::AppendLogMessagePart(llvm::StringRef part,
- bool is_expr) {
- if (is_expr) {
- logMessageParts.emplace_back(part, is_expr);
- } else {
- std::string formatted;
- lldb::SBError error = FormatLogText(part, formatted);
- if (error.Fail())
- return error;
- logMessageParts.emplace_back(formatted, is_expr);
- }
- return lldb::SBError();
-}
-
-// TODO: consolidate this code with the implementation in
-// FormatEntity::ParseInternal().
-lldb::SBError BreakpointBase::FormatLogText(llvm::StringRef text,
- std::string &formatted) {
- lldb::SBError error;
- while (!text.empty()) {
- size_t backslash_pos = text.find_first_of('\\');
- if (backslash_pos == std::string::npos) {
- formatted += text.str();
- return error;
- }
-
- formatted += text.substr(0, backslash_pos).str();
- // Skip the characters before and including '\'.
- text = text.drop_front(backslash_pos + 1);
-
- if (text.empty()) {
- error.SetErrorString(
- "'\\' character was not followed by another character");
- return error;
- }
-
- const char desens_char = text[0];
- text = text.drop_front(); // Skip the desensitized char character
- switch (desens_char) {
- case 'a':
- formatted.push_back('\a');
- break;
- case 'b':
- formatted.push_back('\b');
- break;
- case 'f':
- formatted.push_back('\f');
- break;
- case 'n':
- formatted.push_back('\n');
- break;
- case 'r':
- formatted.push_back('\r');
- break;
- case 't':
- formatted.push_back('\t');
- break;
- case 'v':
- formatted.push_back('\v');
- break;
- case '\'':
- formatted.push_back('\'');
- break;
- case '\\':
- formatted.push_back('\\');
- break;
- case '0':
- // 1 to 3 octal chars
- {
- if (text.empty()) {
- error.SetErrorString("missing octal number following '\\0'");
- return error;
- }
-
- // Make a string that can hold onto the initial zero char, up to 3
- // octal digits, and a terminating NULL.
- char oct_str[5] = {0, 0, 0, 0, 0};
-
- size_t i;
- for (i = 0;
- i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7');
- ++i) {
- oct_str[i] = text[i];
- }
-
- text = text.drop_front(i);
- unsigned long octal_value = ::strtoul(oct_str, nullptr, 8);
- if (octal_value <= UINT8_MAX) {
- formatted.push_back((char)octal_value);
- } else {
- error.SetErrorString("octal number is larger than a single byte");
- return error;
- }
- }
- break;
-
- case 'x': {
- if (text.empty()) {
- error.SetErrorString("missing hex number following '\\x'");
- return error;
- }
- // hex number in the text
- if (isxdigit(text[0])) {
- // Make a string that can hold onto two hex chars plus a
- // NULL terminator
- char hex_str[3] = {0, 0, 0};
- hex_str[0] = text[0];
-
- text = text.drop_front();
-
- if (!text.empty() && isxdigit(text[0])) {
- hex_str[1] = text[0];
- text = text.drop_front();
- }
-
- unsigned long hex_value = strtoul(hex_str, nullptr, 16);
- if (hex_value <= UINT8_MAX) {
- formatted.push_back((char)hex_value);
- } else {
- error.SetErrorString("hex number is larger than a single byte");
- return error;
- }
- } else {
- formatted.push_back(desens_char);
- }
- break;
- }
-
- default:
- // Just desensitize any other character by just printing what came
- // after the '\'
- formatted.push_back(desens_char);
- break;
- }
- }
- return error;
-}
-
-// logMessage will be divided into array of LogMessagePart as two kinds:
-// 1. raw print text message, and
-// 2. interpolated expression for evaluation which is inside matching curly
-// braces.
-//
-// The function tries to parse logMessage into a list of LogMessageParts
-// for easy later access in BreakpointHitCallback.
-void BreakpointBase::SetLogMessage() {
- logMessageParts.clear();
-
- // Contains unmatched open curly braces indices.
- std::vector<int> unmatched_curly_braces;
-
- // Contains all matched curly braces in logMessage.
- // Loop invariant: matched_curly_braces_ranges are sorted by start index in
- // ascending order without any overlap between them.
- std::vector<std::pair<int, int>> matched_curly_braces_ranges;
-
- lldb::SBError error;
- // Part1 - parse matched_curly_braces_ranges.
- // locating all curly braced expression ranges in logMessage.
- // The algorithm takes care of nested and imbalanced curly braces.
- for (size_t i = 0; i < logMessage.size(); ++i) {
- if (logMessage[i] == '{') {
- unmatched_curly_braces.push_back(i);
- } else if (logMessage[i] == '}') {
- if (unmatched_curly_braces.empty())
- // Nothing to match.
- continue;
-
- int last_unmatched_index = unmatched_curly_braces.back();
- unmatched_curly_braces.pop_back();
-
- // Erase any matched ranges included in the new match.
- while (!matched_curly_braces_ranges.empty()) {
- assert(matched_curly_braces_ranges.back().first !=
- last_unmatched_index &&
- "How can a curley brace be matched twice?");
- if (matched_curly_braces_ranges.back().first < last_unmatched_index)
- break;
-
- // This is a nested range let's earse it.
- assert((size_t)matched_curly_braces_ranges.back().second < i);
- matched_curly_braces_ranges.pop_back();
- }
-
- // Assert invariant.
- assert(matched_curly_braces_ranges.empty() ||
- matched_curly_braces_ranges.back().first < last_unmatched_index);
- matched_curly_braces_ranges.emplace_back(last_unmatched_index, i);
- }
- }
-
- // Part2 - parse raw text and expresions parts.
- // All expression ranges have been parsed in matched_curly_braces_ranges.
- // The code below uses matched_curly_braces_ranges to divide logMessage
- // into raw text parts and expression parts.
- int last_raw_text_start = 0;
- for (const std::pair<int, int> &curly_braces_range :
- matched_curly_braces_ranges) {
- // Raw text before open curly brace.
- assert(curly_braces_range.first >= last_raw_text_start);
- size_t raw_text_len = curly_braces_range.first - last_raw_text_start;
- if (raw_text_len > 0) {
- error = AppendLogMessagePart(
- llvm::StringRef(logMessage.c_str() + last_raw_text_start,
- raw_text_len),
- /*is_expr=*/false);
- if (error.Fail()) {
- NotifyLogMessageError(error.GetCString());
- return;
- }
- }
-
- // Expression between curly braces.
- assert(curly_braces_range.second > curly_braces_range.first);
- size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1;
- error = AppendLogMessagePart(
- llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1,
- expr_len),
- /*is_expr=*/true);
- if (error.Fail()) {
- NotifyLogMessageError(error.GetCString());
- return;
- }
-
- last_raw_text_start = curly_braces_range.second + 1;
- }
- // Trailing raw text after close curly brace.
- assert(last_raw_text_start >= 0);
- if (logMessage.size() > (size_t)last_raw_text_start) {
- error = AppendLogMessagePart(
- llvm::StringRef(logMessage.c_str() + last_raw_text_start,
- logMessage.size() - last_raw_text_start),
- /*is_expr=*/false);
- if (error.Fail()) {
- NotifyLogMessageError(error.GetCString());
- return;
- }
- }
-
- bp.SetCallback(BreakpointBase::BreakpointHitCallback, this);
-}
-
-void BreakpointBase::NotifyLogMessageError(llvm::StringRef error) {
- std::string message = "Log message has error: ";
- message += error;
- g_dap.SendOutput(OutputType::Console, message);
-}
-
-/*static*/
-bool BreakpointBase::BreakpointHitCallback(
- void *baton, lldb::SBProcess &process, lldb::SBThread &thread,
- lldb::SBBreakpointLocation &location) {
- if (!baton)
- return true;
-
- BreakpointBase *bp = (BreakpointBase *)baton;
- lldb::SBFrame frame = thread.GetSelectedFrame();
-
- std::string output;
- for (const BreakpointBase::LogMessagePart &messagePart :
- bp->logMessageParts) {
- if (messagePart.is_expr) {
- // Try local frame variables first before fall back to expression
- // evaluation
- const std::string &expr_str = messagePart.text;
- const char *expr = expr_str.c_str();
- lldb::SBValue value =
- frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);
- if (value.GetError().Fail())
- value = frame.EvaluateExpression(expr);
- output += VariableDescription(value).display_value;
- } else {
- output += messagePart.text;
- }
- }
- if (!output.empty() && output.back() != '\n')
- output.push_back('\n'); // Ensure log message has line break.
- g_dap.SendOutput(OutputType::Console, output.c_str());
-
- // Do not stop.
- return false;
-}
+ hitCondition(std::string(GetString(obj, "hitCondition"))) {}
void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) {
if (condition != request_bp.condition) {
@@ -318,10 +25,6 @@ void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) {
hitCondition = request_bp.hitCondition;
SetHitCondition();
}
- if (logMessage != request_bp.logMessage) {
- logMessage = request_bp.logMessage;
- SetLogMessage();
- }
}
const char *BreakpointBase::GetBreakpointLabel() {
diff --git a/lldb/tools/lldb-dap/BreakpointBase.h b/lldb/tools/lldb-dap/BreakpointBase.h
index 41787f78610215..5a04bb201615fc 100644
--- a/lldb/tools/lldb-dap/BreakpointBase.h
+++ b/lldb/tools/lldb-dap/BreakpointBase.h
@@ -9,7 +9,6 @@
#ifndef LLDB_TOOLS_LLDB_DAP_BREAKPOINTBASE_H
#define LLDB_TOOLS_LLDB_DAP_BREAKPOINTBASE_H
-#include "JSONUtils.h"
#include "lldb/API/SBBreakpoint.h"
#include "llvm/Support/JSON.h"
#include <string>
@@ -18,44 +17,24 @@
namespace lldb_dap {
struct BreakpointBase {
- // logMessage part can be either a raw text or an expression.
- struct LogMessagePart {
- LogMessagePart(llvm::StringRef text, bool is_expr)
- : text(text), is_expr(is_expr) {}
- std::string text;
- bool is_expr;
- };
+
// An optional expression for conditional breakpoints.
std::string condition;
// An optional expression that controls how many hits of the breakpoint are
// ignored. The backend is expected to interpret the expression as needed
std::string hitCondition;
- // If this attribute exists and is non-empty, the backend must not 'break'
- // (stop) but log the message instead. Expressions within {} are
- // interpolated.
- std::string logMessage;
- std::vector<LogMessagePart> logMessageParts;
- // The LLDB breakpoint associated wit this source breakpoint
- lldb::SBBreakpoint bp;
BreakpointBase() = default;
BreakpointBase(const llvm::json::Object &obj);
+ virtual ~BreakpointBase() = default;
- void SetCondition();
- void SetHitCondition();
- void SetLogMessage();
- void UpdateBreakpoint(const BreakpointBase &request_bp);
+ virtual void SetCondition() = 0;
+ virtual void SetHitCondition() = 0;
+ virtual void CreateJsonObject(llvm::json::Object &object) = 0;
- // Format \param text and return formatted text in \param formatted.
- // \return any formatting failures.
- lldb::SBError FormatLogText(llvm::StringRef text, std::string &formatted);
- lldb::SBError AppendLogMessagePart(llvm::StringRef part, bool is_expr);
- void NotifyLogMessageError(llvm::StringRef error);
+ void UpdateBreakpoint(const BreakpointBase &request_bp);
static const char *GetBreakpointLabel();
- static bool BreakpointHitCallback(void *baton, lldb::SBProcess &process,
- lldb::SBThread &thread,
- lldb::SBBreakpointLocation &location);
};
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 554567eb3b0e23..f8c0e4ecf36c2f 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -24,6 +24,7 @@ tablegen(LLVM Options.inc -gen-opt-parser-defs)
add_public_tablegen_target(LLDBDAPOptionsTableGen)
add_lldb_tool(lldb-dap
lldb-dap.cpp
+ Breakpoint.cpp
BreakpointBase.cpp
ExceptionBreakpoint.cpp
FifoFiles.cpp
diff --git a/lldb/tools/lldb-dap/FunctionBreakpoint.cpp b/lldb/tools/lldb-dap/FunctionBreakpoint.cpp
index d4bdb976500ecd..21743bf908706d 100644
--- a/lldb/tools/lldb-dap/FunctionBreakpoint.cpp
+++ b/lldb/tools/lldb-dap/FunctionBreakpoint.cpp
@@ -12,21 +12,13 @@
namespace lldb_dap {
FunctionBreakpoint::FunctionBreakpoint(const llvm::json::Object &obj)
- : BreakpointBase(obj), functionName(std::string(GetString(obj, "name"))) {}
+ : Breakpoint(obj), functionName(std::string(GetString(obj, "name"))) {}
void FunctionBreakpoint::SetBreakpoint() {
if (functionName.empty())
return;
bp = g_dap.target.BreakpointCreateByName(functionName.c_str());
- // See comments in BreakpointBase::GetBreakpointLabel() for details of why
- // we add a label to our breakpoints.
- bp.AddName(GetBreakpointLabel());
- if (!condition.empty())
- SetCondition();
- if (!hitCondition.empty())
- SetHitCondition();
- if (!logMessage.empty())
- SetLogMessage();
+ Breakpoint::SetBreakpoint();
}
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/FunctionBreakpoint.h b/lldb/tools/lldb-dap/FunctionBreakpoint.h
index fc23e94e128763..b15ff1931a6b22 100644
--- a/lldb/tools/lldb-dap/FunctionBreakpoint.h
+++ b/lldb/tools/lldb-dap/FunctionBreakpoint.h
@@ -9,11 +9,11 @@
#ifndef LLDB_TOOLS_LLDB_DAP_FUNCTIONBREAKPOINT_H
#define LLDB_TOOLS_LLDB_DAP_FUNCTIONBREAKPOINT_H
-#include "BreakpointBase.h"
+#include "Breakpoint.h"
namespace lldb_dap {
-struct FunctionBreakpoint : public BreakpointBase {
+struct FunctionBreakpoint : public Breakpoint {
std::string functionName;
FunctionBreakpoint() = default;
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index a8b438d9d6df39..878449a91aa66a 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -364,54 +364,14 @@ llvm::json::Value CreateScope(const llvm::StringRef name,
// },
// "required": [ "verified" ]
// }
-llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp,
+llvm::json::Value CreateBreakpoint(BreakpointBase *bp,
std::optional<llvm::StringRef> request_path,
std::optional<uint32_t> request_line,
std::optional<uint32_t> request_column) {
- // Each breakpoint location is treated as a separate breakpoint for VS code.
- // They don't have the notion of a single breakpoint with multiple locations.
llvm::json::Object object;
- if (!bp.IsValid())
- return llvm::json::Value(std::move(object));
-
- object.try_emplace("verified", bp.GetNumResolvedLocations() > 0);
- object.try_emplace("id", bp.GetID());
- // VS Code DAP doesn't currently allow one breakpoint to have multiple
- // locations so we just report the first one. If we report all locations
- // then the IDE starts showing the wrong line numbers and locations for
- // other source file and line breakpoints in the same file.
-
- // Below we search for the first resolved location in a breakpoint and report
- // this as the breakpoint location since it will have a complete location
- // that is at least loaded in the current process.
- lldb::SBBreakpointLocation bp_loc;
- const auto num_locs = bp.GetNumLocations();
- for (size_t i = 0; i < num_locs; ++i) {
- bp_loc = bp.GetLocationAtIndex(i);
- if (bp_loc.IsResolved())
- break;
- }
- // If not locations are resolved, use the first location.
- if (!bp_loc.IsResolved())
- bp_loc = bp.GetLocationAtIndex(0);
- auto bp_addr = bp_loc.GetAddress();
-
if (request_path)
object.try_emplace("source", CreateSource(*request_path));
-
- if (bp_addr.IsValid()) {
- std::string formatted_addr =
- "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_dap.target));
- object.try_emplace("instructionReference", formatted_addr);
- auto line_entry = bp_addr.GetLineEntry();
- const auto line = line_entry.GetLine();
- if (line != UINT32_MAX)
- object.try_emplace("line", line);
- const auto column = line_entry.GetColumn();
- if (column != 0)
- object.try_emplace("column", column);
- object.try_emplace("source", CreateSource(line_entry));
- }
+ bp->CreateJsonObject(object);
// We try to add request_line as a fallback
if (request_line)
object.try_emplace("line", *request_line);
@@ -506,7 +466,7 @@ llvm::json::Value CreateModule(lldb::SBModule &module) {
return llvm::json::Value(std::move(object));
}
-void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints,
+void AppendBreakpoint(BreakpointBase *bp, llvm::json::Array &breakpoints,
std::optional<llvm::StringRef> request_path,
std::optional<uint32_t> request_line) {
breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line));
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index 62338548890c0c..1515f5ba2e5f4d 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -9,6 +9,7 @@
#ifndef LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
#define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
+#include "BreakpointBase.h"
#include "DAPForward.h"
#include "lldb/API/SBModule.h"
#include "llvm/ADT/StringRef.h"
@@ -191,7 +192,7 @@ void FillResponse(const llvm::json::Object &request,
/// provided by the setBreakpoints request are returned to the IDE as a
/// fallback.
void AppendBreakpoint(
- lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints,
+ BreakpointBase *bp, llvm::json::Array &breakpoints,
std::optional<llvm::StringRef> request_path = std::nullopt,
std::optional<uint32_t> request_line = std::nullopt);
@@ -223,7 +224,7 @@ void AppendBreakpoint(
/// A "Breakpoint" JSON object with that follows the formal JSON
/// definition outlined by Microsoft.
llvm::json::Value
-CreateBreakpoint(lldb::SBBreakpoint &bp,
+CreateBreakpoint(BreakpointBase *bp,
std::optional<llvm::StringRef> request_path = std::nullopt,
std::optional<uint32_t> request_line = std::nullopt,
std::optional<uint32_t> request_column = std::nullopt);
diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp
index 3bd83c0a6874de..f5dd1346cb9e54 100644
--- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp
+++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp
@@ -12,22 +12,308 @@
namespace lldb_dap {
SourceBreakpoint::SourceBreakpoint(const llvm::json::Object &obj)
- : BreakpointBase(obj), line(GetUnsigned(obj, "line", 0)),
- column(GetUnsigned(obj, "column", 0)) {}
+ : Breakpoint(obj), logMessage(std::string(GetString(obj, "logMessage"))),
+ line(GetUnsigned(obj, "line", 0)), column(GetUnsigned(obj, "column", 0)) {
+}
void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) {
lldb::SBFileSpecList module_list;
bp = g_dap.target.BreakpointCreateByLocation(source_path.str().c_str(), line,
column, 0, module_list);
- // See comments in BreakpointBase::GetBreakpointLabel() for details of why
- // we add a label to our breakpoints.
- bp.AddName(GetBreakpointLabel());
- if (!condition.empty())
- SetCondition();
- if (!hitCondition.empty())
- SetHitCondition();
if (!logMessage.empty())
SetLogMessage();
+ Breakpoint::SetBreakpoint();
+}
+
+void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) {
+ if (logMessage != request_bp.logMessage) {
+ logMessage = request_bp.logMessage;
+ SetLogMessage();
+ }
+ BreakpointBase::UpdateBreakpoint(request_bp);
+}
+
+lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part,
+ bool is_expr) {
+ if (is_expr) {
+ logMessageParts.emplace_back(part, is_expr);
+ } else {
+ std::string formatted;
+ lldb::SBError error = FormatLogText(part, formatted);
+ if (error.Fail())
+ return error;
+ logMessageParts.emplace_back(formatted, is_expr);
+ }
+ return lldb::SBError();
+}
+
+// TODO: consolidate this code with the implementation in
+// FormatEntity::ParseInternal().
+lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text,
+ std::string &formatted) {
+ lldb::SBError error;
+ while (!text.empty()) {
+ size_t backslash_pos = text.find_first_of('\\');
+ if (backslash_pos == std::string::npos) {
+ formatted += text.str();
+ return error;
+ }
+
+ formatted += text.substr(0, backslash_pos).str();
+ // Skip the characters before and including '\'.
+ text = text.drop_front(backslash_pos + 1);
+
+ if (text.empty()) {
+ error.SetErrorString(
+ "'\\' character was not followed by another character");
+ return error;
+ }
+
+ const char desens_char = text[0];
+ text = text.drop_front(); // Skip the desensitized char character
+ switch (desens_char) {
+ case 'a':
+ formatted.push_back('\a');
+ break;
+ case 'b':
+ formatted.push_back('\b');
+ break;
+ case 'f':
+ formatted.push_back('\f');
+ break;
+ case 'n':
+ formatted.push_back('\n');
+ break;
+ case 'r':
+ formatted.push_back('\r');
+ break;
+ case 't':
+ formatted.push_back('\t');
+ break;
+ case 'v':
+ formatted.push_back('\v');
+ break;
+ case '\'':
+ formatted.push_back('\'');
+ break;
+ case '\\':
+ formatted.push_back('\\');
+ break;
+ case '0':
+ // 1 to 3 octal chars
+ {
+ if (text.empty()) {
+ error.SetErrorString("missing octal number following '\\0'");
+ return error;
+ }
+
+ // Make a string that can hold onto the initial zero char, up to 3
+ // octal digits, and a terminating NULL.
+ char oct_str[5] = {0, 0, 0, 0, 0};
+
+ size_t i;
+ for (i = 0;
+ i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7');
+ ++i) {
+ oct_str[i] = text[i];
+ }
+
+ text = text.drop_front(i);
+ unsigned long octal_value = ::strtoul(oct_str, nullptr, 8);
+ if (octal_value <= UINT8_MAX) {
+ formatted.push_back((char)octal_value);
+ } else {
+ error.SetErrorString("octal number is larger than a single byte");
+ return error;
+ }
+ }
+ break;
+
+ case 'x': {
+ if (text.empty()) {
+ error.SetErrorString("missing hex number following '\\x'");
+ return error;
+ }
+ // hex number in the text
+ if (isxdigit(text[0])) {
+ // Make a string that can hold onto two hex chars plus a
+ // NULL terminator
+ char hex_str[3] = {0, 0, 0};
+ hex_str[0] = text[0];
+
+ text = text.drop_front();
+
+ if (!text.empty() && isxdigit(text[0])) {
+ hex_str[1] = text[0];
+ text = text.drop_front();
+ }
+
+ unsigned long hex_value = strtoul(hex_str, nullptr, 16);
+ if (hex_value <= UINT8_MAX) {
+ formatted.push_back((char)hex_value);
+ } else {
+ error.SetErrorString("hex number is larger than a single byte");
+ return error;
+ }
+ } else {
+ formatted.push_back(desens_char);
+ }
+ break;
+ }
+
+ default:
+ // Just desensitize any other character by just printing what came
+ // after the '\'
+ formatted.push_back(desens_char);
+ break;
+ }
+ }
+ return error;
+}
+
+// logMessage will be divided into array of LogMessagePart as two kinds:
+// 1. raw print text message, and
+// 2. interpolated expression for evaluation which is inside matching curly
+// braces.
+//
+// The function tries to parse logMessage into a list of LogMessageParts
+// for easy later access in BreakpointHitCallback.
+void SourceBreakpoint::SetLogMessage() {
+ logMessageParts.clear();
+
+ // Contains unmatched open curly braces indices.
+ std::vector<int> unmatched_curly_braces;
+
+ // Contains all matched curly braces in logMessage.
+ // Loop invariant: matched_curly_braces_ranges are sorted by start index in
+ // ascending order without any overlap between them.
+ std::vector<std::pair<int, int>> matched_curly_braces_ranges;
+
+ lldb::SBError error;
+ // Part1 - parse matched_curly_braces_ranges.
+ // locating all curly braced expression ranges in logMessage.
+ // The algorithm takes care of nested and imbalanced curly braces.
+ for (size_t i = 0; i < logMessage.size(); ++i) {
+ if (logMessage[i] == '{') {
+ unmatched_curly_braces.push_back(i);
+ } else if (logMessage[i] == '}') {
+ if (unmatched_curly_braces.empty())
+ // Nothing to match.
+ continue;
+
+ int last_unmatched_index = unmatched_curly_braces.back();
+ unmatched_curly_braces.pop_back();
+
+ // Erase any matched ranges included in the new match.
+ while (!matched_curly_braces_ranges.empty()) {
+ assert(matched_curly_braces_ranges.back().first !=
+ last_unmatched_index &&
+ "How can a curley brace be matched twice?");
+ if (matched_curly_braces_ranges.back().first < last_unmatched_index)
+ break;
+
+ // This is a nested range let's earse it.
+ assert((size_t)matched_curly_braces_ranges.back().second < i);
+ matched_curly_braces_ranges.pop_back();
+ }
+
+ // Assert invariant.
+ assert(matched_curly_braces_ranges.empty() ||
+ matched_curly_braces_ranges.back().first < last_unmatched_index);
+ matched_curly_braces_ranges.emplace_back(last_unmatched_index, i);
+ }
+ }
+
+ // Part2 - parse raw text and expresions parts.
+ // All expression ranges have been parsed in matched_curly_braces_ranges.
+ // The code below uses matched_curly_braces_ranges to divide logMessage
+ // into raw text parts and expression parts.
+ int last_raw_text_start = 0;
+ for (const std::pair<int, int> &curly_braces_range :
+ matched_curly_braces_ranges) {
+ // Raw text before open curly brace.
+ assert(curly_braces_range.first >= last_raw_text_start);
+ size_t raw_text_len = curly_braces_range.first - last_raw_text_start;
+ if (raw_text_len > 0) {
+ error = AppendLogMessagePart(
+ llvm::StringRef(logMessage.c_str() + last_raw_text_start,
+ raw_text_len),
+ /*is_expr=*/false);
+ if (error.Fail()) {
+ NotifyLogMessageError(error.GetCString());
+ return;
+ }
+ }
+
+ // Expression between curly braces.
+ assert(curly_braces_range.second > curly_braces_range.first);
+ size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1;
+ error = AppendLogMessagePart(
+ llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1,
+ expr_len),
+ /*is_expr=*/true);
+ if (error.Fail()) {
+ NotifyLogMessageError(error.GetCString());
+ return;
+ }
+
+ last_raw_text_start = curly_braces_range.second + 1;
+ }
+ // Trailing raw text after close curly brace.
+ assert(last_raw_text_start >= 0);
+ if (logMessage.size() > (size_t)last_raw_text_start) {
+ error = AppendLogMessagePart(
+ llvm::StringRef(logMessage.c_str() + last_raw_text_start,
+ logMessage.size() - last_raw_text_start),
+ /*is_expr=*/false);
+ if (error.Fail()) {
+ NotifyLogMessageError(error.GetCString());
+ return;
+ }
+ }
+
+ bp.SetCallback(BreakpointHitCallback, this);
+}
+
+void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) {
+ std::string message = "Log message has error: ";
+ message += error;
+ g_dap.SendOutput(OutputType::Console, message);
+}
+
+/*static*/
+bool SourceBreakpoint::BreakpointHitCallback(
+ void *baton, lldb::SBProcess &process, lldb::SBThread &thread,
+ lldb::SBBreakpointLocation &location) {
+ if (!baton)
+ return true;
+
+ SourceBreakpoint *bp = (SourceBreakpoint *)baton;
+ lldb::SBFrame frame = thread.GetSelectedFrame();
+
+ std::string output;
+ for (const SourceBreakpoint::LogMessagePart &messagePart :
+ bp->logMessageParts) {
+ if (messagePart.is_expr) {
+ // Try local frame variables first before fall back to expression
+ // evaluation
+ const std::string &expr_str = messagePart.text;
+ const char *expr = expr_str.c_str();
+ lldb::SBValue value =
+ frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);
+ if (value.GetError().Fail())
+ value = frame.EvaluateExpression(expr);
+ output += VariableDescription(value).display_value;
+ } else {
+ output += messagePart.text;
+ }
+ }
+ if (!output.empty() && output.back() != '\n')
+ output.push_back('\n'); // Ensure log message has line break.
+ g_dap.SendOutput(OutputType::Console, output.c_str());
+
+ // Do not stop.
+ return false;
}
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.h b/lldb/tools/lldb-dap/SourceBreakpoint.h
index f4b54a44fc6875..aa3fbe6d0f96d2 100644
--- a/lldb/tools/lldb-dap/SourceBreakpoint.h
+++ b/lldb/tools/lldb-dap/SourceBreakpoint.h
@@ -9,21 +9,45 @@
#ifndef LLDB_TOOLS_LLDB_DAP_SOURCEBREAKPOINT_H
#define LLDB_TOOLS_LLDB_DAP_SOURCEBREAKPOINT_H
-#include "BreakpointBase.h"
+#include "Breakpoint.h"
#include "llvm/ADT/StringRef.h"
namespace lldb_dap {
-struct SourceBreakpoint : public BreakpointBase {
+struct SourceBreakpoint : public Breakpoint {
+ // logMessage part can be either a raw text or an expression.
+ struct LogMessagePart {
+ LogMessagePart(llvm::StringRef text, bool is_expr)
+ : text(text), is_expr(is_expr) {}
+ std::string text;
+ bool is_expr;
+ };
+ // If this attribute exists and is non-empty, the backend must not 'break'
+ // (stop) but log the message instead. Expressions within {} are
+ // interpolated.
+ std::string logMessage;
+ std::vector<LogMessagePart> logMessageParts;
uint32_t line; ///< The source line of the breakpoint or logpoint
uint32_t column; ///< An optional source column of the breakpoint
- SourceBreakpoint() : BreakpointBase(), line(0), column(0) {}
+ SourceBreakpoint() : Breakpoint(), line(0), column(0) {}
SourceBreakpoint(const llvm::json::Object &obj);
// Set this breakpoint in LLDB as a new breakpoint
void SetBreakpoint(const llvm::StringRef source_path);
+ void UpdateBreakpoint(const SourceBreakpoint &request_bp);
+
+ void SetLogMessage();
+ // Format \param text and return formatted text in \param formatted.
+ // \return any formatting failures.
+ lldb::SBError FormatLogText(llvm::StringRef text, std::string &formatted);
+ lldb::SBError AppendLogMessagePart(llvm::StringRef part, bool is_expr);
+ void NotifyLogMessageError(llvm::StringRef error);
+
+ static bool BreakpointHitCallback(void *baton, lldb::SBProcess &process,
+ lldb::SBThread &thread,
+ lldb::SBBreakpointLocation &location);
};
inline bool operator<(const SourceBreakpoint &lhs,
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 01494dcc7da00f..67022347e6d624 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -525,7 +525,8 @@ void EventThreadFunction() {
if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) {
auto event_type =
lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event);
- auto bp = lldb::SBBreakpoint::GetBreakpointFromEvent(event);
+ auto bp =
+ Breakpoint(lldb::SBBreakpoint::GetBreakpointFromEvent(event));
// If the breakpoint was originated from the IDE, it will have the
// BreakpointBase::GetBreakpointLabel() label attached. Regardless
// of wether the locations were added or removed, the breakpoint
@@ -541,7 +542,7 @@ void EventThreadFunction() {
// mapped. Note that CreateBreakpoint doesn't apply source mapping.
// Besides, the current implementation of VSCode ignores the
// "source" element of breakpoint events.
- llvm::json::Value source_bp = CreateBreakpoint(bp);
+ llvm::json::Value source_bp = CreateBreakpoint(&bp);
source_bp.getAsObject()->erase("source");
body.try_emplace("breakpoint", source_bp);
@@ -2345,7 +2346,7 @@ void request_setBreakpoints(const llvm::json::Object &request) {
existing_source_bps->second.find(src_bp.line);
if (existing_bp != existing_source_bps->second.end()) {
existing_bp->second.UpdateBreakpoint(src_bp);
- AppendBreakpoint(existing_bp->second.bp, response_breakpoints, path,
+ AppendBreakpoint(&existing_bp->second, response_breakpoints, path,
src_bp.line);
continue;
}
@@ -2354,7 +2355,7 @@ void request_setBreakpoints(const llvm::json::Object &request) {
g_dap.source_breakpoints[path][src_bp.line] = src_bp;
SourceBreakpoint &new_bp = g_dap.source_breakpoints[path][src_bp.line];
new_bp.SetBreakpoint(path.data());
- AppendBreakpoint(new_bp.bp, response_breakpoints, path, new_bp.line);
+ AppendBreakpoint(&new_bp, response_breakpoints, path, new_bp.line);
}
}
}
@@ -2567,7 +2568,7 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) {
// handled it here and we don't need to set a new breakpoint below.
request_bps.erase(request_pos);
// Add this breakpoint info to the response
- AppendBreakpoint(pair.second.bp, response_breakpoints);
+ AppendBreakpoint(&pair.second, response_breakpoints);
}
}
// Remove any breakpoints that are no longer in our list
@@ -2581,7 +2582,7 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) {
g_dap.function_breakpoints[pair.first()] = std::move(pair.second);
FunctionBreakpoint &new_bp = g_dap.function_breakpoints[pair.first()];
new_bp.SetBreakpoint();
- AppendBreakpoint(new_bp.bp, response_breakpoints);
+ AppendBreakpoint(&new_bp, response_breakpoints);
}
llvm::json::Object body;
@@ -3582,8 +3583,8 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) {
FillResponse(request, response);
llvm::json::Array response_breakpoints;
for (uint32_t i = 0; g_dap.target.GetBreakpointAtIndex(i).IsValid(); ++i) {
- auto bp = g_dap.target.GetBreakpointAtIndex(i);
- AppendBreakpoint(bp, response_breakpoints);
+ auto bp = Breakpoint(g_dap.target.GetBreakpointAtIndex(i));
+ AppendBreakpoint(&bp, response_breakpoints);
}
llvm::json::Object body;
body.try_emplace("breakpoints", std::move(response_breakpoints));
diff --git a/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn b/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
index d8292df8c0e74f..98c2068f6da291 100644
--- a/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
+++ b/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
@@ -38,6 +38,7 @@ executable("lldb-dap") {
# FIXME: rpath/install_name stuff on macOS for framework on macOS
sources = [
+ "Breakpoint.cpp",
"BreakpointBase.cpp",
"DAP.cpp",
"ExceptionBreakpoint.cpp",
>From 2743af56220630f55a0c5bc0a7a13806c817aaad Mon Sep 17 00:00:00 2001
From: Zequan Wu <zequanwu at google.com>
Date: Mon, 12 Feb 2024 16:42:09 -0500
Subject: [PATCH 2/2] [lldb-dap] Add support for data breakpoint.
This implements functionality to handle `DataBreakpointInfo` request and `SetDataBreakpoints` request.
It doesn't handle the case when `name` is an expression, see Todo comment for details.
---
.../test/tools/lldb-dap/dap_server.py | 37 +++
.../tools/lldb-dap/databreakpoint/Makefile | 3 +
.../TestDAP_setDataBreakpoints.py | 100 +++++++
.../tools/lldb-dap/databreakpoint/main.cpp | 17 ++
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
lldb/tools/lldb-dap/DAPForward.h | 2 +
lldb/tools/lldb-dap/Watchpoint.cpp | 48 ++++
lldb/tools/lldb-dap/Watchpoint.h | 34 +++
lldb/tools/lldb-dap/lldb-dap.cpp | 249 ++++++++++++++++++
.../gn/secondary/lldb/tools/lldb-dap/BUILD.gn | 1 +
10 files changed, 492 insertions(+)
create mode 100644 lldb/test/API/tools/lldb-dap/databreakpoint/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
create mode 100644 lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp
create mode 100644 lldb/tools/lldb-dap/Watchpoint.cpp
create mode 100644 lldb/tools/lldb-dap/Watchpoint.h
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index bb863bb8719176..2d48cfbd819366 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -501,6 +501,17 @@ def get_local_variable_value(self, name, frameIndex=0, threadId=None):
return variable["value"]
return None
+ def get_local_variable_child(self, name, child_name, frameIndex=0, threadId=None):
+ local = self.get_local_variable(name, frameIndex, threadId)
+ if local["variablesReference"] == 0:
+ return None
+ children = self.request_variables(local["variablesReference"])[
+ "body"]["variables"]
+ for child in children:
+ if child["name"] == child_name:
+ return child
+ return None
+
def replay_packets(self, replay_file_path):
f = open(replay_file_path, "r")
mode = "invalid"
@@ -895,6 +906,32 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
}
return self.send_recv(command_dict)
+ def request_dataBreakpointInfo(self, variablesReference, name):
+ args_dict = {"variablesReference": variablesReference, "name": name}
+ command_dict = {
+ "command": "dataBreakpointInfo",
+ "type": "request",
+ "arguments": args_dict,
+ }
+ return self.send_recv(command_dict)
+
+ def request_setDataBreakpoint(self, dataBreakpoints):
+ """dataBreakpoints is a list of dictionary with following fields:
+ {
+ dataId: (address in hex)/(size in bytes)
+ accessType: read/write/readWrite
+ [condition]: string
+ [hitCondition]: string
+ }
+ """
+ args_dict = {"breakpoints": dataBreakpoints}
+ command_dict = {
+ "command": "setDataBreakpoints",
+ "type": "request",
+ "arguments": args_dict,
+ }
+ return self.send_recv(command_dict)
+
def request_compileUnits(self, moduleId):
args_dict = {"moduleId": moduleId}
command_dict = {
diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/Makefile b/lldb/test/API/tools/lldb-dap/databreakpoint/Makefile
new file mode 100644
index 00000000000000..99998b20bcb050
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
new file mode 100644
index 00000000000000..a13b4399a02f18
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
@@ -0,0 +1,100 @@
+"""
+Test lldb-dap dataBreakpointInfo and setDataBreakpoints requests
+"""
+
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase):
+ def setUp(self):
+ lldbdap_testcase.DAPTestCaseBase.setUp(self)
+ self.accessTypes = ["read", "write", "readWrite"]
+
+ @skipIfWindows
+ @skipIfRemote
+ def test_functionality(self):
+ """Tests setting data breakpoints.
+ """
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(program)
+ source = "main.cpp"
+ first_loop_break_line = line_number(
+ source, "// first loop breakpoint")
+ breakpoint_ids = self.set_source_breakpoints(
+ source, [first_loop_break_line])
+ self.continue_to_breakpoints(breakpoint_ids)
+ self.dap_server.get_local_variables()
+ # Test write watchpoints on x, arr[2]
+ response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
+ arr = self.dap_server.get_local_variable("arr")
+ response_arr_2 = self.dap_server.request_dataBreakpointInfo(
+ arr["variablesReference"], "[2]")
+
+ # Test response from dataBreakpointInfo request.
+ self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
+ self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
+ self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "1")
+ self.assertEquals(response_arr_2["body"]
+ ["accessTypes"], self.accessTypes)
+ dataBreakpoints = [
+ {
+ "dataId": response_x["body"]["dataId"],
+ "accessType": "write"
+ },
+ {
+ "dataId": response_arr_2["body"]["dataId"],
+ "accessType": "write"
+ }
+ ]
+ self.dap_server.request_setDataBreakpoint(dataBreakpoints)
+
+ self.dap_server.request_continue()
+ self.dap_server.wait_for_stopped()
+ x_val = self.dap_server.get_local_variable_value("x")
+ i_val = self.dap_server.get_local_variable_value("i")
+ self.assertEquals(x_val, "2")
+ self.assertEquals(i_val, "1")
+
+ self.dap_server.request_continue()
+ self.dap_server.wait_for_stopped()
+ arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
+ i_val = self.dap_server.get_local_variable_value("i")
+ self.assertEquals(arr_2["value"], "'z'")
+ self.assertEquals(i_val, "2")
+ self.dap_server.request_setDataBreakpoint([])
+
+ # Test hit condition
+ second_loop_break_line = line_number(
+ source, "// second loop breakpoint")
+ breakpoint_ids = self.set_source_breakpoints(
+ source, [second_loop_break_line])
+ self.continue_to_breakpoints(breakpoint_ids)
+ dataBreakpoints = [
+ {
+ "dataId": response_x["body"]["dataId"],
+ "accessType": "write",
+ "hitCondition": "2"
+ }
+ ]
+ self.dap_server.request_setDataBreakpoint(dataBreakpoints)
+ self.dap_server.request_continue()
+ self.dap_server.wait_for_stopped()
+ x_val = self.dap_server.get_local_variable_value("x")
+ self.assertEquals(x_val, "3")
+
+ # Test condition
+ dataBreakpoints = [
+ {
+ "dataId": response_x["body"]["dataId"],
+ "accessType": "write",
+ "condition": "x==10"
+ }
+ ]
+ self.dap_server.request_setDataBreakpoint(dataBreakpoints)
+ self.dap_server.request_continue()
+ self.dap_server.wait_for_stopped()
+ x_val = self.dap_server.get_local_variable_value("x")
+ self.assertEquals(x_val, "10")
diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp b/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp
new file mode 100644
index 00000000000000..8082fe02f3e534
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp
@@ -0,0 +1,17 @@
+int main(int argc, char const *argv[]) {
+ // Test for data breakpoint
+ int x = 0;
+ char arr[4] = {'a', 'b', 'c', 'd'};
+ for (int i = 0; i < 5; ++i) { // first loop breakpoint
+ if (i == 1) {
+ x = i + 1;
+ } else if (i == 2) {
+ arr[i] = 'z';
+ }
+ }
+
+ x = 1;
+ for (int i = 0; i < 10; ++i) { // second loop breakpoint
+ ++x;
+ }
+}
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index f8c0e4ecf36c2f..f8f0d86453f585 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 fffff1e3f79020..8c79488fae8dbf 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 00000000000000..2f176e0da84f15
--- /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 00000000000000..026b07d67241ce
--- /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 67022347e6d624..84218809373915 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>
@@ -1647,6 +1648,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 +2594,248 @@ 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);
+ bool is_duplicated_variable_name = name.contains(" @");
+
+ lldb::SBValue variable;
+ 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 {
+ // We are expanding a variable that has children, so we will return its
+ // children.
+ 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);
+ }
+ }
+ }
+ }
+
+ if (variable.IsValid()) {
+ std::string addr = llvm::utohexstr(variable.GetLoadAddress());
+ std::string size = llvm::utostr(variable.GetByteSize());
+ body.try_emplace("dataId", addr + "/" + size);
+ body.try_emplace("accessTypes", std::move(accessTypes));
+ body.try_emplace("description",
+ size + " bytes at " + addr + " " + name.str());
+ } else {
+ // TODO: name might be an expression if variablesReference == 0. In that
+ // case, we need to evaluate expression in the scope of given frame (or
+ // global scope if not given), but SBTarget::EvaluateExpression can only
+ // evalute expression on selected thread and frame. Also, it doesn't specify
+ // the number of bytes to watch.
+ body.try_emplace("dataId", nullptr);
+ body.try_emplace("description", "variable not found.");
+ }
+
+ 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",
@@ -3611,6 +3856,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);
diff --git a/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn b/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
index 98c2068f6da291..dc958934485ec1 100644
--- a/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
+++ b/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
@@ -52,5 +52,6 @@ executable("lldb-dap") {
"RunInTerminal.cpp",
"SourceBreakpoint.cpp",
"lldb-dap.cpp",
+ "Watchpoint.cpp"
]
}
More information about the lldb-commits
mailing list