Path: blob/main/contrib/llvm-project/lldb/tools/lldb-server/lldb-platform.cpp
34879 views
//===-- lldb-platform.cpp ---------------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include <cerrno>9#if defined(__APPLE__)10#include <netinet/in.h>11#endif12#include <csignal>13#include <cstdint>14#include <cstdio>15#include <cstdlib>16#include <cstring>17#if !defined(_WIN32)18#include <sys/wait.h>19#endif20#include <fstream>21#include <optional>2223#include "llvm/Support/FileSystem.h"24#include "llvm/Support/WithColor.h"25#include "llvm/Support/raw_ostream.h"2627#include "Acceptor.h"28#include "LLDBServerUtilities.h"29#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h"30#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"31#include "lldb/Host/ConnectionFileDescriptor.h"32#include "lldb/Host/HostGetOpt.h"33#include "lldb/Host/OptionParser.h"34#include "lldb/Host/common/TCPSocket.h"35#include "lldb/Utility/FileSpec.h"36#include "lldb/Utility/Status.h"3738using namespace lldb;39using namespace lldb_private;40using namespace lldb_private::lldb_server;41using namespace lldb_private::process_gdb_remote;42using namespace llvm;4344// option descriptors for getopt_long_only()4546static int g_debug = 0;47static int g_verbose = 0;48static int g_server = 0;4950static struct option g_long_options[] = {51{"debug", no_argument, &g_debug, 1},52{"verbose", no_argument, &g_verbose, 1},53{"log-file", required_argument, nullptr, 'l'},54{"log-channels", required_argument, nullptr, 'c'},55{"listen", required_argument, nullptr, 'L'},56{"port-offset", required_argument, nullptr, 'p'},57{"gdbserver-port", required_argument, nullptr, 'P'},58{"min-gdbserver-port", required_argument, nullptr, 'm'},59{"max-gdbserver-port", required_argument, nullptr, 'M'},60{"socket-file", required_argument, nullptr, 'f'},61{"server", no_argument, &g_server, 1},62{nullptr, 0, nullptr, 0}};6364#if defined(__APPLE__)65#define LOW_PORT (IPPORT_RESERVED)66#define HIGH_PORT (IPPORT_HIFIRSTAUTO)67#else68#define LOW_PORT (1024u)69#define HIGH_PORT (49151u)70#endif7172#if !defined(_WIN32)73// Watch for signals74static void signal_handler(int signo) {75switch (signo) {76case SIGHUP:77// Use SIGINT first, if that does not work, use SIGHUP as a last resort.78// And we should not call exit() here because it results in the global79// destructors to be invoked and wreaking havoc on the threads still80// running.81llvm::errs() << "SIGHUP received, exiting lldb-server...\n";82abort();83break;84}85}86#endif8788static void display_usage(const char *progname, const char *subcommand) {89fprintf(stderr, "Usage:\n %s %s [--log-file log-file-name] [--log-channels "90"log-channel-list] [--port-file port-file-path] --server "91"--listen port\n",92progname, subcommand);93exit(0);94}9596static Status save_socket_id_to_file(const std::string &socket_id,97const FileSpec &file_spec) {98FileSpec temp_file_spec(file_spec.GetDirectory().GetStringRef());99Status error(llvm::sys::fs::create_directory(temp_file_spec.GetPath()));100if (error.Fail())101return Status("Failed to create directory %s: %s",102temp_file_spec.GetPath().c_str(), error.AsCString());103104Status status;105if (auto Err = llvm::writeToOutput(file_spec.GetPath(),106[&socket_id](llvm::raw_ostream &OS) {107OS << socket_id;108return llvm::Error::success();109}))110return Status("Failed to atomically write file %s: %s",111file_spec.GetPath().c_str(),112llvm::toString(std::move(Err)).c_str());113return status;114}115116// main117int main_platform(int argc, char *argv[]) {118const char *progname = argv[0];119const char *subcommand = argv[1];120argc--;121argv++;122#if !defined(_WIN32)123signal(SIGPIPE, SIG_IGN);124signal(SIGHUP, signal_handler);125#endif126int long_option_index = 0;127Status error;128std::string listen_host_port;129int ch;130131std::string log_file;132StringRef133log_channels; // e.g. "lldb process threads:gdb-remote default:linux all"134135GDBRemoteCommunicationServerPlatform::PortMap gdbserver_portmap;136int min_gdbserver_port = 0;137int max_gdbserver_port = 0;138uint16_t port_offset = 0;139140FileSpec socket_file;141bool show_usage = false;142int option_error = 0;143int socket_error = -1;144145std::string short_options(OptionParser::GetShortOptionString(g_long_options));146147#if __GLIBC__148optind = 0;149#else150optreset = 1;151optind = 1;152#endif153154while ((ch = getopt_long_only(argc, argv, short_options.c_str(),155g_long_options, &long_option_index)) != -1) {156switch (ch) {157case 0: // Any optional that auto set themselves will return 0158break;159160case 'L':161listen_host_port.append(optarg);162break;163164case 'l': // Set Log File165if (optarg && optarg[0])166log_file.assign(optarg);167break;168169case 'c': // Log Channels170if (optarg && optarg[0])171log_channels = StringRef(optarg);172break;173174case 'f': // Socket file175if (optarg && optarg[0])176socket_file.SetFile(optarg, FileSpec::Style::native);177break;178179case 'p': {180if (!llvm::to_integer(optarg, port_offset)) {181WithColor::error() << "invalid port offset string " << optarg << "\n";182option_error = 4;183break;184}185if (port_offset < LOW_PORT || port_offset > HIGH_PORT) {186WithColor::error() << llvm::formatv(187"port offset {0} is not in the "188"valid user port range of {1} - {2}\n",189port_offset, LOW_PORT, HIGH_PORT);190option_error = 5;191}192} break;193194case 'P':195case 'm':196case 'M': {197uint16_t portnum;198if (!llvm::to_integer(optarg, portnum)) {199WithColor::error() << "invalid port number string " << optarg << "\n";200option_error = 2;201break;202}203if (portnum < LOW_PORT || portnum > HIGH_PORT) {204WithColor::error() << llvm::formatv(205"port number {0} is not in the "206"valid user port range of {1} - {2}\n",207portnum, LOW_PORT, HIGH_PORT);208option_error = 1;209break;210}211if (ch == 'P')212gdbserver_portmap.AllowPort(portnum);213else if (ch == 'm')214min_gdbserver_port = portnum;215else216max_gdbserver_port = portnum;217} break;218219case 'h': /* fall-through is intentional */220case '?':221show_usage = true;222break;223}224}225226if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, 0))227return -1;228229// Make a port map for a port range that was specified.230if (min_gdbserver_port && min_gdbserver_port < max_gdbserver_port) {231gdbserver_portmap = GDBRemoteCommunicationServerPlatform::PortMap(232min_gdbserver_port, max_gdbserver_port);233} else if (min_gdbserver_port || max_gdbserver_port) {234WithColor::error() << llvm::formatv(235"--min-gdbserver-port ({0}) is not lower than "236"--max-gdbserver-port ({1})\n",237min_gdbserver_port, max_gdbserver_port);238option_error = 3;239}240241// Print usage and exit if no listening port is specified.242if (listen_host_port.empty())243show_usage = true;244245if (show_usage || option_error) {246display_usage(progname, subcommand);247exit(option_error);248}249250// Skip any options we consumed with getopt_long_only.251argc -= optind;252argv += optind;253lldb_private::Args inferior_arguments;254inferior_arguments.SetArguments(argc, const_cast<const char **>(argv));255256const bool children_inherit_listen_socket = false;257// the test suite makes many connections in parallel, let's not miss any.258// The highest this should get reasonably is a function of the number259// of target CPUs. For now, let's just use 100.260const int backlog = 100;261262std::unique_ptr<Acceptor> acceptor_up(Acceptor::Create(263listen_host_port, children_inherit_listen_socket, error));264if (error.Fail()) {265fprintf(stderr, "failed to create acceptor: %s", error.AsCString());266exit(socket_error);267}268269error = acceptor_up->Listen(backlog);270if (error.Fail()) {271printf("failed to listen: %s\n", error.AsCString());272exit(socket_error);273}274if (socket_file) {275error =276save_socket_id_to_file(acceptor_up->GetLocalSocketId(), socket_file);277if (error.Fail()) {278fprintf(stderr, "failed to write socket id to %s: %s\n",279socket_file.GetPath().c_str(), error.AsCString());280return 1;281}282}283284GDBRemoteCommunicationServerPlatform platform(285acceptor_up->GetSocketProtocol(), acceptor_up->GetSocketScheme());286if (port_offset > 0)287platform.SetPortOffset(port_offset);288289do {290const bool children_inherit_accept_socket = true;291Connection *conn = nullptr;292error = acceptor_up->Accept(children_inherit_accept_socket, conn);293if (error.Fail()) {294WithColor::error() << error.AsCString() << '\n';295exit(socket_error);296}297printf("Connection established.\n");298299if (g_server) {300// Collect child zombie processes.301#if !defined(_WIN32)302::pid_t waitResult;303while ((waitResult = waitpid(-1, nullptr, WNOHANG)) > 0) {304// waitResult is the child pid305gdbserver_portmap.FreePortForProcess(waitResult);306}307#endif308// TODO: Clean up portmap for Windows when children die309// See https://github.com/llvm/llvm-project/issues/90923310311// After collecting zombie ports, get the next available312GDBRemoteCommunicationServerPlatform::PortMap portmap_for_child;313llvm::Expected<uint16_t> available_port =314gdbserver_portmap.GetNextAvailablePort();315if (available_port) {316// GetNextAvailablePort() may return 0 if gdbserver_portmap is empty.317if (*available_port)318portmap_for_child.AllowPort(*available_port);319} else {320llvm::consumeError(available_port.takeError());321fprintf(stderr,322"no available gdbserver port for connection - dropping...\n");323delete conn;324continue;325}326platform.SetPortMap(std::move(portmap_for_child));327328auto childPid = fork();329if (childPid) {330gdbserver_portmap.AssociatePortWithProcess(*available_port, childPid);331// Parent doesn't need a connection to the lldb client332delete conn;333334// Parent will continue to listen for new connections.335continue;336} else {337// Child process will handle the connection and exit.338g_server = 0;339// Listening socket is owned by parent process.340acceptor_up.release();341}342} else {343// If not running as a server, this process will not accept344// connections while a connection is active.345acceptor_up.reset();346347// When not running in server mode, use all available ports348platform.SetPortMap(std::move(gdbserver_portmap));349}350351platform.SetConnection(std::unique_ptr<Connection>(conn));352353if (platform.IsConnected()) {354if (inferior_arguments.GetArgumentCount() > 0) {355lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;356std::optional<uint16_t> port;357std::string socket_name;358Status error = platform.LaunchGDBServer(inferior_arguments,359"", // hostname360pid, port, socket_name);361if (error.Success())362platform.SetPendingGdbServer(pid, *port, socket_name);363else364fprintf(stderr, "failed to start gdbserver: %s\n", error.AsCString());365}366367bool interrupt = false;368bool done = false;369while (!interrupt && !done) {370if (platform.GetPacketAndSendResponse(std::nullopt, error, interrupt,371done) !=372GDBRemoteCommunication::PacketResult::Success)373break;374}375376if (error.Fail())377WithColor::error() << error.AsCString() << '\n';378}379} while (g_server);380381fprintf(stderr, "lldb-server exiting...\n");382383return 0;384}385386387