| //===-- lldb-gdbserver.cpp --------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "lldb/lldb-python.h" |
| |
| // C Includes |
| #include <errno.h> |
| #include <getopt.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #ifndef _WIN32 |
| #include <signal.h> |
| #include <unistd.h> |
| #endif |
| |
| // C++ Includes |
| |
| // Other libraries and framework includes |
| #include "lldb/lldb-private-log.h" |
| #include "lldb/Core/Error.h" |
| #include "lldb/Core/ConnectionFileDescriptor.h" |
| #include "lldb/Core/ConnectionMachPort.h" |
| #include "lldb/Core/Debugger.h" |
| #include "lldb/Core/PluginManager.h" |
| #include "lldb/Core/StreamFile.h" |
| #include "lldb/Host/OptionParser.h" |
| #include "lldb/Interpreter/CommandInterpreter.h" |
| #include "lldb/Interpreter/CommandReturnObject.h" |
| #include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h" |
| #include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" |
| |
| #ifndef LLGS_PROGRAM_NAME |
| #define LLGS_PROGRAM_NAME "lldb-gdbserver" |
| #endif |
| |
| #ifndef LLGS_VERSION_STR |
| #define LLGS_VERSION_STR "local_build" |
| #endif |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| // lldb-gdbserver state |
| |
| namespace |
| { |
| static lldb::thread_t s_listen_thread = LLDB_INVALID_HOST_THREAD; |
| static std::unique_ptr<ConnectionFileDescriptor> s_listen_connection_up; |
| static std::string s_listen_url; |
| } |
| |
| //---------------------------------------------------------------------- |
| // option descriptors for getopt_long_only() |
| //---------------------------------------------------------------------- |
| |
| int g_debug = 0; |
| int g_verbose = 0; |
| |
| static struct option g_long_options[] = |
| { |
| { "debug", no_argument, &g_debug, 1 }, |
| { "platform", required_argument, NULL, 'p' }, |
| { "verbose", no_argument, &g_verbose, 1 }, |
| { "lldb-command", required_argument, NULL, 'c' }, |
| { "log-file", required_argument, NULL, 'l' }, |
| { "log-flags", required_argument, NULL, 'f' }, |
| { "attach", required_argument, NULL, 'a' }, |
| { "named-pipe", required_argument, NULL, 'P' }, |
| { "native-regs", no_argument, NULL, 'r' }, // Specify to use the native registers instead of the gdb defaults for the architecture. NOTE: this is a do-nothing arg as it's behavior is default now. FIXME remove call from lldb-platform. |
| { "setsid", no_argument, NULL, 'S' }, // Call setsid() to make llgs run in its own session. |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| |
| //---------------------------------------------------------------------- |
| // Watch for signals |
| //---------------------------------------------------------------------- |
| static int g_sigpipe_received = 0; |
| static int g_sighup_received_count = 0; |
| |
| #ifndef _WIN32 |
| |
| void |
| signal_handler(int signo) |
| { |
| Log *log (GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| fprintf (stderr, "lldb-gdbserver:%s received signal %d\n", __FUNCTION__, signo); |
| if (log) |
| log->Printf ("lldb-gdbserver:%s received signal %d", __FUNCTION__, signo); |
| |
| switch (signo) |
| { |
| case SIGPIPE: |
| g_sigpipe_received = 1; |
| break; |
| case SIGHUP: |
| ++g_sighup_received_count; |
| |
| // For now, swallow SIGHUP. |
| if (log) |
| log->Printf ("lldb-gdbserver:%s swallowing SIGHUP (receive count=%d)", __FUNCTION__, g_sighup_received_count); |
| signal (SIGHUP, signal_handler); |
| break; |
| } |
| } |
| #endif // #ifndef _WIN32 |
| |
| static void |
| display_usage (const char *progname) |
| { |
| fprintf(stderr, "Usage:\n %s [--log-file log-file-path] [--log-flags flags] [--lldb-command command]* [--platform platform_name] [--setsid] [--named-pipe named-pipe-path] [--native-regs] [--attach pid] [[HOST]:PORT] " |
| "[-- PROGRAM ARG1 ARG2 ...]\n", progname); |
| exit(0); |
| } |
| |
| static void |
| dump_available_platforms (FILE *output_file) |
| { |
| fprintf (output_file, "Available platform plugins:\n"); |
| for (int i = 0; ; ++i) |
| { |
| const char *plugin_name = PluginManager::GetPlatformPluginNameAtIndex (i); |
| const char *plugin_desc = PluginManager::GetPlatformPluginDescriptionAtIndex (i); |
| |
| if (!plugin_name || !plugin_desc) |
| break; |
| |
| fprintf (output_file, "%s\t%s\n", plugin_name, plugin_desc); |
| } |
| |
| if ( Platform::GetDefaultPlatform () ) |
| { |
| // add this since the default platform doesn't necessarily get registered by |
| // the plugin name (e.g. 'host' doesn't show up as a |
| // registered platform plugin even though it's the default). |
| fprintf (output_file, "%s\tDefault platform for this host.\n", Platform::GetDefaultPlatform ()->GetPluginName ().AsCString ()); |
| } |
| } |
| |
| static void |
| initialize_lldb_gdbserver () |
| { |
| PluginManager::Initialize (); |
| Debugger::Initialize (NULL); |
| } |
| |
| static void |
| terminate_lldb_gdbserver () |
| { |
| Debugger::Terminate(); |
| PluginManager::Terminate (); |
| } |
| |
| static void |
| run_lldb_commands (const lldb::DebuggerSP &debugger_sp, const std::vector<std::string> lldb_commands) |
| { |
| for (const auto &lldb_command : lldb_commands) |
| { |
| printf("(lldb) %s\n", lldb_command.c_str ()); |
| |
| lldb_private::CommandReturnObject result; |
| debugger_sp->GetCommandInterpreter ().HandleCommand (lldb_command.c_str (), eLazyBoolNo, result); |
| const char *output = result.GetOutputData (); |
| if (output && output[0]) |
| puts (output); |
| } |
| } |
| |
| static lldb::PlatformSP |
| setup_platform (const std::string platform_name) |
| { |
| lldb::PlatformSP platform_sp; |
| |
| if (platform_name.empty()) |
| { |
| printf ("using the default platform: "); |
| platform_sp = Platform::GetDefaultPlatform (); |
| printf ("%s\n", platform_sp->GetPluginName ().AsCString ()); |
| return platform_sp; |
| } |
| |
| Error error; |
| platform_sp = Platform::Create (platform_name.c_str(), error); |
| if (error.Fail ()) |
| { |
| // the host platform isn't registered with that name (at |
| // least, not always. Check if the given name matches |
| // the default platform name. If so, use it. |
| if ( Platform::GetDefaultPlatform () && ( Platform::GetDefaultPlatform ()->GetPluginName () == ConstString (platform_name.c_str()) ) ) |
| { |
| platform_sp = Platform::GetDefaultPlatform (); |
| } |
| else |
| { |
| fprintf (stderr, "error: failed to create platform with name '%s'\n", platform_name.c_str()); |
| dump_available_platforms (stderr); |
| exit (1); |
| } |
| } |
| printf ("using platform: %s\n", platform_name.c_str ()); |
| |
| return platform_sp; |
| } |
| |
| void |
| handle_attach_to_pid (GDBRemoteCommunicationServer &gdb_server, lldb::pid_t pid) |
| { |
| Error 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 (GDBRemoteCommunicationServer &gdb_server, const std::string &process_name) |
| { |
| // FIXME implement. |
| } |
| |
| void |
| handle_attach (GDBRemoteCommunicationServer &gdb_server, const std::string &attach_target) |
| { |
| assert (!attach_target.empty () && "attach_target cannot be empty"); |
| |
| // First check if the attach_target is convertable 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 (GDBRemoteCommunicationServer &gdb_server, int argc, const char *const argv[]) |
| { |
| Error error; |
| error = gdb_server.SetLaunchArguments (argv, argc); |
| if (error.Fail ()) |
| { |
| fprintf (stderr, "error: failed to set launch args for '%s': %s\n", argv[0], error.AsCString()); |
| exit(1); |
| } |
| |
| unsigned int launch_flags = eLaunchFlagStopAtEntry | eLaunchFlagDebug; |
| |
| error = gdb_server.SetLaunchFlags (launch_flags); |
| if (error.Fail ()) |
| { |
| fprintf (stderr, "error: failed to set launch flags for '%s': %s\n", argv[0], error.AsCString()); |
| exit(1); |
| } |
| |
| error = gdb_server.LaunchProcess (); |
| if (error.Fail ()) |
| { |
| fprintf (stderr, "error: failed to launch '%s': %s\n", argv[0], error.AsCString()); |
| exit(1); |
| } |
| } |
| |
| static lldb::thread_result_t |
| ListenThread (lldb::thread_arg_t /* arg */) |
| { |
| Error error; |
| |
| if (s_listen_connection_up) |
| { |
| // Do the listen on another thread so we can continue on... |
| if (s_listen_connection_up->Connect(s_listen_url.c_str(), &error) != eConnectionStatusSuccess) |
| s_listen_connection_up.reset(); |
| } |
| return nullptr; |
| } |
| |
| static Error |
| StartListenThread (const char *hostname, uint16_t port) |
| { |
| Error error; |
| if (IS_VALID_LLDB_HOST_THREAD(s_listen_thread)) |
| { |
| error.SetErrorString("listen thread already running"); |
| } |
| else |
| { |
| char listen_url[512]; |
| if (hostname && hostname[0]) |
| snprintf(listen_url, sizeof(listen_url), "listen://%s:%i", hostname, port); |
| else |
| snprintf(listen_url, sizeof(listen_url), "listen://%i", port); |
| |
| s_listen_url = listen_url; |
| s_listen_connection_up.reset (new ConnectionFileDescriptor ()); |
| s_listen_thread = Host::ThreadCreate (listen_url, ListenThread, nullptr, &error); |
| } |
| return error; |
| } |
| |
| static bool |
| JoinListenThread () |
| { |
| if (IS_VALID_LLDB_HOST_THREAD(s_listen_thread)) |
| { |
| Host::ThreadJoin(s_listen_thread, nullptr, nullptr); |
| s_listen_thread = LLDB_INVALID_HOST_THREAD; |
| } |
| return true; |
| } |
| |
| void |
| start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host_and_port, const char *const progname, const char *const named_pipe_path) |
| { |
| Error error; |
| |
| if (host_and_port && host_and_port[0]) |
| { |
| std::string final_host_and_port; |
| std::string listening_host; |
| std::string listening_port; |
| uint32_t listening_portno = 0; |
| |
| // If host_and_port starts with ':', default the host to be "localhost" and expect the remainder to be the port. |
| if (host_and_port[0] == ':') |
| final_host_and_port.append ("localhost"); |
| final_host_and_port.append (host_and_port); |
| |
| const std::string::size_type colon_pos = final_host_and_port.find (':'); |
| if (colon_pos != std::string::npos) |
| { |
| listening_host = final_host_and_port.substr (0, colon_pos); |
| listening_port = final_host_and_port.substr (colon_pos + 1); |
| listening_portno = Args::StringToUInt32 (listening_port.c_str (), 0); |
| } |
| else |
| { |
| fprintf (stderr, "failed to parse host and port from connection string '%s'\n", final_host_and_port.c_str ()); |
| display_usage (progname); |
| exit (1); |
| } |
| |
| // Start the listener on a new thread. We need to do this so we can resolve the |
| // bound listener port. |
| StartListenThread(listening_host.c_str (), static_cast<uint16_t> (listening_portno)); |
| printf ("Listening to port %s for a connection from %s...\n", listening_port.c_str (), listening_host.c_str ()); |
| |
| // If we have a named pipe to write the port number back to, do that now. |
| if (named_pipe_path && named_pipe_path[0] && listening_portno == 0) |
| { |
| // FIXME use new generic named pipe support. |
| int fd = ::open(named_pipe_path, O_WRONLY); |
| if (fd > -1) |
| { |
| const uint16_t bound_port = s_listen_connection_up->GetBoundPort (10); |
| |
| char port_str[64]; |
| const ssize_t port_str_len = ::snprintf (port_str, sizeof(port_str), "%u", bound_port); |
| // Write the port number as a C string with the NULL terminator. |
| ::write (fd, port_str, port_str_len + 1); |
| close (fd); |
| } |
| else |
| { |
| fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path); |
| } |
| } |
| |
| // Join the listener thread. |
| if (!JoinListenThread ()) |
| { |
| fprintf (stderr, "failed to join the listener thread\n"); |
| display_usage (progname); |
| exit (1); |
| } |
| |
| // Ensure we connected. |
| if (s_listen_connection_up) |
| { |
| printf ("Connection established.\n"); |
| gdb_server.SetConnection (s_listen_connection_up.release()); |
| } |
| else |
| { |
| fprintf (stderr, "failed to connect to '%s': %s\n", final_host_and_port.c_str (), error.AsCString ()); |
| display_usage (progname); |
| exit (1); |
| } |
| } |
| |
| if (gdb_server.IsConnected()) |
| { |
| // After we connected, we need to get an initial ack from... |
| if (gdb_server.HandshakeWithClient(&error)) |
| { |
| // We'll use a half a second timeout interval so that an exit conditions can |
| // be checked that often. |
| const uint32_t TIMEOUT_USEC = 500000; |
| |
| bool interrupt = false; |
| bool done = false; |
| while (!interrupt && !done && (g_sighup_received_count < 1)) |
| { |
| const GDBRemoteCommunication::PacketResult result = gdb_server.GetPacketAndSendResponse (TIMEOUT_USEC, error, interrupt, done); |
| if ((result != GDBRemoteCommunication::PacketResult::Success) && |
| (result != GDBRemoteCommunication::PacketResult::ErrorReplyTimeout)) |
| { |
| // We're bailing out - we only support successful handling and timeouts. |
| fprintf(stderr, "leaving packet loop due to PacketResult %d\n", result); |
| break; |
| } |
| } |
| |
| if (error.Fail()) |
| { |
| fprintf(stderr, "error: %s\n", error.AsCString()); |
| } |
| } |
| else |
| { |
| fprintf(stderr, "error: handshake with client failed\n"); |
| } |
| } |
| else |
| { |
| fprintf (stderr, "no connection information provided, unable to run\n"); |
| display_usage (progname); |
| exit (1); |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // main |
| //---------------------------------------------------------------------- |
| int |
| main (int argc, char *argv[]) |
| { |
| #ifndef _WIN32 |
| // Setup signal handlers first thing. |
| signal (SIGPIPE, signal_handler); |
| signal (SIGHUP, signal_handler); |
| #endif |
| |
| const char *progname = argv[0]; |
| int long_option_index = 0; |
| StreamSP log_stream_sp; |
| Args log_args; |
| Error error; |
| int ch; |
| std::string platform_name; |
| std::string attach_target; |
| std::string named_pipe_path; |
| |
| initialize_lldb_gdbserver (); |
| |
| lldb::DebuggerSP debugger_sp = Debugger::CreateInstance (); |
| |
| debugger_sp->SetInputFileHandle(stdin, false); |
| debugger_sp->SetOutputFileHandle(stdout, false); |
| debugger_sp->SetErrorFileHandle(stderr, false); |
| |
| // ProcessLaunchInfo launch_info; |
| ProcessAttachInfo attach_info; |
| |
| bool show_usage = false; |
| int option_error = 0; |
| #if __GLIBC__ |
| optind = 0; |
| #else |
| optreset = 1; |
| optind = 1; |
| #endif |
| |
| std::string short_options(OptionParser::GetShortOptionString(g_long_options)); |
| |
| std::vector<std::string> lldb_commands; |
| |
| while ((ch = getopt_long_only(argc, argv, short_options.c_str(), g_long_options, &long_option_index)) != -1) |
| { |
| switch (ch) |
| { |
| case 0: // Any optional that auto set themselves will return 0 |
| break; |
| |
| case 'l': // Set Log File |
| if (optarg && optarg[0]) |
| { |
| if ((strcasecmp(optarg, "stdout") == 0) || (strcmp(optarg, "/dev/stdout") == 0)) |
| { |
| log_stream_sp.reset (new StreamFile (stdout, false)); |
| } |
| else if ((strcasecmp(optarg, "stderr") == 0) || (strcmp(optarg, "/dev/stderr") == 0)) |
| { |
| log_stream_sp.reset (new StreamFile (stderr, false)); |
| } |
| else |
| { |
| FILE *log_file = fopen(optarg, "w"); |
| if (log_file) |
| { |
| setlinebuf(log_file); |
| log_stream_sp.reset (new StreamFile (log_file, true)); |
| } |
| else |
| { |
| const char *errno_str = strerror(errno); |
| fprintf (stderr, "Failed to open log file '%s' for writing: errno = %i (%s)", optarg, errno, errno_str ? errno_str : "unknown error"); |
| } |
| |
| } |
| } |
| break; |
| |
| case 'f': // Log Flags |
| if (optarg && optarg[0]) |
| log_args.AppendArgument(optarg); |
| break; |
| |
| case 'c': // lldb commands |
| if (optarg && optarg[0]) |
| lldb_commands.push_back(optarg); |
| break; |
| |
| case 'p': // platform name |
| if (optarg && optarg[0]) |
| platform_name = optarg; |
| break; |
| |
| case 'P': // named pipe |
| if (optarg && optarg[0]) |
| named_pipe_path = optarg; |
| break; |
| |
| case 'r': |
| // Do nothing, native regs is the default these days |
| break; |
| |
| #ifndef _WIN32 |
| case 'S': |
| // 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) |
| { |
| const char *errno_str = strerror(errno); |
| fprintf (stderr, "failed to set new session id for %s (%s)\n", LLGS_PROGRAM_NAME, errno_str ? errno_str : "<no error string>"); |
| } |
| } |
| break; |
| #endif |
| |
| case 'a': // attach {pid|process_name} |
| if (optarg && optarg[0]) |
| attach_target = optarg; |
| break; |
| |
| case 'h': /* fall-through is intentional */ |
| case '?': |
| show_usage = true; |
| break; |
| } |
| } |
| |
| if (show_usage || option_error) |
| { |
| display_usage(progname); |
| exit(option_error); |
| } |
| |
| if (log_stream_sp) |
| { |
| if (log_args.GetArgumentCount() == 0) |
| log_args.AppendArgument("default"); |
| ProcessGDBRemoteLog::EnableLog (log_stream_sp, 0,log_args.GetConstArgumentVector(), log_stream_sp.get()); |
| } |
| |
| // Skip any options we consumed with getopt_long_only. |
| argc -= optind; |
| argv += optind; |
| |
| if (argc == 0) |
| { |
| display_usage(progname); |
| exit(255); |
| } |
| |
| // Run any commands requested. |
| run_lldb_commands (debugger_sp, lldb_commands); |
| |
| // Setup the platform that GDBRemoteCommunicationServer will use. |
| lldb::PlatformSP platform_sp = setup_platform (platform_name); |
| |
| const bool is_platform = false; |
| GDBRemoteCommunicationServer gdb_server (is_platform, platform_sp, debugger_sp); |
| |
| const char *const host_and_port = argv[0]; |
| argc -= 1; |
| argv += 1; |
| |
| // Any arguments left over are for the 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 (argc > 0) |
| handle_launch (gdb_server, argc, argv); |
| |
| // Print version info. |
| printf("%s-%s", LLGS_PROGRAM_NAME, LLGS_VERSION_STR); |
| |
| start_listener (gdb_server, host_and_port, progname, named_pipe_path.c_str ()); |
| |
| terminate_lldb_gdbserver (); |
| |
| fprintf(stderr, "lldb-gdbserver exiting...\n"); |
| |
| return 0; |
| } |