https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/196395
>From 75d0fa835d306d945c872fde8def69e987eee9c2 Mon Sep 17 00:00:00 2001 From: Nerixyz <[email protected]> Date: Thu, 7 May 2026 20:38:54 +0200 Subject: [PATCH 1/2] [lldb][Windows] Support OutputDebugString Co-authored-by: Alvin Wong <[email protected]> --- .../Process/Windows/Common/DebuggerThread.cpp | 19 ++-- .../Process/Windows/Common/IDebugDelegate.h | 3 +- .../Windows/Common/LocalDebugDelegate.cpp | 6 +- .../Windows/Common/LocalDebugDelegate.h | 3 +- .../Windows/Common/NativeProcessWindows.h | 5 +- .../Windows/Common/ProcessDebugger.cpp | 6 +- .../Process/Windows/Common/ProcessDebugger.h | 3 +- .../Process/Windows/Common/ProcessWindows.cpp | 91 ++++++++++++++++++- .../Process/Windows/Common/ProcessWindows.h | 7 +- .../Process/Windows/output_debug_string.cpp | 57 ++++++++++++ 10 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 lldb/test/Shell/Process/Windows/output_debug_string.cpp diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp index 6359a63dfef91..15cba5d2b51d2 100644 --- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp @@ -173,7 +173,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); @@ -233,10 +233,10 @@ 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 = WaitForDebugEvent(&dbe, INFINITE); + LLDB_LOG_VERBOSE(log, "Calling WaitForDebugEventEx"); + BOOL wait_result = WaitForDebugEventEx(&dbe, INFINITE); if (wait_result) { DWORD continue_status = DBG_CONTINUE; bool shutting_down = m_is_shutting_down; @@ -312,9 +312,9 @@ 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 (WaitForDebugEvent(&dbe, 0)) { + while (WaitForDebugEventEx(&dbe, 0)) { continue_status = DBG_CONTINUE; if (dbe.dwDebugEventCode == EXCEPTION_DEBUG_EVENT && !(dbe.u.Exception.ExceptionRecord.ExceptionCode == @@ -337,7 +337,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; @@ -345,7 +345,7 @@ void DebuggerThread::DebugLoop() { } FreeProcessHandles(); - LLDB_LOG(log, "WaitForDebugEvent loop completed, exiting."); + LLDB_LOG(log, "WaitForDebugEventEx loop completed, exiting."); ::SetEvent(m_debugging_ended_event); } @@ -567,6 +567,9 @@ DebuggerThread::HandleUnloadDllEvent(const UNLOAD_DLL_DEBUG_INFO &info, DWORD DebuggerThread::HandleODSEvent(const OUTPUT_DEBUG_STRING_INFO &info, DWORD thread_id) { + m_debug_delegate->OnDebugString( + llvm::bit_cast<lldb::addr_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 aff2dd610eea0..842fa4a6464b5 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 7494dbbb0cfb6..d602771b3a7a4 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 cfba787a3d220..75add16ce6099 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -173,8 +173,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 51b2dfcb74d86..ac89f146e2847 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp @@ -543,7 +543,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 28b5554069c90..9d3c2e8e31ba4 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" @@ -870,7 +871,95 @@ 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) { + if (buffer.size() % 2 != 0) { + LLDB_LOG(log, "Read debug string had uneven size: {0}", buffer.size()); + return; + } + 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 228619d0e3d5e..58dc510a17850 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; @@ -114,6 +115,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..231d529e30a84 --- /dev/null +++ b/lldb/test/Shell/Process/Windows/output_debug_string.cpp @@ -0,0 +1,57 @@ +// 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()); + + Sleep(100); + + std::string maxA((1 << 20) - 4, 'C'); + maxA.push_back(L'D'); + maxA.push_back(L'\n'); + OutputDebugStringA(maxA.c_str()); + + 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 >From 96212e81abbb4637330454d4cabf7af47d72bf5e Mon Sep 17 00:00:00 2001 From: Nerixyz <[email protected]> Date: Fri, 8 May 2026 15:07:44 +0200 Subject: [PATCH 2/2] fix: remove unnecessary sleep and add comment about the others --- lldb/test/Shell/Process/Windows/output_debug_string.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lldb/test/Shell/Process/Windows/output_debug_string.cpp b/lldb/test/Shell/Process/Windows/output_debug_string.cpp index 231d529e30a84..d6e6580484e0f 100644 --- a/lldb/test/Shell/Process/Windows/output_debug_string.cpp +++ b/lldb/test/Shell/Process/Windows/output_debug_string.cpp @@ -19,13 +19,14 @@ int main() { maxW.push_back(L'\n'); OutputDebugStringW(maxW.c_str()); - Sleep(100); - 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'); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
