From 24b2de7b776f8f23788d855b1eec290c6e208821 Mon Sep 17 00:00:00 2001 From: Andrew Burgess Date: Tue, 31 Aug 2021 14:04:36 +0100 Subject: [PATCH] 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. --- gdb/NEWS | 8 +- gdb/doc/gdb.texinfo | 1 + gdb/doc/python.texi | 70 ++++++- gdb/python/py-connection.c | 210 +++++++++++++++++++- gdb/remote.c | 9 + gdb/remote.h | 8 + gdb/testsuite/gdb.python/py-connection.exp | 14 +- gdb/testsuite/gdb.python/py-send-packet.c | 31 +++ gdb/testsuite/gdb.python/py-send-packet.exp | 99 +++++++++ gdb/testsuite/gdb.python/py-send-packet.py | 176 ++++++++++++++++ 10 files changed, 615 insertions(+), 11 deletions(-) create mode 100644 gdb/testsuite/gdb.python/py-send-packet.c create mode 100644 gdb/testsuite/gdb.python/py-send-packet.exp create mode 100644 gdb/testsuite/gdb.python/py-send-packet.py diff --git a/gdb/NEWS b/gdb/NEWS index 09c5169e4ed..eeca1d39b10 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -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. diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 738f2e4c0c7..9d507795993 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -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, diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 33748eeb9f3..4a66c11c19d 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -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 diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c index f1dfa26e39c..48e14fed4ad 100644 --- a/gdb/python/py-connection.c +++ b/gdb/python/py-connection.c @@ -26,6 +26,8 @@ #include "py-events.h" #include "py-event.h" #include "arch-utils.h" +#include "remote.h" +#include "charset.h" #include @@ -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 &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 &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 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 */ +}; diff --git a/gdb/remote.c b/gdb/remote.c index b4269809b4e..7df904bf10b 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -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 (target); + return rt != nullptr; +} + /* Per-program-space data key. */ static const struct program_space_key> remote_pspace_data; diff --git a/gdb/remote.h b/gdb/remote.h index 0178294ab1d..0ddf55d19d3 100644 --- a/gdb/remote.h +++ b/gdb/remote.h @@ -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 &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 diff --git a/gdb/testsuite/gdb.python/py-connection.exp b/gdb/testsuite/gdb.python/py-connection.exp index b805b052f73..96c83781839 100644 --- a/gdb/testsuite/gdb.python/py-connection.exp +++ b/gdb/testsuite/gdb.python/py-connection.exp @@ -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)" \ - "" \ - "print gdb.TargetConnection while it is still valid" + "" \ + "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)" "" \ - "print gdb.TargetConnection now its invalid" +gdb_test "python print(conn)" "" \ + "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 diff --git a/gdb/testsuite/gdb.python/py-send-packet.c b/gdb/testsuite/gdb.python/py-send-packet.c new file mode 100644 index 00000000000..49fbd79e11b --- /dev/null +++ b/gdb/testsuite/gdb.python/py-send-packet.c @@ -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 . */ + +volatile int global_var = 0; + +void +breakpt () +{ + /* Nothing. */ +} + +int +main (void) +{ + breakpt (); + return 0; +} diff --git a/gdb/testsuite/gdb.python/py-send-packet.exp b/gdb/testsuite/gdb.python/py-send-packet.exp new file mode 100644 index 00000000000..59035757414 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-send-packet.exp @@ -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 . + +# 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)" \ + "" + +# 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" +} diff --git a/gdb/testsuite/gdb.python/py-send-packet.py b/gdb/testsuite/gdb.python/py-send-packet.py new file mode 100644 index 00000000000..23abc42101c --- /dev/null +++ b/gdb/testsuite/gdb.python/py-send-packet.py @@ -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 . + +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.") -- 2.30.2