Author: Nerixyz
Date: 2026-06-02T15:07:56+02:00
New Revision: 2a7524b4e33c8dfd667fa28341ec7c6c529d9c50

URL: 
https://github.com/llvm/llvm-project/commit/2a7524b4e33c8dfd667fa28341ec7c6c529d9c50
DIFF: 
https://github.com/llvm/llvm-project/commit/2a7524b4e33c8dfd667fa28341ec7c6c529d9c50.diff

LOG: [lldb][Windows] Support OutputDebugString (#196395)

This picks https://reviews.llvm.org/D128541 back up. It implements
support for `OutputDebugStringA/W` on Windows. It's used by some logging
systems.

The main changes since the original patch:
- Use `WaitForDebugEventEx` for getting debug events over
`WaitForDebugEvent`. According to the
[docs](https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-waitfordebugeventex),
the only difference is that the -Ex version correctly outputs Unicode
strings.
- Support strings longer than 64 KiB. I set an arbitrary limit of 1 MiB
for the strings we read. The debugger interface only tells us the length
modulo 64 KiB which is a bit awkward. Libraries like Qt already chunk
calls to `OutputDebugString`, so strings shouldn't be too big in
practice.
- Output to stdout instead of a log channel, so the output is always
enabled. I don't know if this should go to stdout or stderr.

Tested that it works with lldb-dap and shows up in the debug console in
all modes.

Closes #185891.

---------

Co-authored-by: Alvin Wong <[email protected]>

Added: 
    lldb/test/Shell/Process/Windows/output_debug_string.cpp

Modified: 
    lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
    lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
    lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
    lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
    lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
    lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
    lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
    lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
    lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
    llvm/docs/ReleaseNotes.md

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp 
b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index 32ccd87c72631..8eff602f65c4c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -211,7 +211,7 @@ Status DebuggerThread::StopDebugging(bool terminate) {
   // If we're stuck waiting for an exception to continue (e.g. the user is at a
   // breakpoint messing around in the debugger), continue it now.  But only
   // AFTER calling TerminateProcess to make sure that the very next call to
-  // WaitForDebugEvent is an exit process event.
+  // WaitForDebugEventEx is an exit process event.
   if (m_active_exception.get()) {
     LLDB_LOG(log, "masking active exception");
     ContinueAsyncException(ExceptionResult::MaskException);
@@ -271,7 +271,7 @@ void DebuggerThread::DebugLoop() {
   Log *log = GetLog(WindowsLog::Event);
   DEBUG_EVENT dbe = {};
   bool should_debug = true;
-  LLDB_LOG_VERBOSE(log, "Entering WaitForDebugEvent loop");
+  LLDB_LOG_VERBOSE(log, "Entering WaitForDebugEventEx loop");
   while (should_debug) {
     LLDB_LOG_VERBOSE(log, "Calling WaitForDebugEvent");
     BOOL wait_result = g_wait_for_debug_event(&dbe, INFINITE);
@@ -350,7 +350,7 @@ void DebuggerThread::DebugLoop() {
           // detaching with leaving breakpoint exception event on the queue may
           // cause target process to crash so process events as possible since
           // target threads are running at this time, there is possibility to
-          // have some breakpoint exception between last WaitForDebugEvent and
+          // have some breakpoint exception between last WaitForDebugEventEx 
and
           // DebugActiveProcessStop but ignore for now.
           while (g_wait_for_debug_event(&dbe, 0)) {
             continue_status = DBG_CONTINUE;
@@ -375,7 +375,7 @@ void DebuggerThread::DebugLoop() {
         should_debug = false;
       }
     } else {
-      LLDB_LOG(log, "returned FALSE from WaitForDebugEvent.  Error = {0}",
+      LLDB_LOG(log, "returned FALSE from WaitForDebugEventEx.  Error = {0}",
                ::GetLastError());
 
       should_debug = false;
@@ -383,7 +383,7 @@ void DebuggerThread::DebugLoop() {
   }
   FreeProcessHandles();
 
-  LLDB_LOG(log, "WaitForDebugEvent loop completed, exiting.");
+  LLDB_LOG(log, "WaitForDebugEventEx loop completed, exiting.");
   ::SetEvent(m_debugging_ended_event);
 }
 
@@ -765,6 +765,10 @@ DebuggerThread::HandleUnloadDllEvent(const 
UNLOAD_DLL_DEBUG_INFO &info,
 DWORD
 DebuggerThread::HandleODSEvent(const OUTPUT_DEBUG_STRING_INFO &info,
                                DWORD thread_id) {
+  m_debug_delegate->OnDebugString(
+      static_cast<lldb::addr_t>(
+          reinterpret_cast<uintptr_t>(info.lpDebugStringData)),
+      info.fUnicode == TRUE, info.nDebugStringLength);
   return DBG_CONTINUE;
 }
 

diff  --git a/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h 
b/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
index 687ccab10bf0c..049d72fafeb72 100644
--- a/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
+++ b/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
@@ -35,7 +35,8 @@ class IDebugDelegate {
   virtual void OnLoadDll(const ModuleSpec &module_spec,
                          lldb::addr_t module_addr) = 0;
   virtual void OnUnloadDll(lldb::addr_t module_addr) = 0;
-  virtual void OnDebugString(const std::string &string) = 0;
+  virtual void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                             uint16_t length_lower_word) = 0;
   virtual void OnDebuggerError(const Status &error, uint32_t type) = 0;
 };
 }

diff  --git a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp 
b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
index dfa1d76d35fa0..647502ddaac25 100644
--- a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
@@ -56,9 +56,11 @@ void LocalDebugDelegate::OnUnloadDll(lldb::addr_t 
module_addr) {
     process->OnUnloadDll(module_addr);
 }
 
-void LocalDebugDelegate::OnDebugString(const std::string &string) {
+void LocalDebugDelegate::OnDebugString(lldb::addr_t debug_string_addr,
+                                       bool is_unicode,
+                                       uint16_t length_lower_word) {
   if (ProcessWindowsSP process = GetProcessPointer())
-    process->OnDebugString(string);
+    process->OnDebugString(debug_string_addr, is_unicode, length_lower_word);
 }
 
 void LocalDebugDelegate::OnDebuggerError(const Status &error, uint32_t type) {

diff  --git a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h 
b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
index 14aec6048a5f4..91f0fb64edaba 100644
--- a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
+++ b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
@@ -51,7 +51,8 @@ class LocalDebugDelegate : public IDebugDelegate {
   void OnLoadDll(const lldb_private::ModuleSpec &module_spec,
                  lldb::addr_t module_addr) override;
   void OnUnloadDll(lldb::addr_t module_addr) override;
-  void OnDebugString(const std::string &message) override;
+  void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                     uint16_t length_lower_word) override;
   void OnDebuggerError(const Status &error, uint32_t type) override;
 
 private:

diff  --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h 
b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
index d2c37d202f242..a15b51c7c66ab 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -194,8 +194,9 @@ class NativeDebugDelegate : public IDebugDelegate {
     m_process.OnUnloadDll(module_addr);
   }
 
-  void OnDebugString(const std::string &string) override {
-    m_process.OnDebugString(string);
+  void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                     uint16_t length_lower_word) override {
+    m_process.OnDebugString(debug_string_addr, is_unicode, length_lower_word);
   }
 
   void OnDebuggerError(const Status &error, uint32_t type) override {

diff  --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
index 0748bde171832..ada2096df66f3 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
@@ -549,7 +549,11 @@ void ProcessDebugger::OnUnloadDll(lldb::addr_t 
module_addr) {
   // Do nothing by default
 }
 
-void ProcessDebugger::OnDebugString(const std::string &string) {}
+void ProcessDebugger::OnDebugString(lldb::addr_t debug_string_addr,
+                                    bool is_unicode,
+                                    uint16_t length_lower_word) {
+  // Do nothing by default
+}
 
 void ProcessDebugger::OnDebuggerError(const Status &error, uint32_t type) {
   llvm::sys::ScopedLock lock(m_mutex);

diff  --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h 
b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
index a4db76455ef21..25c877d5b4f17 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
@@ -59,7 +59,8 @@ class ProcessDebugger {
   virtual void OnLoadDll(const ModuleSpec &module_spec,
                          lldb::addr_t module_addr);
   virtual void OnUnloadDll(lldb::addr_t module_addr);
-  virtual void OnDebugString(const std::string &string);
+  virtual void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                             uint16_t length_lower_word);
   virtual void OnDebuggerError(const Status &error, uint32_t type);
 
 protected:

diff  --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index beea043ccb2e7..42b3d713a737c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -36,6 +36,7 @@
 #include "lldb/Utility/State.h"
 
 #include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/ErrorExtras.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/Threading.h"
 #include "llvm/Support/raw_ostream.h"
@@ -871,7 +872,91 @@ void ProcessWindows::OnUnloadDll(lldb::addr_t module_addr) 
{
     dyld->OnUnloadModule(module_addr);
 }
 
-void ProcessWindows::OnDebugString(const std::string &string) {}
+void ProcessWindows::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;
+  llvm::Error err =
+      ReadDebugString(debug_string_addr, is_unicode, length_lower_word, 
buffer);
+  if (err) {
+    LLDB_LOG_ERROR(log, std::move(err),
+                   "Failed to read debug string at {1:x} (size & 0xffff={2}, "
+                   "unicode={3}): {0}",
+                   debug_string_addr, length_lower_word, is_unicode);
+    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;
+    }
+
+    AppendSTDOUT(out.data(), out.size());
+  } else {
+    AppendSTDOUT(buffer.data(), buffer.size());
+  }
+}
+
+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);

diff  --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h 
b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 31cf498a16d50..da35ae923dcee 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -91,7 +91,8 @@ class ProcessWindows : public Process, public ProcessDebugger 
{
   void OnLoadDll(const ModuleSpec &module_spec,
                  lldb::addr_t module_addr) override;
   void OnUnloadDll(lldb::addr_t module_addr) override;
-  void OnDebugString(const std::string &string) override;
+  void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
+                     uint16_t length_lower_word) override;
   void OnDebuggerError(const Status &error, uint32_t type) override;
 
   std::optional<uint32_t> GetWatchpointSlotCount() override;
@@ -118,6 +119,10 @@ 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;

diff  --git a/lldb/test/Shell/Process/Windows/output_debug_string.cpp 
b/lldb/test/Shell/Process/Windows/output_debug_string.cpp
new file mode 100644
index 0000000000000..d6e6580484e0f
--- /dev/null
+++ b/lldb/test/Shell/Process/Windows/output_debug_string.cpp
@@ -0,0 +1,58 @@
+// REQUIRES: target-windows
+// RUN: %build --compiler=clang-cl -o %t.exe -- %s
+// RUN: %lldb -f %t.exe -o "log enable windows process" -o r -o q | FileCheck 
%s
+
+#include <Windows.h>
+#include <string>
+
+int main() {
+  OutputDebugStringA("My string\nnext line\nBut this doesn't have trailing 
newline|");
+  OutputDebugStringW(L"OutputDebugStringW with some emojis 🦎🐊🐢🐍\n");
+  OutputDebugStringW(L"Another W1\n");
+  OutputDebugStringW(L"Another W2\n");
+  OutputDebugStringA("Some A1\n");
+  OutputDebugStringA("Some A2\n");
+  OutputDebugStringA("Some A3\n");
+
+  std::wstring maxW((1 << 19) - 4, L'A');
+  maxW.push_back(L'B');
+  maxW.push_back(L'\n');
+  OutputDebugStringW(maxW.c_str());
+
+  std::string maxA((1 << 20) - 4, 'C');
+  maxA.push_back(L'D');
+  maxA.push_back(L'\n');
+  OutputDebugStringA(maxA.c_str());
+
+  // Give LLDB time to print out the previous debug strings.
+  // The following ones should generate a log.
+  // This makes sure we see the log after the previous line, not before.
+  Sleep(100);
+
+  std::wstring tooBigW((1 << 19) - 3, L'E');
+  tooBigW.push_back(L'F');
+  tooBigW.push_back(L'\n');
+  OutputDebugStringW(tooBigW.c_str());
+
+  Sleep(100);
+
+  std::string tooBigA((1 << 20) - 3, 'G');
+  tooBigA.push_back(L'H');
+  tooBigA.push_back(L'\n');
+  OutputDebugStringA(tooBigA.c_str());
+
+  return 0;
+}
+
+// CHECK: My string
+// CHECK-NEXT: next line
+// CHECK-NEXT: But this doesn't have trailing newline|OutputDebugStringW with 
some emojis 🦎🐊🐢🐍
+// CHECK-NEXT: Another W1
+// CHECK-NEXT: Another W2
+// CHECK-NEXT: Some A1
+// CHECK-NEXT: Some A2
+// CHECK-NEXT: Some A3
+// CHECK-NEXT: {{A+B}}
+// CHECK-NEXT: {{C+D}}
+// CHECK-NEXT: Failed to read debug string at 0x{{.*}} (size & 0xffff=0, 
unicode=true): String is 1 MiB or larger
+// CHECK-NEXT: Failed to read debug string at 0x{{.*}} (size & 0xffff=0, 
unicode=false): String is 1 MiB or larger

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index ef77fe1ba28a8..01b7179cb42c3 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -364,6 +364,9 @@ Makes programs 10x faster by doing Special New Thing.
 #### Windows
 
 * Python 3.11 or later is now recommended for building LLDB 23 on Windows. 
From LLDB 24, Python 3.11 or later will be required.
+* Messages from `OutputDebugString[A|W]` are now shown inline when using LLDB
+  from the command-line and in the output window when using lldb-dap.
+
 
 ### Changes to BOLT
 


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to