diff --git a/lldb/bindings/headers.swig b/lldb/bindings/headers.swig index b1d88726f7543..408db90b925f1 100644 --- a/lldb/bindings/headers.swig +++ b/lldb/bindings/headers.swig @@ -30,6 +30,7 @@ #include "lldb/API/SBFile.h" #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBFileSpecList.h" +#include "lldb/API/SBFormat.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBFunction.h" #include "lldb/API/SBHostOS.h" diff --git a/lldb/bindings/interface/SBFormatDocstrings.i b/lldb/bindings/interface/SBFormatDocstrings.i new file mode 100644 index 0000000000000..a7304f5f6a848 --- /dev/null +++ b/lldb/bindings/interface/SBFormatDocstrings.i @@ -0,0 +1,5 @@ +%feature("docstring", +"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." +) lldb::SBFormat; diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig index 373c2f6cf545c..9ca479218f621 100644 --- a/lldb/bindings/interfaces.swig +++ b/lldb/bindings/interfaces.swig @@ -34,6 +34,7 @@ %include "./interface/SBFileDocstrings.i" %include "./interface/SBFileSpecDocstrings.i" %include "./interface/SBFileSpecListDocstrings.i" +%include "./interface/SBFormatDocstrings.i" %include "./interface/SBFrameDocstrings.i" %include "./interface/SBFunctionDocstrings.i" %include "./interface/SBHostOSDocstrings.i" @@ -106,6 +107,7 @@ %include "lldb/API/SBFile.h" %include "lldb/API/SBFileSpec.h" %include "lldb/API/SBFileSpecList.h" +%include "lldb/API/SBFormat.h" %include "lldb/API/SBFrame.h" %include "lldb/API/SBFunction.h" %include "lldb/API/SBHostOS.h" diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h index c6f01cc03f263..2630a82df0e71 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 b241052ed9cc2..1a720a479d9a6 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 0000000000000..1bbaad18cafaf --- /dev/null +++ b/lldb/include/lldb/API/SBFormat.h @@ -0,0 +1,65 @@ +//===-- 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(); + + /// Create an \a SBFormat by parsing the given format string. If parsing + /// fails, this object is initialized as invalid. + /// + /// \param[in] format + /// The format string to parse. + /// + /// \param[out] error + /// An object where error messages will be written to if parsing fails. + SBFormat(const char *format, lldb::SBError &error); + + SBFormat(const lldb::SBFormat &rhs); + + lldb::SBFormat &operator=(const lldb::SBFormat &rhs); + + ~SBFormat(); + + /// \return + /// \b true if and only if this object is valid and can be used for + /// formatting. + explicit operator bool() const; + +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 7c4477f9125d1..821ff3cf7ce51 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,21 @@ 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. + /// + /// \return + /// An error object with an error message in case of failures. + SBError GetDescriptionWithFormat(const SBFormat &format, SBStream &output); + protected: friend class SBBlock; friend class SBExecutionContext; diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h index a4972be514cfc..36f6df4118c21 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 - 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 + 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(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(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 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 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 6824d916030a0..6c18511c6e1ac 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,23 @@ 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. + /// + /// \return + /// \b true if and only if dumping with the given \p format worked. + bool DumpUsingFormat(Stream &strm, + const lldb_private::FormatEntity::Entry *format, + llvm::StringRef frame_marker = {}); + /// 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 f66b2ad4362f9..d38985f5c1f92 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 typedef std::shared_ptr ExpressionVariableSP; typedef std::unique_ptr FileUP; typedef std::shared_ptr FileSP; +typedef std::shared_ptr FormatEntrySP; typedef std::shared_ptr FunctionSP; typedef std::shared_ptr FuncUnwindersSP; typedef std::shared_ptr 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 d1fb478bc8bb9..a41861c59d287 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 aa89ffe24c3e0..2c9f8703d56b4 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 582af90eda8a4..7d478ecc7f599 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 0000000000000..51cddceea0372 --- /dev/null +++ b/lldb/source/API/SBFormat.cpp @@ -0,0 +1,44 @@ +//===-- 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 +#include + +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; +} + +SBFormat::operator bool() const { return (bool)m_opaque_sp; } + +SBFormat::SBFormat(const char *format, lldb::SBError &error) { + FormatEntrySP format_entry_sp = std::make_shared(); + Status status = FormatEntity::Parse(format, *format_entry_sp); + + error.SetError(status); + if (error.Success()) + m_opaque_sp = format_entry_sp; +} + +lldb::FormatEntrySP SBFormat::GetFormatEntrySP() const { return m_opaque_sp; } diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp index da5c6075e8f7b..a16bbc2ae7562 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" @@ -947,6 +948,40 @@ SBValue SBFrame::FindRegister(const char *name) { return result; } +SBError SBFrame::GetDescriptionWithFormat(const SBFormat &format, + SBStream &output) { + Stream &strm = output.ref(); + + std::unique_lock lock; + ExecutionContext exe_ctx(m_opaque_sp.get(), lock); + + StackFrame *frame = nullptr; + Target *target = exe_ctx.GetTargetPtr(); + Process *process = exe_ctx.GetProcessPtr(); + SBError error; + + if (!format) { + error.SetErrorString("The provided SBFormat object is invalid"); + return error; + } + + 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())) { + return error; + } + } + } + error.SetErrorStringWithFormat( + "It was not possible to generate a frame " + "description with the given format string '%s'", + format.GetFormatEntrySP()->string.c_str()); + return error; +} + 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 00ab20243855b..d8047b4242065 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=", 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 11ada92348ece..f0d78d8aa5fef 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -1779,18 +1779,30 @@ 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) { + 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; + } + 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,10 +1812,7 @@ 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 { + if (!DumpUsingFormat(*strm, frame_format, frame_marker)) { Dump(strm, true, false); strm->EOL(); } diff --git a/lldb/test/API/python_api/format/TestFormat.py b/lldb/test/API/python_api/format/TestFormat.py new file mode 100644 index 0000000000000..db20f02ba1c1e --- /dev/null +++ b/lldb/test/API/python_api/format/TestFormat.py @@ -0,0 +1,24 @@ +""" +Test the lldb Python SBFormat API. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class FormatAPITestCase(TestBase): + def test_format(self): + format = lldb.SBFormat() + self.assertFalse(format) + + error = lldb.SBError() + format = lldb.SBFormat("${bad}", error) + self.assertIn("invalid top level item 'bad'", error.GetCString()) + self.assertFalse(format) # We expect an invalid object back if we have an error + self.assertTrue(error.Fail()) + + format = lldb.SBFormat("${frame.index}", error) + self.assertIs(error.GetCString(), None) + self.assertTrue(format) + self.assertTrue(error.Success()) 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 245b3f34b70c8..c46d20c64c265 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 1bff198e4ac00..ba111a77d6e54 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -824,4 +824,19 @@ 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(format.data(), error); + if (error.Fail()) { + g_dap.SendOutput( + OutputType::Console, + llvm::formatv( + "The provided frame format '{0}' couldn't be parsed: {1}\n", format, + error.GetCString()) + .str()); + } +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index b00c103c33b7a..11c2cc1fa8646 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 2ff17616c2e99..2023291729762 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; + lldb::SBStream stream; + if (g_dap.frame_format && + frame.GetDescriptionWithFormat(g_dap.frame_format, stream).Success()) { + frame_name = stream.GetData(); + + // `function_name` can be a nullptr, which throws an error when assigned to + // an `std::string`. + } else 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 e103aabb87020..01738b3f5150b 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 a0ae7ac834939..6d6c46b55518c 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. If the format string contains errors, an error message will be displayed on the Debug Console and the default frame names will be used. 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. If the format string contains errors, an error message will be displayed on the Debug Console and the default frame names will be used. This might come with a performance cost because debug information might need to be processed to generate the description.", + "default": "" } } }