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

>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 1/3] [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

>From 127d8fd41f7285f3dccc3abb1b5f6d5a3c45402d Mon Sep 17 00:00:00 2001
From: John Harrison <[email protected]>
Date: Thu, 15 Jan 2026 15:55:27 -0800
Subject: [PATCH 2/3] Simplifying temp_file.py and removing a test I moved into
 its own file.

---
 .../Python/lldbsuite/support/temp_file.py        |  4 +---
 .../lldb-dap/exception/TestDAP_exception.py      | 16 ----------------
 2 files changed, 1 insertion(+), 19 deletions(-)

diff --git a/lldb/packages/Python/lldbsuite/support/temp_file.py 
b/lldb/packages/Python/lldbsuite/support/temp_file.py
index 17fdd9f7d4eca..e28eaaef1379c 100644
--- a/lldb/packages/Python/lldbsuite/support/temp_file.py
+++ b/lldb/packages/Python/lldbsuite/support/temp_file.py
@@ -9,10 +9,8 @@
 
 
 class OnDiskTempFile:
-    def __init__(self, delete=True, immediate=False):
+    def __init__(self, delete=True):
         self.path = None
-        if immediate:
-            self._set_path()
 
     def __del__(self):
         if self.path and os.path.exists(self.path):
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 0cb48f345474e..b92c3290ceb4c 100644
--- a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
+++ b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
@@ -23,19 +23,3 @@ def test_stopped_description(self):
         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"])

>From 3a8bdbf31f6d44b41be2c4991f6f53af7d63b1b6 Mon Sep 17 00:00:00 2001
From: John Harrison <[email protected]>
Date: Thu, 15 Jan 2026 16:34:40 -0800
Subject: [PATCH 3/3] Removing ubsan from TestDAP_exception.py flags.

---
 lldb/test/API/tools/lldb-dap/exception/Makefile | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lldb/test/API/tools/lldb-dap/exception/Makefile 
b/lldb/test/API/tools/lldb-dap/exception/Makefile
index b27db90a40de2..10495940055b6 100644
--- a/lldb/test/API/tools/lldb-dap/exception/Makefile
+++ b/lldb/test/API/tools/lldb-dap/exception/Makefile
@@ -1,4 +1,3 @@
 C_SOURCES := main.c
-CFLAGS_EXTRAS := -fsanitize=undefined -g
 
 include Makefile.rules

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

Reply via email to