[Lldb-commits] [lldb] [lldb-dap] Adding more details to 'exceptionInfo'. (PR #176465)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Fri Jan 16 11:53:55 PST 2026
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/176465
>From 0b50dabc60648c3211787d534dd14cea5279f9ac Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 16 Jan 2026 11:31:17 -0800
Subject: [PATCH 1/2] [lldb-dap] Adding more details to 'exceptionInfo'.
---
.../test/tools/lldb-dap/lldbdap_testcase.py | 48 ++++++----
.../TestDAP_setExceptionBreakpoints.py | 8 +-
.../lldb-dap/exception/TestDAP_exception.py | 2 +-
.../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 ++
.../Handler/ExceptionInfoRequestHandler.cpp | 90 +++++++++++++------
10 files changed, 144 insertions(+), 54 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/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/TestDAP_exception.py b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
index f044bcae41892..b92c3290ceb4c 100644
--- a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
+++ b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
@@ -18,7 +18,7 @@ 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")
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..c756cb1241945
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories
@@ -0,0 +1 @@
+instrumentation-runtime
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/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp
index ddf55e6fb382d..c76fc83fa8cbe 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,78 @@ 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{};
+ if (const char *name = exception.GetName())
+ body.details->evaluateName = name;
+ if (const char *typeName = exception.GetDisplayTypeName())
+ body.details->typeName = typeName;
+
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
>From 4b209449608a5e394af5ee603c81f3539f756e8d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 16 Jan 2026 11:53:39 -0800
Subject: [PATCH 2/2] Fixing tests after splitting up the PR.
---
.../breakpoint/TestDAP_setExceptionBreakpoints.py | 8 ++------
.../lldb-dap/exception/objc/TestDAP_exception_objc.py | 2 +-
2 files changed, 3 insertions(+), 7 deletions(-)
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 684726c927bc1..5ed7e13fd0b44 100644
--- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py
@@ -35,9 +35,5 @@ def test_functionality(self):
if response:
self.assertTrue(response["success"])
- 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"
- )
+ self.continue_to_exception_breakpoint(r"C\+\+ Throw")
+ self.continue_to_exception_breakpoint(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 40233af4b2bd6..694cadb6ed2fe 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,7 @@ def test_break_on_throw_and_catch(self):
if response:
self.assertTrue(response["success"])
- self.continue_to_exception_breakpoint("hit Objective-C exception")
+ self.continue_to_exception_breakpoint("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
More information about the lldb-commits
mailing list