Skip to content

Commit 2a7524b

Browse files
Nerixyzalvinhochun
andauthored
[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]>
1 parent 9942a38 commit 2a7524b

11 files changed

Lines changed: 180 additions & 15 deletions

File tree

lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ Status DebuggerThread::StopDebugging(bool terminate) {
211211
// If we're stuck waiting for an exception to continue (e.g. the user is at a
212212
// breakpoint messing around in the debugger), continue it now. But only
213213
// AFTER calling TerminateProcess to make sure that the very next call to
214-
// WaitForDebugEvent is an exit process event.
214+
// WaitForDebugEventEx is an exit process event.
215215
if (m_active_exception.get()) {
216216
LLDB_LOG(log, "masking active exception");
217217
ContinueAsyncException(ExceptionResult::MaskException);
@@ -271,7 +271,7 @@ void DebuggerThread::DebugLoop() {
271271
Log *log = GetLog(WindowsLog::Event);
272272
DEBUG_EVENT dbe = {};
273273
bool should_debug = true;
274-
LLDB_LOG_VERBOSE(log, "Entering WaitForDebugEvent loop");
274+
LLDB_LOG_VERBOSE(log, "Entering WaitForDebugEventEx loop");
275275
while (should_debug) {
276276
LLDB_LOG_VERBOSE(log, "Calling WaitForDebugEvent");
277277
BOOL wait_result = g_wait_for_debug_event(&dbe, INFINITE);
@@ -350,7 +350,7 @@ void DebuggerThread::DebugLoop() {
350350
// detaching with leaving breakpoint exception event on the queue may
351351
// cause target process to crash so process events as possible since
352352
// target threads are running at this time, there is possibility to
353-
// have some breakpoint exception between last WaitForDebugEvent and
353+
// have some breakpoint exception between last WaitForDebugEventEx and
354354
// DebugActiveProcessStop but ignore for now.
355355
while (g_wait_for_debug_event(&dbe, 0)) {
356356
continue_status = DBG_CONTINUE;
@@ -375,15 +375,15 @@ void DebuggerThread::DebugLoop() {
375375
should_debug = false;
376376
}
377377
} else {
378-
LLDB_LOG(log, "returned FALSE from WaitForDebugEvent. Error = {0}",
378+
LLDB_LOG(log, "returned FALSE from WaitForDebugEventEx. Error = {0}",
379379
::GetLastError());
380380

381381
should_debug = false;
382382
}
383383
}
384384
FreeProcessHandles();
385385

386-
LLDB_LOG(log, "WaitForDebugEvent loop completed, exiting.");
386+
LLDB_LOG(log, "WaitForDebugEventEx loop completed, exiting.");
387387
::SetEvent(m_debugging_ended_event);
388388
}
389389

@@ -765,6 +765,10 @@ DebuggerThread::HandleUnloadDllEvent(const UNLOAD_DLL_DEBUG_INFO &info,
765765
DWORD
766766
DebuggerThread::HandleODSEvent(const OUTPUT_DEBUG_STRING_INFO &info,
767767
DWORD thread_id) {
768+
m_debug_delegate->OnDebugString(
769+
static_cast<lldb::addr_t>(
770+
reinterpret_cast<uintptr_t>(info.lpDebugStringData)),
771+
info.fUnicode == TRUE, info.nDebugStringLength);
768772
return DBG_CONTINUE;
769773
}
770774

lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class IDebugDelegate {
3535
virtual void OnLoadDll(const ModuleSpec &module_spec,
3636
lldb::addr_t module_addr) = 0;
3737
virtual void OnUnloadDll(lldb::addr_t module_addr) = 0;
38-
virtual void OnDebugString(const std::string &string) = 0;
38+
virtual void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
39+
uint16_t length_lower_word) = 0;
3940
virtual void OnDebuggerError(const Status &error, uint32_t type) = 0;
4041
};
4142
}

lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ void LocalDebugDelegate::OnUnloadDll(lldb::addr_t module_addr) {
5656
process->OnUnloadDll(module_addr);
5757
}
5858

59-
void LocalDebugDelegate::OnDebugString(const std::string &string) {
59+
void LocalDebugDelegate::OnDebugString(lldb::addr_t debug_string_addr,
60+
bool is_unicode,
61+
uint16_t length_lower_word) {
6062
if (ProcessWindowsSP process = GetProcessPointer())
61-
process->OnDebugString(string);
63+
process->OnDebugString(debug_string_addr, is_unicode, length_lower_word);
6264
}
6365

6466
void LocalDebugDelegate::OnDebuggerError(const Status &error, uint32_t type) {

lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class LocalDebugDelegate : public IDebugDelegate {
5151
void OnLoadDll(const lldb_private::ModuleSpec &module_spec,
5252
lldb::addr_t module_addr) override;
5353
void OnUnloadDll(lldb::addr_t module_addr) override;
54-
void OnDebugString(const std::string &message) override;
54+
void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
55+
uint16_t length_lower_word) override;
5556
void OnDebuggerError(const Status &error, uint32_t type) override;
5657

5758
private:

lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,9 @@ class NativeDebugDelegate : public IDebugDelegate {
194194
m_process.OnUnloadDll(module_addr);
195195
}
196196

197-
void OnDebugString(const std::string &string) override {
198-
m_process.OnDebugString(string);
197+
void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
198+
uint16_t length_lower_word) override {
199+
m_process.OnDebugString(debug_string_addr, is_unicode, length_lower_word);
199200
}
200201

201202
void OnDebuggerError(const Status &error, uint32_t type) override {

lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,11 @@ void ProcessDebugger::OnUnloadDll(lldb::addr_t module_addr) {
549549
// Do nothing by default
550550
}
551551

552-
void ProcessDebugger::OnDebugString(const std::string &string) {}
552+
void ProcessDebugger::OnDebugString(lldb::addr_t debug_string_addr,
553+
bool is_unicode,
554+
uint16_t length_lower_word) {
555+
// Do nothing by default
556+
}
553557

554558
void ProcessDebugger::OnDebuggerError(const Status &error, uint32_t type) {
555559
llvm::sys::ScopedLock lock(m_mutex);

lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class ProcessDebugger {
5959
virtual void OnLoadDll(const ModuleSpec &module_spec,
6060
lldb::addr_t module_addr);
6161
virtual void OnUnloadDll(lldb::addr_t module_addr);
62-
virtual void OnDebugString(const std::string &string);
62+
virtual void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
63+
uint16_t length_lower_word);
6364
virtual void OnDebuggerError(const Status &error, uint32_t type);
6465

6566
protected:

lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "lldb/Utility/State.h"
3737

3838
#include "llvm/Support/ConvertUTF.h"
39+
#include "llvm/Support/ErrorExtras.h"
3940
#include "llvm/Support/Format.h"
4041
#include "llvm/Support/Threading.h"
4142
#include "llvm/Support/raw_ostream.h"
@@ -871,7 +872,91 @@ void ProcessWindows::OnUnloadDll(lldb::addr_t module_addr) {
871872
dyld->OnUnloadModule(module_addr);
872873
}
873874

874-
void ProcessWindows::OnDebugString(const std::string &string) {}
875+
void ProcessWindows::OnDebugString(lldb::addr_t debug_string_addr,
876+
bool is_unicode,
877+
uint16_t length_lower_word) {
878+
Log *log = GetLog(WindowsLog::Process);
879+
880+
llvm::SmallVector<char, 256> buffer;
881+
llvm::Error err =
882+
ReadDebugString(debug_string_addr, is_unicode, length_lower_word, buffer);
883+
if (err) {
884+
LLDB_LOG_ERROR(log, std::move(err),
885+
"Failed to read debug string at {1:x} (size & 0xffff={2}, "
886+
"unicode={3}): {0}",
887+
debug_string_addr, length_lower_word, is_unicode);
888+
return;
889+
}
890+
if (buffer.empty())
891+
return;
892+
893+
if (is_unicode) {
894+
assert(buffer.size() % 2 == 0);
895+
llvm::ArrayRef<unsigned short> utf16(
896+
reinterpret_cast<const unsigned short *>(buffer.data()),
897+
buffer.size() / 2);
898+
std::string out;
899+
if (!llvm::convertUTF16ToUTF8String(utf16, out)) {
900+
LLDB_LOG(log, "Debug string is not valid Utf 16");
901+
return;
902+
}
903+
904+
AppendSTDOUT(out.data(), out.size());
905+
} else {
906+
AppendSTDOUT(buffer.data(), buffer.size());
907+
}
908+
}
909+
910+
llvm::Error
911+
ProcessWindows::ReadDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
912+
uint16_t length_lower_word,
913+
llvm::SmallVectorImpl<char> &output) {
914+
if (is_unicode && length_lower_word % 2 != 0)
915+
return llvm::createStringError(
916+
"Utf16 string can't have uneven size in bytes");
917+
918+
const auto is_zero_terminated = [&] {
919+
// The zero terminator is always at the end of the buffer.
920+
if (is_unicode)
921+
return output.size() >= 2 && output.back() == 0 &&
922+
output[output.size() - 2] == 0;
923+
924+
return !output.empty() && output.back() == 0;
925+
};
926+
927+
// Read at most 1 MiB ((1 << 16) * 16 - 1 Bytes) since we don't know the exact
928+
// size of the string. We know that `strlen(string) & 0xffff ==
929+
// length_lower_word`, so we read in chunks until we reach the terminator:
930+
// - 0: `length_lower_word` Bytes
931+
// - 1..16: 64 KiB (= 2^16 Bytes)
932+
size_t start = length_lower_word == 0 ? 1 : 0;
933+
for (size_t i = start; i < 16; ++i) {
934+
output.resize_for_overwrite(length_lower_word + i * (1 << 16));
935+
size_t chunk_size = i == 0 ? length_lower_word : (1 << 16);
936+
lldb::addr_t addr = debug_string_addr + output.size_in_bytes() - chunk_size;
937+
938+
Status error;
939+
size_t bytes_read =
940+
DoReadMemory(addr, output.end() - chunk_size, chunk_size, error);
941+
if (error.Fail())
942+
return error.takeError();
943+
944+
if (bytes_read != chunk_size) {
945+
return llvm::createStringErrorV(
946+
"Expected to read {0} bytes, but read {1}", chunk_size, bytes_read);
947+
}
948+
949+
if (is_zero_terminated())
950+
break;
951+
}
952+
953+
if (!is_zero_terminated())
954+
return llvm::createStringError("String is 1 MiB or larger");
955+
956+
// Remove null terminator.
957+
output.pop_back_n(is_unicode ? 2 : 1);
958+
return llvm::Error::success();
959+
}
875960

876961
void ProcessWindows::OnDebuggerError(const Status &error, uint32_t type) {
877962
llvm::sys::ScopedLock lock(m_mutex);

lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ class ProcessWindows : public Process, public ProcessDebugger {
9191
void OnLoadDll(const ModuleSpec &module_spec,
9292
lldb::addr_t module_addr) override;
9393
void OnUnloadDll(lldb::addr_t module_addr) override;
94-
void OnDebugString(const std::string &string) override;
94+
void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
95+
uint16_t length_lower_word) override;
9596
void OnDebuggerError(const Status &error, uint32_t type) override;
9697

9798
std::optional<uint32_t> GetWatchpointSlotCount() override;
@@ -118,6 +119,10 @@ class ProcessWindows : public Process, public ProcessDebugger {
118119
MemoryRegionInfo &info) override;
119120

120121
private:
122+
llvm::Error ReadDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
123+
uint16_t length_lower_word,
124+
llvm::SmallVectorImpl<char> &output);
125+
121126
struct WatchpointInfo {
122127
uint32_t slot_id;
123128
lldb::addr_t address;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// REQUIRES: target-windows
2+
// RUN: %build --compiler=clang-cl -o %t.exe -- %s
3+
// RUN: %lldb -f %t.exe -o "log enable windows process" -o r -o q | FileCheck %s
4+
5+
#include <Windows.h>
6+
#include <string>
7+
8+
int main() {
9+
OutputDebugStringA("My string\nnext line\nBut this doesn't have trailing newline|");
10+
OutputDebugStringW(L"OutputDebugStringW with some emojis 🦎🐊🐢🐍\n");
11+
OutputDebugStringW(L"Another W1\n");
12+
OutputDebugStringW(L"Another W2\n");
13+
OutputDebugStringA("Some A1\n");
14+
OutputDebugStringA("Some A2\n");
15+
OutputDebugStringA("Some A3\n");
16+
17+
std::wstring maxW((1 << 19) - 4, L'A');
18+
maxW.push_back(L'B');
19+
maxW.push_back(L'\n');
20+
OutputDebugStringW(maxW.c_str());
21+
22+
std::string maxA((1 << 20) - 4, 'C');
23+
maxA.push_back(L'D');
24+
maxA.push_back(L'\n');
25+
OutputDebugStringA(maxA.c_str());
26+
27+
// Give LLDB time to print out the previous debug strings.
28+
// The following ones should generate a log.
29+
// This makes sure we see the log after the previous line, not before.
30+
Sleep(100);
31+
32+
std::wstring tooBigW((1 << 19) - 3, L'E');
33+
tooBigW.push_back(L'F');
34+
tooBigW.push_back(L'\n');
35+
OutputDebugStringW(tooBigW.c_str());
36+
37+
Sleep(100);
38+
39+
std::string tooBigA((1 << 20) - 3, 'G');
40+
tooBigA.push_back(L'H');
41+
tooBigA.push_back(L'\n');
42+
OutputDebugStringA(tooBigA.c_str());
43+
44+
return 0;
45+
}
46+
47+
// CHECK: My string
48+
// CHECK-NEXT: next line
49+
// CHECK-NEXT: But this doesn't have trailing newline|OutputDebugStringW with some emojis 🦎🐊🐢🐍
50+
// CHECK-NEXT: Another W1
51+
// CHECK-NEXT: Another W2
52+
// CHECK-NEXT: Some A1
53+
// CHECK-NEXT: Some A2
54+
// CHECK-NEXT: Some A3
55+
// CHECK-NEXT: {{A+B}}
56+
// CHECK-NEXT: {{C+D}}
57+
// CHECK-NEXT: Failed to read debug string at 0x{{.*}} (size & 0xffff=0, unicode=true): String is 1 MiB or larger
58+
// CHECK-NEXT: Failed to read debug string at 0x{{.*}} (size & 0xffff=0, unicode=false): String is 1 MiB or larger

0 commit comments

Comments
 (0)