--- gdb/Makefile.in | 13 + gdb/configure | 24 ++ gdb/configure.ac | 19 ++ gdb/mi/mi-cmds.c | 4 gdb/mi/mi-cmds.h | 4 gdb/mi/mi-telnet.c | 426 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 490 insertions(+) Index: b/gdb/Makefile.in =================================================================== --- b.orig/gdb/Makefile.in 2011-10-21 16:45:15.075968998 +0400 +++ b/gdb/Makefile.in 2011-10-28 17:52:21.476015998 +0400 @@ -221,6 +221,15 @@ SUBDIR_MI_CFLAGS= # +# MI telnet directory definitions +# +SUBDIR_MITEL_OBS = mi-telnet.o +SUBDIR_MITEL_SRCS = mi/mi-telnet.c +SUBDIR_MITEL_DEPS= +SUBDIR_MITEL_LDFLAGS= +SUBDIR_MITEL_CFLAGS= -DENABLE_GDBMITEL + +# # TUI sub directory definitions # @@ -1939,6 +1948,10 @@ $(COMPILE) $(srcdir)/mi/mi-common.c $(POSTCOMPILE) +mi-telnet.o: $(srcdir)/mi/mi-telnet.c + $(COMPILE) $(srcdir)/mi/mi-telnet.c + $(POSTCOMPILE) + # gdb/common/ dependencies # # Need to explicitly specify the compile rule as make will do nothing Index: b/gdb/configure =================================================================== --- b.orig/gdb/configure 2011-10-21 16:45:12.585968998 +0400 +++ b/gdb/configure 2011-10-21 16:47:17.245968998 +0400 @@ -954,6 +954,7 @@ enable_64_bit_bfd enable_gdbcli enable_gdbmi +enable_gdbmitel enable_tui enable_gdbtk with_libunwind @@ -1631,6 +1632,7 @@ --enable-64-bit-bfd 64-bit support (on hosts with narrower word sizes) --disable-gdbcli disable command-line interface (CLI) --disable-gdbmi disable machine-interface (MI) + --enable-gdbmitel enable remote interface over telnet --enable-tui enable full-screen terminal user interface (TUI) --enable-gdbtk enable gdbtk graphical user interface (GUI) --enable-profiling enable profiling of GDB @@ -8180,6 +8182,28 @@ fi fi +# Enable MI telnet. +# Check whether --enable-gdbmitel was given. +if test "${enable_gdbmitel+set}" = set; then : + enableval=$enable_gdbmitel; case $enableval in + yes | no) + ;; + *) + as_fn_error "bad value $enableval for --enable-gdbmitel" "$LINENO" 5 ;; + esac +else + enable_gdbmitel=no +fi + +if test x"$enable_gdbmitel" = xyes; then + if test -d $srcdir/mi; then + CONFIG_OBS="$CONFIG_OBS \$(SUBDIR_MITEL_OBS)" + CONFIG_DEPS="$CONFIG_DEPS \$(SUBDIR_MITEL_DEPS)" + CONFIG_SRCS="$CONFIG_SRCS \$(SUBDIR_MITEL_SRCS)" + ENABLE_CFLAGS="$ENABLE_CFLAGS \$(SUBDIR_MITEL_CFLAGS)" + fi +fi + # Enable TUI. # Check whether --enable-tui was given. if test "${enable_tui+set}" = set; then : Index: b/gdb/configure.ac =================================================================== --- b.orig/gdb/configure.ac 2011-10-21 16:45:09.985968998 +0400 +++ b/gdb/configure.ac 2011-10-21 16:46:03.775968998 +0400 @@ -303,6 +303,25 @@ fi fi +# Enable MI telnet. +AC_ARG_ENABLE(gdbmitel, +AS_HELP_STRING([--enable-gdbmitel], [enable remote interface over telnet]), + [case $enableval in + yes | no) + ;; + *) + AC_MSG_ERROR([bad value $enableval for --enable-gdbmitel]) ;; + esac], + [enable_gdbmitel=no]) +if test x"$enable_gdbmitel" = xyes; then + if test -d $srcdir/mi; then + CONFIG_OBS="$CONFIG_OBS \$(SUBDIR_MITEL_OBS)" + CONFIG_DEPS="$CONFIG_DEPS \$(SUBDIR_MITEL_DEPS)" + CONFIG_SRCS="$CONFIG_SRCS \$(SUBDIR_MITEL_SRCS)" + ENABLE_CFLAGS="$ENABLE_CFLAGS \$(SUBDIR_MITEL_CFLAGS)" + fi +fi + # Enable TUI. AC_ARG_ENABLE(tui, AS_HELP_STRING([--enable-tui], [enable full-screen terminal user interface (TUI)]), Index: b/gdb/mi/mi-cmds.c =================================================================== --- b.orig/gdb/mi/mi-cmds.c 2011-10-21 16:45:22.655968998 +0400 +++ b/gdb/mi/mi-cmds.c 2011-10-21 16:46:41.615968998 +0400 @@ -137,6 +137,10 @@ { "var-show-attributes", { NULL, 0 }, mi_cmd_var_show_attributes}, { "var-show-format", { NULL, 0 }, mi_cmd_var_show_format}, { "var-update", { NULL, 0 }, mi_cmd_var_update}, +#ifdef ENABLE_GDBMITEL + { "start-telnet-service", { NULL, 0 }, mi_cmd_start_telnet_service}, + { "stop-telnet-service", { NULL, 0 }, mi_cmd_stop_telnet_service}, +#endif { NULL, } }; Index: b/gdb/mi/mi-cmds.h =================================================================== --- b.orig/gdb/mi/mi-cmds.h 2011-10-21 16:45:21.785968998 +0400 +++ b/gdb/mi/mi-cmds.h 2011-10-21 16:46:23.845968998 +0400 @@ -116,6 +116,10 @@ extern mi_cmd_argv_ftype mi_cmd_var_update; extern mi_cmd_argv_ftype mi_cmd_enable_pretty_printing; extern mi_cmd_argv_ftype mi_cmd_var_set_update_range; +#ifdef ENABLE_GDBMITEL +extern mi_cmd_argv_ftype mi_cmd_start_telnet_service; +extern mi_cmd_argv_ftype mi_cmd_stop_telnet_service; +#endif /* Description of a single command. */ Index: b/gdb/mi/mi-telnet.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ b/gdb/mi/mi-telnet.c 2011-11-11 16:20:48.582148706 +0300 @@ -0,0 +1,426 @@ +/* MI telnet support + + Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2010, + 2011 Free Software Foundation, Inc. + + Contributed by Mentor Graphics Company + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* + MI telnet support allows remote user to connect to gdb using telnet. + GDB should be built with "--enable-gdbmitel=yes" option to enable MI telnet. + + To Start the service use MI command "-start-telnet-service [port]". + Default port value is 9950. + Service can be stopped by issuing "-stop-telnet-service" command. + + The service requires asynchronous debugging mode. It should be set with + "set target-async on" option. + + The following options are required for interactive commands (like "commands") + and can be turned on directly via telnet on the fly: + "set confirm on" + "set interactive on" +*/ + +#include +#include + +#include "defs.h" +#include "top.h" +#include "mi-cmds.h" +#include "gdb_string.h" +#include "event-loop.h" +#include "interps.h" +#include "main.h" +#include "breakpoint.h" +#include "cli/cli-cmds.h" +#include "cli/cli-decode.h" + +#define DEFAULT_PORT 9950 +#define MAX_CMD_LENGTH 65536 + +/* socket for accepting telnet connections */ +static int telnet_s = -1; + +/* client connection descriptor */ +static int client_fd = -1; + +/* buffer for received command and for intermediate interactive questions */ +static char cmd_buf[MAX_CMD_LENGTH]; +static char cmd_buf_intermediate[MAX_CMD_LENGTH]; + +/* for output redirecting */ +static struct ui_file *str_file = NULL; + +/* + * Send the reply to remote client + */ +static void +reply_msg (const char *msg) +{ + char *result = NULL; + long len; + + if (client_fd != -1 && msg != NULL) { + /* flush additional output if any */ + if (str_file) { + gdb_flush (str_file); + result = ui_file_xstrdup (str_file, &len); + + send (client_fd, result, len, 0); + + xfree (result); + ui_file_rewind (str_file); + } + + send (client_fd, msg, strlen (msg), 0); + } +} + +/* + * Hook for readline to receive input over telnet + */ +static char * +telnet_readline_hook (char *prompt) +{ + long len; + + reply_msg (prompt); + + len = recv (client_fd, cmd_buf_intermediate, MAX_CMD_LENGTH, 0); + if (len <= 0) + return NULL; + + cmd_buf_intermediate [len -1] = cmd_buf_intermediate [len -2] = '\0'; + + return xstrdup(cmd_buf_intermediate); +}; + +/* + * Hook for query to ask remote client for additional 'yes or no' question + */ +static int +telnet_query_hook (const char *msg, va_list argp) +{ + int answer; + int retval = 0; + char *question = NULL; + + gdb_flush (gdb_stdout); + + question = xstrvprintf (msg, argp); + + reply_msg (question); + reply_msg ("(y or n) "); + + answer = recv (client_fd, cmd_buf_intermediate, MAX_CMD_LENGTH, 0); + if (answer <= 0) + return 0; /* 'No' by default */ + + /* Check the first letter in the answer */ + switch (cmd_buf_intermediate[0]) { + case 'y': + case 'Y': + retval = 1; + break; + + case 'n': + case 'N': + default: + retval = 0; + break; + } + + xfree(question); + return retval; +} + +/* + * Add "&" postfix for "run" commands to ensure background execution. + * TODO: Are there any other way to detect blocking commands? + */ +static const char *const async_commands[] = { + "run", + "continue", + "next", + "finish", + "until", + "go", + NULL, +}; + +void +force_async_run_command (char *buffer, int len) +{ + int i; + struct cmd_list_element *c; + char *command; + + /* Is "&" already specified in command? */ + if (strchr (buffer, '&')) + return; + + /* make a copy of the command since lookup_cmd will erase it */ + command = xstrdup (buffer); + + c = lookup_cmd (&command, cmdlist, "", -1, 1); + if (!c) { + xfree (command); + return; + } + + /* find out whether command should be followed by '&' and add it */ + for (i = 0; async_commands[i] != NULL; i++) { + if (!strcmp (async_commands[i], c->name)) { + buffer[len - 2] = ' '; + buffer[len - 1] = '&'; + buffer[len] = '\0'; + } + } +} + +/* + * Read and execute telnet command + */ +static void +telnet_event_handler(int err, gdb_client_data client_data) +{ + long len; + struct gdb_exception e; + char *result = NULL; + struct cleanup *cleanup; + struct interp *old_interp = NULL, *interp_to_use = NULL; + + /* receive remote command */ + len = recv (client_fd, cmd_buf, MAX_CMD_LENGTH, 0); + if (len <= 0) { + /* client disconnected */ + delete_file_handler (client_fd); + close (client_fd); + client_fd = -1; + return; + } + + /* remove CR/LF sent by telnet */ + cmd_buf[len - 1] = cmd_buf[len - 2] = '\0'; + + /* make sure "run" commands are in async mode */ + force_async_run_command(cmd_buf, len); + + /* setup cleanups for input/output */ + cleanup = make_cleanup (null_cleanup, NULL); + make_cleanup_restore_ui_file (&gdb_stdin); + make_cleanup_restore_ui_file (&gdb_stdout); + make_cleanup_restore_ui_file (&gdb_stderr); + make_cleanup_restore_ui_file (&gdb_stdlog); + make_cleanup_restore_ui_file (&gdb_stdtarg); + make_cleanup_restore_ui_file (&gdb_stdtargerr); + + /* execute command in sync, set batch mode to off */ + make_cleanup_restore_integer (&interpreter_async); + make_cleanup_restore_integer (&batch_flag); + interpreter_async = 1; + batch_flag = 0; + + /* figure out current interpreter */ + if (current_interp_named_p (INTERP_MI)) { + old_interp = interp_lookup (INTERP_MI); + } else + if (current_interp_named_p (INTERP_MI1)) { + old_interp = interp_lookup (INTERP_MI1); + } else + if (current_interp_named_p (INTERP_MI2)) { + old_interp = interp_lookup (INTERP_MI2); + } else + if (current_interp_named_p (INTERP_MI3)) { + old_interp = interp_lookup (INTERP_MI3); + }; + + /* switch to console interp */ + interp_to_use = interp_lookup (INTERP_CONSOLE); + if (!interp_set (interp_to_use, 0)) { + reply_msg ("Error: unexpectedly failed to use CLI the interpreter\n"); + do_cleanups (cleanup); + return; + } + + /* first command from remote client, allocate file for output */ + if (!str_file) + str_file = mem_fileopen (); + + /* redirect and catch all input/output into string */ + if (ui_out_redirect (current_uiout, str_file) < 0) + reply_msg ("Error: unexpectedly failed to redirect user interface. No command results will be print\n"); + else + make_cleanup_ui_out_redirect_pop (current_uiout); + + gdb_stdout = str_file; + gdb_stderr = str_file; + gdb_stdlog = str_file; + gdb_stdtarg = str_file; + gdb_stdtargerr = str_file; + + /* Hooks for commands that may ask user for questions + * TODO: remote user still should explicitly do "set confirm on; set interactive on" + * for proper execution of interactive commands (like "commands ") + */ + deprecated_readline_hook = telnet_readline_hook; + deprecated_query_hook = telnet_query_hook; + + /* console interp is properly set up, execute the command */ + TRY_CATCH (e, RETURN_MASK_ALL) + { + execute_command (cmd_buf, 1); + } + + /* do any breakpoint-related stuff */ + bpstat_do_actions (); + + deprecated_readline_hook = 0; + deprecated_query_hook = 0; + + gdb_flush (str_file); + + if (e.reason < 0) { + reply_msg ("Error: "); + reply_msg (e.message); + reply_msg ("\n"); + } + + /* send the reply to the client */ + result = ui_file_xstrdup (str_file, &len); + if (len > 0) { + send (client_fd, result, len, 0); + xfree (result); + } + + /* clear output buffer and restore gdb settings */ + ui_file_rewind (str_file); + do_cleanups (cleanup); + + /* show prompt */ + reply_msg ("\n(gdb) "); + + /* get back to MI interp */ + interp_set (old_interp, 0); +} + +/* + * Handler for the listening socket + * Accepts client connection and registers it within event_loop + */ +static void +telnet_accept_handler(int err, gdb_client_data client_data) +{ + struct sockaddr_in client; + socklen_t len; + struct gdb_exception e; + int s = -1; + + s = accept (telnet_s, (struct sockaddr *)&client, &len); + if (s == -1) { + return; + close (telnet_s); + telnet_s = client_fd = -1; + return; + } + + if (client_fd != -1) { + reply_msg ("Error: another client already connected\n"); + close (s); + return; + } else + client_fd = s; + + /* add handler for the client socket */ + add_file_handler (client_fd, telnet_event_handler, 0); + + /* Remote connection is set up, show prompt */ + reply_msg ("(gdb) "); +} + +/* + * Command to start listening for client on specified port + */ +void +mi_cmd_start_telnet_service (char *command, char **argv, int argc) +{ + struct sockaddr_in serv, client; + int port; + socklen_t len; + + if (telnet_s != -1 || client_fd != -1) + error (_("Error: telnet service already started")); + + if (argc != 0 && argc != 1) + error (_("Usage: -start_telnet_service [port]")); + + if (argc == 1) + port = atoi(argv[0]); + else + port = DEFAULT_PORT; + + if (port <= 0) + error (_("Error: wrong port value: %d"), port); + + /* setup server side of telnet service */ + memset ((char *)&serv, 0, sizeof (serv)); + serv.sin_family = AF_INET; + serv.sin_port = htons (port); + serv.sin_addr.s_addr = htonl (INADDR_ANY); + + telnet_s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (telnet_s == -1) + error (_("Error: socket can't be created")); + + if (bind (telnet_s, (const struct sockaddr *)&serv, sizeof (serv)) == -1) { + close(telnet_s); + telnet_s = -1; + error (_("Error: port %d can't be bind"), port); + } + + /* one client so far allowed */ + if (listen (telnet_s, 1) == -1) { + close(telnet_s); + telnet_s = -1; + error (_("Error: %s"), safe_strerror (errno)); + } + + /* register handler that will accept connections */ + add_file_handler (telnet_s, telnet_accept_handler, 0); +}; + +/* + * Command to stop listening for remote clients + */ +void +mi_cmd_stop_telnet_service (char *command, char **argv, int argc) +{ + if (client_fd != -1) { + delete_file_handler (client_fd); + close (client_fd); + } + + if (telnet_s != -1) { + delete_file_handler (telnet_s); + close (telnet_s); + } + + telnet_s = client_fd = -1; +} +