Skip to content

Commit 262158b

Browse files
authored
[lldb-dap] Support StackFrameFormat (llvm#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 llvm#137057
1 parent 42622c7 commit 262158b

File tree

5 files changed

+124
-22
lines changed

5 files changed

+124
-22
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ def request_modules(self):
10461046
return self.send_recv({"command": "modules", "type": "request"})
10471047

10481048
def request_stackTrace(
1049-
self, threadId=None, startFrame=None, levels=None, dump=False
1049+
self, threadId=None, startFrame=None, levels=None, format=None, dump=False
10501050
):
10511051
if threadId is None:
10521052
threadId = self.get_thread_id()
@@ -1055,6 +1055,8 @@ def request_stackTrace(
10551055
args_dict["startFrame"] = startFrame
10561056
if levels is not None:
10571057
args_dict["levels"] = levels
1058+
if format is not None:
1059+
args_dict["format"] = format
10581060
command_dict = {
10591061
"command": "stackTrace",
10601062
"type": "request",

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,14 @@ def get_dict_value(self, d, key_path):
161161
return value
162162

163163
def get_stackFrames_and_totalFramesCount(
164-
self, threadId=None, startFrame=None, levels=None, dump=False
164+
self, threadId=None, startFrame=None, levels=None, format=None, dump=False
165165
):
166166
response = self.dap_server.request_stackTrace(
167-
threadId=threadId, startFrame=startFrame, levels=levels, dump=dump
167+
threadId=threadId,
168+
startFrame=startFrame,
169+
levels=levels,
170+
format=format,
171+
dump=dump,
168172
)
169173
if response:
170174
stackFrames = self.get_dict_value(response, ["body", "stackFrames"])
@@ -177,9 +181,15 @@ def get_stackFrames_and_totalFramesCount(
177181
return (stackFrames, totalFrames)
178182
return (None, 0)
179183

180-
def get_stackFrames(self, threadId=None, startFrame=None, levels=None, dump=False):
184+
def get_stackFrames(
185+
self, threadId=None, startFrame=None, levels=None, format=None, dump=False
186+
):
181187
(stackFrames, totalFrames) = self.get_stackFrames_and_totalFramesCount(
182-
threadId=threadId, startFrame=startFrame, levels=levels, dump=dump
188+
threadId=threadId,
189+
startFrame=startFrame,
190+
levels=levels,
191+
format=format,
192+
dump=dump,
183193
)
184194
return stackFrames
185195

lldb/test/API/tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py

+28-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test lldb-dap stackTrace request with an extended backtrace thread.
33
"""
44

5-
65
import os
76

87
import lldbdap_testcase
@@ -12,11 +11,7 @@
1211

1312

1413
class TestDAP_extendedStackTrace(lldbdap_testcase.DAPTestCaseBase):
15-
@skipUnlessDarwin
16-
def test_stackTrace(self):
17-
"""
18-
Tests the 'stackTrace' packet on a thread with an extended backtrace.
19-
"""
14+
def build_and_run(self, displayExtendedBacktrace=True):
2015
backtrace_recording_lib = findBacktraceRecordingDylib()
2116
if not backtrace_recording_lib:
2217
self.skipTest(
@@ -36,7 +31,7 @@ def test_stackTrace(self):
3631
"DYLD_LIBRARY_PATH=/usr/lib/system/introspection",
3732
"DYLD_INSERT_LIBRARIES=" + backtrace_recording_lib,
3833
],
39-
displayExtendedBacktrace=True,
34+
displayExtendedBacktrace=displayExtendedBacktrace,
4035
)
4136
source = "main.m"
4237
breakpoint = line_number(source, "breakpoint 1")
@@ -47,6 +42,12 @@ def test_stackTrace(self):
4742
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
4843
)
4944

45+
@skipUnlessDarwin
46+
def test_stackTrace(self):
47+
"""
48+
Tests the 'stackTrace' packet on a thread with an extended backtrace.
49+
"""
50+
self.build_and_run()
5051
events = self.continue_to_next_stop()
5152

5253
stackFrames, totalFrames = self.get_stackFrames_and_totalFramesCount(
@@ -102,3 +103,23 @@ def test_stackTrace(self):
102103
self.assertGreaterEqual(
103104
totalFrames, i, "total frames should include a pagination offset"
104105
)
106+
107+
@skipUnlessDarwin
108+
def test_stackTraceWithFormat(self):
109+
"""
110+
Tests the 'stackTrace' packet on a thread with an extended backtrace using stack trace formats.
111+
"""
112+
self.build_and_run(displayExtendedBacktrace=False)
113+
events = self.continue_to_next_stop()
114+
115+
stackFrames, _ = self.get_stackFrames_and_totalFramesCount(
116+
threadId=events[0]["body"]["threadId"], format={"includeAll": True}
117+
)
118+
119+
stackLabels = [
120+
(i, frame)
121+
for i, frame in enumerate(stackFrames)
122+
if frame.get("presentationHint", "") == "label"
123+
]
124+
125+
self.assertEqual(len(stackLabels), 2, "expected two label stack frames")

lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test lldb-dap stackTrace request
33
"""
44

5-
65
import os
76

87
import lldbdap_testcase
@@ -217,3 +216,30 @@ def test_functionNameWithArgs(self):
217216
self.continue_to_next_stop()
218217
frame = self.get_stackFrames()[0]
219218
self.assertEqual(frame["name"], "recurse(x=1)")
219+
220+
@skipIfWindows
221+
def test_StackFrameFormat(self):
222+
"""
223+
Test the StackFrameFormat.
224+
"""
225+
program = self.getBuildArtifact("a.out")
226+
self.build_and_launch(program)
227+
source = "main.c"
228+
229+
self.set_source_breakpoints(source, [line_number(source, "recurse end")])
230+
231+
self.continue_to_next_stop()
232+
frame = self.get_stackFrames(format={"parameters": True})[0]
233+
self.assertEqual(frame["name"], "recurse(x=1)")
234+
235+
frame = self.get_stackFrames(format={"parameterNames": True})[0]
236+
self.assertEqual(frame["name"], "recurse(x=1)")
237+
238+
frame = self.get_stackFrames(format={"parameterValues": True})[0]
239+
self.assertEqual(frame["name"], "recurse(x=1)")
240+
241+
frame = self.get_stackFrames(format={"parameters": False, "line": True})[0]
242+
self.assertEqual(frame["name"], "main.c:6:5 recurse")
243+
244+
frame = self.get_stackFrames(format={"parameters": False, "module": True})[0]
245+
self.assertEqual(frame["name"], "a.out recurse")

lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp

+52-9
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ static constexpr int StackPageSize = 20;
4949
//
5050
// s=3,l=3 = [th0->s3, label1, th1->s0]
5151
static bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
52+
lldb::SBFormat &frame_format,
5253
llvm::json::Array &stack_frames, int64_t &offset,
53-
const int64_t start_frame, const int64_t levels) {
54+
const int64_t start_frame, const int64_t levels,
55+
const bool include_all) {
5456
bool reached_end_of_stack = false;
5557
for (int64_t i = start_frame;
5658
static_cast<int64_t>(stack_frames.size()) < levels; i++) {
5759
if (i == -1) {
5860
stack_frames.emplace_back(
59-
CreateExtendedStackFrameLabel(thread, dap.frame_format));
61+
CreateExtendedStackFrameLabel(thread, frame_format));
6062
continue;
6163
}
6264

@@ -67,10 +69,10 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
6769
break;
6870
}
6971

70-
stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format));
72+
stack_frames.emplace_back(CreateStackFrame(frame, frame_format));
7173
}
7274

73-
if (dap.configuration.displayExtendedBacktrace && reached_end_of_stack) {
75+
if (include_all && reached_end_of_stack) {
7476
// Check for any extended backtraces.
7577
for (uint32_t bt = 0;
7678
bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) {
@@ -80,8 +82,9 @@ static bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
8082
continue;
8183

8284
reached_end_of_stack = FillStackFrames(
83-
dap, backtrace, stack_frames, offset,
84-
(start_frame - offset) > 0 ? start_frame - offset : -1, levels);
85+
dap, backtrace, frame_format, stack_frames, offset,
86+
(start_frame - offset) > 0 ? start_frame - offset : -1, levels,
87+
include_all);
8588
if (static_cast<int64_t>(stack_frames.size()) >= levels)
8689
break;
8790
}
@@ -178,14 +181,54 @@ void StackTraceRequestHandler::operator()(
178181
llvm::json::Array stack_frames;
179182
llvm::json::Object body;
180183

184+
lldb::SBFormat frame_format = dap.frame_format;
185+
bool include_all = dap.configuration.displayExtendedBacktrace;
186+
187+
if (const auto *format = arguments->getObject("format")) {
188+
// Indicates that all stack frames should be included, even those the debug
189+
// adapter might otherwise hide.
190+
include_all = GetBoolean(format, "includeAll").value_or(false);
191+
192+
// Parse the properties that have a corresponding format string.
193+
// FIXME: Support "parameterTypes" and "hex".
194+
const bool module = GetBoolean(format, "module").value_or(false);
195+
const bool line = GetBoolean(format, "line").value_or(false);
196+
const bool parameters = GetBoolean(format, "parameters").value_or(false);
197+
const bool parameter_names =
198+
GetBoolean(format, "parameterNames").value_or(false);
199+
const bool parameter_values =
200+
GetBoolean(format, "parameterValues").value_or(false);
201+
202+
// Only change the format string if we have to.
203+
if (module || line || parameters || parameter_names || parameter_values) {
204+
std::string format_str;
205+
llvm::raw_string_ostream os(format_str);
206+
207+
if (module)
208+
os << "{${module.file.basename} }";
209+
210+
if (line)
211+
os << "{${line.file.basename}:${line.number}:${line.column} }";
212+
213+
if (parameters || parameter_names || parameter_values)
214+
os << "{${function.name-with-args}}";
215+
else
216+
os << "{${function.name-without-args}}";
217+
218+
lldb::SBError error;
219+
frame_format = lldb::SBFormat(format_str.c_str(), error);
220+
assert(error.Success());
221+
}
222+
}
223+
181224
if (thread.IsValid()) {
182225
const auto start_frame =
183226
GetInteger<uint64_t>(arguments, "startFrame").value_or(0);
184227
const auto levels = GetInteger<uint64_t>(arguments, "levels").value_or(0);
185228
int64_t offset = 0;
186-
bool reached_end_of_stack =
187-
FillStackFrames(dap, thread, stack_frames, offset, start_frame,
188-
levels == 0 ? INT64_MAX : levels);
229+
bool reached_end_of_stack = FillStackFrames(
230+
dap, thread, frame_format, stack_frames, offset, start_frame,
231+
levels == 0 ? INT64_MAX : levels, include_all);
189232
body.try_emplace("totalFrames",
190233
start_frame + stack_frames.size() +
191234
(reached_end_of_stack ? 0 : StackPageSize));

0 commit comments

Comments
 (0)