668 lines · cpp
1//===-- lldb-platform.cpp ---------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include <cerrno>10#if defined(__APPLE__)11#include <netinet/in.h>12#endif13#include <csignal>14#include <cstdint>15#include <cstdio>16#include <cstdlib>17#include <cstring>18#if !defined(_WIN32)19#include <sys/wait.h>20#endif21#include <fstream>22#include <optional>23 24#include "llvm/Option/ArgList.h"25#include "llvm/Option/OptTable.h"26#include "llvm/Option/Option.h"27#include "llvm/Support/FileSystem.h"28#include "llvm/Support/ScopedPrinter.h"29#include "llvm/Support/WithColor.h"30#include "llvm/Support/raw_ostream.h"31 32#include "LLDBServerUtilities.h"33#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h"34#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"35#include "lldb/Host/ConnectionFileDescriptor.h"36#include "lldb/Host/FileSystem.h"37#include "lldb/Host/HostGetOpt.h"38#include "lldb/Host/HostInfo.h"39#include "lldb/Host/MainLoop.h"40#include "lldb/Host/OptionParser.h"41#include "lldb/Host/Socket.h"42#include "lldb/Host/common/TCPSocket.h"43#if LLDB_ENABLE_POSIX44#include "lldb/Host/posix/DomainSocket.h"45#endif46#include "lldb/Utility/FileSpec.h"47#include "lldb/Utility/LLDBLog.h"48#include "lldb/Utility/Status.h"49#include "lldb/Utility/UriParser.h"50 51using namespace lldb;52using namespace lldb_private;53using namespace lldb_private::lldb_server;54using namespace lldb_private::process_gdb_remote;55using namespace llvm;56 57// The test suite makes many connections in parallel, let's not miss any.58// The highest this should get reasonably is a function of the number59// of target CPUs. For now, let's just use 100.60static const int backlog = 100;61static const int socket_error = -1;62 63namespace {64using namespace llvm::opt;65 66enum ID {67 OPT_INVALID = 0, // This is not an option ID.68#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),69#include "PlatformOptions.inc"70#undef OPTION71};72 73#define OPTTABLE_STR_TABLE_CODE74#include "PlatformOptions.inc"75#undef OPTTABLE_STR_TABLE_CODE76 77#define OPTTABLE_PREFIXES_TABLE_CODE78#include "PlatformOptions.inc"79#undef OPTTABLE_PREFIXES_TABLE_CODE80 81static constexpr opt::OptTable::Info InfoTable[] = {82#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),83#include "PlatformOptions.inc"84#undef OPTION85};86 87class PlatformOptTable : public opt::GenericOptTable {88public:89 PlatformOptTable()90 : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}91 92 void PrintHelp(llvm::StringRef Name) {93 std::string Usage =94 (Name + " [options] --listen <[host]:port> [[--] program args...]")95 .str();96 97 std::string Title = "lldb-server platform";98 99 OptTable::printHelp(llvm::outs(), Usage.c_str(), Title.c_str());100 101 llvm::outs() << R"(102DESCRIPTION103 Acts as a platform server for remote debugging. When LLDB clients connect,104 the platform server handles platform operations (file transfers, process105 launching) and spawns debug server instances (lldb-server gdbserver) to106 handle actual debugging sessions.107 108 By default, the server exits after handling one connection. Use --server109 to keep running and accept multiple connections sequentially.110 111EXAMPLES112 # Listen on port 1234, exit after first connection113 lldb-server platform --listen tcp://0.0.0.0:1234114 115 # Listen on port 5555, accept multiple connections116 lldb-server platform --server --listen tcp://localhost:5555117 118 # Listen on Unix domain socket119 lldb-server platform --listen unix:///tmp/lldb-server.sock120 121)";122 }123};124} // namespace125 126#if defined(__APPLE__)127#define LOW_PORT (IPPORT_RESERVED)128#define HIGH_PORT (IPPORT_HIFIRSTAUTO)129#else130#define LOW_PORT (1024u)131#define HIGH_PORT (49151u)132#endif133 134#if !defined(_WIN32)135// Watch for signals136static void signal_handler(int signo) {137 switch (signo) {138 case SIGHUP:139 // Use SIGINT first, if that does not work, use SIGHUP as a last resort.140 // And we should not call exit() here because it results in the global141 // destructors to be invoked and wreaking havoc on the threads still142 // running.143 llvm::errs() << "SIGHUP received, exiting lldb-server...\n";144 abort();145 break;146 }147}148#endif149 150static void display_usage(PlatformOptTable &Opts, const char *progname,151 const char *subcommand) {152 std::string Name =153 (llvm::sys::path::filename(progname) + " " + subcommand).str();154 Opts.PrintHelp(Name);155}156 157static Status parse_listen_host_port(Socket::SocketProtocol &protocol,158 const std::string &listen_host_port,159 std::string &address,160 uint16_t &platform_port,161 std::string &gdb_address,162 const uint16_t gdbserver_port) {163 std::string hostname;164 // Try to match socket name as URL - e.g., tcp://localhost:5555165 if (std::optional<URI> uri = URI::Parse(listen_host_port)) {166 if (!Socket::FindProtocolByScheme(uri->scheme.str().c_str(), protocol)) {167 return Status::FromErrorStringWithFormat(168 "Unknown protocol scheme \"%s\".", uri->scheme.str().c_str());169 }170 if (protocol == Socket::ProtocolTcp) {171 hostname = uri->hostname;172 if (uri->port) {173 platform_port = *(uri->port);174 }175 } else176 address = listen_host_port.substr(uri->scheme.size() + strlen("://"));177 } else {178 // Try to match socket name as $host:port - e.g., localhost:5555179 llvm::Expected<Socket::HostAndPort> host_port =180 Socket::DecodeHostAndPort(listen_host_port);181 if (!llvm::errorToBool(host_port.takeError())) {182 protocol = Socket::ProtocolTcp;183 hostname = host_port->hostname;184 platform_port = host_port->port;185 } else186 address = listen_host_port;187 }188 189 if (protocol == Socket::ProtocolTcp) {190 if (platform_port != 0 && platform_port == gdbserver_port) {191 return Status::FromErrorStringWithFormat(192 "The same platform and gdb ports %u.", platform_port);193 }194 address = llvm::formatv("[{0}]:{1}", hostname, platform_port).str();195 gdb_address = llvm::formatv("[{0}]:{1}", hostname, gdbserver_port).str();196 } else {197 if (gdbserver_port) {198 return Status::FromErrorStringWithFormat(199 "--gdbserver-port %u is redundant for non-tcp protocol %s.",200 gdbserver_port, Socket::FindSchemeByProtocol(protocol));201 }202 }203 return Status();204}205 206static Status save_socket_id_to_file(const std::string &socket_id,207 const FileSpec &file_spec) {208 FileSpec temp_file_spec(file_spec.GetDirectory().GetStringRef());209 Status error(llvm::sys::fs::create_directory(temp_file_spec.GetPath()));210 if (error.Fail())211 return Status::FromErrorStringWithFormat(212 "Failed to create directory %s: %s", temp_file_spec.GetPath().c_str(),213 error.AsCString());214 215 Status status;216 if (auto Err = llvm::writeToOutput(file_spec.GetPath(),217 [&socket_id](llvm::raw_ostream &OS) {218 OS << socket_id;219 return llvm::Error::success();220 }))221 return Status::FromErrorStringWithFormat(222 "Failed to atomically write file %s: %s", file_spec.GetPath().c_str(),223 llvm::toString(std::move(Err)).c_str());224 return status;225}226 227static Status ListenGdbConnectionsIfNeeded(228 const Socket::SocketProtocol protocol, std::unique_ptr<TCPSocket> &gdb_sock,229 const std::string &gdb_address, uint16_t &gdbserver_port) {230 if (protocol != Socket::ProtocolTcp)231 return Status();232 233 gdb_sock = std::make_unique<TCPSocket>(/*should_close=*/true);234 Status error = gdb_sock->Listen(gdb_address, backlog);235 if (error.Fail())236 return error;237 238 if (gdbserver_port == 0)239 gdbserver_port = gdb_sock->GetLocalPortNumber();240 241 return Status();242}243 244static llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>>245AcceptGdbConnectionsIfNeeded(const FileSpec &debugserver_path,246 const Socket::SocketProtocol protocol,247 std::unique_ptr<TCPSocket> &gdb_sock,248 MainLoop &main_loop, const uint16_t gdbserver_port,249 const lldb_private::Args &args) {250 if (protocol != Socket::ProtocolTcp)251 return std::vector<MainLoopBase::ReadHandleUP>();252 253 return gdb_sock->Accept(main_loop, [debugserver_path, gdbserver_port,254 &args](std::unique_ptr<Socket> sock_up) {255 Log *log = GetLog(LLDBLog::Platform);256 Status error;257 SharedSocket shared_socket(sock_up.get(), error);258 if (error.Fail()) {259 LLDB_LOGF(log, "gdbserver SharedSocket failed: %s", error.AsCString());260 return;261 }262 lldb::pid_t child_pid = LLDB_INVALID_PROCESS_ID;263 std::string socket_name;264 GDBRemoteCommunicationServerPlatform platform(265 debugserver_path, Socket::ProtocolTcp, gdbserver_port);266 error = platform.LaunchGDBServer(args, child_pid, socket_name,267 shared_socket.GetSendableFD());268 if (error.Success() && child_pid != LLDB_INVALID_PROCESS_ID) {269 error = shared_socket.CompleteSending(child_pid);270 if (error.Fail()) {271 Host::Kill(child_pid, SIGTERM);272 LLDB_LOGF(log, "gdbserver CompleteSending failed: %s",273 error.AsCString());274 return;275 }276 }277 });278}279 280static void client_handle(GDBRemoteCommunicationServerPlatform &platform,281 const lldb_private::Args &args) {282 if (!platform.IsConnected())283 return;284 285 if (args.GetArgumentCount() > 0) {286 lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;287 std::string socket_name;288 Status error = platform.LaunchGDBServer(args, pid, socket_name,289 SharedSocket::kInvalidFD);290 if (error.Success())291 platform.SetPendingGdbServer(socket_name);292 else293 fprintf(stderr, "failed to start gdbserver: %s\n", error.AsCString());294 }295 296 bool interrupt = false;297 bool done = false;298 Status error;299 while (!interrupt && !done) {300 if (platform.GetPacketAndSendResponse(std::nullopt, error, interrupt,301 done) !=302 GDBRemoteCommunication::PacketResult::Success)303 break;304 }305 306 printf("Disconnected.\n");307}308 309static Status spawn_process(const char *progname, const FileSpec &prog,310 const Socket *conn_socket, uint16_t gdb_port,311 const lldb_private::Args &args,312 const std::string &log_file,313 const StringRef log_channels, MainLoop &main_loop,314 bool multi_client) {315 Status error;316 SharedSocket shared_socket(conn_socket, error);317 if (error.Fail())318 return error;319 320 ProcessLaunchInfo launch_info;321 322 launch_info.SetExecutableFile(prog, false);323 launch_info.SetArg0(progname);324 Args &self_args = launch_info.GetArguments();325 self_args.AppendArgument(progname);326 self_args.AppendArgument(llvm::StringRef("platform"));327 self_args.AppendArgument(llvm::StringRef("--child-platform-fd"));328 self_args.AppendArgument(llvm::to_string(shared_socket.GetSendableFD()));329 launch_info.AppendDuplicateFileAction((int64_t)shared_socket.GetSendableFD(),330 (int64_t)shared_socket.GetSendableFD());331 if (gdb_port) {332 self_args.AppendArgument(llvm::StringRef("--gdbserver-port"));333 self_args.AppendArgument(llvm::to_string(gdb_port));334 }335 if (!log_file.empty()) {336 self_args.AppendArgument(llvm::StringRef("--log-file"));337 self_args.AppendArgument(log_file);338 }339 if (!log_channels.empty()) {340 self_args.AppendArgument(llvm::StringRef("--log-channels"));341 self_args.AppendArgument(log_channels);342 }343 if (args.GetArgumentCount() > 0) {344 self_args.AppendArgument("--");345 self_args.AppendArguments(args);346 }347 348 launch_info.SetLaunchInSeparateProcessGroup(false);349 350 // Set up process monitor callback based on whether we're in server mode.351 if (multi_client)352 // In server mode: empty callback (don't terminate when child exits).353 launch_info.SetMonitorProcessCallback([](lldb::pid_t, int, int) {});354 else355 // In single-client mode: terminate main loop when child exits.356 launch_info.SetMonitorProcessCallback([&main_loop](lldb::pid_t, int, int) {357 main_loop.AddPendingCallback(358 [](MainLoopBase &loop) { loop.RequestTermination(); });359 });360 361 // Copy the current environment.362 launch_info.GetEnvironment() = Host::GetEnvironment();363 364 launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);365 366 // Close STDIN, STDOUT and STDERR.367 launch_info.AppendCloseFileAction(STDIN_FILENO);368 launch_info.AppendCloseFileAction(STDOUT_FILENO);369 launch_info.AppendCloseFileAction(STDERR_FILENO);370 371 // Redirect STDIN, STDOUT and STDERR to "/dev/null".372 launch_info.AppendSuppressFileAction(STDIN_FILENO, true, false);373 launch_info.AppendSuppressFileAction(STDOUT_FILENO, false, true);374 launch_info.AppendSuppressFileAction(STDERR_FILENO, false, true);375 376 std::string cmd;377 self_args.GetCommandString(cmd);378 379 error = Host::LaunchProcess(launch_info);380 if (error.Fail())381 return error;382 383 lldb::pid_t child_pid = launch_info.GetProcessID();384 if (child_pid == LLDB_INVALID_PROCESS_ID)385 return Status::FromErrorString("invalid pid");386 387 LLDB_LOG(GetLog(LLDBLog::Platform), "lldb-platform launched '{0}', pid={1}",388 cmd, child_pid);389 390 error = shared_socket.CompleteSending(child_pid);391 if (error.Fail()) {392 Host::Kill(child_pid, SIGTERM);393 return error;394 }395 396 return Status();397}398 399static FileSpec GetDebugserverPath() {400 if (const char *p = getenv("LLDB_DEBUGSERVER_PATH")) {401 FileSpec candidate(p);402 if (FileSystem::Instance().Exists(candidate))403 return candidate;404 }405#if defined(__APPLE__)406 FileSpec candidate = HostInfo::GetSupportExeDir();407 candidate.AppendPathComponent("debugserver");408 if (FileSystem::Instance().Exists(candidate))409 return candidate;410 return FileSpec();411#else412 // On non-apple platforms, *we* are the debug server.413 return HostInfo::GetProgramFileSpec();414#endif415}416 417// main418int main_platform(int argc, char *argv[]) {419 const char *progname = argv[0];420 const char *subcommand = argv[1];421 argc--;422 argv++;423#if !defined(_WIN32)424 signal(SIGPIPE, SIG_IGN);425 signal(SIGHUP, signal_handler);426#endif427 428 // Special handling for 'help' as first argument.429 if (argc > 0 && strcmp(argv[0], "help") == 0) {430 PlatformOptTable Opts;431 display_usage(Opts, progname, subcommand);432 return EXIT_SUCCESS;433 }434 435 Status error;436 shared_fd_t fd = SharedSocket::kInvalidFD;437 uint16_t gdbserver_port = 0;438 FileSpec socket_file;439 440 PlatformOptTable Opts;441 BumpPtrAllocator Alloc;442 StringSaver Saver(Alloc);443 bool HasError = false;444 445 opt::InputArgList Args =446 Opts.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](llvm::StringRef Msg) {447 WithColor::error() << Msg << "\n";448 HasError = true;449 });450 451 std::string Name =452 (llvm::sys::path::filename(progname) + " " + subcommand).str();453 std::string HelpText =454 "Use '" + Name + " --help' for a complete list of options.\n";455 456 if (HasError) {457 llvm::errs() << HelpText;458 return EXIT_FAILURE;459 }460 461 if (Args.hasArg(OPT_help)) {462 display_usage(Opts, progname, subcommand);463 return EXIT_SUCCESS;464 }465 466 // Parse arguments.467 std::string listen_host_port = Args.getLastArgValue(OPT_listen).str();468 std::string log_file = Args.getLastArgValue(OPT_log_file).str();469 StringRef log_channels = Args.getLastArgValue(OPT_log_channels);470 bool multi_client = Args.hasArg(OPT_server);471 [[maybe_unused]] bool debug = Args.hasArg(OPT_debug);472 [[maybe_unused]] bool verbose = Args.hasArg(OPT_verbose);473 474 if (Args.hasArg(OPT_socket_file)) {475 socket_file.SetFile(Args.getLastArgValue(OPT_socket_file),476 FileSpec::Style::native);477 }478 479 if (Args.hasArg(OPT_gdbserver_port)) {480 if (!llvm::to_integer(Args.getLastArgValue(OPT_gdbserver_port),481 gdbserver_port)) {482 WithColor::error() << "invalid --gdbserver-port value\n";483 return EXIT_FAILURE;484 }485 }486 487 if (Args.hasArg(OPT_child_platform_fd)) {488 uint64_t _fd;489 if (!llvm::to_integer(Args.getLastArgValue(OPT_child_platform_fd), _fd)) {490 WithColor::error() << "invalid --child-platform-fd value\n";491 return EXIT_FAILURE;492 }493 fd = (shared_fd_t)_fd;494 }495 496 if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, 0))497 return -1;498 499 // Print usage and exit if no listening port is specified.500 if (listen_host_port.empty() && fd == SharedSocket::kInvalidFD) {501 WithColor::error() << "either --listen or --child-platform-fd is required\n"502 << HelpText;503 return EXIT_FAILURE;504 }505 506 // Get remaining arguments for inferior.507 std::vector<llvm::StringRef> Inputs;508 for (opt::Arg *Arg : Args.filtered(OPT_INPUT))509 Inputs.push_back(Arg->getValue());510 if (opt::Arg *Arg = Args.getLastArg(OPT_REM)) {511 for (const char *Val : Arg->getValues())512 Inputs.push_back(Val);513 }514 515 lldb_private::Args inferior_arguments;516 if (!Inputs.empty()) {517 std::vector<const char *> args_ptrs;518 for (const auto &Input : Inputs)519 args_ptrs.push_back(Input.data());520 inferior_arguments.SetArguments(args_ptrs.size(), args_ptrs.data());521 }522 523 FileSpec debugserver_path = GetDebugserverPath();524 if (!debugserver_path) {525 WithColor::error(errs()) << "Could not find debug server executable.";526 return EXIT_FAILURE;527 }528 529 Log *log = GetLog(LLDBLog::Platform);530 if (fd != SharedSocket::kInvalidFD) {531 // Child process will handle the connection and exit.532 NativeSocket sockfd;533 error = SharedSocket::GetNativeSocket(fd, sockfd);534 if (error.Fail()) {535 LLDB_LOGF(log, "lldb-platform child: %s", error.AsCString());536 return socket_error;537 }538 539 std::unique_ptr<Socket> socket;540 if (gdbserver_port) {541 socket = std::make_unique<TCPSocket>(sockfd, /*should_close=*/true);542 } else {543#if LLDB_ENABLE_POSIX544 llvm::Expected<std::unique_ptr<DomainSocket>> domain_socket =545 DomainSocket::FromBoundNativeSocket(sockfd, /*should_close=*/true);546 if (!domain_socket) {547 LLDB_LOG_ERROR(log, domain_socket.takeError(),548 "Failed to create socket: {0}");549 return socket_error;550 }551 socket = std::move(domain_socket.get());552#else553 WithColor::error() << "lldb-platform child: Unix domain sockets are not "554 "supported on this platform.";555 return socket_error;556#endif557 }558 559 GDBRemoteCommunicationServerPlatform platform(560 debugserver_path, socket->GetSocketProtocol(), gdbserver_port);561 platform.SetConnection(562 std::make_unique<ConnectionFileDescriptor>(std::move(socket)));563 client_handle(platform, inferior_arguments);564 return EXIT_SUCCESS;565 }566 567 if (gdbserver_port != 0 &&568 (gdbserver_port < LOW_PORT || gdbserver_port > HIGH_PORT)) {569 WithColor::error() << llvm::formatv("Port number {0} is not in the "570 "valid user port range of {1} - {2}\n",571 gdbserver_port, LOW_PORT, HIGH_PORT);572 return EXIT_FAILURE;573 }574 575 Socket::SocketProtocol protocol = Socket::ProtocolUnixDomain;576 std::string address;577 std::string gdb_address;578 uint16_t platform_port = 0;579 error = parse_listen_host_port(protocol, listen_host_port, address,580 platform_port, gdb_address, gdbserver_port);581 if (error.Fail()) {582 printf("Failed to parse listen address: %s\n", error.AsCString());583 return socket_error;584 }585 586 std::unique_ptr<Socket> platform_sock = Socket::Create(protocol, error);587 if (error.Fail()) {588 printf("Failed to create platform socket: %s\n", error.AsCString());589 return socket_error;590 }591 error = platform_sock->Listen(address, backlog);592 if (error.Fail()) {593 printf("Failed to listen platform: %s\n", error.AsCString());594 return socket_error;595 }596 if (protocol == Socket::ProtocolTcp && platform_port == 0)597 platform_port =598 static_cast<TCPSocket *>(platform_sock.get())->GetLocalPortNumber();599 600 if (socket_file) {601 error = save_socket_id_to_file(602 protocol == Socket::ProtocolTcp603 ? (platform_port ? llvm::to_string(platform_port) : "")604 : address,605 socket_file);606 if (error.Fail()) {607 fprintf(stderr, "failed to write socket id to %s: %s\n",608 socket_file.GetPath().c_str(), error.AsCString());609 return EXIT_FAILURE;610 }611 }612 613 std::unique_ptr<TCPSocket> gdb_sock;614 // Update gdbserver_port if it is still 0 and protocol is tcp.615 error = ListenGdbConnectionsIfNeeded(protocol, gdb_sock, gdb_address,616 gdbserver_port);617 if (error.Fail()) {618 printf("Failed to listen gdb: %s\n", error.AsCString());619 return socket_error;620 }621 622 MainLoop main_loop;623 {624 llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>> platform_handles =625 platform_sock->Accept(626 main_loop, [progname, gdbserver_port, &inferior_arguments, log_file,627 log_channels, &main_loop, multi_client,628 &platform_handles](std::unique_ptr<Socket> sock_up) {629 printf("Connection established.\n");630 Status error = spawn_process(631 progname, HostInfo::GetProgramFileSpec(), sock_up.get(),632 gdbserver_port, inferior_arguments, log_file, log_channels,633 main_loop, multi_client);634 if (error.Fail()) {635 Log *log = GetLog(LLDBLog::Platform);636 LLDB_LOGF(log, "spawn_process failed: %s", error.AsCString());637 WithColor::error()638 << "spawn_process failed: " << error.AsCString() << "\n";639 if (!multi_client)640 main_loop.RequestTermination();641 }642 if (!multi_client)643 platform_handles->clear();644 });645 if (!platform_handles) {646 printf("Failed to accept platform: %s\n",647 llvm::toString(platform_handles.takeError()).c_str());648 return socket_error;649 }650 651 llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>> gdb_handles =652 AcceptGdbConnectionsIfNeeded(debugserver_path, protocol, gdb_sock,653 main_loop, gdbserver_port,654 inferior_arguments);655 if (!gdb_handles) {656 printf("Failed to accept gdb: %s\n",657 llvm::toString(gdb_handles.takeError()).c_str());658 return socket_error;659 }660 661 main_loop.Run();662 }663 664 fprintf(stderr, "lldb-server exiting...\n");665 666 return EXIT_SUCCESS;667}668