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);