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 <[email protected]> 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 <[email protected]> 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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
