[Lldb-commits] [lldb] [lldb-dap] Support StackFrameFormat (PR #137113)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Thu Apr 24 09:50:45 PDT 2025
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 <jonas at devlieghere.com>
Date: Wed, 23 Apr 2025 20:05:19 -0700
Subject: [PATCH 1/3] [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 <jonas at devlieghere.com>
Date: Thu, 24 Apr 2025 09:45:18 -0700
Subject: [PATCH 2/3] 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 <jonas at devlieghere.com>
Date: Thu, 24 Apr 2025 09:50:32 -0700
Subject: [PATCH 3/3] 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:
More information about the lldb-commits
mailing list