llvm-project
473 строки · 15.7 Кб
1//===-- lldb-gdbserver.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#include <cstdint>11#include <cstdio>12#include <cstdlib>13#include <cstring>14
15#ifndef _WIN3216#include <csignal>17#include <unistd.h>18#endif19
20#include "LLDBServerUtilities.h"21#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h"22#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"23#include "lldb/Host/Config.h"24#include "lldb/Host/ConnectionFileDescriptor.h"25#include "lldb/Host/FileSystem.h"26#include "lldb/Host/Pipe.h"27#include "lldb/Host/Socket.h"28#include "lldb/Host/common/NativeProcessProtocol.h"29#include "lldb/Target/Process.h"30#include "lldb/Utility/LLDBLog.h"31#include "lldb/Utility/Status.h"32#include "llvm/ADT/StringRef.h"33#include "llvm/Option/ArgList.h"34#include "llvm/Option/OptTable.h"35#include "llvm/Option/Option.h"36#include "llvm/Support/Errno.h"37#include "llvm/Support/Error.h"38#include "llvm/Support/WithColor.h"39
40#if defined(__linux__)41#include "Plugins/Process/Linux/NativeProcessLinux.h"42#elif defined(__FreeBSD__)43#include "Plugins/Process/FreeBSD/NativeProcessFreeBSD.h"44#elif defined(__NetBSD__)45#include "Plugins/Process/NetBSD/NativeProcessNetBSD.h"46#elif defined(_WIN32)47#include "Plugins/Process/Windows/Common/NativeProcessWindows.h"48#endif49
50#ifndef LLGS_PROGRAM_NAME51#define LLGS_PROGRAM_NAME "lldb-server"52#endif53
54#ifndef LLGS_VERSION_STR55#define LLGS_VERSION_STR "local_build"56#endif57
58using namespace llvm;59using namespace lldb;60using namespace lldb_private;61using namespace lldb_private::lldb_server;62using namespace lldb_private::process_gdb_remote;63
64namespace {65#if defined(__linux__)66typedef process_linux::NativeProcessLinux::Manager NativeProcessManager;67#elif defined(__FreeBSD__)68typedef process_freebsd::NativeProcessFreeBSD::Manager NativeProcessManager;69#elif defined(__NetBSD__)70typedef process_netbsd::NativeProcessNetBSD::Manager NativeProcessManager;71#elif defined(_WIN32)72typedef NativeProcessWindows::Manager NativeProcessManager;73#else74// Dummy implementation to make sure the code compiles
75class NativeProcessManager : public NativeProcessProtocol::Manager {76public:77NativeProcessManager(MainLoop &mainloop)78: NativeProcessProtocol::Manager(mainloop) {}79
80llvm::Expected<std::unique_ptr<NativeProcessProtocol>>81Launch(ProcessLaunchInfo &launch_info,82NativeProcessProtocol::NativeDelegate &native_delegate) override {83llvm_unreachable("Not implemented");84}85llvm::Expected<std::unique_ptr<NativeProcessProtocol>>86Attach(lldb::pid_t pid,87NativeProcessProtocol::NativeDelegate &native_delegate) override {88llvm_unreachable("Not implemented");89}90};91#endif92}
93
94#ifndef _WIN3295// Watch for signals
96static int g_sighup_received_count = 0;97
98static void sighup_handler(MainLoopBase &mainloop) {99++g_sighup_received_count;100
101Log *log = GetLog(LLDBLog::Process);102LLDB_LOGF(log, "lldb-server:%s swallowing SIGHUP (receive count=%d)",103__FUNCTION__, g_sighup_received_count);104
105if (g_sighup_received_count >= 2)106mainloop.RequestTermination();107}
108#endif // #ifndef _WIN32109
110void handle_attach_to_pid(GDBRemoteCommunicationServerLLGS &gdb_server,111lldb::pid_t pid) {112Status error = gdb_server.AttachToProcess(pid);113if (error.Fail()) {114fprintf(stderr, "error: failed to attach to pid %" PRIu64 ": %s\n", pid,115error.AsCString());116exit(1);117}118}
119
120void handle_attach_to_process_name(GDBRemoteCommunicationServerLLGS &gdb_server,121const std::string &process_name) {122// FIXME implement.123}
124
125void handle_attach(GDBRemoteCommunicationServerLLGS &gdb_server,126const std::string &attach_target) {127assert(!attach_target.empty() && "attach_target cannot be empty");128
129// First check if the attach_target is convertible to a long. If so, we'll use130// it as a pid.131char *end_p = nullptr;132const long int pid = strtol(attach_target.c_str(), &end_p, 10);133
134// We'll call it a match if the entire argument is consumed.135if (end_p &&136static_cast<size_t>(end_p - attach_target.c_str()) ==137attach_target.size())138handle_attach_to_pid(gdb_server, static_cast<lldb::pid_t>(pid));139else140handle_attach_to_process_name(gdb_server, attach_target);141}
142
143void handle_launch(GDBRemoteCommunicationServerLLGS &gdb_server,144llvm::ArrayRef<llvm::StringRef> Arguments) {145ProcessLaunchInfo info;146info.GetFlags().Set(eLaunchFlagStopAtEntry | eLaunchFlagDebug |147eLaunchFlagDisableASLR);148info.SetArguments(Args(Arguments), true);149
150llvm::SmallString<64> cwd;151if (std::error_code ec = llvm::sys::fs::current_path(cwd)) {152llvm::errs() << "Error getting current directory: " << ec.message() << "\n";153exit(1);154}155FileSpec cwd_spec(cwd);156FileSystem::Instance().Resolve(cwd_spec);157info.SetWorkingDirectory(cwd_spec);158info.GetEnvironment() = Host::GetEnvironment();159
160gdb_server.SetLaunchInfo(info);161
162Status error = gdb_server.LaunchProcess();163if (error.Fail()) {164llvm::errs() << llvm::formatv("error: failed to launch '{0}': {1}\n",165Arguments[0], error);166exit(1);167}168}
169
170Status writeSocketIdToPipe(Pipe &port_pipe, llvm::StringRef socket_id) {171size_t bytes_written = 0;172// Write the port number as a C string with the NULL terminator.173return port_pipe.Write(socket_id.data(), socket_id.size() + 1, bytes_written);174}
175
176Status writeSocketIdToPipe(const char *const named_pipe_path,177llvm::StringRef socket_id) {178Pipe port_name_pipe;179// Wait for 10 seconds for pipe to be opened.180auto error = port_name_pipe.OpenAsWriterWithTimeout(named_pipe_path, false,181std::chrono::seconds{10});182if (error.Fail())183return error;184return writeSocketIdToPipe(port_name_pipe, socket_id);185}
186
187Status writeSocketIdToPipe(lldb::pipe_t unnamed_pipe,188llvm::StringRef socket_id) {189Pipe port_pipe{LLDB_INVALID_PIPE, unnamed_pipe};190return writeSocketIdToPipe(port_pipe, socket_id);191}
192
193void ConnectToRemote(MainLoop &mainloop,194GDBRemoteCommunicationServerLLGS &gdb_server,195bool reverse_connect, llvm::StringRef host_and_port,196const char *const progname, const char *const subcommand,197const char *const named_pipe_path, pipe_t unnamed_pipe,198int connection_fd) {199Status error;200
201std::unique_ptr<Connection> connection_up;202std::string url;203
204if (connection_fd != -1) {205url = llvm::formatv("fd://{0}", connection_fd).str();206
207// Create the connection.208#if LLDB_ENABLE_POSIX && !defined _WIN32209::fcntl(connection_fd, F_SETFD, FD_CLOEXEC);210#endif211} else if (!host_and_port.empty()) {212llvm::Expected<std::string> url_exp =213LLGSArgToURL(host_and_port, reverse_connect);214if (!url_exp) {215llvm::errs() << llvm::formatv("error: invalid host:port or URL '{0}': "216"{1}\n",217host_and_port,218llvm::toString(url_exp.takeError()));219exit(-1);220}221
222url = std::move(url_exp.get());223}224
225if (!url.empty()) {226// Create the connection or server.227std::unique_ptr<ConnectionFileDescriptor> conn_fd_up{228new ConnectionFileDescriptor};229auto connection_result = conn_fd_up->Connect(230url,231[named_pipe_path, unnamed_pipe](llvm::StringRef socket_id) {232// If we have a named pipe to write the socket id back to, do that233// now.234if (named_pipe_path && named_pipe_path[0]) {235Status error = writeSocketIdToPipe(named_pipe_path, socket_id);236if (error.Fail())237llvm::errs() << llvm::formatv(238"failed to write to the named pipe '{0}': {1}\n",239named_pipe_path, error.AsCString());240}241// If we have an unnamed pipe to write the socket id back to, do242// that now.243else if (unnamed_pipe != LLDB_INVALID_PIPE) {244Status error = writeSocketIdToPipe(unnamed_pipe, socket_id);245if (error.Fail())246llvm::errs() << llvm::formatv(247"failed to write to the unnamed pipe: {0}\n", error);248}249},250&error);251
252if (error.Fail()) {253llvm::errs() << llvm::formatv(254"error: failed to connect to client at '{0}': {1}\n", url, error);255exit(-1);256}257if (connection_result != eConnectionStatusSuccess) {258llvm::errs() << llvm::formatv(259"error: failed to connect to client at '{0}' "260"(connection status: {1})\n",261url, static_cast<int>(connection_result));262exit(-1);263}264connection_up = std::move(conn_fd_up);265}266error = gdb_server.InitializeConnection(std::move(connection_up));267if (error.Fail()) {268llvm::errs() << llvm::formatv("failed to initialize connection\n", error);269exit(-1);270}271llvm::outs() << "Connection established.\n";272}
273
274namespace {275using namespace llvm::opt;276
277enum ID {278OPT_INVALID = 0, // This is not an option ID.279#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),280#include "LLGSOptions.inc"281#undef OPTION282};283
284#define PREFIX(NAME, VALUE) \285constexpr llvm::StringLiteral NAME##_init[] = VALUE; \286constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \287NAME##_init, std::size(NAME##_init) - 1);288#include "LLGSOptions.inc"289#undef PREFIX290
291static constexpr opt::OptTable::Info InfoTable[] = {292#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),293#include "LLGSOptions.inc"294#undef OPTION295};296
297class LLGSOptTable : public opt::GenericOptTable {298public:299LLGSOptTable() : opt::GenericOptTable(InfoTable) {}300
301void PrintHelp(llvm::StringRef Name) {302std::string Usage =303(Name + " [options] [[host]:port] [[--] program args...]").str();304OptTable::printHelp(llvm::outs(), Usage.c_str(), "lldb-server");305llvm::outs() << R"(306DESCRIPTION
307lldb-server connects to the LLDB client, which drives the debugging session.
308If no connection options are given, the [host]:port argument must be present
309and will denote the address that lldb-server will listen on. [host] defaults
310to "localhost" if empty. Port can be zero, in which case the port number will
311be chosen dynamically and written to destinations given by --named-pipe and
312--pipe arguments.
313
314If no target is selected at startup, lldb-server can be directed by the LLDB
315client to launch or attach to a process.
316
317)";318}319};320} // namespace321
322int main_gdbserver(int argc, char *argv[]) {323Status error;324MainLoop mainloop;325#ifndef _WIN32326// Setup signal handlers first thing.327signal(SIGPIPE, SIG_IGN);328MainLoop::SignalHandleUP sighup_handle =329mainloop.RegisterSignal(SIGHUP, sighup_handler, error);330#endif331
332const char *progname = argv[0];333const char *subcommand = argv[1];334std::string attach_target;335std::string named_pipe_path;336std::string log_file;337StringRef
338log_channels; // e.g. "lldb process threads:gdb-remote default:linux all"339lldb::pipe_t unnamed_pipe = LLDB_INVALID_PIPE;340bool reverse_connect = false;341int connection_fd = -1;342
343// ProcessLaunchInfo launch_info;344ProcessAttachInfo attach_info;345
346LLGSOptTable Opts;347llvm::BumpPtrAllocator Alloc;348llvm::StringSaver Saver(Alloc);349bool HasError = false;350opt::InputArgList Args = Opts.parseArgs(argc - 1, argv + 1, OPT_UNKNOWN,351Saver, [&](llvm::StringRef Msg) {352WithColor::error() << Msg << "\n";353HasError = true;354});355std::string Name =356(llvm::sys::path::filename(argv[0]) + " g[dbserver]").str();357std::string HelpText =358"Use '" + Name + " --help' for a complete list of options.\n";359if (HasError) {360llvm::errs() << HelpText;361return 1;362}363
364if (Args.hasArg(OPT_help)) {365Opts.PrintHelp(Name);366return 0;367}368
369#ifndef _WIN32370if (Args.hasArg(OPT_setsid)) {371// Put llgs into a new session. Terminals group processes372// into sessions and when a special terminal key sequences373// (like control+c) are typed they can cause signals to go out to374// all processes in a session. Using this --setsid (-S) option375// will cause debugserver to run in its own sessions and be free376// from such issues.377//378// This is useful when llgs is spawned from a command379// line application that uses llgs to do the debugging,380// yet that application doesn't want llgs receiving the381// signals sent to the session (i.e. dying when anyone hits ^C).382{383const ::pid_t new_sid = setsid();384if (new_sid == -1) {385WithColor::warning()386<< llvm::formatv("failed to set new session id for {0} ({1})\n",387LLGS_PROGRAM_NAME, llvm::sys::StrError());388}389}390}391#endif392
393log_file = Args.getLastArgValue(OPT_log_file).str();394log_channels = Args.getLastArgValue(OPT_log_channels);395named_pipe_path = Args.getLastArgValue(OPT_named_pipe).str();396reverse_connect = Args.hasArg(OPT_reverse_connect);397attach_target = Args.getLastArgValue(OPT_attach).str();398if (Args.hasArg(OPT_pipe)) {399uint64_t Arg;400if (!llvm::to_integer(Args.getLastArgValue(OPT_pipe), Arg)) {401WithColor::error() << "invalid '--pipe' argument\n" << HelpText;402return 1;403}404unnamed_pipe = (pipe_t)Arg;405}406if (Args.hasArg(OPT_fd)) {407if (!llvm::to_integer(Args.getLastArgValue(OPT_fd), connection_fd)) {408WithColor::error() << "invalid '--fd' argument\n" << HelpText;409return 1;410}411}412
413if (!LLDBServerUtilities::SetupLogging(414log_file, log_channels,415LLDB_LOG_OPTION_PREPEND_TIMESTAMP |416LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION))417return -1;418
419std::vector<llvm::StringRef> Inputs;420for (opt::Arg *Arg : Args.filtered(OPT_INPUT))421Inputs.push_back(Arg->getValue());422if (opt::Arg *Arg = Args.getLastArg(OPT_REM)) {423for (const char *Val : Arg->getValues())424Inputs.push_back(Val);425}426if (Inputs.empty() && connection_fd == -1) {427WithColor::error() << "no connection arguments\n" << HelpText;428return 1;429}430
431NativeProcessManager manager(mainloop);432GDBRemoteCommunicationServerLLGS gdb_server(mainloop, manager);433
434llvm::StringRef host_and_port;435if (!Inputs.empty()) {436host_and_port = Inputs.front();437Inputs.erase(Inputs.begin());438}439
440// Any arguments left over are for the program that we need to launch. If441// there442// are no arguments, then the GDB server will start up and wait for an 'A'443// packet444// to launch a program, or a vAttach packet to attach to an existing process,445// unless446// explicitly asked to attach with the --attach={pid|program_name} form.447if (!attach_target.empty())448handle_attach(gdb_server, attach_target);449else if (!Inputs.empty())450handle_launch(gdb_server, Inputs);451
452// Print version info.453printf("%s-%s\n", LLGS_PROGRAM_NAME, LLGS_VERSION_STR);454
455ConnectToRemote(mainloop, gdb_server, reverse_connect, host_and_port,456progname, subcommand, named_pipe_path.c_str(),457unnamed_pipe, connection_fd);458
459if (!gdb_server.IsConnected()) {460fprintf(stderr, "no connection information provided, unable to run\n");461return 1;462}463
464Status ret = mainloop.Run();465if (ret.Fail()) {466fprintf(stderr, "lldb-server terminating due to error: %s\n",467ret.AsCString());468return 1;469}470fprintf(stderr, "lldb-server exiting...\n");471
472return 0;473}
474