https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/173015
>From 40f3dc018772d1bf58319f1df959f5c75c70c863 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Fri, 19 Dec 2025 15:10:36 +0000 Subject: [PATCH 1/2] [lldb][windows] add attach by name capability --- .../Process/Windows/Common/ProcessWindows.cpp | 52 +++++++++++++++++++ .../Process/Windows/Common/ProcessWindows.h | 19 +++++++ .../tools/lldb-dap/attach/TestDAP_attach.py | 1 - 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 127dd0f59e9ae..19b895cda77d9 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -10,6 +10,7 @@ // Windows includes #include "lldb/Host/windows/windows.h" +#include <TlHelp32.h> #include <psapi.h> #include "lldb/Breakpoint/Watchpoint.h" @@ -35,6 +36,7 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/State.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Format.h" #include "llvm/Support/Threading.h" @@ -227,6 +229,56 @@ ProcessWindows::DoAttachToProcessWithID(lldb::pid_t pid, return error; } +Status ProcessWindows::DoAttachToProcessWithName( + const char *process_name, const ProcessAttachInfo &attach_info) { + Status error; + Clear(); + + if (!process_name || process_name[0] == '\0') { + error = Status::FromErrorString("Invalid process name"); + return error; + } + + lldb::pid_t pid = FindProcessByName(process_name, error); + + if (pid == LLDB_INVALID_PROCESS_ID) { + error = Status::FromErrorStringWithFormatv("Unable to find process '%s'", + process_name); + return error; + } + + return DoAttachToProcessWithID(pid, attach_info); +} + +lldb::pid_t ProcessWindows::FindProcessByName(const char *process_name, + Status &error) { + HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) { + error = Status(GetLastError(), eErrorTypeWin32); + return LLDB_INVALID_PROCESS_ID; + } + auto cleanup = llvm::make_scope_exit([&] { CloseHandle(hProcessSnap); }); + + llvm::StringRef process_filename = llvm::sys::path::filename(process_name); + + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + if (!Process32FirstW(hProcessSnap, &pe32)) { + error = Status(GetLastError(), eErrorTypeWin32); + return LLDB_INVALID_PROCESS_ID; + } + + do { + std::string exe_name; + llvm::convertWideToUTF8(pe32.szExeFile, exe_name); + + if (exe_name == process_filename) + return pe32.th32ProcessID; + } while (Process32NextW(hProcessSnap, &pe32)); + + return LLDB_INVALID_PROCESS_ID; +} + Status ProcessWindows::DoResume(RunDirection direction) { Log *log = GetLog(WindowsLog::Process); 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 33e4de6b85932..be861db31ba36 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h @@ -49,6 +49,9 @@ class ProcessWindows : public Process, public ProcessDebugger { Status DoAttachToProcessWithID( lldb::pid_t pid, const lldb_private::ProcessAttachInfo &attach_info) override; + Status + DoAttachToProcessWithName(const char *process_name, + const ProcessAttachInfo &attach_info) override; Status DoResume(lldb::RunDirection direction) override; Status DoDestroy() override; Status DoHalt(bool &caused_stop) override; @@ -101,6 +104,22 @@ class ProcessWindows : public Process, public ProcessDebugger { void SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) override; + /// Finds the pid matching the process_name. + /// + /// If there are multiple processes with the same name, this method will + /// return the first one. + /// + /// \param[in] process_name + /// The name of the process to find. + /// + /// \param[out] error + /// An error that indicates the success or failure of the search. + /// + /// \return + /// Returns the pid of the process if a match was found. Returns \code + /// LLDB_INVALID_PROCESS otherwise. + lldb::pid_t FindProcessByName(const char *process_name, Status &error); + protected: ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py index d9acd0a4cae01..d6287397a93b0 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py @@ -59,7 +59,6 @@ def test_by_name(self): self.attach(program=program) self.continue_and_verify_pid() - @expectedFailureWindows def test_by_name_waitFor(self): """ Tests waiting for, and attaching to a process by process name that >From cf7de951ea0e24f2efd8f6d68623b89089af2fdf Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Mon, 22 Dec 2025 16:52:29 +0000 Subject: [PATCH 2/2] switch to an async implementation --- .../Process/Windows/Common/ProcessWindows.cpp | 290 ++++++++++++++++-- .../Process/Windows/Common/ProcessWindows.h | 30 +- .../TestSBCommandReturnObject.py | 3 - .../multiple-targets/TestMultipleTargets.py | 5 - 4 files changed, 270 insertions(+), 58 deletions(-) diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 19b895cda77d9..f79e436c9caf6 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -25,16 +25,20 @@ #include "lldb/Host/HostProcess.h" #include "lldb/Host/Pipe.h" #include "lldb/Host/PseudoTerminal.h" +#include "lldb/Host/ThreadLauncher.h" #include "lldb/Host/windows/ConnectionGenericFileWindows.h" #include "lldb/Host/windows/HostThreadWindows.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/Platform.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" +#include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/State.h" +#include "lldb/Utility/StringExtractor.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/ConvertUTF.h" @@ -125,11 +129,24 @@ llvm::StringRef ProcessWindows::GetPluginDescriptionStatic() { ProcessWindows::ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp) : lldb_private::Process(target_sp, listener_sp), - m_watchpoint_ids( - RegisterContextWindows::GetNumHardwareBreakpointSlots(), - LLDB_INVALID_BREAK_ID) {} + m_async_broadcaster(nullptr, "lldb.process.windows.async-broadcaster"), + m_async_listener_sp( + Listener::MakeListener("lldb.process.windows.async-listener")), + m_async_thread_state_mutex(), + m_watchpoint_ids(RegisterContextWindows::GetNumHardwareBreakpointSlots(), + LLDB_INVALID_BREAK_ID) { + m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, + "async thread should exit"); + m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, + "async thread continue"); + m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadDidExit, + "async thread did exit"); +} -ProcessWindows::~ProcessWindows() {} +ProcessWindows::~ProcessWindows() { + Clear(); + StopAsyncThread(); +} Status ProcessWindows::EnableBreakpointSite(BreakpointSite *bp_site) { if (bp_site->HardwareRequired()) @@ -195,9 +212,10 @@ Status ProcessWindows::DoDetach(bool keep_stopped) { } error = DetachProcess(); - if (error.Success()) + if (error.Success()) { + StopAsyncThread(); SetPrivateState(eStateDetached); - else + } else LLDB_LOG(log, "Detaching process error: {0}", error); } else { error = Status::FromErrorStringWithFormatv( @@ -234,49 +252,252 @@ Status ProcessWindows::DoAttachToProcessWithName( Status error; Clear(); - if (!process_name || process_name[0] == '\0') { - error = Status::FromErrorString("Invalid process name"); + if (!process_name || !process_name[0]) { + error = Status::FromErrorString("invalid process name"); return error; } - lldb::pid_t pid = FindProcessByName(process_name, error); - - if (pid == LLDB_INVALID_PROCESS_ID) { - error = Status::FromErrorStringWithFormatv("Unable to find process '%s'", - process_name); + if (error.Fail()) { + SetExitStatus(-1, error.AsCString()); return error; } - return DoAttachToProcessWithID(pid, attach_info); + StreamString packet; + if (attach_info.GetWaitForLaunch()) { + if (attach_info.GetIgnoreExisting()) + packet.PutCString("vAttachWait"); + else + packet.PutCString("vAttachOrWait"); + } else { + packet.PutCString("vAttachName"); + } + + packet.PutChar(';'); + packet.PutBytesAsRawHex8(process_name, strlen(process_name), + endian::InlHostByteOrder(), + endian::InlHostByteOrder()); + + auto data_sp = std::make_shared<EventDataBytes>(packet.GetString()); + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue, data_sp); + + return error; } -lldb::pid_t ProcessWindows::FindProcessByName(const char *process_name, - Status &error) { - HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) { - error = Status(GetLastError(), eErrorTypeWin32); - return LLDB_INVALID_PROCESS_ID; +thread_result_t ProcessWindows::AsyncThread() { + Log *log = GetLog(WindowsLog::Process); + LLDB_LOGF(log, "ProcessWindows::%s(pid = %" PRIu64 ") thread starting...", + __FUNCTION__, GetID()); + + EventSP event_sp; + bool done = false; + + while (!done && GetPrivateState() != eStateExited) { + LLDB_LOGF(log, + "ProcessWindows::%s(pid = %" PRIu64 + ") listener.WaitForEvent (NULL, event_sp)...", + __FUNCTION__, GetID()); + + if (!m_async_listener_sp->GetEvent(event_sp, std::nullopt)) { + LLDB_LOGF(log, + "ProcessWindows::%s(pid = %" PRIu64 + ") listener.WaitForEvent (NULL, event_sp) => false", + __FUNCTION__, GetID()); + done = true; + continue; + } + + const uint32_t event_type = event_sp->GetType(); + if (!event_sp->BroadcasterIs(&m_async_broadcaster)) { + continue; + } + + LLDB_LOGF(log, + "ProcessWindows::%s(pid = %" PRIu64 + ") Got an event of type: %d...", + __FUNCTION__, GetID(), event_type); + + switch (event_type) { + case eBroadcastBitAsyncContinue: { + const EventDataBytes *continue_packet = + EventDataBytes::GetEventDataFromEvent(event_sp.get()); + if (!continue_packet) + break; + + const char *continue_cstr = (const char *)continue_packet->GetBytes(); + LLDB_LOGF(log, + "ProcessWindows::%s(pid = %" PRIu64 + ") got eBroadcastBitAsyncContinue: %s", + __FUNCTION__, GetID(), continue_cstr); + + // Handle attach-by-name with wait + if (::strstr(continue_cstr, "vAttachWait") != nullptr || + ::strstr(continue_cstr, "vAttachOrWait") != nullptr || + ::strstr(continue_cstr, "vAttachName") != nullptr) { + + bool wait_for_launch = + (::strstr(continue_cstr, "vAttachWait") != nullptr || + ::strstr(continue_cstr, "vAttachOrWait") != nullptr); + + const char *semicolon = ::strchr(continue_cstr, ';'); + if (!semicolon) { + SetExitStatus(-1, "invalid attach packet format"); + done = true; + break; + } + + std::string process_name; + StringExtractor extractor(semicolon + 1); + extractor.GetHexByteString(process_name); + + Status error = DoAttachByName(process_name.c_str(), wait_for_launch); + + if (error.Fail()) { + SetExitStatus(-1, error.AsCString()); + done = true; + } + } else { + SetPrivateState(eStateRunning); + } + break; + } + + case eBroadcastBitAsyncThreadShouldExit: + LLDB_LOGF(log, + "ProcessWindows::%s(pid = %" PRIu64 + ") got eBroadcastBitAsyncThreadShouldExit...", + __FUNCTION__, GetID()); + done = true; + break; + + default: + LLDB_LOGF(log, + "ProcessWindows::%s(pid = %" PRIu64 + ") got unknown event 0x%8.8x", + __FUNCTION__, GetID(), event_type); + done = true; + break; + } } - auto cleanup = llvm::make_scope_exit([&] { CloseHandle(hProcessSnap); }); - llvm::StringRef process_filename = llvm::sys::path::filename(process_name); + LLDB_LOGF(log, "ProcessWindows::%s(pid = %" PRIu64 ") thread exiting...", + __FUNCTION__, GetID()); + + return {}; +} + +Status ProcessWindows::DoAttachByName(const char *process_name, + bool wait_for_launch) { + Log *log = GetLog(WindowsLog::Process); + Status error; + + if (!StartAsyncThread()) + return Status::FromErrorString("Could not start async thread."); + + PlatformSP platform_sp(GetTarget().GetPlatform()); + ProcessInstanceInfoList process_infos; + ProcessInstanceInfoMatch match_info; + match_info.GetProcessInfo().GetExecutableFile().SetFile( + process_name, FileSpec::Style::native); + match_info.SetNameMatchType(NameMatch::Equals); + + if (wait_for_launch) { + LLDB_LOGF(log, "ProcessWindows::%s waiting for process '%s' to launch", + __FUNCTION__, process_name); + + const auto timeout = std::chrono::seconds(30); + const auto start_time = std::chrono::steady_clock::now(); + + while (std::chrono::steady_clock::now() - start_time < timeout) { + process_infos.clear(); + platform_sp->FindProcesses(match_info, process_infos); + + if (process_infos.size() > 0) + break; + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (GetPrivateState() == eStateExited) + return Status("attach cancelled"); + } + + if (process_infos.empty()) + return Status::FromErrorStringWithFormat( + "timed out waiting for process '%s' to launch", process_name); + } else { + platform_sp->FindProcesses(match_info, process_infos); + } - PROCESSENTRY32W pe32; - pe32.dwSize = sizeof(PROCESSENTRY32W); - if (!Process32FirstW(hProcessSnap, &pe32)) { - error = Status(GetLastError(), eErrorTypeWin32); - return LLDB_INVALID_PROCESS_ID; + uint32_t num_matches = process_infos.size(); + + if (num_matches == 0) + return Status::FromErrorStringWithFormat("no process found with name '%s'", + process_name); + else if (num_matches == 1) { + lldb::pid_t pid = process_infos[0].GetProcessID(); + LLDB_LOGF(log, "ProcessWindows::%s attaching to pid %" PRIu64, __FUNCTION__, + pid); + ProcessAttachInfo attach_info; + attach_info.SetProcessID(pid); + error = DoAttachToProcessWithID(pid, attach_info); + } else { + StreamString s; + ProcessInstanceInfo::DumpTableHeader(s, true, false); + for (size_t i = 0; i < num_matches; i++) + process_infos[i].DumpAsTableRow(s, platform_sp->GetUserIDResolver(), true, + false); + error = Status::FromErrorStringWithFormat( + "more than one process named '%s':\n%s", process_name, s.GetData()); } - do { - std::string exe_name; - llvm::convertWideToUTF8(pe32.szExeFile, exe_name); + return error; +} - if (exe_name == process_filename) - return pe32.th32ProcessID; - } while (Process32NextW(hProcessSnap, &pe32)); +bool ProcessWindows::StartAsyncThread() { + Log *log = GetLog(WindowsLog::Process); - return LLDB_INVALID_PROCESS_ID; + LLDB_LOGF(log, "ProcessWindows::%s ()", __FUNCTION__); + + std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex); + if (!m_async_thread.IsJoinable()) { + // Create a thread that watches our internal state and controls which + // events make it to clients (into the DCProcess event queue). + + llvm::Expected<HostThread> async_thread = + ThreadLauncher::LaunchThread("<lldb.process.gdb-remote.async>", [this] { + return ProcessWindows::AsyncThread(); + }); + if (!async_thread) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), async_thread.takeError(), + "failed to launch host thread: {0}"); + return false; + } + m_async_thread = *async_thread; + } else + LLDB_LOGF(log, + "ProcessWindows::%s () - Called when Async thread was " + "already running.", + __FUNCTION__); + + return m_async_thread.IsJoinable(); +} + +void ProcessWindows::StopAsyncThread() { + Log *log = GetLog(WindowsLog::Process); + + LLDB_LOGF(log, "ProcessWindows::%s ()", __FUNCTION__); + + std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex); + if (m_async_thread.IsJoinable()) { + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit); + + // Stop the stdio thread + m_async_thread.Join(nullptr); + m_async_thread.Reset(); + } else + LLDB_LOGF( + log, + "ProcessWindows::%s () - Called when Async thread was not running.", + __FUNCTION__); } Status ProcessWindows::DoResume(RunDirection direction) { @@ -337,6 +558,7 @@ Status ProcessWindows::DoResume(RunDirection direction) { Status ProcessWindows::DoDestroy() { StateType private_state = GetPrivateState(); + StopAsyncThread(); return DestroyProcess(private_state); } diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h index be861db31ba36..86679973875b4 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h @@ -52,6 +52,10 @@ class ProcessWindows : public Process, public ProcessDebugger { Status DoAttachToProcessWithName(const char *process_name, const ProcessAttachInfo &attach_info) override; + Status DoAttachByName(const char *process_name, bool wait_for_launch); + bool StartAsyncThread(); + void StopAsyncThread(); + lldb::thread_result_t AsyncThread(); Status DoResume(lldb::RunDirection direction) override; Status DoDestroy() override; Status DoHalt(bool &caused_stop) override; @@ -104,28 +108,18 @@ class ProcessWindows : public Process, public ProcessDebugger { void SetPseudoConsoleHandle(const std::shared_ptr<PseudoConsole> &pty) override; - /// Finds the pid matching the process_name. - /// - /// If there are multiple processes with the same name, this method will - /// return the first one. - /// - /// \param[in] process_name - /// The name of the process to find. - /// - /// \param[out] error - /// An error that indicates the success or failure of the search. - /// - /// \return - /// Returns the pid of the process if a match was found. Returns \code - /// LLDB_INVALID_PROCESS otherwise. - lldb::pid_t FindProcessByName(const char *process_name, Status &error); - protected: ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); Status DoGetMemoryRegionInfo(lldb::addr_t vm_addr, MemoryRegionInfo &info) override; + enum { + eBroadcastBitAsyncContinue = (1 << 0), + eBroadcastBitAsyncThreadShouldExit = (1 << 1), + eBroadcastBitAsyncThreadDidExit = (1 << 2) + }; + private: struct WatchpointInfo { uint32_t slot_id; @@ -136,6 +130,10 @@ class ProcessWindows : public Process, public ProcessDebugger { }; std::map<lldb::break_id_t, WatchpointInfo> m_watchpoints; std::vector<lldb::break_id_t> m_watchpoint_ids; + std::recursive_mutex m_async_thread_state_mutex; + HostThread m_async_thread; + Broadcaster m_async_broadcaster; + lldb::ListenerSP m_async_listener_sp; }; } // namespace lldb_private diff --git a/lldb/test/API/api/command-return-object/TestSBCommandReturnObject.py b/lldb/test/API/api/command-return-object/TestSBCommandReturnObject.py index f8632fd720325..8ea0578c0c1ad 100644 --- a/lldb/test/API/api/command-return-object/TestSBCommandReturnObject.py +++ b/lldb/test/API/api/command-return-object/TestSBCommandReturnObject.py @@ -11,9 +11,6 @@ class TestSBCommandReturnObject(TestBase): NO_DEBUG_INFO_TESTCASE = True @skipIfNoSBHeaders - @expectedFailureAll( - oslist=["windows"], archs=["i[3-6]86", "x86_64"], bugnumber="llvm.org/pr43570" - ) @skipIfHostIncompatibleWithTarget def test_sb_command_return_object(self): self.driver_exe = self.getBuildArtifact("command-return-object") diff --git a/lldb/test/API/api/multiple-targets/TestMultipleTargets.py b/lldb/test/API/api/multiple-targets/TestMultipleTargets.py index 08e214e2bcaba..fc31aad144acc 100644 --- a/lldb/test/API/api/multiple-targets/TestMultipleTargets.py +++ b/lldb/test/API/api/multiple-targets/TestMultipleTargets.py @@ -2,10 +2,8 @@ import os -import lldb from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * -from lldbsuite.test import lldbutil class TestMultipleTargets(TestBase): @@ -13,9 +11,6 @@ class TestMultipleTargets(TestBase): @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) @skipIfNoSBHeaders - @expectedFailureAll( - oslist=["windows"], archs=["i[3-6]86", "x86_64"], bugnumber="llvm.org/pr20282" - ) @expectedFlakeyNetBSD @skipIfHostIncompatibleWithTarget def test_multiple_targets(self): _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
