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:
parent
e5b176f25f
commit
24b2de7b77
8
gdb/NEWS
8
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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
31
gdb/testsuite/gdb.python/py-send-packet.c
Normal file
31
gdb/testsuite/gdb.python/py-send-packet.c
Normal 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;
|
||||
}
|
99
gdb/testsuite/gdb.python/py-send-packet.exp
Normal file
99
gdb/testsuite/gdb.python/py-send-packet.exp
Normal 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"
|
||||
}
|
176
gdb/testsuite/gdb.python/py-send-packet.py
Normal file
176
gdb/testsuite/gdb.python/py-send-packet.py
Normal 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.")
|
Loading…
x
Reference in New Issue
Block a user