From a6b151f18761ed51bf9bfc7460a6e4023dd63449 Mon Sep 17 00:00:00 2001 From: Daniel Jacobowitz Date: Fri, 30 Nov 2007 21:50:19 +0000 Subject: [PATCH] * remote.c (remote_cmdlist): New variable. (PACKET_vFile_open, PACKET_vFile_pread, PACKET_vFile_pwrite) (PACKET_vFile_close, PACKET_vFile_unlink): New constants. (remote_buffer_add_string, remote_buffer_add_bytes) (remote_buffer_add_int, remote_hostio_parse_result) (remote_hostio_send_command, remote_hostio_open, remote_hostio_pwrite) (remote_hostio_pread, remote_hostio_close, remote_hostio_unlink) (remote_fileio_errno_to_host, remote_hostio_error, fclose_cleanup) (remote_hostio_close_cleanup, remote_file_put, remote_file_get) (remote_file_delete, remote_put_command, remote_get_command) (remote_delete_command, remote_command): New functions. (_initialize_remote): Register new packets and commands. * Makefile.in (gdb_fileio_h): New variable. (remote.o): Update. (SUBDIR_MI_OBS): Add mi-cmd-target.o. (SUBDIR_MI_SRCS): Add mi/mi-cmd-target.c. (mi-cmd-target.o): New rule. * mi/mi-cmd-target.c: New file. * mi/mi-cmds.c (mi_cmds): Add target-file-delete, target-file-get, and target-file-put. * mi/mi-cmds.h (mi_cmd_target_file_get, mi_cmd_target_file_put) (mi_cmd_target_file_delete): Declare. * remote.h (remote_file_put, remote_file_get, remote_file_delete): Declare. * NEWS: Describe new file transfer support. * gdb.texinfo (Debugging Programs with Multiple Processes): Correct formatting. (Remote Debugging): Add File Transfer section. (Remote Configuration): Document Host I/O packets. (GDB/MI): Add GDB/MI File Transfer Commands section. (Remote Protocol): Add Host I/O Packets section. (Packets): Add vFile. * Makefile.in (OBS): Add hostio.o. (hostio.o): New rule. * server.h (handle_vFile): Declare. * hostio.c: New file. * server.c (handle_v_requests): Take packet_len and new_packet_len for binary packets. Call handle_vFile. (main): Update call to handle_v_requests. * gdb.server/file-transfer.exp, gdb.server/transfer.txt, gdb.mi/mi-file-transfer.exp: New. --- gdb/ChangeLog | 28 + gdb/Makefile.in | 11 +- gdb/NEWS | 25 + gdb/doc/ChangeLog | 10 + gdb/doc/gdb.texinfo | 241 +++++++- gdb/gdbserver/ChangeLog | 10 + gdb/gdbserver/Makefile.in | 3 +- gdb/gdbserver/hostio.c | 517 ++++++++++++++++ gdb/gdbserver/server.c | 11 +- gdb/gdbserver/server.h | 3 + gdb/mi/mi-cmd-target.c | 100 +++ gdb/mi/mi-cmds.c | 3 + gdb/mi/mi-cmds.h | 3 + gdb/remote.c | 668 +++++++++++++++++++++ gdb/remote.h | 6 + gdb/testsuite/ChangeLog | 5 + gdb/testsuite/gdb.mi/mi-file-transfer.exp | 99 +++ gdb/testsuite/gdb.server/file-transfer.exp | 80 +++ gdb/testsuite/gdb.server/transfer.txt | 12 + 19 files changed, 1828 insertions(+), 7 deletions(-) create mode 100644 gdb/gdbserver/hostio.c create mode 100644 gdb/mi/mi-cmd-target.c create mode 100644 gdb/testsuite/gdb.mi/mi-file-transfer.exp create mode 100644 gdb/testsuite/gdb.server/file-transfer.exp create mode 100644 gdb/testsuite/gdb.server/transfer.txt diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 4850791c44..20a73d6d8d 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,31 @@ +2007-11-30 Daniel Jacobowitz + + * remote.c (remote_cmdlist): New variable. + (PACKET_vFile_open, PACKET_vFile_pread, PACKET_vFile_pwrite) + (PACKET_vFile_close, PACKET_vFile_unlink): New constants. + (remote_buffer_add_string, remote_buffer_add_bytes) + (remote_buffer_add_int, remote_hostio_parse_result) + (remote_hostio_send_command, remote_hostio_open, remote_hostio_pwrite) + (remote_hostio_pread, remote_hostio_close, remote_hostio_unlink) + (remote_fileio_errno_to_host, remote_hostio_error, fclose_cleanup) + (remote_hostio_close_cleanup, remote_file_put, remote_file_get) + (remote_file_delete, remote_put_command, remote_get_command) + (remote_delete_command, remote_command): New functions. + (_initialize_remote): Register new packets and commands. + * Makefile.in (gdb_fileio_h): New variable. + (remote.o): Update. + (SUBDIR_MI_OBS): Add mi-cmd-target.o. + (SUBDIR_MI_SRCS): Add mi/mi-cmd-target.c. + (mi-cmd-target.o): New rule. + * mi/mi-cmd-target.c: New file. + * mi/mi-cmds.c (mi_cmds): Add target-file-delete, target-file-get, + and target-file-put. + * mi/mi-cmds.h (mi_cmd_target_file_get, mi_cmd_target_file_put) + (mi_cmd_target_file_delete): Declare. + * remote.h (remote_file_put, remote_file_get, remote_file_delete): + Declare. + * NEWS: Describe new file transfer support. + 2007-11-30 Vladimir Prus * infrun.c (handle_inferior_event): Don't diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 079511462d..309b2e91bc 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -184,7 +184,7 @@ SUBDIR_CLI_CFLAGS= SUBDIR_MI_OBS = \ mi-out.o mi-console.o \ mi-cmds.o mi-cmd-env.o mi-cmd-var.o mi-cmd-break.o mi-cmd-stack.o \ - mi-cmd-file.o mi-cmd-disas.o mi-symbol-cmds.o \ + mi-cmd-file.o mi-cmd-disas.o mi-symbol-cmds.o mi-cmd-target.o \ mi-interp.o \ mi-main.o mi-parse.o mi-getopt.o mi-common.o SUBDIR_MI_SRCS = \ @@ -192,7 +192,7 @@ SUBDIR_MI_SRCS = \ mi/mi-cmds.c mi/mi-cmd-env.c \ mi/mi-cmd-var.c mi/mi-cmd-break.c mi/mi-cmd-stack.c \ mi/mi-cmd-file.c mi/mi-cmd-disas.c mi/mi-symbol-cmds.c \ - mi/mi-interp.c \ + mi/mi-cmd-target.c mi/mi-interp.c \ mi/mi-main.c mi/mi-parse.c mi/mi-getopt.c mi/mi-common.c SUBDIR_MI_DEPS = SUBDIR_MI_LDFLAGS= @@ -675,6 +675,7 @@ mep_desc_h = $(OPCODES_SRC)/mep-desc.h mep_opc_h = $(OPCODES_SRC)/mep-opc.h sh_opc_h = $(OPCODES_SRC)/sh-opc.h gdb_callback_h = $(INCLUDE_DIR)/gdb/callback.h +gdb_fileio_h = $(INCLUDE_DIR)/gdb/fileio.h gdb_sim_arm_h = $(INCLUDE_DIR)/gdb/sim-arm.h gdb_sim_frv_h = $(INCLUDE_DIR)/gdb/sim-frv.h gdb_sim_m32c_h = $(INCLUDE_DIR)/gdb/sim-m32c.h @@ -2602,7 +2603,8 @@ remote.o: remote.c $(defs_h) $(gdb_string_h) $(inferior_h) $(bfd_h) \ $(gdb_stabs_h) $(gdbthread_h) $(remote_h) $(regcache_h) $(value_h) \ $(gdb_assert_h) $(event_loop_h) $(event_top_h) $(inf_loop_h) \ $(serial_h) $(gdbcore_h) $(remote_fileio_h) $(solib_h) $(observer_h) \ - $(cli_decode_h) $(cli_setshow_h) $(memory_map_h) $(target_descriptions_h) + $(cli_decode_h) $(cli_setshow_h) $(memory_map_h) \ + $(target_descriptions_h) $(gdb_fileio_h) remote-fileio.o: remote-fileio.c $(defs_h) $(gdb_string_h) $(gdbcmd_h) \ $(remote_h) $(gdb_fileio_h) $(gdb_wait_h) $(gdb_stat_h) \ $(exceptions_h) $(remote_fileio_h) @@ -3186,6 +3188,9 @@ mi-cmd-stack.o: $(srcdir)/mi/mi-cmd-stack.c $(defs_h) $(target_h) $(frame_h) \ $(value_h) $(mi_cmds_h) $(ui_out_h) $(symtab_h) $(block_h) \ $(stack_h) $(dictionary_h) $(gdb_string_h) $(CC) -c $(INTERNAL_CFLAGS) $(srcdir)/mi/mi-cmd-stack.c +mi-cmd-target.o: $(srcdir)/mi/mi-cmd-target.c $(defs_h) $(mi_cmds_h) \ + $(mi_getopt_h) $(remote_h) + $(CC) -c $(INTERNAL_CFLAGS) $(srcdir)/mi/mi-cmd-target.c mi-cmd-var.o: $(srcdir)/mi/mi-cmd-var.c $(defs_h) $(mi_cmds_h) $(ui_out_h) \ $(mi_out_h) $(varobj_h) $(value_h) $(gdb_string_h) $(CC) -c $(INTERNAL_CFLAGS) $(srcdir)/mi/mi-cmd-var.c diff --git a/gdb/NEWS b/gdb/NEWS index 22283fd72f..d60402f4cd 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -28,6 +28,9 @@ registers on PowerPC targets. * The GDB remote stub, gdbserver, now supports thread debugging on GNU/Linux targets even when the libthread_db library is not available. +* The GDB remote stub, gdbserver, now supports the new file transfer +commands (remote put, remote get, and remote delete). + * hppa*64*-*-hpux11* target broken The debugger is unable to start a program and fails with the following error: "Error trying to get information about dynamic linker". @@ -37,6 +40,28 @@ targets even when the libthread_db library is not available. building a single GDB executable that supports multiple remote target architectures. +* New commands + +remote put +remote get +remote delete + Transfer files to and from a remote target, and delete remote files. + +* New MI commands + +-target-file-put +-target-file-get +-target-file-delete + Transfer files to and from a remote target, and delete remote files. + +* New remote packets + +vFile:open: +vFile:close: +vFile:pread: +vFile:pwrite: +vFile:unlink: + Open, close, read, write, and delete files on the remote system. *** Changes in GDB 6.7 diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 89ac3e665f..181146ec8f 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,13 @@ +2007-11-30 Daniel Jacobowitz + + * gdb.texinfo (Debugging Programs with Multiple Processes): Correct + formatting. + (Remote Debugging): Add File Transfer section. + (Remote Configuration): Document Host I/O packets. + (GDB/MI): Add GDB/MI File Transfer Commands section. + (Remote Protocol): Add Host I/O Packets section. + (Packets): Add vFile. + 2007-11-17 Eli Zaretskii * gdb.texinfo (Set Breaks, Disabling): Clarify behavior of diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index af34740d68..35cc0573c8 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -2480,7 +2480,7 @@ the child process just like any other process which you attached to. On some systems, @value{GDBN} provides support for debugging programs that create additional processes using the @code{fork} or @code{vfork} functions. Currently, the only platforms with this feature are HP-UX (11.x and later -only?) and GNU/Linux (kernel version 2.5.60 and later). +only?) and @sc{gnu}/Linux (kernel version 2.5.60 and later). By default, when a program forks, @value{GDBN} will continue to debug the parent process and the child process will run unimpeded. @@ -12670,6 +12670,7 @@ configuration of @value{GDBN}; use @code{help target} to list them. @menu * Connecting:: Connecting to a remote target +* File Transfer:: Sending files to a remote system * Server:: Using the gdbserver program * Remote Configuration:: Remote configuration * Remote Stub:: Implementing a remote stub @@ -12817,6 +12818,38 @@ can add new commands that only the external monitor will understand and implement. @end table +@node File Transfer +@section Sending files to a remote system +@cindex remote target, file transfer +@cindex file transfer +@cindex sending files to remote systems + +Some remote targets offer the ability to transfer files over the same +connection used to communicate with @value{GDBN}. This is convenient +for targets accessible through other means, e.g.@: @sc{gnu}/Linux systems +running @code{gdbserver} over a network interface. For other targets, +e.g.@: embedded devices with only a single serial port, this may be +the only way to upload or download files. + +Not all remote targets support these commands. + +@table @code +@kindex remote put +@item remote put @var{hostfile} @var{targetfile} +Copy file @var{hostfile} from the host system (the machine running +@value{GDBN}) to @var{targetfile} on the target system. + +@kindex remote get +@item remote get @var{targetfile} @var{hostfile} +Copy file @var{targetfile} from the target system to @var{hostfile} +on the host system. + +@kindex remote delete +@item remote delete @var{targetfile} +Delete @var{targetfile} from the target system. + +@end table + @node Server @section Using the @code{gdbserver} Program @@ -13153,6 +13186,25 @@ are: @tab @code{QPassSignals} @tab @code{handle @var{signal}} +@item @code{hostio-close-packet} +@tab @code{vFile:close} +@tab @code{remote get}, @code{remote put} + +@item @code{hostio-open-packet} +@tab @code{vFile:open} +@tab @code{remote get}, @code{remote put} + +@item @code{hostio-pread-packet} +@tab @code{vFile:pread} +@tab @code{remote get}, @code{remote put} + +@item @code{hostio-pwrite-packet} +@tab @code{vFile:pwrite} +@tab @code{remote get}, @code{remote put} + +@item @code{hostio-unlink-packet} +@tab @code{vFile:unlink} +@tab @code{remote delete} @end multitable @node Remote Stub @@ -17349,6 +17401,7 @@ may repeat one or more times. * GDB/MI Signal Handling Commands:: @end ignore * GDB/MI Target Manipulation:: +* GDB/MI File Transfer Commands:: * GDB/MI Miscellaneous Commands:: @end menu @@ -21295,6 +21348,88 @@ The corresponding @value{GDBN} command is @samp{target}. (gdb) @end smallexample +@c %%%%%%%%%%%%%%%%%%%%%%%%%%%% SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +@node GDB/MI File Transfer Commands +@section @sc{gdb/mi} File Transfer Commands + + +@subheading The @code{-target-file-put} Command +@findex -target-file-put + +@subsubheading Synopsis + +@smallexample + -target-file-put @var{hostfile} @var{targetfile} +@end smallexample + +Copy file @var{hostfile} from the host system (the machine running +@value{GDBN}) to @var{targetfile} on the target system. + +@subsubheading @value{GDBN} Command + +The corresponding @value{GDBN} command is @samp{remote put}. + +@subsubheading Example + +@smallexample +(gdb) +-target-file-put localfile remotefile +^done +(gdb) +@end smallexample + + +@subheading The @code{-target-file-put} Command +@findex -target-file-get + +@subsubheading Synopsis + +@smallexample + -target-file-get @var{targetfile} @var{hostfile} +@end smallexample + +Copy file @var{targetfile} from the target system to @var{hostfile} +on the host system. + +@subsubheading @value{GDBN} Command + +The corresponding @value{GDBN} command is @samp{remote get}. + +@subsubheading Example + +@smallexample +(gdb) +-target-file-get remotefile localfile +^done +(gdb) +@end smallexample + + +@subheading The @code{-target-file-delete} Command +@findex -target-file-delete + +@subsubheading Synopsis + +@smallexample + -target-file-delete @var{targetfile} +@end smallexample + +Delete @var{targetfile} from the target system. + +@subsubheading @value{GDBN} Command + +The corresponding @value{GDBN} command is @samp{remote delete}. + +@subsubheading Example + +@smallexample +(gdb) +-target-file-delete remotefile +^done +(gdb) +@end smallexample + + @c %%%%%%%%%%%%%%%%%%%%%%%%%%%% SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @node GDB/MI Miscellaneous Commands @section Miscellaneous @sc{gdb/mi} Commands @@ -22920,6 +23055,7 @@ Show the current setting of the target wait timeout. * General Query Packets:: * Register Packet Format:: * Tracepoint Packets:: +* Host I/O Packets:: * Interrupts:: * Examples:: * File-I/O Remote Protocol Extension:: @@ -23423,6 +23559,11 @@ command in the @samp{vCont} packet. The @samp{vCont} packet is not supported. @end table +@item vFile:@var{operation}:@var{parameter}@dots{} +@cindex @samp{vFile} packet +Perform a file operation on the target system. For details, +see @ref{Host I/O Packets}. + @item vFlashErase:@var{addr},@var{length} @cindex @samp{vFlashErase} packet Direct the stub to erase @var{length} bytes of flash starting at @@ -24572,6 +24713,104 @@ There is a trace experiment running. @end table +@node Host I/O Packets +@section Host I/O Packets +@cindex Host I/O, remote protocol +@cindex file transfer, remote protocol + +The @dfn{Host I/O} packets allow @value{GDBN} to perform I/O +operations on the far side of a remote link. For example, Host I/O is +used to upload and download files to a remote target with its own +filesystem. Host I/O uses the same constant values and data structure +layout as the target-initiated File-I/O protocol. However, the +Host I/O packets are structured differently. The target-initiated +protocol relies on target memory to store parameters and buffers. +Host I/O requests are initiated by @value{GDBN}, and the +target's memory is not involved. @xref{File-I/O Remote Protocol +Extension}, for more details on the target-initiated protocol. + +The Host I/O request packets all encode a single operation along with +its arguments. They have this format: + +@table @samp + +@item vFile:@var{operation}: @var{parameter}@dots{} +@var{operation} is the name of the particular request; the target +should compare the entire packet name up to the second colon when checking +for a supported operation. The format of @var{parameter} depends on +the operation. Numbers are always passed in hexadecimal. Negative +numbers have an explicit minus sign (i.e.@: two's complement is not +used). Strings (e.g.@: filenames) are encoded as a series of +hexadecimal bytes. The last argument to a system call may be a +buffer of escaped binary data (@pxref{Binary Data}). + +@end table + +The valid responses to Host I/O packets are: + +@table @samp + +@item F @var{result} [, @var{errno}] [; @var{attachment}] +@var{result} is the integer value returned by this operation, usually +non-negative for success and -1 for errors. If an error has occured, +@var{errno} will be included in the result. @var{errno} will have a +value defined by the File-I/O protocol (@pxref{Errno Values}). For +operations which return data, @var{attachment} supplies the data as a +binary buffer. Binary buffers in response packets are escaped in the +normal way (@pxref{Binary Data}). See the individual packet +documentation for the interpretation of @var{result} and +@var{attachment}. + +@item +An empty response indicates that this operation is not recognized. + +@end table + +These are the supported Host I/O operations: + +@table @samp +@item vFile:open: @var{pathname}, @var{flags}, @var{mode} +Open a file at @var{pathname} and return a file descriptor for it, or +return -1 if an error occurs. @var{pathname} is a string, +@var{flags} is an integer indicating a mask of open flags +(@pxref{Open Flags}), and @var{mode} is an integer indicating a mask +of mode bits to use if the file is created (@pxref{mode_t Values}). +@xref{open} for details of the open flags and mode values. + +@item vFile:close: @var{fd} +Close the open file corresponding to @var{fd} and return 0, or +-1 if an error occurs. + +@item vFile:pread: @var{fd}, @var{count}, @var{offset} +Read data from the open file corresponding to @var{fd}. Up to +@var{count} bytes will be read from the file, starting at @var{offset} +relative to the start of the file. The target may read fewer bytes; +common reasons include packet size limits and an end-of-file +condition. The number of bytes read is returned. Zero should only be +returned for a successful read at the end of the file, or if +@var{count} was zero. + +The data read should be returned as a binary attachment on success. +If zero bytes were read, the response should include an empty binary +attachment (i.e.@: a trailing semicolon). The return value is the +number of target bytes read; the binary attachment may be longer if +some characters were escaped. + +@item vFile:pwrite: @var{fd}, @var{offset}, @var{data} +Write @var{data} (a binary buffer) to the open file corresponding +to @var{fd}. Start the write at @var{offset} from the start of the +file. Unlike many @code{write} system calls, there is no +separate @var{count} argument; the length of @var{data} in the +packet is used. @samp{vFile:write} returns the number of bytes written, +which may be shorter than the length of @var{data}, or -1 if an +error occurred. + +@item vFile:unlink: @var{pathname} +Delete the file at @var{pathname} on the target. Return 0, +or -1 if an error occurs. @var{pathname} is a string. + +@end table + @node Interrupts @section Interrupts @cindex interrupts (remote protocol) diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog index 245163bc37..aa1661fec6 100644 --- a/gdb/gdbserver/ChangeLog +++ b/gdb/gdbserver/ChangeLog @@ -1,3 +1,13 @@ +2007-11-30 Daniel Jacobowitz + + * Makefile.in (OBS): Add hostio.o. + (hostio.o): New rule. + * server.h (handle_vFile): Declare. + * hostio.c: New file. + * server.c (handle_v_requests): Take packet_len and new_packet_len + for binary packets. Call handle_vFile. + (main): Update call to handle_v_requests. + 2007-11-05 Daniel Jacobowitz * linux-low.c: Include . diff --git a/gdb/gdbserver/Makefile.in b/gdb/gdbserver/Makefile.in index 9ac7c443a9..9658ce3640 100644 --- a/gdb/gdbserver/Makefile.in +++ b/gdb/gdbserver/Makefile.in @@ -139,7 +139,7 @@ TAGFILES = $(SOURCES) ${HFILES} ${ALLPARAM} ${POSSLIBS} OBS = inferiors.o regcache.o remote-utils.o server.o signals.o target.o \ utils.o version.o \ - mem-break.o \ + mem-break.o hostio.o \ $(XML_BUILTIN) \ $(DEPFILES) GDBSERVER_LIBS = @GDBSERVER_LIBS@ @@ -277,6 +277,7 @@ regcache_h = $(srcdir)/regcache.h server_h = $(srcdir)/server.h $(regcache_h) config.h $(srcdir)/target.h \ $(srcdir)/mem-break.h +hostio.o: hostio.c $(server_h) inferiors.o: inferiors.c $(server_h) mem-break.o: mem-break.c $(server_h) proc-service.o: proc-service.c $(server_h) $(gdb_proc_service_h) diff --git a/gdb/gdbserver/hostio.c b/gdb/gdbserver/hostio.c new file mode 100644 index 0000000000..5160ba9194 --- /dev/null +++ b/gdb/gdbserver/hostio.c @@ -0,0 +1,517 @@ +/* Host file transfer support for gdbserver. + Copyright (C) 2006 + Free Software Foundation, Inc. + + Contributed by CodeSourcery. + + 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 2 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +#include "server.h" +#include "gdb/fileio.h" + +#include +#include +#include +#include + +extern int remote_debug; + +struct fd_list +{ + int fd; + struct fd_list *next; +}; + +static struct fd_list *open_fds; + +static int +safe_fromhex (char a, int *nibble) +{ + if (a >= '0' && a <= '9') + *nibble = a - '0'; + else if (a >= 'a' && a <= 'f') + *nibble = a - 'a' + 10; + else if (a >= 'A' && a <= 'F') + *nibble = a - 'A' + 10; + else + return -1; + + return 0; +} + +static int +require_filename (char **pp, char *filename) +{ + int count; + char *p; + + p = *pp; + count = 0; + + while (*p && *p != ',') + { + int nib1, nib2; + + /* Don't allow overflow. */ + if (count >= PATH_MAX - 1) + return -1; + + if (safe_fromhex (p[0], &nib1) + || safe_fromhex (p[1], &nib2)) + return -1; + + filename[count++] = nib1 * 16 + nib2; + p += 2; + } + + filename[count] = '\0'; + *pp = p; + return 0; +} + +static int +require_int (char **pp, int *value) +{ + char *p; + int count; + + p = *pp; + *value = 0; + count = 0; + + while (*p && *p != ',') + { + int nib; + + /* Don't allow overflow. */ + if (count >= 7) + return -1; + + if (safe_fromhex (p[0], &nib)) + return -1; + *value = *value * 16 + nib; + p++; + count++; + } + + *pp = p; + return 0; +} + +static int +require_data (char *p, int p_len, char **data, int *data_len) +{ + int input_index, output_index, escaped; + + *data = malloc (p_len); + + output_index = 0; + escaped = 0; + for (input_index = 0; input_index < p_len; input_index++) + { + char b = p[input_index]; + + if (escaped) + { + (*data)[output_index++] = b ^ 0x20; + escaped = 0; + } + else if (b == '}') + escaped = 1; + else + (*data)[output_index++] = b; + } + + if (escaped) + return -1; + + *data_len = output_index; + return 0; +} + +static int +require_comma (char **pp) +{ + if (**pp == ',') + { + (*pp)++; + return 0; + } + else + return -1; +} + +static int +require_end (char *p) +{ + if (*p == '\0') + return 0; + else + return -1; +} + +static int +require_valid_fd (int fd) +{ + struct fd_list *fd_ptr; + + for (fd_ptr = open_fds; fd_ptr != NULL; fd_ptr = fd_ptr->next) + if (fd_ptr->fd == fd) + return 0; + + return -1; +} + +static int +errno_to_fileio_errno (int error) +{ + switch (error) + { + case EPERM: + return FILEIO_EPERM; + case ENOENT: + return FILEIO_ENOENT; + case EINTR: + return FILEIO_EINTR; + case EIO: + return FILEIO_EIO; + case EBADF: + return FILEIO_EBADF; + case EACCES: + return FILEIO_EACCES; + case EFAULT: + return FILEIO_EFAULT; + case EBUSY: + return FILEIO_EBUSY; + case EEXIST: + return FILEIO_EEXIST; + case ENODEV: + return FILEIO_ENODEV; + case ENOTDIR: + return FILEIO_ENOTDIR; + case EISDIR: + return FILEIO_EISDIR; + case EINVAL: + return FILEIO_EINVAL; + case ENFILE: + return FILEIO_ENFILE; + case EMFILE: + return FILEIO_EMFILE; + case EFBIG: + return FILEIO_EFBIG; + case ENOSPC: + return FILEIO_ENOSPC; + case ESPIPE: + return FILEIO_ESPIPE; + case EROFS: + return FILEIO_EROFS; + case ENOSYS: + return FILEIO_ENOSYS; + case ENAMETOOLONG: + return FILEIO_ENAMETOOLONG; + } + return FILEIO_EUNKNOWN; +} + +static void +hostio_error (char *own_buf, int error) +{ + int fileio_error = errno_to_fileio_errno (error); + + sprintf (own_buf, "F-1,%x", fileio_error); +} + +static void +hostio_packet_error (char *own_buf) +{ + hostio_error (own_buf, EINVAL); +} + +static void +hostio_reply (char *own_buf, int result) +{ + sprintf (own_buf, "F%x", result); +} + +static int +hostio_reply_with_data (char *own_buf, char *buffer, int len, + int *new_packet_len) +{ + int input_index, output_index, out_maxlen; + + sprintf (own_buf, "F%x;", len); + output_index = strlen (own_buf); + + out_maxlen = PBUFSIZ; + + for (input_index = 0; input_index < len; input_index++) + { + char b = buffer[input_index]; + + if (b == '$' || b == '#' || b == '}' || b == '*') + { + /* These must be escaped. */ + if (output_index + 2 > out_maxlen) + break; + own_buf[output_index++] = '}'; + own_buf[output_index++] = b ^ 0x20; + } + else + { + if (output_index + 1 > out_maxlen) + break; + own_buf[output_index++] = b; + } + } + + *new_packet_len = output_index; + return input_index; +} + +static int +fileio_open_flags_to_host (int fileio_open_flags, int *open_flags_p) +{ + int open_flags = 0; + + if (fileio_open_flags & ~FILEIO_O_SUPPORTED) + return -1; + + if (fileio_open_flags & FILEIO_O_CREAT) + open_flags |= O_CREAT; + if (fileio_open_flags & FILEIO_O_EXCL) + open_flags |= O_EXCL; + if (fileio_open_flags & FILEIO_O_TRUNC) + open_flags |= O_TRUNC; + if (fileio_open_flags & FILEIO_O_APPEND) + open_flags |= O_APPEND; + if (fileio_open_flags & FILEIO_O_RDONLY) + open_flags |= O_RDONLY; + if (fileio_open_flags & FILEIO_O_WRONLY) + open_flags |= O_WRONLY; + if (fileio_open_flags & FILEIO_O_RDWR) + open_flags |= O_RDWR; +/* On systems supporting binary and text mode, always open files in + binary mode. */ +#ifdef O_BINARY + open_flags |= O_BINARY; +#endif + + *open_flags_p = open_flags; + return 0; +} + +static void +handle_open (char *own_buf) +{ + char filename[PATH_MAX]; + char *p; + int fileio_flags, mode, flags, fd; + struct fd_list *new_fd; + + p = own_buf + strlen ("vFile:open:"); + + if (require_filename (&p, filename) + || require_comma (&p) + || require_int (&p, &fileio_flags) + || require_comma (&p) + || require_int (&p, &mode) + || require_end (p) + || fileio_open_flags_to_host (fileio_flags, &flags)) + { + hostio_packet_error (own_buf); + return; + } + + /* We do not need to convert MODE, since the fileio protocol + uses the standard values. */ + fd = open (filename, flags, mode); + + if (fd == -1) + { + hostio_error (own_buf, errno); + return; + } + + /* Record the new file descriptor. */ + new_fd = malloc (sizeof (struct fd_list)); + new_fd->fd = fd; + new_fd->next = open_fds; + open_fds = new_fd; + + hostio_reply (own_buf, fd); +} + +static void +handle_pread (char *own_buf, int *new_packet_len) +{ + int fd, ret, len, offset, bytes_sent; + char *p, *data; + + p = own_buf + strlen ("vFile:pread:"); + + if (require_int (&p, &fd) + || require_comma (&p) + || require_valid_fd (fd) + || require_int (&p, &len) + || require_comma (&p) + || require_int (&p, &offset) + || require_end (p)) + { + hostio_packet_error (own_buf); + return; + } + + data = malloc (len); + ret = pread (fd, data, len, offset); + + if (ret == -1) + { + hostio_error (own_buf, errno); + free (data); + return; + } + + bytes_sent = hostio_reply_with_data (own_buf, data, ret, new_packet_len); + + /* If we were using read, and the data did not all fit in the reply, + we would have to back up using lseek here. With pread it does + not matter. But we still have a problem; the return value in the + packet might be wrong, so we must fix it. This time it will + definitely fit. */ + if (bytes_sent < ret) + bytes_sent = hostio_reply_with_data (own_buf, data, bytes_sent, + new_packet_len); + + free (data); +} + +static void +handle_pwrite (char *own_buf, int packet_len) +{ + int fd, ret, len, offset; + char *p, *data; + + p = own_buf + strlen ("vFile:pwrite:"); + + if (require_int (&p, &fd) + || require_comma (&p) + || require_valid_fd (fd) + || require_int (&p, &offset) + || require_comma (&p) + || require_data (p, packet_len - (p - own_buf), &data, &len)) + { + hostio_packet_error (own_buf); + return; + } + + ret = pwrite (fd, data, len, offset); + + if (ret == -1) + { + hostio_error (own_buf, errno); + free (data); + return; + } + + hostio_reply (own_buf, ret); + free (data); +} + +static void +handle_close (char *own_buf) +{ + int fd, ret; + char *p; + struct fd_list **open_fd_p, *old_fd; + + p = own_buf + strlen ("vFile:close:"); + + if (require_int (&p, &fd) + || require_valid_fd (fd) + || require_end (p)) + { + hostio_packet_error (own_buf); + return; + } + + ret = close (fd); + + if (ret == -1) + { + hostio_error (own_buf, errno); + return; + } + + open_fd_p = &open_fds; + while (*open_fd_p && (*open_fd_p)->fd != fd) + open_fd_p = &(*open_fd_p)->next; + + old_fd = *open_fd_p; + *open_fd_p = (*open_fd_p)->next; + free (old_fd); + + hostio_reply (own_buf, ret); +} + +static void +handle_unlink (char *own_buf) +{ + char filename[PATH_MAX]; + char *p; + int ret; + + p = own_buf + strlen ("vFile:unlink:"); + + if (require_filename (&p, filename) + || require_end (p)) + { + hostio_packet_error (own_buf); + return; + } + + ret = unlink (filename); + + if (ret == -1) + { + hostio_error (own_buf, errno); + return; + } + + hostio_reply (own_buf, ret); +} + +/* Handle all the 'F' file transfer packets. */ + +int +handle_vFile (char *own_buf, int packet_len, int *new_packet_len) +{ + if (strncmp (own_buf, "vFile:open:", 11) == 0) + handle_open (own_buf); + else if (strncmp (own_buf, "vFile:pread:", 11) == 0) + handle_pread (own_buf, new_packet_len); + else if (strncmp (own_buf, "vFile:pwrite:", 12) == 0) + handle_pwrite (own_buf, packet_len); + else if (strncmp (own_buf, "vFile:close:", 12) == 0) + handle_close (own_buf); + else if (strncmp (own_buf, "vFile:unlink:", 13) == 0) + handle_unlink (own_buf); + else + return 0; + + return 1; +} diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c index 991ed15caf..0261e6bdf3 100644 --- a/gdb/gdbserver/server.c +++ b/gdb/gdbserver/server.c @@ -772,7 +772,8 @@ err: /* Handle all of the extended 'v' packets. */ void -handle_v_requests (char *own_buf, char *status, int *signal) +handle_v_requests (char *own_buf, char *status, int *signal, + int packet_len, int *new_packet_len) { if (strncmp (own_buf, "vCont;", 6) == 0) { @@ -786,6 +787,10 @@ handle_v_requests (char *own_buf, char *status, int *signal) return; } + if (strncmp (own_buf, "vFile:", 6) == 0 + && handle_vFile (own_buf, packet_len, new_packet_len)) + return; + /* Otherwise we didn't know what packet it was. Say we didn't understand it. */ own_buf[0] = 0; @@ -1218,8 +1223,10 @@ main (int argc, char *argv[]) } case 'v': /* Extended (long) request. */ - handle_v_requests (own_buf, &status, &signal); + handle_v_requests (own_buf, &status, &signal, + packet_len, &new_packet_len); break; + default: /* It is a request we don't understand. Respond with an empty packet so that gdb knows that we don't support this diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h index d350f44165..54ba614fd9 100644 --- a/gdb/gdbserver/server.h +++ b/gdb/gdbserver/server.h @@ -156,6 +156,9 @@ extern int pass_signals[]; extern jmp_buf toplevel; +/* Functions from hostio.c. */ +extern int handle_vFile (char *, int, int *); + /* From remote-utils.c */ extern int remote_debug; diff --git a/gdb/mi/mi-cmd-target.c b/gdb/mi/mi-cmd-target.c new file mode 100644 index 0000000000..4d33bba4ac --- /dev/null +++ b/gdb/mi/mi-cmd-target.c @@ -0,0 +1,100 @@ +/* MI Command Set - target commands. + Copyright (C) 2007 Free Software Foundation, Inc. + + 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 . */ + +#include "defs.h" +#include "mi-cmds.h" +#include "mi-getopt.h" +#include "remote.h" + +/* Get a file from the target. */ + +enum mi_cmd_result +mi_cmd_target_file_get (char *command, char **argv, int argc) +{ + int optind = 0; + char *optarg; + const char *remote_file, *local_file; + static struct mi_opt opts[] = + { + { 0, 0, 0 } + }; + static const char *prefix = "mi_cmd_target_file_get"; + + if (mi_getopt (prefix, argc, argv, opts, &optind, &optarg) != -1 + || optind != argc - 2) + error (_("mi_cmd_target_file_get: Usage: REMOTE_FILE LOCAL_FILE")); + + remote_file = argv[optind]; + local_file = argv[optind + 1]; + + remote_file_get (remote_file, local_file, 0); + + return MI_CMD_DONE; +} + +/* Send a file to the target. */ + +enum mi_cmd_result +mi_cmd_target_file_put (char *command, char **argv, int argc) +{ + int optind = 0; + char *optarg; + const char *remote_file, *local_file; + static struct mi_opt opts[] = + { + { 0, 0, 0 } + }; + static const char *prefix = "mi_cmd_target_file_put"; + + if (mi_getopt (prefix, argc, argv, opts, &optind, &optarg) != -1 + || optind != argc - 2) + error (_("mi_cmd_target_file_put: Usage: LOCAL_FILE REMOTE_FILE")); + + local_file = argv[optind]; + remote_file = argv[optind + 1]; + + remote_file_put (local_file, remote_file, 0); + + return MI_CMD_DONE; +} + +/* Delete a file on the target. */ + +enum mi_cmd_result +mi_cmd_target_file_delete (char *command, char **argv, int argc) +{ + int optind = 0; + char *optarg; + const char *remote_file, *local_file; + static struct mi_opt opts[] = + { + { 0, 0, 0 } + }; + static const char *prefix = "mi_cmd_target_file_delete"; + + if (mi_getopt (prefix, argc, argv, opts, &optind, &optarg) != -1 + || optind != argc - 1) + error (_("mi_cmd_target_file_delete: Usage: REMOTE_FILE")); + + remote_file = argv[optind]; + + remote_file_delete (remote_file, 0); + + return MI_CMD_DONE; +} + diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index 5bd8725e9c..4082d89bb9 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -123,6 +123,9 @@ struct mi_cmd mi_cmds[] = { "target-disconnect", { "disconnect", 0 }, 0 }, { "target-download", { NULL, 0 }, mi_cmd_target_download}, { "target-exec-status", { NULL, 0 }, NULL, NULL }, + { "target-file-delete", { NULL, 0 }, NULL, mi_cmd_target_file_delete }, + { "target-file-get", { NULL, 0 }, NULL, mi_cmd_target_file_get }, + { "target-file-put", { NULL, 0 }, NULL, mi_cmd_target_file_put }, { "target-list-available-targets", { NULL, 0 }, NULL, NULL }, { "target-list-current-targets", { NULL, 0 }, NULL, NULL }, { "target-list-parameters", { NULL, 0 }, NULL, NULL }, diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index 9888c85fb8..9ea08e8f57 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -100,6 +100,9 @@ extern mi_cmd_argv_ftype mi_cmd_stack_list_locals; extern mi_cmd_argv_ftype mi_cmd_stack_select_frame; extern mi_cmd_argv_ftype mi_cmd_symbol_list_lines; extern mi_cmd_args_ftype mi_cmd_target_download; +extern mi_cmd_argv_ftype mi_cmd_target_file_get; +extern mi_cmd_argv_ftype mi_cmd_target_file_put; +extern mi_cmd_argv_ftype mi_cmd_target_file_delete; extern mi_cmd_args_ftype mi_cmd_target_select; extern mi_cmd_argv_ftype mi_cmd_thread_list_ids; extern mi_cmd_argv_ftype mi_cmd_thread_select; diff --git a/gdb/remote.c b/gdb/remote.c index 2d8a88b814..ec7044e5eb 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -58,6 +58,7 @@ #include "gdbcore.h" /* for exec_bfd */ #include "remote-fileio.h" +#include "gdb/fileio.h" #include "memory-map.h" @@ -207,6 +208,10 @@ static void show_remote_protocol_packet_cmd (struct ui_file *file, void _initialize_remote (void); +/* For "remote". */ + +static struct cmd_list_element *remote_cmdlist; + /* For "set remote" and "show remote". */ static struct cmd_list_element *remote_set_cmdlist; @@ -901,6 +906,11 @@ enum { PACKET_Z2, PACKET_Z3, PACKET_Z4, + PACKET_vFile_open, + PACKET_vFile_pread, + PACKET_vFile_pwrite, + PACKET_vFile_close, + PACKET_vFile_unlink, PACKET_qXfer_auxv, PACKET_qXfer_features, PACKET_qXfer_libraries, @@ -6276,6 +6286,631 @@ remote_read_description (struct target_ops *target) return NULL; } +/* Remote file transfer support. This is host-initiated I/O, not + target-initiated; for target-initiated, see remote-fileio.c. */ + +/* If *LEFT is at least the length of STRING, copy STRING to + *BUFFER, update *BUFFER to point to the new end of the buffer, and + decrease *LEFT. Otherwise raise an error. */ + +static void +remote_buffer_add_string (char **buffer, int *left, char *string) +{ + int len = strlen (string); + + if (len > *left) + error (_("Packet too long for target.")); + + memcpy (*buffer, string, len); + *buffer += len; + *left -= len; + + /* NUL-terminate the buffer as a convenience, if there is + room. */ + if (*left) + **buffer = '\0'; +} + +/* If *LEFT is large enough, hex encode LEN bytes from BYTES into + *BUFFER, update *BUFFER to point to the new end of the buffer, and + decrease *LEFT. Otherwise raise an error. */ + +static void +remote_buffer_add_bytes (char **buffer, int *left, const gdb_byte *bytes, + int len) +{ + if (2 * len > *left) + error (_("Packet too long for target.")); + + bin2hex (bytes, *buffer, len); + *buffer += 2 * len; + *left -= 2 * len; + + /* NUL-terminate the buffer as a convenience, if there is + room. */ + if (*left) + **buffer = '\0'; +} + +/* If *LEFT is large enough, convert VALUE to hex and add it to + *BUFFER, update *BUFFER to point to the new end of the buffer, and + decrease *LEFT. Otherwise raise an error. */ + +static void +remote_buffer_add_int (char **buffer, int *left, ULONGEST value) +{ + int len = hexnumlen (value); + + if (len > *left) + error (_("Packet too long for target.")); + + hexnumstr (*buffer, value); + *buffer += len; + *left -= len; + + /* NUL-terminate the buffer as a convenience, if there is + room. */ + if (*left) + **buffer = '\0'; +} + +/* Parse an I/O result packet from BUFFER. Set RETCODE to the return + value, *REMOTE_ERRNO to the remote error number or zero if none + was included, and *ATTACHMENT to point to the start of the annex + if any. The length of the packet isn't needed here; there may + be NUL bytes in BUFFER, but they will be after *ATTACHMENT. + + Return 0 if the packet could be parsed, -1 if it could not. If + -1 is returned, the other variables may not be initialized. */ + +static int +remote_hostio_parse_result (char *buffer, int *retcode, + int *remote_errno, char **attachment) +{ + char *p, *p2; + + *remote_errno = 0; + *attachment = NULL; + + if (buffer[0] != 'F') + return -1; + + errno = 0; + *retcode = strtol (&buffer[1], &p, 16); + if (errno != 0 || p == &buffer[1]) + return -1; + + /* Check for ",errno". */ + if (*p == ',') + { + errno = 0; + *remote_errno = strtol (p + 1, &p2, 16); + if (errno != 0 || p + 1 == p2) + return -1; + p = p2; + } + + /* Check for ";attachment". If there is no attachment, the + packet should end here. */ + if (*p == ';') + { + *attachment = p + 1; + return 0; + } + else if (*p == '\0') + return 0; + else + return -1; +} + +/* Send a prepared I/O packet to the target and read its response. + The prepared packet is in the global RS->BUF before this function + is called, and the answer is there when we return. + + COMMAND_BYTES is the length of the request to send, which may include + binary data. WHICH_PACKET is the packet configuration to check + before attempting a packet. If an error occurs, *REMOTE_ERRNO + is set to the error number and -1 is returned. Otherwise the value + returned by the function is returned. + + ATTACHMENT and ATTACHMENT_LEN should be non-NULL if and only if an + attachment is expected; an error will be reported if there's a + mismatch. If one is found, *ATTACHMENT will be set to point into + the packet buffer and *ATTACHMENT_LEN will be set to the + attachment's length. */ + +static int +remote_hostio_send_command (int command_bytes, int which_packet, + int *remote_errno, char **attachment, + int *attachment_len) +{ + struct remote_state *rs = get_remote_state (); + int ret, bytes_read; + char *attachment_tmp; + + if (remote_protocol_packets[which_packet].support == PACKET_DISABLE) + { + *remote_errno = FILEIO_ENOSYS; + return -1; + } + + putpkt_binary (rs->buf, command_bytes); + bytes_read = getpkt_sane (&rs->buf, &rs->buf_size, 0); + + /* If it timed out, something is wrong. Don't try to parse the + buffer. */ + if (bytes_read < 0) + { + *remote_errno = FILEIO_EINVAL; + return -1; + } + + switch (packet_ok (rs->buf, &remote_protocol_packets[which_packet])) + { + case PACKET_ERROR: + *remote_errno = FILEIO_EINVAL; + return -1; + case PACKET_UNKNOWN: + *remote_errno = FILEIO_ENOSYS; + return -1; + case PACKET_OK: + break; + } + + if (remote_hostio_parse_result (rs->buf, &ret, remote_errno, + &attachment_tmp)) + { + *remote_errno = FILEIO_EINVAL; + return -1; + } + + /* Make sure we saw an attachment if and only if we expected one. */ + if ((attachment_tmp == NULL && attachment != NULL) + || (attachment_tmp != NULL && attachment == NULL)) + { + *remote_errno = FILEIO_EINVAL; + return -1; + } + + /* If an attachment was found, it must point into the packet buffer; + work out how many bytes there were. */ + if (attachment_tmp != NULL) + { + *attachment = attachment_tmp; + *attachment_len = bytes_read - (*attachment - rs->buf); + } + + return ret; +} + +/* Open FILENAME on the remote target, using FLAGS and MODE. Return a + remote file descriptor, or -1 if an error occurs (and set + *REMOTE_ERRNO). */ + +static int +remote_hostio_open (const char *filename, int flags, int mode, + int *remote_errno) +{ + struct remote_state *rs = get_remote_state (); + char *p = rs->buf; + int left = get_remote_packet_size () - 1; + + remote_buffer_add_string (&p, &left, "vFile:open:"); + + remote_buffer_add_bytes (&p, &left, (const gdb_byte *) filename, + strlen (filename)); + remote_buffer_add_string (&p, &left, ","); + + remote_buffer_add_int (&p, &left, flags); + remote_buffer_add_string (&p, &left, ","); + + remote_buffer_add_int (&p, &left, mode); + + return remote_hostio_send_command (p - rs->buf, PACKET_vFile_open, + remote_errno, NULL, NULL); +} + +/* Write up to LEN bytes from WRITE_BUF to FD on the remote target. + Return the number of bytes written, or -1 if an error occurs (and + set *REMOTE_ERRNO). */ + +static int +remote_hostio_pwrite (int fd, const gdb_byte *write_buf, int len, + ULONGEST offset, int *remote_errno) +{ + struct remote_state *rs = get_remote_state (); + char *p = rs->buf; + int left = get_remote_packet_size (); + int out_len; + + remote_buffer_add_string (&p, &left, "vFile:pwrite:"); + + remote_buffer_add_int (&p, &left, fd); + remote_buffer_add_string (&p, &left, ","); + + remote_buffer_add_int (&p, &left, offset); + remote_buffer_add_string (&p, &left, ","); + + p += remote_escape_output (write_buf, len, p, &out_len, + get_remote_packet_size () - (p - rs->buf)); + + return remote_hostio_send_command (p - rs->buf, PACKET_vFile_pwrite, + remote_errno, NULL, NULL); +} + +/* Read up to LEN bytes FD on the remote target into READ_BUF + Return the number of bytes read, or -1 if an error occurs (and + set *REMOTE_ERRNO). */ + +static int +remote_hostio_pread (int fd, gdb_byte *read_buf, int len, + ULONGEST offset, int *remote_errno) +{ + struct remote_state *rs = get_remote_state (); + char *p = rs->buf; + char *attachment; + int left = get_remote_packet_size (); + int ret, attachment_len; + int read_len; + + remote_buffer_add_string (&p, &left, "vFile:pread:"); + + remote_buffer_add_int (&p, &left, fd); + remote_buffer_add_string (&p, &left, ","); + + remote_buffer_add_int (&p, &left, len); + remote_buffer_add_string (&p, &left, ","); + + remote_buffer_add_int (&p, &left, offset); + + ret = remote_hostio_send_command (p - rs->buf, PACKET_vFile_pread, + remote_errno, &attachment, + &attachment_len); + + if (ret < 0) + return ret; + + read_len = remote_unescape_input (attachment, attachment_len, + read_buf, len); + if (read_len != ret) + error (_("Read returned %d, but %d bytes."), ret, (int) read_len); + + return ret; +} + +/* Close FD on the remote target. Return 0, or -1 if an error occurs + (and set *REMOTE_ERRNO). */ + +static int +remote_hostio_close (int fd, int *remote_errno) +{ + struct remote_state *rs = get_remote_state (); + char *p = rs->buf; + int left = get_remote_packet_size () - 1; + + remote_buffer_add_string (&p, &left, "vFile:close:"); + + remote_buffer_add_int (&p, &left, fd); + + return remote_hostio_send_command (p - rs->buf, PACKET_vFile_close, + remote_errno, NULL, NULL); +} + +/* Unlink FILENAME on the remote target. Return 0, or -1 if an error + occurs (and set *REMOTE_ERRNO). */ + +static int +remote_hostio_unlink (const char *filename, int *remote_errno) +{ + struct remote_state *rs = get_remote_state (); + char *p = rs->buf; + int left = get_remote_packet_size () - 1; + + remote_buffer_add_string (&p, &left, "vFile:unlink:"); + + remote_buffer_add_bytes (&p, &left, (const gdb_byte *) filename, + strlen (filename)); + + return remote_hostio_send_command (p - rs->buf, PACKET_vFile_unlink, + remote_errno, NULL, NULL); +} + +static int +remote_fileio_errno_to_host (int errnum) +{ + switch (errnum) + { + case FILEIO_EPERM: + return EPERM; + case FILEIO_ENOENT: + return ENOENT; + case FILEIO_EINTR: + return EINTR; + case FILEIO_EIO: + return EIO; + case FILEIO_EBADF: + return EBADF; + case FILEIO_EACCES: + return EACCES; + case FILEIO_EFAULT: + return EFAULT; + case FILEIO_EBUSY: + return EBUSY; + case FILEIO_EEXIST: + return EEXIST; + case FILEIO_ENODEV: + return ENODEV; + case FILEIO_ENOTDIR: + return ENOTDIR; + case FILEIO_EISDIR: + return EISDIR; + case FILEIO_EINVAL: + return EINVAL; + case FILEIO_ENFILE: + return ENFILE; + case FILEIO_EMFILE: + return EMFILE; + case FILEIO_EFBIG: + return EFBIG; + case FILEIO_ENOSPC: + return ENOSPC; + case FILEIO_ESPIPE: + return ESPIPE; + case FILEIO_EROFS: + return EROFS; + case FILEIO_ENOSYS: + return ENOSYS; + case FILEIO_ENAMETOOLONG: + return ENAMETOOLONG; + } + return -1; +} + +static char * +remote_hostio_error (int errnum) +{ + int host_error = remote_fileio_errno_to_host (errnum); + + if (host_error == -1) + error (_("Unknown remote I/O error %d"), errnum); + else + error (_("Remote I/O error: %s"), safe_strerror (host_error)); +} + +static void +fclose_cleanup (void *file) +{ + fclose (file); +} + +static void +remote_hostio_close_cleanup (void *opaque) +{ + int fd = *(int *) opaque; + int remote_errno; + + remote_hostio_close (fd, &remote_errno); +} + +void +remote_file_put (const char *local_file, const char *remote_file, int from_tty) +{ + struct cleanup *back_to, *close_cleanup; + int retcode, fd, remote_errno, bytes, io_size; + FILE *file; + gdb_byte *buffer; + int bytes_in_buffer; + int saw_eof; + ULONGEST offset; + + if (!remote_desc) + error (_("command can only be used with remote target")); + + file = fopen (local_file, "rb"); + if (file == NULL) + perror_with_name (local_file); + back_to = make_cleanup (fclose_cleanup, file); + + fd = remote_hostio_open (remote_file, (FILEIO_O_WRONLY | FILEIO_O_CREAT + | FILEIO_O_TRUNC), + 0700, &remote_errno); + if (fd == -1) + remote_hostio_error (remote_errno); + + /* Send up to this many bytes at once. They won't all fit in the + remote packet limit, so we'll transfer slightly fewer. */ + io_size = get_remote_packet_size (); + buffer = xmalloc (io_size); + make_cleanup (xfree, buffer); + + close_cleanup = make_cleanup (remote_hostio_close_cleanup, &fd); + + bytes_in_buffer = 0; + saw_eof = 0; + offset = 0; + while (bytes_in_buffer || !saw_eof) + { + if (!saw_eof) + { + bytes = fread (buffer + bytes_in_buffer, 1, io_size - bytes_in_buffer, + file); + if (bytes == 0) + { + if (ferror (file)) + error (_("Error reading %s."), local_file); + else + { + /* EOF. Unless there is something still in the + buffer from the last iteration, we are done. */ + saw_eof = 1; + if (bytes_in_buffer == 0) + break; + } + } + } + else + bytes = 0; + + bytes += bytes_in_buffer; + bytes_in_buffer = 0; + + retcode = remote_hostio_pwrite (fd, buffer, bytes, offset, &remote_errno); + + if (retcode < 0) + remote_hostio_error (remote_errno); + else if (retcode == 0) + error (_("Remote write of %d bytes returned 0!"), bytes); + else if (retcode < bytes) + { + /* Short write. Save the rest of the read data for the next + write. */ + bytes_in_buffer = bytes - retcode; + memmove (buffer, buffer + retcode, bytes_in_buffer); + } + + offset += retcode; + } + + discard_cleanups (close_cleanup); + if (remote_hostio_close (fd, &remote_errno)) + remote_hostio_error (remote_errno); + + if (from_tty) + printf_filtered (_("Successfully sent file \"%s\".\n"), local_file); + do_cleanups (back_to); +} + +void +remote_file_get (const char *remote_file, const char *local_file, int from_tty) +{ + struct cleanup *back_to, *close_cleanup; + int retcode, fd, remote_errno, bytes, io_size; + FILE *file; + gdb_byte *buffer; + ULONGEST offset; + + if (!remote_desc) + error (_("command can only be used with remote target")); + + fd = remote_hostio_open (remote_file, FILEIO_O_RDONLY, 0, &remote_errno); + if (fd == -1) + remote_hostio_error (remote_errno); + + file = fopen (local_file, "wb"); + if (file == NULL) + perror_with_name (local_file); + back_to = make_cleanup (fclose_cleanup, file); + + /* Send up to this many bytes at once. They won't all fit in the + remote packet limit, so we'll transfer slightly fewer. */ + io_size = get_remote_packet_size (); + buffer = xmalloc (io_size); + make_cleanup (xfree, buffer); + + close_cleanup = make_cleanup (remote_hostio_close_cleanup, &fd); + + offset = 0; + while (1) + { + bytes = remote_hostio_pread (fd, buffer, io_size, offset, &remote_errno); + if (bytes == 0) + /* Success, but no bytes, means end-of-file. */ + break; + if (bytes == -1) + remote_hostio_error (remote_errno); + + offset += bytes; + + bytes = fwrite (buffer, 1, bytes, file); + if (bytes == 0) + perror_with_name (local_file); + } + + discard_cleanups (close_cleanup); + if (remote_hostio_close (fd, &remote_errno)) + remote_hostio_error (remote_errno); + + if (from_tty) + printf_filtered (_("Successfully fetched file \"%s\".\n"), remote_file); + do_cleanups (back_to); +} + +void +remote_file_delete (const char *remote_file, int from_tty) +{ + int retcode, remote_errno; + + if (!remote_desc) + error (_("command can only be used with remote target")); + + retcode = remote_hostio_unlink (remote_file, &remote_errno); + if (retcode == -1) + remote_hostio_error (remote_errno); + + if (from_tty) + printf_filtered (_("Successfully deleted file \"%s\".\n"), remote_file); +} + +static void +remote_put_command (char *args, int from_tty) +{ + struct cleanup *back_to; + char **argv; + + argv = buildargv (args); + if (argv == NULL) + nomem (0); + back_to = make_cleanup_freeargv (argv); + if (argv[0] == NULL || argv[1] == NULL || argv[2] != NULL) + error (_("Invalid parameters to remote put")); + + remote_file_put (argv[0], argv[1], from_tty); + + do_cleanups (back_to); +} + +static void +remote_get_command (char *args, int from_tty) +{ + struct cleanup *back_to; + char **argv; + + argv = buildargv (args); + if (argv == NULL) + nomem (0); + back_to = make_cleanup_freeargv (argv); + if (argv[0] == NULL || argv[1] == NULL || argv[2] != NULL) + error (_("Invalid parameters to remote get")); + + remote_file_get (argv[0], argv[1], from_tty); + + do_cleanups (back_to); +} + +static void +remote_delete_command (char *args, int from_tty) +{ + struct cleanup *back_to; + char **argv; + + argv = buildargv (args); + if (argv == NULL) + nomem (0); + back_to = make_cleanup_freeargv (argv); + if (argv[0] == NULL || argv[1] != NULL) + error (_("Invalid parameters to remote delete")); + + remote_file_delete (argv[0], from_tty); + + do_cleanups (back_to); +} + +static void +remote_command (char *args, int from_tty) +{ + help_list (remote_cmdlist, "remote ", -1, gdb_stdout); +} + static void init_remote_ops (void) { @@ -6719,6 +7354,21 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL, add_packet_config_cmd (&remote_protocol_packets[PACKET_qSupported], "qSupported", "supported-packets", 0); + add_packet_config_cmd (&remote_protocol_packets[PACKET_vFile_open], + "vFile:open", "hostio-open", 0); + + add_packet_config_cmd (&remote_protocol_packets[PACKET_vFile_pread], + "vFile:pread", "hostio-pread", 0); + + add_packet_config_cmd (&remote_protocol_packets[PACKET_vFile_pwrite], + "vFile:pwrite", "hostio-pwrite", 0); + + add_packet_config_cmd (&remote_protocol_packets[PACKET_vFile_close], + "vFile:close", "hostio-close", 0); + + add_packet_config_cmd (&remote_protocol_packets[PACKET_vFile_unlink], + "vFile:unlink", "hostio-unlink", 0); + /* Keep the old ``set remote Z-packet ...'' working. Each individual Z sub-packet has its own set and show commands, but users may have sets to this variable in their .gdbinit files (or in their @@ -6733,6 +7383,24 @@ packets."), show_remote_protocol_Z_packet_cmd, /* FIXME: i18n: Use of remote protocol `Z' packets is %s. */ &remote_set_cmdlist, &remote_show_cmdlist); + add_prefix_cmd ("remote", class_files, remote_command, _("\ +Manipulate files on the remote system\n\ +Transfer files to and from the remote target system."), + &remote_cmdlist, "remote ", + 0 /* allow-unknown */, &cmdlist); + + add_cmd ("put", class_files, remote_put_command, + _("Copy a local file to the remote system."), + &remote_cmdlist); + + add_cmd ("get", class_files, remote_get_command, + _("Copy a remote file to the local system."), + &remote_cmdlist); + + add_cmd ("delete", class_files, remote_delete_command, + _("Delete a remote file."), + &remote_cmdlist); + /* Eventually initialize fileio. See fileio.c */ initialize_remote_fileio (remote_set_cmdlist, remote_show_cmdlist); } diff --git a/gdb/remote.h b/gdb/remote.h index 7ba4e492ce..f3ec8d7e66 100644 --- a/gdb/remote.h +++ b/gdb/remote.h @@ -67,4 +67,10 @@ extern void (*deprecated_target_wait_loop_hook) (void); void register_remote_g_packet_guess (struct gdbarch *gdbarch, int bytes, const struct target_desc *tdesc); +void remote_file_put (const char *local_file, const char *remote_file, + int from_tty); +void remote_file_get (const char *remote_file, const char *local_file, + int from_tty); +void remote_file_delete (const char *remote_file, int from_tty); + #endif diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 6385c74dfb..9c0f50568a 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2007-11-30 Daniel Jacobowitz + + * gdb.server/file-transfer.exp, gdb.server/transfer.txt, + gdb.mi/mi-file-transfer.exp: New. + 2007-11-25 Jan Kratochvil * gdb.dwarf2/dw2-ranges.S, gdb.dwarf2/dw2-ranges.exp: New files. diff --git a/gdb/testsuite/gdb.mi/mi-file-transfer.exp b/gdb/testsuite/gdb.mi/mi-file-transfer.exp new file mode 100644 index 0000000000..3c065640c7 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-file-transfer.exp @@ -0,0 +1,99 @@ +# This testcase is part of GDB, the GNU debugger. +# Copyright 2007 Free Software Foundation, Inc. + +# 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 . + +# Test gdbserver monitor commands. + +load_lib gdbserver-support.exp +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +if { [skip_gdbserver_tests] } { + return 0 +} + +set testfile "basics" +set srcfile ${testfile}.c +set binfile ${objdir}/${subdir}/${testfile} +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } { + untested mi-file-transfer.exp + return -1 +} + +gdb_exit +if [mi_gdb_start] { + continue +} +mi_delete_breakpoints +mi_gdb_reinitialize_dir $srcdir/$subdir +mi_gdb_file_cmd ${binfile} + +proc mi_gdbserver_run { } { + mi_gdb_test "kill" ".*" "" + + set res [gdbserver_spawn ""] + set protocol [lindex $res 0] + set gdbport [lindex $res 1] + + if { [mi_gdb_target_cmd $protocol $gdbport] != 0 } { + return -1 + } + + return 0 +} + +proc test_file_transfer { filename description } { + mi_gdb_test "-target-file-put \"$filename\" \"down-server\"" \ + "\\^done" "put $description" + mi_gdb_test "-target-file-get \"down-server\" \"up-server\"" \ + "\\^done" "get $description" + + if { ![is_remote target] } { + # If we can check the target copy of the file, do that too. + # This should catch symmetric errors in upload and download. + set result [remote_exec host "cmp -s $filename down-server"] + if { [lindex $result 0] == 0 } { + pass "compare intermediate $description" + } else { + fail "compare intermediate $description" + } + } + + set result [remote_exec host "cmp -s $filename up-server"] + if { [lindex $result 0] == 0 } { + pass "compare $description" + } else { + fail "compare $description" + } + + mi_gdb_test "-target-file-delete \"down-server\"" \ + "\\^done" "deleted $description" + + if { ![is_remote target] } { + if { ! [remote_file target exists down-server] } { + pass "verified deleted $description" + } else { + fail "verified deleted $description" + } + } + + catch { file delete up-server } +} + +mi_gdbserver_run + +test_file_transfer "$binfile" "binary file" + +mi_gdb_exit diff --git a/gdb/testsuite/gdb.server/file-transfer.exp b/gdb/testsuite/gdb.server/file-transfer.exp new file mode 100644 index 0000000000..6450a5dd7f --- /dev/null +++ b/gdb/testsuite/gdb.server/file-transfer.exp @@ -0,0 +1,80 @@ +# This testcase is part of GDB, the GNU debugger. +# Copyright 2007 Free Software Foundation, Inc. + +# 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 . + +# Test gdbserver monitor commands. + +load_lib gdbserver-support.exp + +set testfile "server" +set srcfile ${testfile}.c +set binfile ${objdir}/${subdir}/${testfile} + +if { [skip_gdbserver_tests] } { + return 0 +} + +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } { + untested file-transfer.exp + return -1 +} + +gdb_exit +gdb_start +gdb_load $binfile +gdb_reinitialize_dir $srcdir/$subdir + +gdbserver_run "" + +proc test_file_transfer { filename description } { + gdb_test "remote put \"$filename\" down-server" \ + "Successfully sent .*" "put $description" + gdb_test "remote get down-server up-server" \ + "Successfully fetched .*" "get $description" + + if { ![is_remote target] } { + # If we can check the target copy of the file, do that too. + # This should catch symmetric errors in upload and download. + set result [remote_exec host "cmp -s $filename down-server"] + if { [lindex $result 0] == 0 } { + pass "compare intermediate $description" + } else { + fail "compare intermediate $description" + } + } + + set result [remote_exec host "cmp -s $filename up-server"] + if { [lindex $result 0] == 0 } { + pass "compare $description" + } else { + fail "compare $description" + } + + gdb_test "remote delete down-server" \ + "Successfully deleted .*" "deleted $description" + + if { ![is_remote target] } { + if { ! [remote_file target exists down-server] } { + pass "verified deleted $description" + } else { + fail "verified deleted $description" + } + } + + catch { file delete up-server } +} + +test_file_transfer "$binfile" "binary file" +test_file_transfer "$srcdir/$subdir/transfer.txt" "text file" diff --git a/gdb/testsuite/gdb.server/transfer.txt b/gdb/testsuite/gdb.server/transfer.txt new file mode 100644 index 0000000000..566f4c5e23 --- /dev/null +++ b/gdb/testsuite/gdb.server/transfer.txt @@ -0,0 +1,12 @@ +This text file is a test input for GDB's file transfer commands. It +contains some characters which need to be escaped in remote protocol +packets, like "*" and "}" and "$" and "#". Actually, it contains +a good sampling of printable characters: + +!"#$%&'()*+,-./0123456789:;<=>?@ +ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` +abcdefghijklmnopqrstuvwxyz{|}~ + +!"#$%&'()*+,-./0123456789:;<=>?@ +ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` +abcdefghijklmnopqrstuvwxyz{|}~ -- 2.34.1