| 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 | |