[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 10:57:13 PST 2023


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

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.


>From 233aade4eb058ad2eba04334b0bbd96f5307ac48 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/SBFrame.h               |  9 ++++-
 lldb/include/lldb/Target/StackFrame.h         | 18 ++++++++-
 .../test/tools/lldb-dap/dap_server.py         |  2 +
 .../test/tools/lldb-dap/lldbdap_testcase.py   |  4 ++
 lldb/source/API/SBFrame.cpp                   | 38 ++++++++++++++++---
 lldb/source/Target/StackFrame.cpp             | 30 +++++++++------
 .../lldb-dap/stackTrace/TestDAP_stackTrace.py | 23 +++++++++--
 lldb/tools/lldb-dap/DAP.h                     |  1 +
 lldb/tools/lldb-dap/JSONUtils.cpp             | 17 ++++++---
 lldb/tools/lldb-dap/lldb-dap.cpp              |  4 ++
 lldb/tools/lldb-dap/package.json              | 10 +++++
 11 files changed, 130 insertions(+), 26 deletions(-)

diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h
index 7c4477f9125d1cd..75e04d794baf848 100644
--- a/lldb/include/lldb/API/SBFrame.h
+++ b/lldb/include/lldb/API/SBFrame.h
@@ -87,8 +87,15 @@ class LLDB_API SBFrame {
   // display to a user
   const char *GetDisplayFunctionName();
 
+  /// Similar to \a GetDisplayFunctionName() but with function arguments and
+  /// their values inserted into the function display name whenever possible.
+  ///
+  /// \param[in] output
+  ///   The stream where the display name is written to.
+  void GetDisplayFunctionNameWithArgs(SBStream &output);
+
   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;
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 6824d916030a024..7b8510dfb4b40c6 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.
+  /// If the format is invalid, then the default formatter will be used (see \a
+  /// StackFrame::Dump()), in which case \b false is returned. Otherwise, \b
+  /// true is returned.
+  ///
+  /// \param[in] strm
+  ///   The Stream to print the description to.
+  ///
+  /// \param[in] frame_marker
+  ///   Optional string that will be prepended to the frame output description.
+  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/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index d1fb478bc8bb9ee..0c305fdd1ad9bbe 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="`",
+        showFramesWithFunctionArgs=False,
     ):
         args_dict = {"program": program}
         if args:
@@ -773,6 +774,7 @@ def request_launch(
             args_dict["runInTerminal"] = runInTerminal
         if postRunCommands:
             args_dict["postRunCommands"] = postRunCommands
+        args_dict["showFramesWithFunctionArgs"] = showFramesWithFunctionArgs
         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..0775922500ea4a8 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="`",
+        showFramesWithFunctionArgs=False,
     ):
         """Sending launch request to dap"""
 
@@ -391,6 +392,7 @@ def cleanup():
             enableAutoVariableSummaries=enableAutoVariableSummaries,
             enableSyntheticChildDebugging=enableSyntheticChildDebugging,
             commandEscapePrefix=commandEscapePrefix,
+            showFramesWithFunctionArgs=showFramesWithFunctionArgs,
         )
 
         if expectFailure:
@@ -428,6 +430,7 @@ def build_and_launch(
         enableAutoVariableSummaries=False,
         enableSyntheticChildDebugging=False,
         commandEscapePrefix="`",
+        showFramesWithFunctionArgs=False,
     ):
         """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,
+            showFramesWithFunctionArgs=showFramesWithFunctionArgs,
         )
diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp
index da5c6075e8f7b4b..814966f966ef7f2 100644
--- a/lldb/source/API/SBFrame.cpp
+++ b/lldb/source/API/SBFrame.cpp
@@ -601,8 +601,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 +814,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) {
@@ -1232,6 +1234,32 @@ const char *SBFrame::GetFunctionName() const {
   return name;
 }
 
+void SBFrame::GetDisplayFunctionNameWithArgs(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();
+
+  if (target && process) {
+    Process::StopLocker stop_locker;
+    if (stop_locker.TryLock(&process->GetRunLock())) {
+      frame = exe_ctx.GetFramePtr();
+      if (frame) {
+        FormatEntity::Entry format;
+        Status s = FormatEntity::Parse("${function.name-with-args}", format);
+        assert(
+            s.Success() &&
+            "The ${function.name-with-args} format must be parsed correctly");
+        frame->DumpUsingFormat(strm, &format);
+      }
+    }
+  }
+}
+
 const char *SBFrame::GetDisplayFunctionName() {
   LLDB_INSTRUMENT_VA(this);
 
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 11ada92348ecee2..71e7f356245f1c3 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -1779,18 +1779,32 @@ 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;
+  }
+  Dump(&strm, true, false);
+  strm.EOL();
+  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 +1814,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 {
-    Dump(strm, true, false);
-    strm->EOL();
-  }
+  DumpUsingFormat(*strm, frame_format, frame_marker);
 }
 
 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..153dd77169b3a8d 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, showFramesWithFunctionArgs=True)
+        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.h b/lldb/tools/lldb-dap/DAP.h
index b00c103c33b7a92..765e64a321f1530 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -189,6 +189,7 @@ struct DAP {
   ReplMode repl_mode;
   bool auto_repl_mode_collision_warning;
   std::string command_escape_prefix = "`";
+  bool show_frames_with_function_args = false;
 
   DAP();
   ~DAP();
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 2ff17616c2e9986..4dcd792b3ec4213 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.show_frames_with_function_args) {
+    lldb::SBStream stream;
+    frame.GetDisplayFunctionNameWithArgs(stream);
+    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..3b8aa7851274e1d 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -653,6 +653,8 @@ void request_attach(const llvm::json::Object &request) {
       GetBoolean(arguments, "enableSyntheticChildDebugging", false);
   g_dap.command_escape_prefix =
       GetString(arguments, "commandEscapePrefix", "`");
+  g_dap.show_frames_with_function_args =
+      GetBoolean(arguments, "showFramesWithFunctionArgs", false);
 
   // 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 +1807,8 @@ void request_launch(const llvm::json::Object &request) {
       GetBoolean(arguments, "enableSyntheticChildDebugging", false);
   g_dap.command_escape_prefix =
       GetString(arguments, "commandEscapePrefix", "`");
+  g_dap.show_frames_with_function_args =
+      GetBoolean(arguments, "showFramesWithFunctionArgs", false);
 
   // 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..753887a8ef0161c 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": "`"
+							},
+							"showFramesWithFunctionArgs": {
+								"type": "boolean",
+								"description": "When enabled, show function arguments along with their corresponding values in the stack frames. This comes with a performance cost because debug information needs to be processed to generate such values.",
+								"default": "false"
 							}
 						}
 					},
@@ -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": "`"
+							},
+							"showFramesWithFunctionArgs": {
+								"type": "boolean",
+								"description": "When enabled, show function arguments along with their corresponding values in the stack frames. This comes with a performance cost because debug information needs to be processed to generate such values.",
+								"default": "false"
 							}
 						}
 					}



More information about the lldb-commits mailing list