aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog54
-rw-r--r--gdb/NEWS28
-rw-r--r--gdb/common/environ.c75
-rw-r--r--gdb/common/environ.h24
-rw-r--r--gdb/common/rsp-low.c41
-rw-r--r--gdb/common/rsp-low.h8
-rw-r--r--gdb/doc/ChangeLog11
-rw-r--r--gdb/doc/gdb.texinfo116
-rw-r--r--gdb/gdbserver/ChangeLog7
-rw-r--r--gdb/gdbserver/server.c65
-rw-r--r--gdb/remote.c76
-rw-r--r--gdb/testsuite/ChangeLog5
-rw-r--r--gdb/testsuite/gdb.base/share-env-with-gdbserver.c40
-rw-r--r--gdb/testsuite/gdb.base/share-env-with-gdbserver.exp255
-rw-r--r--gdb/unittests/environ-selftests.c244
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.
diff --git a/gdb/NEWS b/gdb/NEWS
index 7354154..fda3064 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -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 = &current_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 */