From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 86072 invoked by alias); 21 Aug 2017 19:11:54 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Received: (qmail 86057 invoked by uid 89); 21 Aug 2017 19:11:53 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-26.9 required=5.0 tests=BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,RP_MATCHES_RCVD,SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=escaped, unexpectedly X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Mon, 21 Aug 2017 19:11:47 +0000 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 8484961460; Mon, 21 Aug 2017 19:11:45 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 8484961460 Authentication-Results: ext-mx10.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx10.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=sergiodj@redhat.com Received: from localhost (unused-10-15-17-193.yyz.redhat.com [10.15.17.193]) by smtp.corp.redhat.com (Postfix) with ESMTPS id EF89460C20; Mon, 21 Aug 2017 19:11:44 +0000 (UTC) From: Sergio Durigan Junior To: GDB Patches Cc: Pedro Alves , Simon Marchi , Eli Zaretskii Subject: Re: [PATCH v3] Implement the ability to set/unset environment variables to GDBserver when starting the inferior References: <20170629194106.23070-1-sergiodj@redhat.com> <20170813061929.27676-1-sergiodj@redhat.com> Date: Mon, 21 Aug 2017 19:11:00 -0000 In-Reply-To: <20170813061929.27676-1-sergiodj@redhat.com> (Sergio Durigan Junior's message of "Sun, 13 Aug 2017 02:19:29 -0400") Message-ID: <87fuckka6n.fsf@redhat.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-IsSubscribed: yes X-SW-Source: 2017-08/txt/msg00408.txt.bz2 Ping. On Sunday, August 13 2017, I wrote: > Changes from v2 (addressing comments by Simon): > > - Make m_user_set_env_list and m_user_unset_env_list std::set's, > instead of std::vector's. This simplified the code and had the nice > side effect of "automatically" avoid duplicates in the sets (at the > expense of a bit more processing). This involved the adjustment of > several parts of the code. > > - Removed automatic parameter from unset method; now the code has two > unset's which are called depending on the case. > > - Implement bin2hex overload, and get rid of str2hex. > > - Nits and small mistakes fixed. > > - s/rerun_to_main/do_prepare_inferior/g in the testcase, in order to > avoid accidentally overwriting the top rerun_to_main proc (and mess > up with subsequent tests). > > > This patch implements the ability to set/unset environment variables > on the remote target, mimicking what GDB already offers to the user. > There are two features present here: user-set and user-unset > environment variables. > > User-set environment variables are only the variables that are > explicitly set by the user, using the 'set environment' command. This > means that variables that were already present in the environment when > starting GDB/GDBserver are not transmitted/considered by this feature. > > User-unset environment variables are variables that are explicitly > unset by the user, using the 'unset environment' command. > > The idea behind this patch is to store user-set and user-unset > environment variables in two separate sets, both part of gdb_environ. > Then, when extended_remote_create_inferior is preparing to start the > inferior, it will iterate over the two sets and set/unset variables > accordingly. Three new packets are introduced: > > - QEnvironmentHexEncoded, which is used to set environment variables, > and contains an hex-encoded string in the format "VAR=VALUE" (VALUE > can be empty if the user set a variable with a null value, by doing > 'set environment VAR='). > > - QEnvironmentUnset, which is used to unset environment variables, and > contains an hex-encoded string in the format "VAR". > > - QEnvironmentReset, which is always the first packet to be > transmitted, and is used to reset the environment, i.e., discard any > changes made by the user on previous runs. > > The QEnvironmentHexEncoded packet is inspired on LLDB's extensions to > the RSP. Details about it can be seen here: > > > > I decided not to implement the QEnvironment packet because it is > considered deprecated by LLDB. This packet, on LLDB, serves the same > purpose of QEnvironmentHexEncoded, but sends the information using a > plain text, non-hex-encoded string. > > The other two packets are new. > > This patch also includes updates to the documentation, testsuite, and > unit tests, without introducing regressions. > > gdb/ChangeLog: > yyyy-mm-dd Sergio Durigan Junior > > * 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 (run_tests): Add tests for > m_user_set_env_list and m_user_unset_env_list. > > gdb/gdbserver/ChangeLog: > yyyy-mm-dd Sergio Durigan Junior > > * server.c (handle_general_set): Handle QEnvironmentHexEncoded, > QEnvironmentUnset and QEnvironmentReset packets. > (handle_query): Inform remote that QEnvironmentHexEncoded, > QEnvironmentUnset and QEnvironmentReset are supported. > > gdb/doc/ChangeLog: > yyyy-mm-dd Sergio Durigan Junior > > * 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) : Add "environment-hex-encoded", > "QEnvironmentHexEncoded", "environment-unset", "QEnvironmentUnset", > "environment-reset" and "QEnvironmentReset" to the table. > (Remote Protocol) QEnvironmentReset>: New item, explaining the packet. > > gdb/testsuite/ChangeLog: > yyyy-mm-dd Sergio Durigan Junior > > * gdb.base/share-env-with-gdbserver.c: New file. > * gdb.base/share-env-with-gdbserver.exp: Likewise. > --- > gdb/NEWS | 24 ++ > gdb/common/environ.c | 75 ++++-- > gdb/common/environ.h | 24 +- > gdb/common/rsp-low.c | 39 ++++ > gdb/common/rsp-low.h | 8 + > gdb/doc/gdb.texinfo | 116 ++++++++++ > gdb/gdbserver/server.c | 65 +++++- > gdb/remote.c | 76 ++++++ > gdb/testsuite/gdb.base/share-env-with-gdbserver.c | 40 ++++ > .../gdb.base/share-env-with-gdbserver.exp | 254 +++++++++++++++++++++ > gdb/unittests/environ-selftests.c | 64 ++++++ > 11 files changed, 769 insertions(+), 16 deletions(-) > create mode 100644 gdb/testsuite/gdb.base/share-env-with-gdbserver.c > create mode 100644 gdb/testsuite/gdb.base/share-env-with-gdbserver.exp > > diff --git a/gdb/NEWS b/gdb/NEWS > index 9cd1df1cfe..4349cce6e2 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. > + > * On Unix systems, GDBserver now does globbing expansion and variable > substitution in inferior command line arguments. > > @@ -14,6 +26,18 @@ > > * 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 698bda3ac1..7753f6ebca 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::iterator it_env; > > /* We iterate until '.end () - 1' because the last element is > always NULL. */ > - for (std::vector::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 (&m_environ_vector[0]); > } > + > +/* See common/environ.h. */ > + > +const std::set & > +gdb_environ::user_set_env () const > +{ > + return m_user_set_env; > +} > + > +const std::set & > +gdb_environ::user_unset_env () const > +{ > + return m_user_unset_env; > +} > diff --git a/gdb/common/environ.h b/gdb/common/environ.h > index 0bbb191361..5ac27fe761 100644 > --- a/gdb/common/environ.h > +++ b/gdb/common/environ.h > @@ -18,6 +18,7 @@ > #define ENVIRON_H 1 > > #include > +#include > > /* 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 &user_set_env () const; > + > + /* Return the user-set environment vector. */ > + const std::set &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 m_environ_vector; > + > + /* The environment variables explicitly set by the user. */ > + std::set m_user_set_env; > + > + /* The environment variables explicitly unset by the user. */ > + std::set m_user_unset_env; > }; > > #endif /* defined (ENVIRON_H) */ > diff --git a/gdb/common/rsp-low.c b/gdb/common/rsp-low.c > index eb85ab5701..9f71699144 100644 > --- a/gdb/common/rsp-low.c > +++ b/gdb/common/rsp-low.c > @@ -132,6 +132,29 @@ 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); > + > + 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 +169,22 @@ 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; > + > + 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 b57f58bc75..91cff4dac5 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 return 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/gdb.texinfo b/gdb/doc/gdb.texinfo > index 7528183506..1c871cb34c 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 > @@ -20816,6 +20826,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} > @@ -36490,6 +36512,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/server.c b/gdb/gdbserver/server.c > index 38383510e8..fa1116a5ee 100644 > --- a/gdb/gdbserver/server.c > +++ b/gdb/gdbserver/server.c > @@ -631,6 +631,67 @@ handle_general_set (char *own_buf) > return; > } > > + if (startswith (own_buf, "QEnvironmentReset")) > + { > + 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) > @@ -2228,7 +2289,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 ff59a0f81f..8c275fcf4f 100644 > --- a/gdb/remote.c > +++ b/gdb/remote.c > @@ -73,6 +73,7 @@ > #include "record-btrace.h" > #include > #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/gdb.base/share-env-with-gdbserver.c b/gdb/testsuite/gdb.base/share-env-with-gdbserver.c > new file mode 100644 > index 0000000000..740bd0fb52 > --- /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 . */ > + > +#include > +#include > + > +/* 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 0000000000..0aca8f6b5e > --- /dev/null > +++ b/gdb/testsuite/gdb.base/share-env-with-gdbserver.exp > @@ -0,0 +1,254 @@ > +# 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 . > + > +# 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 hex decimal 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" "' = 123 43; asd b ### [];;;" > +} > + > +# Test setting and unsetting environment variables in various > +# fashions. > + > +proc test_set_unset_vars { } { > + global hex decimal 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 28b16f828f..e17715b43c 100644 > --- a/gdb/unittests/environ-selftests.c > +++ b/gdb/unittests/environ-selftests.c > @@ -22,6 +22,12 @@ > #include "common/environ.h" > #include "common/diagnostics.h" > > +static bool > +set_contains (const std::set &set, std::string key) > +{ > + return set.find (key) != set.end (); > +} > + > namespace selftests { > namespace gdb_environ_tests { > > @@ -38,6 +44,8 @@ run_tests () > /* 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); > @@ -45,14 +53,24 @@ run_tests () > /* Check if unset followed by a set in an empty vector works. */ > 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"))); > > /* Initialize the environment vector using the host's 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); > @@ -65,6 +83,9 @@ run_tests () > 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_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"); > @@ -75,6 +96,8 @@ run_tests () > variable. */ > 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_ENVIRON") == NULL); > > /* Reinitialize our environ vector using the host environ. We > @@ -89,6 +112,23 @@ run_tests () > ++num_found; > SELF_CHECK (num_found == 1); > > + /* Before unsetting our test variable, test that user-unset works > + fine. */ > + 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"))); > + env.set ("GDB_SELFTEST_ENVIRON", "1"); > + SELF_CHECK (env.user_set_env ().size () == 1); > + SELF_CHECK (env.user_unset_env ().size () == 0); > + env.unset ("GDB_SELFTEST_ENVIRON"); > + 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"))); > + > /* Get rid of our test variable. */ > unsetenv ("GDB_SELFTEST_ENVIRON"); > > @@ -97,6 +137,10 @@ run_tests () > 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); > @@ -104,6 +148,11 @@ run_tests () > env.unset ("GDB_SELFTEST_ENVIRON_1"); > 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); > > env.clear (); > > @@ -111,11 +160,16 @@ run_tests () > valid state (i.e., its only element is NULL). */ > 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); > gdb_environ env2; > 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); > @@ -125,18 +179,26 @@ run_tests () > 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"))); > gdb_environ env3 = std::move (env); > SELF_CHECK (env.envp ()[0] == NULL); > + SELF_CHECK (env.user_set_env ().size () == 0); > SELF_CHECK (strcmp (env3.get ("A"), "1") == 0); > SELF_CHECK (env3.envp ()[1] == NULL); > + SELF_CHECK (set_contains (env3.user_set_env (), std::string ("A=1"))); > + SELF_CHECK (env3.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 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 +210,8 @@ 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); > } > } /* namespace gdb_environ */ > } /* namespace selftests */ > -- > 2.13.3 -- Sergio GPG key ID: 237A 54B1 0287 28BF 00EF 31F4 D0EB 7628 65FC 5E36 Please send encrypted e-mail if possible http://sergiodj.net/