llvm-project
383 строки · 12.0 Кб
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-exception
6//
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/Support/FileSystem.h"25#include "llvm/Support/WithColor.h"26#include "llvm/Support/raw_ostream.h"27
28#include "Acceptor.h"29#include "LLDBServerUtilities.h"30#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h"31#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"32#include "lldb/Host/ConnectionFileDescriptor.h"33#include "lldb/Host/HostGetOpt.h"34#include "lldb/Host/OptionParser.h"35#include "lldb/Host/common/TCPSocket.h"36#include "lldb/Utility/FileSpec.h"37#include "lldb/Utility/Status.h"38
39using namespace lldb;40using namespace lldb_private;41using namespace lldb_private::lldb_server;42using namespace lldb_private::process_gdb_remote;43using namespace llvm;44
45// option descriptors for getopt_long_only()
46
47static int g_debug = 0;48static int g_verbose = 0;49static int g_server = 0;50
51static struct option g_long_options[] = {52{"debug", no_argument, &g_debug, 1},53{"verbose", no_argument, &g_verbose, 1},54{"log-file", required_argument, nullptr, 'l'},55{"log-channels", required_argument, nullptr, 'c'},56{"listen", required_argument, nullptr, 'L'},57{"port-offset", required_argument, nullptr, 'p'},58{"gdbserver-port", required_argument, nullptr, 'P'},59{"min-gdbserver-port", required_argument, nullptr, 'm'},60{"max-gdbserver-port", required_argument, nullptr, 'M'},61{"socket-file", required_argument, nullptr, 'f'},62{"server", no_argument, &g_server, 1},63{nullptr, 0, nullptr, 0}};64
65#if defined(__APPLE__)66#define LOW_PORT (IPPORT_RESERVED)67#define HIGH_PORT (IPPORT_HIFIRSTAUTO)68#else69#define LOW_PORT (1024u)70#define HIGH_PORT (49151u)71#endif72
73#if !defined(_WIN32)74// Watch for signals
75static void signal_handler(int signo) {76switch (signo) {77case SIGHUP:78// Use SIGINT first, if that does not work, use SIGHUP as a last resort.79// And we should not call exit() here because it results in the global80// destructors to be invoked and wreaking havoc on the threads still81// running.82llvm::errs() << "SIGHUP received, exiting lldb-server...\n";83abort();84break;85}86}
87#endif88
89static void display_usage(const char *progname, const char *subcommand) {90fprintf(stderr, "Usage:\n %s %s [--log-file log-file-name] [--log-channels "91"log-channel-list] [--port-file port-file-path] --server "92"--listen port\n",93progname, subcommand);94exit(0);95}
96
97static Status save_socket_id_to_file(const std::string &socket_id,98const FileSpec &file_spec) {99FileSpec temp_file_spec(file_spec.GetDirectory().GetStringRef());100Status error(llvm::sys::fs::create_directory(temp_file_spec.GetPath()));101if (error.Fail())102return Status("Failed to create directory %s: %s",103temp_file_spec.GetPath().c_str(), error.AsCString());104
105Status status;106if (auto Err = llvm::writeToOutput(file_spec.GetPath(),107[&socket_id](llvm::raw_ostream &OS) {108OS << socket_id;109return llvm::Error::success();110}))111return Status("Failed to atomically write file %s: %s",112file_spec.GetPath().c_str(),113llvm::toString(std::move(Err)).c_str());114return status;115}
116
117// main
118int main_platform(int argc, char *argv[]) {119const char *progname = argv[0];120const char *subcommand = argv[1];121argc--;122argv++;123#if !defined(_WIN32)124signal(SIGPIPE, SIG_IGN);125signal(SIGHUP, signal_handler);126#endif127int long_option_index = 0;128Status error;129std::string listen_host_port;130int ch;131
132std::string log_file;133StringRef
134log_channels; // e.g. "lldb process threads:gdb-remote default:linux all"135
136GDBRemoteCommunicationServerPlatform::PortMap gdbserver_portmap;137int min_gdbserver_port = 0;138int max_gdbserver_port = 0;139uint16_t port_offset = 0;140
141FileSpec socket_file;142bool show_usage = false;143int option_error = 0;144int socket_error = -1;145
146std::string short_options(OptionParser::GetShortOptionString(g_long_options));147
148#if __GLIBC__149optind = 0;150#else151optreset = 1;152optind = 1;153#endif154
155while ((ch = getopt_long_only(argc, argv, short_options.c_str(),156g_long_options, &long_option_index)) != -1) {157switch (ch) {158case 0: // Any optional that auto set themselves will return 0159break;160
161case 'L':162listen_host_port.append(optarg);163break;164
165case 'l': // Set Log File166if (optarg && optarg[0])167log_file.assign(optarg);168break;169
170case 'c': // Log Channels171if (optarg && optarg[0])172log_channels = StringRef(optarg);173break;174
175case 'f': // Socket file176if (optarg && optarg[0])177socket_file.SetFile(optarg, FileSpec::Style::native);178break;179
180case 'p': {181if (!llvm::to_integer(optarg, port_offset)) {182WithColor::error() << "invalid port offset string " << optarg << "\n";183option_error = 4;184break;185}186if (port_offset < LOW_PORT || port_offset > HIGH_PORT) {187WithColor::error() << llvm::formatv(188"port offset {0} is not in the "189"valid user port range of {1} - {2}\n",190port_offset, LOW_PORT, HIGH_PORT);191option_error = 5;192}193} break;194
195case 'P':196case 'm':197case 'M': {198uint16_t portnum;199if (!llvm::to_integer(optarg, portnum)) {200WithColor::error() << "invalid port number string " << optarg << "\n";201option_error = 2;202break;203}204if (portnum < LOW_PORT || portnum > HIGH_PORT) {205WithColor::error() << llvm::formatv(206"port number {0} is not in the "207"valid user port range of {1} - {2}\n",208portnum, LOW_PORT, HIGH_PORT);209option_error = 1;210break;211}212if (ch == 'P')213gdbserver_portmap.AllowPort(portnum);214else if (ch == 'm')215min_gdbserver_port = portnum;216else217max_gdbserver_port = portnum;218} break;219
220case 'h': /* fall-through is intentional */221case '?':222show_usage = true;223break;224}225}226
227if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, 0))228return -1;229
230// Make a port map for a port range that was specified.231if (min_gdbserver_port && min_gdbserver_port < max_gdbserver_port) {232gdbserver_portmap = GDBRemoteCommunicationServerPlatform::PortMap(233min_gdbserver_port, max_gdbserver_port);234} else if (min_gdbserver_port || max_gdbserver_port) {235WithColor::error() << llvm::formatv(236"--min-gdbserver-port ({0}) is not lower than "237"--max-gdbserver-port ({1})\n",238min_gdbserver_port, max_gdbserver_port);239option_error = 3;240}241
242// Print usage and exit if no listening port is specified.243if (listen_host_port.empty())244show_usage = true;245
246if (show_usage || option_error) {247display_usage(progname, subcommand);248exit(option_error);249}250
251// Skip any options we consumed with getopt_long_only.252argc -= optind;253argv += optind;254lldb_private::Args inferior_arguments;255inferior_arguments.SetArguments(argc, const_cast<const char **>(argv));256
257const bool children_inherit_listen_socket = false;258// the test suite makes many connections in parallel, let's not miss any.259// The highest this should get reasonably is a function of the number260// of target CPUs. For now, let's just use 100.261const int backlog = 100;262
263std::unique_ptr<Acceptor> acceptor_up(Acceptor::Create(264listen_host_port, children_inherit_listen_socket, error));265if (error.Fail()) {266fprintf(stderr, "failed to create acceptor: %s", error.AsCString());267exit(socket_error);268}269
270error = acceptor_up->Listen(backlog);271if (error.Fail()) {272printf("failed to listen: %s\n", error.AsCString());273exit(socket_error);274}275if (socket_file) {276error =277save_socket_id_to_file(acceptor_up->GetLocalSocketId(), socket_file);278if (error.Fail()) {279fprintf(stderr, "failed to write socket id to %s: %s\n",280socket_file.GetPath().c_str(), error.AsCString());281return 1;282}283}284
285GDBRemoteCommunicationServerPlatform platform(286acceptor_up->GetSocketProtocol(), acceptor_up->GetSocketScheme());287if (port_offset > 0)288platform.SetPortOffset(port_offset);289
290do {291const bool children_inherit_accept_socket = true;292Connection *conn = nullptr;293error = acceptor_up->Accept(children_inherit_accept_socket, conn);294if (error.Fail()) {295WithColor::error() << error.AsCString() << '\n';296exit(socket_error);297}298printf("Connection established.\n");299
300if (g_server) {301// Collect child zombie processes.302#if !defined(_WIN32)303::pid_t waitResult;304while ((waitResult = waitpid(-1, nullptr, WNOHANG)) > 0) {305// waitResult is the child pid306gdbserver_portmap.FreePortForProcess(waitResult);307}308#endif309// TODO: Clean up portmap for Windows when children die310// See https://github.com/llvm/llvm-project/issues/90923311
312// After collecting zombie ports, get the next available313GDBRemoteCommunicationServerPlatform::PortMap portmap_for_child;314llvm::Expected<uint16_t> available_port =315gdbserver_portmap.GetNextAvailablePort();316if (available_port)317portmap_for_child.AllowPort(*available_port);318else {319llvm::consumeError(available_port.takeError());320fprintf(stderr,321"no available gdbserver port for connection - dropping...\n");322delete conn;323continue;324}325platform.SetPortMap(std::move(portmap_for_child));326
327auto childPid = fork();328if (childPid) {329gdbserver_portmap.AssociatePortWithProcess(*available_port, childPid);330// Parent doesn't need a connection to the lldb client331delete conn;332
333// Parent will continue to listen for new connections.334continue;335} else {336// Child process will handle the connection and exit.337g_server = 0;338// Listening socket is owned by parent process.339acceptor_up.release();340}341} else {342// If not running as a server, this process will not accept343// connections while a connection is active.344acceptor_up.reset();345
346// When not running in server mode, use all available ports347platform.SetPortMap(std::move(gdbserver_portmap));348}349
350platform.SetConnection(std::unique_ptr<Connection>(conn));351
352if (platform.IsConnected()) {353if (inferior_arguments.GetArgumentCount() > 0) {354lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;355std::optional<uint16_t> port = 0;356std::string socket_name;357Status error = platform.LaunchGDBServer(inferior_arguments,358"", // hostname359pid, port, socket_name);360if (error.Success())361platform.SetPendingGdbServer(pid, *port, socket_name);362else363fprintf(stderr, "failed to start gdbserver: %s\n", error.AsCString());364}365
366bool interrupt = false;367bool done = false;368while (!interrupt && !done) {369if (platform.GetPacketAndSendResponse(std::nullopt, error, interrupt,370done) !=371GDBRemoteCommunication::PacketResult::Success)372break;373}374
375if (error.Fail())376WithColor::error() << error.AsCString() << '\n';377}378} while (g_server);379
380fprintf(stderr, "lldb-server exiting...\n");381
382return 0;383}
384