[lldb] Extract debug server location code (#145706)

.. from the guts of GDBRemoteCommunication to ~top level.

This is motivated by #131519 and by the fact that's impossible to guess
whether the author of a symlink intended it to be a "convenience
shortcut" -- meaning it should be resolved before looking for related
files; or an "implementation detail" -- meaning the related files should
be located near the symlink itself.

This debate is particularly ridiculous when it comes to lldb-server
running in platform mode, because it also functions as a debug server,
so what we really just need to do is to pass /proc/self/exe in a
platform-independent manner.

Moving the location logic higher up achieves that as lldb-platform (on
non-macos) can pass `HostInfo::GetProgramFileSpec`, while liblldb can
use the existing complex logic (which only worked on liblldb anyway as
lldb-platform doesn't have a lldb_private::Platform instance).

Another benefit of this patch is a reduction in dependency from
GDBRemoteCommunication to the rest of liblldb (achieved by avoiding the
Platform dependency).
This commit is contained in:
Pavel Labath 2025-06-27 11:16:57 +02:00 committed by GitHub
parent 7255c3aee3
commit 2c90c0b90c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 160 additions and 133 deletions

View File

@ -11,7 +11,6 @@
#include "lldb/Host/Config.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/Pipe.h"
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Host/Socket.h"
@ -33,14 +32,6 @@
#include <sys/stat.h>
#include <variant>
#if defined(__APPLE__)
#define DEBUGSERVER_BASENAME "debugserver"
#elif defined(_WIN32)
#define DEBUGSERVER_BASENAME "lldb-server.exe"
#else
#define DEBUGSERVER_BASENAME "lldb-server"
#endif
#if HAVE_LIBCOMPRESSION
#include <compression.h>
#endif
@ -836,77 +827,11 @@ GDBRemoteCommunication::CheckForPacket(const uint8_t *src, size_t src_len,
return GDBRemoteCommunication::PacketType::Invalid;
}
FileSpec GDBRemoteCommunication::GetDebugserverPath(Platform *platform) {
Log *log = GetLog(GDBRLog::Process);
// If we locate debugserver, keep that located version around
static FileSpec g_debugserver_file_spec;
FileSpec debugserver_file_spec;
Environment host_env = Host::GetEnvironment();
// Always check to see if we have an environment override for the path to the
// debugserver to use and use it if we do.
std::string env_debugserver_path = host_env.lookup("LLDB_DEBUGSERVER_PATH");
if (!env_debugserver_path.empty()) {
debugserver_file_spec.SetFile(env_debugserver_path,
FileSpec::Style::native);
LLDB_LOGF(log,
"GDBRemoteCommunication::%s() gdb-remote stub exe path set "
"from environment variable: %s",
__FUNCTION__, env_debugserver_path.c_str());
} else
debugserver_file_spec = g_debugserver_file_spec;
bool debugserver_exists =
FileSystem::Instance().Exists(debugserver_file_spec);
if (!debugserver_exists) {
// The debugserver binary is in the LLDB.framework/Resources directory.
debugserver_file_spec = HostInfo::GetSupportExeDir();
if (debugserver_file_spec) {
debugserver_file_spec.AppendPathComponent(DEBUGSERVER_BASENAME);
debugserver_exists = FileSystem::Instance().Exists(debugserver_file_spec);
if (debugserver_exists) {
LLDB_LOGF(log,
"GDBRemoteCommunication::%s() found gdb-remote stub exe '%s'",
__FUNCTION__, debugserver_file_spec.GetPath().c_str());
g_debugserver_file_spec = debugserver_file_spec;
} else {
if (platform)
debugserver_file_spec =
platform->LocateExecutable(DEBUGSERVER_BASENAME);
else
debugserver_file_spec.Clear();
if (debugserver_file_spec) {
// Platform::LocateExecutable() wouldn't return a path if it doesn't
// exist
debugserver_exists = true;
} else {
LLDB_LOGF(log,
"GDBRemoteCommunication::%s() could not find "
"gdb-remote stub exe '%s'",
__FUNCTION__, debugserver_file_spec.GetPath().c_str());
}
// Don't cache the platform specific GDB server binary as it could
// change from platform to platform
g_debugserver_file_spec.Clear();
}
}
}
return debugserver_file_spec;
}
Status GDBRemoteCommunication::StartDebugserverProcess(
std::variant<llvm::StringRef, shared_fd_t> comm, Platform *platform,
std::variant<llvm::StringRef, shared_fd_t> comm,
ProcessLaunchInfo &launch_info, const Args *inferior_args) {
Log *log = GetLog(GDBRLog::Process);
FileSpec debugserver_file_spec = GetDebugserverPath(platform);
if (!debugserver_file_spec)
return Status::FromErrorString("unable to locate " DEBUGSERVER_BASENAME);
launch_info.SetExecutableFile(debugserver_file_spec,
/*add_exe_file_as_first_arg=*/true);
Args &debugserver_args = launch_info.GetArguments();
#if !defined(__APPLE__)

View File

@ -134,16 +134,12 @@ public:
std::chrono::seconds GetPacketTimeout() const { return m_packet_timeout; }
// Get the debugserver path and check that it exist.
static FileSpec GetDebugserverPath(Platform *platform);
// Start a debugserver instance on the current host using the
// supplied connection URL.
static Status StartDebugserverProcess(
std::variant<llvm::StringRef, shared_fd_t> comm,
Platform *platform, // If non nullptr, then check with the platform for
// the GDB server binary if it can't be located
ProcessLaunchInfo &launch_info, const Args *inferior_args);
static Status
StartDebugserverProcess(std::variant<llvm::StringRef, shared_fd_t> comm,
ProcessLaunchInfo &launch_info,
const Args *inferior_args);
void DumpHistory(Stream &strm);

View File

@ -46,9 +46,10 @@ using namespace lldb_private;
// GDBRemoteCommunicationServerPlatform constructor
GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform(
const Socket::SocketProtocol socket_protocol, uint16_t gdbserver_port)
: GDBRemoteCommunicationServerCommon(), m_socket_protocol(socket_protocol),
m_gdbserver_port(gdbserver_port) {
FileSpec debugserver_path, const Socket::SocketProtocol socket_protocol,
uint16_t gdbserver_port)
: m_debugserver_path(std::move(debugserver_path)),
m_socket_protocol(socket_protocol), m_gdbserver_port(gdbserver_port) {
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_qC,
@ -102,14 +103,15 @@ Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer(
debugserver_launch_info.SetLaunchInSeparateProcessGroup(false);
debugserver_launch_info.SetMonitorProcessCallback(
[](lldb::pid_t, int, int) {});
if (!FileSystem::Instance().Exists(m_debugserver_path))
return Status::FromErrorString("debugserver does not exist");
debugserver_launch_info.SetExecutableFile(m_debugserver_path,
/*add_exe_file_as_first_arg=*/true);
Status error;
if (fd == SharedSocket::kInvalidFD) {
if (m_socket_protocol == Socket::ProtocolTcp) {
// Just check that GDBServer exists. GDBServer must be launched after
// accepting the connection.
if (!GetDebugserverPath(nullptr))
return Status::FromErrorString("unable to locate debugserver");
// The server will be launched after accepting the connection.
return Status();
}
@ -120,13 +122,11 @@ Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer(
#endif
socket_name = GetDomainSocketPath("gdbserver").GetPath();
url << socket_name;
error = StartDebugserverProcess(url.str(), nullptr, debugserver_launch_info,
&args);
error = StartDebugserverProcess(url.str(), debugserver_launch_info, &args);
} else {
if (m_socket_protocol != Socket::ProtocolTcp)
return Status::FromErrorString("protocol must be tcp");
error =
StartDebugserverProcess(fd, nullptr, debugserver_launch_info, &args);
error = StartDebugserverProcess(fd, debugserver_launch_info, &args);
}
if (error.Success()) {

View File

@ -26,7 +26,8 @@ class GDBRemoteCommunicationServerPlatform
: public GDBRemoteCommunicationServerCommon {
public:
GDBRemoteCommunicationServerPlatform(
const Socket::SocketProtocol socket_protocol, uint16_t gdbserver_port);
FileSpec debugserver_path, const Socket::SocketProtocol socket_protocol,
uint16_t gdbserver_port);
~GDBRemoteCommunicationServerPlatform() override;
@ -40,6 +41,7 @@ public:
void SetPendingGdbServer(const std::string &socket_name);
protected:
const FileSpec m_debugserver_path;
const Socket::SocketProtocol m_socket_protocol;
std::recursive_mutex m_spawned_pids_mutex;
std::set<lldb::pid_t> m_spawned_pids;

View File

@ -34,6 +34,7 @@
#include "lldb/DataFormatters/FormatManager.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/HostThread.h"
#include "lldb/Host/PosixApi.h"
#include "lldb/Host/PseudoTerminal.h"
@ -92,7 +93,14 @@
#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
#if defined(__APPLE__)
#define DEBUGSERVER_BASENAME "debugserver"
#elif defined(_WIN32)
#define DEBUGSERVER_BASENAME "lldb-server.exe"
#else
#define DEBUGSERVER_BASENAME "lldb-server"
#endif
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
@ -3448,6 +3456,51 @@ ProcessGDBRemote::EstablishConnectionIfNeeded(const ProcessInfo &process_info) {
return error;
}
static FileSpec GetDebugserverPath(Platform &platform) {
Log *log = GetLog(GDBRLog::Process);
// If we locate debugserver, keep that located version around
static FileSpec g_debugserver_file_spec;
FileSpec debugserver_file_spec;
Environment host_env = Host::GetEnvironment();
// Always check to see if we have an environment override for the path to the
// debugserver to use and use it if we do.
std::string env_debugserver_path = host_env.lookup("LLDB_DEBUGSERVER_PATH");
if (!env_debugserver_path.empty()) {
debugserver_file_spec.SetFile(env_debugserver_path,
FileSpec::Style::native);
LLDB_LOG(log, "gdb-remote stub exe path set from environment variable: {0}",
env_debugserver_path);
} else
debugserver_file_spec = g_debugserver_file_spec;
if (FileSystem::Instance().Exists(debugserver_file_spec))
return debugserver_file_spec;
// The debugserver binary is in the LLDB.framework/Resources directory.
debugserver_file_spec = HostInfo::GetSupportExeDir();
if (debugserver_file_spec) {
debugserver_file_spec.AppendPathComponent(DEBUGSERVER_BASENAME);
if (FileSystem::Instance().Exists(debugserver_file_spec)) {
LLDB_LOG(log, "found gdb-remote stub exe '{0}'", debugserver_file_spec);
g_debugserver_file_spec = debugserver_file_spec;
} else {
debugserver_file_spec = platform.LocateExecutable(DEBUGSERVER_BASENAME);
if (!debugserver_file_spec) {
// Platform::LocateExecutable() wouldn't return a path if it doesn't
// exist
LLDB_LOG(log, "could not find gdb-remote stub exe '{0}'",
debugserver_file_spec);
}
// Don't cache the platform specific GDB server binary as it could
// change from platform to platform
g_debugserver_file_spec.Clear();
}
}
return debugserver_file_spec;
}
Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
const ProcessInfo &process_info) {
using namespace std::placeholders; // For _1, _2, etc.
@ -3466,6 +3519,8 @@ Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
std::bind(MonitorDebugserverProcess, this_wp, _1, _2, _3));
debugserver_launch_info.SetUserID(process_info.GetUserID());
FileSpec debugserver_path = GetDebugserverPath(*GetTarget().GetPlatform());
#if defined(__APPLE__)
// On macOS 11, we need to support x86_64 applications translated to
// arm64. We check whether a binary is translated and spawn the correct
@ -3478,12 +3533,12 @@ Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
NULL, 0) == 0 &&
bufsize > 0) {
if (processInfo.kp_proc.p_flag & P_TRANSLATED) {
FileSpec rosetta_debugserver(
"/Library/Apple/usr/libexec/oah/debugserver");
debugserver_launch_info.SetExecutableFile(rosetta_debugserver, false);
debugserver_path = FileSpec("/Library/Apple/usr/libexec/oah/debugserver");
}
}
#endif
debugserver_launch_info.SetExecutableFile(debugserver_path,
/*add_exe_file_as_first_arg=*/true);
llvm::Expected<Socket::Pair> socket_pair = Socket::CreatePair();
if (!socket_pair)
@ -3495,7 +3550,6 @@ Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
return error;
error = m_gdb_comm.StartDebugserverProcess(shared_socket.GetSendableFD(),
GetTarget().GetPlatform().get(),
debugserver_launch_info, nullptr);
if (error.Fail()) {

View File

@ -1,8 +1,3 @@
""" Check that errors while handling qLaunchGDBServer are reported to the user.
Though this isn't a platform command in itself, the best way to test it is
from Python because we can juggle multiple processes more easily.
"""
import os
import socket
import shutil
@ -15,14 +10,7 @@ from lldbsuite.test import lldbutil
class TestPlatformProcessLaunchGDBServer(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@skipIfRemote
# Windows cannot delete the executable while it is running.
# On Darwin we may be using debugserver.
@skipUnlessPlatform(["linux"])
@add_test_categories(["lldb-server"])
def test_platform_process_launch_gdb_server(self):
self.build()
def _launch_and_connect(self, exe):
hostname = socket.getaddrinfo("localhost", 0, proto=socket.IPPROTO_TCP)[0][4][0]
listen_url = "[%s]:0" % hostname
@ -33,16 +21,9 @@ class TestPlatformProcessLaunchGDBServer(TestBase):
listen_url,
"--socket-file",
port_file,
"--",
self.getBuildArtifact("a.out"),
"foo",
]
# Run lldb-server from a new location.
new_lldb_server = self.getBuildArtifact("lldb-server")
shutil.copy(lldbgdbserverutils.get_lldb_server_exe(), new_lldb_server)
self.spawnSubprocess(new_lldb_server, commandline_args)
self.spawnSubprocess(exe, commandline_args)
socket_id = lldbutil.wait_for_file_on_target(self, port_file)
new_platform = lldb.SBPlatform("remote-" + self.getPlatform())
@ -51,10 +32,52 @@ class TestPlatformProcessLaunchGDBServer(TestBase):
connect_url = "connect://[%s]:%s" % (hostname, socket_id)
self.runCmd("platform connect %s" % connect_url)
# First connect to lldb-server which spawn a process to handle the connection.
# Then remove our new lldb-server so that when it tries to invoke itself as a
wd = self.getBuildArtifact("wd")
os.mkdir(wd)
new_platform.SetWorkingDirectory(wd)
@skipIfRemote
# Windows cannot delete the executable while it is running.
# On Darwin we may be using debugserver.
@skipUnlessPlatform(["linux"])
@add_test_categories(["lldb-server"])
def test_launch_error(self):
"""
Check that errors while handling qLaunchGDBServer are reported to the
user. Though this isn't a platform command in itself, the best way to
test it is from Python because we can juggle multiple processes more
easily.
"""
self.build()
# Run lldb-server from a new location.
new_lldb_server = self.getBuildArtifact("lldb-server")
shutil.copy(lldbgdbserverutils.get_lldb_server_exe(), new_lldb_server)
self._launch_and_connect(new_lldb_server)
# Now, remove our new lldb-server so that when it tries to invoke itself as a
# gdbserver, it fails.
os.remove(new_lldb_server)
self.runCmd("target create {}".format(self.getBuildArtifact("a.out")))
self.expect("run", substrs=["unable to launch a GDB server on"], error=True)
@skipIfRemote
@skipIfDarwin # Uses debugserver for debugging
@add_test_categories(["lldb-server"])
def test_launch_with_unusual_process_name(self):
"""
Test that lldb-server can launch a debug session when running under an
unusual name (or under a symlink which resolves to an unusal name).
"""
self.build()
# Run lldb-server from a new location.
new_lldb_server = self.getBuildArtifact("obfuscated-server")
shutil.copy(lldbgdbserverutils.get_lldb_server_exe(), new_lldb_server)
self._launch_and_connect(new_lldb_server)
self.runCmd("target create {}".format(self.getBuildArtifact("a.out")))
self.expect("run", substrs=["exited with status = 0"])

View File

@ -144,9 +144,9 @@ class TestDAP_console(lldbdap_testcase.DAPTestCaseBase):
)
# Verify the exit status message is printed.
self.assertIn(
"exited with status = -1 (0xffffffff) debugserver died with signal SIGTERM",
self.assertRegex(
console_output,
".*exited with status = -1 .* died with signal SIGTERM.*",
"Exit status does not contain message 'exited with status'",
)

View File

@ -30,6 +30,7 @@
#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h"
#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostGetOpt.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/MainLoop.h"
@ -192,14 +193,15 @@ static Status ListenGdbConnectionsIfNeeded(
}
static llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>>
AcceptGdbConnectionsIfNeeded(const Socket::SocketProtocol protocol,
AcceptGdbConnectionsIfNeeded(const FileSpec &debugserver_path,
const Socket::SocketProtocol protocol,
std::unique_ptr<TCPSocket> &gdb_sock,
MainLoop &main_loop, const uint16_t gdbserver_port,
const lldb_private::Args &args) {
if (protocol != Socket::ProtocolTcp)
return std::vector<MainLoopBase::ReadHandleUP>();
return gdb_sock->Accept(main_loop, [gdbserver_port,
return gdb_sock->Accept(main_loop, [debugserver_path, gdbserver_port,
&args](std::unique_ptr<Socket> sock_up) {
Log *log = GetLog(LLDBLog::Platform);
Status error;
@ -210,8 +212,8 @@ AcceptGdbConnectionsIfNeeded(const Socket::SocketProtocol protocol,
}
lldb::pid_t child_pid = LLDB_INVALID_PROCESS_ID;
std::string socket_name;
GDBRemoteCommunicationServerPlatform platform(Socket::ProtocolTcp,
gdbserver_port);
GDBRemoteCommunicationServerPlatform platform(
debugserver_path, Socket::ProtocolTcp, gdbserver_port);
error = platform.LaunchGDBServer(args, child_pid, socket_name,
shared_socket.GetSendableFD());
if (error.Success() && child_pid != LLDB_INVALID_PROCESS_ID) {
@ -341,6 +343,24 @@ static Status spawn_process(const char *progname, const FileSpec &prog,
return Status();
}
static FileSpec GetDebugserverPath() {
if (const char *p = getenv("LLDB_DEBUGSERVER_PATH")) {
FileSpec candidate(p);
if (FileSystem::Instance().Exists(candidate))
return candidate;
}
#if defined(__APPLE__)
FileSpec candidate = HostInfo::GetSupportExeDir();
candidate.AppendPathComponent("debugserver");
if (FileSystem::Instance().Exists(candidate))
return candidate;
return FileSpec();
#else
// On non-apple platforms, *we* are the debug server.
return HostInfo::GetProgramFileSpec();
#endif
}
// main
int main_platform(int argc, char *argv[]) {
const char *progname = argv[0];
@ -453,6 +473,12 @@ int main_platform(int argc, char *argv[]) {
lldb_private::Args inferior_arguments;
inferior_arguments.SetArguments(argc, const_cast<const char **>(argv));
FileSpec debugserver_path = GetDebugserverPath();
if (!debugserver_path) {
WithColor::error(errs()) << "Could not find debug server executable.";
return EXIT_FAILURE;
}
Log *log = GetLog(LLDBLog::Platform);
if (fd != SharedSocket::kInvalidFD) {
// Child process will handle the connection and exit.
@ -483,8 +509,8 @@ int main_platform(int argc, char *argv[]) {
#endif
}
GDBRemoteCommunicationServerPlatform platform(socket->GetSocketProtocol(),
gdbserver_port);
GDBRemoteCommunicationServerPlatform platform(
debugserver_path, socket->GetSocketProtocol(), gdbserver_port);
platform.SetConnection(
std::make_unique<ConnectionFileDescriptor>(std::move(socket)));
client_handle(platform, inferior_arguments);
@ -576,8 +602,9 @@ int main_platform(int argc, char *argv[]) {
}
llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>> gdb_handles =
AcceptGdbConnectionsIfNeeded(protocol, gdb_sock, main_loop,
gdbserver_port, inferior_arguments);
AcceptGdbConnectionsIfNeeded(debugserver_path, protocol, gdb_sock,
main_loop, gdbserver_port,
inferior_arguments);
if (!gdb_handles) {
printf("Failed to accept gdb: %s\n",
llvm::toString(gdb_handles.takeError()).c_str());