[Lldb-commits] [lldb] [lldb] Multithreading lldb-server works on Windows now; fixed gdb port mapping (PR #100670)

Dmitry Vasilyev via lldb-commits lldb-commits at lists.llvm.org
Thu Aug 1 04:49:03 PDT 2024


https://github.com/slydiman updated https://github.com/llvm/llvm-project/pull/100670

>From 7fb5b2ee53f3125dc2b93cb6fba28b35c70b70e1 Mon Sep 17 00:00:00 2001
From: Dmitry Vasilyev <dvassiliev at accesssoftek.com>
Date: Thu, 25 Jul 2024 00:34:34 +0400
Subject: [PATCH 1/2] [lldb] Multithreading lldb-server works on Windows now;
 fixed gdb port mapping

Removed fork(). Used threads and the common thread-safe port map for all platform connections.

Updated lldb::FileSystem to use llvm::vfs::createPhysicalFileSystem() with an own virtual working directory per thread.

This patch depends on #100659, #100666.

This patch fixes #97537, #90923, #56346.

lldb-server has been tested on Windows with 50 connections and 100 processes launched simultaneously. Tested also the cross build with Linux x86_64 host and Linux Aarch64 target.
---
 lldb/include/lldb/Host/FileSystem.h           |   7 +
 lldb/source/Host/common/FileSystem.cpp        |   8 +
 lldb/source/Host/posix/PipePosix.cpp          |  12 +
 .../GDBRemoteCommunicationServerCommon.cpp    |  15 +-
 .../GDBRemoteCommunicationServerPlatform.cpp  | 309 ++++++++++++------
 .../GDBRemoteCommunicationServerPlatform.h    |  31 +-
 lldb/tools/lldb-server/lldb-platform.cpp      |  99 ++----
 7 files changed, 298 insertions(+), 183 deletions(-)

diff --git a/lldb/include/lldb/Host/FileSystem.h b/lldb/include/lldb/Host/FileSystem.h
index 640f3846e448c..5e25414a894d3 100644
--- a/lldb/include/lldb/Host/FileSystem.h
+++ b/lldb/include/lldb/Host/FileSystem.h
@@ -47,6 +47,12 @@ class FileSystem {
 
   static FileSystem &Instance();
 
+  static void InitializePerThread() {
+    lldbassert(!InstancePerThread() && "Already initialized.");
+    InstancePerThread().emplace(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>(
+        llvm::vfs::createPhysicalFileSystem().release()));
+  }
+
   template <class... T> static void Initialize(T &&...t) {
     lldbassert(!InstanceImpl() && "Already initialized.");
     InstanceImpl().emplace(std::forward<T>(t)...);
@@ -206,6 +212,7 @@ class FileSystem {
 
 private:
   static std::optional<FileSystem> &InstanceImpl();
+  static std::optional<FileSystem> &InstancePerThread();
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> m_fs;
   std::unique_ptr<TildeExpressionResolver> m_tilde_resolver;
   std::string m_home_directory;
diff --git a/lldb/source/Host/common/FileSystem.cpp b/lldb/source/Host/common/FileSystem.cpp
index 5153a0a9ec513..cb76086616d6b 100644
--- a/lldb/source/Host/common/FileSystem.cpp
+++ b/lldb/source/Host/common/FileSystem.cpp
@@ -49,7 +49,15 @@ void FileSystem::Terminate() {
   InstanceImpl().reset();
 }
 
+std::optional<FileSystem> &FileSystem::InstancePerThread() {
+  static thread_local std::optional<FileSystem> t_fs;
+  return t_fs;
+}
+
 std::optional<FileSystem> &FileSystem::InstanceImpl() {
+  std::optional<FileSystem> &fs = InstancePerThread();
+  if (fs)
+    return fs;
   static std::optional<FileSystem> g_fs;
   return g_fs;
 }
diff --git a/lldb/source/Host/posix/PipePosix.cpp b/lldb/source/Host/posix/PipePosix.cpp
index f35c348990df6..1aa02efe86610 100644
--- a/lldb/source/Host/posix/PipePosix.cpp
+++ b/lldb/source/Host/posix/PipePosix.cpp
@@ -324,6 +324,18 @@ Status PipePosix::ReadWithTimeout(void *buf, size_t size,
         bytes_read += result;
         if (bytes_read == size || result == 0)
           break;
+
+        // This is the workaround for the following bug in Linux multithreading
+        // select() https://bugzilla.kernel.org/show_bug.cgi?id=546
+        // ReadWithTimeout() with a non-zero timeout is used only to
+        // read the port number from the gdbserver pipe
+        // in GDBRemoteCommunication::StartDebugserverProcess().
+        // The port number may be "1024\0".."65535\0".
+        if (timeout.count() > 0 && size == 6 && bytes_read == 5 &&
+            static_cast<char *>(buf)[4] == '\0') {
+          break;
+        }
+
       } else if (errno == EINTR) {
         continue;
       } else {
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
index f9d37490e16ae..cef836e001adf 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
@@ -646,7 +646,9 @@ GDBRemoteCommunicationServerCommon::Handle_vFile_Size(
   packet.GetHexByteString(path);
   if (!path.empty()) {
     uint64_t Size;
-    if (llvm::sys::fs::file_size(path, Size))
+    FileSpec file_spec(path);
+    FileSystem::Instance().Resolve(file_spec);
+    if (llvm::sys::fs::file_size(file_spec.GetPath(), Size))
       return SendErrorResponse(5);
     StreamString response;
     response.PutChar('F');
@@ -725,7 +727,9 @@ GDBRemoteCommunicationServerCommon::Handle_vFile_unlink(
   packet.SetFilePos(::strlen("vFile:unlink:"));
   std::string path;
   packet.GetHexByteString(path);
-  Status error(llvm::sys::fs::remove(path));
+  FileSpec file_spec(path);
+  FileSystem::Instance().Resolve(file_spec);
+  Status error(llvm::sys::fs::remove(file_spec.GetPath()));
   StreamString response;
   response.Printf("F%x,%x", error.GetError(), error.GetError());
   return SendPacketNoLock(response.GetString());
@@ -744,6 +748,13 @@ GDBRemoteCommunicationServerCommon::Handle_qPlatform_shell(
       // uint32_t timeout = packet.GetHexMaxU32(false, 32);
       if (packet.GetChar() == ',')
         packet.GetHexByteString(working_dir);
+      else {
+        auto cwd = FileSystem::Instance()
+                       .GetVirtualFileSystem()
+                       ->getCurrentWorkingDirectory();
+        if (cwd)
+          working_dir = *cwd;
+      }
       int status, signo;
       std::string output;
       FileSpec working_spec(working_dir);
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp
index 65f1cc12ba307..6e3b7b4a351e0 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp
@@ -18,13 +18,13 @@
 #include <sstream>
 #include <thread>
 
-#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/Threading.h"
 
 #include "lldb/Host/Config.h"
 #include "lldb/Host/ConnectionFileDescriptor.h"
 #include "lldb/Host/FileAction.h"
+#include "lldb/Host/FileSystem.h"
 #include "lldb/Host/Host.h"
 #include "lldb/Host/HostInfo.h"
 #include "lldb/Interpreter/CommandCompletions.h"
@@ -44,8 +44,17 @@ using namespace lldb;
 using namespace lldb_private::process_gdb_remote;
 using namespace lldb_private;
 
+// Copy assignment operator to avoid copying m_mutex
+GDBRemoteCommunicationServerPlatform::PortMap &
+GDBRemoteCommunicationServerPlatform::PortMap::operator=(
+    const GDBRemoteCommunicationServerPlatform::PortMap &o) {
+  m_port_map = std::move(o.m_port_map);
+  return *this;
+}
+
 GDBRemoteCommunicationServerPlatform::PortMap::PortMap(uint16_t min_port,
-                                                       uint16_t max_port) {
+                                                       uint16_t max_port)
+    : m_mutex() {
   assert(min_port);
   for (; min_port < max_port; ++min_port)
     m_port_map[min_port] = LLDB_INVALID_PROCESS_ID;
@@ -54,11 +63,13 @@ GDBRemoteCommunicationServerPlatform::PortMap::PortMap(uint16_t min_port,
 void GDBRemoteCommunicationServerPlatform::PortMap::AllowPort(uint16_t port) {
   assert(port);
   // Do not modify existing mappings
+  std::lock_guard<std::mutex> guard(m_mutex);
   m_port_map.insert({port, LLDB_INVALID_PROCESS_ID});
 }
 
 llvm::Expected<uint16_t>
 GDBRemoteCommunicationServerPlatform::PortMap::GetNextAvailablePort() {
+  std::lock_guard<std::mutex> guard(m_mutex);
   if (m_port_map.empty())
     return 0; // Bind to port zero and get a port, we didn't have any
               // limitations
@@ -75,6 +86,7 @@ GDBRemoteCommunicationServerPlatform::PortMap::GetNextAvailablePort() {
 
 bool GDBRemoteCommunicationServerPlatform::PortMap::AssociatePortWithProcess(
     uint16_t port, lldb::pid_t pid) {
+  std::lock_guard<std::mutex> guard(m_mutex);
   auto pos = m_port_map.find(port);
   if (pos != m_port_map.end()) {
     pos->second = pid;
@@ -84,6 +96,7 @@ bool GDBRemoteCommunicationServerPlatform::PortMap::AssociatePortWithProcess(
 }
 
 bool GDBRemoteCommunicationServerPlatform::PortMap::FreePort(uint16_t port) {
+  std::lock_guard<std::mutex> guard(m_mutex);
   std::map<uint16_t, lldb::pid_t>::iterator pos = m_port_map.find(port);
   if (pos != m_port_map.end()) {
     pos->second = LLDB_INVALID_PROCESS_ID;
@@ -94,6 +107,7 @@ bool GDBRemoteCommunicationServerPlatform::PortMap::FreePort(uint16_t port) {
 
 bool GDBRemoteCommunicationServerPlatform::PortMap::FreePortForProcess(
     lldb::pid_t pid) {
+  std::lock_guard<std::mutex> guard(m_mutex);
   if (!m_port_map.empty()) {
     for (auto &pair : m_port_map) {
       if (pair.second == pid) {
@@ -106,15 +120,22 @@ bool GDBRemoteCommunicationServerPlatform::PortMap::FreePortForProcess(
 }
 
 bool GDBRemoteCommunicationServerPlatform::PortMap::empty() const {
+  std::lock_guard<std::mutex> guard(m_mutex);
   return m_port_map.empty();
 }
 
+GDBRemoteCommunicationServerPlatform::PortMap
+    GDBRemoteCommunicationServerPlatform::g_port_map;
+std::set<lldb::pid_t> GDBRemoteCommunicationServerPlatform::g_spawned_pids;
+std::mutex GDBRemoteCommunicationServerPlatform::g_spawned_pids_mutex;
+
 // GDBRemoteCommunicationServerPlatform constructor
 GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform(
-    const Socket::SocketProtocol socket_protocol, const char *socket_scheme)
-    : GDBRemoteCommunicationServerCommon(),
-      m_socket_protocol(socket_protocol), m_socket_scheme(socket_scheme),
-      m_spawned_pids_mutex(), m_port_map(), m_port_offset(0) {
+    const Socket::SocketProtocol socket_protocol, const char *socket_scheme,
+    const lldb_private::Args &args, uint16_t port_offset)
+    : GDBRemoteCommunicationServerCommon(), m_socket_protocol(socket_protocol),
+      m_socket_scheme(socket_scheme), m_inferior_arguments(args),
+      m_port_offset(port_offset) {
   m_pending_gdb_server.pid = LLDB_INVALID_PROCESS_ID;
   m_pending_gdb_server.port = 0;
 
@@ -159,11 +180,72 @@ GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform(
 GDBRemoteCommunicationServerPlatform::~GDBRemoteCommunicationServerPlatform() =
     default;
 
+lldb::thread_result_t GDBRemoteCommunicationServerPlatform::ThreadProc() {
+  // We need a virtual working directory per thread.
+  FileSystem::InitializePerThread();
+
+  Log *log = GetLog(LLDBLog::Platform);
+
+  if (IsConnected()) {
+    LLDB_LOGF(log,
+              "GDBRemoteCommunicationServerPlatform::%s() "
+              "Thread started...",
+              __FUNCTION__);
+
+    if (m_inferior_arguments.GetArgumentCount() > 0) {
+      lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
+      std::optional<uint16_t> port;
+      std::string socket_name;
+      Status error = LaunchGDBServer(m_inferior_arguments,
+                                     "", // hostname
+                                     pid, port, socket_name);
+      if (error.Success())
+        SetPendingGdbServer(pid, *port, socket_name);
+    }
+
+    bool interrupt = false;
+    bool done = false;
+    Status error;
+    while (!interrupt && !done) {
+      if (GetPacketAndSendResponse(std::nullopt, error, interrupt, done) !=
+          GDBRemoteCommunication::PacketResult::Success)
+        break;
+    }
+
+    if (error.Fail()) {
+      LLDB_LOGF(log,
+                "GDBRemoteCommunicationServerPlatform::%s() "
+                "GetPacketAndSendResponse: %s",
+                __FUNCTION__, error.AsCString());
+    }
+  }
+
+  LLDB_LOGF(log,
+            "GDBRemoteCommunicationServerPlatform::%s() "
+            "Disconnected. Killing child processes...",
+            __FUNCTION__);
+  for (lldb::pid_t pid : m_spawned_pids)
+    KillSpawnedProcess(pid);
+
+  // Do do not wait for child processes. See comments in
+  // DebugserverProcessReaped() for details.
+
+  FileSystem::Terminate();
+
+  LLDB_LOGF(log,
+            "GDBRemoteCommunicationServerPlatform::%s() "
+            "Thread exited.",
+            __FUNCTION__);
+
+  delete this;
+  return {};
+}
+
 Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer(
     const lldb_private::Args &args, std::string hostname, lldb::pid_t &pid,
     std::optional<uint16_t> &port, std::string &socket_name) {
   if (!port) {
-    llvm::Expected<uint16_t> available_port = m_port_map.GetNextAvailablePort();
+    llvm::Expected<uint16_t> available_port = g_port_map.GetNextAvailablePort();
     if (available_port)
       port = *available_port;
     else
@@ -181,23 +263,25 @@ Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer(
   if (hostname.empty())
     hostname = "127.0.0.1";
 
-  Log *log = GetLog(LLDBLog::Platform);
-  LLDB_LOGF(log, "Launching debugserver with: %s:%u...", hostname.c_str(),
-            *port);
+  auto cwd = FileSystem::Instance()
+                 .GetVirtualFileSystem()
+                 ->getCurrentWorkingDirectory();
+  if (cwd)
+    debugserver_launch_info.SetWorkingDirectory(FileSpec(*cwd));
 
   // Do not run in a new session so that it can not linger after the platform
   // closes.
   debugserver_launch_info.SetLaunchInSeparateProcessGroup(false);
   debugserver_launch_info.SetMonitorProcessCallback(
-      std::bind(&GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped,
-                this, std::placeholders::_1));
+      &GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped);
 
   std::ostringstream url;
 // debugserver does not accept the URL scheme prefix.
 #if !defined(__APPLE__)
   url << m_socket_scheme << "://";
 #endif
-  uint16_t *port_ptr = &*port;
+  uint16_t child_port = *port;
+  uint16_t *port_ptr = &child_port;
   if (m_socket_protocol == Socket::ProtocolTcp) {
     std::string platform_uri = GetConnection()->GetURI();
     std::optional<URI> parsed_uri = URI::Parse(platform_uri);
@@ -208,19 +292,44 @@ Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer(
     port_ptr = nullptr;
   }
 
+  Log *log = GetLog(LLDBLog::Platform);
+  LLDB_LOGF(log,
+            "GDBRemoteCommunicationServerPlatform::%s() "
+            "Host %s launching debugserver with: %s...",
+            __FUNCTION__, hostname.c_str(), url.str().c_str());
+
   Status error = StartDebugserverProcess(
       url.str().c_str(), nullptr, debugserver_launch_info, port_ptr, &args, -1);
 
   pid = debugserver_launch_info.GetProcessID();
+
+  if (error.Success()) {
+    LLDB_LOGF(log,
+              "GDBRemoteCommunicationServerPlatform::%s() "
+              "debugserver launched successfully as pid %" PRIu64,
+              __FUNCTION__, pid);
+  } else {
+    LLDB_LOGF(log,
+              "GDBRemoteCommunicationServerPlatform::%s() "
+              "debugserver launch failed: %s",
+              __FUNCTION__, error.AsCString());
+  }
+
+  // TODO: Be sure gdbserver uses the requested port.
+  // assert(!port_ptr || *port == 0 || *port == child_port)
+  // Use only the original *port returned by GetNextAvailablePort()
+  // for AssociatePortWithProcess() or FreePort() below.
+
   if (pid != LLDB_INVALID_PROCESS_ID) {
-    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-    m_spawned_pids.insert(pid);
+    AddSpawnedProcess(pid);
     if (*port > 0)
-      m_port_map.AssociatePortWithProcess(*port, pid);
+      g_port_map.AssociatePortWithProcess(*port, pid);
   } else {
     if (*port > 0)
-      m_port_map.FreePort(*port);
+      g_port_map.FreePort(*port);
   }
+  if (port_ptr)
+    *port = child_port;
   return error;
 }
 
@@ -230,10 +339,6 @@ GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer(
   // Spawn a local debugserver as a platform so we can then attach or launch a
   // process...
 
-  Log *log = GetLog(LLDBLog::Platform);
-  LLDB_LOGF(log, "GDBRemoteCommunicationServerPlatform::%s() called",
-            __FUNCTION__);
-
   ConnectionFileDescriptor file_conn;
   std::string hostname;
   packet.SetFilePos(::strlen("qLaunchGDBServer;"));
@@ -255,18 +360,9 @@ GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer(
   Status error =
       LaunchGDBServer(Args(), hostname, debugserver_pid, port, socket_name);
   if (error.Fail()) {
-    LLDB_LOGF(log,
-              "GDBRemoteCommunicationServerPlatform::%s() debugserver "
-              "launch failed: %s",
-              __FUNCTION__, error.AsCString());
     return SendErrorResponse(9);
   }
 
-  LLDB_LOGF(log,
-            "GDBRemoteCommunicationServerPlatform::%s() debugserver "
-            "launched successfully as pid %" PRIu64,
-            __FUNCTION__, debugserver_pid);
-
   StreamGDBRemote response;
   assert(port);
   response.Printf("pid:%" PRIu64 ";port:%u;", debugserver_pid,
@@ -317,28 +413,45 @@ GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess(
 
   lldb::pid_t pid = packet.GetU64(LLDB_INVALID_PROCESS_ID);
 
+  if (SpawnedProcessFinished(pid))
+    m_spawned_pids.erase(pid);
+
   // verify that we know anything about this pid. Scope for locker
-  {
-    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-    if (m_spawned_pids.find(pid) == m_spawned_pids.end()) {
-      // not a pid we know about
-      return SendErrorResponse(10);
-    }
+  if ((m_spawned_pids.find(pid) == m_spawned_pids.end())) {
+    // not a pid we know about
+    return SendErrorResponse(10); // ECHILD
   }
 
   // go ahead and attempt to kill the spawned process
-  if (KillSpawnedProcess(pid))
+  if (KillSpawnedProcess(pid)) {
+    m_spawned_pids.erase(pid);
     return SendOKResponse();
-  else
-    return SendErrorResponse(11);
+  } else
+    return SendErrorResponse(11); // EDEADLK
+}
+
+void GDBRemoteCommunicationServerPlatform::AddSpawnedProcess(lldb::pid_t pid) {
+  std::lock_guard<std::mutex> guard(g_spawned_pids_mutex);
+
+  // If MonitorChildProcessThreadFunction() failed hope the system will not
+  // reuse pid of zombie processes.
+  // assert(g_spawned_pids.find(pid) == g_spawned_pids.end());
+
+  g_spawned_pids.insert(pid);
+  m_spawned_pids.insert(pid);
+}
+
+bool GDBRemoteCommunicationServerPlatform::SpawnedProcessFinished(
+    lldb::pid_t pid) {
+  std::lock_guard<std::mutex> guard(g_spawned_pids_mutex);
+  return (g_spawned_pids.find(pid) == g_spawned_pids.end());
 }
 
 bool GDBRemoteCommunicationServerPlatform::KillSpawnedProcess(lldb::pid_t pid) {
   // make sure we know about this process
-  {
-    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-    if (m_spawned_pids.find(pid) == m_spawned_pids.end())
-      return false;
+  if (SpawnedProcessFinished(pid)) {
+    // it seems the process has been finished recently
+    return true;
   }
 
   // first try a SIGTERM (standard kill)
@@ -346,46 +459,30 @@ bool GDBRemoteCommunicationServerPlatform::KillSpawnedProcess(lldb::pid_t pid) {
 
   // check if that worked
   for (size_t i = 0; i < 10; ++i) {
-    {
-      std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-      if (m_spawned_pids.find(pid) == m_spawned_pids.end()) {
-        // it is now killed
-        return true;
-      }
+    if (SpawnedProcessFinished(pid)) {
+      // it is now killed
+      return true;
     }
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
   }
 
-  {
-    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-    if (m_spawned_pids.find(pid) == m_spawned_pids.end())
-      return true;
-  }
+  if (SpawnedProcessFinished(pid))
+    return true;
 
   // the launched process still lives.  Now try killing it again, this time
   // with an unblockable signal.
   Host::Kill(pid, SIGKILL);
 
   for (size_t i = 0; i < 10; ++i) {
-    {
-      std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-      if (m_spawned_pids.find(pid) == m_spawned_pids.end()) {
-        // it is now killed
-        return true;
-      }
+    if (SpawnedProcessFinished(pid)) {
+      // it is now killed
+      return true;
     }
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
   }
 
   // check one more time after the final sleep
-  {
-    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-    if (m_spawned_pids.find(pid) == m_spawned_pids.end())
-      return true;
-  }
-
-  // no luck - the process still lives
-  return false;
+  return SpawnedProcessFinished(pid);
 }
 
 GDBRemoteCommunication::PacketResult
@@ -442,12 +539,14 @@ GDBRemoteCommunication::PacketResult
 GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir(
     StringExtractorGDBRemote &packet) {
 
-  llvm::SmallString<64> cwd;
-  if (std::error_code ec = llvm::sys::fs::current_path(cwd))
-    return SendErrorResponse(ec.value());
+  auto cwd = FileSystem::Instance()
+                 .GetVirtualFileSystem()
+                 ->getCurrentWorkingDirectory();
+  if (!cwd)
+    return SendErrorResponse(cwd.getError());
 
   StreamString response;
-  response.PutBytesAsRawHex8(cwd.data(), cwd.size());
+  response.PutBytesAsRawHex8(cwd->data(), cwd->size());
   return SendPacketNoLock(response.GetString());
 }
 
@@ -458,7 +557,9 @@ GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir(
   std::string path;
   packet.GetHexByteString(path);
 
-  if (std::error_code ec = llvm::sys::fs::set_current_path(path))
+  if (std::error_code ec = FileSystem::Instance()
+                               .GetVirtualFileSystem()
+                               ->setCurrentWorkingDirectory(path))
     return SendErrorResponse(ec.value());
   return SendOKResponse();
 }
@@ -519,10 +620,25 @@ GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo(
 }
 
 void GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped(
-    lldb::pid_t pid) {
-  std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-  m_port_map.FreePortForProcess(pid);
-  m_spawned_pids.erase(pid);
+    lldb::pid_t pid, int signal, int status) {
+
+  // Note MonitoringProcessLauncher::LaunchProcess() does not store the monitor
+  // thread and we cannot control it. The child process monitor thread will call
+  // static DebugserverProcessReaped() callback. It may happen after destroying
+  // the GDBRemoteCommunicationServerPlatform instance.
+  // HostProcessWindows::MonitorThread() calls the callback anyway when the
+  // process is finished. But MonitorChildProcessThreadFunction() in
+  // common/Host.cpp can fail and exit w/o calling the callback. So g_port_map
+  // and g_spawned_pids may leak in this case. The system must not reuse pid
+  // of zombie processes, so leaking g_spawned_pids shouldn't be a problem.
+  // But we can do nothing with g_port_map in this case.
+
+  g_port_map.FreePortForProcess(pid);
+
+  {
+    std::lock_guard<std::mutex> guard(g_spawned_pids_mutex);
+    g_spawned_pids.erase(pid);
+  }
 }
 
 Status GDBRemoteCommunicationServerPlatform::LaunchProcess() {
@@ -530,38 +646,51 @@ Status GDBRemoteCommunicationServerPlatform::LaunchProcess() {
     return Status("%s: no process command line specified to launch",
                   __FUNCTION__);
 
+  auto cwd = FileSystem::Instance()
+                 .GetVirtualFileSystem()
+                 ->getCurrentWorkingDirectory();
+  if (cwd)
+    m_process_launch_info.SetWorkingDirectory(FileSpec(*cwd));
+
   // specify the process monitor if not already set.  This should generally be
   // what happens since we need to reap started processes.
   if (!m_process_launch_info.GetMonitorProcessCallback())
-    m_process_launch_info.SetMonitorProcessCallback(std::bind(
-        &GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped, this,
-        std::placeholders::_1));
+    m_process_launch_info.SetMonitorProcessCallback(
+        &GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped);
+
+  Log *log = GetLog(LLDBLog::Platform);
 
   Status error = Host::LaunchProcess(m_process_launch_info);
   if (!error.Success()) {
-    fprintf(stderr, "%s: failed to launch executable %s", __FUNCTION__,
-            m_process_launch_info.GetArguments().GetArgumentAtIndex(0));
+    LLDB_LOGF(log,
+              "GDBRemoteCommunicationServerPlatform::%s() "
+              "Failed to launch executable %s: %s",
+              __FUNCTION__,
+              m_process_launch_info.GetArguments().GetArgumentAtIndex(0),
+              error.AsCString());
     return error;
   }
 
-  printf("Launched '%s' as process %" PRIu64 "...\n",
-         m_process_launch_info.GetArguments().GetArgumentAtIndex(0),
-         m_process_launch_info.GetProcessID());
+  LLDB_LOGF(log,
+            "GDBRemoteCommunicationServerPlatform::%s() "
+            "Launched '%s' as process %" PRIu64,
+            __FUNCTION__,
+            m_process_launch_info.GetArguments().GetArgumentAtIndex(0),
+            m_process_launch_info.GetProcessID());
 
   // add to list of spawned processes.  On an lldb-gdbserver, we would expect
   // there to be only one.
   const auto pid = m_process_launch_info.GetProcessID();
   if (pid != LLDB_INVALID_PROCESS_ID) {
     // add to spawned pids
-    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
-    m_spawned_pids.insert(pid);
+    AddSpawnedProcess(pid);
   }
 
   return error;
 }
 
 void GDBRemoteCommunicationServerPlatform::SetPortMap(PortMap &&port_map) {
-  m_port_map = std::move(port_map);
+  g_port_map = std::move(port_map);
 }
 
 const FileSpec &GDBRemoteCommunicationServerPlatform::GetDomainSocketDir() {
@@ -594,10 +723,6 @@ GDBRemoteCommunicationServerPlatform::GetDomainSocketPath(const char *prefix) {
   return FileSpec(socket_path.c_str());
 }
 
-void GDBRemoteCommunicationServerPlatform::SetPortOffset(uint16_t port_offset) {
-  m_port_offset = port_offset;
-}
-
 void GDBRemoteCommunicationServerPlatform::SetPendingGdbServer(
     lldb::pid_t pid, uint16_t port, const std::string &socket_name) {
   m_pending_gdb_server.pid = pid;
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h
index 1853025466cf0..357ac2beba64d 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h
@@ -32,12 +32,15 @@ class GDBRemoteCommunicationServerPlatform
     // communicate on.
 
     // Construct an empty map, where empty means any port is allowed.
-    PortMap() = default;
+    PortMap() : m_mutex() {};
 
     // Make a port map with a range of free ports
     // from min_port to max_port-1.
     PortMap(uint16_t min_port, uint16_t max_port);
 
+    // Copy assignment operator to avoid copying m_mutex
+    PortMap &operator=(const PortMap &o);
+
     // Add a port to the map. If it is already in the map do not modify
     // its mapping. (used ports remain used, new ports start as free)
     void AllowPort(uint16_t port);
@@ -70,10 +73,12 @@ class GDBRemoteCommunicationServerPlatform
 
   private:
     std::map<uint16_t, lldb::pid_t> m_port_map;
+    mutable std::mutex m_mutex;
   };
 
   GDBRemoteCommunicationServerPlatform(
-      const Socket::SocketProtocol socket_protocol, const char *socket_scheme);
+      const Socket::SocketProtocol socket_protocol, const char *socket_scheme,
+      const lldb_private::Args &args, uint16_t port_offset = 0);
 
   ~GDBRemoteCommunicationServerPlatform() override;
 
@@ -81,11 +86,7 @@ class GDBRemoteCommunicationServerPlatform
 
   // Set both ports to zero to let the platform automatically bind to
   // a port chosen by the OS.
-  void SetPortMap(PortMap &&port_map);
-
-  void SetPortOffset(uint16_t port_offset);
-
-  void SetInferiorArguments(const lldb_private::Args &args);
+  static void SetPortMap(PortMap &&port_map);
 
   // Set port if you want to use a specific port number.
   // Otherwise port will be set to the port that was chosen for you.
@@ -96,14 +97,18 @@ class GDBRemoteCommunicationServerPlatform
   void SetPendingGdbServer(lldb::pid_t pid, uint16_t port,
                            const std::string &socket_name);
 
+  lldb::thread_result_t ThreadProc();
+
 protected:
   const Socket::SocketProtocol m_socket_protocol;
   const std::string m_socket_scheme;
-  std::recursive_mutex m_spawned_pids_mutex;
+  const lldb_private::Args m_inferior_arguments;
+  const uint16_t m_port_offset;
   std::set<lldb::pid_t> m_spawned_pids;
+  static std::set<lldb::pid_t> g_spawned_pids;
+  static std::mutex g_spawned_pids_mutex;
 
-  PortMap m_port_map;
-  uint16_t m_port_offset;
+  static PortMap g_port_map;
   struct {
     lldb::pid_t pid;
     uint16_t port;
@@ -129,9 +134,11 @@ class GDBRemoteCommunicationServerPlatform
   PacketResult Handle_jSignalsInfo(StringExtractorGDBRemote &packet);
 
 private:
-  bool KillSpawnedProcess(lldb::pid_t pid);
+  void AddSpawnedProcess(lldb::pid_t pid);
+  static bool SpawnedProcessFinished(lldb::pid_t pid);
+  static bool KillSpawnedProcess(lldb::pid_t pid);
 
-  void DebugserverProcessReaped(lldb::pid_t pid);
+  static void DebugserverProcessReaped(lldb::pid_t pid, int signal, int status);
 
   static const FileSpec &GetDomainSocketDir();
 
diff --git a/lldb/tools/lldb-server/lldb-platform.cpp b/lldb/tools/lldb-server/lldb-platform.cpp
index 7148a1d2a3094..1cbadb0a01750 100644
--- a/lldb/tools/lldb-server/lldb-platform.cpp
+++ b/lldb/tools/lldb-server/lldb-platform.cpp
@@ -32,9 +32,11 @@
 #include "lldb/Host/ConnectionFileDescriptor.h"
 #include "lldb/Host/HostGetOpt.h"
 #include "lldb/Host/OptionParser.h"
+#include "lldb/Host/ThreadLauncher.h"
 #include "lldb/Host/common/TCPSocket.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/Status.h"
+#include "lldb/Utility/UriParser.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -282,10 +284,10 @@ int main_platform(int argc, char *argv[]) {
     }
   }
 
-  GDBRemoteCommunicationServerPlatform platform(
-      acceptor_up->GetSocketProtocol(), acceptor_up->GetSocketScheme());
-  if (port_offset > 0)
-    platform.SetPortOffset(port_offset);
+  if (!gdbserver_portmap.empty()) {
+    GDBRemoteCommunicationServerPlatform::SetPortMap(
+        std::move(gdbserver_portmap));
+  }
 
   do {
     const bool children_inherit_accept_socket = true;
@@ -297,85 +299,28 @@ int main_platform(int argc, char *argv[]) {
     }
     printf("Connection established.\n");
 
+    GDBRemoteCommunicationServerPlatform *platform =
+        new GDBRemoteCommunicationServerPlatform(
+            acceptor_up->GetSocketProtocol(), acceptor_up->GetSocketScheme(),
+            inferior_arguments, port_offset);
+    platform->SetConnection(std::unique_ptr<Connection>(conn));
+
     if (g_server) {
-      // Collect child zombie processes.
-#if !defined(_WIN32)
-      ::pid_t waitResult;
-      while ((waitResult = waitpid(-1, nullptr, WNOHANG)) > 0) {
-        // waitResult is the child pid
-        gdbserver_portmap.FreePortForProcess(waitResult);
-      }
-#endif
-      // TODO: Clean up portmap for Windows when children die
-      // See https://github.com/llvm/llvm-project/issues/90923
-
-      // After collecting zombie ports, get the next available
-      GDBRemoteCommunicationServerPlatform::PortMap portmap_for_child;
-      llvm::Expected<uint16_t> available_port =
-          gdbserver_portmap.GetNextAvailablePort();
-      if (available_port) {
-        // GetNextAvailablePort() may return 0 if gdbserver_portmap is empty.
-        if (*available_port)
-          portmap_for_child.AllowPort(*available_port);
-      } else {
-        llvm::consumeError(available_port.takeError());
-        fprintf(stderr,
-                "no available gdbserver port for connection - dropping...\n");
-        delete conn;
-        continue;
-      }
-      platform.SetPortMap(std::move(portmap_for_child));
-
-      auto childPid = fork();
-      if (childPid) {
-        gdbserver_portmap.AssociatePortWithProcess(*available_port, childPid);
-        // Parent doesn't need a connection to the lldb client
-        delete conn;
-
-        // Parent will continue to listen for new connections.
-        continue;
-      } else {
-        // Child process will handle the connection and exit.
-        g_server = 0;
-        // Listening socket is owned by parent process.
-        acceptor_up.release();
+      std::optional<URI> uri = URI::Parse(conn->GetURI());
+      const std::string thread_name =
+          uri ? llvm::formatv("conn:{0}>", uri->port) : std::string("conn:?");
+      auto maybe_thread = ThreadLauncher::LaunchThread(
+          thread_name, [platform] { return platform->ThreadProc(); });
+      if (!maybe_thread) {
+        WithColor::error() << "failed to start thread: "
+                           << maybe_thread.takeError() << '\n';
+        delete platform;
       }
     } else {
       // If not running as a server, this process will not accept
       // connections while a connection is active.
       acceptor_up.reset();
-
-      // When not running in server mode, use all available ports
-      platform.SetPortMap(std::move(gdbserver_portmap));
-    }
-
-    platform.SetConnection(std::unique_ptr<Connection>(conn));
-
-    if (platform.IsConnected()) {
-      if (inferior_arguments.GetArgumentCount() > 0) {
-        lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
-        std::optional<uint16_t> port;
-        std::string socket_name;
-        Status error = platform.LaunchGDBServer(inferior_arguments,
-                                                "", // hostname
-                                                pid, port, socket_name);
-        if (error.Success())
-          platform.SetPendingGdbServer(pid, *port, socket_name);
-        else
-          fprintf(stderr, "failed to start gdbserver: %s\n", error.AsCString());
-      }
-
-      bool interrupt = false;
-      bool done = false;
-      while (!interrupt && !done) {
-        if (platform.GetPacketAndSendResponse(std::nullopt, error, interrupt,
-                                              done) !=
-            GDBRemoteCommunication::PacketResult::Success)
-          break;
-      }
-
-      if (error.Fail())
-        WithColor::error() << error.AsCString() << '\n';
+      platform->ThreadProc();
     }
   } while (g_server);
 

>From 17916b6bc0174e65b49f63c363377cc25e9a6f45 Mon Sep 17 00:00:00 2001
From: Dmitry Vasilyev <dvassiliev at accesssoftek.com>
Date: Mon, 29 Jul 2024 20:55:21 +0400
Subject: [PATCH 2/2] Merged with #100659

---
 .../Host/posix/ProcessLauncherPosixFork.cpp   | 10 +++++--
 .../Host/windows/ProcessLauncherWindows.cpp   | 26 +++++++++++++++----
 2 files changed, 29 insertions(+), 7 deletions(-)

diff --git a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
index 0a832ebad13a7..fd0f2a0a55f31 100644
--- a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
+++ b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
@@ -201,7 +201,7 @@ struct ForkLaunchInfo {
   execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
 
 #if defined(__linux__)
-  if (errno == ETXTBSY) {
+  for (int i = 0; i < 50; ++i) {
     // On android M and earlier we can get this error because the adb daemon
     // can hold a write handle on the executable even after it has finished
     // uploading it. This state lasts only a short time and happens only when
@@ -210,7 +210,13 @@ struct ForkLaunchInfo {
     // shell" command in the fork() child before it has had a chance to exec.)
     // Since this state should clear up quickly, wait a while and then give it
     // one more go.
-    usleep(50000);
+    // If `lldb-server platform` copies the executable in one thread and
+    // launches gdbserver in another thread (fork+execve), the FD may stay
+    // opened in the forked child process until execve() even if the first
+    // thread closed the file. Let's wait a while.
+    if (errno != ETXTBSY)
+      break;
+    usleep(100000);
     execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
   }
 #endif
diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index baa422c15cae2..ee5f8fda1d492 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -113,14 +113,30 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
   // command line is not empty, its contents may be modified by CreateProcessW.
   WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0];
 
-  BOOL result = ::CreateProcessW(
-      wexecutable.c_str(), pwcommandLine, NULL, NULL, TRUE, flags, env_block,
-      wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
-      &startupinfo, &pi);
+  BOOL result;
+  DWORD last_error = 0;
+  // This is the workaround for the error "The process cannot access the file
+  // because it is being used by another process". Note the executable file is
+  // installed to the target by the process `lldb-server platform`, but launched
+  // by the process `lldb-server gdbserver`. Sometimes system may block the file
+  // for some time after copying.
+  for (int i = 0; i < 50; ++i) {
+    result = ::CreateProcessW(
+        wexecutable.c_str(), pwcommandLine, NULL, NULL, TRUE, flags, env_block,
+        wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
+        &startupinfo, &pi);
+    if (!result) {
+      last_error = ::GetLastError();
+      if (last_error != ERROR_SHARING_VIOLATION)
+        break;
+      ::Sleep(100);
+    } else
+      break;
+  }
 
   if (!result) {
     // Call GetLastError before we make any other system calls.
-    error.SetError(::GetLastError(), eErrorTypeWin32);
+    error.SetError(last_error, eErrorTypeWin32);
     // Note that error 50 ("The request is not supported") will occur if you
     // try debug a 64-bit inferior from a 32-bit LLDB.
   }



More information about the lldb-commits mailing list