1//===-- StackTraceRequestHandler.cpp --------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "DAP.h"
10#include "EventHelper.h"
11#include "JSONUtils.h"
12#include "RequestHandler.h"
13
14namespace lldb_dap {
15
16/// Page size used for reporting addtional frames in the 'stackTrace' request.
17static constexpr int StackPageSize = 20;
18
19// Fill in the stack frames of the thread.
20//
21// Threads stacks may contain runtime specific extended backtraces, when
22// constructing a stack trace first report the full thread stack trace then
23// perform a breadth first traversal of any extended backtrace frames.
24//
25// For example:
26//
27// Thread (id=th0) stack=[s0, s1, s2, s3]
28// \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1]
29// \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1]
30// \ Extended backtrace "Application Specific Backtrace" Thread (id=th3)
31// stack=[s0, s1, s2]
32//
33// Which will flatten into:
34//
35// 0. th0->s0
36// 1. th0->s1
37// 2. th0->s2
38// 3. th0->s3
39// 4. label - Enqueued from th1, sf=-1, i=-4
40// 5. th1->s0
41// 6. th1->s1
42// 7. label - Enqueued from th2
43// 8. th2->s0
44// 9. th2->s1
45// 10. label - Application Specific Backtrace
46// 11. th3->s0
47// 12. th3->s1
48// 13. th3->s2
49//
50// s=3,l=3 = [th0->s3, label1, th1->s0]
51static bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
52 lldb::SBFormat &frame_format,
53 llvm::json::Array &stack_frames, int64_t &offset,
54 const int64_t start_frame, const int64_t levels,
55 const bool include_all) {
56 bool reached_end_of_stack = false;
57 for (int64_t i = start_frame;
58 static_cast<int64_t>(stack_frames.size()) < levels; i++) {
59 if (i == -1) {
60 stack_frames.emplace_back(
61 A: CreateExtendedStackFrameLabel(thread, format&: frame_format));
62 continue;
63 }
64
65 lldb::SBFrame frame = thread.GetFrameAtIndex(idx: i);
66 if (!frame.IsValid()) {
67 offset += thread.GetNumFrames() + 1 /* label between threads */;
68 reached_end_of_stack = true;
69 break;
70 }
71
72 stack_frames.emplace_back(A: CreateStackFrame(frame, format&: frame_format));
73 }
74
75 if (include_all && reached_end_of_stack) {
76 // Check for any extended backtraces.
77 for (uint32_t bt = 0;
78 bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) {
79 lldb::SBThread backtrace = thread.GetExtendedBacktraceThread(
80 type: thread.GetProcess().GetExtendedBacktraceTypeAtIndex(idx: bt));
81 if (!backtrace.IsValid())
82 continue;
83
84 reached_end_of_stack = FillStackFrames(
85 dap, thread&: backtrace, frame_format, stack_frames, offset,
86 start_frame: (start_frame - offset) > 0 ? start_frame - offset : -1, levels,
87 include_all);
88 if (static_cast<int64_t>(stack_frames.size()) >= levels)
89 break;
90 }
91 }
92
93 return reached_end_of_stack;
94}
95
96// "StackTraceRequest": {
97// "allOf": [ { "$ref": "#/definitions/Request" }, {
98// "type": "object",
99// "description": "StackTrace request; value of command field is
100// 'stackTrace'. The request returns a stacktrace from the current execution
101// state.", "properties": {
102// "command": {
103// "type": "string",
104// "enum": [ "stackTrace" ]
105// },
106// "arguments": {
107// "$ref": "#/definitions/StackTraceArguments"
108// }
109// },
110// "required": [ "command", "arguments" ]
111// }]
112// },
113// "StackTraceArguments": {
114// "type": "object",
115// "description": "Arguments for 'stackTrace' request.",
116// "properties": {
117// "threadId": {
118// "type": "integer",
119// "description": "Retrieve the stacktrace for this thread."
120// },
121// "startFrame": {
122// "type": "integer",
123// "description": "The index of the first frame to return; if omitted
124// frames start at 0."
125// },
126// "levels": {
127// "type": "integer",
128// "description": "The maximum number of frames to return. If levels is
129// not specified or 0, all frames are returned."
130// },
131// "format": {
132// "$ref": "#/definitions/StackFrameFormat",
133// "description": "Specifies details on how to format the stack frames.
134// The attribute is only honored by a debug adapter if the corresponding
135// capability `supportsValueFormattingOptions` is true."
136// }
137// },
138// "required": [ "threadId" ]
139// },
140// "StackTraceResponse": {
141// "allOf": [ { "$ref": "#/definitions/Response" }, {
142// "type": "object",
143// "description": "Response to `stackTrace` request.",
144// "properties": {
145// "body": {
146// "type": "object",
147// "properties": {
148// "stackFrames": {
149// "type": "array",
150// "items": {
151// "$ref": "#/definitions/StackFrame"
152// },
153// "description": "The frames of the stackframe. If the array has
154// length zero, there are no stackframes available. This means that
155// there is no location information available."
156// },
157// "totalFrames": {
158// "type": "integer",
159// "description": "The total number of frames available in the
160// stack. If omitted or if `totalFrames` is larger than the
161// available frames, a client is expected to request frames until
162// a request returns less frames than requested (which indicates
163// the end of the stack). Returning monotonically increasing
164// `totalFrames` values for subsequent requests can be used to
165// enforce paging in the client."
166// }
167// },
168// "required": [ "stackFrames" ]
169// }
170// },
171// "required": [ "body" ]
172// }]
173// }
174void StackTraceRequestHandler::operator()(
175 const llvm::json::Object &request) const {
176 llvm::json::Object response;
177 FillResponse(request, response);
178 lldb::SBError error;
179 const auto *arguments = request.getObject(K: "arguments");
180 lldb::SBThread thread = dap.GetLLDBThread(arguments: *arguments);
181 llvm::json::Array stack_frames;
182 llvm::json::Object body;
183
184 lldb::SBFormat frame_format = dap.frame_format;
185 bool include_all = dap.configuration.displayExtendedBacktrace;
186
187 if (const auto *format = arguments->getObject(K: "format")) {
188 // Indicates that all stack frames should be included, even those the debug
189 // adapter might otherwise hide.
190 include_all = GetBoolean(obj: format, key: "includeAll").value_or(u: false);
191
192 // Parse the properties that have a corresponding format string.
193 // FIXME: Support "parameterTypes" and "hex".
194 const bool module = GetBoolean(obj: format, key: "module").value_or(u: false);
195 const bool line = GetBoolean(obj: format, key: "line").value_or(u: false);
196 const bool parameters = GetBoolean(obj: format, key: "parameters").value_or(u: false);
197 const bool parameter_names =
198 GetBoolean(obj: format, key: "parameterNames").value_or(u: false);
199 const bool parameter_values =
200 GetBoolean(obj: format, key: "parameterValues").value_or(u: 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
224 if (thread.IsValid()) {
225 const auto start_frame =
226 GetInteger<uint64_t>(obj: arguments, key: "startFrame").value_or(u: 0);
227 const auto levels = GetInteger<uint64_t>(obj: arguments, key: "levels").value_or(u: 0);
228 int64_t offset = 0;
229 bool reached_end_of_stack = FillStackFrames(
230 dap, thread, frame_format, stack_frames, offset, start_frame,
231 levels: levels == 0 ? INT64_MAX : levels, include_all);
232 body.try_emplace(K: "totalFrames",
233 Args: start_frame + stack_frames.size() +
234 (reached_end_of_stack ? 0 : StackPageSize));
235 }
236
237 body.try_emplace(K: "stackFrames", Args: std::move(stack_frames));
238 response.try_emplace(K: "body", Args: std::move(body));
239 dap.SendJSON(json: llvm::json::Value(std::move(response)));
240}
241
242} // namespace lldb_dap
243

source code of lldb/tools/lldb-dap/Handler/StackTraceRequestHandler.cpp