//===-- 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 "DAPError.h" #include "EventHelper.h" #include "LLDBUtils.h" #include "Protocol/ProtocolRequests.h" #include "ProtocolUtils.h" #include "RequestHandler.h" #include "lldb/API/SBStream.h" #include "lldb/lldb-enumerations.h" using namespace lldb_dap; using namespace lldb_dap::protocol; /// Page size used for reporting additional frames in the 'stackTrace' request. static constexpr int k_stack_page_size = 20; // Create a "StackFrame" object for a LLDB frame object. static StackFrame CreateStackFrame(DAP &dap, lldb::SBFrame &frame, lldb::SBFormat &format) { StackFrame stack_frame; stack_frame.id = MakeDAPFrameID(frame); lldb::SBStream stream; if (format && frame.GetDescriptionWithFormat(format, stream).Success()) { stack_frame.name = llvm::StringRef(stream.GetData(), stream.GetSize()); // `function_name` can be a nullptr, which throws an error when assigned to // an `std::string`. } else if (llvm::StringRef name = frame.GetDisplayFunctionName(); !name.empty()) { stack_frame.name = name; } if (stack_frame.name.empty()) { // If the function name is unavailable, display the pc address as a 16-digit // hex string, e.g. "0x0000000000012345" stack_frame.name = GetLoadAddressString(frame.GetPC()); } // We only include `[opt]` if a custom frame format is not specified. if (!format && frame.GetFunction().GetIsOptimized()) stack_frame.name += " [opt]"; std::optional source = dap.ResolveSource(frame); if (source && !IsAssemblySource(*source)) { // This is a normal source with a valid line entry. auto line_entry = frame.GetLineEntry(); stack_frame.line = line_entry.GetLine(); stack_frame.column = line_entry.GetColumn(); } else if (frame.GetSymbol().IsValid()) { // This is a source where the disassembly is used, but there is a valid // symbol. Calculate the line of the current PC from the start of the // current symbol. lldb::SBInstructionList inst_list = dap.target.ReadInstructions( frame.GetSymbol().GetStartAddress(), frame.GetPCAddress(), nullptr); size_t inst_line = inst_list.GetSize(); // Line numbers are 1-based. stack_frame.line = inst_line + 1; stack_frame.column = 1; } else { // No valid line entry or symbol. stack_frame.line = 0; stack_frame.column = 0; } stack_frame.source = std::move(source); stack_frame.instructionPointerReference = frame.GetPC(); if (frame.IsArtificial() || frame.IsHidden()) stack_frame.presentationHint = StackFrame::ePresentationHintSubtle; if (const lldb::SBModule module = frame.GetModule()) { if (llvm::StringRef uuid = module.GetUUIDString(); !uuid.empty()) stack_frame.moduleId = uuid.str(); } return stack_frame; } // Create a "StackFrame" label object for a LLDB thread. static StackFrame CreateExtendedStackFrameLabel(lldb::SBThread &thread, lldb::SBFormat &format) { StackFrame stack_frame; lldb::SBStream stream; if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { stack_frame.name = llvm::StringRef(stream.GetData(), stream.GetSize()); } else { const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID(); if (llvm::StringRef queue_name = thread.GetQueueName(); !queue_name.empty()) { stack_frame.name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name, thread_idx); } else { stack_frame.name = llvm::formatv("Thread {0}", thread_idx); } } stack_frame.id = thread.GetThreadID() + 1; stack_frame.presentationHint = StackFrame::ePresentationHintLabel; stack_frame.line = 0; stack_frame.column = 0; return stack_frame; } // 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, std::vector &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(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(stack_frames.size()) >= levels) break; } } return reached_end_of_stack; } llvm::Expected StackTraceRequestHandler::Run(const protocol::StackTraceArguments &args) const { if (dap.ProcessIsNotStopped()) return llvm::make_error(); lldb::SBThread thread = dap.GetLLDBThread(args.threadId); if (!thread.IsValid()) return llvm::make_error("invalid thread"); lldb::SBFormat frame_format = dap.frame_format; bool include_all = dap.configuration.displayExtendedBacktrace; if (args.format) { const StackFrameFormat &format = *args.format; include_all = format.includeAll; // FIXME: Support "parameterTypes" and "hex". // Only change the format string if we have to. if (format.module || format.line || format.parameters || format.parameterNames || format.parameterValues) { std::string format_str; llvm::raw_string_ostream os(format_str); if (format.module) os << "{${module.file.basename} }"; if (format.line) os << "{${line.file.basename}:${line.number}:${line.column} }"; if (format.parameters || format.parameterNames || format.parameterValues) os << "{${function.name-with-args}}"; else os << "{${function.name-without-args}}"; lldb::SBError error; frame_format = lldb::SBFormat(format_str.c_str(), error); if (error.Fail()) return ToError(error); } } StackTraceResponseBody body; const auto levels = args.levels == 0 ? INT64_MAX : args.levels; int64_t offset = 0; bool reached_end_of_stack = FillStackFrames(dap, thread, frame_format, body.stackFrames, offset, args.startFrame, levels, include_all); body.totalFrames = args.startFrame + body.stackFrames.size() + (reached_end_of_stack ? 0 : k_stack_page_size); return body; }