llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: John Harrison (ashgti) <details> <summary>Changes</summary> In 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/b761af2c-90ac-4eb7-9926-3ab133f1b753" /> I included a new test for stopping at `lldb::eStopReasonInstrumentation` and ensuring we have additional information reported. --- Full diff: https://github.com/llvm/llvm-project/pull/176465.diff 10 Files Affected: - (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py (+30-18) - (modified) lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py (+6-2) - (modified) lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py (+1-1) - (modified) lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py (+2-3) - (modified) lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py (+5-5) - (added) lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile (+4) - (added) lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py (+25) - (added) lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories (+1) - (added) lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c (+5) - (modified) lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp (+65-25) ``````````diff 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 `````````` </details> https://github.com/llvm/llvm-project/pull/176465 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
