Author: John Harrison Date: 2026-02-03T08:43:41-08:00 New Revision: 51e5b6c6acc01a41581ac5631edcc7fc974310b5
URL: https://github.com/llvm/llvm-project/commit/51e5b6c6acc01a41581ac5631edcc7fc974310b5 DIFF: https://github.com/llvm/llvm-project/commit/51e5b6c6acc01a41581ac5631edcc7fc974310b5.diff LOG: [lldb-dap] Migrating 'stopped' event to structured types. (#176273) Updates the 'stopped' event to use structure types. Additionally, I adjusted the description to include the full `GetStopDescription` that can have more details. Added: lldb/test/API/tools/lldb-dap/stopped-events/Makefile lldb/test/API/tools/lldb-dap/stopped-events/TestDAP_stopped_events.py lldb/test/API/tools/lldb-dap/stopped-events/main.cpp lldb/unittests/DAP/ProtocolEventsTest.cpp Modified: lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py lldb/tools/lldb-dap/EventHelper.cpp lldb/tools/lldb-dap/JSONUtils.cpp lldb/tools/lldb-dap/JSONUtils.h lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp lldb/tools/lldb-dap/Protocol/ProtocolEvents.h lldb/tools/lldb-dap/SBAPIExtras.h lldb/unittests/DAP/CMakeLists.txt Removed: ################################################################################ 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 14391db74302c..32e37c502e358 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 @@ -300,6 +300,7 @@ def __init__( self.stopped_thread: Optional[dict] = None self.thread_stacks: Optional[Dict[int, List[dict]]] self.thread_stop_reasons: Dict[str, Any] = {} + self.focused_tid: Optional[int] = None self.frame_scopes: Dict[str, Any] = {} # keyed by breakpoint id self.resolved_breakpoints: dict[int, Breakpoint] = {} @@ -539,6 +540,8 @@ def _handle_event(self, packet: Event) -> None: self._process_stopped() tid = body["threadId"] self.thread_stop_reasons[tid] = body + if "preserveFocusHint" not in body or not body["preserveFocusHint"]: + self.focused_tid = tid elif event.startswith("progress"): # Progress events come in as 'progressStart', 'progressUpdate', # and 'progressEnd' events. Keep these around in case test @@ -599,6 +602,7 @@ def _process_continued(self, all_threads_continued: bool): self.frame_scopes = {} if all_threads_continued: self.thread_stop_reasons = {} + self.focused_tid = None def _update_verified_breakpoints(self, breakpoints: List[Breakpoint]): for bp in breakpoints: @@ -1593,7 +1597,7 @@ def request_threads(self): tid = thread["id"] if tid in self.thread_stop_reasons: thread_stop_info = self.thread_stop_reasons[tid] - copy_keys = ["reason", "description", "text"] + copy_keys = ["reason", "description", "text", "hitBreakpointIds"] for key in copy_keys: if key in thread_stop_info: thread[key] = thread_stop_info[key] 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 d204e87a73acb..9f4780f5d9733 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 @@ -213,6 +213,7 @@ def verify_stop_exception_info( 'exception' with the description matching 'expected_description' and text match 'expected_text', if specified.""" stopped_events = self.dap_server.wait_for_stopped() + self.assertIsNotNone(stopped_events, "No stopped events detected") for stopped_event in stopped_events: body = stopped_event["body"] if body["reason"] != "exception": @@ -235,7 +236,7 @@ def verify_stop_exception_info( f"for stopped event {stopped_event!r}", ) return - self.fail(f"No valid stop exception info detected in {stopped_events}") + self.fail(f"No valid stop exception info detected in {stopped_events!r}") def verify_stop_on_entry(self) -> None: """Waits for the process to be stopped and then verifies at least one @@ -471,9 +472,11 @@ def continue_to_breakpoints(self, breakpoint_ids: List[int]): self.do_continue() self.verify_breakpoint_hit(breakpoint_ids) - def continue_to_exception_breakpoint(self, description, text=None): + def continue_to_exception_breakpoint( + self, expected_description, expected_text=None + ): self.do_continue() - self.verify_stop_exception_info(description, text) + self.verify_stop_exception_info(expected_description, expected_text) def continue_to_exit(self, exitCode=0): self.do_continue() diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py index 5ed7e13fd0b44..2aac9310cb133 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py @@ -35,5 +35,9 @@ def test_functionality(self): if response: self.assertTrue(response["success"]) - self.continue_to_exception_breakpoint(r"C\+\+ Throw") - self.continue_to_exception_breakpoint(r"C\+\+ Catch") + self.continue_to_exception_breakpoint( + expected_description=r"breakpoint 1\.1", expected_text=r"C\+\+ Throw" + ) + self.continue_to_exception_breakpoint( + expected_description=r"breakpoint 2\.1", expected_text=r"C\+\+ Catch" + ) diff --git a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py index 694cadb6ed2fe..89f96f428ec23 100644 --- a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py +++ b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py @@ -44,7 +44,10 @@ def test_break_on_throw_and_catch(self): if response: self.assertTrue(response["success"]) - self.continue_to_exception_breakpoint("Objective-C Throw") + self.continue_to_exception_breakpoint( + expected_description="hit Objective-C exception", + expected_text="Objective-C Throw", + ) # FIXME: Catching objc exceptions do not appear to be working. # Xcode appears to set a breakpoint on '__cxa_begin_catch' for objc diff --git a/lldb/test/API/tools/lldb-dap/stopped-events/Makefile b/lldb/test/API/tools/lldb-dap/stopped-events/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stopped-events/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/stopped-events/TestDAP_stopped_events.py b/lldb/test/API/tools/lldb-dap/stopped-events/TestDAP_stopped_events.py new file mode 100644 index 0000000000000..03dff39c82ed9 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stopped-events/TestDAP_stopped_events.py @@ -0,0 +1,141 @@ +""" +Test lldb-dap 'stopped' events. +""" + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbdap_testcase + + +class TestDAP_stopped_events(lldbdap_testcase.DAPTestCaseBase): + """ + Test validates diff erent operations that produce 'stopped' events. + """ + + ANY_THREAD = {} + + def matches(self, a: dict, b: dict) -> bool: + """Returns true if 'a' is a subset of 'b', otherwise false.""" + return a | b == a + + def verify_threads(self, expected_threads): + threads_resp = self.dap_server.request_threads() + self.assertTrue(threads_resp["success"]) + threads = threads_resp["body"]["threads"] + self.assertEqual(len(threads), len(expected_threads)) + for idx, expected_thread in enumerate(expected_threads): + thread = threads[idx] + self.assertTrue( + self.matches(thread, expected_thread), + f"Invalid thread state in {threads_resp}", + ) + + @expectedFailureAll( + oslist=["freebsd"], + bugnumber="llvm.org/pr18190 thread states not properly maintained", + ) + @expectedFailureNetBSD + @skipIfWindows # This is flakey on Windows: llvm.org/pr24668, llvm.org/pr38373 + def test_multiple_threads_sample_breakpoint(self): + """ + Test that multiple threads being stopped on the same breakpoint only produces a single 'stopped' event. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + line = line_number("main.cpp", "breakpoint") + [bp] = self.set_source_breakpoints("main.cpp", [line]) + + events = self.continue_to_next_stop() + self.assertEqual(len(events), 2, "Expected exactly two 'stopped' events") + for event in events: + body = event["body"] + self.assertEqual(body["reason"], "breakpoint") + self.assertEqual(body["text"], "breakpoint 1.1") + self.assertEqual(body["description"], "breakpoint 1.1") + self.assertEqual(body["hitBreakpointIds"], [int(bp)]) + self.assertIsNotNone(body["threadId"]) + + # We should have three threads, something along the lines of: + # + # Process 1234 stopped + # thread #1: tid = 0x01, 0x0a libsystem_pthread.dylib`pthread_mutex_lock + 12, queue = 'com.apple.main-thread' + # * thread #2: tid = 0x02, 0x0b a.out`add(a=1, b=2) at main.cpp:10:32, stop reason = breakpoint 1.1 + # thread #3: tid = 0x03, 0x0c a.out`add(a=4, b=5) at main.cpp:10:32, stop reason = breakpoint 1.1 + self.verify_threads( + [ + {}, + { + "reason": "breakpoint", + "text": "breakpoint 1.1", + "description": "breakpoint 1.1", + }, + { + "reason": "breakpoint", + "text": "breakpoint 1.1", + "description": "breakpoint 1.1", + }, + ] + ) + + self.assertEqual( + self.dap_server.threads[1]["id"], + self.dap_server.focused_tid, + "Expected thread#2 to be focused", + ) + + self.continue_to_exit() + + @expectedFailureAll( + oslist=["freebsd"], + bugnumber="llvm.org/pr18190 thread states not properly maintained", + ) + @expectedFailureNetBSD + @skipIfWindows # This is flakey on Windows: llvm.org/pr24668, llvm.org/pr38373 + def test_multiple_breakpoints_same_location(self): + """ + Test stopping at a location that reports multiple overlapping breakpoints. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + line_1 = line_number("main.cpp", "breakpoint") + [bp1] = self.set_source_breakpoints("main.cpp", [line_1]) + [bp2] = self.set_function_breakpoints(["my_add"]) + + events = self.continue_to_next_stop() + self.assertEqual(len(events), 2, "Expected two stopped events") + for event in events: + body = event["body"] + self.assertEqual(body["reason"], "breakpoint") + self.assertEqual(body["text"], "breakpoint 1.1 2.1") + self.assertEqual(body["description"], "breakpoint 1.1 2.1") + self.assertEqual(body["hitBreakpointIds"], [int(bp1), int(bp2)]) + self.assertIsNotNone(body["threadId"]) + + # Should return something like: + # Process 1234 stopped + # thread #1: tid = 0x01, 0x0a libsystem_pthread.dylib`pthread_mutex_lock + 12, queue = 'com.apple.main-thread' + # * thread #2: tid = 0x02, 0x0b a.out`add(a=1, b=2) at main.cpp:10:32, stop reason = breakpoint 1.1 2.1 + # thread #3: tid = 0x03, 0x0c a.out`add(a=4, b=5) at main.cpp:10:32, stop reason = breakpoint 1.1 2.1 + self.verify_threads( + [ + self.ANY_THREAD, + { + "reason": "breakpoint", + "description": "breakpoint 1.1 2.1", + "text": "breakpoint 1.1 2.1", + }, + { + "reason": "breakpoint", + "description": "breakpoint 1.1 2.1", + "text": "breakpoint 1.1 2.1", + }, + ] + ) + + self.assertEqual( + self.dap_server.threads[1]["id"], + self.dap_server.focused_tid, + "Expected thread#2 to be focused", + ) + + self.continue_to_exit() diff --git a/lldb/test/API/tools/lldb-dap/stopped-events/main.cpp b/lldb/test/API/tools/lldb-dap/stopped-events/main.cpp new file mode 100644 index 0000000000000..4ad66cac33b08 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stopped-events/main.cpp @@ -0,0 +1,29 @@ +#include "pseudo_barrier.h" +#include <thread> + +pseudo_barrier_t g_barrier; + +static int my_add(int a, int b) { // breakpoint + return a + b; +} + +int main(int argc, char const *argv[]) { + // Don't let either thread do anything until they're both ready. + pseudo_barrier_init(g_barrier, 2); + + std::thread t1([] { + // Wait until both threads are running + pseudo_barrier_wait(g_barrier); + my_add(1, 2); + }); + std::thread t2([] { + // Wait until both threads are running + pseudo_barrier_wait(g_barrier); + my_add(4, 5); + }); + + t1.join(); + t2.join(); + + return 0; +} diff --git a/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py b/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py index acd6108853787..be6dd84ec4d44 100644 --- a/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py +++ b/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py @@ -39,8 +39,7 @@ def test_correct_thread(self): "breakpoint %s." % breakpoint_ids[0] ) ) - self.assertFalse(stopped_event[0]["body"]["preserveFocusHint"]) - self.assertTrue(stopped_event[0]["body"]["threadCausedFocus"]) + self.assertNotIn("preserveFocusHint", stopped_event[0]["body"]) # All threads should be named Thread {index} threads = self.dap_server.get_threads() self.assertTrue(all(len(t["name"]) > 0 for t in threads)) diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index cc34b30f4244c..dbf4823408b11 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -20,14 +20,20 @@ #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "ProtocolUtils.h" +#include "SBAPIExtras.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBPlatform.h" #include "lldb/API/SBStream.h" +#include "lldb/API/SBThread.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-types.h" #include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" #include <mutex> #include <utility> @@ -172,8 +178,79 @@ void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { dap.SendJSON(llvm::json::Value(std::move(event))); } -// Send a thread stopped event for all threads as long as the process -// is stopped. +static void SendStoppedEvent(DAP &dap, lldb::SBThread &thread, bool on_entry, + bool all_threads_stopped, bool preserve_focus) { + protocol::StoppedEventBody body; + if (on_entry) { + body.reason = protocol::eStoppedReasonEntry; + } else { + switch (thread.GetStopReason()) { + case lldb::eStopReasonTrace: + case lldb::eStopReasonPlanComplete: + case lldb::eStopReasonProcessorTrace: + case lldb::eStopReasonHistoryBoundary: + body.reason = protocol::eStoppedReasonStep; + break; + case lldb::eStopReasonBreakpoint: { + ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); + if (exc_bp) { + body.reason = protocol::eStoppedReasonException; + body.text = exc_bp->GetLabel(); + } else { + InstructionBreakpoint *inst_bp = + dap.GetInstructionBPFromStopReason(thread); + body.reason = inst_bp ? protocol::eStoppedReasonInstructionBreakpoint + : protocol::eStoppedReasonBreakpoint; + + llvm::raw_string_ostream OS(body.text); + OS << "breakpoint"; + for (size_t idx = 0; idx < thread.GetStopReasonDataCount(); idx += 2) { + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx); + lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(idx + 1); + body.hitBreakpointIds.push_back(bp_id); + OS << " " << bp_id << "." << bp_loc_id; + } + } + } break; + case lldb::eStopReasonWatchpoint: { + body.reason = protocol::eStoppedReasonDataBreakpoint; + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); + body.hitBreakpointIds.push_back(bp_id); + body.text = llvm::formatv("data breakpoint {0}", bp_id).str(); + } break; + case lldb::eStopReasonSignal: + case lldb::eStopReasonException: + case lldb::eStopReasonInstrumentation: + body.reason = protocol::eStoppedReasonException; + break; + case lldb::eStopReasonExec: + case lldb::eStopReasonFork: + case lldb::eStopReasonVFork: + case lldb::eStopReasonVForkDone: + body.reason = protocol::eStoppedReasonEntry; + break; + case lldb::eStopReasonInterrupt: + body.reason = protocol::eStoppedReasonPause; + break; + case lldb::eStopReasonThreadExiting: + case lldb::eStopReasonInvalid: + case lldb::eStopReasonNone: + return; + } + } + lldb::tid_t tid = thread.GetThreadID(); + lldb::SBStream description; + thread.GetStopDescription(description); + body.description = {description.GetData(), description.GetSize()}; + body.threadId = tid; + body.allThreadsStopped = all_threads_stopped; + body.preserveFocusHint = preserve_focus; + + dap.Send(protocol::Event{"stopped", std::move(body)}); +} + +// Send a thread stopped event for the first stopped thread as the process is +// stopped. llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { lldb::SBMutex lock = dap.GetAPIMutex(); std::lock_guard<lldb::SBMutex> guard(lock); @@ -188,63 +265,38 @@ llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { llvm::DenseSet<lldb::tid_t> old_thread_ids; old_thread_ids.swap(dap.thread_ids); - uint32_t stop_id = on_entry ? 0 : process.GetStopID(); - const uint32_t num_threads = process.GetNumThreads(); - - // First make a pass through the threads to see if the focused thread - // has a stop reason. In case the focus thread doesn't have a stop - // reason, remember the first thread that has a stop reason so we can - // set it as the focus thread if below if needed. - lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID; - uint32_t num_threads_with_reason = 0; - bool focus_thread_exists = false; - for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { - lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - const lldb::tid_t tid = thread.GetThreadID(); - const bool has_reason = ThreadHasStopReason(thread); - // If the focus thread doesn't have a stop reason, clear the thread ID - if (tid == dap.focus_tid) { - focus_thread_exists = true; - if (!has_reason) - dap.focus_tid = LLDB_INVALID_THREAD_ID; - } - if (has_reason) { - ++num_threads_with_reason; - if (first_tid_with_reason == LLDB_INVALID_THREAD_ID) - first_tid_with_reason = tid; - } - } - // We will have cleared dap.focus_tid if the focus thread doesn't have - // a stop reason, so if it was cleared, or wasn't set, or doesn't exist, - // then set the focus thread to the first thread with a stop reason. - if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID) - dap.focus_tid = first_tid_with_reason; - - // If no threads stopped with a reason, then report the first one so - // we at least let the UI know we stopped. - if (num_threads_with_reason == 0) { - lldb::SBThread thread = process.GetThreadAtIndex(0); - dap.focus_tid = thread.GetThreadID(); - dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); - } else { - for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { - lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - dap.thread_ids.insert(thread.GetThreadID()); - if (ThreadHasStopReason(thread)) { - dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); - } - } + lldb::tid_t focused_tid = LLDB_INVALID_THREAD_ID; + for (auto thread : process) { + // Collect all known thread ids for sending thread events. + dap.thread_ids.insert(thread.GetThreadID()); + + if (!ThreadHasStopReason(thread)) + continue; + + // When we stop, report allThreadsStopped for the *first* stopped thread to + // ensure the list of stopped threads is up to date. + bool first_stop = focused_tid == LLDB_INVALID_THREAD_ID; + SendStoppedEvent(dap, thread, on_entry, /*all_threads_stopped=*/first_stop, + /*preserve_focus=*/!first_stop); + + // Default focus to the first stopped thread. + if (focused_tid == LLDB_INVALID_THREAD_ID) + focused_tid = thread.GetThreadID(); } - for (const auto &tid : old_thread_ids) { - auto end = dap.thread_ids.end(); - auto pos = dap.thread_ids.find(tid); - if (pos == end) + if (focused_tid == LLDB_INVALID_THREAD_ID) + return make_error<DAPError>("no stopped threads"); + + // Update focused thread. + dap.focus_tid = focused_tid; + + for (const auto &tid : old_thread_ids) + if (!dap.thread_ids.contains(tid)) SendThreadExitedEvent(dap, tid); - } dap.RunStopCommands(); + return Error::success(); } diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 9c32e3fac64ae..79925b4bb37d3 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -345,163 +345,6 @@ llvm::json::Object CreateEventObject(const llvm::StringRef event_name) { return event; } -// "StoppedEvent": { -// "allOf": [ { "$ref": "#/definitions/Event" }, { -// "type": "object", -// "description": "Event message for 'stopped' event type. The event -// indicates that the execution of the debuggee has stopped -// due to some condition. This can be caused by a break -// point previously set, a stepping action has completed, -// by executing a debugger statement etc.", -// "properties": { -// "event": { -// "type": "string", -// "enum": [ "stopped" ] -// }, -// "body": { -// "type": "object", -// "properties": { -// "reason": { -// "type": "string", -// "description": "The reason for the event. For backward -// compatibility this string is shown in the UI if -// the 'description' attribute is missing (but it -// must not be translated).", -// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ] -// }, -// "description": { -// "type": "string", -// "description": "The full reason for the event, e.g. 'Paused -// on exception'. This string is shown in the UI -// as is." -// }, -// "threadId": { -// "type": "integer", -// "description": "The thread which was stopped." -// }, -// "text": { -// "type": "string", -// "description": "Additional information. E.g. if reason is -// 'exception', text contains the exception name. -// This string is shown in the UI." -// }, -// "allThreadsStopped": { -// "type": "boolean", -// "description": "If allThreadsStopped is true, a debug adapter -// can announce that all threads have stopped. -// The client should use this information to -// enable that all threads can be expanded to -// access their stacktraces. If the attribute -// is missing or false, only the thread with the -// given threadId can be expanded." -// } -// }, -// "required": [ "reason" ] -// } -// }, -// "required": [ "event", "body" ] -// }] -// } -llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, - uint32_t stop_id) { - llvm::json::Object event(CreateEventObject("stopped")); - llvm::json::Object body; - switch (thread.GetStopReason()) { - case lldb::eStopReasonTrace: - case lldb::eStopReasonPlanComplete: - body.try_emplace("reason", "step"); - break; - case lldb::eStopReasonBreakpoint: { - ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); - if (exc_bp) { - body.try_emplace("reason", "exception"); - EmplaceSafeString(body, "description", exc_bp->GetLabel()); - } else { - InstructionBreakpoint *inst_bp = - dap.GetInstructionBPFromStopReason(thread); - if (inst_bp) { - body.try_emplace("reason", "instruction breakpoint"); - } else { - body.try_emplace("reason", "breakpoint"); - } - std::vector<lldb::break_id_t> bp_ids; - std::ostringstream desc_sstream; - desc_sstream << "breakpoint"; - for (size_t idx = 0; idx < thread.GetStopReasonDataCount(); idx += 2) { - lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx); - lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(idx + 1); - bp_ids.push_back(bp_id); - desc_sstream << " " << bp_id << "." << bp_loc_id; - } - std::string desc_str = desc_sstream.str(); - body.try_emplace("hitBreakpointIds", llvm::json::Array(bp_ids)); - EmplaceSafeString(body, "description", desc_str); - } - } break; - case lldb::eStopReasonWatchpoint: { - body.try_emplace("reason", "data breakpoint"); - lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); - body.try_emplace("hitBreakpointIds", - llvm::json::Array{llvm::json::Value(bp_id)}); - EmplaceSafeString(body, "description", - llvm::formatv("data breakpoint {0}", bp_id).str()); - } break; - case lldb::eStopReasonInstrumentation: - body.try_emplace("reason", "exception"); - break; - case lldb::eStopReasonProcessorTrace: - body.try_emplace("reason", "processor trace"); - break; - case lldb::eStopReasonHistoryBoundary: - body.try_emplace("reason", "history boundary"); - break; - case lldb::eStopReasonSignal: - case lldb::eStopReasonException: - body.try_emplace("reason", "exception"); - break; - case lldb::eStopReasonExec: - body.try_emplace("reason", "entry"); - break; - case lldb::eStopReasonFork: - body.try_emplace("reason", "fork"); - break; - case lldb::eStopReasonVFork: - body.try_emplace("reason", "vfork"); - break; - case lldb::eStopReasonVForkDone: - body.try_emplace("reason", "vforkdone"); - break; - case lldb::eStopReasonInterrupt: - body.try_emplace("reason", "async interrupt"); - break; - case lldb::eStopReasonThreadExiting: - case lldb::eStopReasonInvalid: - case lldb::eStopReasonNone: - break; - } - if (stop_id == 0) - body["reason"] = "entry"; - const lldb::tid_t tid = thread.GetThreadID(); - body.try_emplace("threadId", (int64_t)tid); - // If no description has been set, then set it to the default thread stopped - // description. If we have breakpoints that get hit and shouldn't be reported - // as breakpoints, then they will set the description above. - if (!ObjectContainsKey(body, "description")) { - char description[1024]; - if (thread.GetStopDescription(description, sizeof(description))) { - EmplaceSafeString(body, "description", description); - } - } - // "threadCausedFocus" is used in tests to validate breaking behavior. - if (tid == dap.focus_tid) { - body.try_emplace("threadCausedFocus", true); - } - body.try_emplace("preserveFocusHint", tid != dap.focus_tid); - body.try_emplace("allThreadsStopped", true); - event.try_emplace("body", std::move(body)); - return llvm::json::Value(std::move(event)); -} - llvm::StringRef GetNonNullVariableName(lldb::SBValue &v) { const llvm::StringRef name = v.GetName(); return !name.empty() ? name : "<null>"; diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 15449d6ece62a..c2ffa11eceb95 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -234,36 +234,6 @@ void FillResponse(const llvm::json::Object &request, /// definition outlined by Microsoft. llvm::json::Object CreateEventObject(const llvm::StringRef event_name); -/// Create a "StoppedEvent" object for a LLDB thread object. -/// -/// This function will fill in the following keys in the returned -/// object's "body" object: -/// "reason" - With a valid stop reason enumeration string value -/// that Microsoft specifies -/// "threadId" - The thread ID as an integer -/// "description" - a stop description (like "breakpoint 12.3") as a -/// string -/// "preserveFocusHint" - a boolean value that states if this thread -/// should keep the focus in the GUI. -/// "allThreadsStopped" - set to True to indicate that all threads -/// stop when any thread stops. -/// -/// \param[in] dap -/// The DAP session associated with the stopped thread. -/// -/// \param[in] thread -/// The LLDB thread to use when populating out the "StoppedEvent" -/// object. -/// -/// \param[in] stop_id -/// The stop id for this event. -/// -/// \return -/// A "StoppedEvent" JSON object with that follows the formal JSON -/// definition outlined by Microsoft. -llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, - uint32_t stop_id); - /// \return /// The variable name of \a value or a default placeholder. llvm::StringRef GetNonNullVariableName(lldb::SBValue &value); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp index df6be06637a13..b1985cbb7d053 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp @@ -8,6 +8,8 @@ #include "Protocol/ProtocolEvents.h" #include "JSONUtils.h" +#include "lldb/lldb-defines.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/JSON.h" using namespace llvm; @@ -64,4 +66,49 @@ llvm::json::Value toJSON(const MemoryEventBody &MEB) { {"count", MEB.count}}; } +static llvm::json::Value toJSON(const StoppedReason &SR) { + assert(SR != eStoppedReasonUninitialized && "StopReason Uninitialized"); + switch (SR) { + case eStoppedReasonUninitialized: + return ""; + case eStoppedReasonStep: + return "step"; + case eStoppedReasonBreakpoint: + return "breakpoint"; + case eStoppedReasonException: + return "exception"; + case eStoppedReasonPause: + return "pause"; + case eStoppedReasonEntry: + return "entry"; + case eStoppedReasonGoto: + return "goto"; + case eStoppedReasonFunctionBreakpoint: + return "function breakpoint"; + case eStoppedReasonDataBreakpoint: + return "data breakpoint"; + case eStoppedReasonInstructionBreakpoint: + return "instruction breakpoint"; + } +} + +llvm::json::Value toJSON(const StoppedEventBody &SEB) { + llvm::json::Object Result{{"reason", SEB.reason}}; + + if (!SEB.description.empty()) + Result.insert({"description", SEB.description}); + if (SEB.threadId != LLDB_INVALID_THREAD_ID) + Result.insert({"threadId", SEB.threadId}); + if (SEB.preserveFocusHint) + Result.insert({"preserveFocusHint", SEB.preserveFocusHint}); + if (!SEB.text.empty()) + Result.insert({"text", SEB.text}); + if (SEB.allThreadsStopped) + Result.insert({"allThreadsStopped", SEB.allThreadsStopped}); + if (!SEB.hitBreakpointIds.empty()) + Result.insert({"hitBreakpointIds", SEB.hitBreakpointIds}); + + return Result; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h index 5cd5a843d284e..5c415f76c37fd 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h @@ -117,6 +117,68 @@ struct MemoryEventBody { }; llvm::json::Value toJSON(const MemoryEventBody &); +enum StoppedReason : unsigned { + eStoppedReasonUninitialized, + eStoppedReasonStep, + eStoppedReasonBreakpoint, + eStoppedReasonException, + eStoppedReasonPause, + eStoppedReasonEntry, + eStoppedReasonGoto, + eStoppedReasonFunctionBreakpoint, + eStoppedReasonDataBreakpoint, + eStoppedReasonInstructionBreakpoint, +}; + +/// The event indicates that the execution of the debuggee has stopped due to +/// some condition. +/// +/// This can be caused by a breakpoint previously set, a stepping request has +/// completed, by executing a debugger statement etc. +struct StoppedEventBody { + /// The reason for the event. + /// + /// For backward compatibility this string is shown in the UI if the + /// `description` attribute is missing (but it must not be translated). + StoppedReason reason = eStoppedReasonUninitialized; + + /// The full reason for the event, e.g. 'Paused on exception'. This string is + /// shown in the UI as is and can be translated. + std::string description; + + /// The thread which was stopped. + lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; + + /// A value of true hints to the client that this event should not change the + /// focus. + bool preserveFocusHint = false; + + /// Additional information. E.g. if reason is `exception`, text contains the + /// exception name. This string is shown in the UI. + std::string text; + + /// "If `allThreadsStopped` is true, a debug adapter can announce that all + /// threads have stopped. + /// + /// - The client should use this information to enable that all threads can be + /// expanded to access their stacktraces. + /// - If the attribute is missing or false, only the thread with the given + /// `threadId` can be expanded. + bool allThreadsStopped = false; + + /// Ids of the breakpoints that triggered the event. In most cases there is + /// only a single breakpoint but here are some examples for multiple + /// breakpoints: + /// + /// - Different types of breakpoints map to the same location. + /// - Multiple source breakpoints get collapsed to the same instruction by the + /// compiler/runtime. + /// - Multiple function breakpoints with diff erent function names map to the + /// same location. + std::vector<lldb::break_id_t> hitBreakpointIds; +}; +llvm::json::Value toJSON(const StoppedEventBody &); + } // end namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/SBAPIExtras.h b/lldb/tools/lldb-dap/SBAPIExtras.h index 0745b2e043c21..eb59cb08ea4fd 100644 --- a/lldb/tools/lldb-dap/SBAPIExtras.h +++ b/lldb/tools/lldb-dap/SBAPIExtras.h @@ -8,6 +8,7 @@ // Extensions on SB API. //===----------------------------------------------------------------------===// +#include "lldb/API/SBProcess.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBThread.h" @@ -34,11 +35,19 @@ struct iter { bool operator!=(const iter &other) { return index != other.index; } }; +/// SBProcess thread iterator. +using process_thread_iter = + iter<SBProcess, SBThread, size_t, &SBProcess::GetThreadAtIndex>; +inline process_thread_iter begin(SBProcess P) { return {P, 0}; } +inline process_thread_iter end(SBProcess P) { return {P, P.GetNumThreads()}; } + /// SBThreadCollection thread iterator. -using thread_iter = iter<SBThreadCollection, SBThread, size_t, - &SBThreadCollection::GetThreadAtIndex>; -inline thread_iter begin(SBThreadCollection TC) { return {TC, 0}; } -inline thread_iter end(SBThreadCollection TC) { return {TC, TC.GetSize()}; } +using thread_collection_iter = iter<SBThreadCollection, SBThread, size_t, + &SBThreadCollection::GetThreadAtIndex>; +inline thread_collection_iter begin(SBThreadCollection TC) { return {TC, 0}; } +inline thread_collection_iter end(SBThreadCollection TC) { + return {TC, TC.GetSize()}; +} /// SBThread frame iterator. using frame_iter = diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt index 9fef37e00ed5d..97f9cad7477ed 100644 --- a/lldb/unittests/DAP/CMakeLists.txt +++ b/lldb/unittests/DAP/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_unittest(DAPTests Handler/ContinueTest.cpp JSONUtilsTest.cpp LLDBUtilsTest.cpp + ProtocolEventsTest.cpp ProtocolRequestsTest.cpp ProtocolTypesTest.cpp ProtocolUtilsTest.cpp diff --git a/lldb/unittests/DAP/ProtocolEventsTest.cpp b/lldb/unittests/DAP/ProtocolEventsTest.cpp new file mode 100644 index 0000000000000..b6efc2791e578 --- /dev/null +++ b/lldb/unittests/DAP/ProtocolEventsTest.cpp @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 "Protocol/ProtocolEvents.h" +#include "TestingSupport/TestUtilities.h" +#include "llvm/Testing/Support/Error.h" +#include <gtest/gtest.h> + +using namespace llvm; +using namespace lldb_dap::protocol; +using lldb_private::PrettyPrint; +using llvm::json::parse; +using llvm::json::Value; + +TEST(ProtocolEventsTest, StoppedEventBody) { + StoppedEventBody body; + body.reason = lldb_dap::protocol::eStoppedReasonBreakpoint; + Expected<Value> expected_body = parse(R"({ + "reason": "breakpoint" + })"); + ASSERT_THAT_EXPECTED(expected_body, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected_body), PrettyPrint(body)); + + body.reason = eStoppedReasonBreakpoint; + body.description = "desc"; + body.text = "text"; + body.preserveFocusHint = true; + body.allThreadsStopped = true; + body.hitBreakpointIds = {1, 2, 3}; + expected_body = parse(R"({ + "reason": "breakpoint", + "allThreadsStopped": true, + "description": "desc", + "text": "text", + "preserveFocusHint": true, + "hitBreakpointIds": [1, 2, 3] + })"); + ASSERT_THAT_EXPECTED(expected_body, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected_body), PrettyPrint(body)); +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
