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 @@ -22,8 +22,7 @@ def set_source_breakpoints(self, source_path, lines, condition=None, hitCondition=None): '''Sets source breakpoints and returns an array of strings containing - the breakpoint location IDs ("1.1", "1.2") for each breakpoint - that was set. + the breakpoint IDs ("1", "2") for each breakpoint that was set. ''' response = self.vscode.request_setBreakpoints( source_path, lines, condition=condition, hitCondition=hitCondition) @@ -32,17 +31,14 @@ breakpoints = response['body']['breakpoints'] breakpoint_ids = [] for breakpoint in breakpoints: - response_id = breakpoint['id'] - bp_id = response_id >> 32 - bp_loc_id = response_id & 0xffffffff - breakpoint_ids.append('%i.%i' % (bp_id, bp_loc_id)) + breakpoint_ids.append('%i' % (breakpoint['id'])) return breakpoint_ids def set_function_breakpoints(self, functions, condition=None, hitCondition=None): '''Sets breakpoints by function name given an array of function names - and returns an array of strings containing the breakpoint location - IDs ("1.1", "1.2") for each breakpoint that was set. + and returns an array of strings containing the breakpoint IDs + ("1", "2") for each breakpoint that was set. ''' response = self.vscode.request_setFunctionBreakpoints( functions, condition=condition, hitCondition=hitCondition) @@ -51,18 +47,15 @@ breakpoints = response['body']['breakpoints'] breakpoint_ids = [] for breakpoint in breakpoints: - response_id = breakpoint['id'] - bp_id = response_id >> 32 - bp_loc_id = response_id & 0xffffffff - breakpoint_ids.append('%i.%i' % (bp_id, bp_loc_id)) + breakpoint_ids.append('%i' % (breakpoint['id'])) return breakpoint_ids def verify_breakpoint_hit(self, breakpoint_ids): '''Wait for the process we are debugging to stop, and verify we hit any breakpoint location in the "breakpoint_ids" array. - "breakpoint_ids" should be a list of breakpoint location ID strings - (["1.1", "2.1"]). The return value from - self.set_source_breakpoints() can be passed to this function''' + "breakpoint_ids" should be a list of breakpoint ID strings + (["1", "2"]). The return value from self.set_source_breakpoints() + or self.set_function_breakpoints() can be passed to this function''' stopped_events = self.vscode.wait_for_stopped() for stopped_event in stopped_events: if 'body' in stopped_event: @@ -73,14 +66,21 @@ continue if 'description' not in body: continue - # Description is "breakpoint 1.1", so look for any location id - # ("1.1") in the description field as verification that one of - # the breakpoint locations was hit + # Descriptions for breakpoints will be in the form + # "breakpoint 1.1", so look for any description that matches + # ("breakpoint 1.") in the description field as verification + # that one of the breakpoint locations was hit. VSCode doesn't + # allow breakpoints to have multiple locations, but LLDB does. + # So when looking at the description we just want to make sure + # the right breakpoint matches and not worry about the actual + # location. description = body['description'] + print("description: %s" % (description)) for breakpoint_id in breakpoint_ids: - if breakpoint_id in description: - return True - return False + match_desc = 'breakpoint %s.' % (breakpoint_id) + if match_desc in description: + return + self.assertTrue(False, "breakpoint not hit") def verify_exception_breakpoint_hit(self, filter_label): '''Wait for the process we are debugging to stop, and verify the stop 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 @@ -111,6 +111,7 @@ self.exit_status = None self.initialize_body = None self.thread_stop_reasons = {} + self.breakpoint_events = [] self.sequence = 1 self.threads = None self.recv_thread.start() @@ -186,7 +187,7 @@ self.output[category] = output self.output_condition.notify() self.output_condition.release() - # no need to add 'output' packets to our packets list + # no need to add 'output' event packets to our packets list return keepGoing elif event == 'process': # When a new process is attached or launched, remember the @@ -200,6 +201,13 @@ self._process_stopped() tid = body['threadId'] self.thread_stop_reasons[tid] = body + elif event == 'breakpoint': + # Breakpoint events come in when a breakpoint has locations + # added or removed. Keep track of them so we can look for them + # in tests. + self.breakpoint_events.append(packet) + # no need to add 'breakpoint' event packets to our packets list + return keepGoing elif packet_type == 'response': if packet['command'] == 'disconnect': keepGoing = False diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint-events/Makefile b/lldb/test/API/tools/lldb-vscode/breakpoint-events/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint-events/Makefile @@ -0,0 +1,4 @@ +DYLIB_NAME := unlikely_name +DYLIB_CXX_SOURCES := foo.cpp +CXX_SOURCES := main.cpp +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint-events/TestVSCode_breakpointEvents.py b/lldb/test/API/tools/lldb-vscode/breakpoint-events/TestVSCode_breakpointEvents.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint-events/TestVSCode_breakpointEvents.py @@ -0,0 +1,121 @@ +""" +Test lldb-vscode setBreakpoints request +""" + + +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_breakpointEvents(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows + def test_breakpoint_events(self): + ''' + This test sets a breakpoint in a shared library and runs and stops + at the entry point of a program. When we stop at the entry point, + the shared library won't be loaded yet. At this point the + breakpoint should set itself, but not be verified because no + locations are resolved. We will then continue and expect to get a + breakpoint event that informs us that the breakpoint in the shared + library is "changed" and the correct line number should be + supplied. We also set a breakpoint using a LLDB command using the + "preRunCommands" when launching our program. Any breapoints set via + the command interpreter should not be have breakpoint events sent + back to VS Code as the UI isn't able to add new breakpoints to + their UI. Code has been added that tags breakpoints set from VS Code + DAP packets so we know the IDE knows about them. If VS Code is ever + able to register breakpoints that aren't initially set in the GUI, + then we will need to revise this. + ''' + main_source_basename = 'main.cpp' + main_source_path = os.path.join(os.getcwd(), main_source_basename) + foo_source_basename = 'foo.cpp' + foo_source_path = os.path.join(os.getcwd(), foo_source_basename) + main_bp_line = line_number('main.cpp', 'main breakpoint 1') + foo_bp1_line = line_number('foo.cpp', 'foo breakpoint 1') + foo_bp2_line = line_number('foo.cpp', 'foo breakpoint 2') + + # Visual Studio Code Debug Adaptors have no way to specify the file + # without launching or attaching to a process, so we must start a + # process in order to be able to set breakpoints. + program = self.getBuildArtifact("a.out") + + # Set a breakpoint after creating the target by running a command line + # command. It will eventually resolve and cause a breakpoint changed + # event to be sent to lldb-vscode. We want to make sure we don't send a + # breakpoint any breakpoints that were set from the command line. + # Breakpoints that are set via the VS code DAP packets will be + # registered and marked with a special keyword to ensure we deliver + # breakpoint events for these breakpoints but not for ones that are not + # set via the command interpreter. + bp_command = 'breakpoint set --file foo.cpp --line %u' % (foo_bp2_line) + self.build_and_launch(program, stopOnEntry=True, + preRunCommands=[bp_command]) + main_bp_id = 0 + foo_bp_id = 0 + # Set breakoints and verify that they got set correctly + vscode_breakpoint_ids = [] + response = self.vscode.request_setBreakpoints(main_source_path, + [main_bp_line]) + if response: + breakpoints = response['body']['breakpoints'] + for breakpoint in breakpoints: + main_bp_id = breakpoint['id'] + vscode_breakpoint_ids.append("%i" % (main_bp_id)) + # line = breakpoint['line'] + self.assertTrue(breakpoint['verified'], + "expect main breakpoint to be verified") + + response = self.vscode.request_setBreakpoints(foo_source_path, + [foo_bp1_line]) + if response: + breakpoints = response['body']['breakpoints'] + for breakpoint in breakpoints: + foo_bp_id = breakpoint['id'] + vscode_breakpoint_ids.append("%i" % (foo_bp_id)) + self.assertFalse(breakpoint['verified'], + "expect foo breakpoint to not be verified") + + # Get the stop at the entry point + self.continue_to_next_stop() + + # We are now stopped at the entry point to the program. Shared + # libraries are not loaded yet (at least on macOS they aren't) and any + # breakpoints set in foo.cpp should not be resolved. + self.assertTrue(len(self.vscode.breakpoint_events) == 0, + "no breakpoint events when stopped at entry point") + + # Continue to the breakpoint + self.continue_to_breakpoints(vscode_breakpoint_ids) + + # Make sure we only get an event for the breakpoint we set via a call + # to self.vscode.request_setBreakpoints(...), not the breakpoint + # we set with with a LLDB command in preRunCommands. + self.assertTrue(len(self.vscode.breakpoint_events) == 1, + "make sure we got a breakpoint event") + event = self.vscode.breakpoint_events[0] + # Verify the details of the breakpoint changed notification. + body = event['body'] + self.assertTrue(body['reason'] == 'changed', + "breakpoint event is says breakpoint is changed") + breakpoint = body['breakpoint'] + self.assertTrue(breakpoint['verified'] == True, + "breakpoint event is says it is verified") + self.assertTrue(breakpoint['id'] == foo_bp_id, + "breakpoint event is for breakpoint %i" % (foo_bp_id)) + self.assertTrue('line' in breakpoint and breakpoint['line'] > 0, + "breakpoint event is has a line number") + self.assertTrue("foo.cpp" in breakpoint['source']['path'], + "breakpoint event path contains foo.cpp") + + output = self.get_console() # REMOVE PRIOR TO CHECKIN + with open("/tmp/b", "w") as f: + f.write(output) diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint-events/foo.h b/lldb/test/API/tools/lldb-vscode/breakpoint-events/foo.h new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint-events/foo.h @@ -0,0 +1,2 @@ + +int foo(int); diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint-events/foo.cpp b/lldb/test/API/tools/lldb-vscode/breakpoint-events/foo.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint-events/foo.cpp @@ -0,0 +1,11 @@ +#include + +static void unique_function_name() { + puts(__PRETTY_FUNCTION__); // foo breakpoint 2 +} + +int foo(int x) { + // foo breakpoint 1 + unique_function_name(); + return x+42; +} diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint-events/main.cpp b/lldb/test/API/tools/lldb-vscode/breakpoint-events/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint-events/main.cpp @@ -0,0 +1,7 @@ + +#include "foo.h" + +int main(int argc, char const *argv[]) { + int f = foo(argc); + return 0; // main breakpoint 1 +} diff --git a/lldb/tools/lldb-vscode/BreakpointBase.h b/lldb/tools/lldb-vscode/BreakpointBase.h --- a/lldb/tools/lldb-vscode/BreakpointBase.h +++ b/lldb/tools/lldb-vscode/BreakpointBase.h @@ -15,7 +15,7 @@ #include namespace lldb_vscode { - + struct BreakpointBase { // An optional expression for conditional breakpoints. @@ -36,6 +36,7 @@ void SetCondition(); void SetHitCondition(); void UpdateBreakpoint(const BreakpointBase &request_bp); + static const char *GetBreakpointLabel(); }; } // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/BreakpointBase.cpp b/lldb/tools/lldb-vscode/BreakpointBase.cpp --- a/lldb/tools/lldb-vscode/BreakpointBase.cpp +++ b/lldb/tools/lldb-vscode/BreakpointBase.cpp @@ -18,7 +18,7 @@ void BreakpointBase::SetCondition() { bp.SetCondition(condition.c_str()); } -void BreakpointBase::SetHitCondition() { +void BreakpointBase::SetHitCondition() { uint64_t hitCount = 0; if (llvm::to_integer(hitCondition, hitCount)) bp.SetIgnoreCount(hitCount - 1); @@ -34,3 +34,19 @@ SetHitCondition(); } } + +const char *BreakpointBase::GetBreakpointLabel() { + // Breakpoints in LLDB can have names added to them which are kind of like + // labels or categories. All breakpoints that are set through the IDE UI get + // sent through the various VS code DAP set*Breakpoint packets, and these + // breakpoints will be labeled with this name so if breakpoint update events + // come in for breakpoints that the IDE doesn't know about, like if a + // breakpoint is set manually using the debugger console, we won't report any + // updates on them and confused the IDE. This function gets called by all of + // the breakpoint classes after they set breakpoints to mark a breakpoint as + // a UI breakpoint. We can later check a lldb::SBBreakpoint object that comes + // in via LLDB breakpoint changed events and check the breakpoint by calling + // "bool lldb::SBBreakpoint::MatchesName(const char *)" to check if a + // breakpoint in one of the UI breakpoints that we should report changes for. + return "vscode"; +} diff --git a/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp b/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp --- a/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp +++ b/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "ExceptionBreakpoint.h" +#include "BreakpointBase.h" #include "VSCode.h" namespace lldb_vscode { @@ -18,6 +19,9 @@ bool throw_value = filter.find("_throw") != std::string::npos; bp = g_vsc.target.BreakpointCreateForException(language, catch_value, throw_value); + // See comments in BreakpointBase::GetBreakpointLabel() for details of why + // we add a label to our breakpoints. + bp.AddName(BreakpointBase::GetBreakpointLabel()); } void ExceptionBreakpoint::ClearBreakpoint() { @@ -28,4 +32,3 @@ } } // namespace lldb_vscode - diff --git a/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp b/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp --- a/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp +++ b/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp @@ -18,6 +18,9 @@ if (functionName.empty()) return; bp = g_vsc.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()) diff --git a/lldb/tools/lldb-vscode/JSONUtils.h b/lldb/tools/lldb-vscode/JSONUtils.h --- a/lldb/tools/lldb-vscode/JSONUtils.h +++ b/lldb/tools/lldb-vscode/JSONUtils.h @@ -199,13 +199,13 @@ /// Converts breakpoint location to a Visual Studio Code "Breakpoint" /// JSON object and appends it to the \a breakpoints array. /// -/// \param[in] bp_loc -/// A LLDB breakpoint location object to convert into a JSON value +/// \param[in] bp +/// A LLDB breakpoint object to convert into a JSON value /// /// \return /// A "Breakpoint" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateBreakpoint(lldb::SBBreakpointLocation &bp_loc); +llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp); /// Create a "Event" JSON object using \a event_name as the event name /// diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp --- a/lldb/tools/lldb-vscode/JSONUtils.cpp +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -281,16 +281,33 @@ // }, // "required": [ "verified" ] // } -llvm::json::Value CreateBreakpoint(lldb::SBBreakpointLocation &bp_loc) { +llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp) { // 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_loc.IsValid()) + if (!bp.IsValid()) return llvm::json::Value(std::move(object)); - object.try_emplace("verified", true); - const auto vs_id = MakeVSCodeBreakpointID(bp_loc); - object.try_emplace("id", vs_id); + 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; ilabel); } else { body.try_emplace("reason", "breakpoint"); + char desc_str[64]; + uint64_t bp_id = thread.GetStopReasonDataAtIndex(0); + uint64_t bp_loc_id = thread.GetStopReasonDataAtIndex(1); + snprintf(desc_str, sizeof(desc_str), "breakpoint %" PRIu64 ".%" PRIu64, + bp_id, bp_loc_id); + EmplaceSafeString(body, "description", desc_str); } } break; case lldb::eStopReasonWatchpoint: @@ -870,4 +885,3 @@ } } // namespace lldb_vscode - diff --git a/lldb/tools/lldb-vscode/LLDBUtils.h b/lldb/tools/lldb-vscode/LLDBUtils.h --- a/lldb/tools/lldb-vscode/LLDBUtils.h +++ b/lldb/tools/lldb-vscode/LLDBUtils.h @@ -106,46 +106,6 @@ /// The LLDB frame index ID. uint32_t GetLLDBFrameID(uint64_t dap_frame_id); -/// Given a LLDB breakpoint, make a breakpoint ID that is unique to a -/// specific breakpoint and breakpoint location. -/// -/// VSCode requires a Breakpoint "id" to be unique, so we use the -/// breakpoint ID in the lower BREAKPOINT_ID_SHIFT bits and the -/// breakpoint location ID in the upper BREAKPOINT_ID_SHIFT bits. -/// -/// \param[in] bp_loc -/// The LLDB break point location. -/// -/// \return -/// A unique integer that allows us to easily find the right -/// stack frame within a thread on subsequent VS code requests. -int64_t MakeVSCodeBreakpointID(lldb::SBBreakpointLocation &bp_loc); - -/// Given a VSCode breakpoint ID, convert to a LLDB breakpoint ID. -/// -/// VSCode requires a Breakpoint "id" to be unique, so we use the -/// breakpoint ID in the lower BREAKPOINT_ID_SHIFT bits and the -/// breakpoint location ID in the upper BREAKPOINT_ID_SHIFT bits. -/// -/// \param[in] dap_breakpoint_id -/// The VSCode breakpoint ID to convert to an LLDB breakpoint ID. -/// -/// \return -/// The LLDB breakpoint ID. -uint32_t GetLLDBBreakpointID(uint64_t dap_breakpoint_id); - -/// Given a VSCode breakpoint ID, convert to a LLDB breakpoint location ID. -/// -/// VSCode requires a Breakpoint "id" to be unique, so we use the -/// breakpoint ID in the lower BREAKPOINT_ID_SHIFT bits and the -/// breakpoint location ID in the upper BREAKPOINT_ID_SHIFT bits. -/// -/// \param[in] dap_breakpoint_id -/// The VSCode frame ID to convert to a breakpoint location ID. -/// -/// \return -/// The LLDB breakpoint location ID. -uint32_t GetLLDBBreakpointLocationID(uint64_t dap_breakpoint_id); } // namespace lldb_vscode #endif diff --git a/lldb/tools/lldb-vscode/LLDBUtils.cpp b/lldb/tools/lldb-vscode/LLDBUtils.cpp --- a/lldb/tools/lldb-vscode/LLDBUtils.cpp +++ b/lldb/tools/lldb-vscode/LLDBUtils.cpp @@ -79,19 +79,4 @@ frame.GetFrameID()); } -static uint32_t constexpr BREAKPOINT_ID_SHIFT = 22; - -uint32_t GetLLDBBreakpointID(uint64_t dap_breakpoint_id) { - return dap_breakpoint_id >> BREAKPOINT_ID_SHIFT; -} - -uint32_t GetLLDBBreakpointLocationID(uint64_t dap_breakpoint_id) { - return dap_breakpoint_id & ((1u << BREAKPOINT_ID_SHIFT) - 1); -} - -int64_t MakeVSCodeBreakpointID(lldb::SBBreakpointLocation &bp_loc) { - return (int64_t)(bp_loc.GetBreakpoint().GetID() << BREAKPOINT_ID_SHIFT | - bp_loc.GetID()); -} - } // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/SourceBreakpoint.cpp b/lldb/tools/lldb-vscode/SourceBreakpoint.cpp --- a/lldb/tools/lldb-vscode/SourceBreakpoint.cpp +++ b/lldb/tools/lldb-vscode/SourceBreakpoint.cpp @@ -17,6 +17,9 @@ void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) { bp = g_vsc.target.BreakpointCreateByLocation(source_path.str().c_str(), line); + // 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()) 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 @@ -379,27 +379,20 @@ if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { auto event_type = lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); - const auto num_locs = - lldb::SBBreakpoint::GetNumBreakpointLocationsFromEvent(event); auto bp = lldb::SBBreakpoint::GetBreakpointFromEvent(event); - bool added = event_type & lldb::eBreakpointEventTypeLocationsAdded; - bool removed = - event_type & lldb::eBreakpointEventTypeLocationsRemoved; - if (added || removed) { - for (size_t i = 0; i < num_locs; ++i) { - auto bp_loc = - lldb::SBBreakpoint::GetBreakpointLocationAtIndexFromEvent( - event, i); - auto bp_event = CreateEventObject("breakpoint"); - llvm::json::Object body; - body.try_emplace("breakpoint", CreateBreakpoint(bp_loc)); - if (added) - body.try_emplace("reason", "new"); - else - body.try_emplace("reason", "removed"); - bp_event.try_emplace("body", std::move(body)); - g_vsc.SendJSON(llvm::json::Value(std::move(bp_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 + // ins't going away, so we the reason is always "changed". + if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || + event_type & lldb::eBreakpointEventTypeLocationsRemoved) && + bp.MatchesName(BreakpointBase::GetBreakpointLabel())) { + auto bp_event = CreateEventObject("breakpoint"); + llvm::json::Object body; + body.try_emplace("breakpoint", CreateBreakpoint(bp)); + body.try_emplace("reason", "changed"); + bp_event.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(bp_event))); } } } else if (event.BroadcasterMatchesRef(g_vsc.broadcaster)) {