https://github.com/walter-erquinigo updated https://github.com/llvm/llvm-project/pull/71843
>From 7b9b5897e2e801d7bc97639ed302b885481659bd Mon Sep 17 00:00:00 2001 From: walter erquinigo <wal...@modular.com> Date: Thu, 9 Nov 2023 13:15:55 -0500 Subject: [PATCH] [lldb-dap] Add an option to show function args in stack frames When this option is enabled, display names of stack frames are generated using the `${function.name-with-args}` formatter instead of simply calling `SBFrame::GetDisplayFunctionName`. This makes lldb-dap show an output similar to the one in the CLI. This option is disabled by default because of its performance cost. It's a good option for non-gigantic programs. --- lldb/include/lldb/API/SBDefines.h | 1 + lldb/include/lldb/API/SBError.h | 1 + lldb/include/lldb/API/SBFormat.h | 72 ++++ lldb/include/lldb/API/SBFrame.h | 23 +- lldb/include/lldb/Core/FormatEntity.h | 404 +++++++++--------- lldb/include/lldb/Target/StackFrame.h | 24 +- lldb/include/lldb/lldb-forward.h | 4 + .../test/tools/lldb-dap/dap_server.py | 4 + .../test/tools/lldb-dap/lldbdap_testcase.py | 4 + lldb/source/API/CMakeLists.txt | 1 + lldb/source/API/SBFormat.cpp | 50 +++ lldb/source/API/SBFrame.cpp | 40 +- lldb/source/Core/FormatEntity.cpp | 20 +- lldb/source/Target/StackFrame.cpp | 35 +- .../lldb-dap/stackTrace/TestDAP_stackTrace.py | 23 +- lldb/tools/lldb-dap/DAP.cpp | 11 + lldb/tools/lldb-dap/DAP.h | 4 + lldb/tools/lldb-dap/JSONUtils.cpp | 17 +- lldb/tools/lldb-dap/lldb-dap.cpp | 2 + lldb/tools/lldb-dap/package.json | 10 + 20 files changed, 507 insertions(+), 243 deletions(-) create mode 100644 lldb/include/lldb/API/SBFormat.h create mode 100644 lldb/source/API/SBFormat.cpp diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h index c6f01cc03f263c8..2630a82df0e7135 100644 --- a/lldb/include/lldb/API/SBDefines.h +++ b/lldb/include/lldb/API/SBDefines.h @@ -71,6 +71,7 @@ class LLDB_API SBExpressionOptions; class LLDB_API SBFile; class LLDB_API SBFileSpec; class LLDB_API SBFileSpecList; +class LLDB_API SBFormat; class LLDB_API SBFrame; class LLDB_API SBFunction; class LLDB_API SBHostOS; diff --git a/lldb/include/lldb/API/SBError.h b/lldb/include/lldb/API/SBError.h index b241052ed9cc2a2..1a720a479d9a689 100644 --- a/lldb/include/lldb/API/SBError.h +++ b/lldb/include/lldb/API/SBError.h @@ -80,6 +80,7 @@ class LLDB_API SBError { friend class SBData; friend class SBDebugger; friend class SBFile; + friend class SBFormat; friend class SBHostOS; friend class SBPlatform; friend class SBProcess; diff --git a/lldb/include/lldb/API/SBFormat.h b/lldb/include/lldb/API/SBFormat.h new file mode 100644 index 000000000000000..b913091fbada524 --- /dev/null +++ b/lldb/include/lldb/API/SBFormat.h @@ -0,0 +1,72 @@ + +//===-- SBFormat.h ----------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_API_SBFORMAT_H +#define LLDB_API_SBFORMAT_H + +#include "lldb/API/SBDefines.h" + +namespace lldb_private { +namespace python { +class SWIGBridge; +} // namespace python +namespace lua { +class SWIGBridge; +} // namespace lua +} // namespace lldb_private + +namespace lldb { + +/// Class that represents a format string that can be used to generate +/// descriptions of objects like frames and threads. See +/// https://lldb.llvm.org/use/formatting.html for more information. +class LLDB_API SBFormat { +public: + SBFormat(); + + SBFormat(const lldb::SBFormat &rhs); + + lldb::SBFormat &operator=(const lldb::SBFormat &rhs); + + ~SBFormat(); + + /// bool operator version of \a IsValid(). + explicit operator bool() const; + + /// \return + /// \b true if and only if this object is valid and can be used for + /// formatting. + bool IsValid() const; + + /// Parse the given format string. + /// + /// \param[in] format + /// The format string to parse. + /// + /// \param[out] error + /// An object where error messages will be written to if parsing fails. + /// + /// \return + /// An \a SBFormat object with the parsing result, which might be an invalid + /// object if parsing fails. + static lldb::SBFormat Parse(const char *format, lldb::SBError &error); + +protected: + friend class SBFrame; + + /// \return + /// The underlying shared pointer storage for this object. + lldb::FormatEntrySP GetFormatEntrySP() const; + + /// The storage for this object. + lldb::FormatEntrySP m_opaque_sp; +}; + +} // namespace lldb +#endif // LLDB_API_SBFORMAT_H diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h index 7c4477f9125d1cd..f28652e4d6e7b6e 100644 --- a/lldb/include/lldb/API/SBFrame.h +++ b/lldb/include/lldb/API/SBFrame.h @@ -88,7 +88,7 @@ class LLDB_API SBFrame { const char *GetDisplayFunctionName(); const char *GetFunctionName() const; - + // Return the frame function's language. If there isn't a function, then // guess the language type from the mangled name. lldb::LanguageType GuessLanguage() const; @@ -193,6 +193,27 @@ class LLDB_API SBFrame { bool GetDescription(lldb::SBStream &description); + /// Similar to \a GetDescription() but the format of the description can be + /// configured via the \p format parameter. See + /// https://lldb.llvm.org/use/formatting.html for more information on format + /// strings. + /// + /// \param[in] format + /// The format to use for generating the description. + /// + /// \param[out] output + /// The stream where the description will be written to. + /// + /// \param[in] default_value + /// If the description couldn't be generated, and this parameter is not + /// null, it will be printed to the \p output stream. This doesn't affect + /// the return value of this method. + /// + /// \return + /// \b true if and only if a description for the frame was generated. + bool GetDescriptionWithFormat(const SBFormat &format, SBStream &output, + const char *default_value = nullptr); + protected: friend class SBBlock; friend class SBExecutionContext; diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h index a4972be514cfcc5..36f6df4118c21f2 100644 --- a/lldb/include/lldb/Core/FormatEntity.h +++ b/lldb/include/lldb/Core/FormatEntity.h @@ -35,227 +35,217 @@ class StringRef; } namespace lldb_private { -class FormatEntity { -public: - struct Entry { - enum class Type { - Invalid, - ParentNumber, - ParentString, - EscapeCode, - Root, - String, - Scope, - Variable, - VariableSynthetic, - ScriptVariable, - ScriptVariableSynthetic, - AddressLoad, - AddressFile, - AddressLoadOrFile, - ProcessID, - ProcessFile, - ScriptProcess, - ThreadID, - ThreadProtocolID, - ThreadIndexID, - ThreadName, - ThreadQueue, - ThreadStopReason, - ThreadStopReasonRaw, - ThreadReturnValue, - ThreadCompletedExpression, - ScriptThread, - ThreadInfo, - TargetArch, - ScriptTarget, - ModuleFile, - File, - Lang, - FrameIndex, - FrameNoDebug, - FrameRegisterPC, - FrameRegisterSP, - FrameRegisterFP, - FrameRegisterFlags, - FrameRegisterByName, - FrameIsArtificial, - ScriptFrame, - FunctionID, - FunctionDidChange, - FunctionInitialFunction, - FunctionName, - FunctionNameWithArgs, - FunctionNameNoArgs, - FunctionMangledName, - FunctionAddrOffset, - FunctionAddrOffsetConcrete, - FunctionLineOffset, - FunctionPCOffset, - FunctionInitial, - FunctionChanged, - FunctionIsOptimized, - LineEntryFile, - LineEntryLineNumber, - LineEntryColumn, - LineEntryStartAddress, - LineEntryEndAddress, - CurrentPCArrow - }; - - struct Definition { - /// The name/string placeholder that corresponds to this definition. - const char *name; - /// Insert this exact string into the output - const char *string = nullptr; - /// Entry::Type corresponding to this definition. - const Entry::Type type; - /// Data that is returned as the value of the format string. - const uint64_t data = 0; - /// The number of children of this node in the tree of format strings. - const uint32_t num_children = 0; - /// An array of "num_children" Definition entries. - const Definition *children = nullptr; - /// Whether the separator is kept during parsing or not. It's used - /// for entries with parameters. - const bool keep_separator = false; - - constexpr Definition(const char *name, const FormatEntity::Entry::Type t) - : name(name), type(t) {} - - constexpr Definition(const char *name, const char *string) - : name(name), string(string), type(Entry::Type::EscapeCode) {} - - constexpr Definition(const char *name, const FormatEntity::Entry::Type t, - const uint64_t data) - : name(name), type(t), data(data) {} - - constexpr Definition(const char *name, const FormatEntity::Entry::Type t, - const uint64_t num_children, - const Definition *children, - const bool keep_separator = false) - : name(name), type(t), num_children(num_children), children(children), - keep_separator(keep_separator) {} - }; - - template <size_t N> - static constexpr Definition - DefinitionWithChildren(const char *name, const FormatEntity::Entry::Type t, - const Definition (&children)[N], - bool keep_separator = false) { - return Definition(name, t, N, children, keep_separator); - } +namespace FormatEntity { +struct Entry { + enum class Type { + Invalid, + ParentNumber, + ParentString, + EscapeCode, + Root, + String, + Scope, + Variable, + VariableSynthetic, + ScriptVariable, + ScriptVariableSynthetic, + AddressLoad, + AddressFile, + AddressLoadOrFile, + ProcessID, + ProcessFile, + ScriptProcess, + ThreadID, + ThreadProtocolID, + ThreadIndexID, + ThreadName, + ThreadQueue, + ThreadStopReason, + ThreadStopReasonRaw, + ThreadReturnValue, + ThreadCompletedExpression, + ScriptThread, + ThreadInfo, + TargetArch, + ScriptTarget, + ModuleFile, + File, + Lang, + FrameIndex, + FrameNoDebug, + FrameRegisterPC, + FrameRegisterSP, + FrameRegisterFP, + FrameRegisterFlags, + FrameRegisterByName, + FrameIsArtificial, + ScriptFrame, + FunctionID, + FunctionDidChange, + FunctionInitialFunction, + FunctionName, + FunctionNameWithArgs, + FunctionNameNoArgs, + FunctionMangledName, + FunctionAddrOffset, + FunctionAddrOffsetConcrete, + FunctionLineOffset, + FunctionPCOffset, + FunctionInitial, + FunctionChanged, + FunctionIsOptimized, + LineEntryFile, + LineEntryLineNumber, + LineEntryColumn, + LineEntryStartAddress, + LineEntryEndAddress, + CurrentPCArrow + }; - Entry(Type t = Type::Invalid, const char *s = nullptr, - const char *f = nullptr) - : string(s ? s : ""), printf_format(f ? f : ""), type(t) {} + struct Definition { + /// The name/string placeholder that corresponds to this definition. + const char *name; + /// Insert this exact string into the output + const char *string = nullptr; + /// Entry::Type corresponding to this definition. + const Entry::Type type; + /// Data that is returned as the value of the format string. + const uint64_t data = 0; + /// The number of children of this node in the tree of format strings. + const uint32_t num_children = 0; + /// An array of "num_children" Definition entries. + const Definition *children = nullptr; + /// Whether the separator is kept during parsing or not. It's used + /// for entries with parameters. + const bool keep_separator = false; + + constexpr Definition(const char *name, const FormatEntity::Entry::Type t) + : name(name), type(t) {} + + constexpr Definition(const char *name, const char *string) + : name(name), string(string), type(Entry::Type::EscapeCode) {} + + constexpr Definition(const char *name, const FormatEntity::Entry::Type t, + const uint64_t data) + : name(name), type(t), data(data) {} + + constexpr Definition(const char *name, const FormatEntity::Entry::Type t, + const uint64_t num_children, + const Definition *children, + const bool keep_separator = false) + : name(name), type(t), num_children(num_children), children(children), + keep_separator(keep_separator) {} + }; - Entry(llvm::StringRef s); - Entry(char ch); + template <size_t N> + static constexpr Definition + DefinitionWithChildren(const char *name, const FormatEntity::Entry::Type t, + const Definition (&children)[N], + bool keep_separator = false) { + return Definition(name, t, N, children, keep_separator); + } - void AppendChar(char ch); + Entry(Type t = Type::Invalid, const char *s = nullptr, + const char *f = nullptr) + : string(s ? s : ""), printf_format(f ? f : ""), type(t) {} - void AppendText(const llvm::StringRef &s); + Entry(llvm::StringRef s); + Entry(char ch); - void AppendText(const char *cstr); + void AppendChar(char ch); - void AppendEntry(const Entry &&entry) { children.push_back(entry); } + void AppendText(const llvm::StringRef &s); - void Clear() { - string.clear(); - printf_format.clear(); - children.clear(); - type = Type::Invalid; - fmt = lldb::eFormatDefault; - number = 0; - deref = false; - } + void AppendText(const char *cstr); - static const char *TypeToCString(Type t); + void AppendEntry(const Entry &&entry) { children.push_back(entry); } - void Dump(Stream &s, int depth = 0) const; + void Clear() { + string.clear(); + printf_format.clear(); + children.clear(); + type = Type::Invalid; + fmt = lldb::eFormatDefault; + number = 0; + deref = false; + } - bool operator==(const Entry &rhs) const { - if (string != rhs.string) - return false; - if (printf_format != rhs.printf_format) - return false; - const size_t n = children.size(); - const size_t m = rhs.children.size(); - for (size_t i = 0; i < std::min<size_t>(n, m); ++i) { - if (!(children[i] == rhs.children[i])) - return false; - } - if (children != rhs.children) - return false; - if (type != rhs.type) - return false; - if (fmt != rhs.fmt) - return false; - if (deref != rhs.deref) + static const char *TypeToCString(Type t); + + void Dump(Stream &s, int depth = 0) const; + + bool operator==(const Entry &rhs) const { + if (string != rhs.string) + return false; + if (printf_format != rhs.printf_format) + return false; + const size_t n = children.size(); + const size_t m = rhs.children.size(); + for (size_t i = 0; i < std::min<size_t>(n, m); ++i) { + if (!(children[i] == rhs.children[i])) return false; - return true; } + if (children != rhs.children) + return false; + if (type != rhs.type) + return false; + if (fmt != rhs.fmt) + return false; + if (deref != rhs.deref) + return false; + return true; + } + + std::string string; + std::string printf_format; + std::vector<Entry> children; + Type type; + lldb::Format fmt = lldb::eFormatDefault; + lldb::addr_t number = 0; + bool deref = false; +}; - std::string string; - std::string printf_format; - std::vector<Entry> children; - Type type; - lldb::Format fmt = lldb::eFormatDefault; - lldb::addr_t number = 0; - bool deref = false; - }; +bool Format(const Entry &entry, Stream &s, const SymbolContext *sc, + const ExecutionContext *exe_ctx, const Address *addr, + ValueObject *valobj, bool function_changed, bool initial_function); - static bool Format(const Entry &entry, Stream &s, const SymbolContext *sc, - const ExecutionContext *exe_ctx, const Address *addr, - ValueObject *valobj, bool function_changed, - bool initial_function); - - static bool FormatStringRef(const llvm::StringRef &format, Stream &s, - const SymbolContext *sc, - const ExecutionContext *exe_ctx, - const Address *addr, ValueObject *valobj, - bool function_changed, bool initial_function); - - static bool FormatCString(const char *format, Stream &s, - const SymbolContext *sc, - const ExecutionContext *exe_ctx, - const Address *addr, ValueObject *valobj, - bool function_changed, bool initial_function); - - static Status Parse(const llvm::StringRef &format, Entry &entry); - - static Status ExtractVariableInfo(llvm::StringRef &format_str, - llvm::StringRef &variable_name, - llvm::StringRef &variable_format); - - static void AutoComplete(lldb_private::CompletionRequest &request); - - // Format the current elements into the stream \a s. - // - // The root element will be stripped off and the format str passed in will be - // either an empty string (print a description of this object), or contain a - // `.`-separated series like a domain name that identifies further - // sub-elements to display. - static bool FormatFileSpec(const FileSpec &file, Stream &s, - llvm::StringRef elements, - llvm::StringRef element_format); - - /// For each variable in 'args' this function writes the variable - /// name and it's pretty-printed value representation to 'out_stream' - /// in following format: - /// - /// \verbatim - /// name_1=repr_1, name_2=repr_2 ... - /// \endverbatim - static void PrettyPrintFunctionArguments(Stream &out_stream, - VariableList const &args, - ExecutionContextScope *exe_scope); - -protected: - static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry, - uint32_t depth); -}; +bool FormatStringRef(const llvm::StringRef &format, Stream &s, + const SymbolContext *sc, const ExecutionContext *exe_ctx, + const Address *addr, ValueObject *valobj, + bool function_changed, bool initial_function); + +bool FormatCString(const char *format, Stream &s, const SymbolContext *sc, + const ExecutionContext *exe_ctx, const Address *addr, + ValueObject *valobj, bool function_changed, + bool initial_function); + +Status Parse(const llvm::StringRef &format, Entry &entry); + +Status ExtractVariableInfo(llvm::StringRef &format_str, + llvm::StringRef &variable_name, + llvm::StringRef &variable_format); + +void AutoComplete(lldb_private::CompletionRequest &request); + +// Format the current elements into the stream \a s. +// +// The root element will be stripped off and the format str passed in will be +// either an empty string (print a description of this object), or contain a +// `.`-separated series like a domain name that identifies further +// sub-elements to display. +bool FormatFileSpec(const FileSpec &file, Stream &s, llvm::StringRef elements, + llvm::StringRef element_format); + +/// For each variable in 'args' this function writes the variable +/// name and it's pretty-printed value representation to 'out_stream' +/// in following format: +/// +/// \verbatim +/// name_1=repr_1, name_2=repr_2 ... +/// \endverbatim +void PrettyPrintFunctionArguments(Stream &out_stream, VariableList const &args, + ExecutionContextScope *exe_scope); +} // namespace FormatEntity } // namespace lldb_private #endif // LLDB_CORE_FORMATENTITY_H diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index 6824d916030a024..34eecb59c7bfff9 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -14,6 +14,7 @@ #include "lldb/Utility/Flags.h" +#include "lldb/Core/FormatEntity.h" #include "lldb/Core/ValueObjectList.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Target/ExecutionContextScope.h" @@ -324,8 +325,29 @@ class StackFrame : public ExecutionContextScope, /// C string with the assembly instructions for this function. const char *Disassemble(); + /// Print a description of this frame using the provided frame format. + /// + /// \param[out] strm + /// The Stream to print the description to. + /// + /// \param[in] frame_marker + /// Optional string that will be prepended to the frame output description. + /// + /// \param[in] use_fallback_format_on_error + /// If dumping with the given \p format fails and this flag is enabled, then + /// dumping will be retried with a default fallback format. + /// + /// \return + /// \b true if and only if dumping with the given \p format or with the + /// fallback format worked. + bool DumpUsingFormat(Stream &strm, + const lldb_private::FormatEntity::Entry *format, + llvm::StringRef frame_marker = {}, + bool use_fallback_format_on_error = true); + /// Print a description for this frame using the frame-format formatter - /// settings. + /// settings. If the current frame-format settings are invalid, then the + /// default formatter will be used (see \a StackFrame::Dump()). /// /// \param [in] strm /// The Stream to print the description to. diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index f66b2ad4362f926..d38985f5c1f9238 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -96,6 +96,9 @@ class File; class FileSpec; class FileSpecList; class Flags; +namespace FormatEntity { +struct Entry; +} // namespace FormatEntity class FormatManager; class FormattersMatchCandidate; class FuncUnwinders; @@ -335,6 +338,7 @@ typedef std::shared_ptr<lldb_private::ExecutionContextRef> typedef std::shared_ptr<lldb_private::ExpressionVariable> ExpressionVariableSP; typedef std::unique_ptr<lldb_private::File> FileUP; typedef std::shared_ptr<lldb_private::File> FileSP; +typedef std::shared_ptr<lldb_private::FormatEntity::Entry> FormatEntrySP; typedef std::shared_ptr<lldb_private::Function> FunctionSP; typedef std::shared_ptr<lldb_private::FuncUnwinders> FuncUnwindersSP; typedef std::shared_ptr<lldb_private::InlineFunctionInfo> InlineFunctionInfoSP; diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index d1fb478bc8bb9ee..a41861c59d2875a 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -732,6 +732,7 @@ def request_launch( enableAutoVariableSummaries=False, enableSyntheticChildDebugging=False, commandEscapePrefix="`", + customFrameFormat=None, ): args_dict = {"program": program} if args: @@ -773,6 +774,9 @@ def request_launch( args_dict["runInTerminal"] = runInTerminal if postRunCommands: args_dict["postRunCommands"] = postRunCommands + if customFrameFormat: + args_dict["customFrameFormat"] = customFrameFormat + args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging args_dict["commandEscapePrefix"] = commandEscapePrefix diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index aa89ffe24c3e026..2c9f8703d56b405 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -352,6 +352,7 @@ def launch( enableAutoVariableSummaries=False, enableSyntheticChildDebugging=False, commandEscapePrefix="`", + customFrameFormat=None, ): """Sending launch request to dap""" @@ -391,6 +392,7 @@ def cleanup(): enableAutoVariableSummaries=enableAutoVariableSummaries, enableSyntheticChildDebugging=enableSyntheticChildDebugging, commandEscapePrefix=commandEscapePrefix, + customFrameFormat=customFrameFormat, ) if expectFailure: @@ -428,6 +430,7 @@ def build_and_launch( enableAutoVariableSummaries=False, enableSyntheticChildDebugging=False, commandEscapePrefix="`", + customFrameFormat=None, ): """Build the default Makefile target, create the DAP debug adaptor, and launch the process. @@ -459,4 +462,5 @@ def build_and_launch( enableAutoVariableSummaries=enableAutoVariableSummaries, enableSyntheticChildDebugging=enableSyntheticChildDebugging, commandEscapePrefix=commandEscapePrefix, + customFrameFormat=customFrameFormat, ) diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt index 582af90eda8a4e0..7d478ecc7f599e9 100644 --- a/lldb/source/API/CMakeLists.txt +++ b/lldb/source/API/CMakeLists.txt @@ -45,6 +45,7 @@ add_lldb_library(liblldb SHARED ${option_framework} SBFileSpec.cpp SBFile.cpp SBFileSpecList.cpp + SBFormat.cpp SBFrame.cpp SBFunction.cpp SBHostOS.cpp diff --git a/lldb/source/API/SBFormat.cpp b/lldb/source/API/SBFormat.cpp new file mode 100644 index 000000000000000..22040f67ad225a1 --- /dev/null +++ b/lldb/source/API/SBFormat.cpp @@ -0,0 +1,50 @@ +//===-- SBFormat.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 "lldb/API/SBFormat.h" +#include "Utils.h" +#include "lldb/Core/FormatEntity.h" +#include "lldb/lldb-types.h" +#include <lldb/API/SBError.h> +#include <lldb/Utility/Status.h> + +using namespace lldb; +using namespace lldb_private; + +SBFormat::SBFormat() : m_opaque_sp() {} + +SBFormat::SBFormat(const SBFormat &rhs) { + m_opaque_sp = clone(rhs.m_opaque_sp); +} + +SBFormat::~SBFormat() = default; + +SBFormat &SBFormat::operator=(const SBFormat &rhs) { + if (this != &rhs) + m_opaque_sp = clone(rhs.m_opaque_sp); + return *this; +} + +bool SBFormat::IsValid() const { return this->operator bool(); } + +SBFormat::operator bool() const { return (bool)m_opaque_sp; } + +SBFormat SBFormat::Parse(const char *format, lldb::SBError &error) { + FormatEntrySP format_entry_sp = std::make_shared<FormatEntity::Entry>(); + Status status = FormatEntity::Parse(format, *format_entry_sp); + + SBFormat sb_format; + if (status.Fail()) + error.SetError(status); + else + sb_format.m_opaque_sp = format_entry_sp; + + return sb_format; +} + +lldb::FormatEntrySP SBFormat::GetFormatEntrySP() const { return m_opaque_sp; } diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp index da5c6075e8f7b4b..9eb2428e3e0180c 100644 --- a/lldb/source/API/SBFrame.cpp +++ b/lldb/source/API/SBFrame.cpp @@ -45,6 +45,7 @@ #include "lldb/API/SBAddress.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBExpressionOptions.h" +#include "lldb/API/SBFormat.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBSymbolContext.h" #include "lldb/API/SBThread.h" @@ -601,8 +602,8 @@ SBValue SBFrame::FindValue(const char *name, ValueType value_type, stop_if_block_is_inlined_function, [frame](Variable *v) { return v->IsInScope(frame); }, &variable_list); - if (value_type == eValueTypeVariableGlobal - || value_type == eValueTypeVariableStatic) { + if (value_type == eValueTypeVariableGlobal || + value_type == eValueTypeVariableStatic) { const bool get_file_globals = true; VariableList *frame_vars = frame->GetVariableList(get_file_globals, nullptr); @@ -814,9 +815,11 @@ SBValueList SBFrame::GetVariables(const lldb::SBVariablesOptions &options) { if (num_variables) { size_t num_produced = 0; for (const VariableSP &variable_sp : *variable_list) { - if (INTERRUPT_REQUESTED(dbg, - "Interrupted getting frame variables with {0} of {1} " - "produced.", num_produced, num_variables)) + if (INTERRUPT_REQUESTED( + dbg, + "Interrupted getting frame variables with {0} of {1} " + "produced.", + num_produced, num_variables)) return {}; if (variable_sp) { @@ -947,6 +950,33 @@ SBValue SBFrame::FindRegister(const char *name) { return result; } +bool SBFrame::GetDescriptionWithFormat(const SBFormat &format, SBStream &output, + const char *default_value) { + Stream &strm = output.ref(); + + std::unique_lock<std::recursive_mutex> lock; + ExecutionContext exe_ctx(m_opaque_sp.get(), lock); + + StackFrame *frame = nullptr; + Target *target = exe_ctx.GetTargetPtr(); + Process *process = exe_ctx.GetProcessPtr(); + + if (target && process) { + Process::StopLocker stop_locker; + if (stop_locker.TryLock(&process->GetRunLock())) { + frame = exe_ctx.GetFramePtr(); + if (frame && + frame->DumpUsingFormat(strm, format.GetFormatEntrySP().get(), + /*frame_marker=*/{}, + /*use_fallback_format_on_error=*/true)) { + return true; + } + } + } + strm << default_value; + return false; +} + bool SBFrame::GetDescription(SBStream &description) { LLDB_INSTRUMENT_VA(this, description); diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp index 00ab20243855b28..d8047b424206579 100644 --- a/lldb/source/Core/FormatEntity.cpp +++ b/lldb/source/Core/FormatEntity.cpp @@ -286,13 +286,6 @@ void FormatEntity::Entry::AppendText(const char *cstr) { return AppendText(llvm::StringRef(cstr)); } -Status FormatEntity::Parse(const llvm::StringRef &format_str, Entry &entry) { - entry.Clear(); - entry.type = Entry::Type::Root; - llvm::StringRef modifiable_format(format_str); - return ParseInternal(modifiable_format, entry, 0); -} - #define ENUM_TO_CSTR(eee) \ case FormatEntity::Entry::Type::eee: \ return #eee @@ -1991,8 +1984,8 @@ static const Definition *FindEntry(const llvm::StringRef &format_str, return parent; } -Status FormatEntity::ParseInternal(llvm::StringRef &format, Entry &parent_entry, - uint32_t depth) { +static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry, + uint32_t depth) { Status error; while (!format.empty() && error.Success()) { const size_t non_special_chars = format.find_first_of("${}\\"); @@ -2017,7 +2010,7 @@ Status FormatEntity::ParseInternal(llvm::StringRef &format, Entry &parent_entry, case '{': { format = format.drop_front(); // Skip the '{' Entry scope_entry(Entry::Type::Scope); - error = FormatEntity::ParseInternal(format, scope_entry, depth + 1); + error = ParseInternal(format, scope_entry, depth + 1); if (error.Fail()) return error; parent_entry.AppendEntry(std::move(scope_entry)); @@ -2467,3 +2460,10 @@ void FormatEntity::PrettyPrintFunctionArguments( out_stream.Printf("%s=<unavailable>", var_name); } } + +Status FormatEntity::Parse(const llvm::StringRef &format_str, Entry &entry) { + entry.Clear(); + entry.type = Entry::Type::Root; + llvm::StringRef modifiable_format(format_str); + return ParseInternal(modifiable_format, entry, 0); +} diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 11ada92348ecee2..edcd49208fd8844 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -1779,18 +1779,36 @@ void StackFrame::CalculateExecutionContext(ExecutionContext &exe_ctx) { exe_ctx.SetContext(shared_from_this()); } +bool StackFrame::DumpUsingFormat(Stream &strm, + const FormatEntity::Entry *format, + llvm::StringRef frame_marker, + bool use_fallback_format_on_error) { + GetSymbolContext(eSymbolContextEverything); + ExecutionContext exe_ctx(shared_from_this()); + StreamString s; + s.PutCString(frame_marker); + + if (format && FormatEntity::Format(*format, s, &m_sc, &exe_ctx, nullptr, + nullptr, false, false)) { + strm.PutCString(s.GetString()); + return true; + } + if (use_fallback_format_on_error) { + Dump(&strm, true, false); + strm.EOL(); + return true; + } + return false; +} + void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique, const char *frame_marker) { if (strm == nullptr) return; - GetSymbolContext(eSymbolContextEverything); ExecutionContext exe_ctx(shared_from_this()); StreamString s; - if (frame_marker) - s.PutCString(frame_marker); - const FormatEntity::Entry *frame_format = nullptr; Target *target = exe_ctx.GetTargetPtr(); if (target) { @@ -1800,13 +1818,8 @@ void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique, frame_format = target->GetDebugger().GetFrameFormat(); } } - if (frame_format && FormatEntity::Format(*frame_format, s, &m_sc, &exe_ctx, - nullptr, nullptr, false, false)) { - strm->PutCString(s.GetString()); - } else { - Dump(strm, true, false); - strm->EOL(); - } + DumpUsingFormat(*strm, frame_format, frame_marker, + /*use_fallback_format_on_error=*/true); } void StackFrame::Dump(Stream *strm, bool show_frame_index, diff --git a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py index 245b3f34b70c868..c46d20c64c265b5 100644 --- a/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py +++ b/lldb/test/API/tools/lldb-dap/stackTrace/TestDAP_stackTrace.py @@ -3,12 +3,13 @@ """ +import os + import dap_server +import lldbdap_testcase +from lldbsuite.test import lldbutil from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * -from lldbsuite.test import lldbutil -import lldbdap_testcase -import os class TestDAP_stackTrace(lldbdap_testcase.DAPTestCaseBase): @@ -187,3 +188,19 @@ def test_stackTrace(self): self.assertEquals( 0, len(stackFrames), "verify zero frames with startFrame out of bounds" ) + + @skipIfWindows + @skipIfRemote + def test_functionNameWithArgs(self): + """ + Test that the stack frame without a function name is given its pc in the response. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program, customFrameFormat="${function.name-with-args}") + source = "main.c" + + self.set_source_breakpoints(source, [line_number(source, "recurse end")]) + + self.continue_to_next_stop() + frame = self.get_stackFrames()[0] + self.assertEquals(frame["name"], "recurse(x=1)") diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 1bff198e4ac000f..70b7298c141960e 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -824,4 +824,15 @@ bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger, return true; } +void DAP::SetFrameFormat(llvm::StringRef format) { + if (format.empty()) + return; + lldb::SBError error; + g_dap.frame_format = lldb::SBFormat::Parse(format.data(), error); + if (error) { + llvm::errs() << "The provided frame format '" << format + << "' failed parsing. " << error.GetCString() << "\n"; + } +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index b00c103c33b7a92..11c2cc1fa8646a9 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -36,6 +36,7 @@ #include "lldb/API/SBCommunication.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBEvent.h" +#include "lldb/API/SBFormat.h" #include "lldb/API/SBHostOS.h" #include "lldb/API/SBInstruction.h" #include "lldb/API/SBInstructionList.h" @@ -189,6 +190,7 @@ struct DAP { ReplMode repl_mode; bool auto_repl_mode_collision_warning; std::string command_escape_prefix = "`"; + lldb::SBFormat frame_format; DAP(); ~DAP(); @@ -305,6 +307,8 @@ struct DAP { /// \return Error if waiting for the process fails, no error if succeeds. lldb::SBError WaitForProcessToStop(uint32_t seconds); + void SetFrameFormat(llvm::StringRef format); + private: // Send the JSON in "json_str" to the "out" stream. Correctly send the // "Content-Length:" field followed by the length, followed by the raw diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 2ff17616c2e9986..35eee4b65886271 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -785,11 +785,18 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { int64_t frame_id = MakeDAPFrameID(frame); object.try_emplace("id", frame_id); - // `function_name` can be a nullptr, which throws an error when assigned to an - // `std::string`. - const char *function_name = frame.GetDisplayFunctionName(); - std::string frame_name = - function_name == nullptr ? std::string() : function_name; + std::string frame_name; + if (g_dap.frame_format.IsValid()) { + lldb::SBStream stream; + frame.GetDescriptionWithFormat(g_dap.frame_format, stream, "No value"); + frame_name = stream.GetData(); + } else { + // `function_name` can be a nullptr, which throws an error when assigned to + // an `std::string`. + if (const char *name = frame.GetDisplayFunctionName()) + frame_name = name; + } + if (frame_name.empty()) { // If the function name is unavailable, display the pc address as a 16-digit // hex string, e.g. "0x0000000000012345" diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index e103aabb870207f..01738b3f5150ba3 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -653,6 +653,7 @@ void request_attach(const llvm::json::Object &request) { GetBoolean(arguments, "enableSyntheticChildDebugging", false); g_dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`"); + g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat")); // This is a hack for loading DWARF in .o files on Mac where the .o files // in the debug map of the main executable have relative paths which require @@ -1805,6 +1806,7 @@ void request_launch(const llvm::json::Object &request) { GetBoolean(arguments, "enableSyntheticChildDebugging", false); g_dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`"); + g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat")); // This is a hack for loading DWARF in .o files on Mac where the .o files // in the debug map of the main executable have relative paths which require diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index a0ae7ac834939d5..beda7adfcc758cc 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -255,6 +255,11 @@ "type": "string", "description": "The escape prefix to use for executing regular LLDB commands in the Debug Console, instead of printing variables. Defaults to a back-tick (`). If it's an empty string, then all expression in the Debug Console are treated as regular LLDB commands.", "default": "`" + }, + "customFrameFormat": { + "type": "string", + "description": "If non-empty, stack frames will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html for an explanation on format strings for frames. This might come with a performance cost because debug information might need to be processed to generate the description.", + "default": "" } } }, @@ -349,6 +354,11 @@ "type": "string", "description": "The escape prefix character to use for executing regular LLDB commands in the Debug Console, instead of printing variables. Defaults to a back-tick (`). If empty, then all expression in the Debug Console are treated as regular LLDB commands.", "default": "`" + }, + "customFrameFormat": { + "type": "string", + "description": "If non-empty, stack frames will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html for an explanation on format strings for frames. This might come with a performance cost because debug information might need to be processed to generate the description.", + "default": "" } } } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits