https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/137113
>From 21681616560eceb9b46ef4c370817099fe149701 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Wed, 23 Apr 2025 20:05:19 -0700 Subject: [PATCH 1/4] [lldb-dap] Support StackFrameFormat 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 --- .../test/tools/lldb-dap/dap_server.py | 4 +- .../test/tools/lldb-dap/lldbdap_testcase.py | 18 ++++++-- .../lldb-dap/stackTrace/TestDAP_stackTrace.py | 30 +++++++++++++ .../Handler/StackTraceRequestHandler.cpp | 42 ++++++++++++++++--- 4 files changed, 84 insertions(+), 10 deletions(-) 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/stackTrace/TestDAP_stackTrace.py b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py index 56ed1ebdf7ab4..713b5d841cfcd 100644 --- a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py +++ b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py @@ -217,3 +217,33 @@ 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={"includeAll": True})[0] + self.assertEqual(frame["name"], "a.out main.c:6:5 recurse(x=1)") + + 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..359237f1db0b4 100644 --- a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp @@ -49,6 +49,7 @@ 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) { bool reached_end_of_stack = false; @@ -56,7 +57,7 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, 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,7 +68,7 @@ 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) { @@ -80,7 +81,7 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, continue; reached_end_of_stack = FillStackFrames( - dap, backtrace, stack_frames, offset, + dap, backtrace, frame_format, stack_frames, offset, (start_frame - offset) > 0 ? start_frame - offset : -1, levels); if (static_cast<int64_t>(stack_frames.size()) >= levels) break; @@ -178,14 +179,45 @@ void StackTraceRequestHandler::operator()( llvm::json::Array stack_frames; llvm::json::Object body; + lldb::SBFormat frame_format = dap.frame_format; + + if (const auto *format = arguments->getObject("format")) { + 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); + const bool line = GetBoolean(format, "line").value_or(false); + const bool module = GetBoolean(format, "module").value_or(false); + const bool include_all = GetBoolean(format, "includeAll").value_or(false); + + std::string format_str; + llvm::raw_string_ostream os(format_str); + + if (include_all || module) + os << "{${module.file.basename} }"; + + if (include_all || line) + os << "{${line.file.basename}:${line.number}:${line.column} }"; + + if (include_all || 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); + FillStackFrames(dap, thread, frame_format, stack_frames, offset, + start_frame, levels == 0 ? INT64_MAX : levels); body.try_emplace("totalFrames", start_frame + stack_frames.size() + (reached_end_of_stack ? 0 : StackPageSize)); >From 5b1378f178fb38d8fd158571bcdee61a6b5d6eda Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Thu, 24 Apr 2025 09:45:18 -0700 Subject: [PATCH 2/4] Fix incorrect interpretation of includeAll --- .../TestDAP_extendedStackTrace.py | 36 +++++++++--- .../lldb-dap/stackTrace/TestDAP_stackTrace.py | 4 -- .../Handler/StackTraceRequestHandler.cpp | 57 ++++++++++++------- 3 files changed, 64 insertions(+), 33 deletions(-) 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..7c616a72d3c79 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,8 @@ 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 +32,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 +43,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 +104,23 @@ def test_stackTrace(self): self.assertGreaterEqual( totalFrames, i, "total frames should include a pagination offset" ) + + @skipIfWindows + 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 713b5d841cfcd..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 @@ -230,9 +229,6 @@ def test_StackFrameFormat(self): self.set_source_breakpoints(source, [line_number(source, "recurse end")]) self.continue_to_next_stop() - frame = self.get_stackFrames(format={"includeAll": True})[0] - self.assertEqual(frame["name"], "a.out main.c:6:5 recurse(x=1)") - frame = self.get_stackFrames(format={"parameters": True})[0] self.assertEqual(frame["name"], "recurse(x=1)") diff --git a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp index 359237f1db0b4..06e1c06545b73 100644 --- a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp @@ -51,7 +51,8 @@ static constexpr int StackPageSize = 20; 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++) { @@ -71,7 +72,9 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, stack_frames.emplace_back(CreateStackFrame(frame, frame_format)); } - if (dap.configuration.displayExtendedBacktrace && reached_end_of_stack) { + const bool include_extended_backtrace = + include_all || dap.configuration.displayExtendedBacktrace; + if (include_extended_backtrace && reached_end_of_stack) { // Check for any extended backtraces. for (uint32_t bt = 0; bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) { @@ -82,7 +85,8 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, reached_end_of_stack = FillStackFrames( dap, backtrace, frame_format, stack_frames, offset, - (start_frame - offset) > 0 ? start_frame - offset : -1, levels); + (start_frame - offset) > 0 ? start_frame - offset : -1, levels, + include_all); if (static_cast<int64_t>(stack_frames.size()) >= levels) break; } @@ -180,34 +184,43 @@ void StackTraceRequestHandler::operator()( llvm::json::Object body; lldb::SBFormat frame_format = dap.frame_format; + bool include_all = false; 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); - const bool line = GetBoolean(format, "line").value_or(false); - const bool module = GetBoolean(format, "module").value_or(false); - const bool include_all = GetBoolean(format, "includeAll").value_or(false); - std::string format_str; - llvm::raw_string_ostream os(format_str); + // 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 (include_all || module) - os << "{${module.file.basename} }"; + if (module) + os << "{${module.file.basename} }"; - if (include_all || line) - os << "{${line.file.basename}:${line.number}:${line.column} }"; + if (line) + os << "{${line.file.basename}:${line.number}:${line.column} }"; - if (include_all || parameters || parameter_names || parameter_values) - os << "{${function.name-with-args}}"; - else - os << "{${function.name-without-args}}"; + 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()); + lldb::SBError error; + frame_format = lldb::SBFormat(format_str.c_str(), error); + assert(error.Success()); + } } if (thread.IsValid()) { @@ -215,9 +228,9 @@ void StackTraceRequestHandler::operator()( 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, frame_format, 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)); >From 3cf5b4ffa290c79093411141deb5175c0457d8b9 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Thu, 24 Apr 2025 09:50:32 -0700 Subject: [PATCH 3/4] Black & Darker disagree --- .../lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py | 1 - 1 file changed, 1 deletion(-) 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 7c616a72d3c79..d91428a41edac 100644 --- a/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py +++ b/lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py @@ -11,7 +11,6 @@ class TestDAP_extendedStackTrace(lldbdap_testcase.DAPTestCaseBase): - def build_and_run(self, displayExtendedBacktrace=True): backtrace_recording_lib = findBacktraceRecordingDylib() if not backtrace_recording_lib: >From 66f22534cdfa385808129117ccd84bd3ca9a8cd5 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Thu, 24 Apr 2025 09:59:54 -0700 Subject: [PATCH 4/4] Simplify dap.configuration.displayExtendedBacktrace --- lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp index 06e1c06545b73..4ea4cd1e517d4 100644 --- a/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp @@ -72,9 +72,7 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, stack_frames.emplace_back(CreateStackFrame(frame, frame_format)); } - const bool include_extended_backtrace = - include_all || dap.configuration.displayExtendedBacktrace; - if (include_extended_backtrace && 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++) { @@ -184,7 +182,7 @@ void StackTraceRequestHandler::operator()( llvm::json::Object body; lldb::SBFormat frame_format = dap.frame_format; - bool include_all = false; + 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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits