[Lldb-commits] [lldb] [lldb-dap] Add an option to show function args in stack frames (PR #71843)

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Thu Nov 9 18:05:49 PST 2023


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 <walter at 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": ""
 							}
 						}
 					}



More information about the lldb-commits mailing list