gdb/python: add gdb.RemoteTargetConnection.send_packet

This commits adds a new sub-class of gdb.TargetConnection,
gdb.RemoteTargetConnection.  This sub-class is created for all
'remote' and 'extended-remote' targets.

This new sub-class has one additional method over its base class,
'send_packet'.  This new method is equivalent to the 'maint
packet' CLI command, it allows a custom packet to be sent to a remote
target.

The outgoing packet can either be a bytes object, or a Unicode string,
so long as the Unicode string contains only ASCII characters.

The result of calling RemoteTargetConnection.send_packet is a bytes
object containing the reply that came from the remote.
This commit is contained in:
Andrew Burgess 2021-08-31 14:04:36 +01:00
parent e5b176f25f
commit 24b2de7b77
10 changed files with 615 additions and 11 deletions

View File

@ -68,7 +68,9 @@ maint packet
integer type given a size and a signed-ness.
** New gdb.TargetConnection object type that represents a connection
(as displayed by the 'info connections' command).
(as displayed by the 'info connections' command). A sub-class,
gdb.RemoteTargetConnection, is used to represent 'remote' and
'extended-remote' connections.
** The gdb.Inferior type now has a 'connection' property which is an
instance of gdb.TargetConnection, the connection used by this
@ -82,6 +84,10 @@ maint packet
** New gdb.connections() function that returns a list of all
currently active connections.
** New gdb.RemoteTargetConnection.send_packet(PACKET) method. This
is equivalent to the existing 'maint packet' CLI command; it
allows a user specified packet to be sent to the remote target.
* New features in the GDB remote stub, GDBserver
** GDBserver is now supported on OpenRISC GNU/Linux.

View File

@ -39278,6 +39278,7 @@ possible to have a backtrace of @value{GDBN} printed to the standard
error stream. This is @samp{on} by default for @code{internal-error}
and @samp{off} by default for @code{internal-warning}.
@anchor{maint packet}
@kindex maint packet
@item maint packet @var{text}
If @value{GDBN} is talking to an inferior via the serial protocol,

View File

@ -6007,15 +6007,36 @@ describes how @value{GDBN} controls the program being debugged.
Examples of different connection types are @samp{native} and
@samp{remote}. @xref{Inferiors Connections and Programs}.
@value{GDBN} uses the @code{gdb.TargetConnection} object type to
represent a connection in Python code. To get a list of all
connections use @code{gdb.connections}
Connections in @value{GDBN} are represented as instances of
@code{gdb.TargetConnection}, or as one of its sub-classes. To get a
list of all connections use @code{gdb.connections}
(@pxref{gdbpy_connections,,gdb.connections}).
To get the connection for a single @code{gdb.Inferior} read its
@code{gdb.Inferior.connection} attribute
(@pxref{gdbpy_inferior_connection,,gdb.Inferior.connection}).
Currently there is only a single sub-class of
@code{gdb.TargetConnection}, @code{gdb.RemoteTargetConnection},
however, additional sub-classes may be added in future releases of
@value{GDBN}. As a result you should avoid writing code like:
@smallexample
conn = gdb.selected_inferior().connection
if type(conn) is gdb.RemoteTargetConnection:
print("This is a remote target connection")
@end smallexample
@noindent
as this may fail when more connection types are added. Instead, you
should write:
@smallexample
conn = gdb.selected_inferior().connection
if isinstance(conn, gdb.RemoteTargetConnection):
print("This is a remote target connection")
@end smallexample
A @code{gdb.TargetConnection} has the following method:
@defun TargetConnection.is_valid ()
@ -6062,6 +6083,49 @@ contain the @samp{@var{hostname}:@var{port}} that was used to connect
to the remote target.
@end defvar
The @code{gdb.RemoteTargetConnection} class is a sub-class of
@code{gdb.TargetConnection}, and is used to represent @samp{remote}
and @samp{extended-remote} connections. In addition to the attributes
and methods available from the @code{gdb.TargetConnection} base class,
a @code{gdb.RemoteTargetConnection} has the following method:
@kindex maint packet
@defun RemoteTargetConnection.send_packet (@var{packet})
This method sends @var{packet} to the remote target and returns the
response. The @var{packet} should either be a @code{bytes} object, or
a @code{Unicode} string.
If @var{packet} is a @code{Unicode} string, then the string is encoded
to a @code{bytes} object using the @sc{ascii} codec. If the string
can't be encoded then an @code{UnicodeError} is raised.
If @var{packet} is not a @code{bytes} object, or a @code{Unicode}
string, then a @code{TypeError} is raised. If @var{packet} is empty
then a @code{ValueError} is raised.
The response is returned as a @code{bytes} object. For Python 3 if it
is known that the response can be represented as a string then this
can be decoded from the buffer. For example, if it is known that the
response is an @sc{ascii} string:
@smallexample
remote_connection.send_packet("some_packet").decode("ascii")
@end smallexample
In Python 2 @code{bytes} and @code{str} are aliases, so the result is
already a string, if the response includes non-printable characters,
or null characters, then these will be present in the result, care
should be taken when processing the result to handle this case.
The prefix, suffix, and checksum (as required by the remote serial
protocol) are automatically added to the outgoing packet, and removed
from the incoming packet before the contents of the reply are
returned.
This is equivalent to the @code{maintenance packet} command
(@pxref{maint packet}).
@end defun
@node TUI Windows In Python
@subsubsection Implementing new TUI windows
@cindex Python TUI Windows

View File

@ -26,6 +26,8 @@
#include "py-events.h"
#include "py-event.h"
#include "arch-utils.h"
#include "remote.h"
#include "charset.h"
#include <map>
@ -47,6 +49,9 @@ struct connection_object
extern PyTypeObject connection_object_type
CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("connection_object");
extern PyTypeObject remote_connection_object_type
CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("remote_connection_object");
/* Require that CONNECTION be valid. */
#define CONNPY_REQUIRE_VALID(connection) \
do { \
@ -81,8 +86,14 @@ target_to_connection_object (process_stratum_target *target)
auto conn_obj_iter = all_connection_objects.find (target);
if (conn_obj_iter == all_connection_objects.end ())
{
conn_obj.reset (PyObject_New (connection_object,
&connection_object_type));
PyTypeObject *type;
if (is_remote_target (target))
type = &remote_connection_object_type;
else
type = &connection_object_type;
conn_obj.reset (PyObject_New (connection_object, type));
if (conn_obj == nullptr)
return nullptr;
conn_obj->target = target;
@ -284,9 +295,148 @@ gdbpy_initialize_connection (void)
(PyObject *) &connection_object_type) < 0)
return -1;
if (PyType_Ready (&remote_connection_object_type) < 0)
return -1;
if (gdb_pymodule_addobject (gdb_module, "RemoteTargetConnection",
(PyObject *) &remote_connection_object_type) < 0)
return -1;
return 0;
}
/* Set of callbacks used to implement gdb.send_packet. */
struct py_send_packet_callbacks : public send_remote_packet_callbacks
{
/* Constructor, initialise the result to nullptr. It is invalid to try
and read the result before sending a packet and processing the
reply. */
py_send_packet_callbacks ()
: m_result (nullptr)
{ /* Nothing. */ }
/* There's nothing to do when the packet is sent. */
void sending (gdb::array_view<const char> &buf) override
{ /* Nothing. */ }
/* When the result is returned create a Python object and assign this
into M_RESULT. If for any reason we can't create a Python object to
represent the result then M_RESULT is set to nullptr, and Python's
internal error flags will be set. If the result we got back from the
remote is empty then set the result to None. */
void received (gdb::array_view<const char> &buf) override
{
if (buf.size () > 0 && buf.data ()[0] != '\0')
m_result.reset (PyBytes_FromStringAndSize (buf.data (), buf.size ()));
else
{
/* We didn't get back any result data; set the result to None. */
Py_INCREF (Py_None);
m_result.reset (Py_None);
}
}
/* Get a reference to the result as a Python object. It is invalid to
call this before sending a packet to the remote and processing the
reply.
The result value is setup in the RECEIVED call above. If the RECEIVED
call causes an error then the result value will be set to nullptr,
and the error reason is left stored in Python's global error state.
It is important that the result is inspected immediately after sending
a packet to the remote, and any error fetched, calling any other
Python functions that might clear the error state, or rely on an error
not being set will cause undefined behaviour. */
gdbpy_ref<> result () const
{
return m_result;
}
private:
/* A reference to the result value. */
gdbpy_ref<> m_result;
};
/* Implement RemoteTargetConnection.send_packet function. Send a packet to
the target identified by SELF. The connection must still be valid, and
the packet to be sent must be non-empty, otherwise an exception will be
thrown. */
static PyObject *
connpy_send_packet (PyObject *self, PyObject *args, PyObject *kw)
{
connection_object *conn = (connection_object *) self;
CONNPY_REQUIRE_VALID (conn);
static const char *keywords[] = {"packet", nullptr};
PyObject *packet_obj;
if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O", keywords,
&packet_obj))
return nullptr;
/* If the packet is a unicode string then convert it to a bytes object. */
if (PyUnicode_Check (packet_obj))
{
/* We encode the string to bytes using the ascii codec, if this fails
then a suitable error will have been set. */
packet_obj = PyUnicode_AsASCIIString (packet_obj);
if (packet_obj == nullptr)
return nullptr;
}
/* Check the packet is now a bytes object. */
if (!PyBytes_Check (packet_obj))
{
PyErr_SetString (PyExc_TypeError, _("Packet is not a bytes object"));
return nullptr;
}
Py_ssize_t packet_len = 0;
char *packet_str_nonconst = nullptr;
if (PyBytes_AsStringAndSize (packet_obj, &packet_str_nonconst,
&packet_len) < 0)
return nullptr;
const char *packet_str = packet_str_nonconst;
gdb_assert (packet_str != nullptr);
if (packet_len == 0)
{
PyErr_SetString (PyExc_ValueError, _("Packet must not be empty"));
return nullptr;
}
try
{
scoped_restore_current_thread restore_thread;
switch_to_target_no_thread (conn->target);
gdb::array_view<const char> view (packet_str, packet_len);
py_send_packet_callbacks callbacks;
send_remote_packet (view, &callbacks);
PyObject *result = callbacks.result ().release ();
/* If we encountered an error converting the reply to a Python
object, then the result here can be nullptr. In that case, Python
should be aware that an error occurred. */
gdb_assert ((result == nullptr) == (PyErr_Occurred () != nullptr));
return result;
}
catch (const gdb_exception &except)
{
gdbpy_convert_exception (except);
return nullptr;
}
}
/* Global initialization for this file. */
void _initialize_py_connection ();
@ -307,6 +457,17 @@ Return true if this TargetConnection is valid, false if not." },
{ NULL }
};
/* Methods for the gdb.RemoteTargetConnection object type. */
static PyMethodDef remote_connection_object_methods[] =
{
{ "send_packet", (PyCFunction) connpy_send_packet,
METH_VARARGS | METH_KEYWORDS,
"send_packet (PACKET) -> Bytes\n\
Send PACKET to a remote target, return the reply as a bytes array." },
{ NULL }
};
/* Attributes for the gdb.TargetConnection object type. */
static gdb_PyGetSetDef connection_object_getset[] =
@ -345,7 +506,7 @@ PyTypeObject connection_object_type =
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
"GDB target connection object", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
@ -364,3 +525,46 @@ PyTypeObject connection_object_type =
0, /* tp_init */
0 /* tp_alloc */
};
/* Define the gdb.RemoteTargetConnection object type. */
PyTypeObject remote_connection_object_type =
{
PyVarObject_HEAD_INIT (NULL, 0)
"gdb.RemoteTargetConnection", /* tp_name */
sizeof (connection_object), /* tp_basicsize */
0, /* tp_itemsize */
connpy_connection_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
connpy_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"GDB remote target connection object", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
remote_connection_object_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&connection_object_type, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0 /* tp_alloc */
};

View File

@ -994,6 +994,15 @@ public:
bool supports_disable_randomization () override;
};
/* See remote.h. */
bool
is_remote_target (process_stratum_target *target)
{
remote_target *rt = dynamic_cast<remote_target *> (target);
return rt != nullptr;
}
/* Per-program-space data key. */
static const struct program_space_key<char, gdb::xfree_deleter<char>>
remote_pspace_data;

View File

@ -24,6 +24,8 @@
struct target_desc;
struct remote_target;
class process_stratum_target;
/* True when printing "remote" debug statements is enabled. */
extern bool remote_debug;
@ -113,4 +115,10 @@ struct send_remote_packet_callbacks
extern void send_remote_packet (gdb::array_view<const char> &buf,
send_remote_packet_callbacks *callbacks);
/* Return true if TARGET is a remote, or extended-remote target, otherwise,
return false. */
extern bool is_remote_target (process_stratum_target *target);
#endif

View File

@ -33,12 +33,18 @@ if ![runto_main] then {
return 0
}
if { [target_info exists gdb_protocol] } {
set connection_type "RemoteTargetConnection"
} else {
set connection_type "TargetConnection"
}
# Create a gdb.TargetConnection object and check it is initially
# valid.
gdb_test_no_output "python conn = gdb.selected_inferior().connection"
gdb_test "python print(conn)" \
"<gdb.TargetConnection num=1, what=\"\[^\"\]+\">" \
"print gdb.TargetConnection while it is still valid"
"<gdb.${connection_type} num=1, what=\"\[^\"\]+\">" \
"print gdb.${connection_type} while it is still valid"
gdb_test "python print(conn.is_valid())" "True" "is_valid returns True"
# Get the connection again, and ensure we get the exact same object.
@ -53,8 +59,8 @@ gdb_test "disconnect" "" "kill the inferior" \
"A program is being debugged already\\. Kill it\\? .*y or n. $" "y"
gdb_test "info connections" "No connections\\." \
"info connections now all the connections have gone"
gdb_test "python print(conn)" "<gdb.TargetConnection \\(invalid\\)>" \
"print gdb.TargetConnection now its invalid"
gdb_test "python print(conn)" "<gdb.${connection_type} \\(invalid\\)>" \
"print gdb.${connection_type} now its invalid"
gdb_test "python print(conn.is_valid())" "False" "is_valid returns False"
# Now check that accessing properties of the invalid connection cases

View File

@ -0,0 +1,31 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2021 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/>. */
volatile int global_var = 0;
void
breakpt ()
{
/* Nothing. */
}
int
main (void)
{
breakpt ();
return 0;
}

View File

@ -0,0 +1,99 @@
# Copyright (C) 2021 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/>.
# Test the gdb.RemoteTargetConnection.send_packet API. This is done
# by connecting to a remote target and fetching the thread list in two
# ways, first, we manually send the packets required to read the
# thread list using gdb.TargetConnection.send_packet, then we compare
# the results to the thread list using the standard API calls.
load_lib gdb-python.exp
load_lib gdbserver-support.exp
standard_testfile
if {[skip_gdbserver_tests]} {
return 0
}
if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
return -1
}
if { [skip_python_tests] } {
return 0
}
# Make sure we're disconnected, in case we're testing with an
# extended-remote board, therefore already connected.
gdb_test "disconnect" ".*"
gdbserver_run ""
gdb_breakpoint "breakpt"
gdb_continue_to_breakpoint "breakpt"
# Source the python script.
set remote_python_file [gdb_remote_download host \
${srcdir}/${subdir}/${testfile}.py]
gdb_test "source $remote_python_file" "Sourcing complete\\." \
"source ${testfile}.py script"
# The test is actually written in the Python script. Run it now.
gdb_test "python run_send_packet_test()" "Send packet test passed"
# Check the string representation of a remote target connection.
gdb_test "python print(gdb.selected_inferior().connection)" \
"<gdb.RemoteTargetConnection num=$decimal, what=\".*\">"
# Check to see if there's any auxv data for this target.
gdb_test_multiple "info auxv" "" {
-re -wrap "The program has no auxiliary information now\\. " {
set skip_auxv_test true
}
-re -wrap "0\\s+AT_NULL\\s+End of vector\\s+0x0" {
set skip_auxv_test false
}
}
if { ! $skip_auxv_test } {
# Use 'maint packet' to fetch the auxv data.
set reply_data ""
gdb_test_multiple "maint packet qXfer:auxv:read::0,1000" "" {
-re "sending: \"qXfer:auxv:read::0,1000\"\r\n" {
exp_continue
}
-re -wrap "received: \"(.*)\"" {
set reply_data $expect_out(1,string)
}
}
# Expand the '\x' in the output, so we can pass a string through
# to Python.
set reply_data [string map {\x \\x} $reply_data]
gdb_assert { ![string equal "$reply_data" ""] }
# Run the test, fetches the auxv data in Python and confirm it
# matches the expected results.
gdb_test "python run_auxv_send_packet_test(\"$reply_data\")" \
"auxv send packet test passed"
}
set sizeof_global_var [get_valueof "/d" "sizeof(global_var)" "UNKNOWN"]
if { $sizeof_global_var == 4 } {
gdb_test_no_output "set debug remote 1"
gdb_test "python run_set_global_var_test()" \
"set global_var test passed"
}

View File

@ -0,0 +1,176 @@
# Copyright (C) 2021 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/>.
import xml.etree.ElementTree as ET
import gdb
# Make use of gdb.RemoteTargetConnection.send_packet to fetch the
# thread list from the remote target.
#
# Sending existing serial protocol packets like this is not a good
# idea, there should be better ways to get this information using an
# official API, this is just being used as a test case.
#
# Really, the send_packet API would be used to send target
# specific packets to the target, but these are, by definition, target
# specific, so hard to test in a general testsuite.
def get_thread_list_str():
start_pos = 0
thread_desc = ""
conn = gdb.selected_inferior().connection
if not isinstance(conn, gdb.RemoteTargetConnection):
raise gdb.GdbError("connection is the wrong type")
while True:
str = conn.send_packet("qXfer:threads:read::%d,200" % start_pos).decode("ascii")
start_pos += 200
c = str[0]
str = str[1:]
thread_desc += str
if c == "l":
break
return thread_desc
# Use gdb.RemoteTargetConnection.send_packet to manually fetch the
# thread list, then extract the thread list using the gdb.Inferior and
# gdb.InferiorThread API. Compare the two results to ensure we
# managed to successfully read the thread list from the remote.
def run_send_packet_test():
# Find the IDs of all current threads.
all_threads = {}
for inf in gdb.inferiors():
for thr in inf.threads():
id = "p%x.%x" % (thr.ptid[0], thr.ptid[1])
all_threads[id] = False
# Now fetch the thread list from the remote, and parse the XML.
str = get_thread_list_str()
threads_xml = ET.fromstring(str)
# Look over all threads in the XML list and check we expected to
# find them, mark the ones we do find.
for thr in threads_xml:
id = thr.get("id")
if not id in all_threads:
raise "found unexpected thread in remote thread list"
else:
all_threads[id] = True
# Check that all the threads were found in the XML list.
for id in all_threads:
if not all_threads[id]:
raise "thread missingt from remote thread list"
# Test complete.
print("Send packet test passed")
# Convert a bytes object to a string. This follows the same rules as
# the 'maint packet' command so that the output from the two sources
# can be compared.
def bytes_to_string(byte_array):
# Python 2/3 compatibility. We need a function that can give us
# the value of a single element in BYTE_ARRAY as an integer.
if sys.version_info[0] > 2:
value_of_single_byte = int
else:
value_of_single_byte = ord
res = ""
for b in byte_array:
b = value_of_single_byte(b)
if b >= 32 and b <= 126:
res = res + ("%c" % b)
else:
res = res + ("\\x%02x" % b)
return res
# A very simple test for sending the packet that reads the auxv data.
# We convert the result to a string and expect to find some
# hex-encoded bytes in the output. This test will only work on
# targets that actually supply auxv data.
def run_auxv_send_packet_test(expected_result):
inf = gdb.selected_inferior()
conn = inf.connection
assert isinstance(conn, gdb.RemoteTargetConnection)
res = conn.send_packet("qXfer:auxv:read::0,1000")
assert isinstance(res, bytes)
string = bytes_to_string(res)
assert string.count("\\x") > 0
assert string == expected_result
print("auxv send packet test passed")
# Check that the value of 'global_var' is EXPECTED_VAL.
def check_global_var(expected_val):
val = int(gdb.parse_and_eval("global_var"))
val = val & 0xFFFFFFFF
if val != expected_val:
raise gdb.GdbError("global_var is 0x%x, expected 0x%x" % (val, expected_val))
# Set the 'X' packet to the remote target to set a global variable.
# Checks that we can send byte values.
def run_set_global_var_test():
inf = gdb.selected_inferior()
conn = inf.connection
assert isinstance(conn, gdb.RemoteTargetConnection)
addr = gdb.parse_and_eval("&global_var")
res = conn.send_packet("X%x,4:\x01\x01\x01\x01" % addr)
assert isinstance(res, bytes)
check_global_var(0x01010101)
res = conn.send_packet(b"X%x,4:\x02\x02\x02\x02" % addr)
assert isinstance(res, bytes)
check_global_var(0x02020202)
if sys.version_info[0] > 2:
# On Python 3 this first attempt will not work as we're
# passing a Unicode string containing non-ascii characters.
saw_error = False
try:
res = conn.send_packet("X%x,4:\xff\xff\xff\xff" % addr)
except UnicodeError:
saw_error = True
except:
assert False
assert saw_error
check_global_var(0x02020202)
# Now we pass a bytes object, which will work.
res = conn.send_packet(b"X%x,4:\xff\xff\xff\xff" % addr)
check_global_var(0xFFFFFFFF)
else:
# On Python 2 we need to force the creation of a Unicode
# string, but, with that done, we expect to see the same error
# as on Python 3; the unicode string contains non-ascii
# characters.
saw_error = False
try:
res = conn.send_packet(unicode("X%x,4:\xff\xff\xff\xff") % addr)
except UnicodeError:
saw_error = True
except:
assert False
assert saw_error
check_global_var(0x02020202)
# Now we pass a plain string, which, on Python 2, is the same
# as a bytes object, this, we expect to work.
res = conn.send_packet("X%x,4:\xff\xff\xff\xff" % addr)
check_global_var(0xFFFFFFFF)
print("set global_var test passed")
# Just to indicate the file was sourced correctly.
print("Sourcing complete.")