Author: Jonas Devlieghere Date: 2025-04-24T16:25:31-07:00 New Revision: 262158b8aa12634c17f4b37cba62564e5c9baab4
URL: https://github.com/llvm/llvm-project/commit/262158b8aa12634c17f4b37cba62564e5c9baab4 DIFF: https://github.com/llvm/llvm-project/commit/262158b8aa12634c17f4b37cba62564e5c9baab4.diff LOG: [lldb-dap] Support StackFrameFormat (#137113) The debug adapter protocol supports an option to provide formatting information for a stack frames as part of the StackTrace request. lldb-dap incorrectly advertises it supports this, but until this PR that support wasn't actually implemented. Fixes #137057 Added: Modified: lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp Removed: ################################################################################ diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index a9915ba2f6de6..dadf6b1f8774c 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -1046,7 +1046,7 @@ def request_modules(self): return self.send_recv({"command": "modules", "type": "request"}) def request_stackTrace( - self, threadId=None, startFrame=None, levels=None, dump=False + self, threadId=None, startFrame=None, levels=None, format=None, dump=False ): if threadId is None: threadId = self.get_thread_id() @@ -1055,6 +1055,8 @@ def request_stackTrace( args_dict["startFrame"] = startFrame if levels is not None: args_dict["levels"] = levels + if format is not None: + args_dict["format"] = format command_dict = { "command": "stackTrace", "type": "request", 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 70b04b051e0ec..b5b55b336d535 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 @@ -161,10 +161,14 @@ def get_dict_value(self, d, key_path): return value def get_stackFrames_and_totalFramesCount( - self, threadId=None, startFrame=None, levels=None, dump=False + self, threadId=None, startFrame=None, levels=None, format=None, dump=False ): response = self.dap_server.request_stackTrace( - threadId=threadId, startFrame=startFrame, levels=levels, dump=dump + threadId=threadId, + startFrame=startFrame, + levels=levels, + format=format, + dump=dump, ) if response: stackFrames = self.get_dict_value(response, ["body", "stackFrames"]) @@ -177,9 +181,15 @@ def get_stackFrames_and_totalFramesCount( return (stackFrames, totalFrames) return (None, 0) - def get_stackFrames(self, threadId=None, startFrame=None, levels=None, dump=False): + def get_stackFrames( + self, threadId=None, startFrame=None, levels=None, format=None, dump=False + ): (stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount( - threadId=threadId, startFrame=startFrame, levels=levels, dump=dump + threadId=threadId, + startFrame=startFrame, + levels=levels, + format=format, + dump=dump, ) return stackFrames diff --git a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py index f6b613da964b8..33a3bde3a5a42 100644 --- a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py +++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py @@ -2,7 +2,6 @@ Test lldb-dap stackTrace request with an extended backtrace thread. """ - import os import lldbdap_testcase @@ -12,11 +11,7 @@ class TestDAP_extendedStackTrace(lldbdap_testcase.DAPTestCaseBase): - @skipUnlessDarwin - def test_stackTrace(self): - """ - Tests the 'stackTrace' packet on a thread with an extended backtrace. - """ + def build_and_run(self, displayExtendedBacktrace=True): backtrace_recording_lib = findBacktraceRecordingDylib() if not backtrace_recording_lib: self.skipTest( @@ -36,7 +31,7 @@ def test_stackTrace(self): "DYLD_LIBRARY_PATH=/usr/lib/system/introspection", "DYLD_INSERT_LIBRARIES=" + backtrace_recording_lib, ], - displayExtendedBacktrace=True, + displayExtendedBacktrace=displayExtendedBacktrace, ) source = "main.m" breakpoint = line_number(source, "breakpoint 1") @@ -47,6 +42,12 @@ def test_stackTrace(self): len(breakpoint_ids), len(lines), "expect correct number of breakpoints" ) + @skipUnlessDarwin + def test_stackTrace(self): + """ + Tests the 'stackTrace' packet on a thread with an extended backtrace. + """ + self.build_and_run() events = self.continue_to_next_stop() stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount( @@ -102,3 +103,23 @@ def test_stackTrace(self): self.assertGreaterEqual( totalFrames, i, "total frames should include a pagination offset" ) + + @skipUnlessDarwin + def test_stackTraceWithFormat(self): + """ + Tests the 'stackTrace' packet on a thread with an extended backtrace using stack trace formats. + """ + self.build_and_run(displayExtendedBacktrace=False) + events = self.continue_to_next_stop() + + stackFrames, _ = self.get_stackFrames_and_totalFramesCount( + threadId=events[0]["body"]["threadId"], format={"includeAll": True} + ) + + stackLabels = [ + (i, frame) + for i, frame in enumerate(stackFrames) + if frame.get("presentationHint", "") == "label" + ] + + self.assertEqual(len(stackLabels), 2, "expected two label stack frames") diff --git a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py index 56ed1ebdf7ab4..3a11df7505994 100644 --- a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py +++ b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py @@ -2,7 +2,6 @@ Test lldb-dap stackTrace request """ - import os import lldbdap_testcase @@ -217,3 +216,30 @@ def test_functionNameWithArgs(self): self.continue_to_next_stop() frame = self.get_stackFrames()[0] self.assertEqual(frame["name"], "recurse(x=1)") + + @skipIfWindows + def test_StackFrameFormat(self): + """ + Test the StackFrameFormat. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.c" + + self.set_source_breakpoints(source, [line_number(source, "recurse end")]) + + self.continue_to_next_stop() + frame = self.get_stackFrames(format={"parameters": True})[0] + self.assertEqual(frame["name"], "recurse(x=1)") + + frame = self.get_stackFrames(format={"parameterNames": True})[0] + self.assertEqual(frame["name"], "recurse(x=1)") + + frame = self.get_stackFrames(format={"parameterValues": True})[0] + self.assertEqual(frame["name"], "recurse(x=1)") + + frame = self.get_stackFrames(format={"parameters": False, "line": True})[0] + self.assertEqual(frame["name"], "main.c:6:5 recurse") + + frame = self.get_stackFrames(format={"parameters": False, "module": True})[0] + self.assertEqual(frame["name"], "a.out recurse") diff --git a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp index a58e3325af100..4ea4cd1e517d4 100644 --- a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp @@ -49,14 +49,16 @@ static constexpr int StackPageSize = 20; // // s=3,l=3 = [th0->s3, label1, th1->s0] static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, + lldb::SBFormat &frame_format, llvm::json::Array &stack_frames, int64_t &offset, - const int64_t start_frame, const int64_t levels) { + const int64_t start_frame, const int64_t levels, + const bool include_all) { bool reached_end_of_stack = false; for (int64_t i = start_frame; static_cast<int64_t>(stack_frames.size()) < levels; i++) { if (i == -1) { stack_frames.emplace_back( - CreateExtendedStackFrameLabel(thread, dap.frame_format)); + CreateExtendedStackFrameLabel(thread, frame_format)); continue; } @@ -67,10 +69,10 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, break; } - stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format)); + stack_frames.emplace_back(CreateStackFrame(frame, frame_format)); } - if (dap.configuration.displayExtendedBacktrace && reached_end_of_stack) { + if (include_all && reached_end_of_stack) { // Check for any extended backtraces. for (uint32_t bt = 0; bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) { @@ -80,8 +82,9 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, continue; reached_end_of_stack = FillStackFrames( - dap, backtrace, stack_frames, offset, - (start_frame - offset) > 0 ? start_frame - offset : -1, levels); + dap, backtrace, frame_format, stack_frames, offset, + (start_frame - offset) > 0 ? start_frame - offset : -1, levels, + include_all); if (static_cast<int64_t>(stack_frames.size()) >= levels) break; } @@ -178,14 +181,54 @@ void StackTraceRequestHandler::operator()( llvm::json::Array stack_frames; llvm::json::Object body; + lldb::SBFormat frame_format = dap.frame_format; + bool include_all = dap.configuration.displayExtendedBacktrace; + + if (const auto *format = arguments->getObject("format")) { + // Indicates that all stack frames should be included, even those the debug + // adapter might otherwise hide. + include_all = GetBoolean(format, "includeAll").value_or(false); + + // Parse the properties that have a corresponding format string. + // FIXME: Support "parameterTypes" and "hex". + const bool module = GetBoolean(format, "module").value_or(false); + const bool line = GetBoolean(format, "line").value_or(false); + const bool parameters = GetBoolean(format, "parameters").value_or(false); + const bool parameter_names = + GetBoolean(format, "parameterNames").value_or(false); + const bool parameter_values = + GetBoolean(format, "parameterValues").value_or(false); + + // Only change the format string if we have to. + if (module || line || parameters || parameter_names || parameter_values) { + std::string format_str; + llvm::raw_string_ostream os(format_str); + + if (module) + os << "{${module.file.basename} }"; + + if (line) + os << "{${line.file.basename}:${line.number}:${line.column} }"; + + if (parameters || parameter_names || parameter_values) + os << "{${function.name-with-args}}"; + else + os << "{${function.name-without-args}}"; + + lldb::SBError error; + frame_format = lldb::SBFormat(format_str.c_str(), error); + assert(error.Success()); + } + } + if (thread.IsValid()) { const auto start_frame = GetInteger<uint64_t>(arguments, "startFrame").value_or(0); const auto levels = GetInteger<uint64_t>(arguments, "levels").value_or(0); int64_t offset = 0; - bool reached_end_of_stack = - FillStackFrames(dap, thread, stack_frames, offset, start_frame, - levels == 0 ? INT64_MAX : levels); + bool reached_end_of_stack = FillStackFrames( + dap, thread, frame_format, stack_frames, offset, start_frame, + levels == 0 ? INT64_MAX : levels, include_all); body.try_emplace("totalFrames", start_frame + stack_frames.size() + (reached_end_of_stack ? 0 : StackPageSize)); _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits