diff options
-rw-r--r-- | gdb/ChangeLog | 54 | ||||
-rw-r--r-- | gdb/NEWS | 28 | ||||
-rw-r--r-- | gdb/common/environ.c | 75 | ||||
-rw-r--r-- | gdb/common/environ.h | 24 | ||||
-rw-r--r-- | gdb/common/rsp-low.c | 41 | ||||
-rw-r--r-- | gdb/common/rsp-low.h | 8 | ||||
-rw-r--r-- | gdb/doc/ChangeLog | 11 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 116 | ||||
-rw-r--r-- | gdb/gdbserver/ChangeLog | 7 | ||||
-rw-r--r-- | gdb/gdbserver/server.c | 65 | ||||
-rw-r--r-- | gdb/remote.c | 76 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 5 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/share-env-with-gdbserver.c | 40 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/share-env-with-gdbserver.exp | 255 | ||||
-rw-r--r-- | gdb/unittests/environ-selftests.c | 244 |
15 files changed, 984 insertions, 65 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 1d50e2c..9d153fa 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,57 @@ +2017-08-31 Sergio Durigan Junior <sergiodj@redhat.com> + + * NEWS (Changes since GDB 8.0): Add entry mentioning new support + for setting/unsetting environment variables on the remote target. + (New remote packets): Add entries for QEnvironmentHexEncoded, + QEnvironmentUnset and QEnvironmentReset. + * common/environ.c (gdb_environ::operator=): Extend method to + handle m_user_set_env_list and m_user_unset_env_list. + (gdb_environ::clear): Likewise. + (match_var_in_string): Change type of first parameter from 'char + *' to 'const char *'. + (gdb_environ::set): Extend method to handle + m_user_set_env_list and m_user_unset_env_list. + (gdb_environ::unset): Likewise. + (gdb_environ::clear_user_set_env): New method. + (gdb_environ::user_set_envp): Likewise. + (gdb_environ::user_unset_envp): Likewise. + * common/environ.h (gdb_environ): Handle m_user_set_env_list and + m_user_unset_env_list on move constructor/assignment. + (unset): Add new default parameter 'update_unset_list = true'. + (clear_user_set_env): New method. + (user_set_envp): Likewise. + (user_unset_envp): Likewise. + (m_user_set_env_list): New std::set. + (m_user_unset_env_list): Likewise. + * common/rsp-low.c (hex2str): New function. + (bin2hex): New overload for bin2hex function. + * common/rsp-low.c (hex2str): New prototype. + (str2hex): New overload prototype. + * remote.c: Include "environ.h". Add QEnvironmentHexEncoded, + QEnvironmentUnset and QEnvironmentReset. + (remote_protocol_features): Add QEnvironmentHexEncoded, + QEnvironmentUnset and QEnvironmentReset packets. + (send_environment_packet): New function. + (extended_remote_environment_support): Likewise. + (extended_remote_create_inferior): Call + extended_remote_environment_support. + (_initialize_remote): Add QEnvironmentHexEncoded, + QEnvironmentUnset and QEnvironmentReset packet configs. + * unittests/environ-selftests.c (gdb_selftest_env_var): + New variable. + (test_vector_initialization): New function. + (test_init_from_host_environ): Likewise. + (test_reinit_from_host_environ): Likewise. + (test_set_A_unset_B_unset_A_cannot_find_A_can_find_B): + Likewise. + (test_unset_set_empty_vector): Likewise. + (test_vector_clear): Likewise. + (test_std_move): Likewise. + (test_move_constructor): + (test_self_move): Likewise. + (test_set_unset_reset): Likewise. + (run_tests): Rewrite in terms of the functions above. + 2017-08-31 Weimin Pan <weimin.pan@oracle.com> * sparc64-tdep.c (adi_stat_t): Fix comment formatting. @@ -3,6 +3,18 @@ *** Changes since GDB 8.0 +* On Unix systems, GDB now supports transmitting environment variables + that are to be set or unset to GDBserver. These variables will + affect the environment to be passed to the remote inferior. + + To inform GDB of environment variables that are to be transmitted to + GDBserver, use the "set environment" command. Only user set + environment variables are sent to GDBserver. + + To inform GDB of environment variables that are to be unset before + the remote inferior is started by the GDBserver, use the "unset + environment" command. + * New features in the GDB remote stub, GDBserver ** New "--selftest" command line option runs some GDBserver self @@ -17,8 +29,24 @@ "target remote", you can disable the startup with shell by using the new "--no-startup-with-shell" GDBserver command line option. + ** On Unix systems, GDBserver now supports receiving environment + variables that are to be set or unset from GDB. These variables + will affect the environment to be passed to the inferior. + * New remote packets +QEnvironmentHexEncoded + Inform GDBserver of an environment variable that is to be passed to + the inferior when starting it. + +QEnvironmentUnset + Inform GDBserver of an environment variable that is to be unset + before starting the remote inferior. + +QEnvironmentReset + Inform GDBserver that the environment should be reset (i.e., + user-set environment variables should be unset). + QStartupWithShell Indicates whether the inferior must be started with a shell or not. diff --git a/gdb/common/environ.c b/gdb/common/environ.c index 698bda3..7753f6e 100644 --- a/gdb/common/environ.c +++ b/gdb/common/environ.c @@ -30,8 +30,12 @@ gdb_environ::operator= (gdb_environ &&e) return *this; m_environ_vector = std::move (e.m_environ_vector); + m_user_set_env = std::move (e.m_user_set_env); + m_user_unset_env = std::move (e.m_user_unset_env); e.m_environ_vector.clear (); e.m_environ_vector.push_back (NULL); + e.m_user_set_env.clear (); + e.m_user_unset_env.clear (); return *this; } @@ -65,6 +69,8 @@ gdb_environ::clear () m_environ_vector.clear (); /* Always add the NULL element. */ m_environ_vector.push_back (NULL); + m_user_set_env.clear (); + m_user_unset_env.clear (); } /* Helper function to check if STRING contains an environment variable @@ -72,7 +78,7 @@ gdb_environ::clear () if it contains, false otherwise. */ static bool -match_var_in_string (char *string, const char *var, size_t var_len) +match_var_in_string (const char *string, const char *var, size_t var_len) { if (strncmp (string, var, var_len) == 0 && string[var_len] == '=') return true; @@ -99,32 +105,59 @@ gdb_environ::get (const char *var) const void gdb_environ::set (const char *var, const char *value) { + char *fullvar = concat (var, "=", value, NULL); + /* We have to unset the variable in the vector if it exists. */ - unset (var); + unset (var, false); /* Insert the element before the last one, which is always NULL. */ - m_environ_vector.insert (m_environ_vector.end () - 1, - concat (var, "=", value, NULL)); + m_environ_vector.insert (m_environ_vector.end () - 1, fullvar); + + /* Mark this environment variable as having been set by the user. + This will be useful when we deal with setting environment + variables on the remote target. */ + m_user_set_env.insert (std::string (fullvar)); + + /* If this environment variable is marked as unset by the user, then + remove it from the list, because now the user wants to set + it. */ + m_user_unset_env.erase (std::string (var)); } /* See common/environ.h. */ void -gdb_environ::unset (const char *var) +gdb_environ::unset (const char *var, bool update_unset_list) { size_t len = strlen (var); + std::vector<char *>::iterator it_env; /* We iterate until '.end () - 1' because the last element is always NULL. */ - for (std::vector<char *>::iterator el = m_environ_vector.begin (); - el != m_environ_vector.end () - 1; - ++el) - if (match_var_in_string (*el, var, len)) - { - xfree (*el); - m_environ_vector.erase (el); - break; - } + for (it_env = m_environ_vector.begin (); + it_env != m_environ_vector.end () - 1; + ++it_env) + if (match_var_in_string (*it_env, var, len)) + break; + + if (it_env != m_environ_vector.end () - 1) + { + m_user_set_env.erase (std::string (*it_env)); + xfree (*it_env); + + m_environ_vector.erase (it_env); + } + + if (update_unset_list) + m_user_unset_env.insert (std::string (var)); +} + +/* See common/environ.h. */ + +void +gdb_environ::unset (const char *var) +{ + unset (var, true); } /* See common/environ.h. */ @@ -134,3 +167,17 @@ gdb_environ::envp () const { return const_cast<char **> (&m_environ_vector[0]); } + +/* See common/environ.h. */ + +const std::set<std::string> & +gdb_environ::user_set_env () const +{ + return m_user_set_env; +} + +const std::set<std::string> & +gdb_environ::user_unset_env () const +{ + return m_user_unset_env; +} diff --git a/gdb/common/environ.h b/gdb/common/environ.h index 0bbb191..4003f4e 100644 --- a/gdb/common/environ.h +++ b/gdb/common/environ.h @@ -18,6 +18,7 @@ #define ENVIRON_H 1 #include <vector> +#include <set> /* Class that represents the environment variables as seen by the inferior. */ @@ -41,12 +42,16 @@ public: /* Move constructor. */ gdb_environ (gdb_environ &&e) - : m_environ_vector (std::move (e.m_environ_vector)) + : m_environ_vector (std::move (e.m_environ_vector)), + m_user_set_env (std::move (e.m_user_set_env)), + m_user_unset_env (std::move (e.m_user_unset_env)) { /* Make sure that the moved-from vector is left at a valid state (only one NULL element). */ e.m_environ_vector.clear (); e.m_environ_vector.push_back (NULL); + e.m_user_set_env.clear (); + e.m_user_unset_env.clear (); } /* Move assignment. */ @@ -73,9 +78,26 @@ public: /* Return the environment vector represented as a 'char **'. */ char **envp () const; + /* Return the user-set environment vector. */ + const std::set<std::string> &user_set_env () const; + + /* Return the user-unset environment vector. */ + const std::set<std::string> &user_unset_env () const; + private: + /* Unset VAR in environment. If UPDATE_UNSET_LIST is true, then + also update M_USER_UNSET_ENV to reflect the unsetting of the + environment variable. */ + void unset (const char *var, bool update_unset_list); + /* A vector containing the environment variables. */ std::vector<char *> m_environ_vector; + + /* The environment variables explicitly set by the user. */ + std::set<std::string> m_user_set_env; + + /* The environment variables explicitly unset by the user. */ + std::set<std::string> m_user_unset_env; }; #endif /* defined (ENVIRON_H) */ diff --git a/gdb/common/rsp-low.c b/gdb/common/rsp-low.c index eb85ab5..4befeb1 100644 --- a/gdb/common/rsp-low.c +++ b/gdb/common/rsp-low.c @@ -132,6 +132,30 @@ hex2bin (const char *hex, gdb_byte *bin, int count) /* See rsp-low.h. */ +std::string +hex2str (const char *hex) +{ + std::string ret; + size_t len = strlen (hex); + + ret.reserve (len / 2); + for (size_t i = 0; i < len; ++i) + { + if (hex[0] == '\0' || hex[1] == '\0') + { + /* Hex string is short, or of uneven length. Return what we + have so far. */ + return ret; + } + ret += fromhex (hex[0]) * 16 + fromhex (hex[1]); + hex += 2; + } + + return ret; +} + +/* See rsp-low.h. */ + int bin2hex (const gdb_byte *bin, char *hex, int count) { @@ -146,6 +170,23 @@ bin2hex (const gdb_byte *bin, char *hex, int count) return i; } +/* See rsp-low.h. */ + +std::string +bin2hex (const gdb_byte *bin, int count) +{ + std::string ret; + + ret.reserve (count * 2); + for (int i = 0; i < count; ++i) + { + ret += tohex ((*bin >> 4) & 0xf); + ret += tohex (*bin++ & 0xf); + } + + return ret; +} + /* Return whether byte B needs escaping when sent as part of binary data. */ static int diff --git a/gdb/common/rsp-low.h b/gdb/common/rsp-low.h index b57f58b..2b3685f 100644 --- a/gdb/common/rsp-low.h +++ b/gdb/common/rsp-low.h @@ -52,6 +52,10 @@ extern char *unpack_varlen_hex (char *buff, ULONGEST *result); extern int hex2bin (const char *hex, gdb_byte *bin, int count); +/* Like hex2bin, but return a std::string. */ + +extern std::string hex2str (const char *hex); + /* Convert some bytes to a hexadecimal representation. BIN holds the bytes to convert. COUNT says how many bytes to convert. The resulting characters are stored in HEX, followed by a NUL @@ -59,6 +63,10 @@ extern int hex2bin (const char *hex, gdb_byte *bin, int count); extern int bin2hex (const gdb_byte *bin, char *hex, int count); +/* Overloaded version of bin2hex that returns a std::string. */ + +extern std::string bin2hex (const gdb_byte *bin, int count); + /* Convert BUFFER, binary data at least LEN_UNITS addressable memory units long, into escaped binary data in OUT_BUF. Only copy memory units that fit completely in OUT_BUF. Set *OUT_LEN_UNITS to the number of units from diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index bf82730..106d545 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,14 @@ +2017-08-31 Sergio Durigan Junior <sergiodj@redhat.com> + + * gdb.texinfo (set environment): Add @anchor. Explain that + environment variables set by the user are sent to GDBserver. + (unset environment): Likewise, but for unsetting variables. + (Connecting) <Remote Packet>: Add "environment-hex-encoded", + "QEnvironmentHexEncoded", "environment-unset", "QEnvironmentUnset", + "environment-reset" and "QEnvironmentReset" to the table. + (Remote Protocol) <QEnvironmentHexEncoded, QEnvironmentUnset, + QEnvironmentReset>: New item, explaining the packet. + 2017-08-23 Jan Kratochvil <jan.kratochvil@redhat.com> * gdb.texinfo (Compiling and Injecting Code): Add to subsection diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index d977b23..874cdeb 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -2363,6 +2363,7 @@ print the names and values of all environment variables to be given to your program. You can abbreviate @code{environment} as @code{env}. @kindex set environment +@anchor{set environment} @item set environment @var{varname} @r{[}=@var{value}@r{]} Set environment variable @var{varname} to @var{value}. The value changes for your program (and the shell @value{GDBN} uses to launch @@ -2391,12 +2392,21 @@ If necessary, you can avoid that by using the @samp{env} program as a wrapper instead of using @code{set environment}. @xref{set exec-wrapper}, for an example doing just that. +Environment variables that are set by the user are also transmitted to +@command{gdbserver} to be used when starting the remote inferior. +@pxref{QEnvironmentHexEncoded}. + @kindex unset environment +@anchor{unset environment} @item unset environment @var{varname} Remove variable @var{varname} from the environment to be passed to your program. This is different from @samp{set env @var{varname} =}; @code{unset environment} removes the variable from the environment, rather than assigning it an empty value. + +Environment variables that are unset by the user are also unset on +@command{gdbserver} when starting the remote inferior. +@pxref{QEnvironmentUnset}. @end table @emph{Warning:} On Unix systems, @value{GDBN} runs your program using @@ -20849,6 +20859,18 @@ are: @tab @code{QStartupWithShell} @tab @code{set startup-with-shell} +@item @code{environment-hex-encoded} +@tab @code{QEnvironmentHexEncoded} +@tab @code{set environment} + +@item @code{environment-unset} +@tab @code{QEnvironmentUnset} +@tab @code{unset environment} + +@item @code{environment-reset} +@tab @code{QEnvironmentReset} +@tab @code{Reset the inferior environment (i.e., unset user-set variables)} + @item @code{conditional-breakpoints-packet} @tab @code{Z0 and Z1} @tab @code{Support for target-side breakpoint condition evaluation} @@ -36604,6 +36626,100 @@ actually support starting the inferior using a shell. Use of this packet is controlled by the @code{set startup-with-shell} command; @pxref{set startup-with-shell}. +@item QEnvironmentHexEncoded:@var{hex-value} +@anchor{QEnvironmentHexEncoded} +@cindex set environment variable, remote request +@cindex @samp{QEnvironmentHexEncoded} packet +On UNIX-like targets, it is possible to set environment variables that +will be passed to the inferior during the startup process. This +packet is used to inform @command{gdbserver} of an environment +variable that has been defined by the user on @value{GDBN} (@pxref{set +environment}). + +The packet is composed by @var{hex-value}, an hex encoded +representation of the @var{name=value} format representing an +environment variable. The name of the environment variable is +represented by @var{name}, and the value to be assigned to the +environment variable is represented by @var{value}. If the variable +has no value (i.e., the value is @code{null}), then @var{value} will +not be present. + +This packet is only available in extended mode (@pxref{extended +mode}). + +Reply: +@table @samp +@item OK +The request succeeded. +@end table + +This packet is not probed by default; the remote stub must request it, +by supplying an appropriate @samp{qSupported} response +(@pxref{qSupported}). This should only be done on targets that +actually support passing environment variables to the starting +inferior. + +This packet is related to the @code{set environment} command; +@pxref{set environment}. + +@item QEnvironmentUnset:@var{hex-value} +@anchor{QEnvironmentUnset} +@cindex unset environment variable, remote request +@cindex @samp{QEnvironmentUnset} packet +On UNIX-like targets, it is possible to unset environment variables +before starting the inferior in the remote target. This packet is +used to inform @command{gdbserver} of an environment variable that has +been unset by the user on @value{GDBN} (@pxref{unset environment}). + +The packet is composed by @var{hex-value}, an hex encoded +representation of the name of the environment variable to be unset. + +This packet is only available in extended mode (@pxref{extended +mode}). + +Reply: +@table @samp +@item OK +The request succeeded. +@end table + +This packet is not probed by default; the remote stub must request it, +by supplying an appropriate @samp{qSupported} response +(@pxref{qSupported}). This should only be done on targets that +actually support passing environment variables to the starting +inferior. + +This packet is related to the @code{unset environment} command; +@pxref{unset environment}. + +@item QEnvironmentReset +@anchor{QEnvironmentReset} +@cindex reset environment, remote request +@cindex @samp{QEnvironmentReset} packet +On UNIX-like targets, this packet is used to reset the state of +environment variables in the remote target before starting the +inferior. In this context, reset means unsetting all environment +variables that were previously set by the user (i.e., were not +initially present in the environment). It is sent to +@command{gdbserver} before the @samp{QEnvironmentHexEncoded} +(@pxref{QEnvironmentHexEncoded}) and the @samp{QEnvironmentUnset} +(@pxref{QEnvironmentUnset}) packets. + +This packet is only available in extended mode (@pxref{extended +mode}). + +Reply: +@table @samp +@item OK +The request succeeded. +@end table + +This packet is not probed by default; the remote stub must request it, +by supplying an appropriate @samp{qSupported} response +(@pxref{qSupported}). This should only be done on targets that +actually support passing environment variables to the starting +inferior. + @item qfThreadInfo @itemx qsThreadInfo @cindex list active threads, remote request diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog index 117ba5f..492f0ab 100644 --- a/gdb/gdbserver/ChangeLog +++ b/gdb/gdbserver/ChangeLog @@ -1,3 +1,10 @@ +2017-08-31 Sergio Durigan Junior <sergiodj@redhat.com> + + * server.c (handle_general_set): Handle QEnvironmentHexEncoded, + QEnvironmentUnset and QEnvironmentReset packets. + (handle_query): Inform remote that QEnvironmentHexEncoded, + QEnvironmentUnset and QEnvironmentReset are supported. + 2017-08-25 Simon Marchi <simon.marchi@ericsson.com> * inferiors.h (inferior_target_data): Rename to ... diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c index 8200aa1..88a5ddc 100644 --- a/gdb/gdbserver/server.c +++ b/gdb/gdbserver/server.c @@ -633,6 +633,67 @@ handle_general_set (char *own_buf) return; } + if (strcmp (own_buf, "QEnvironmentReset") == 0) + { + our_environ = gdb_environ::from_host_environ (); + + write_ok (own_buf); + return; + } + + if (startswith (own_buf, "QEnvironmentHexEncoded:")) + { + const char *p = own_buf + sizeof ("QEnvironmentHexEncoded:") - 1; + /* The final form of the environment variable. FINAL_VAR will + hold the 'VAR=VALUE' format. */ + std::string final_var = hex2str (p); + std::string var_name, var_value; + + if (remote_debug) + { + debug_printf (_("[QEnvironmentHexEncoded received '%s']\n"), p); + debug_printf (_("[Environment variable to be set: '%s']\n"), + final_var.c_str ()); + debug_flush (); + } + + size_t pos = final_var.find ('='); + if (pos == std::string::npos) + { + warning (_("Unexpected format for environment variable: '%s'"), + final_var.c_str ()); + write_enn (own_buf); + return; + } + + var_name = final_var.substr (0, pos); + var_value = final_var.substr (pos + 1, std::string::npos); + + our_environ.set (var_name.c_str (), var_value.c_str ()); + + write_ok (own_buf); + return; + } + + if (startswith (own_buf, "QEnvironmentUnset:")) + { + const char *p = own_buf + sizeof ("QEnvironmentUnset:") - 1; + std::string varname = hex2str (p); + + if (remote_debug) + { + debug_printf (_("[QEnvironmentUnset received '%s']\n"), p); + debug_printf (_("[Environment variable to be unset: '%s']\n"), + varname.c_str ()); + debug_flush (); + } + + our_environ.unset (varname.c_str ()); + + write_ok (own_buf); + return; + } + if (strcmp (own_buf, "QStartNoAckMode") == 0) { if (remote_debug) @@ -2230,7 +2291,9 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p) } sprintf (own_buf, - "PacketSize=%x;QPassSignals+;QProgramSignals+;QStartupWithShell+", + "PacketSize=%x;QPassSignals+;QProgramSignals+;" + "QStartupWithShell+;QEnvironmentHexEncoded+;" + "QEnvironmentReset+;QEnvironmentUnset+", PBUFSIZ - 1); if (target_supports_catch_syscall ()) diff --git a/gdb/remote.c b/gdb/remote.c index 2249533..3347dd8 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -73,6 +73,7 @@ #include "record-btrace.h" #include <algorithm> #include "common/scoped_restore.h" +#include "environ.h" /* Temp hacks for tracepoint encoding migration. */ static char *target_buf; @@ -1430,6 +1431,9 @@ enum { PACKET_QCatchSyscalls, PACKET_QProgramSignals, PACKET_QStartupWithShell, + PACKET_QEnvironmentHexEncoded, + PACKET_QEnvironmentReset, + PACKET_QEnvironmentUnset, PACKET_qCRC, PACKET_qSearch_memory, PACKET_vAttach, @@ -4637,6 +4641,12 @@ static const struct protocol_feature remote_protocol_features[] = { PACKET_QProgramSignals }, { "QStartupWithShell", PACKET_DISABLE, remote_supported_packet, PACKET_QStartupWithShell }, + { "QEnvironmentHexEncoded", PACKET_DISABLE, remote_supported_packet, + PACKET_QEnvironmentHexEncoded }, + { "QEnvironmentReset", PACKET_DISABLE, remote_supported_packet, + PACKET_QEnvironmentReset }, + { "QEnvironmentUnset", PACKET_DISABLE, remote_supported_packet, + PACKET_QEnvironmentUnset }, { "QStartNoAckMode", PACKET_DISABLE, remote_supported_packet, PACKET_QStartNoAckMode }, { "multiprocess", PACKET_DISABLE, remote_supported_packet, @@ -9556,6 +9566,57 @@ extended_remote_run (const std::string &args) } } +/* Helper function to send set/unset environment packets. ACTION is + either "set" or "unset". PACKET is either "QEnvironmentHexEncoded" + or "QEnvironmentUnsetVariable". VALUE is the variable to be + sent. */ + +static void +send_environment_packet (struct remote_state *rs, + const char *action, + const char *packet, + const char *value) +{ + /* Convert the environment variable to an hex string, which + is the best format to be transmitted over the wire. */ + std::string encoded_value = bin2hex ((const gdb_byte *) value, + strlen (value)); + + xsnprintf (rs->buf, get_remote_packet_size (), + "%s:%s", packet, encoded_value.c_str ()); + + putpkt (rs->buf); + getpkt (&rs->buf, &rs->buf_size, 0); + if (strcmp (rs->buf, "OK") != 0) + warning (_("Unable to %s environment variable '%s' on remote."), + action, value); +} + +/* Helper function to handle the QEnvironment* packets. */ + +static void +extended_remote_environment_support (struct remote_state *rs) +{ + if (packet_support (PACKET_QEnvironmentReset) != PACKET_DISABLE) + { + putpkt ("QEnvironmentReset"); + getpkt (&rs->buf, &rs->buf_size, 0); + if (strcmp (rs->buf, "OK") != 0) + warning (_("Unable to reset environment on remote.")); + } + + gdb_environ *e = ¤t_inferior ()->environment; + + if (packet_support (PACKET_QEnvironmentHexEncoded) != PACKET_DISABLE) + for (const std::string &el : e->user_set_env ()) + send_environment_packet (rs, "set", "QEnvironmentHexEncoded", + el.c_str ()); + + if (packet_support (PACKET_QEnvironmentUnset) != PACKET_DISABLE) + for (const std::string &el : e->user_unset_env ()) + send_environment_packet (rs, "unset", "QEnvironmentUnset", el.c_str ()); +} + /* In the extended protocol we want to be able to do things like "run" and have them basically work as expected. So we need a special create_inferior function. We support changing the @@ -9596,6 +9657,8 @@ Remote replied unexpectedly while setting startup-with-shell: %s"), rs->buf); } + extended_remote_environment_support (rs); + /* Now restart the remote server. */ run_worked = extended_remote_run (args) != -1; if (!run_worked) @@ -14067,6 +14130,19 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL, add_packet_config_cmd (&remote_protocol_packets[PACKET_QStartupWithShell], "QStartupWithShell", "startup-with-shell", 0); + add_packet_config_cmd (&remote_protocol_packets + [PACKET_QEnvironmentHexEncoded], + "QEnvironmentHexEncoded", "environment-hex-encoded", + 0); + + add_packet_config_cmd (&remote_protocol_packets[PACKET_QEnvironmentReset], + "QEnvironmentReset", "environment-reset", + 0); + + add_packet_config_cmd (&remote_protocol_packets[PACKET_QEnvironmentUnset], + "QEnvironmentUnset", "environment-unset", + 0); + add_packet_config_cmd (&remote_protocol_packets[PACKET_qSymbol], "qSymbol", "symbol-lookup", 0); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index aa1d6b8..c15743b 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2017-08-31 Sergio Durigan Junior <sergiodj@redhat.com> + + * gdb.base/share-env-with-gdbserver.c: New file. + * gdb.base/share-env-with-gdbserver.exp: Likewise. + 2017-08-28 Simon Marchi <simon.marchi@ericsson.com> * gdb.base/commands.exp (gdbvar_simple_if_test, diff --git a/gdb/testsuite/gdb.base/share-env-with-gdbserver.c b/gdb/testsuite/gdb.base/share-env-with-gdbserver.c new file mode 100644 index 0000000..740bd0f --- /dev/null +++ b/gdb/testsuite/gdb.base/share-env-with-gdbserver.c @@ -0,0 +1,40 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2017 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 <http://www.gnu.org/licenses/>. */ + +#include <stdio.h> +#include <stdlib.h> + +/* Wrapper around getenv for GDB. */ + +static const char * +my_getenv (const char *name) +{ + return getenv (name); +} + +int +main (int argc, char *argv[]) +{ + const char *myvar = getenv ("GDB_TEST_VAR"); + + if (myvar != NULL) + printf ("It worked! myvar = '%s'\n", myvar); + else + printf ("It failed."); + + return 0; /* break-here */ +} diff --git a/gdb/testsuite/gdb.base/share-env-with-gdbserver.exp b/gdb/testsuite/gdb.base/share-env-with-gdbserver.exp new file mode 100644 index 0000000..7521be1 --- /dev/null +++ b/gdb/testsuite/gdb.base/share-env-with-gdbserver.exp @@ -0,0 +1,255 @@ +# This testcase is part of GDB, the GNU debugger. + +# Copyright 2017 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 <http://www.gnu.org/licenses/>. + +# This test doesn't make sense on native-gdbserver. +if { [use_gdb_stub] } { + untested "not supported" + return +} + +standard_testfile + +if { [prepare_for_testing "failed to prepare" $testfile $srcfile debug] } { + return -1 +} + +set test_var_name "GDB_TEST_VAR" + +# Helper function that performs a check on the output of "getenv". +# +# - VAR_NAME is the name of the variable to be checked. +# +# - VAR_VALUE is the value expected. +# +# - TEST_MSG, if not empty, is the test message to be used by the +# "gdb_test". +# +# - EMPTY_VAR_P, if non-zero, means that the variable is not expected +# to exist. In this case, VAR_VALUE is not considered. + +proc check_getenv { var_name var_value { test_msg "" } { empty_var_p 0 } } { + global hex decimal + + if { $test_msg == "" } { + set test_msg "print result of getenv for $var_name" + } + + if { $empty_var_p } { + set var_value_match "0x0" + } else { + set var_value_match "$hex \"$var_value\"" + } + + gdb_test "print my_getenv (\"$var_name\")" "\\\$$decimal = $var_value_match" \ + $test_msg +} + +# Helper function to re-run to main and breaking at the "break-here" +# label. + +proc do_prepare_inferior { } { + global decimal hex + + if { ![runto_main] } { + return -1 + } + + gdb_breakpoint [gdb_get_line_number "break-here"] + + gdb_test "continue" "Breakpoint $decimal, main \\\(argc=1, argv=$hex\\\) at.*" \ + "continue until breakpoint" +} + +# Helper function that does the actual testing. +# +# - VAR_VALUE is the value of the environment variable. +# +# - VAR_NAME is the name of the environment variable. If empty, +# defaults to $test_var_name. +# +# - VAR_NAME_MATCH is the name (regex) that will be used to query the +# environment about the variable (via getenv). This is useful when +# we're testing variables with strange names (e.g., with an equal +# sign in the name) and we know that the variable will actually be +# set using another name. If empty, defatults, to $var_name. +# +# - VAR_VALUE_MATCH is the value (regex) that will be used to match +# the result of getenv. The rationale is the same as explained for +# VAR_NAME_MATCH. If empty, defaults, to $var_value. + +proc do_test { var_value { var_name "" } { var_name_match "" } { var_value_match "" } } { + global binfile test_var_name + + clean_restart $binfile + + if { $var_name == "" } { + set var_name $test_var_name + } + + if { $var_name_match == "" } { + set var_name_match $var_name + } + + if { $var_value_match == "" } { + set var_value_match $var_value + } + + if { $var_value != "" } { + gdb_test_no_output "set environment $var_name = $var_value" \ + "set $var_name = $var_value" + } else { + gdb_test "set environment $var_name =" \ + "Setting environment variable \"$var_name\" to null value." \ + "set $var_name to null value" + } + + do_prepare_inferior + + check_getenv "$var_name_match" "$var_value_match" \ + "print result of getenv for $var_name" +} + +with_test_prefix "long var value" { + do_test "this is my test variable; testing long vars; {}" +} + +with_test_prefix "empty var" { + do_test "" +} + +with_test_prefix "strange named var" { + # In this test we're doing the following: + # + # (gdb) set environment 'asd =' = 123 43; asd b ### [];;; + # + # However, due to how GDB parses this line, the environment + # variable will end up named <'asd> (without the <>), and its + # value will be <' = 123 43; asd b ### [];;;> (without the <>). + do_test "123 43; asd b ### \[\];;;" "'asd ='" "'asd" \ + [string_to_regexp "' = 123 43; asd b ### \[\];;;"] +} + +# Test setting and unsetting environment variables in various +# fashions. + +proc test_set_unset_vars { } { + global binfile + + clean_restart $binfile + + with_test_prefix "set 3 environment variables" { + # Set some environment variables + gdb_test_no_output "set environment A = 1" \ + "set A to 1" + gdb_test_no_output "set environment B = 2" \ + "set B to 2" + gdb_test_no_output "set environment C = 3" \ + "set C to 3" + + do_prepare_inferior + + # Check that the variables are known by the inferior + check_getenv "A" "1" + check_getenv "B" "2" + check_getenv "C" "3" + } + + with_test_prefix "unset one variable, reset one" { + # Now, unset/reset some values + gdb_test_no_output "unset environment A" \ + "unset A" + gdb_test_no_output "set environment B = 4" \ + "set B to 4" + + do_prepare_inferior + + check_getenv "A" "" "" 1 + check_getenv "B" "4" + check_getenv "C" "3" + } + + with_test_prefix "unset two variables, reset one" { + # Unset more values + gdb_test_no_output "unset environment B" \ + "unset B" + gdb_test_no_output "set environment A = 1" \ + "set A to 1 again" + gdb_test_no_output "unset environment C" \ + "unset C" + + do_prepare_inferior + + check_getenv "A" "1" + check_getenv "B" "" "" 1 + check_getenv "C" "" "" 1 + } +} + +with_test_prefix "test set/unset of vars" { + test_set_unset_vars +} + +# Test that unsetting works. + +proc test_unset { } { + global hex decimal binfile gdb_prompt + + clean_restart $binfile + + do_prepare_inferior + + set test_msg "check if unset works" + set found_home 0 + gdb_test_multiple "print my_getenv (\"HOME\")" $test_msg { + -re "\\\$$decimal = $hex \".*\"\r\n$gdb_prompt $" { + pass $test_msg + set found_home 1 + } + -re "\\\$$decimal = 0x0\r\n$gdb_prompt $" { + untested $test_msg + } + } + + if { $found_home == 1 } { + with_test_prefix "simple unset" { + # We can do the test, because $HOME exists (and therefore can + # be unset). + gdb_test_no_output "unset environment HOME" "unset HOME" + + do_prepare_inferior + + # $HOME now must be empty + check_getenv "HOME" "" "" 1 + } + + with_test_prefix "set-then-unset" { + clean_restart $binfile + + # Test if setting and then unsetting $HOME works. + gdb_test_no_output "set environment HOME = test" "set HOME as test" + gdb_test_no_output "unset environment HOME" "unset HOME again" + + do_prepare_inferior + + check_getenv "HOME" "" "" 1 + } + } +} + +with_test_prefix "test unset of vars" { + test_unset +} diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c index 1e938e6..81a71ee 100644 --- a/gdb/unittests/environ-selftests.c +++ b/gdb/unittests/environ-selftests.c @@ -22,64 +22,60 @@ #include "common/environ.h" #include "common/diagnostics.h" +static const char gdb_selftest_env_var[] = "GDB_SELFTEST_ENVIRON"; + +static bool +set_contains (const std::set<std::string> &set, std::string key) +{ + return set.find (key) != set.end (); +} + namespace selftests { namespace gdb_environ_tests { +/* Test if the vector is initialized in a correct way. */ + static void -run_tests () +test_vector_initialization () { - /* Set a test environment variable. This will be unset at the end - of this function. */ - if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0) - error (_("Could not set environment variable for testing.")); - gdb_environ env; /* When the vector is initialized, there should always be one NULL element in it. */ SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (env.user_unset_env ().size () == 0); /* Make sure that there is no other element. */ SELF_CHECK (env.get ("PWD") == NULL); +} - /* Check if unset followed by a set in an empty vector works. */ - env.set ("PWD", "test"); - SELF_CHECK (strcmp (env.get ("PWD"), "test") == 0); - /* The second element must be NULL. */ - SELF_CHECK (env.envp ()[1] == NULL); - env.unset ("PWD"); - SELF_CHECK (env.envp ()[0] == NULL); +/* Test initialization using the host's environ. */ +static void +test_init_from_host_environ () +{ /* Initialize the environment vector using the host's environ. */ - env = gdb_environ::from_host_environ (); + gdb_environ env = gdb_environ::from_host_environ (); + + /* The user-set and user-unset lists must be empty. */ + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (env.user_unset_env ().size () == 0); /* Our test environment variable should be present at the vector. */ - SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0); - - /* Set our test variable to another value. */ - env.set ("GDB_SELFTEST_ENVIRON", "test"); - SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0); - - /* And unset our test variable. The variable still exists in the - host's environment, but doesn't exist in our vector. */ - env.unset ("GDB_SELFTEST_ENVIRON"); - SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL); - - /* Re-set the test variable. */ - env.set ("GDB_SELFTEST_ENVIRON", "1"); - SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0); + SELF_CHECK (strcmp (env.get (gdb_selftest_env_var), "1") == 0); +} - /* When we clear our environ vector, there should be only one - element on it (NULL), and we shouldn't be able to get our test - variable. */ - env.clear (); - SELF_CHECK (env.envp ()[0] == NULL); - SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL); +/* Test reinitialization using the host's environ. */ +static void +test_reinit_from_host_environ () +{ /* Reinitialize our environ vector using the host environ. We should be able to see one (and only one) instance of the test variable. */ + gdb_environ env = gdb_environ::from_host_environ (); env = gdb_environ::from_host_environ (); char **envp = env.envp (); int num_found = 0; @@ -88,15 +84,23 @@ run_tests () if (strcmp (envp[i], "GDB_SELFTEST_ENVIRON=1") == 0) ++num_found; SELF_CHECK (num_found == 1); +} - /* Get rid of our test variable. */ - unsetenv ("GDB_SELFTEST_ENVIRON"); +/* Test the case when we set a variable A, then set a variable B, + then unset A, and make sure that we cannot find A in the environ + vector, but can still find B. */ + +static void +test_set_A_unset_B_unset_A_cannot_find_A_can_find_B () +{ + gdb_environ env; - /* Test the case when we set a variable A, then set a variable B, - then unset A, and make sure that we cannot find A in the environ - vector, but can still find B. */ env.set ("GDB_SELFTEST_ENVIRON_1", "aaa"); SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_1"), "aaa") == 0); + /* User-set environ var list must contain one element. */ + SELF_CHECK (env.user_set_env ().size () == 1); + SELF_CHECK (set_contains (env.user_set_env (), + std::string ("GDB_SELFTEST_ENVIRON_1=aaa"))); env.set ("GDB_SELFTEST_ENVIRON_2", "bbb"); SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0); @@ -105,38 +109,117 @@ run_tests () SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL); SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0); + /* The user-set environ var list must contain only one element + now. */ + SELF_CHECK (set_contains (env.user_set_env (), + std::string ("GDB_SELFTEST_ENVIRON_2=bbb"))); + SELF_CHECK (env.user_set_env ().size () == 1); +} + +/* Check if unset followed by a set in an empty vector works. */ + +static void +test_unset_set_empty_vector () +{ + gdb_environ env; + + env.set ("PWD", "test"); + SELF_CHECK (strcmp (env.get ("PWD"), "test") == 0); + SELF_CHECK (set_contains (env.user_set_env (), std::string ("PWD=test"))); + SELF_CHECK (env.user_unset_env ().size () == 0); + /* The second element must be NULL. */ + SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (env.user_set_env ().size () == 1); + env.unset ("PWD"); + SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (env.user_unset_env ().size () == 1); + SELF_CHECK (set_contains (env.user_unset_env (), std::string ("PWD"))); +} + +/* When we clear our environ vector, there should be only one + element on it (NULL), and we shouldn't be able to get our test + variable. */ + +static void +test_vector_clear () +{ + gdb_environ env; + + env.set (gdb_selftest_env_var, "1"); + env.clear (); + SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (env.user_unset_env ().size () == 0); + SELF_CHECK (env.get (gdb_selftest_env_var) == NULL); +} + +/* Test that after a std::move the moved-from object is left at a + valid state (i.e., its only element is NULL). */ + +static void +test_std_move () +{ + gdb_environ env; + gdb_environ env2; - /* Test that after a std::move the moved-from object is left at a - valid state (i.e., its only element is NULL). */ env.set ("A", "1"); SELF_CHECK (strcmp (env.get ("A"), "1") == 0); - gdb_environ env2; + SELF_CHECK (set_contains (env.user_set_env (), std::string ("A=1"))); + SELF_CHECK (env.user_set_env ().size () == 1); + env2 = std::move (env); SELF_CHECK (env.envp ()[0] == NULL); SELF_CHECK (strcmp (env2.get ("A"), "1") == 0); SELF_CHECK (env2.envp ()[1] == NULL); + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (set_contains (env2.user_set_env (), std::string ("A=1"))); + SELF_CHECK (env2.user_set_env ().size () == 1); env.set ("B", "2"); SELF_CHECK (strcmp (env.get ("B"), "2") == 0); SELF_CHECK (env.envp ()[1] == NULL); +} + +/* Test that the move constructor leaves everything at a valid + state. */ + +static void +test_move_constructor () +{ + gdb_environ env; - /* Test that the move constructor leaves everything at a valid - state. */ - env.clear (); env.set ("A", "1"); SELF_CHECK (strcmp (env.get ("A"), "1") == 0); - gdb_environ env3 = std::move (env); + SELF_CHECK (set_contains (env.user_set_env (), std::string ("A=1"))); + + gdb_environ env2 = std::move (env); SELF_CHECK (env.envp ()[0] == NULL); - SELF_CHECK (strcmp (env3.get ("A"), "1") == 0); - SELF_CHECK (env3.envp ()[1] == NULL); + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (strcmp (env2.get ("A"), "1") == 0); + SELF_CHECK (env2.envp ()[1] == NULL); + SELF_CHECK (set_contains (env2.user_set_env (), std::string ("A=1"))); + SELF_CHECK (env2.user_set_env ().size () == 1); + env.set ("B", "2"); SELF_CHECK (strcmp (env.get ("B"), "2") == 0); SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (set_contains (env.user_set_env (), std::string ("B=2"))); + SELF_CHECK (env.user_set_env ().size () == 1); +} + +/* Test that self-moving works. */ + +static void +test_self_move () +{ + gdb_environ env; /* Test self-move. */ - env.clear (); env.set ("A", "1"); SELF_CHECK (strcmp (env.get ("A"), "1") == 0); + SELF_CHECK (set_contains (env.user_set_env (), std::string ("A=1"))); + SELF_CHECK (env.user_set_env ().size () == 1); /* Some compilers warn about moving to self, but that's precisely what we want to test here, so turn this warning off. */ @@ -148,6 +231,69 @@ run_tests () SELF_CHECK (strcmp (env.get ("A"), "1") == 0); SELF_CHECK (strcmp (env.envp ()[0], "A=1") == 0); SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (set_contains (env.user_set_env (), std::string ("A=1"))); + SELF_CHECK (env.user_set_env ().size () == 1); +} + +/* Test if set/unset/reset works. */ + +static void +test_set_unset_reset () +{ + gdb_environ env = gdb_environ::from_host_environ (); + + /* Our test variable should already be present in the host's environment. */ + SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") != NULL); + + /* Set our test variable to another value. */ + env.set ("GDB_SELFTEST_ENVIRON", "test"); + SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0); + SELF_CHECK (env.user_set_env ().size () == 1); + SELF_CHECK (env.user_unset_env ().size () == 0); + + /* And unset our test variable. The variable still exists in the + host's environment, but doesn't exist in our vector. */ + env.unset ("GDB_SELFTEST_ENVIRON"); + SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL); + SELF_CHECK (env.user_set_env ().size () == 0); + SELF_CHECK (env.user_unset_env ().size () == 1); + SELF_CHECK (set_contains (env.user_unset_env (), + std::string ("GDB_SELFTEST_ENVIRON"))); + + /* Re-set the test variable. */ + env.set ("GDB_SELFTEST_ENVIRON", "1"); + SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0); +} + +static void +run_tests () +{ + /* Set a test environment variable. */ + if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0) + error (_("Could not set environment variable for testing.")); + + test_vector_initialization (); + + test_unset_set_empty_vector (); + + test_init_from_host_environ (); + + test_set_unset_reset (); + + test_vector_clear (); + + test_reinit_from_host_environ (); + + /* Get rid of our test variable. We won't need it anymore. */ + unsetenv ("GDB_SELFTEST_ENVIRON"); + + test_set_A_unset_B_unset_A_cannot_find_A_can_find_B (); + + test_std_move (); + + test_move_constructor (); + + test_self_move (); } } /* namespace gdb_environ */ } /* namespace selftests */ |