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