[Lldb-commits] [lldb] [lldb][Windows] Forward OUTPUT_DEBUG_STRING_EVENT through lldb-server (PR #203546)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Fri Jun 12 07:32:53 PDT 2026


https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/203546

Hoist `ReadDebugString` into `ProcessDebugger` so both plugins share it, then add
`NativeProcessWindows::OnDebugString` that reads the string, converts UTF-16 to
UTF-8 when needed, and delivers via `NativeDelegate::NewProcessOutput`.
    
Fixes `Process/Windows/output_debug_string.cpp` on Windows under `LLDB_USE_LLDB_SERVER=1`.

>From 56484d768158327f1dd62b889a438dba2568698c Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 12 Jun 2026 15:28:58 +0100
Subject: [PATCH] [lldb][Windows] Forward OUTPUT_DEBUG_STRING_EVENT through
 lldb-server

---
 .../Windows/Common/NativeProcessWindows.cpp   | 38 ++++++++++++++
 .../Windows/Common/NativeProcessWindows.h     |  2 +
 .../Windows/Common/ProcessDebugger.cpp        | 51 +++++++++++++++++++
 .../Process/Windows/Common/ProcessDebugger.h  | 12 +++++
 .../Process/Windows/Common/ProcessWindows.cpp | 51 -------------------
 .../Process/Windows/Common/ProcessWindows.h   |  4 --
 6 files changed, 103 insertions(+), 55 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index f0e88e78e6136..889a2e743c07f 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -713,6 +713,44 @@ void NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr) {
   m_pending_library_events = true;
 }
 
+void NativeProcessWindows::OnDebugString(lldb::addr_t debug_string_addr,
+                                         bool is_unicode,
+                                         uint16_t length_lower_word) {
+  Log *log = GetLog(WindowsLog::Process);
+
+  llvm::SmallVector<char, 256> buffer;
+  if (llvm::Error err = ProcessDebugger::ReadDebugString(
+          debug_string_addr, is_unicode, length_lower_word, buffer)) {
+    std::string err_str = llvm::toString(std::move(err));
+    std::string msg =
+        llvm::formatv("Failed to read debug string at {0:x} "
+                      "(size & 0xffff={1}, unicode={2}): {3}\n",
+                      debug_string_addr, length_lower_word, is_unicode, err_str)
+            .str();
+    LLDB_LOG(log, "{0}", msg);
+    m_delegate.NewProcessOutput(this, llvm::StringRef(msg));
+    return;
+  }
+  if (buffer.empty())
+    return;
+
+  if (is_unicode) {
+    assert(buffer.size() % 2 == 0);
+    llvm::ArrayRef<unsigned short> utf16(
+        reinterpret_cast<const unsigned short *>(buffer.data()),
+        buffer.size() / 2);
+    std::string out;
+    if (!llvm::convertUTF16ToUTF8String(utf16, out)) {
+      LLDB_LOG(log, "Debug string is not valid Utf 16");
+      return;
+    }
+    m_delegate.NewProcessOutput(this, llvm::StringRef(out.data(), out.size()));
+  } else {
+    m_delegate.NewProcessOutput(this,
+                                llvm::StringRef(buffer.data(), buffer.size()));
+  }
+}
+
 llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
 NativeProcessWindows::Manager::Launch(
     ProcessLaunchInfo &launch_info,
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
index d1c57fc01e06b..e2879bcd803fe 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -124,6 +124,8 @@ class NativeProcessWindows : public NativeProcessProtocol,
   void OnLoadDll(const ModuleSpec &module_spec,
                  lldb::addr_t module_addr) override;
   void OnUnloadDll(lldb::addr_t module_addr) override;
+  void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                     uint16_t length_lower_word) override;
 
 protected:
   NativeThreadWindows *GetThreadByID(lldb::tid_t thread_id);
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
index 5a2c3bdc87d47..84c74bb1ba919 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
@@ -555,6 +555,57 @@ void ProcessDebugger::OnDebugString(lldb::addr_t debug_string_addr,
   // Do nothing by default
 }
 
+llvm::Error
+ProcessDebugger::ReadDebugString(lldb::addr_t debug_string_addr,
+                                 bool is_unicode, uint16_t length_lower_word,
+                                 llvm::SmallVectorImpl<char> &output) {
+  if (is_unicode && length_lower_word % 2 != 0)
+    return llvm::createStringError(
+        "Utf16 string can't have uneven size in bytes");
+
+  const auto is_zero_terminated = [&] {
+    // The zero terminator is always at the end of the buffer.
+    if (is_unicode)
+      return output.size() >= 2 && output.back() == 0 &&
+             output[output.size() - 2] == 0;
+
+    return !output.empty() && output.back() == 0;
+  };
+
+  // Read at most 1 MiB ((1 << 16) * 16 - 1 Bytes) since we don't know the exact
+  // size of the string. We know that `strlen(string) & 0xffff ==
+  // length_lower_word`, so we read in chunks until we reach the terminator:
+  // - 0: `length_lower_word` Bytes
+  // - 1..16: 64 KiB (= 2^16 Bytes)
+  size_t start = length_lower_word == 0 ? 1 : 0;
+  for (size_t i = start; i < 16; ++i) {
+    output.resize_for_overwrite(length_lower_word + i * (1 << 16));
+    size_t chunk_size = i == 0 ? length_lower_word : (1 << 16);
+    lldb::addr_t addr = debug_string_addr + output.size_in_bytes() - chunk_size;
+
+    size_t bytes_read = 0;
+    Status error =
+        ReadMemory(addr, output.end() - chunk_size, chunk_size, bytes_read);
+    if (error.Fail())
+      return error.takeError();
+
+    if (bytes_read != chunk_size) {
+      return llvm::createStringErrorV(
+          "Expected to read {0} bytes, but read {1}", chunk_size, bytes_read);
+    }
+
+    if (is_zero_terminated())
+      break;
+  }
+
+  if (!is_zero_terminated())
+    return llvm::createStringError("String is 1 MiB or larger");
+
+  // Remove null terminator.
+  output.pop_back_n(is_unicode ? 2 : 1);
+  return llvm::Error::success();
+}
+
 void ProcessDebugger::OnDebuggerError(const Status &error, uint32_t type) {
   llvm::sys::ScopedLock lock(m_mutex);
   Log *log = GetLog(WindowsLog::Process);
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
index 25c877d5b4f17..10620b44beabf 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
@@ -14,6 +14,9 @@
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-forward.h"
 #include "lldb/lldb-types.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorExtras.h"
 #include "llvm/Support/Mutex.h"
 
 #include "ForwardDecl.h"
@@ -82,6 +85,15 @@ class ProcessDebugger {
   Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
                     size_t &bytes_read);
 
+  /// Read an OUTPUT_DEBUG_STRING_INFO payload from the inferior.
+  /// `length_lower_word` is the OS-supplied low 16 bits of the string length;
+  /// the helper walks 64 KiB chunks (up to 1 MiB) until it finds the NUL
+  /// terminator, then strips it. Shared between ProcessWindows (in-process)
+  /// and NativeProcessWindows (lldb-server).
+  llvm::Error ReadDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                              uint16_t length_lower_word,
+                              llvm::SmallVectorImpl<char> &output);
+
   Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
                      size_t &bytes_written);
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index cd91dd23cb298..1333b6bc80e95 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -839,57 +839,6 @@ void ProcessWindows::OnDebugString(lldb::addr_t debug_string_addr,
   }
 }
 
-llvm::Error
-ProcessWindows::ReadDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
-                                uint16_t length_lower_word,
-                                llvm::SmallVectorImpl<char> &output) {
-  if (is_unicode && length_lower_word % 2 != 0)
-    return llvm::createStringError(
-        "Utf16 string can't have uneven size in bytes");
-
-  const auto is_zero_terminated = [&] {
-    // The zero terminator is always at the end of the buffer.
-    if (is_unicode)
-      return output.size() >= 2 && output.back() == 0 &&
-             output[output.size() - 2] == 0;
-
-    return !output.empty() && output.back() == 0;
-  };
-
-  // Read at most 1 MiB ((1 << 16) * 16 - 1 Bytes) since we don't know the exact
-  // size of the string. We know that `strlen(string) & 0xffff ==
-  // length_lower_word`, so we read in chunks until we reach the terminator:
-  // - 0: `length_lower_word` Bytes
-  // - 1..16: 64 KiB (= 2^16 Bytes)
-  size_t start = length_lower_word == 0 ? 1 : 0;
-  for (size_t i = start; i < 16; ++i) {
-    output.resize_for_overwrite(length_lower_word + i * (1 << 16));
-    size_t chunk_size = i == 0 ? length_lower_word : (1 << 16);
-    lldb::addr_t addr = debug_string_addr + output.size_in_bytes() - chunk_size;
-
-    Status error;
-    size_t bytes_read =
-        DoReadMemory(addr, output.end() - chunk_size, chunk_size, error);
-    if (error.Fail())
-      return error.takeError();
-
-    if (bytes_read != chunk_size) {
-      return llvm::createStringErrorV(
-          "Expected to read {0} bytes, but read {1}", chunk_size, bytes_read);
-    }
-
-    if (is_zero_terminated())
-      break;
-  }
-
-  if (!is_zero_terminated())
-    return llvm::createStringError("String is 1 MiB or larger");
-
-  // Remove null terminator.
-  output.pop_back_n(is_unicode ? 2 : 1);
-  return llvm::Error::success();
-}
-
 void ProcessWindows::OnDebuggerError(const Status &error, uint32_t type) {
   llvm::sys::ScopedLock lock(m_mutex);
   Log *log = GetLog(WindowsLog::Process);
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 677ade4971eed..9c6a7528e1b31 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -121,10 +121,6 @@ class ProcessWindows : public Process, public ProcessDebugger {
                                MemoryRegionInfo &info) override;
 
 private:
-  llvm::Error ReadDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
-                              uint16_t length_lower_word,
-                              llvm::SmallVectorImpl<char> &output);
-
   struct WatchpointInfo {
     uint32_t slot_id;
     lldb::addr_t address;



More information about the lldb-commits mailing list