[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