243 lines · cpp
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-exception6//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, when22// constructing a stack trace first report the full thread stack trace then23// 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->s036// 1. th0->s137// 2. th0->s238// 3. th0->s339// 4. label - Enqueued from th1, sf=-1, i=-440// 5. th1->s041// 6. th1->s142// 7. label - Enqueued from th243// 8. th2->s044// 9. th2->s145// 10. label - Application Specific Backtrace46// 11. th3->s047// 12. th3->s148// 13. th3->s249//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 CreateExtendedStackFrameLabel(thread, frame_format));62 continue;63 }64 65 lldb::SBFrame frame = thread.GetFrameAtIndex(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(CreateStackFrame(dap, frame, 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 thread.GetProcess().GetExtendedBacktraceTypeAtIndex(bt));81 if (!backtrace.IsValid())82 continue;83 84 reached_end_of_stack = FillStackFrames(85 dap, backtrace, frame_format, stack_frames, offset,86 (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 is100// 'stackTrace'. The request returns a stacktrace from the current execution101// 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 omitted124// frames start at 0."125// },126// "levels": {127// "type": "integer",128// "description": "The maximum number of frames to return. If levels is129// 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 corresponding135// 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 has154// length zero, there are no stackframes available. This means that155// there is no location information available."156// },157// "totalFrames": {158// "type": "integer",159// "description": "The total number of frames available in the160// stack. If omitted or if `totalFrames` is larger than the161// available frames, a client is expected to request frames until162// a request returns less frames than requested (which indicates163// the end of the stack). Returning monotonically increasing164// `totalFrames` values for subsequent requests can be used to165// 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("arguments");180 lldb::SBThread thread = dap.GetLLDBThread(*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("format")) {188 // Indicates that all stack frames should be included, even those the debug189 // 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 else216 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>(arguments, "startFrame").value_or(0);227 const auto levels = GetInteger<uint64_t>(arguments, "levels").value_or(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 == 0 ? INT64_MAX : levels, include_all);232 body.try_emplace("totalFrames",233 start_frame + stack_frames.size() +234 (reached_end_of_stack ? 0 : StackPageSize));235 }236 237 body.try_emplace("stackFrames", std::move(stack_frames));238 response.try_emplace("body", std::move(body));239 dap.SendJSON(llvm::json::Value(std::move(response)));240}241 242} // namespace lldb_dap243