[Lldb-commits] [lldb] [lldb-dap] Add an option to provide a format for threads (PR #72196)

via lldb-commits lldb-commits at lists.llvm.org
Mon Nov 13 18:46:24 PST 2023


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Walter Erquinigo (walter-erquinigo)

<details>
<summary>Changes</summary>

When this option gets enabled, descriptions of threads will be generated using the format provided in the launch configuration instead of generating it manually in the dap code. This allows lldb-dap to show an output similar to the one in the CLI.
This is very similar to https://github.com/llvm/llvm-project/pull/71843


---
Full diff: https://github.com/llvm/llvm-project/pull/72196.diff


15 Files Affected:

- (modified) lldb/include/lldb/API/SBFormat.h (+1) 
- (modified) lldb/include/lldb/API/SBThread.h (+15) 
- (modified) lldb/include/lldb/Target/Thread.h (+22-5) 
- (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (+3) 
- (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py (+4) 
- (modified) lldb/source/API/SBThread.cpp (+30-5) 
- (modified) lldb/source/Target/Thread.cpp (+13-6) 
- (renamed) lldb/test/API/tools/lldb-dap/threads/Makefile () 
- (renamed) lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py (+23-2) 
- (renamed) lldb/test/API/tools/lldb-dap/threads/main.c () 
- (modified) lldb/tools/lldb-dap/DAP.cpp (+15) 
- (modified) lldb/tools/lldb-dap/DAP.h (+3) 
- (modified) lldb/tools/lldb-dap/JSONUtils.cpp (+25-18) 
- (modified) lldb/tools/lldb-dap/lldb-dap.cpp (+2) 
- (modified) lldb/tools/lldb-dap/package.json (+10) 


``````````diff
diff --git a/lldb/include/lldb/API/SBFormat.h b/lldb/include/lldb/API/SBFormat.h
index 1bbaad18cafafab..757340ab2b5c2a4 100644
--- a/lldb/include/lldb/API/SBFormat.h
+++ b/lldb/include/lldb/API/SBFormat.h
@@ -52,6 +52,7 @@ class LLDB_API SBFormat {
 
 protected:
   friend class SBFrame;
+  friend class SBThread;
 
   /// \return
   ///   The underlying shared pointer storage for this object.
diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h
index 49c3d954fa93329..dcf6aa9d5424e85 100644
--- a/lldb/include/lldb/API/SBThread.h
+++ b/lldb/include/lldb/API/SBThread.h
@@ -200,6 +200,21 @@ class LLDB_API SBThread {
 
   bool GetDescription(lldb::SBStream &description, bool stop_format) const;
 
+  /// 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);
+
   bool GetStatus(lldb::SBStream &status) const;
 
   SBThread GetExtendedBacktraceThread(const char *type);
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index dabdbb8d455021a..2c47b66d8b54d95 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -490,6 +490,23 @@ class Thread : public std::enable_shared_from_this<Thread>,
   void DumpTraceInstructions(Stream &s, size_t count,
                              size_t start_position = 0) const;
 
+  /// Print a description of this thread using the provided thread format.
+  ///
+  /// \param[out] strm
+  ///   The Stream to print the description to.
+  ///
+  /// \param[in] frame_idx
+  ///   If not \b LLDB_INVALID_FRAME_ID, then use this frame index as context to
+  ///   generate the description.
+  ///
+  /// \param[in] format
+  ///   The input format.
+  ///
+  /// \return
+  ///   \b true if and only if dumping with the given \p format worked.
+  bool DumpUsingFormat(Stream &strm, uint32_t frame_idx,
+                       const FormatEntity::Entry *format);
+
   // If stop_format is true, this will be the form used when we print stop
   // info. If false, it will be the form we use for thread list and co.
   void DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
@@ -1169,11 +1186,11 @@ class Thread : public std::enable_shared_from_this<Thread>,
   void ResetStopInfo();
 
   void SetShouldReportStop(Vote vote);
-  
-  void SetShouldRunBeforePublicStop(bool newval) { 
-      m_should_run_before_public_stop = newval; 
+
+  void SetShouldRunBeforePublicStop(bool newval) {
+    m_should_run_before_public_stop = newval;
   }
-  
+
   bool ShouldRunBeforePublicStop() {
       return m_should_run_before_public_stop;
   }
@@ -1266,7 +1283,7 @@ class Thread : public std::enable_shared_from_this<Thread>,
   uint32_t m_stop_info_override_stop_id; // The stop ID containing the last time
                                          // the stop info was checked against
                                          // the stop info override
-  bool m_should_run_before_public_stop;  // If this thread has "stop others" 
+  bool m_should_run_before_public_stop;  // If this thread has "stop others"
                                          // private work to do, then it will
                                          // set this.
   const uint32_t m_index_id; ///< A unique 1 based index assigned to each thread
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 a41861c59d2875a..518e3b9cf5bab33 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
@@ -733,6 +733,7 @@ def request_launch(
         enableSyntheticChildDebugging=False,
         commandEscapePrefix="`",
         customFrameFormat=None,
+        customThreadFormat=None,
     ):
         args_dict = {"program": program}
         if args:
@@ -776,6 +777,8 @@ def request_launch(
             args_dict["postRunCommands"] = postRunCommands
         if customFrameFormat:
             args_dict["customFrameFormat"] = customFrameFormat
+        if customThreadFormat:
+            args_dict["customThreadFormat"] = customThreadFormat
 
         args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
         args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
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 2c9f8703d56b405..9d79872b029a334 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
@@ -353,6 +353,7 @@ def launch(
         enableSyntheticChildDebugging=False,
         commandEscapePrefix="`",
         customFrameFormat=None,
+        customThreadFormat=None,
     ):
         """Sending launch request to dap"""
 
@@ -393,6 +394,7 @@ def cleanup():
             enableSyntheticChildDebugging=enableSyntheticChildDebugging,
             commandEscapePrefix=commandEscapePrefix,
             customFrameFormat=customFrameFormat,
+            customThreadFormat=customThreadFormat,
         )
 
         if expectFailure:
@@ -431,6 +433,7 @@ def build_and_launch(
         enableSyntheticChildDebugging=False,
         commandEscapePrefix="`",
         customFrameFormat=None,
+        customThreadFormat=None,
     ):
         """Build the default Makefile target, create the DAP debug adaptor,
         and launch the process.
@@ -463,4 +466,5 @@ def build_and_launch(
             enableSyntheticChildDebugging=enableSyntheticChildDebugging,
             commandEscapePrefix=commandEscapePrefix,
             customFrameFormat=customFrameFormat,
+            customThreadFormat=customThreadFormat,
         )
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index 15c909b0b46008f..18c086bb04c682f 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -12,6 +12,7 @@
 #include "lldb/API/SBDebugger.h"
 #include "lldb/API/SBEvent.h"
 #include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBFormat.h"
 #include "lldb/API/SBFrame.h"
 #include "lldb/API/SBProcess.h"
 #include "lldb/API/SBStream.h"
@@ -1223,17 +1224,41 @@ bool SBThread::GetDescription(SBStream &description, bool stop_format) const {
   ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
 
   if (exe_ctx.HasThreadScope()) {
-    exe_ctx.GetThreadPtr()->DumpUsingSettingsFormat(strm,
-                                                    LLDB_INVALID_THREAD_ID,
-                                                    stop_format);
-    // strm.Printf("SBThread: tid = 0x%4.4" PRIx64,
-    // exe_ctx.GetThreadPtr()->GetID());
+    exe_ctx.GetThreadPtr()->DumpUsingSettingsFormat(
+        strm, LLDB_INVALID_THREAD_ID, stop_format);
   } else
     strm.PutCString("No value");
 
   return true;
 }
 
+SBError SBThread::GetDescriptionWithFormat(const SBFormat &format,
+                                           SBStream &output) {
+  Stream &strm = output.ref();
+
+  SBError error;
+  if (!format) {
+    error.SetErrorString("The provided SBFormat object is invalid");
+    return error;
+  }
+
+  std::unique_lock<std::recursive_mutex> lock;
+  ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
+
+  if (exe_ctx.HasThreadScope()) {
+    if (exe_ctx.GetThreadPtr()->DumpUsingFormat(
+            strm, LLDB_INVALID_THREAD_ID, format.GetFormatEntrySP().get())) {
+      return error;
+    }
+  }
+
+  error.SetErrorStringWithFormat(
+      "It was not possible to generate a thread description with the given "
+      "format string '%s'",
+      format.GetFormatEntrySP()->string.c_str());
+  return error;
+}
+
 SBThread SBThread::GetExtendedBacktraceThread(const char *type) {
   LLDB_INSTRUMENT_VA(this, type);
 
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 6731b76c87b88b6..865cee97e6d8783 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -1589,12 +1589,12 @@ Status Thread::JumpToLine(const FileSpec &file, uint32_t line,
   return Status();
 }
 
-void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
-                                     bool stop_format) {
+bool Thread::DumpUsingFormat(Stream &strm, uint32_t frame_idx,
+                             const FormatEntity::Entry *format) {
   ExecutionContext exe_ctx(shared_from_this());
   Process *process = exe_ctx.GetProcessPtr();
-  if (process == nullptr)
-    return;
+  if (!process || !format)
+    return false;
 
   StackFrameSP frame_sp;
   SymbolContext frame_sc;
@@ -1606,6 +1606,14 @@ void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
     }
   }
 
+  return FormatEntity::Format(*format, strm, frame_sp ? &frame_sc : nullptr,
+                              &exe_ctx, nullptr, nullptr, false, false);
+}
+
+void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
+                                     bool stop_format) {
+  ExecutionContext exe_ctx(shared_from_this());
+
   const FormatEntity::Entry *thread_format;
   if (stop_format)
     thread_format = exe_ctx.GetTargetRef().GetDebugger().GetThreadStopFormat();
@@ -1614,8 +1622,7 @@ void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
 
   assert(thread_format);
 
-  FormatEntity::Format(*thread_format, strm, frame_sp ? &frame_sc : nullptr,
-                       &exe_ctx, nullptr, nullptr, false, false);
+  DumpUsingFormat(strm, frame_idx, thread_format);
 }
 
 void Thread::SettingsInitialize() {}
diff --git a/lldb/test/API/tools/lldb-dap/correct-thread/Makefile b/lldb/test/API/tools/lldb-dap/threads/Makefile
similarity index 100%
rename from lldb/test/API/tools/lldb-dap/correct-thread/Makefile
rename to lldb/test/API/tools/lldb-dap/threads/Makefile
diff --git a/lldb/test/API/tools/lldb-dap/correct-thread/TestDAP_correct_thread.py b/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py
similarity index 63%
rename from lldb/test/API/tools/lldb-dap/correct-thread/TestDAP_correct_thread.py
rename to lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py
index 43d3b02e408c79b..a51f80d535b4b74 100644
--- a/lldb/test/API/tools/lldb-dap/correct-thread/TestDAP_correct_thread.py
+++ b/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py
@@ -2,14 +2,13 @@
 Test lldb-dap setBreakpoints request
 """
 
-import dap_server
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
 from lldbsuite.test import lldbutil
 import lldbdap_testcase
 
 
-class TestDAP_correct_thread(lldbdap_testcase.DAPTestCaseBase):
+class TestDAP_threads(lldbdap_testcase.DAPTestCaseBase):
     @skipIfWindows
     @skipIfRemote
     def test_correct_thread(self):
@@ -44,3 +43,25 @@ def test_correct_thread(self):
         )
         self.assertFalse(stopped_event[0]["body"]["preserveFocusHint"])
         self.assertTrue(stopped_event[0]["body"]["threadCausedFocus"])
+
+    @skipIfWindows
+    @skipIfRemote
+    def test_thread_format(self):
+        """
+        Tests the support for custom thread formats.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, customThreadFormat="This is thread index #${thread.index}")
+        source = "main.c"
+        breakpoint_line = line_number(source, "// break here")
+        lines = [breakpoint_line]
+        # Set breakpoint in the thread function
+        breakpoint_ids = self.set_source_breakpoints(source, lines)
+        self.assertEqual(
+            len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
+        )
+        self.continue_to_breakpoints(breakpoint_ids)
+        # We're now stopped at the breakpoint in the first thread, thread #2.
+        threads = self.dap_server.get_threads()
+        self.assertEquals(threads[0]["name"], "This is thread index #1")
+        self.assertEquals(threads[1]["name"], "This is thread index #2")
diff --git a/lldb/test/API/tools/lldb-dap/correct-thread/main.c b/lldb/test/API/tools/lldb-dap/threads/main.c
similarity index 100%
rename from lldb/test/API/tools/lldb-dap/correct-thread/main.c
rename to lldb/test/API/tools/lldb-dap/threads/main.c
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index ba111a77d6e5496..40074f004de4c53 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -839,4 +839,19 @@ void DAP::SetFrameFormat(llvm::StringRef format) {
   }
 }
 
+void DAP::SetThreadFormat(llvm::StringRef format) {
+  if (format.empty())
+    return;
+  lldb::SBError error;
+  g_dap.thread_format = lldb::SBFormat(format.data(), error);
+  if (error.Fail()) {
+    g_dap.SendOutput(
+        OutputType::Console,
+        llvm::formatv(
+            "The provided thread format '{0}' couldn't be parsed: {1}\n",
+            format, error.GetCString())
+            .str());
+  }
+}
+
 } // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 11c2cc1fa8646a9..c7d56a06bfa1fdf 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -191,6 +191,7 @@ struct DAP {
   bool auto_repl_mode_collision_warning;
   std::string command_escape_prefix = "`";
   lldb::SBFormat frame_format;
+  lldb::SBFormat thread_format;
 
   DAP();
   ~DAP();
@@ -309,6 +310,8 @@ struct DAP {
 
   void SetFrameFormat(llvm::StringRef format);
 
+  void SetThreadFormat(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 2023291729762f1..50ade02801529c3 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -852,26 +852,33 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
 llvm::json::Value CreateThread(lldb::SBThread &thread) {
   llvm::json::Object object;
   object.try_emplace("id", (int64_t)thread.GetThreadID());
-  const char *thread_name = thread.GetName();
-  const char *queue_name = thread.GetQueueName();
-
   std::string thread_str;
-  if (thread_name) {
-    thread_str = std::string(thread_name);
-  } else if (queue_name) {
-    auto kind = thread.GetQueue().GetKind();
-    std::string queue_kind_label = "";
-    if (kind == lldb::eQueueKindSerial) {
-      queue_kind_label = " (serial)";
-    } else if (kind == lldb::eQueueKindConcurrent) {
-      queue_kind_label = " (concurrent)";
-    }
-
-    thread_str = llvm::formatv("Thread {0} Queue: {1}{2}", thread.GetIndexID(),
-                               queue_name, queue_kind_label)
-                     .str();
+  lldb::SBStream stream;
+  if (g_dap.thread_format &&
+      thread.GetDescriptionWithFormat(g_dap.thread_format, stream).Success()) {
+    thread_str = stream.GetData();
   } else {
-    thread_str = llvm::formatv("Thread {0}", thread.GetIndexID()).str();
+    const char *thread_name = thread.GetName();
+    const char *queue_name = thread.GetQueueName();
+
+    if (thread_name) {
+      thread_str = std::string(thread_name);
+    } else if (queue_name) {
+      auto kind = thread.GetQueue().GetKind();
+      std::string queue_kind_label = "";
+      if (kind == lldb::eQueueKindSerial) {
+        queue_kind_label = " (serial)";
+      } else if (kind == lldb::eQueueKindConcurrent) {
+        queue_kind_label = " (concurrent)";
+      }
+
+      thread_str =
+          llvm::formatv("Thread {0} Queue: {1}{2}", thread.GetIndexID(),
+                        queue_name, queue_kind_label)
+              .str();
+    } else {
+      thread_str = llvm::formatv("Thread {0}", thread.GetIndexID()).str();
+    }
   }
 
   EmplaceSafeString(object, "name", thread_str);
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 01738b3f5150ba3..d6b593eba93ecaf 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -654,6 +654,7 @@ void request_attach(const llvm::json::Object &request) {
   g_dap.command_escape_prefix =
       GetString(arguments, "commandEscapePrefix", "`");
   g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
+  g_dap.SetThreadFormat(GetString(arguments, "customThreadFormat"));
 
   // 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
@@ -1807,6 +1808,7 @@ void request_launch(const llvm::json::Object &request) {
   g_dap.command_escape_prefix =
       GetString(arguments, "commandEscapePrefix", "`");
   g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
+  g_dap.SetThreadFormat(GetString(arguments, "customThreadFormat"));
 
   // 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 6d6c46b55518c07..ebb1103d695e175 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -260,6 +260,11 @@
 								"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": ""
+							},
+							"customThreadFormat": {
+								"type": "string",
+								"description": "If non-empty, threads will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html for an explanation on format strings for threads. If the format string contains errors, an error message will be displayed on the Debug Console and the default thread names will be used. This might come with a performance cost because debug information might need to be processed to generate the description.",
+								"default": ""
 							}
 						}
 					},
@@ -359,6 +364,11 @@
 								"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": ""
+							},
+							"customThreadFormat": {
+								"type": "string",
+								"description": "If non-empty, threads will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html for an explanation on format strings for threads. If the format string contains errors, an error message will be displayed on the Debug Console and the default thread names will be used. This might come with a performance cost because debug information might need to be processed to generate the description.",
+								"default": ""
 							}
 						}
 					}

``````````

</details>


https://github.com/llvm/llvm-project/pull/72196


More information about the lldb-commits mailing list