From 54749956f63461ea5d81395be1e3e45fb2b632e0 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 27 Aug 2024 00:19:39 +0530 Subject: [PATCH] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. (#105278) Added support for "supportsInstructionBreakpoints" capability and now it this command is triggered when we set instruction breakpoint. We need this support as part of enabling disassembly view debugging. Following features should work as part of this feature enablement: 1. Settings breakpoints in disassembly view: Unsetting the breakpoint is not happening from the disassembly view. Currently we need to unset breakpoint manually from the breakpoint List. Multiple breakpoints are getting set for the same $ 2. Step over, step into, continue in the disassembly view The format for DisassembleRequest and DisassembleResponse at https://raw.githubusercontent.com/microsoft/vscode/master/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts . Ref Images: Set instruction breakpoint in disassembly view: ![image](https://github.com/user-attachments/assets/833bfb34-86f4-40e2-8c20-14b638a612a2) After issuing continue: ![image](https://github.com/user-attachments/assets/884572a3-915e-422b-b8dd-d132e5c00de6) --------- Co-authored-by: Santhosh Kumar Ellendula Co-authored-by: Santhosh Kumar Ellendula --- .../test/tools/lldb-dap/dap_server.py | 14 + .../test/tools/lldb-dap/lldbdap_testcase.py | 5 +- .../lldb-dap/instruction-breakpoint/Makefile | 6 + .../TestDAP_instruction_breakpoint.py | 97 +++++++ .../lldb-dap/instruction-breakpoint/main.cpp | 18 ++ lldb/tools/lldb-dap/CMakeLists.txt | 1 + lldb/tools/lldb-dap/DAP.cpp | 30 ++- lldb/tools/lldb-dap/DAP.h | 8 + lldb/tools/lldb-dap/DAPForward.h | 1 + lldb/tools/lldb-dap/InstructionBreakpoint.cpp | 28 ++ lldb/tools/lldb-dap/InstructionBreakpoint.h | 36 +++ lldb/tools/lldb-dap/JSONUtils.cpp | 72 ++++- lldb/tools/lldb-dap/JSONUtils.h | 11 + lldb/tools/lldb-dap/lldb-dap.cpp | 253 ++++++++++++++++++ 14 files changed, 577 insertions(+), 3 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile create mode 100644 lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py create mode 100644 lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp create mode 100644 lldb/tools/lldb-dap/InstructionBreakpoint.cpp create mode 100644 lldb/tools/lldb-dap/InstructionBreakpoint.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 874383a13e2bb6..b095171d8fd1a4 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 @@ -1099,6 +1099,20 @@ def terminate(self): self.send.close() # self.recv.close() + def request_setInstructionBreakpoints(self, memory_reference=[]): + breakpoints = [] + for i in memory_reference: + args_dict = { + "instructionReference": i, + } + breakpoints.append(args_dict) + args_dict = {"breakpoints": breakpoints} + command_dict = { + "command": "setInstructionBreakpoints", + "type": "request", + "arguments": args_dict, + } + return self.send_recv(command_dict) class DebugAdaptorServer(DebugCommunication): def __init__( diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index 86eba355da83db..709b7aff11d7f2 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -81,7 +81,10 @@ def verify_breakpoint_hit(self, breakpoint_ids): body = stopped_event["body"] if "reason" not in body: continue - if body["reason"] != "breakpoint": + if ( + body["reason"] != "breakpoint" + and body["reason"] != "instruction breakpoint" + ): continue if "description" not in body: continue diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile new file mode 100644 index 00000000000000..697527c4e55223 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main-copy.cpp +CXXFLAGS_EXTRAS := -O0 -g +include Makefile.rules + +main-copy.cpp: main.cpp + cp -f $< $@ diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py new file mode 100644 index 00000000000000..91b04aca7b7bd8 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py @@ -0,0 +1,97 @@ +import dap_server +import shutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbdap_testcase +import os +import lldb + + +class TestDAP_InstructionBreakpointTestCase(lldbdap_testcase.DAPTestCaseBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + lldbdap_testcase.DAPTestCaseBase.setUp(self) + + self.main_basename = "main-copy.cpp" + self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename)) + + def test_instruction_breakpoint(self): + self.build() + self.instruction_breakpoint_test() + + def instruction_breakpoint_test(self): + """Sample test to ensure SBFrame::Disassemble produces SOME output""" + # Create a target by the debugger. + target = self.createTestTarget() + + main_line = line_number("main.cpp", "breakpoint 1") + + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Set source breakpoint 1 + response = self.dap_server.request_setBreakpoints(self.main_path, [main_line]) + breakpoints = response["body"]["breakpoints"] + self.assertEquals(len(breakpoints), 1) + breakpoint = breakpoints[0] + self.assertEqual( + breakpoint["line"], main_line, "incorrect breakpoint source line" + ) + self.assertTrue(breakpoint["verified"], "breakpoint is not verified") + self.assertEqual( + self.main_basename, breakpoint["source"]["name"], "incorrect source name" + ) + self.assertEqual( + self.main_path, breakpoint["source"]["path"], "incorrect source file path" + ) + other_breakpoint_id = breakpoint["id"] + + # Continue and then verifiy the breakpoint + self.dap_server.request_continue() + self.verify_breakpoint_hit([other_breakpoint_id]) + + # now we check the stack trace making sure that we got mapped source paths + frames = self.dap_server.request_stackTrace()["body"]["stackFrames"] + intstructionPointerReference = [] + setIntstructionBreakpoints = [] + intstructionPointerReference.append(frames[0]["instructionPointerReference"]) + self.assertEqual( + frames[0]["source"]["name"], self.main_basename, "incorrect source name" + ) + self.assertEqual( + frames[0]["source"]["path"], self.main_path, "incorrect source file path" + ) + + # Check disassembly view + instruction = self.disassemble(frameIndex=0) + self.assertEqual( + instruction["address"], + intstructionPointerReference[0], + "current breakpoint reference is not in the disaasembly view", + ) + + # Get next instruction address to set instruction breakpoint + disassembled_instruction_list = self.dap_server.disassembled_instructions + instruction_addr_list = list(disassembled_instruction_list.keys()) + index = instruction_addr_list.index(intstructionPointerReference[0]) + if len(instruction_addr_list) >= (index + 1): + next_inst_addr = instruction_addr_list[index + 1] + if len(next_inst_addr) > 2: + setIntstructionBreakpoints.append(next_inst_addr) + instruction_breakpoint_response = ( + self.dap_server.request_setInstructionBreakpoints( + setIntstructionBreakpoints + ) + ) + inst_breakpoints = instruction_breakpoint_response["body"][ + "breakpoints" + ] + self.assertEqual( + inst_breakpoints[0]["instructionReference"], + next_inst_addr, + "Instruction breakpoint has not been resolved or failed to relocate the instruction breakpoint", + ) + self.dap_server.request_continue() + self.verify_breakpoint_hit([inst_breakpoints[0]["id"]]) diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp new file mode 100644 index 00000000000000..3c710d64171570 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp @@ -0,0 +1,18 @@ +#include +#include + +int function(int x) { + + if (x == 0) // breakpoint 1 + return x; + + if ((x % 2) != 0) + return x; + else + return function(x - 1) + x; +} + +int main(int argc, char const *argv[]) { + int n = function(2); + return n; +} \ No newline at end of file diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index f8f0d86453f585..d68098bf7b3266 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -38,6 +38,7 @@ add_lldb_tool(lldb-dap SourceBreakpoint.cpp DAP.cpp Watchpoint.cpp + InstructionBreakpoint.cpp LINK_LIBS liblldb diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 57b93c28ce9301..6012ee52110b73 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -68,7 +68,7 @@ static std::string capitalize(llvm::StringRef str) { void DAP::PopulateExceptionBreakpoints() { llvm::call_once(init_exception_breakpoints_flag, [this]() { - exception_breakpoints = std::vector {}; + exception_breakpoints = std::vector{}; if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) { exception_breakpoints->emplace_back("cpp_catch", "C++ Catch", @@ -996,4 +996,32 @@ void DAP::SetThreadFormat(llvm::StringRef format) { } } +InstructionBreakpoint * +DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) { + for (auto &bp : instruction_breakpoints) { + if (bp.second.id == bp_id) + return &bp.second; + } + return nullptr; +} + +InstructionBreakpoint * +DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) { + const auto num = thread.GetStopReasonDataCount(); + InstructionBreakpoint *inst_bp = nullptr; + for (size_t i = 0; i < num; i += 2) { + // thread.GetStopReasonDataAtIndex(i) will return the bp ID and + // thread.GetStopReasonDataAtIndex(i+1) will return the location + // within that breakpoint. We only care about the bp ID so we can + // see if this is an instruction breakpoint that is getting hit. + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); + inst_bp = GetInstructionBreakpoint(bp_id); + // If any breakpoint is not an instruction breakpoint, then stop and + // report this as a normal breakpoint + if (inst_bp == nullptr) + return nullptr; + } + return inst_bp; +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 0fc77ac1e81683..f4fdec6e895ad1 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -54,6 +54,7 @@ #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "IOStream.h" +#include "InstructionBreakpoint.h" #include "ProgressEvent.h" #include "RunInTerminal.h" #include "SourceBreakpoint.h" @@ -68,6 +69,8 @@ namespace lldb_dap { typedef llvm::DenseMap SourceBreakpointMap; typedef llvm::StringMap FunctionBreakpointMap; +typedef llvm::DenseMap + InstructionBreakpointMap; enum class OutputType { Console, Stdout, Stderr, Telemetry }; @@ -160,6 +163,7 @@ struct DAP { std::unique_ptr log; llvm::StringMap source_breakpoints; FunctionBreakpointMap function_breakpoints; + InstructionBreakpointMap instruction_breakpoints; std::optional> exception_breakpoints; llvm::once_flag init_exception_breakpoints_flag; std::vector pre_init_commands; @@ -334,6 +338,10 @@ struct DAP { void SetThreadFormat(llvm::StringRef format); + InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id); + + InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread); + private: // Send the JSON in "json_str" to the "out" stream. Correctly send the // "Content-Length:" field followed by the length, followed by the raw diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h index 8c79488fae8dbf..159d999a63c820 100644 --- a/lldb/tools/lldb-dap/DAPForward.h +++ b/lldb/tools/lldb-dap/DAPForward.h @@ -15,6 +15,7 @@ struct ExceptionBreakpoint; struct FunctionBreakpoint; struct SourceBreakpoint; struct Watchpoint; +struct InstructionBreakpoint; } // namespace lldb_dap namespace lldb { diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.cpp b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp new file mode 100644 index 00000000000000..de4f6f5d86717f --- /dev/null +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp @@ -0,0 +1,28 @@ +//===-- InstructionBreakpoint.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 "InstructionBreakpoint.h" +#include "DAP.h" + +namespace lldb_dap { + +// Instruction Breakpoint +InstructionBreakpoint::InstructionBreakpoint(const llvm::json::Object &obj) + : Breakpoint(obj), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0), + offset(GetSigned(obj, "offset", 0)) { + GetString(obj, "instructionReference") + .getAsInteger(0, instructionAddressReference); + instructionAddressReference += offset; +} + +void InstructionBreakpoint::SetInstructionBreakpoint() { + bp = g_dap.target.BreakpointCreateByAddress(instructionAddressReference); + id = bp.GetID(); +} +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.h b/lldb/tools/lldb-dap/InstructionBreakpoint.h new file mode 100644 index 00000000000000..cf1516f46e9551 --- /dev/null +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.h @@ -0,0 +1,36 @@ +//===-- InstructionBreakpoint.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_INSTRUCTIONBREAKPOINT_H +#define LLDB_TOOLS_LLDB_DAP_INSTRUCTIONBREAKPOINT_H + +#include "Breakpoint.h" +#include "llvm/ADT/StringRef.h" + +namespace lldb_dap { + +// Instruction Breakpoint +struct InstructionBreakpoint : public Breakpoint { + + lldb::addr_t instructionAddressReference; + int32_t id; + int32_t offset; + + InstructionBreakpoint() + : Breakpoint(), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0), + offset(0) {} + InstructionBreakpoint(const llvm::json::Object &obj); + + // Set instruction breakpoint in LLDB as a new breakpoint + void SetInstructionBreakpoint(); +}; + +} // namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index c080fd395b7288..7338e7cf41eb03 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -769,6 +769,70 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { return llvm::json::Value(std::move(object)); } +// Response to `setInstructionBreakpoints` request. +// "Breakpoint": { +// "type": "object", +// "description": "Response to `setInstructionBreakpoints` request.", +// "properties": { +// "id": { +// "type": "number", +// "description": "The identifier for the breakpoint. It is needed if +// breakpoint events are used to update or remove breakpoints." +// }, +// "verified": { +// "type": "boolean", +// "description": "If true, the breakpoint could be set (but not +// necessarily at the desired location." +// }, +// "message": { +// "type": "string", +// "description": "A message about the state of the breakpoint. +// This is shown to the user and can be used to explain why a breakpoint +// could not be verified." +// }, +// "source": { +// "type": "Source", +// "description": "The source where the breakpoint is located." +// }, +// "line": { +// "type": "number", +// "description": "The start line of the actual range covered by the +// breakpoint." +// }, +// "column": { +// "type": "number", +// "description": "The start column of the actual range covered by the +// breakpoint." +// }, +// "endLine": { +// "type": "number", +// "description": "The end line of the actual range covered by the +// breakpoint." +// }, +// "endColumn": { +// "type": "number", +// "description": "The end column of the actual range covered by the +// breakpoint. If no end line is given, then the end column is assumed to +// be in the start line." +// }, +// "instructionReference": { +// "type": "string", +// "description": "A memory reference to where the breakpoint is set." +// }, +// "offset": { +// "type": "number", +// "description": "The offset from the instruction reference. +// This can be negative." +// }, +// }, +// "required": [ "id", "verified", "line"] +// } +llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp) { + llvm::json::Object object; + ibp->CreateJsonObject(object); + return llvm::json::Value(std::move(object)); +} + // "Thread": { // "type": "object", // "description": "A Thread", @@ -893,7 +957,13 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, body.try_emplace("reason", "exception"); EmplaceSafeString(body, "description", exc_bp->label); } else { - body.try_emplace("reason", "breakpoint"); + InstructionBreakpoint *inst_bp = + g_dap.GetInstructionBPFromStopReason(thread); + if (inst_bp) { + body.try_emplace("reason", "instruction breakpoint"); + } else { + body.try_emplace("reason", "breakpoint"); + } lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(1); std::string desc_str = diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 1515f5ba2e5f4d..b6356630b72682 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -322,6 +322,17 @@ llvm::json::Value CreateSource(llvm::StringRef source_path); /// definition outlined by Microsoft. llvm::json::Value CreateStackFrame(lldb::SBFrame &frame); +/// Create a "instruction" object for a LLDB disassemble object as described in +/// the Visual Studio Code debug adaptor definition. +/// +/// \param[in] bp +/// The LLDB instruction object used to populate the disassembly +/// instruction. +/// \return +/// A "Scope" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp); + /// Create a "Thread" object for a LLDB thread object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 495ed0256120e8..c5c4b09f15622b 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -1723,6 +1723,8 @@ void request_initialize(const llvm::json::Object &request) { body.try_emplace("supportsLogPoints", true); // The debug adapter supports data watchpoints. body.try_emplace("supportsDataBreakpoints", true); + // The debug adapter support for instruction breakpoint. + body.try_emplace("supportsInstructionBreakpoints", true); // Put in non-DAP specification lldb specific information. llvm::json::Object lldb_json; @@ -4082,6 +4084,254 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) { g_dap.SendJSON(llvm::json::Value(std::move(response))); } +// "SetInstructionBreakpointsRequest" : { +// "allOf" : [ +// {"$ref" : "#/definitions/Request"}, { +// "type" : "object", +// "description" : +// "Replaces all existing instruction breakpoints. Typically, " +// "instruction breakpoints would be set from a disassembly window. " +// "\nTo clear all instruction breakpoints, specify an empty " +// "array.\nWhen an instruction breakpoint is hit, a `stopped` event " +// "(with reason `instruction breakpoint`) is generated.\nClients " +// "should only call this request if the corresponding capability " +// "`supportsInstructionBreakpoints` is true.", +// "properties" : { +// "command" : {"type" : "string", "enum" : +// ["setInstructionBreakpoints"]}, "arguments" : +// {"$ref" : "#/definitions/SetInstructionBreakpointsArguments"} +// }, +// "required" : [ "command", "arguments" ] +// } +// ] +// }, +// "SetInstructionBreakpointsArguments" +// : { +// "type" : "object", +// "description" : "Arguments for `setInstructionBreakpoints` request", +// "properties" : { +// "breakpoints" : { +// "type" : "array", +// "items" : {"$ref" : "#/definitions/InstructionBreakpoint"}, +// "description" : "The instruction references of the breakpoints" +// } +// }, +// "required" : ["breakpoints"] +// }, +// "SetInstructionBreakpointsResponse" +// : { +// "allOf" : [ +// {"$ref" : "#/definitions/Response"}, { +// "type" : "object", +// "description" : "Response to `setInstructionBreakpoints` request", +// "properties" : { +// "body" : { +// "type" : "object", +// "properties" : { +// "breakpoints" : { +// "type" : "array", +// "items" : {"$ref" : "#/definitions/Breakpoint"}, +// "description" : +// "Information about the breakpoints. The array elements +// " "correspond to the elements of the `breakpoints` +// array." +// } +// }, +// "required" : ["breakpoints"] +// } +// }, +// "required" : ["body"] +// } +// ] +// }, +// "InstructionBreakpoint" : { +// "type" : "object", +// "description" : "Properties of a breakpoint passed to the " +// "`setInstructionBreakpoints` request", +// "properties" : { +// "instructionReference" : { +// "type" : "string", +// "description" : +// "The instruction reference of the breakpoint.\nThis should be a " +// "memory or instruction pointer reference from an +// `EvaluateResponse`, " +// "`Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`." +// }, +// "offset" : { +// "type" : "integer", +// "description" : "The offset from the instruction reference in " +// "bytes.\nThis can be negative." +// }, +// "condition" : { +// "type" : "string", +// "description" : "An expression for conditional breakpoints.\nIt is only +// " +// "honored by a debug adapter if the corresponding " +// "capability `supportsConditionalBreakpoints` is true." +// }, +// "hitCondition" : { +// "type" : "string", +// "description" : "An expression that controls how many hits of the " +// "breakpoint are ignored.\nThe debug adapter is expected +// " "to interpret the expression as needed.\nThe +// attribute " "is only honored by a debug adapter if the +// corresponding " "capability +// `supportsHitConditionalBreakpoints` is true." +// }, +// "mode" : { +// "type" : "string", +// "description" : "The mode of this breakpoint. If defined, this must be +// " +// "one of the `breakpointModes` the debug adapter " +// "advertised in its `Capabilities`." +// } +// }, +// "required" : ["instructionReference"] +// }, +// "Breakpoint" +// : { +// "type" : "object", +// "description" : +// "Information about a breakpoint created in `setBreakpoints`, " +// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or " +// "`setDataBreakpoints` requests.", +// "properties" : { +// "id" : { +// "type" : "integer", +// "description" : +// "The identifier for the breakpoint. It is needed if breakpoint +// " "events are used to update or remove breakpoints." +// }, +// "verified" : { +// "type" : "boolean", +// "description" : "If true, the breakpoint could be set (but not " +// "necessarily at the desired location)." +// }, +// "message" : { +// "type" : "string", +// "description" : "A message about the state of the breakpoint.\nThis +// " +// "is shown to the user and can be used to explain +// why " "a breakpoint could not be verified." +// }, +// "source" : { +// "$ref" : "#/definitions/Source", +// "description" : "The source where the breakpoint is located." +// }, +// "line" : { +// "type" : "integer", +// "description" : +// "The start line of the actual range covered by the breakpoint." +// }, +// "column" : { +// "type" : "integer", +// "description" : +// "Start position of the source range covered by the breakpoint. +// " "It is measured in UTF-16 code units and the client +// capability " +// "`columnsStartAt1` determines whether it is 0- or 1-based." +// }, +// "endLine" : { +// "type" : "integer", +// "description" : +// "The end line of the actual range covered by the breakpoint." +// }, +// "endColumn" : { +// "type" : "integer", +// "description" : +// "End position of the source range covered by the breakpoint. It +// " "is measured in UTF-16 code units and the client capability " +// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf +// " "no end line is given, then the end column is assumed to be +// in " "the start line." +// }, +// "instructionReference" : { +// "type" : "string", +// "description" : "A memory reference to where the breakpoint is +// set." +// }, +// "offset" : { +// "type" : "integer", +// "description" : "The offset from the instruction reference.\nThis " +// "can be negative." +// }, +// "reason" : { +// "type" : "string", +// "description" : +// "A machine-readable explanation of why a breakpoint may not be +// " "verified. If a breakpoint is verified or a specific reason +// is " "not known, the adapter should omit this property. +// Possible " "values include:\n\n- `pending`: Indicates a +// breakpoint might be " "verified in the future, but the adapter +// cannot verify it in the " "current state.\n - `failed`: +// Indicates a breakpoint was not " "able to be verified, and the +// adapter does not believe it can be " "verified without +// intervention.", +// "enum" : [ "pending", "failed" ] +// } +// }, +// "required" : ["verified"] +// }, + +void request_setInstructionBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + llvm::json::Array response_breakpoints; + llvm::json::Object body; + FillResponse(request, response); + + auto arguments = request.getObject("arguments"); + auto breakpoints = arguments->getArray("breakpoints"); + + // It holds active instruction breakpoint list received from DAP. + InstructionBreakpointMap request_ibp; + if (breakpoints) { + for (const auto &bp : *breakpoints) { + auto bp_obj = bp.getAsObject(); + if (bp_obj) { + // Read instruction breakpoint request. + InstructionBreakpoint inst_bp(*bp_obj); + // Store them into map for reference. + request_ibp[inst_bp.instructionAddressReference] = std::move(inst_bp); + } + } + + // Iterate previous active instruction breakpoint list. + for (auto &prev_ibp : g_dap.instruction_breakpoints) { + // Find previous instruction breakpoint reference address in newly + // received instruction breakpoint list. + auto inst_reference = request_ibp.find(prev_ibp.first); + // Request for remove and delete the breakpoint, if the prev instruction + // breakpoint ID is not available in active instrcation breakpoint list. + // Means delete removed breakpoint instance. + if (inst_reference == request_ibp.end()) { + g_dap.target.BreakpointDelete(prev_ibp.second.id); + // Update Prev instruction breakpoint list. + g_dap.instruction_breakpoints.erase(prev_ibp.first); + } else { + // Instead of recreating breakpoint instance, update the breakpoint if + // there are any conditional changes. + prev_ibp.second.UpdateBreakpoint(inst_reference->second); + request_ibp.erase(inst_reference); + response_breakpoints.emplace_back( + CreateInstructionBreakpoint(&prev_ibp.second)); + } + } + + for (auto &req_bpi : request_ibp) { + // Add this breakpoint info to the response + g_dap.instruction_breakpoints[req_bpi.first] = std::move(req_bpi.second); + InstructionBreakpoint &new_bp = + g_dap.instruction_breakpoints[req_bpi.first]; + new_bp.SetInstructionBreakpoint(); + response_breakpoints.emplace_back(CreateInstructionBreakpoint(&new_bp)); + } + } + + body.try_emplace("breakpoints", std::move(response_breakpoints)); + response.try_emplace("body", std::move(body)); + g_dap.SendJSON(llvm::json::Value(std::move(response))); +} + void RegisterRequestCallbacks() { g_dap.RegisterRequestCallback("attach", request_attach); g_dap.RegisterRequestCallback("completions", request_completions); @@ -4114,6 +4364,9 @@ void RegisterRequestCallbacks() { g_dap.RegisterRequestCallback("threads", request_threads); g_dap.RegisterRequestCallback("variables", request_variables); g_dap.RegisterRequestCallback("disassemble", request_disassemble); + // Instruction breakpoint request + g_dap.RegisterRequestCallback("setInstructionBreakpoints", + request_setInstructionBreakpoints); // Custom requests g_dap.RegisterRequestCallback("compileUnits", request_compileUnits); g_dap.RegisterRequestCallback("modules", request_modules);