diff options
Diffstat (limited to 'gdb/remote.c')
-rw-r--r-- | gdb/remote.c | 746 |
1 files changed, 583 insertions, 163 deletions
diff --git a/gdb/remote.c b/gdb/remote.c index 7dc057c..858076b 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -19,9 +19,9 @@ /* See the GDB User Guide for details of the GDB remote protocol. */ -#include <ctype.h> #include <fcntl.h> #include "exceptions.h" +#include "gdbsupport/common-inferior.h" #include "inferior.h" #include "infrun.h" #include "bfd.h" @@ -80,6 +80,8 @@ #include "async-event.h" #include "gdbsupport/selftest.h" #include "cli/cli-style.h" +#include "gdbsupport/remote-args.h" +#include "gdbsupport/gdb_argv_vec.h" /* The remote target. */ @@ -250,6 +252,7 @@ enum { PACKET_vFile_readlink, PACKET_vFile_fstat, PACKET_vFile_stat, + PACKET_vFile_lstat, PACKET_qXfer_auxv, PACKET_qXfer_features, PACKET_qXfer_exec_file, @@ -399,6 +402,13 @@ enum { errors, and so they should not need to check for this feature. */ PACKET_accept_error_message, + /* Not really a packet; this indicates support for sending the vRun + inferior arguments as a single string. */ + PACKET_vRun_single_argument, + + /* Support the qExecAndArgs packet. */ + PACKET_qExecAndArgs, + PACKET_MAX }; @@ -824,6 +834,11 @@ struct remote_features bool remote_memory_tagging_p () const { return packet_support (PACKET_memory_tagging_feature) == PACKET_ENABLE; } + /* Returns true if there is support for sending vRun inferior arguments + as a single string. */ + bool remote_vrun_single_arg_p () const + { return packet_support (PACKET_vRun_single_argument) == PACKET_ENABLE; } + /* Reset all packets back to "unknown support". Called when opening a new connection to a remote target. */ void reset_all_packet_configs_support (); @@ -843,6 +858,84 @@ struct remote_features packet_config m_protocol_packets[PACKET_MAX]; }; +/* Data structure used to hold the results of the qExecAndArgs packet. */ + +struct remote_exec_and_args_info +{ + /* The result state reflects whether the packet is supported by the + remote target, and then which bits of state the remote target actually + returned to GDB . */ + enum class state + { + /* The remote does not support the qExecAndArgs packet. GDB + should not make assumptions about the remote target's executable + and arguments. */ + UNKNOWN, + + /* The remote does understand the qExecAndArgs, no executable + (and/or arguments) were set at the remote end. If GDB wants the + remote to start an inferior it will need to provide this information. */ + UNSET, + + /* The remote does understand the qExecAndArgs, an executable + and/or arguments were set at the remote end and this information is + held within this object. */ + SET + }; + + /* Create an empty instance, STATE should be state::UNKNOWN or + state::UNSET only. */ + explicit remote_exec_and_args_info (state state = state::UNKNOWN) + : m_state (state) + { + gdb_assert (m_state != state::SET); + } + + /* Create an instance in state::SET, move EXEC and ARGS into this + instance. */ + explicit remote_exec_and_args_info (std::string &&exec, std::string &&args) + : m_state (state::SET), + m_exec (std::move (exec)), + m_args (std::move (args)) + { /* Nothing. */ } + + /* Is this object in state::SET? */ + bool is_set () const + { + return m_state == state::SET; + } + + /* Is this object in state::UNSET? */ + bool is_unset () const + { + return m_state == state::UNSET; + } + + /* Return the argument string. Only call when is_set returns true. */ + const std::string &args () const + { + gdb_assert (m_state == state::SET); + return m_args; + } + + /* Return the executable string. Only call when is_set returns true. */ + const std::string &exec () const + { + gdb_assert (m_state == state::SET); + return m_exec; + } + +private: + /* The state of this instance. */ + state m_state = state::UNKNOWN; + + /* The executable path returned from the remote target. */ + std::string m_exec; + + /* The argument string returned from the remote target. */ + std::string m_args; +}; + class remote_target : public process_stratum_target { public: @@ -1022,8 +1115,8 @@ public: int fileio_fstat (int fd, struct stat *sb, fileio_error *target_errno) override; - int fileio_stat (struct inferior *inf, const char *filename, - struct stat *sb, fileio_error *target_errno) override; + int fileio_lstat (struct inferior *inf, const char *filename, + struct stat *sb, fileio_error *target_errno) override; int fileio_close (int fd, fileio_error *target_errno) override; @@ -1155,6 +1248,12 @@ public: bool is_address_tagged (gdbarch *gdbarch, CORE_ADDR address) override; + /* A remote target can be shared if it is able to create new inferiors. */ + bool is_shareable () override final + { + return true; + } + public: /* Remote specific methods. */ void remote_download_command_source (int num, ULONGEST addr, @@ -1385,7 +1484,8 @@ public: /* Remote specific methods. */ void remote_kill_k (); void extended_remote_disable_randomization (int val); - int extended_remote_run (const std::string &args); + int extended_remote_run (const std::string &remote_exec_file, + const std::string &args); void send_environment_packet (const char *action, const char *packet, @@ -1416,6 +1516,9 @@ public: /* Remote specific methods. */ private: + /* Fetch the executable filename and argument string from the remote. */ + remote_exec_and_args_info fetch_remote_executable_and_arguments (); + bool start_remote_1 (int from_tty, int extended_p); /* The remote state. Don't reference this directly. Use the @@ -1512,15 +1615,72 @@ remote_register_is_expedited (int regnum) return rs->last_seen_expedited_registers.count (regnum) > 0; } +/* An enum used to track where the per-program-space remote exec-file data + came from. This is useful when deciding which warnings to give to the + user. */ + +enum class remote_exec_source +{ + /* The remote exec-file has it's default (empty string) value, neither + the user, nor the remote target have tried to set the value yet. */ + DEFAULT_VALUE, + + /* The remote exec-file value was set based on information fetched from + the remote target. We warn the user if we are replacing a value they + supplied with one fetched from the remote target. */ + VALUE_FROM_REMOTE, + + /* The remote exec-file value was set either directly by the user, or by + GDB after the inferior performed an exec. */ + VALUE_FROM_GDB, + + /* The remote exec-file has it's default (empty string) value, but this + is because the user hasn't supplied a value yet, and the remote target + has specifically told GDB that it has no default executable available. */ + UNSET_VALUE, +}; + +/* Data held per program space for each remote target. */ + +struct remote_per_progspace +{ + /* The following represents the remote exec-file filename. This value + can be set directly by the user with 'set remote exec-file', but can + also be set automatically when connecting to a remote target. We + track both the filename value, and also the source of this value, as + where the value came from determines when the filename value can be + auto-generated from other sources, or when a remote target can + override the current value. See show_remote_exec_file for details. */ + struct exec_info + { + /* The 'remote exec-file' filename value. This will start empty. This + is set either with the 'set remote exec-file' command, or + automatically by GDB when connecting to a remote target. */ + std::string filename; + + /* An enum that indicates where FILENAME came from, or what an empty + VALUE means. */ + remote_exec_source source = remote_exec_source::DEFAULT_VALUE; + } exec_info; +}; + /* Per-program-space data key. */ -static const registry<program_space>::key<char, gdb::xfree_deleter<char>> +static const registry<program_space>::key<remote_per_progspace> remote_pspace_data; -/* The variable registered as the control variable used by the - remote exec-file commands. While the remote exec-file setting is - per-program-space, the set/show machinery uses this as the - location of the remote exec-file value. */ -static std::string remote_exec_file_var; +/* Retrieve the remote_per_progspace object for PSPACE. If no such object + yet exists then create a new one using the default constructor and + return that. */ + +static remote_per_progspace & +get_remote_progspace_info (program_space *pspace) +{ + remote_per_progspace *info = remote_pspace_data.get (pspace); + if (info == nullptr) + info = remote_pspace_data.emplace (pspace); + gdb_assert (info != nullptr); + return *info; +} /* The size to align memory write packets, when practical. The protocol does not guarantee any alignment, and gdb will generate short @@ -1850,47 +2010,55 @@ remote_target::get_remote_state () /* Fetch the remote exec-file from the current program space. */ -static const char * -get_remote_exec_file (void) +static const std::string & +get_remote_exec_file () { - char *remote_exec_file; - - remote_exec_file = remote_pspace_data.get (current_program_space); - if (remote_exec_file == NULL) - return ""; - - return remote_exec_file; + const remote_per_progspace &info + = get_remote_progspace_info (current_program_space); + return info.exec_info.filename; } -/* Set the remote exec file for PSPACE. */ +/* Set the remote exec file for PSPACE. FILENAME is the new exec file + value, and SOURCE describes where FILENAME came from. */ static void set_pspace_remote_exec_file (struct program_space *pspace, - const char *remote_exec_file) + const std::string &filename, + remote_exec_source source) { - char *old_file = remote_pspace_data.get (pspace); - - xfree (old_file); - remote_pspace_data.set (pspace, xstrdup (remote_exec_file)); + remote_per_progspace &info = get_remote_progspace_info (pspace); + info.exec_info.filename = filename; + info.exec_info.source = source; } -/* The "set/show remote exec-file" set command hook. */ +/* The "set remote exec-file" callback. */ static void -set_remote_exec_file (const char *ignored, int from_tty, - struct cmd_list_element *c) +set_remote_exec_file_cb (const std::string &filename) { - set_pspace_remote_exec_file (current_program_space, - remote_exec_file_var.c_str ()); + set_pspace_remote_exec_file (current_program_space, filename, + remote_exec_source::VALUE_FROM_GDB); } -/* The "set/show remote exec-file" show command hook. */ +/* Implement the "show remote exec-file" command. */ static void show_remote_exec_file (struct ui_file *file, int from_tty, struct cmd_list_element *cmd, const char *value) { - gdb_printf (file, "%s\n", get_remote_exec_file ()); + const struct remote_per_progspace::exec_info &info + = get_remote_progspace_info (current_program_space).exec_info; + + if (info.source == remote_exec_source::DEFAULT_VALUE) + gdb_printf (file, _("The remote exec-file is unset, the default " + "remote executable will be used.\n")); + else if (info.source == remote_exec_source::UNSET_VALUE) + gdb_printf (file, _("The remote exec-file is unset, the remote has " + "no default executable set.\n")); + else + gdb_printf (file, _("The remote exec-file is \"%ps\".\n"), + styled_string (file_name_style.style (), + info.filename.c_str ())); } static int @@ -2556,7 +2724,7 @@ packet_check_result (const char *buf) /* The stub recognized the packet request. Check that the operation succeeded. */ if (buf[0] == 'E' - && isxdigit (buf[1]) && isxdigit (buf[2]) + && c_isxdigit (buf[1]) && c_isxdigit (buf[2]) && buf[3] == '\0') /* "Enn" - definitely an error. */ return packet_result::make_numeric_error (buf + 1); @@ -2901,6 +3069,10 @@ remote_target::remote_add_inferior (bool fake_pid_p, int pid, int attached, inf = add_inferior_with_spaces (); } switch_to_inferior_no_thread (inf); + + /* Clear any data left by previous executions. */ + target_pre_inferior (); + inf->push_target (this); inferior_appeared (inf, pid); } @@ -3026,6 +3198,12 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing) inf = remote_add_inferior (fake_pid_p, currthread.pid (), -1, 1); + + /* Fetch the target description for this inferior. Make sure to + leave the currently selected inferior unchanged. */ + scoped_restore_current_thread restore_thread; + switch_to_inferior_no_thread (inf); + target_find_description (); } /* This is really a new thread. Add it. */ @@ -4283,7 +4461,7 @@ static bool has_single_non_exited_thread (inferior *inf) { int count = 0; - for (thread_info *tp ATTRIBUTE_UNUSED : inf->non_exited_threads ()) + for (thread_info &tp ATTRIBUTE_UNUSED : inf->non_exited_threads ()) if (++count > 1) break; return count == 1; @@ -4321,19 +4499,19 @@ remote_target::update_thread_list () /* CONTEXT now holds the current thread list on the remote target end. Delete GDB-side threads no longer found on the target. */ - for (thread_info *tp : all_threads_safe ()) + for (thread_info &tp : all_threads_safe ()) { - if (tp->inf->process_target () != this) + if (tp.inf->process_target () != this) continue; - if (!context.contains_thread (tp->ptid)) + if (!context.contains_thread (tp.ptid)) { /* Do not remove the thread if it is the last thread in the inferior. This situation happens when we have a pending exit process status to process. Otherwise we may end up with a seemingly live inferior (i.e. pid != 0) that has no threads. */ - if (has_single_non_exited_thread (tp->inf)) + if (has_single_non_exited_thread (tp.inf)) continue; /* Do not remove the thread if we've requested to be @@ -4342,11 +4520,11 @@ remote_target::update_thread_list () exit event, and displaced stepping info is recorded in the thread object. If we deleted the thread now, we'd lose that info. */ - if ((tp->thread_options () & GDB_THREAD_OPTION_EXIT) != 0) + if ((tp.thread_options () & GDB_THREAD_OPTION_EXIT) != 0) continue; /* Not found. */ - delete_thread (tp); + delete_thread (&tp); } } @@ -4656,8 +4834,6 @@ remote_target::get_offsets () if (bss_addr != data_addr) warning (_("Target reported unsupported offsets: %s"), buf); } - else - lose = 1; } else if (startswith (ptr, "TextSeg=")) { @@ -4866,7 +5042,11 @@ remote_target::add_current_inferior_and_thread (const char *wait_status) fake_pid_p = true; } - remote_add_inferior (fake_pid_p, curr_ptid.pid (), -1, 1); + const auto inf = remote_add_inferior (fake_pid_p, curr_ptid.pid (), -1, 1); + switch_to_inferior_no_thread (inf); + + /* Fetch the target description for this inferior. */ + target_find_description (); /* Add the main thread and switch to it. Don't try reading registers yet, since we haven't fetched the target description @@ -4943,7 +5123,7 @@ remote_target::process_initial_stop_replies (int from_tty) event_ptid = target_wait (waiton_ptid, &ws, TARGET_WNOHANG); if (remote_debug) - print_target_wait_results (waiton_ptid, event_ptid, ws); + print_target_wait_results (waiton_ptid, event_ptid, ws, this); switch (ws.kind ()) { @@ -5005,6 +5185,24 @@ remote_target::process_initial_stop_replies (int from_tty) the inferiors. */ if (!non_stop) { + /* Ensure changes to the thread state are propagated to the + frontend. In non-stop mode only the current inferior will be + stopped, but in all-stop mode, all inferiors will change, and + the frontend will need updating. */ + process_stratum_target *finish_target; + ptid_t finish_ptid; + if (non_stop) + { + finish_target = current_inferior ()->process_target (); + finish_ptid = ptid_t (current_inferior ()->pid); + } + else + { + finish_target = nullptr; + finish_ptid = minus_one_ptid; + } + scoped_finish_thread_state finish_state (finish_target, finish_ptid); + { /* At this point, the remote target is not async. It needs to be for the poll in stop_all_threads to consider events from it, so enable @@ -5031,26 +5229,26 @@ remote_target::process_initial_stop_replies (int from_tty) /* Now go over all threads that are stopped, and print their current frame. If all-stop, then if there's a signalled thread, pick that as current. */ - for (thread_info *thread : all_non_exited_threads (this)) + for (thread_info &thread : all_non_exited_threads (this)) { if (first == NULL) - first = thread; + first = &thread; if (!non_stop) - thread->set_running (false); - else if (thread->state != THREAD_STOPPED) + thread.set_running (false); + else if (thread.state != THREAD_STOPPED) continue; - if (selected == nullptr && thread->has_pending_waitstatus ()) - selected = thread; + if (selected == nullptr && thread.has_pending_waitstatus ()) + selected = &thread; if (lowest_stopped == NULL - || thread->inf->num < lowest_stopped->inf->num - || thread->per_inf_num < lowest_stopped->per_inf_num) - lowest_stopped = thread; + || thread.inf->num < lowest_stopped->inf->num + || thread.per_inf_num < lowest_stopped->per_inf_num) + lowest_stopped = &thread; if (non_stop) - print_one_stopped_thread (thread); + print_one_stopped_thread (&thread); } /* In all-stop, we only print the status of one thread, and leave @@ -5115,6 +5313,93 @@ as_stop_reply_up (notif_event_up event) return stop_reply_up (stop_reply); } +/* Read, decode, and return a hex-encoded string from *PTR. Update *PTR to + point at the first character past the end of the string that was read + in. */ + +static std::string +get_semicolon_delimited_hex_as_string (const char **ptr) +{ + const char *end = *ptr; + + for (; *end != ';' && *end != '\0'; ++end) + ; + + std::string str = hex2str (*ptr, (end - *ptr) / 2); + *ptr = end; + return str; +} + +/* See declaration in class above. */ + +remote_exec_and_args_info +remote_target::fetch_remote_executable_and_arguments () +{ + /* Setup some constants. This helps keep the return lines shorter in the + rest of this function. */ + constexpr remote_exec_and_args_info::state UNKNOWN + = remote_exec_and_args_info::state::UNKNOWN; + constexpr remote_exec_and_args_info::state UNSET + = remote_exec_and_args_info::state::UNSET; + + if (m_features.packet_support (PACKET_qExecAndArgs) == PACKET_DISABLE) + return remote_exec_and_args_info (UNKNOWN); + + struct remote_state *rs = get_remote_state (); + + putpkt ("qExecAndArgs"); + getpkt (&rs->buf, 0); + + packet_result result + = m_features.packet_ok (rs->buf, PACKET_qExecAndArgs); + switch (result.status ()) + { + case PACKET_ERROR: + warning (_("Remote error: %s"), result.err_msg ()); + [[fallthrough]]; + case PACKET_UNKNOWN: + return remote_exec_and_args_info (UNKNOWN); + case PACKET_OK: + break; + } + + /* First character should be 'U', to indicate no information is set in + the server, or 'S' followed by the filename and arguments. We treat + anything that is not a 'S' as if it were 'U'. */ + if (rs->buf[0] != 'S') + return remote_exec_and_args_info (UNSET); + + if (rs->buf[1] != ';') + { + warning (_("missing first ';' in qExecAndArgs reply")); + return remote_exec_and_args_info (UNSET); + } + + std::string filename, args; + + try + { + const char *ptr = &rs->buf[2]; + filename = get_semicolon_delimited_hex_as_string (&ptr); + + /* PTR now points either at a semicolon, or at the end of the incoming + buffer data. If we are looking at a semicolon, then skip it. */ + if (*ptr == ';') + ++ptr; + + /* We'll collect the inferior arguments in ARGS. */ + args = get_semicolon_delimited_hex_as_string (&ptr); + } + catch (const gdb_exception_error &ex) + { + warning (_("unable to decode qExecAndArgs reply: %s"), + ex.what ()); + return remote_exec_and_args_info (UNKNOWN); + } + + return remote_exec_and_args_info (std::move (filename), std::move (args)); +} + /* Helper for remote_target::start_remote, start the remote connection and sync state. Return true if everything goes OK, otherwise, return false. This function exists so that the scoped_restore created within it will @@ -5200,6 +5485,41 @@ remote_target::start_remote_1 (int from_tty, int extended_p) rs->noack_mode = 1; } + remote_exec_and_args_info exec_and_args + = fetch_remote_executable_and_arguments (); + + /* Update the inferior with the executable and argument string from the + target, these will be used when restarting the inferior, and also + allow the user to view this state. */ + if (exec_and_args.is_set ()) + { + current_inferior ()->set_args (exec_and_args.args ()); + if (!exec_and_args.exec ().empty ()) + { + struct remote_per_progspace::exec_info &info + = get_remote_progspace_info (current_program_space).exec_info; + if (info.source == remote_exec_source::VALUE_FROM_GDB + && info.filename != exec_and_args.exec ()) + warning (_("updating 'remote exec-file' to '%ps' to match " + "remote target"), + styled_string (file_name_style.style (), + exec_and_args.exec ().c_str ())); + info.filename = exec_and_args.exec (); + info.source = remote_exec_source::VALUE_FROM_REMOTE; + } + } + else if (exec_and_args.is_unset ()) + { + struct remote_per_progspace::exec_info &info + = get_remote_progspace_info (current_program_space).exec_info; + if (info.source == remote_exec_source::DEFAULT_VALUE + || info.source == remote_exec_source::VALUE_FROM_REMOTE) + { + info.filename.clear (); + info.source = remote_exec_source::UNSET_VALUE; + } + } + if (extended_p) { /* Tell the remote that we are using the extended protocol. */ @@ -5318,10 +5638,10 @@ remote_target::start_remote_1 (int from_tty, int extended_p) remote_debug_printf ("warning: couldn't determine remote " "current thread; picking first in list."); - for (thread_info *tp : all_non_exited_threads (this, + for (thread_info &tp : all_non_exited_threads (this, minus_one_ptid)) { - switch_to_thread (tp); + switch_to_thread (&tp); break; } } @@ -5862,6 +6182,8 @@ static const struct protocol_feature remote_protocol_features[] = { { "error-message", PACKET_ENABLE, remote_supported_packet, PACKET_accept_error_message }, { "binary-upload", PACKET_DISABLE, remote_supported_packet, PACKET_x }, + { "single-inf-arg", PACKET_DISABLE, remote_supported_packet, + PACKET_vRun_single_argument }, }; static char *remote_support_xml; @@ -5973,6 +6295,10 @@ remote_target::remote_query_supported () != AUTO_BOOLEAN_FALSE) remote_query_supported_append (&q, "memory-tagging+"); + if (m_features.packet_set_cmd_state (PACKET_vRun_single_argument) + != AUTO_BOOLEAN_FALSE) + remote_query_supported_append (&q, "single-inf-arg+"); + /* Keep this one last to work around a gdbserver <= 7.10 bug in the qSupported:xmlRegisters=i386 handling. */ if (remote_support_xml != NULL @@ -6164,10 +6490,29 @@ remote_unpush_target (remote_target *target) /* We have to unpush the target from all inferiors, even those that aren't running. */ scoped_restore_current_inferior restore_current_inferior; + scoped_restore_current_program_space restore_program_space; for (inferior *inf : all_inferiors (target)) { switch_to_inferior_no_thread (inf); + + /* If this remote told INF specifically, that there was no inferior + currently set, then reset this state back to GDB being in the + default state. + + If however, the INF was set, either from the GDB side, or even + from the remote side, then we retain the current setting. The + assumption is that the user is, or will, be debugging the same + executable again in the future, so clearing an existing value + would be unhelpful. */ + struct remote_per_progspace::exec_info &exec_info + = get_remote_progspace_info (inf->pspace).exec_info; + if (exec_info.source == remote_exec_source::UNSET_VALUE) + { + gdb_assert (exec_info.filename.empty ()); + exec_info.source = remote_exec_source::DEFAULT_VALUE; + } + inf->pop_all_targets_at_and_above (process_stratum); generic_mourn_inferior (); } @@ -6485,9 +6830,9 @@ remote_target::remote_detach_1 (inferior *inf, int from_tty) /* See if any thread of the inferior we are detaching has a pending fork status. In that case, we must detach from the child resulting from that fork. */ - for (thread_info *thread : inf->non_exited_threads ()) + for (thread_info &thread : inf->non_exited_threads ()) { - const target_waitstatus *ws = thread_pending_fork_status (thread); + const target_waitstatus *ws = thread_pending_fork_status (&thread); if (ws == nullptr) continue; @@ -6601,7 +6946,8 @@ remote_target::follow_exec (inferior *follow_inf, ptid_t ptid, if (is_target_filename (execd_pathname)) execd_pathname += strlen (TARGET_SYSROOT_PREFIX); - set_pspace_remote_exec_file (follow_inf->pspace, execd_pathname); + set_pspace_remote_exec_file (follow_inf->pspace, execd_pathname, + remote_exec_source::VALUE_FROM_GDB); } /* Same as remote_detach, but don't send the "D" packet; just disconnect. */ @@ -6890,14 +7236,14 @@ char * remote_target::append_pending_thread_resumptions (char *p, char *endp, ptid_t ptid) { - for (thread_info *thread : all_non_exited_threads (this, ptid)) - if (inferior_ptid != thread->ptid - && thread->stop_signal () != GDB_SIGNAL_0) + for (thread_info &thread : all_non_exited_threads (this, ptid)) + if (inferior_ptid != thread.ptid + && thread.stop_signal () != GDB_SIGNAL_0) { - p = append_resumption (p, endp, thread->ptid, - 0, thread->stop_signal ()); - thread->set_stop_signal (GDB_SIGNAL_0); - resume_clear_thread_private_info (thread); + p = append_resumption (p, endp, thread.ptid, + 0, thread.stop_signal ()); + thread.set_stop_signal (GDB_SIGNAL_0); + resume_clear_thread_private_info (&thread); } return p; @@ -6923,8 +7269,8 @@ remote_target::remote_resume_with_hc (ptid_t ptid, int step, else set_continue_thread (ptid); - for (thread_info *thread : all_non_exited_threads (this)) - resume_clear_thread_private_info (thread); + for (thread_info &thread : all_non_exited_threads (this)) + resume_clear_thread_private_info (&thread); buf = rs->buf.data (); if (::execution_direction == EXEC_REVERSE) @@ -7086,8 +7432,8 @@ remote_target::resume (ptid_t scope_ptid, int step, enum gdb_signal siggnal) remote_resume_with_hc (scope_ptid, step, siggnal); /* Update resumed state tracked by the remote target. */ - for (thread_info *tp : all_non_exited_threads (this, scope_ptid)) - get_remote_thread_info (tp)->set_resumed (); + for (thread_info &tp : all_non_exited_threads (this, scope_ptid)) + get_remote_thread_info (&tp)->set_resumed (); /* We've just told the target to resume. The remote server will wait for the inferior to stop, and then send a stop reply. In @@ -7299,15 +7645,15 @@ remote_target::commit_resumed () bool any_pending_vcont_resume = false; - for (thread_info *tp : all_non_exited_threads (this)) + for (thread_info &tp : all_non_exited_threads (this)) { - remote_thread_info *priv = get_remote_thread_info (tp); + remote_thread_info *priv = get_remote_thread_info (&tp); /* If a thread of a process is not meant to be resumed, then we can't wildcard that process. */ if (priv->get_resume_state () == resume_state::NOT_RESUMED) { - get_remote_inferior (tp->inf)->may_wildcard_vcont = false; + get_remote_inferior (tp.inf)->may_wildcard_vcont = false; /* And if we can't wildcard a process, we can't wildcard everything either. */ @@ -7321,7 +7667,7 @@ remote_target::commit_resumed () /* If a thread is the parent of an unfollowed fork/vfork/clone, then we can't do a global wildcard, as that would resume the pending child. */ - if (thread_pending_child_status (tp) != nullptr) + if (thread_pending_child_status (&tp) != nullptr) may_global_wildcard_vcont = false; } @@ -7338,9 +7684,9 @@ remote_target::commit_resumed () struct vcont_builder vcont_builder (this); /* Threads first. */ - for (thread_info *tp : all_non_exited_threads (this)) + for (thread_info &tp : all_non_exited_threads (this)) { - remote_thread_info *remote_thr = get_remote_thread_info (tp); + remote_thread_info *remote_thr = get_remote_thread_info (&tp); /* If the thread was previously vCont-resumed, no need to send a specific action for it. If we didn't receive a resume request for it, don't @@ -7348,14 +7694,14 @@ remote_target::commit_resumed () if (remote_thr->get_resume_state () != resume_state::RESUMED_PENDING_VCONT) continue; - gdb_assert (!thread_is_in_step_over_chain (tp)); + gdb_assert (!thread_is_in_step_over_chain (&tp)); /* We should never be commit-resuming a thread that has a stop reply. Otherwise, we would end up reporting a stop event for a thread while it is running on the remote target. */ remote_state *rs = get_remote_state (); for (const auto &stop_reply : rs->stop_reply_queue) - gdb_assert (stop_reply->ptid != tp->ptid); + gdb_assert (stop_reply->ptid != tp.ptid); const resumed_pending_vcont_info &info = remote_thr->resumed_pending_vcont_info (); @@ -7363,8 +7709,8 @@ remote_target::commit_resumed () /* Check if we need to send a specific action for this thread. If not, it will be included in a wildcard resume instead. */ if (info.step || info.sig != GDB_SIGNAL_0 - || !get_remote_inferior (tp->inf)->may_wildcard_vcont) - vcont_builder.push_action (tp->ptid, info.step, info.sig); + || !get_remote_inferior (tp.inf)->may_wildcard_vcont) + vcont_builder.push_action (tp.ptid, info.step, info.sig); remote_thr->set_resumed (); } @@ -7447,9 +7793,9 @@ remote_target::remote_stop_ns (ptid_t ptid) whether the thread wasn't resumed with a signal. Generating a phony stop in that case would result in losing the signal. */ bool needs_commit = false; - for (thread_info *tp : all_non_exited_threads (this, ptid)) + for (thread_info &tp : all_non_exited_threads (this, ptid)) { - remote_thread_info *remote_thr = get_remote_thread_info (tp); + remote_thread_info *remote_thr = get_remote_thread_info (&tp); if (remote_thr->get_resume_state () == resume_state::RESUMED_PENDING_VCONT) @@ -7470,17 +7816,17 @@ remote_target::remote_stop_ns (ptid_t ptid) if (needs_commit) commit_resumed (); else - for (thread_info *tp : all_non_exited_threads (this, ptid)) + for (thread_info &tp : all_non_exited_threads (this, ptid)) { - remote_thread_info *remote_thr = get_remote_thread_info (tp); + remote_thread_info *remote_thr = get_remote_thread_info (&tp); if (remote_thr->get_resume_state () == resume_state::RESUMED_PENDING_VCONT) { remote_debug_printf ("Enqueueing phony stop reply for thread pending " - "vCont-resume (%d, %ld, %s)", tp->ptid.pid(), - tp->ptid.lwp (), - pulongest (tp->ptid.tid ())); + "vCont-resume (%d, %ld, %s)", tp.ptid.pid(), + tp.ptid.lwp (), + pulongest (tp.ptid.tid ())); /* Check that the thread wasn't resumed with a signal. Generating a phony stop would result in losing the @@ -7490,10 +7836,10 @@ remote_target::remote_stop_ns (ptid_t ptid) gdb_assert (info.sig == GDB_SIGNAL_0); stop_reply_up sr = std::make_unique<stop_reply> (); - sr->ptid = tp->ptid; + sr->ptid = tp.ptid; sr->rs = rs; sr->ws.set_stopped (GDB_SIGNAL_0); - sr->arch = tp->inf->arch (); + sr->arch = tp.inf->arch (); sr->stop_reason = TARGET_STOPPED_BY_NO_REASON; sr->watch_data_address = 0; sr->core = 0; @@ -7789,9 +8135,9 @@ remote_target::remove_new_children (threads_listing_context *context) /* For any threads stopped at a (v)fork/clone event, remove the corresponding child threads from the CONTEXT list. */ - for (thread_info *thread : all_non_exited_threads (this)) + for (thread_info &thread : all_non_exited_threads (this)) { - const target_waitstatus *ws = thread_pending_child_status (thread); + const target_waitstatus *ws = thread_pending_child_status (&thread); if (ws == nullptr) continue; @@ -8486,17 +8832,17 @@ remote_target::select_thread_for_ambiguous_stop_reply /* Consider all non-exited threads of the target, find the first resumed one. */ - for (thread_info *thr : all_non_exited_threads (this)) + for (thread_info &thr : all_non_exited_threads (this)) { - remote_thread_info *remote_thr = get_remote_thread_info (thr); + remote_thread_info *remote_thr = get_remote_thread_info (&thr); if (remote_thr->get_resume_state () != resume_state::RESUMED) continue; if (first_resumed_thread == nullptr) - first_resumed_thread = thr; + first_resumed_thread = &thr; else if (!process_wide_stop - || first_resumed_thread->ptid.pid () != thr->ptid.pid ()) + || first_resumed_thread->ptid.pid () != thr.ptid.pid ()) ambiguous = true; } @@ -8606,8 +8952,8 @@ remote_target::process_stop_reply (stop_reply_up stop_reply, { /* If the target works in all-stop mode, a stop-reply indicates that all the target's threads stopped. */ - for (thread_info *tp : all_non_exited_threads (this)) - get_remote_thread_info (tp)->set_not_resumed (); + for (thread_info &tp : all_non_exited_threads (this)) + get_remote_thread_info (&tp)->set_not_resumed (); } } @@ -8675,9 +9021,9 @@ remote_target::wait_ns (ptid_t ptid, struct target_waitstatus *status, static ptid_t first_remote_resumed_thread (remote_target *target) { - for (thread_info *tp : all_non_exited_threads (target, minus_one_ptid)) - if (tp->resumed ()) - return tp->ptid; + for (thread_info &tp : all_non_exited_threads (target, minus_one_ptid)) + if (tp.resumed ()) + return tp.ptid; return null_ptid; } @@ -10602,9 +10948,9 @@ remote_target::kill_new_fork_children (inferior *inf) /* Kill the fork child threads of any threads in inferior INF that are stopped at a fork event. */ - for (thread_info *thread : inf->non_exited_threads ()) + for (thread_info &thread : inf->non_exited_threads ()) { - const target_waitstatus *ws = thread_pending_fork_status (thread); + const target_waitstatus *ws = thread_pending_fork_status (&thread); if (ws == nullptr) continue; @@ -10814,11 +11160,11 @@ remote_target::extended_remote_disable_randomization (int val) } int -remote_target::extended_remote_run (const std::string &args) +remote_target::extended_remote_run (const std::string &remote_exec_file, + const std::string &args) { struct remote_state *rs = get_remote_state (); int len; - const char *remote_exec_file = get_remote_exec_file (); /* If the user has disabled vRun support, or we have detected that support is not available, do not try it. */ @@ -10828,23 +11174,27 @@ remote_target::extended_remote_run (const std::string &args) strcpy (rs->buf.data (), "vRun;"); len = strlen (rs->buf.data ()); - if (strlen (remote_exec_file) * 2 + len >= get_remote_packet_size ()) + if (remote_exec_file.size () * 2 + len >= get_remote_packet_size ()) error (_("Remote file name too long for run packet")); - len += 2 * bin2hex ((gdb_byte *) remote_exec_file, rs->buf.data () + len, - strlen (remote_exec_file)); + len += 2 * bin2hex ((gdb_byte *) remote_exec_file.data (), + rs->buf.data () + len, + remote_exec_file.size ()); if (!args.empty ()) { - int i; + std::vector<std::string> split_args; + if (!m_features.remote_vrun_single_arg_p ()) + split_args = gdb::remote_args::split (args); + else + split_args.push_back (args); - gdb_argv argv (args.c_str ()); - for (i = 0; argv[i] != NULL; i++) + for (const auto &a : split_args) { - if (strlen (argv[i]) * 2 + 1 + len >= get_remote_packet_size ()) + if (a.size () * 2 + 1 + len >= get_remote_packet_size ()) error (_("Argument list too long for run packet")); rs->buf[len++] = ';'; - len += 2 * bin2hex ((gdb_byte *) argv[i], rs->buf.data () + len, - strlen (argv[i])); + len += 2 * bin2hex ((gdb_byte *) a.c_str (), rs->buf.data () + len, + a.size ()); } } @@ -10872,7 +11222,7 @@ remote_target::extended_remote_run (const std::string &args) "try \"set remote exec-file\"?")); else error (_("Running \"%s\" on the remote target failed"), - remote_exec_file); + remote_exec_file.c_str ()); default: gdb_assert_not_reached ("bad switch"); } @@ -10992,7 +11342,7 @@ extended_remote_target::create_inferior (const char *exec_file, int run_worked; char *stop_reply; struct remote_state *rs = get_remote_state (); - const char *remote_exec_file = get_remote_exec_file (); + const std::string &remote_exec_file = get_remote_exec_file (); /* If running asynchronously, register the target file descriptor with the event loop. */ @@ -11022,7 +11372,7 @@ Remote replied unexpectedly while setting startup-with-shell: %s"), extended_remote_set_inferior_cwd (); /* Now restart the remote server. */ - run_worked = extended_remote_run (args) != -1; + run_worked = extended_remote_run (remote_exec_file, args) != -1; if (!run_worked) { /* vRun was not supported. Fail if we need it to do what the @@ -11650,7 +12000,7 @@ remote_target::remote_write_qxfer (const char *object_name, i = snprintf (rs->buf.data (), max_size, "qXfer:%s:write:%s:%s:", object_name, annex ? annex : "", - phex_nz (offset, sizeof offset)); + phex_nz (offset)); max_size -= (i + 1); /* Escape as much data as fits into rs->buf. */ @@ -11715,8 +12065,8 @@ remote_target::remote_read_qxfer (const char *object_name, snprintf (rs->buf.data (), get_remote_packet_size () - 4, "qXfer:%s:read:%s:%s,%s", object_name, annex ? annex : "", - phex_nz (offset, sizeof offset), - phex_nz (n, sizeof n)); + phex_nz (offset), + phex_nz (n)); i = putpkt (rs->buf); if (i < 0) return TARGET_XFER_E_IO; @@ -11935,7 +12285,7 @@ remote_target::xfer_partial (enum target_object object, while (annex[i] && (i < (get_remote_packet_size () - 8))) { /* Bad caller may have sent forbidden characters. */ - gdb_assert (isprint (annex[i]) && annex[i] != '$' && annex[i] != '#'); + gdb_assert (c_isprint (annex[i]) && annex[i] != '$' && annex[i] != '#'); *p2++ = annex[i]; i++; } @@ -12014,7 +12364,7 @@ remote_target::search_memory (CORE_ADDR start_addr, ULONGEST search_space_len, i = snprintf (rs->buf.data (), max_size, "qSearch:memory:%s;%s;", phex_nz (start_addr, addr_size), - phex_nz (search_space_len, sizeof (search_space_len))); + phex_nz (search_space_len)); max_size -= (i + 1); /* Escape as much data as fits into rs->buf. */ @@ -12185,7 +12535,7 @@ private: for (int i = 0; i < buf.size (); ++i) { gdb_byte c = buf[i]; - if (isprint (c)) + if (c_isprint (c)) gdb_putc (c, &stb); else gdb_printf (&stb, "\\x%02x", (unsigned char) c); @@ -12232,6 +12582,51 @@ cli_packet_command (const char *args, int from_tty) send_remote_packet (view, &cb); } +/* Implement 'maint test-remote-args' command. + + Treat ARGS as an argument string. Split the remote arguments using + gdb::remote_args::split, and then join using gdb::remote_args::join. + The split and joined arguments are printed out. Additionally, the + joined arguments are split and joined a second time, and compared to the + result of the first join, this provides some basic validation that GDB + sess the joined arguments as equivalent to the original argument + string. */ + +static void +test_remote_args_command (const char *args, int from_tty) +{ + std::vector<std::string> split_args = gdb::remote_args::split (args); + + gdb_printf ("Input (%s)\n", args); + for (const std::string &a : split_args) + gdb_printf (" (%s)\n", a.c_str ()); + + gdb::argv_vec tmp_split_args; + for (const std::string &a : split_args) + tmp_split_args.emplace_back (xstrdup (a.c_str ())); + + std::string joined_args = gdb::remote_args::join (tmp_split_args.get ()); + + gdb_printf ("Output (%s)\n", joined_args.c_str ()); + + std::vector<std::string> resplit = gdb::remote_args::split (joined_args); + + tmp_split_args.clear (); + for (const std::string &a : resplit) + tmp_split_args.emplace_back (xstrdup (a.c_str ())); + + std::string rejoined = gdb::remote_args::join (tmp_split_args.get ()); + + if (joined_args != rejoined || split_args != resplit) + { + gdb_printf ("FAILURE ON REJOINING\n"); + gdb_printf ("Resplit args:\n"); + for (const auto & a : resplit) + gdb_printf (" (%s)\n", a.c_str ()); + gdb_printf ("Rejoined (%s)\n", rejoined.c_str ()); + } +} + #if 0 /* --------- UNIT_TEST for THREAD oriented PACKETS ------------------- */ @@ -13149,7 +13544,7 @@ remote_target::fileio_readlink (struct inferior *inf, const char *filename, return ret; } -/* Helper function to handle ::fileio_fstat and ::fileio_stat result +/* Helper function to handle ::fileio_fstat and ::fileio_lstat result processing. When this function is called the remote syscall has been performed and we know we didn't get an error back. @@ -13160,10 +13555,10 @@ remote_target::fileio_readlink (struct inferior *inf, const char *filename, data) is to be placed in ST. */ static int -fileio_process_fstat_and_stat_reply (const char *attachment, - int attachment_len, - int expected_len, - struct stat *st) +fileio_process_fstat_and_lstat_reply (const char *attachment, + int attachment_len, + int expected_len, + struct stat *st) { struct fio_stat fst; @@ -13225,15 +13620,15 @@ remote_target::fileio_fstat (int fd, struct stat *st, fileio_error *remote_errno return 0; } - return fileio_process_fstat_and_stat_reply (attachment, attachment_len, - ret, st); + return fileio_process_fstat_and_lstat_reply (attachment, attachment_len, + ret, st); } -/* Implementation of to_fileio_stat. */ +/* Implementation of to_fileio_lstat. */ int -remote_target::fileio_stat (struct inferior *inf, const char *filename, - struct stat *st, fileio_error *remote_errno) +remote_target::fileio_lstat (struct inferior *inf, const char *filename, + struct stat *st, fileio_error *remote_errno) { struct remote_state *rs = get_remote_state (); char *p = rs->buf.data (); @@ -13242,14 +13637,14 @@ remote_target::fileio_stat (struct inferior *inf, const char *filename, if (remote_hostio_set_filesystem (inf, remote_errno) != 0) return {}; - remote_buffer_add_string (&p, &left, "vFile:stat:"); + remote_buffer_add_string (&p, &left, "vFile:lstat:"); remote_buffer_add_bytes (&p, &left, (const gdb_byte *) filename, strlen (filename)); int attachment_len; const char *attachment; - int ret = remote_hostio_send_command (p - rs->buf.data (), PACKET_vFile_stat, + int ret = remote_hostio_send_command (p - rs->buf.data (), PACKET_vFile_lstat, remote_errno, &attachment, &attachment_len); @@ -13258,8 +13653,8 @@ remote_target::fileio_stat (struct inferior *inf, const char *filename, if (ret < 0) return ret; - return fileio_process_fstat_and_stat_reply (attachment, attachment_len, - ret, st); + return fileio_process_fstat_and_lstat_reply (attachment, attachment_len, + ret, st); } /* Implementation of to_filesystem_is_local. */ @@ -13763,7 +14158,7 @@ remote_target::download_tracepoint (struct bp_location *loc) encode_actions_rsp (loc, &tdp_actions, &stepping_actions); tpaddr = loc->address; - strcpy (addrbuf, phex (tpaddr, sizeof (CORE_ADDR))); + strcpy (addrbuf, phex (tpaddr)); ret = snprintf (buf.data (), buf.size (), "QTDP:%x:%s:%c:%lx:%x", b->number, addrbuf, /* address */ (b->enable_state == bp_enabled ? 'E' : 'D'), @@ -14027,7 +14422,7 @@ remote_target::enable_tracepoint (struct bp_location *location) xsnprintf (rs->buf.data (), get_remote_packet_size (), "QTEnable:%x:%s", location->owner->number, - phex (location->address, sizeof (CORE_ADDR))); + phex (location->address)); putpkt (rs->buf); remote_get_noisy_reply (); if (rs->buf[0] == '\0') @@ -14043,7 +14438,7 @@ remote_target::disable_tracepoint (struct bp_location *location) xsnprintf (rs->buf.data (), get_remote_packet_size (), "QTDisable:%x:%s", location->owner->number, - phex (location->address, sizeof (CORE_ADDR))); + phex (location->address)); putpkt (rs->buf); remote_get_noisy_reply (); if (rs->buf[0] == '\0') @@ -15083,10 +15478,10 @@ remote_target::remote_btrace_maybe_reopen () if (m_features.packet_support (PACKET_qXfer_btrace_conf) != PACKET_ENABLE) return; - for (thread_info *tp : all_non_exited_threads (this)) + for (thread_info &tp : all_non_exited_threads (this)) { memset (&rs->btrace_config, 0x00, sizeof (struct btrace_config)); - btrace_read_config (tp, &rs->btrace_config); + btrace_read_config (&tp, &rs->btrace_config); if (rs->btrace_config.format == BTRACE_FORMAT_NONE) continue; @@ -15116,8 +15511,7 @@ remote_target::remote_btrace_maybe_reopen () btrace_format_string (rs->btrace_config.format)); } - tp->btrace.target - = new btrace_target_info { tp->ptid, rs->btrace_config }; + tp.btrace.target = new btrace_target_info { tp.ptid, rs->btrace_config }; } } @@ -15353,18 +15747,18 @@ remote_target::thread_handle_to_thread_info (const gdb_byte *thread_handle, int handle_len, inferior *inf) { - for (thread_info *tp : all_non_exited_threads (this)) + for (thread_info &tp : all_non_exited_threads (this)) { - remote_thread_info *priv = get_remote_thread_info (tp); + remote_thread_info *priv = get_remote_thread_info (&tp); - if (tp->inf == inf && priv != NULL) + if (tp.inf == inf && priv != NULL) { if (handle_len != priv->thread_handle.size ()) error (_("Thread handle size mismatch: %d vs %zu (from remote)"), handle_len, priv->thread_handle.size ()); if (memcmp (thread_handle, priv->thread_handle.data (), handle_len) == 0) - return tp; + return &tp; } } @@ -15551,9 +15945,9 @@ remote_target::commit_requested_thread_options () /* Now set non-zero options for threads that need them. We don't bother with the case of all threads of a process wanting the same non-zero options as that's not an expected scenario. */ - for (thread_info *tp : all_non_exited_threads (this)) + for (thread_info &tp : all_non_exited_threads (this)) { - gdb_thread_options options = tp->thread_options (); + gdb_thread_options options = tp.thread_options (); if (options == 0) continue; @@ -15569,11 +15963,11 @@ remote_target::commit_requested_thread_options () *obuf_p++ = ';'; obuf_p += xsnprintf (obuf_p, obuf_endp - obuf_p, "%s", - phex_nz (options, sizeof (options))); - if (tp->ptid != magic_null_ptid) + phex_nz (options)); + if (tp.ptid != magic_null_ptid) { *obuf_p++ = ':'; - obuf_p = write_ptid (obuf_p, obuf_endp, tp->ptid); + obuf_p = write_ptid (obuf_p, obuf_endp, tp.ptid); } size_t osize = obuf_p - obuf; @@ -15808,8 +16202,8 @@ create_fetch_memtags_request (gdb::char_vector &packet, CORE_ADDR address, std::string request = string_printf ("qMemTags:%s,%s:%s", phex_nz (address, addr_size), - phex_nz (len, sizeof (len)), - phex_nz (type, sizeof (type))); + phex_nz (len), + phex_nz (type)); strcpy (packet.data (), request.c_str ()); } @@ -15843,8 +16237,8 @@ create_store_memtags_request (gdb::char_vector &packet, CORE_ADDR address, /* Put together the main packet, address and length. */ std::string request = string_printf ("QMemTags:%s,%s:%s:", phex_nz (address, addr_size), - phex_nz (len, sizeof (len)), - phex_nz (type, sizeof (type))); + phex_nz (len), + phex_nz (type)); request += bin2hex (tags.data (), tags.size ()); /* Check if we have exceeded the maximum packet size. */ @@ -16161,9 +16555,7 @@ test_packet_check_result () } /* namespace selftests */ #endif /* GDB_SELF_TEST */ -void _initialize_remote (); -void -_initialize_remote () +INIT_GDB_FILE (remote) { add_target (remote_target_info, remote_target::open); add_target (extended_remote_target_info, extended_remote_target::open); @@ -16422,6 +16814,8 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL, add_packet_config_cmd (PACKET_vFile_stat, "vFile:stat", "hostio-stat", 0); + add_packet_config_cmd (PACKET_vFile_lstat, "vFile:lstat", "hostio-lstat", 0); + add_packet_config_cmd (PACKET_vAttach, "vAttach", "attach", 0); add_packet_config_cmd (PACKET_vRun, "vRun", "run", 0); @@ -16539,6 +16933,13 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL, add_packet_config_cmd (PACKET_accept_error_message, "error-message", "error-message", 0); + add_packet_config_cmd (PACKET_vRun_single_argument, + "single-inferior-argument-feature", + "single-inferior-argument-feature", 0); + + add_packet_config_cmd (PACKET_qExecAndArgs, "qExecAndArgs", + "fetch-exec-and-args", 0); + /* Assert that we've registered "set remote foo-packet" commands for all packet configs. */ { @@ -16608,10 +17009,15 @@ Transfer files to and from the remote target system."), &remote_cmdlist); add_setshow_string_noescape_cmd ("exec-file", class_files, - &remote_exec_file_var, _("\ -Set the remote pathname for \"run\"."), _("\ -Show the remote pathname for \"run\"."), NULL, - set_remote_exec_file, + _("\ +Set the remote file name for starting inferiors."), _("\ +Show the remote file name for starting inferiors."), _("\ +This is the file name, on the remote target, used when starting an\n\ +inferior, for example with the \"run\", \"start\", or \"starti\"\n\ +commands. This setting is only useful when debugging a remote target,\n\ +otherwise, this setting is not used."), + set_remote_exec_file_cb, + get_remote_exec_file, show_remote_exec_file, &remote_set_cmdlist, &remote_show_cmdlist); @@ -16671,6 +17077,20 @@ from the target."), /* Eventually initialize fileio. See fileio.c */ initialize_remote_fileio (&remote_set_cmdlist, &remote_show_cmdlist); + add_cmd ("test-remote-args", class_maintenance, + test_remote_args_command, _("\ +Test remote argument splitting and joining.\n \ + maintenance test-remote-args ARGS\n\ +For remote targets that don't support passing inferior arguments as a\n\ +single string, GDB needs to split the inferior arguments before passing\n\ +them, and gdbserver needs to join the arguments it receives.\n\ +This command splits ARGS just as GDB would before passing them to a\n\ +remote target, and prints the result. This command then joins the\n\ +arguments just as gdbserver would, and prints the results.\n\ +This command is useful in diagnosing problems when passing arguments\n\ +between GDB and a remote target."), + &maintenancelist); + #if GDB_SELF_TEST selftests::register_test ("remote_memory_tagging", selftests::test_memory_tagging_functions); |