[Lldb-commits] [lldb] [lldb-dap] Add an option to provide a format for stack frames (PR #71843)

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Mon Nov 13 16:38:54 PST 2023


https://github.com/walter-erquinigo updated https://github.com/llvm/llvm-project/pull/71843

>From f7e9a2a9e206062f187ae4cc74038bd31ba32d26 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              |  69 +++
 lldb/include/lldb/API/SBFrame.h               |  17 +-
 lldb/include/lldb/Core/FormatEntity.h         | 404 +++++++++---------
 lldb/include/lldb/Target/StackFrame.h         |  18 +-
 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                  |  47 ++
 lldb/source/API/SBFrame.cpp                   |  35 ++
 lldb/source/Core/FormatEntity.cpp             |  20 +-
 lldb/source/Target/StackFrame.cpp             |  25 +-
 .../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, 482 insertions(+), 235 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..acc9467b4e2babb
--- /dev/null
+++ b/lldb/include/lldb/API/SBFormat.h
@@ -0,0 +1,69 @@
+
+//===-- 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();
+
+  /// \return
+  ///   \b true if and only if this object is valid and can be used for
+  ///   formatting.
+  explicit operator bool() 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..821ff3cf7ce519c 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 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..6c18511c6e1ac36 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 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..4ac5b4f7d61f080
--- /dev/null
+++ b/lldb/source/API/SBFormat.cpp
@@ -0,0 +1,47 @@
+//===-- 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;
+}
+
+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;
+  error.SetError(status);
+  if (error.Success())
+    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..a16bbc2ae7562de 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<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();
+  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 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..f0d78d8aa5fefc1 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/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..8fd1a688b9bf4ba 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.Fail()) {
+    llvm::errs() << "The provided frame format '" << format
+                 << "' couldn't be parsed. " << 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..2023291729762f1 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 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..6d6c46b55518c07 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": ""
 							}
 						}
 					}



More information about the lldb-commits mailing list