https://github.com/ashgti created 
https://github.com/llvm/llvm-project/pull/176273

In the stopped event and the exceptionInfo request I've added additional 
information for crash data, instrumentation data and more detailed exception 
data.

For example, when UBSan is enabled, you now see additional information in the 
exception stack trace about the detected issue:

<img width="1728" height="538" alt="Screenshot 2026-01-15 at 3 05 08 PM" 
src="https://github.com/user-attachments/assets/f7d197b1-8f31-4470-bde1-b319d00a8a6e";
 />

I included a new test for stopping at `lldb::eStopReasonInstrumentation` and 
ensuring we have additional information reported.

>From fb2251b4aa81503659a3f02db8874a30fe4614ed Mon Sep 17 00:00:00 2001
From: John Harrison <[email protected]>
Date: Thu, 15 Jan 2026 15:48:07 -0800
Subject: [PATCH] [lldb-dap] Including more detailed exception information.

In the stopped event and the exceptionInfo request I've added additional 
information for crash data, instrumentation data and more detailed exception 
data.
---
 .../Python/lldbsuite/support/temp_file.py     |  18 +-
 .../Python/lldbsuite/test/decorators.py       |   1 +
 .../test/tools/lldb-dap/lldbdap_testcase.py   |  48 ++++--
 .../TestDAP_setExceptionBreakpoints.py        |   8 +-
 .../API/tools/lldb-dap/exception/Makefile     |   1 +
 .../lldb-dap/exception/TestDAP_exception.py   |  18 +-
 .../exception/cpp/TestDAP_exception_cpp.py    |   5 +-
 .../exception/objc/TestDAP_exception_objc.py  |  10 +-
 .../exception/runtime-instruments/Makefile    |   4 +
 .../TestDAP_runtime_instruments.py            |  25 +++
 .../exception/runtime-instruments/categories  |   1 +
 .../exception/runtime-instruments/main.c      |   5 +
 .../tools/lldb-dap/threads/TestDAP_threads.py |   3 +-
 lldb/tools/lldb-dap/EventHelper.cpp           | 123 +++++++++-----
 .../Handler/ExceptionInfoRequestHandler.cpp   |  88 +++++++---
 lldb/tools/lldb-dap/JSONUtils.cpp             | 157 ------------------
 lldb/tools/lldb-dap/JSONUtils.h               |  30 ----
 .../lldb-dap/Protocol/ProtocolEvents.cpp      |  47 ++++++
 lldb/tools/lldb-dap/Protocol/ProtocolEvents.h |  62 +++++++
 19 files changed, 369 insertions(+), 285 deletions(-)
 create mode 100644 
lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile
 create mode 100644 
lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py
 create mode 100644 
lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories
 create mode 100644 
lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c

diff --git a/lldb/packages/Python/lldbsuite/support/temp_file.py 
b/lldb/packages/Python/lldbsuite/support/temp_file.py
index a21e212d279d6..17fdd9f7d4eca 100644
--- a/lldb/packages/Python/lldbsuite/support/temp_file.py
+++ b/lldb/packages/Python/lldbsuite/support/temp_file.py
@@ -9,15 +9,25 @@
 
 
 class OnDiskTempFile:
-    def __init__(self, delete=True):
+    def __init__(self, delete=True, immediate=False):
         self.path = None
+        if immediate:
+            self._set_path()
 
-    def __enter__(self):
+    def __del__(self):
+        if self.path and os.path.exists(self.path):
+            os.remove(self.path)
+
+    def _set_path(self):
+        if self.path:
+            return
         fd, path = tempfile.mkstemp()
         os.close(fd)
         self.path = path
+
+    def __enter__(self):
+        self._set_path()
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
-        if os.path.exists(self.path):
-            os.remove(self.path)
+        pass
diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py 
b/lldb/packages/Python/lldbsuite/test/decorators.py
index a7df9fe63badc..b1516bf214a87 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -1114,6 +1114,7 @@ def is_compiler_with_bounds_safety():
 
     return skipTestIfFn(is_compiler_with_bounds_safety)(func)
 
+
 def skipIfAsan(func):
     """Skip this test if the environment is set up to run LLDB *itself* under 
ASAN."""
     return skipTestIfFn(is_running_under_asan)(func)
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 e4a3e1c786ffe..b2cac4dfb0e5b 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
@@ -208,25 +208,40 @@ def verify_all_breakpoints_hit(self, breakpoint_ids):
                     return
         self.assertTrue(False, f"breakpoints not hit, 
stopped_events={stopped_events}")
 
-    def verify_stop_exception_info(self, expected_description):
+    def verify_stop_exception_info(
+        self, expected_description: str, expected_text: Optional[str] = None
+    ) -> None:
         """Wait for the process we are debugging to stop, and verify the stop
         reason is 'exception' and that the description matches
         'expected_description'
         """
         stopped_events = self.dap_server.wait_for_stopped()
+        self.assertIsNotNone(stopped_events, "No stopped events detected")
         for stopped_event in stopped_events:
-            if "body" in stopped_event:
-                body = stopped_event["body"]
-                if "reason" not in body:
-                    continue
-                if body["reason"] != "exception":
-                    continue
-                if "description" not in body:
-                    continue
-                description = body["description"]
-                if expected_description == description:
-                    return True
-        return False
+            if (
+                "body" not in stopped_event
+                or stopped_event["body"]["reason"] != "exception"
+            ):
+                continue
+            self.assertIn(
+                "description",
+                stopped_event["body"],
+                f"stopped event missing description {stopped_event}",
+            )
+            description = stopped_event["body"]["description"]
+            self.assertRegex(
+                description,
+                expected_description,
+                f"for 'stopped' event {stopped_event!r}",
+            )
+            if expected_text:
+                self.assertRegex(
+                    stopped_event["body"]["text"],
+                    expected_text,
+                    f"for stopped event {stopped_event!r}",
+                )
+            return
+        self.fail(f"No valid stop exception info detected in {stopped_events}")
 
     def verify_stop_on_entry(self) -> None:
         """Waits for the process to be stopped and then verifies at least one
@@ -437,12 +452,9 @@ def continue_to_breakpoints(self, breakpoint_ids):
         self.do_continue()
         self.verify_breakpoint_hit(breakpoint_ids)
 
-    def continue_to_exception_breakpoint(self, filter_label):
+    def continue_to_exception_breakpoint(self, description, text=None):
         self.do_continue()
-        self.assertTrue(
-            self.verify_stop_exception_info(filter_label),
-            'verify we got "%s"' % (filter_label),
-        )
+        self.verify_stop_exception_info(description, 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 4ca733a9a59ca..684726c927bc1 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("C++ Throw")
-        self.continue_to_exception_breakpoint("C++ Catch")
+        self.continue_to_exception_breakpoint(
+            r"breakpoint \d+\.\d+", text=r"C\+\+ Throw"
+        )
+        self.continue_to_exception_breakpoint(
+            r"breakpoint \d+\.\d+", text=r"C\+\+ Catch"
+        )
diff --git a/lldb/test/API/tools/lldb-dap/exception/Makefile 
b/lldb/test/API/tools/lldb-dap/exception/Makefile
index 10495940055b6..b27db90a40de2 100644
--- a/lldb/test/API/tools/lldb-dap/exception/Makefile
+++ b/lldb/test/API/tools/lldb-dap/exception/Makefile
@@ -1,3 +1,4 @@
 C_SOURCES := main.c
+CFLAGS_EXTRAS := -fsanitize=undefined -g
 
 include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py 
b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
index f044bcae41892..0cb48f345474e 100644
--- a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
+++ b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
@@ -18,8 +18,24 @@ def test_stopped_description(self):
         self.build_and_launch(program)
         self.do_continue()
 
-        self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
+        self.verify_stop_exception_info("signal SIGABRT")
         exceptionInfo = self.get_exceptionInfo()
         self.assertEqual(exceptionInfo["breakMode"], "always")
         self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
         self.assertEqual(exceptionInfo["exceptionId"], "signal")
+
+    # @skipUnlessUndefinedBehaviorSanitizer
+    def test_ubsan(self):
+        """
+        Test that we stop at runtime instrumentation locations.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, args=["ubsan"])
+        self.do_continue()
+
+        self.verify_stop_exception_info("Out of bounds index")
+        exceptionInfo = self.get_exceptionInfo()
+        self.assertEqual(exceptionInfo["breakMode"], "always")
+        self.assertEqual("Out of bounds index", exceptionInfo["description"])
+        self.assertEqual(exceptionInfo["exceptionId"], 
"runtime-instrumentation")
+        self.assertIn("main.c", exceptionInfo["details"]["stackTrace"])
diff --git 
a/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py 
b/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
index 6471e2b87251a..4729cbef00c11 100644
--- a/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
+++ b/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py
@@ -2,7 +2,6 @@
 Test exception behavior in DAP with c++ throw.
 """
 
-
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
 import lldbdap_testcase
@@ -18,9 +17,9 @@ def test_stopped_description(self):
         program = self.getBuildArtifact("a.out")
         self.build_and_launch(program)
         self.dap_server.request_continue()
-        self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
+        self.verify_stop_exception_info("signal SIGABRT")
         exceptionInfo = self.get_exceptionInfo()
         self.assertEqual(exceptionInfo["breakMode"], "always")
-        self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
+        self.assertIn("signal SIGABRT", exceptionInfo["description"])
         self.assertEqual(exceptionInfo["exceptionId"], "signal")
         self.assertIsNotNone(exceptionInfo["details"])
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 ddedf7a6de8c6..40233af4b2bd6 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
@@ -16,10 +16,10 @@ def test_stopped_description(self):
         program = self.getBuildArtifact("a.out")
         self.build_and_launch(program)
         self.dap_server.request_continue()
-        self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
+        self.verify_stop_exception_info("signal SIGABRT")
         exception_info = self.get_exceptionInfo()
         self.assertEqual(exception_info["breakMode"], "always")
-        self.assertEqual(exception_info["description"], "signal SIGABRT")
+        self.assertIn("signal SIGABRT", exception_info["description"])
         self.assertEqual(exception_info["exceptionId"], "signal")
         exception_details = exception_info["details"]
         self.assertRegex(exception_details["message"], "SomeReason")
@@ -44,7 +44,7 @@ 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("hit Objective-C exception")
 
         # FIXME: Catching objc exceptions do not appear to be working.
         # Xcode appears to set a breakpoint on '__cxa_begin_catch' for objc
@@ -54,10 +54,10 @@ def test_break_on_throw_and_catch(self):
 
         self.do_continue()
 
-        self.assertTrue(self.verify_stop_exception_info("signal SIGABRT"))
+        self.verify_stop_exception_info("signal SIGABRT")
         exception_info = self.get_exceptionInfo()
         self.assertEqual(exception_info["breakMode"], "always")
-        self.assertEqual(exception_info["description"], "signal SIGABRT")
+        self.assertIn("signal SIGABRT", exception_info["description"])
         self.assertEqual(exception_info["exceptionId"], "signal")
         exception_details = exception_info["details"]
         self.assertRegex(exception_details["message"], "SomeReason")
diff --git 
a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile 
b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile
new file mode 100644
index 0000000000000..b27db90a40de2
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -fsanitize=undefined -g
+
+include Makefile.rules
diff --git 
a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py
 
b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py
new file mode 100644
index 0000000000000..caff7dd1bedcd
--- /dev/null
+++ 
b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py
@@ -0,0 +1,25 @@
+"""
+Test that we stop at runtime instrumentation locations.
+"""
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_runtime_instruments(lldbdap_testcase.DAPTestCaseBase):
+    @skipUnlessUndefinedBehaviorSanitizer
+    def test_ubsan(self):
+        """
+        Test that we stop at ubsan.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        self.do_continue()
+
+        self.verify_stop_exception_info("Out of bounds index")
+        exceptionInfo = self.get_exceptionInfo()
+        self.assertEqual(exceptionInfo["breakMode"], "always")
+        self.assertEqual("Out of bounds index", exceptionInfo["description"])
+        self.assertEqual(exceptionInfo["exceptionId"], 
"runtime-instrumentation")
+        self.assertIn("main.c", exceptionInfo["details"]["stackTrace"])
diff --git 
a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories 
b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories
new file mode 100644
index 0000000000000..33fbf4be1bb2f
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories
@@ -0,0 +1 @@
+instrumentation-runtime
\ No newline at end of file
diff --git a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c 
b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c
new file mode 100644
index 0000000000000..9434b8e2d7583
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c
@@ -0,0 +1,5 @@
+int main(int argc, char const *argv[]) {
+  int data[4] = {0};
+  int *p = data + 5; // ubsan
+  return *p;
+}
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 6c5a9127f131b..6764146ab8551 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -25,9 +25,12 @@
 #include "lldb/API/SBListener.h"
 #include "lldb/API/SBPlatform.h"
 #include "lldb/API/SBStream.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>
 
@@ -188,54 +191,98 @@ 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;
+  lldb::tid_t stopped_thread_idx = 0;
   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;
-    }
+    dap.thread_ids.insert(thread.GetThreadID());
+
+    if (stopped_thread_idx || !ThreadHasStopReason(thread))
+      continue;
+
+    // Stop at the first thread with a stop reason.
+    stopped_thread_idx = thread_idx;
   }
 
-  // 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));
+  lldb::SBThread thread = process.GetThreadAtIndex(stopped_thread_idx);
+  assert(thread.IsValid() && "no valid thread found, process not stopped");
+
+  protocol::StoppedEventBody body;
+  if (on_entry) {
+    body.reason = protocol::eStopReasonEntry;
   } 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));
+    switch (thread.GetStopReason()) {
+    case lldb::eStopReasonTrace:
+    case lldb::eStopReasonPlanComplete:
+      body.reason = protocol::eStopReasonStep;
+      break;
+    case lldb::eStopReasonBreakpoint: {
+      ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread);
+      if (exc_bp) {
+        body.reason = protocol::eStopReasonException;
+        body.text = exc_bp->GetLabel();
+      } else {
+        InstructionBreakpoint *inst_bp =
+            dap.GetInstructionBPFromStopReason(thread);
+        body.reason = inst_bp ? protocol::eStopReasonInstructionBreakpoint
+                              : protocol::eStopReasonBreakpoint;
+
+        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::eStopReasonDataBreakpoint;
+      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::eStopReasonProcessorTrace:
+      body.reason = protocol::eStopReasonStep; // fallback reason
+      break;
+    case lldb::eStopReasonHistoryBoundary:
+      body.reason = protocol::eStopReasonStep; // fallback reason
+      break;
+    case lldb::eStopReasonSignal:
+    case lldb::eStopReasonException:
+    case lldb::eStopReasonInstrumentation:
+      body.reason = protocol::eStopReasonException;
+      break;
+    case lldb::eStopReasonExec:
+    case lldb::eStopReasonFork:
+    case lldb::eStopReasonVFork:
+    case lldb::eStopReasonVForkDone:
+      body.reason = protocol::eStopReasonEntry;
+      break;
+    case lldb::eStopReasonInterrupt:
+      body.reason = protocol::eStopReasonPause;
+      break;
+    case lldb::eStopReasonThreadExiting:
+    case lldb::eStopReasonInvalid:
+    case lldb::eStopReasonNone:
+      llvm_unreachable("invalid stop reason, thread is not stopped");
+      break;
     }
   }
+  lldb::tid_t tid = thread.GetThreadID();
+  lldb::SBStream description;
+  thread.GetStopDescription(description);
+  body.description = description.GetData();
+  body.threadId = tid;
+  body.preserveFocusHint = tid == dap.focus_tid;
+  body.allThreadsStopped = true;
+
+  // Update focused thread.
+  dap.focus_tid = tid;
+
+  dap.Send(protocol::Event{"stopped", std::move(body)});
 
   for (const auto &tid : old_thread_ids) {
     auto end = dap.thread_ids.end();
diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp 
b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
index ddf55e6fb382d..b4f2c615b3396 100644
--- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
@@ -8,15 +8,30 @@
 
 #include "DAP.h"
 #include "DAPError.h"
+#include "DAPLog.h"
 #include "Protocol/ProtocolRequests.h"
 #include "Protocol/ProtocolTypes.h"
 #include "RequestHandler.h"
 #include "lldb/API/SBStream.h"
+#include "lldb/API/SBStructuredData.h"
+#include "lldb/API/SBThreadCollection.h"
+#include "lldb/lldb-enumerations.h"
+#include <utility>
 
 using namespace lldb_dap::protocol;
 
 namespace lldb_dap {
 
+static std::string ThreadSummary(lldb::SBThread &thread) {
+  lldb::SBStream stream;
+  thread.GetDescription(stream);
+  for (uint32_t idx = 0; idx < thread.GetNumFrames(); idx++) {
+    lldb::SBFrame frame = thread.GetFrameAtIndex(idx);
+    frame.GetDescription(stream);
+  }
+  return {stream.GetData(), stream.GetSize()};
+}
+
 /// Retrieves the details of the exception that caused this event to be raised.
 ///
 /// Clients should only call this request if the corresponding capability
@@ -29,53 +44,76 @@ ExceptionInfoRequestHandler::Run(const 
ExceptionInfoArguments &args) const {
     return llvm::make_error<DAPError>(
         llvm::formatv("Invalid thread id: {}", args.threadId).str());
 
-  ExceptionInfoResponseBody response;
-  response.breakMode = eExceptionBreakModeAlways;
+  ExceptionInfoResponseBody body;
+  body.breakMode = eExceptionBreakModeAlways;
   const lldb::StopReason stop_reason = thread.GetStopReason();
   switch (stop_reason) {
+  case lldb::eStopReasonInstrumentation:
+    body.exceptionId = "runtime-instrumentation";
+    break;
   case lldb::eStopReasonSignal:
-    response.exceptionId = "signal";
+    body.exceptionId = "signal";
     break;
   case lldb::eStopReasonBreakpoint: {
     const ExceptionBreakpoint *exc_bp =
         dap.GetExceptionBPFromStopReason(thread);
     if (exc_bp) {
-      response.exceptionId = exc_bp->GetFilter();
-      response.description = exc_bp->GetLabel();
+      body.exceptionId = exc_bp->GetFilter();
+      body.description = exc_bp->GetLabel().str() + "\n";
     } else {
-      response.exceptionId = "exception";
+      body.exceptionId = "exception";
     }
   } break;
   default:
-    response.exceptionId = "exception";
+    body.exceptionId = "exception";
   }
 
   lldb::SBStream stream;
-  if (response.description.empty()) {
-    if (thread.GetStopDescription(stream)) {
-      response.description = {stream.GetData(), stream.GetSize()};
-    }
-  }
+  if (thread.GetStopDescription(stream))
+    body.description += {stream.GetData(), stream.GetSize()};
 
   if (lldb::SBValue exception = thread.GetCurrentException()) {
+    body.details = ExceptionDetails{};
+    body.details->evaluateName = exception.GetName();
+    body.details->typeName = exception.GetDisplayTypeName();
+
     stream.Clear();
-    response.details = ExceptionDetails{};
-    if (exception.GetDescription(stream)) {
-      response.details->message = {stream.GetData(), stream.GetSize()};
-    }
+    if (exception.GetDescription(stream))
+      body.details->message = {stream.GetData(), stream.GetSize()};
 
     if (lldb::SBThread exception_backtrace =
-            thread.GetCurrentExceptionBacktrace()) {
-      stream.Clear();
-      exception_backtrace.GetDescription(stream);
+            thread.GetCurrentExceptionBacktrace())
+      body.details->stackTrace = ThreadSummary(exception_backtrace);
+  }
 
-      for (uint32_t idx = 0; idx < exception_backtrace.GetNumFrames(); idx++) {
-        lldb::SBFrame frame = exception_backtrace.GetFrameAtIndex(idx);
-        frame.GetDescription(stream);
-      }
-      response.details->stackTrace = {stream.GetData(), stream.GetSize()};
+  lldb::SBStructuredData crash_info =
+      dap.target.GetProcess().GetExtendedCrashInformation();
+  stream.Clear();
+  if (crash_info.IsValid() && crash_info.GetDescription(stream))
+    body.description += "\n\nExtended Crash Information:\n" +
+                        std::string(stream.GetData(), stream.GetSize());
+
+  for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) {
+    lldb::InstrumentationRuntimeType type =
+        static_cast<lldb::InstrumentationRuntimeType>(idx);
+    if (!dap.target.GetProcess().IsInstrumentationRuntimePresent(type))
+      continue;
+    lldb::SBThreadCollection threads =
+        thread.GetStopReasonExtendedBacktraces(type);
+    for (uint32_t tidx = 0; tidx < threads.GetSize(); tidx++) {
+      auto thread = threads.GetThreadAtIndex(tidx);
+      if (!thread)
+        continue;
+      ExceptionDetails details;
+      details.stackTrace = ThreadSummary(thread);
+      if (!body.details)
+        body.details = std::move(details);
+      else
+        body.details->innerException.emplace_back(std::move(details));
     }
   }
-  return response;
+
+  return body;
 }
+
 } // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp 
b/lldb/tools/lldb-dap/JSONUtils.cpp
index 5c33c6aa591a6..643ec5c6a685d 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -430,163 +430,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", "breakpoint");
-    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..d72da844457a8 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 StopReason &SR) {
+  switch (SR) {
+  case eStopReasonStep:
+    return "step";
+  case eStopReasonBreakpoint:
+    return "breakpoint";
+  case eStopReasonException:
+    return "exception";
+  case eStopReasonPause:
+    return "pause";
+  case eStopReasonEntry:
+    return "entry";
+  case eStopReasonGoto:
+    return "goto";
+  case eStopReasonFunctionBreakpoint:
+    return "function breakpoint";
+  case eStopReasonDataBreakpoint:
+    return "data breakpoint";
+  case eStopReasonInstructionBreakpoint:
+    return "instruction breakpoint";
+  case eStopReasonInvalid:
+    llvm_unreachable("invalid stop reason");
+    break;
+  }
+}
+
+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..230d28f7e2810 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 StopReason : unsigned {
+  eStopReasonInvalid,
+  eStopReasonStep,
+  eStopReasonBreakpoint,
+  eStopReasonException,
+  eStopReasonPause,
+  eStopReasonEntry,
+  eStopReasonGoto,
+  eStopReasonFunctionBreakpoint,
+  eStopReasonDataBreakpoint,
+  eStopReasonInstructionBreakpoint,
+};
+
+/// 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).
+  StopReason reason = eStopReasonInvalid;
+
+  /// 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 different 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

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to