[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:06:17 PST 2023
https://github.com/walter-erquinigo updated https://github.com/llvm/llvm-project/pull/71843
>From 2f3841dc6d68f755b11f6677ed2d034a88a297c8 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..b9a2dd11bb91dc6 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 is wrong, the default display names for frames 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 is wrong, the default display names for frames 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