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 | |
14 | namespace lldb_dap { |
15 | |
16 | /// Page size used for reporting addtional frames in the 'stackTrace' request. |
17 | static 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] |
51 | static 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 | // } |
174 | void 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 | |