diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -93,6 +93,29 @@ return self.assertTrue(False, "breakpoint not hit") + def verify_watchpoint_hit(self, line_number): + '''Wait for the process we are debugging to stop, and verify we hit + any watchpoint at the line number.''' + stopped_events = self.vscode.wait_for_stopped() + for stopped_event in stopped_events: + if 'body' in stopped_event: + body = stopped_event['body'] + if 'reason' not in body: + continue + if body['reason'] != 'breakpoint': + continue + if 'description' not in body: + continue + description = body['description'] + if 'watchpoint' not in description: + continue + + (_, line) = self.get_source_and_line(threadId=body['threadId']) + if line == line_number: + return + + self.assertTrue(False, "watchpoint not hit") + def verify_stop_exception_info(self, expected_description): '''Wait for the process we are debugging to stop, and verify the stop reason is 'exception' and that the description matches diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -827,6 +827,35 @@ } return self.send_recv(command_dict) + def request_setDataBreakpoints(self, id, address, accessType): + breakpoint = { + 'id': id, + 'dataId': address, + 'enabled': True, + 'accessType': accessType + } + args_dict = { + 'breakpoints': [ breakpoint ] + } + command_dict = { + 'command': 'setDataBreakpoints', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_dataBreakpointInfo(self, name, variables_reference): + args_dict = { + 'name': name, + 'variablesReference': variables_reference + } + command_dict = { + 'command': 'dataBreakpointInfo', + '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-vscode/breakpoint_data/Makefile b/lldb/test/API/tools/lldb-vscode/breakpoint_data/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint_data/Makefile @@ -0,0 +1,8 @@ +CXX_SOURCES := main-copy.cpp +LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)" +USE_LIBDL :=1 + +include Makefile.rules + +main-copy.cpp: main.cpp + cp -f $< $@ diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py b/lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py @@ -0,0 +1,129 @@ +""" +Test lldb-vscode setDataBreakpoints request +""" + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbvscode_testcase +import os + +# Return first element in the array where func is true +def array_find(array, func): + for i in array: + if func(i): + return i + return None + +class TestVSCode_setDataBreakpoints(lldbvscode_testcase.VSCodeTestCaseBase): + + def setUp(self): + lldbvscode_testcase.VSCodeTestCaseBase.setUp(self) + self.main_basename = 'main-copy.cpp' + self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename)) + + @skipIfWindows + @skipIfRemote + def test_global_var(self): + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Break on the main function so we can see the variable scopes + response = self.vscode.request_setFunctionBreakpoints(['main']) + breakpoints = response['body']['breakpoints'] + breakpoint_id = breakpoints[0]['id'] + self.vscode.request_continue() + self.verify_breakpoint_hit([breakpoint_id]) + + # Get and verify that the global_num is in scope + globals = self.vscode.get_global_variables() + global_num = array_find(globals, lambda x: x['name'] == 'global_num') + self.assertIsNotNone(global_num) + + # Get and verify the data breakpoint info + data_breakpoint_info = self.vscode.request_dataBreakpointInfo( + global_num['name'], 2 + ) + self.assertEqual(data_breakpoint_info['body']['accessTypes'], ['read', 'readWrite', 'write']) + + # Set the data breakpoint and verify watchpoint hit after continue + self.vscode.request_setDataBreakpoints( + 'mockId', + data_breakpoint_info['body']['dataId'], + 'readWrite' + ) + self.vscode.request_continue() + expected_line = line_number('main.cpp', '// global_num') + self.verify_watchpoint_hit(expected_line) + + @skipIfWindows + @skipIfRemote + def test_local_var(self): + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Break on the main function so we can see the variable scopes + response = self.vscode.request_setFunctionBreakpoints(['main']) + breakpoints = response['body']['breakpoints'] + breakpoint_id = breakpoints[0]['id'] + self.vscode.request_continue() + self.verify_breakpoint_hit([breakpoint_id]) + + # Get and verify the locals + locals = self.vscode.get_local_variables() + num_a = array_find(locals, lambda x: x['name'] == 'num_a') + self.assertIsNotNone(num_a) + + # Get and verify the data breakpoint info + data_breakpoint_info = self.vscode.request_dataBreakpointInfo( + num_a['name'], 1 + ) + self.assertEqual(data_breakpoint_info['body']['accessTypes'], ['read', 'readWrite', 'write']) + + # Set the data breakpoint and verify breakpoint hit after continue + self.vscode.request_setDataBreakpoints( + 'mockId', + data_breakpoint_info['body']['dataId'], + 'readWrite' + ) + self.vscode.request_continue() + expected_line = line_number('main.cpp', '// num_a first') + self.verify_watchpoint_hit(expected_line) + + # In this example there is a second watchpoint to hit + self.vscode.request_continue() + expected_line = line_number('main.cpp', '// num_a second') + self.verify_watchpoint_hit(expected_line) + + @skipIfWindows + @skipIfRemote + def global_const_num(self): + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Break on the main function so we can see the variable scopes + response = self.vscode.request_setFunctionBreakpoints(['main']) + breakpoints = response['body']['breakpoints'] + breakpoint_id = breakpoints[0]['id'] + self.vscode.request_continue() + self.verify_breakpoint_hit([breakpoint_id]) + + # Get and verify that the global_num is in scope + globals = self.vscode.get_global_variables() + global_const_num = array_find(globals, lambda x: x['name'] == 'global_const_num') + self.assertIsNotNone(global_const_num) + + # Get and verify the data breakpoint info + data_breakpoint_info = self.vscode.request_dataBreakpointInfo( + global_const_num['name'], 2 + ) + self.assertEqual(data_breakpoint_info['body']['accessTypes'], ['read']) + + # Set the data breakpoint and verify watchpoint hit after continue + self.vscode.request_setDataBreakpoints( + 'mockId', + data_breakpoint_info['body']['dataId'], + 'read' + ) + self.vscode.request_continue() + expected_line = line_number('main.cpp', '// global_const_num') + self.verify_watchpoint_hit(expected_line) diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint_data/main.cpp b/lldb/test/API/tools/lldb-vscode/breakpoint_data/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint_data/main.cpp @@ -0,0 +1,20 @@ +#include "string.h" + +static int global_num = 20; +const char *global_str = "hello world"; +const int global_const_num = 123; + +int main() { + int num_a = 10; + int num_b = 20; // num_a first + int *num_a_ptr = &num_a; + int *num_b_ptr = &num_b; + + *num_a_ptr = *num_b_ptr * 20; + *num_b_ptr = *num_a_ptr / 10; // num_a second + global_num = 30; + + int string_sum = 0; // global_num + for (int i = 0; i < strlen(global_str); i++) + string_sum += global_const_num; // global_const_num +} diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/Makefile b/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/Makefile @@ -0,0 +1,9 @@ +CXX_SOURCES := main-copy.cpp +LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)" +USE_LIBDL :=1 +CXXFLAGS_EXTRAS := -O3 + +include Makefile.rules + +main-copy.cpp: main.cpp + cp -f $< $@ diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/TestVSCode_setDataBreakpoints.py b/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/TestVSCode_setDataBreakpoints.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/TestVSCode_setDataBreakpoints.py @@ -0,0 +1,47 @@ +""" +Test lldb-vscode setDataBreakpoints request +""" + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbvscode_testcase +import os + +# Return first element in the array where func is true +def array_find(array, func): + for i in array: + if func(i): + return i + return None + +class TestVSCode_setDataBreakpoints(lldbvscode_testcase.VSCodeTestCaseBase): + + def setUp(self): + lldbvscode_testcase.VSCodeTestCaseBase.setUp(self) + self.main_basename = 'main-copy.cpp' + self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename)) + + @skipIfWindows + @skipIfRemote + def test_local_var(self): + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Break on the main function so we can see the variable scopes + response = self.vscode.request_setFunctionBreakpoints(['main']) + breakpoints = response['body']['breakpoints'] + breakpoint_id = breakpoints[0]['id'] + self.vscode.request_continue() + self.verify_breakpoint_hit([breakpoint_id]) + + # Get and verify the locals + locals = self.vscode.get_local_variables() + num_a = array_find(locals, lambda x: x['name'] == 'num_a') + self.assertIsNotNone(num_a) + + # Get and verify the data breakpoint info + data_breakpoint_info = self.vscode.request_dataBreakpointInfo( + num_a['name'], 1 + ) + # num_a has no watchpoint access as it's stored in a register + self.assertEqual(data_breakpoint_info['body']['accessTypes'], []) diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/main.cpp b/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint_data_optimized/main.cpp @@ -0,0 +1,7 @@ +#include "stdio.h" + +int main() { + int num_a = 10; + int num_b = 20; + printf("%d", num_a + num_b); +} diff --git a/lldb/tools/lldb-vscode/CMakeLists.txt b/lldb/tools/lldb-vscode/CMakeLists.txt --- a/lldb/tools/lldb-vscode/CMakeLists.txt +++ b/lldb/tools/lldb-vscode/CMakeLists.txt @@ -35,6 +35,7 @@ ProgressEvent.cpp RunInTerminal.cpp SourceBreakpoint.cpp + Watchpoint.cpp VSCode.cpp LINK_LIBS diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" @@ -53,6 +54,7 @@ #include "RunInTerminal.h" #include "SourceBreakpoint.h" #include "SourceReference.h" +#include "Watchpoint.h" #define VARREF_LOCALS (int64_t)1 #define VARREF_GLOBALS (int64_t)2 @@ -136,6 +138,7 @@ llvm::StringMap source_breakpoints; FunctionBreakpointMap function_breakpoints; std::vector exception_breakpoints; + std::unordered_map watchpoints; std::vector init_commands; std::vector pre_run_commands; std::vector exit_commands; diff --git a/lldb/tools/lldb-vscode/Watchpoint.h b/lldb/tools/lldb-vscode/Watchpoint.h new file mode 100644 --- /dev/null +++ b/lldb/tools/lldb-vscode/Watchpoint.h @@ -0,0 +1,56 @@ +//===-- 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_VSCODE_WATCHPOINT_H +#define LLDB_TOOLS_LLDB_VSCODE_WATCHPOINT_H + +#include "lldb/API/SBError.h" +#include "lldb/API/SBValue.h" +#include "lldb/API/SBWatchpoint.h" +#include "llvm/Support/JSON.h" + +#include + +namespace lldb_vscode { + +typedef lldb::SBValue (*VariableGetter)(std::string variable_name, + uint32_t variables_reference); + +class Watchpoint { +public: + // Get a watchpoint ID from request + static std::string GetId(const llvm::json::Object &obj); + + // Create a watchpoint from a debug adapter protocol request + Watchpoint(const llvm::json::Object &obj, VariableGetter variable_getter); + + // Update existing watchpoint with new debug adapter protocol request + void Update(const llvm::json::Object &obj); + + // Get watchpoint state for data breakpoint response + llvm::json::Object GetResponse(); + + // Remove any existing watchpoint + void RemoveExisting(); + +private: + VariableGetter m_variable_getter; + std::string m_id; + bool m_enabled; + bool m_read; + bool m_write; + bool m_verified; + std::string m_error; + lldb::SBWatchpoint m_watchpoint; + + void OnError(std::string reason); +}; + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/Watchpoint.cpp b/lldb/tools/lldb-vscode/Watchpoint.cpp new file mode 100644 --- /dev/null +++ b/lldb/tools/lldb-vscode/Watchpoint.cpp @@ -0,0 +1,99 @@ +//===-- 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 "VSCode.h" + +namespace lldb_vscode { + +std::string Watchpoint::GetId(const llvm::json::Object &obj) { + return GetString(obj, "id").str(); +} + +Watchpoint::Watchpoint(const llvm::json::Object &obj, + VariableGetter variable_getter) + : m_variable_getter(variable_getter), m_verified(false) { + Update(obj); +} + +void Watchpoint::Update(const llvm::json::Object &obj) { + RemoveExisting(); + + m_id = GetId(obj); + m_enabled = GetBoolean(obj, "enabled", false); + if (!m_enabled) + return; + + std::string access_type = GetString(obj, "accessType").str(); + m_read = access_type == "read" || access_type == "readWrite"; + m_write = access_type == "write" || access_type == "readWrite"; + if (m_read == false && m_write == false) { + OnError("accessType is invalid. It should be read/write/readWrite."); + return; + } + + std::string data_id = GetString(obj, "dataId").str(); + auto delimiter = data_id.find('/'); + if (delimiter == std::string::npos) { + OnError( + "data_id should be of the format 'variable_name/variable_reference'"); + return; + } + + auto variable_name = data_id.substr(0, delimiter); + auto variable_index_str = data_id.substr(delimiter + 1, data_id.length()); + if (variable_name.size() == 0 || variable_index_str.size() == 0) { + OnError( + "data_id should be of the format 'variable_name/variable_reference'"); + return; + } + + uint32_t variables_reference = stoul(variable_index_str); + lldb::SBValue variable = + m_variable_getter(variable_name, variables_reference); + if (!variable.IsValid()) { + OnError("referenced variable is invalid"); + return; + } + + lldb::SBError error; + m_watchpoint = variable.Watch(true, m_read, m_write, error); + if (!error.Success()) { + OnError(error.GetCString()); + return; + }; + + m_verified = true; + m_error = ""; +} + +llvm::json::Object Watchpoint::GetResponse() { + llvm::json::Object watchpoint_info; + watchpoint_info.try_emplace("id", m_id); + watchpoint_info.try_emplace("verified", m_verified); + if (m_error.size() > 0) + watchpoint_info.try_emplace("message", m_error); + + return watchpoint_info; +} + +void Watchpoint::RemoveExisting() { + if (m_verified) + g_vsc.target.DeleteWatchpoint(m_watchpoint.GetID()); + + m_verified = false; + m_error = ""; +} + +void Watchpoint::OnError(std::string error) { + m_verified = false; + m_error = error; + g_vsc.SendOutput(OutputType::Console, error); +} + +} // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include "llvm/ADT/ArrayRef.h" @@ -56,6 +57,8 @@ #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/raw_ostream.h" +#include "lldb/API/SBMemoryRegionInfo.h" + #include "JSONUtils.h" #include "LLDBUtils.h" #include "OutputRedirector.h" @@ -1541,6 +1544,8 @@ body.try_emplace("supportsProgressReporting", true); // The debug adapter supports 'logMessage' in breakpoint. body.try_emplace("supportsLogPoints", true); + // The debug adapter supports data breakpoints + body.try_emplace("supportsDataBreakpoints", true); response.try_emplace("body", std::move(body)); g_vsc.SendJSON(llvm::json::Value(std::move(response))); @@ -2117,6 +2122,334 @@ g_vsc.SendJSON(llvm::json::Value(std::move(response))); } +static lldb::SBValue get_variable(std::string variable_name, + uint32_t variables_reference) { + bool is_duplicated_variable_name = + variable_name.find(" @") != llvm::StringRef::npos; + lldb::SBValue variable; + + if (lldb::SBValueList *top_scope = GetTopLevelScope(variables_reference)) { + // 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 local_variable = CreateUniqueVariableNameForDisplay( + curr_variable, is_duplicated_variable_name); + if (variable_name == local_variable) { + 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_vsc.variables.GetVariable(variables_reference); + if (!container.IsValid()) { + return variable; + } + + variable = container.GetChildMemberWithName(variable_name.data()); + if (!variable.IsValid()) { + if (variable_name.size() > 0 && variable_name[0] == '[') { + llvm::StringRef index_str(std::move(variable_name.substr(1))); + uint64_t index = 0; + if (!index_str.consumeInteger(0, index)) { + if (index_str == "]") + variable = container.GetChildAtIndex(index); + } + } + } + } + + return variable; +} + +// "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" ] +// }] +// } +// "DataBreakpoint": { +// "type": "object", +// "description": "Properties of a data breakpoint passed to the +// `setDataBreakpoints` request.", "properties": { +// "dataId": { +// "type": "string", +// "description": "An id representing the data. This id is returned from +// the `dataBreakpointInfo` request." +// }, +// "accessType": { +// "$ref": "#/definitions/DataBreakpointAccessType", +// "description": "The access type of the data." +// }, +// "condition": { +// "type": "string", +// "description": "An expression for conditional breakpoints." +// }, +// "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." +// } +// }, +// "required": [ "dataId" ] +// } +void request_setDataBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + lldb::SBError error; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + auto breakpoints = arguments->getArray("breakpoints"); + + std::unordered_set breakpoint_ids; + for (const auto &breakpoint : *breakpoints) { + auto breakpoint_obj = *breakpoint.getAsObject(); + auto breakpoint_id = Watchpoint::GetId(breakpoint_obj); + breakpoint_ids.emplace(breakpoint_id); + + // Update watchpoint if it already exists + if (g_vsc.watchpoints.find(breakpoint_id) != g_vsc.watchpoints.end()) { + auto existing_watchpoint = g_vsc.watchpoints.at(breakpoint_id); + existing_watchpoint.Update(breakpoint_obj); + continue; + } + + g_vsc.watchpoints.emplace(breakpoint_id, Watchpoint(breakpoint_obj, get_variable)); + } + + // If the record of watchpoints has entries that aren't present in the + // request, it means it's been deleted + std::vector breakpoints_to_delete; + for (auto entry : g_vsc.watchpoints) { + auto id = entry.first; + if (breakpoint_ids.find(id) == breakpoint_ids.end()) { + breakpoints_to_delete.push_back(id); + } + } + for (auto entry : breakpoints_to_delete) { + g_vsc.watchpoints.at(entry).RemoveExisting(); + g_vsc.watchpoints.erase(entry); + } + + llvm::json::Object body; + llvm::json::Array breakpoints_to_return; + for (const auto &breakpoint_id: breakpoint_ids) { + breakpoints_to_return.emplace_back(g_vsc.watchpoints.at(breakpoint_id).GetResponse()); + } + + body.try_emplace("breakpoints", std::move(breakpoints_to_return)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +void populate_data_breakpoint_info(llvm::json::Object &response, std::optional data_id, std::string description, bool read, bool write) { + llvm::json::Object body; + llvm::json::Array access_types; + if (data_id.has_value()) + body.try_emplace("dataId", data_id.value()); + else + body.try_emplace("dataId", nullptr); + + body.try_emplace("description", description); + body.try_emplace("canPersist", false); + + if (read) { + access_types.emplace_back("read"); + if (write) + access_types.emplace_back("readWrite"); + } + if (write) + access_types.emplace_back("write"); + + body.try_emplace("accessTypes", std::move(access_types)); + response.try_emplace("body", std::move(body)); +} + +// "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." +// }, +// "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; + lldb::SBError error; + std::stringstream data; + std::stringstream description; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + auto name = GetString(arguments, "name"); + auto variables_reference = arguments->getInteger("variablesReference"); + lldb::SBValue variable = + get_variable(name.str(), variables_reference.value()); + + if (!variable.IsValid()) { + description << "variable '" << name.data() << "' is not a valid variable."; + populate_data_breakpoint_info(response, std::nullopt, description.str(), false, false); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + auto v_address = variable.GetLoadAddress(); + auto v_size = variable.GetByteSize(); + + if (v_address == LLDB_INVALID_ADDRESS) { + populate_data_breakpoint_info(response, std::nullopt, "Variable has an invalid load address. This could be because it is being stored in a register. Try recompiling your program without optimizations.", false, false); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + lldb::SBMemoryRegionInfo region_info; + error = g_vsc.target.GetProcess().GetMemoryRegionInfo(v_address, region_info); + data << name.str() << "/" << variables_reference.value(); + description << name.data() << ": at address " << std::hex << "0x" << v_address + << " with size " << std::dec << v_size; + populate_data_breakpoint_info(response, data.str(), description.str(), error.Success() && region_info.IsReadable(), error.Success() && region_info.IsWritable()); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + // "SetExceptionBreakpointsRequest": { // "allOf": [ { "$ref": "#/definitions/Request" }, { // "type": "object", @@ -2762,7 +3095,6 @@ 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 @@ -2783,40 +3115,8 @@ const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX); if (id_value != UINT64_MAX) { variable = g_vsc.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_vsc.variables.GetVariable(variablesReference); - variable = container.GetChildMemberWithName(name.data()); - if (!variable.IsValid()) { - if (name.startswith("[")) { - 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 = get_variable(name.str(), variablesReference); } if (variable.IsValid()) { @@ -3079,6 +3379,10 @@ g_vsc.RegisterRequestCallback("setFunctionBreakpoints", request_setFunctionBreakpoints); g_vsc.RegisterRequestCallback("setVariable", request_setVariable); + g_vsc.RegisterRequestCallback("setDataBreakpoints", + request_setDataBreakpoints); + g_vsc.RegisterRequestCallback("dataBreakpointInfo", + request_dataBreakpointInfo); g_vsc.RegisterRequestCallback("source", request_source); g_vsc.RegisterRequestCallback("stackTrace", request_stackTrace); g_vsc.RegisterRequestCallback("stepIn", request_stepIn);