| //===-- lldb-gdbserver.cpp --------------------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <cerrno> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| |
| #ifndef _WIN32 |
| #include <csignal> |
| #include <unistd.h> |
| #endif |
| |
| #include "LLDBServerUtilities.h" |
| #include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h" |
| #include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" |
| #include "lldb/Host/Config.h" |
| #include "lldb/Host/ConnectionFileDescriptor.h" |
| #include "lldb/Host/FileSystem.h" |
| #include "lldb/Host/Pipe.h" |
| #include "lldb/Host/Socket.h" |
| #include "lldb/Host/common/NativeProcessProtocol.h" |
| #include "lldb/Target/Process.h" |
| #include "lldb/Utility/Status.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Option/ArgList.h" |
| #include "llvm/Option/OptTable.h" |
| #include "llvm/Option/Option.h" |
| #include "llvm/Support/Errno.h" |
| #include "llvm/Support/WithColor.h" |
| |
| #if defined(__linux__) |
| #include "Plugins/Process/Linux/NativeProcessLinux.h" |
| #elif defined(__FreeBSD__) |
| #include "Plugins/Process/FreeBSD/NativeProcessFreeBSD.h" |
| #elif defined(__NetBSD__) |
| #include "Plugins/Process/NetBSD/NativeProcessNetBSD.h" |
| #elif defined(_WIN32) |
| #include "Plugins/Process/Windows/Common/NativeProcessWindows.h" |
| #endif |
| |
| #ifndef LLGS_PROGRAM_NAME |
| #define LLGS_PROGRAM_NAME "lldb-server" |
| #endif |
| |
| #ifndef LLGS_VERSION_STR |
| #define LLGS_VERSION_STR "local_build" |
| #endif |
| |
| using namespace llvm; |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace lldb_private::lldb_server; |
| using namespace lldb_private::process_gdb_remote; |
| |
| namespace { |
| #if defined(__linux__) |
| typedef process_linux::NativeProcessLinux::Factory NativeProcessFactory; |
| #elif defined(__FreeBSD__) |
| typedef process_freebsd::NativeProcessFreeBSD::Factory NativeProcessFactory; |
| #elif defined(__NetBSD__) |
| typedef process_netbsd::NativeProcessNetBSD::Factory NativeProcessFactory; |
| #elif defined(_WIN32) |
| typedef NativeProcessWindows::Factory NativeProcessFactory; |
| #else |
| // Dummy implementation to make sure the code compiles |
| class NativeProcessFactory : public NativeProcessProtocol::Factory { |
| public: |
| llvm::Expected<std::unique_ptr<NativeProcessProtocol>> |
| Launch(ProcessLaunchInfo &launch_info, |
| NativeProcessProtocol::NativeDelegate &delegate, |
| MainLoop &mainloop) const override { |
| llvm_unreachable("Not implemented"); |
| } |
| llvm::Expected<std::unique_ptr<NativeProcessProtocol>> |
| Attach(lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &delegate, |
| MainLoop &mainloop) const override { |
| llvm_unreachable("Not implemented"); |
| } |
| }; |
| #endif |
| } |
| |
| #ifndef _WIN32 |
| // Watch for signals |
| static int g_sighup_received_count = 0; |
| |
| static void sighup_handler(MainLoopBase &mainloop) { |
| ++g_sighup_received_count; |
| |
| Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| LLDB_LOGF(log, "lldb-server:%s swallowing SIGHUP (receive count=%d)", |
| __FUNCTION__, g_sighup_received_count); |
| |
| if (g_sighup_received_count >= 2) |
| mainloop.RequestTermination(); |
| } |
| #endif // #ifndef _WIN32 |
| |
| void handle_attach_to_pid(GDBRemoteCommunicationServerLLGS &gdb_server, |
| lldb::pid_t pid) { |
| Status error = gdb_server.AttachToProcess(pid); |
| if (error.Fail()) { |
| fprintf(stderr, "error: failed to attach to pid %" PRIu64 ": %s\n", pid, |
| error.AsCString()); |
| exit(1); |
| } |
| } |
| |
| void handle_attach_to_process_name(GDBRemoteCommunicationServerLLGS &gdb_server, |
| const std::string &process_name) { |
| // FIXME implement. |
| } |
| |
| void handle_attach(GDBRemoteCommunicationServerLLGS &gdb_server, |
| const std::string &attach_target) { |
| assert(!attach_target.empty() && "attach_target cannot be empty"); |
| |
| // First check if the attach_target is convertible to a long. If so, we'll use |
| // it as a pid. |
| char *end_p = nullptr; |
| const long int pid = strtol(attach_target.c_str(), &end_p, 10); |
| |
| // We'll call it a match if the entire argument is consumed. |
| if (end_p && |
| static_cast<size_t>(end_p - attach_target.c_str()) == |
| attach_target.size()) |
| handle_attach_to_pid(gdb_server, static_cast<lldb::pid_t>(pid)); |
| else |
| handle_attach_to_process_name(gdb_server, attach_target); |
| } |
| |
| void handle_launch(GDBRemoteCommunicationServerLLGS &gdb_server, |
| llvm::ArrayRef<llvm::StringRef> Arguments) { |
| ProcessLaunchInfo info; |
| info.GetFlags().Set(eLaunchFlagStopAtEntry | eLaunchFlagDebug | |
| eLaunchFlagDisableASLR); |
| info.SetArguments(Args(Arguments), true); |
| |
| llvm::SmallString<64> cwd; |
| if (std::error_code ec = llvm::sys::fs::current_path(cwd)) { |
| llvm::errs() << "Error getting current directory: " << ec.message() << "\n"; |
| exit(1); |
| } |
| FileSpec cwd_spec(cwd); |
| FileSystem::Instance().Resolve(cwd_spec); |
| info.SetWorkingDirectory(cwd_spec); |
| info.GetEnvironment() = Host::GetEnvironment(); |
| |
| gdb_server.SetLaunchInfo(info); |
| |
| Status error = gdb_server.LaunchProcess(); |
| if (error.Fail()) { |
| llvm::errs() << llvm::formatv("error: failed to launch '{0}': {1}\n", |
| Arguments[0], error); |
| exit(1); |
| } |
| } |
| |
| Status writeSocketIdToPipe(Pipe &port_pipe, llvm::StringRef socket_id) { |
| size_t bytes_written = 0; |
| // Write the port number as a C string with the NULL terminator. |
| return port_pipe.Write(socket_id.data(), socket_id.size() + 1, bytes_written); |
| } |
| |
| Status writeSocketIdToPipe(const char *const named_pipe_path, |
| llvm::StringRef socket_id) { |
| Pipe port_name_pipe; |
| // Wait for 10 seconds for pipe to be opened. |
| auto error = port_name_pipe.OpenAsWriterWithTimeout(named_pipe_path, false, |
| std::chrono::seconds{10}); |
| if (error.Fail()) |
| return error; |
| return writeSocketIdToPipe(port_name_pipe, socket_id); |
| } |
| |
| Status writeSocketIdToPipe(lldb::pipe_t unnamed_pipe, |
| llvm::StringRef socket_id) { |
| Pipe port_pipe{LLDB_INVALID_PIPE, unnamed_pipe}; |
| return writeSocketIdToPipe(port_pipe, socket_id); |
| } |
| |
| void ConnectToRemote(MainLoop &mainloop, |
| GDBRemoteCommunicationServerLLGS &gdb_server, |
| bool reverse_connect, llvm::StringRef host_and_port, |
| const char *const progname, const char *const subcommand, |
| const char *const named_pipe_path, pipe_t unnamed_pipe, |
| int connection_fd) { |
| Status error; |
| |
| std::unique_ptr<Connection> connection_up; |
| std::string url; |
| |
| if (connection_fd != -1) { |
| url = llvm::formatv("fd://{0}", connection_fd).str(); |
| |
| // Create the connection. |
| #if LLDB_ENABLE_POSIX && !defined _WIN32 |
| ::fcntl(connection_fd, F_SETFD, FD_CLOEXEC); |
| #endif |
| } else if (!host_and_port.empty()) { |
| llvm::Expected<std::string> url_exp = |
| LLGSArgToURL(host_and_port, reverse_connect); |
| if (!url_exp) { |
| llvm::errs() << llvm::formatv("error: invalid host:port or URL '{0}': " |
| "{1}\n", |
| host_and_port, |
| llvm::toString(url_exp.takeError())); |
| exit(-1); |
| } |
| |
| url = std::move(url_exp.get()); |
| } |
| |
| if (!url.empty()) { |
| // Create the connection or server. |
| std::unique_ptr<ConnectionFileDescriptor> conn_fd_up{ |
| new ConnectionFileDescriptor}; |
| auto connection_result = conn_fd_up->Connect( |
| url, |
| [named_pipe_path, unnamed_pipe](llvm::StringRef socket_id) { |
| // If we have a named pipe to write the socket id back to, do that |
| // now. |
| if (named_pipe_path && named_pipe_path[0]) { |
| Status error = writeSocketIdToPipe(named_pipe_path, socket_id); |
| if (error.Fail()) |
| llvm::errs() << llvm::formatv( |
| "failed to write to the named peipe '{0}': {1}\n", |
| named_pipe_path, error.AsCString()); |
| } |
| // If we have an unnamed pipe to write the socket id back to, do |
| // that now. |
| else if (unnamed_pipe != LLDB_INVALID_PIPE) { |
| Status error = writeSocketIdToPipe(unnamed_pipe, socket_id); |
| if (error.Fail()) |
| llvm::errs() << llvm::formatv( |
| "failed to write to the unnamed pipe: {0}\n", error); |
| } |
| }, |
| &error); |
| |
| if (error.Fail()) { |
| llvm::errs() << llvm::formatv( |
| "error: failed to connect to client at '{0}': {1}\n", url, error); |
| exit(-1); |
| } |
| if (connection_result != eConnectionStatusSuccess) { |
| llvm::errs() << llvm::formatv( |
| "error: failed to connect to client at '{0}' " |
| "(connection status: {1})\n", |
| url, static_cast<int>(connection_result)); |
| exit(-1); |
| } |
| connection_up = std::move(conn_fd_up); |
| } |
| error = gdb_server.InitializeConnection(std::move(connection_up)); |
| if (error.Fail()) { |
| llvm::errs() << llvm::formatv("failed to initialize connection\n", error); |
| exit(-1); |
| } |
| llvm::outs() << "Connection established.\n"; |
| } |
| |
| namespace { |
| enum ID { |
| OPT_INVALID = 0, // This is not an option ID. |
| #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ |
| HELPTEXT, METAVAR, VALUES) \ |
| OPT_##ID, |
| #include "LLGSOptions.inc" |
| #undef OPTION |
| }; |
| |
| #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; |
| #include "LLGSOptions.inc" |
| #undef PREFIX |
| |
| const opt::OptTable::Info InfoTable[] = { |
| #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ |
| HELPTEXT, METAVAR, VALUES) \ |
| { \ |
| PREFIX, NAME, HELPTEXT, \ |
| METAVAR, OPT_##ID, opt::Option::KIND##Class, \ |
| PARAM, FLAGS, OPT_##GROUP, \ |
| OPT_##ALIAS, ALIASARGS, VALUES}, |
| #include "LLGSOptions.inc" |
| #undef OPTION |
| }; |
| |
| class LLGSOptTable : public opt::OptTable { |
| public: |
| LLGSOptTable() : OptTable(InfoTable) {} |
| |
| void PrintHelp(llvm::StringRef Name) { |
| std::string Usage = |
| (Name + " [options] [[host]:port] [[--] program args...]").str(); |
| OptTable::printHelp(llvm::outs(), Usage.c_str(), "lldb-server"); |
| llvm::outs() << R"( |
| DESCRIPTION |
| lldb-server connects to the LLDB client, which drives the debugging session. |
| If no connection options are given, the [host]:port argument must be present |
| and will denote the address that lldb-server will listen on. [host] defaults |
| to "localhost" if empty. Port can be zero, in which case the port number will |
| be chosen dynamically and written to destinations given by --named-pipe and |
| --pipe arguments. |
| |
| If no target is selected at startup, lldb-server can be directed by the LLDB |
| client to launch or attach to a process. |
| |
| )"; |
| } |
| }; |
| } // namespace |
| |
| int main_gdbserver(int argc, char *argv[]) { |
| Status error; |
| MainLoop mainloop; |
| #ifndef _WIN32 |
| // Setup signal handlers first thing. |
| signal(SIGPIPE, SIG_IGN); |
| MainLoop::SignalHandleUP sighup_handle = |
| mainloop.RegisterSignal(SIGHUP, sighup_handler, error); |
| #endif |
| |
| const char *progname = argv[0]; |
| const char *subcommand = argv[1]; |
| std::string attach_target; |
| std::string named_pipe_path; |
| std::string log_file; |
| StringRef |
| log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" |
| lldb::pipe_t unnamed_pipe = LLDB_INVALID_PIPE; |
| bool reverse_connect = false; |
| int connection_fd = -1; |
| |
| // ProcessLaunchInfo launch_info; |
| ProcessAttachInfo attach_info; |
| |
| LLGSOptTable Opts; |
| llvm::BumpPtrAllocator Alloc; |
| llvm::StringSaver Saver(Alloc); |
| bool HasError = false; |
| opt::InputArgList Args = Opts.parseArgs(argc - 1, argv + 1, OPT_UNKNOWN, |
| Saver, [&](llvm::StringRef Msg) { |
| WithColor::error() << Msg << "\n"; |
| HasError = true; |
| }); |
| std::string Name = |
| (llvm::sys::path::filename(argv[0]) + " g[dbserver]").str(); |
| std::string HelpText = |
| "Use '" + Name + " --help' for a complete list of options.\n"; |
| if (HasError) { |
| llvm::errs() << HelpText; |
| return 1; |
| } |
| |
| if (Args.hasArg(OPT_help)) { |
| Opts.PrintHelp(Name); |
| return 0; |
| } |
| |
| #ifndef _WIN32 |
| if (Args.hasArg(OPT_setsid)) { |
| // Put llgs into a new session. Terminals group processes |
| // into sessions and when a special terminal key sequences |
| // (like control+c) are typed they can cause signals to go out to |
| // all processes in a session. Using this --setsid (-S) option |
| // will cause debugserver to run in its own sessions and be free |
| // from such issues. |
| // |
| // This is useful when llgs is spawned from a command |
| // line application that uses llgs to do the debugging, |
| // yet that application doesn't want llgs receiving the |
| // signals sent to the session (i.e. dying when anyone hits ^C). |
| { |
| const ::pid_t new_sid = setsid(); |
| if (new_sid == -1) { |
| WithColor::warning() |
| << llvm::formatv("failed to set new session id for {0} ({1})\n", |
| LLGS_PROGRAM_NAME, llvm::sys::StrError()); |
| } |
| } |
| } |
| #endif |
| |
| log_file = Args.getLastArgValue(OPT_log_file).str(); |
| log_channels = Args.getLastArgValue(OPT_log_channels); |
| named_pipe_path = Args.getLastArgValue(OPT_named_pipe).str(); |
| reverse_connect = Args.hasArg(OPT_reverse_connect); |
| attach_target = Args.getLastArgValue(OPT_attach).str(); |
| if (Args.hasArg(OPT_pipe)) { |
| uint64_t Arg; |
| if (!llvm::to_integer(Args.getLastArgValue(OPT_pipe), Arg)) { |
| WithColor::error() << "invalid '--pipe' argument\n" << HelpText; |
| return 1; |
| } |
| unnamed_pipe = (pipe_t)Arg; |
| } |
| if (Args.hasArg(OPT_fd)) { |
| if (!llvm::to_integer(Args.getLastArgValue(OPT_fd), connection_fd)) { |
| WithColor::error() << "invalid '--fd' argument\n" << HelpText; |
| return 1; |
| } |
| } |
| |
| if (!LLDBServerUtilities::SetupLogging( |
| log_file, log_channels, |
| LLDB_LOG_OPTION_PREPEND_TIMESTAMP | |
| LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION)) |
| return -1; |
| |
| std::vector<llvm::StringRef> Inputs; |
| for (opt::Arg *Arg : Args.filtered(OPT_INPUT)) |
| Inputs.push_back(Arg->getValue()); |
| if (opt::Arg *Arg = Args.getLastArg(OPT_REM)) { |
| for (const char *Val : Arg->getValues()) |
| Inputs.push_back(Val); |
| } |
| if (Inputs.empty() && connection_fd == -1) { |
| WithColor::error() << "no connection arguments\n" << HelpText; |
| return 1; |
| } |
| |
| NativeProcessFactory factory; |
| GDBRemoteCommunicationServerLLGS gdb_server(mainloop, factory); |
| |
| llvm::StringRef host_and_port; |
| if (!Inputs.empty()) { |
| host_and_port = Inputs.front(); |
| Inputs.erase(Inputs.begin()); |
| } |
| |
| // Any arguments left over are for the program that we need to launch. If |
| // there |
| // are no arguments, then the GDB server will start up and wait for an 'A' |
| // packet |
| // to launch a program, or a vAttach packet to attach to an existing process, |
| // unless |
| // explicitly asked to attach with the --attach={pid|program_name} form. |
| if (!attach_target.empty()) |
| handle_attach(gdb_server, attach_target); |
| else if (!Inputs.empty()) |
| handle_launch(gdb_server, Inputs); |
| |
| // Print version info. |
| printf("%s-%s\n", LLGS_PROGRAM_NAME, LLGS_VERSION_STR); |
| |
| ConnectToRemote(mainloop, gdb_server, reverse_connect, host_and_port, |
| progname, subcommand, named_pipe_path.c_str(), |
| unnamed_pipe, connection_fd); |
| |
| if (!gdb_server.IsConnected()) { |
| fprintf(stderr, "no connection information provided, unable to run\n"); |
| return 1; |
| } |
| |
| Status ret = mainloop.Run(); |
| if (ret.Fail()) { |
| fprintf(stderr, "lldb-server terminating due to error: %s\n", |
| ret.AsCString()); |
| return 1; |
| } |
| fprintf(stderr, "lldb-server exiting...\n"); |
| |
| return 0; |
| } |