| //===-- StackTraceRequestHandler.cpp --------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DAP.h" |
| #include "EventHelper.h" |
| #include "JSONUtils.h" |
| #include "RequestHandler.h" |
| |
| namespace lldb_dap { |
| |
| /// Page size used for reporting addtional frames in the 'stackTrace' request. |
| static constexpr int StackPageSize = 20; |
| |
| // Fill in the stack frames of the thread. |
| // |
| // Threads stacks may contain runtime specific extended backtraces, when |
| // constructing a stack trace first report the full thread stack trace then |
| // perform a breadth first traversal of any extended backtrace frames. |
| // |
| // For example: |
| // |
| // Thread (id=th0) stack=[s0, s1, s2, s3] |
| // \ Extended backtrace "libdispatch" Thread (id=th1) stack=[s0, s1] |
| // \ Extended backtrace "libdispatch" Thread (id=th2) stack=[s0, s1] |
| // \ Extended backtrace "Application Specific Backtrace" Thread (id=th3) |
| // stack=[s0, s1, s2] |
| // |
| // Which will flatten into: |
| // |
| // 0. th0->s0 |
| // 1. th0->s1 |
| // 2. th0->s2 |
| // 3. th0->s3 |
| // 4. label - Enqueued from th1, sf=-1, i=-4 |
| // 5. th1->s0 |
| // 6. th1->s1 |
| // 7. label - Enqueued from th2 |
| // 8. th2->s0 |
| // 9. th2->s1 |
| // 10. label - Application Specific Backtrace |
| // 11. th3->s0 |
| // 12. th3->s1 |
| // 13. th3->s2 |
| // |
| // s=3,l=3 = [th0->s3, label1, th1->s0] |
| static bool FillStackFrames(DAP &dap, lldb::SBThread &thread, |
| lldb::SBFormat &frame_format, |
| llvm::json::Array &stack_frames, int64_t &offset, |
| const int64_t start_frame, const int64_t levels, |
| const bool include_all) { |
| bool reached_end_of_stack = false; |
| for (int64_t i = start_frame; |
| static_cast<int64_t>(stack_frames.size()) < levels; i++) { |
| if (i == -1) { |
| stack_frames.emplace_back( |
| CreateExtendedStackFrameLabel(thread, frame_format)); |
| continue; |
| } |
| |
| lldb::SBFrame frame = thread.GetFrameAtIndex(i); |
| if (!frame.IsValid()) { |
| offset += thread.GetNumFrames() + 1 /* label between threads */; |
| reached_end_of_stack = true; |
| break; |
| } |
| |
| stack_frames.emplace_back(CreateStackFrame(dap, frame, frame_format)); |
| } |
| |
| if (include_all && reached_end_of_stack) { |
| // Check for any extended backtraces. |
| for (uint32_t bt = 0; |
| bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) { |
| lldb::SBThread backtrace = thread.GetExtendedBacktraceThread( |
| thread.GetProcess().GetExtendedBacktraceTypeAtIndex(bt)); |
| if (!backtrace.IsValid()) |
| continue; |
| |
| reached_end_of_stack = FillStackFrames( |
| dap, backtrace, frame_format, stack_frames, offset, |
| (start_frame - offset) > 0 ? start_frame - offset : -1, levels, |
| include_all); |
| if (static_cast<int64_t>(stack_frames.size()) >= levels) |
| break; |
| } |
| } |
| |
| return reached_end_of_stack; |
| } |
| |
| // "StackTraceRequest": { |
| // "allOf": [ { "$ref": "#/definitions/Request" }, { |
| // "type": "object", |
| // "description": "StackTrace request; value of command field is |
| // 'stackTrace'. The request returns a stacktrace from the current execution |
| // state.", "properties": { |
| // "command": { |
| // "type": "string", |
| // "enum": [ "stackTrace" ] |
| // }, |
| // "arguments": { |
| // "$ref": "#/definitions/StackTraceArguments" |
| // } |
| // }, |
| // "required": [ "command", "arguments" ] |
| // }] |
| // }, |
| // "StackTraceArguments": { |
| // "type": "object", |
| // "description": "Arguments for 'stackTrace' request.", |
| // "properties": { |
| // "threadId": { |
| // "type": "integer", |
| // "description": "Retrieve the stacktrace for this thread." |
| // }, |
| // "startFrame": { |
| // "type": "integer", |
| // "description": "The index of the first frame to return; if omitted |
| // frames start at 0." |
| // }, |
| // "levels": { |
| // "type": "integer", |
| // "description": "The maximum number of frames to return. If levels is |
| // not specified or 0, all frames are returned." |
| // }, |
| // "format": { |
| // "$ref": "#/definitions/StackFrameFormat", |
| // "description": "Specifies details on how to format the stack frames. |
| // The attribute is only honored by a debug adapter if the corresponding |
| // capability `supportsValueFormattingOptions` is true." |
| // } |
| // }, |
| // "required": [ "threadId" ] |
| // }, |
| // "StackTraceResponse": { |
| // "allOf": [ { "$ref": "#/definitions/Response" }, { |
| // "type": "object", |
| // "description": "Response to `stackTrace` request.", |
| // "properties": { |
| // "body": { |
| // "type": "object", |
| // "properties": { |
| // "stackFrames": { |
| // "type": "array", |
| // "items": { |
| // "$ref": "#/definitions/StackFrame" |
| // }, |
| // "description": "The frames of the stackframe. If the array has |
| // length zero, there are no stackframes available. This means that |
| // there is no location information available." |
| // }, |
| // "totalFrames": { |
| // "type": "integer", |
| // "description": "The total number of frames available in the |
| // stack. If omitted or if `totalFrames` is larger than the |
| // available frames, a client is expected to request frames until |
| // a request returns less frames than requested (which indicates |
| // the end of the stack). Returning monotonically increasing |
| // `totalFrames` values for subsequent requests can be used to |
| // enforce paging in the client." |
| // } |
| // }, |
| // "required": [ "stackFrames" ] |
| // } |
| // }, |
| // "required": [ "body" ] |
| // }] |
| // } |
| void StackTraceRequestHandler::operator()( |
| const llvm::json::Object &request) const { |
| llvm::json::Object response; |
| FillResponse(request, response); |
| lldb::SBError error; |
| const auto *arguments = request.getObject("arguments"); |
| lldb::SBThread thread = dap.GetLLDBThread(*arguments); |
| llvm::json::Array stack_frames; |
| llvm::json::Object body; |
| |
| lldb::SBFormat frame_format = dap.frame_format; |
| bool include_all = dap.configuration.displayExtendedBacktrace; |
| |
| if (const auto *format = arguments->getObject("format")) { |
| // Indicates that all stack frames should be included, even those the debug |
| // adapter might otherwise hide. |
| include_all = GetBoolean(format, "includeAll").value_or(false); |
| |
| // Parse the properties that have a corresponding format string. |
| // FIXME: Support "parameterTypes" and "hex". |
| const bool module = GetBoolean(format, "module").value_or(false); |
| const bool line = GetBoolean(format, "line").value_or(false); |
| const bool parameters = GetBoolean(format, "parameters").value_or(false); |
| const bool parameter_names = |
| GetBoolean(format, "parameterNames").value_or(false); |
| const bool parameter_values = |
| GetBoolean(format, "parameterValues").value_or(false); |
| |
| // Only change the format string if we have to. |
| if (module || line || parameters || parameter_names || parameter_values) { |
| std::string format_str; |
| llvm::raw_string_ostream os(format_str); |
| |
| if (module) |
| os << "{${module.file.basename} }"; |
| |
| if (line) |
| os << "{${line.file.basename}:${line.number}:${line.column} }"; |
| |
| if (parameters || parameter_names || parameter_values) |
| os << "{${function.name-with-args}}"; |
| else |
| os << "{${function.name-without-args}}"; |
| |
| lldb::SBError error; |
| frame_format = lldb::SBFormat(format_str.c_str(), error); |
| assert(error.Success()); |
| } |
| } |
| |
| if (thread.IsValid()) { |
| const auto start_frame = |
| GetInteger<uint64_t>(arguments, "startFrame").value_or(0); |
| const auto levels = GetInteger<uint64_t>(arguments, "levels").value_or(0); |
| int64_t offset = 0; |
| bool reached_end_of_stack = FillStackFrames( |
| dap, thread, frame_format, stack_frames, offset, start_frame, |
| levels == 0 ? INT64_MAX : levels, include_all); |
| body.try_emplace("totalFrames", |
| start_frame + stack_frames.size() + |
| (reached_end_of_stack ? 0 : StackPageSize)); |
| } |
| |
| body.try_emplace("stackFrames", std::move(stack_frames)); |
| response.try_emplace("body", std::move(body)); |
| dap.SendJSON(llvm::json::Value(std::move(response))); |
| } |
| |
| } // namespace lldb_dap |