diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/Makefile.in | 1 | ||||
-rw-r--r-- | gdb/NEWS | 15 | ||||
-rw-r--r-- | gdb/doc/python.texi | 93 | ||||
-rw-r--r-- | gdb/observable.c | 1 | ||||
-rw-r--r-- | gdb/observable.h | 3 | ||||
-rw-r--r-- | gdb/python/py-all-events.def | 1 | ||||
-rw-r--r-- | gdb/python/py-connection.c | 366 | ||||
-rw-r--r-- | gdb/python/py-event-types.def | 5 | ||||
-rw-r--r-- | gdb/python/py-inferior.c | 16 | ||||
-rw-r--r-- | gdb/python/python-internal.h | 6 | ||||
-rw-r--r-- | gdb/python/python.c | 5 | ||||
-rw-r--r-- | gdb/target-connection.c | 4 | ||||
-rw-r--r-- | gdb/testsuite/gdb.multi/multi-target-info-inferiors.exp | 38 | ||||
-rw-r--r-- | gdb/testsuite/gdb.multi/multi-target-info-inferiors.py | 64 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-connection.c | 22 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-connection.exp | 69 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-inferior.exp | 20 |
17 files changed, 725 insertions, 4 deletions
diff --git a/gdb/Makefile.in b/gdb/Makefile.in index af8f1cc..bff2757 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -391,6 +391,7 @@ SUBDIR_PYTHON_SRCS = \ python/py-bpevent.c \ python/py-breakpoint.c \ python/py-cmd.c \ + python/py-connection.c \ python/py-continueevent.c \ python/py-event.c \ python/py-evtregistry.c \ @@ -59,6 +59,21 @@ show logging enabled ** New function gdb.Architecture.integer_type(), which returns an 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). + + ** The gdb.Inferior type now has a 'connection' property which is an + instance of gdb.TargetConnection, the connection used by this + inferior. This can be None if the inferior has no connection. + + ** New 'gdb.events.connection_removed' event registry, which emits a + 'gdb.ConnectionEvent' when a connection is removed from GDB. + This event has a 'connection' property, a gdb.TargetConnection + object for the connection being removed. + + ** New gdb.connections() function that returns a list of all + currently active connections. + * New features in the GDB remote stub, GDBserver ** GDBserver is now supported on OpenRISC GNU/Linux. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 9a76813..33748ee 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -220,6 +220,7 @@ optional arguments while skipping others. Example: * Lazy Strings In Python:: Python representation of lazy strings. * Architectures In Python:: Python representation of architectures. * Registers In Python:: Python representation of registers. +* Connections In Python:: Python representation of connections. * TUI Windows In Python:: Implementing new TUI windows. @end menu @@ -565,6 +566,13 @@ returned from @code{gdb.Architecture.name} (@pxref{gdbpy_architecture_name,,Architecture.name}). @end defun +@anchor{gdbpy_connections} +@defun gdb.connections +Return a list of @code{gdb.TargetConnection} objects, one for each +currently active connection (@pxref{Connections In Python}). The +connection objects are in no particular order in the returned list. +@end defun + @node Exception Handling @subsubsection Exception Handling @cindex python exceptions @@ -3095,10 +3103,18 @@ A @code{gdb.Inferior} object has the following attributes: ID of inferior, as assigned by GDB. @end defvar +@anchor{gdbpy_inferior_connection} +@defvar Inferior.connection +The @code{gdb.TargetConnection} for this inferior (@pxref{Connections +In Python}), or @code{None} if this inferior has no connection. +@end defvar + @defvar Inferior.connection_num ID of inferior's connection as assigned by @value{GDBN}, or None if -the inferior is not connected to a target. -@xref{Inferiors Connections and Programs}. +the inferior is not connected to a target. @xref{Inferiors Connections +and Programs}. This is equivalent to +@code{gdb.Inferior.connection.num} in the case where +@code{gdb.Inferior.connection} is not @code{None}. @end defvar @defvar Inferior.pid @@ -3439,6 +3455,15 @@ which has a single attribute: An integer, the value of the exit code @value{GDBN} will return. @end defvar +@item events.connection_removed +This is emitted when @value{GDBN} removes a connection +(@pxref{Connections In Python}). The event is of type +@code{gdb.ConnectionEvent}. This has a single read-only attribute: + +@defvar ConnectionEvent.connection +The @code{gdb.TargetConnection} that is being removed. +@end defvar + @end table @node Threads In Python @@ -5973,6 +5998,70 @@ properties: A string that is the name of this register group. @end defvar +@node Connections In Python +@subsubsection Connections In Python +@cindex connections in python +@value{GDBN} lets you run and debug multiple programs in a single +session. Each program being debugged has a connection, the connection +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} +(@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}). + +A @code{gdb.TargetConnection} has the following method: + +@defun TargetConnection.is_valid () +Return @code{True} if the @code{gdb.TargetConnection} object is valid, +@code{False} if not. A @code{gdb.TargetConnection} will become +invalid if the connection no longer exists within @value{GDBN}, this +might happen when no inferiors are using the connection, but could be +delayed until the user replaces the current target. + +Reading any of the @code{gdb.TargetConnection} properties will throw +an exception if the connection is invalid. +@end defun + +A @code{gdb.TargetConnection} has the following read-only properties: + +@defvar TargetConnection.num +An integer assigned by @value{GDBN} to uniquely identify this +connection. This is the same value as displayed in the @samp{Num} +column of the @code{info connections} command output (@pxref{Inferiors +Connections and Programs,,info connections}). +@end defvar + +@defvar TargetConnection.type +A string that describes what type of connection this is. This string +will be one of the valid names that can be passed to the @code{target} +command (@pxref{Target Commands,,target command}). +@end defvar + +@defvar TargetConnection.description +A string that gives a short description of this target type. This is +the same string that is displayed in the @samp{Description} column of +the @code{info connection} command output (@pxref{Inferiors +Connections and Programs,,info connections}). +@end defvar + +@defvar TargetConnection.details +An optional string that gives additional information about this +connection. This attribute can be @code{None} if there are no +additional details for this connection. + +An example of a connection type that might have additional details is +the @samp{remote} connection, in this case the details string can +contain the @samp{@var{hostname}:@var{port}} that was used to connect +to the remote target. +@end defvar + @node TUI Windows In Python @subsubsection Implementing new TUI windows @cindex Python TUI Windows diff --git a/gdb/observable.c b/gdb/observable.c index d965ec3..fe88b0b 100644 --- a/gdb/observable.c +++ b/gdb/observable.c @@ -78,6 +78,7 @@ DEFINE_OBSERVABLE (user_selected_context_changed); DEFINE_OBSERVABLE (styling_changed); DEFINE_OBSERVABLE (current_source_symtab_and_line_changed); DEFINE_OBSERVABLE (gdb_exiting); +DEFINE_OBSERVABLE (connection_removed); } /* namespace observers */ } /* namespace gdb */ diff --git a/gdb/observable.h b/gdb/observable.h index f994530..d3c441d 100644 --- a/gdb/observable.h +++ b/gdb/observable.h @@ -253,6 +253,9 @@ extern observable<> current_source_symtab_and_line_changed; /* Called when GDB is about to exit. */ extern observable<int> gdb_exiting; +/* When a connection is removed. */ +extern observable<process_stratum_target */* target */> connection_removed; + } /* namespace observers */ } /* namespace gdb */ diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def index 83f1098..ed22a33 100644 --- a/gdb/python/py-all-events.def +++ b/gdb/python/py-all-events.def @@ -39,3 +39,4 @@ GDB_PY_DEFINE_EVENT(breakpoint_deleted) GDB_PY_DEFINE_EVENT(breakpoint_modified) GDB_PY_DEFINE_EVENT(before_prompt) GDB_PY_DEFINE_EVENT(gdb_exiting) +GDB_PY_DEFINE_EVENT(connection_removed) diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c new file mode 100644 index 0000000..f1dfa26 --- /dev/null +++ b/gdb/python/py-connection.c @@ -0,0 +1,366 @@ +/* Python interface to inferiors. + + Copyright (C) 2009-2021 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "python-internal.h" +#include "process-stratum-target.h" +#include "inferior.h" +#include "observable.h" +#include "target-connection.h" +#include "py-events.h" +#include "py-event.h" +#include "arch-utils.h" + +#include <map> + +/* The Python object that represents a connection. */ + +struct connection_object +{ + PyObject_HEAD + + /* The process target that represents this connection. When a + connection_object is created this field will always point at a valid + target. Later, if GDB stops using this target (the target is popped + from all target stacks) then this field is set to nullptr, which + indicates that this Python object is now in the invalid state (see + the is_valid() method below). */ + struct process_stratum_target *target; +}; + +extern PyTypeObject connection_object_type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("connection_object"); + +/* Require that CONNECTION be valid. */ +#define CONNPY_REQUIRE_VALID(connection) \ + do { \ + if (connection->target == nullptr) \ + { \ + PyErr_SetString (PyExc_RuntimeError, \ + _("Connection no longer exists.")); \ + return nullptr; \ + } \ + } while (0) + +/* A map between process_stratum targets and the Python object representing + them. We actually hold a gdbpy_ref around the Python object so that + reference counts are handled correctly when entries are deleted. */ +static std::map<process_stratum_target *, + gdbpy_ref<connection_object>> all_connection_objects; + +/* Return a reference to a gdb.TargetConnection object for TARGET. If + TARGET is nullptr then a reference to None is returned. + + Previously created gdb.TargetConnection objects are cached, and + additional references to the same connection object can be returned with + later calls to this function. */ + +gdbpy_ref<> +target_to_connection_object (process_stratum_target *target) +{ + if (target == nullptr) + return gdbpy_ref<>::new_reference (Py_None); + + gdbpy_ref <connection_object> conn_obj; + 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)); + if (conn_obj == nullptr) + return nullptr; + conn_obj->target = target; + all_connection_objects.emplace (target, conn_obj); + } + else + conn_obj = conn_obj_iter->second; + + gdb_assert (conn_obj != nullptr); + + /* Repackage the result as a PyObject reference. */ + return gdbpy_ref<> ((PyObject *) conn_obj.release ()); +} + +/* Return a list of gdb.TargetConnection objects, one for each currently + active connection. The returned list is in no particular order. */ + +PyObject * +gdbpy_connections (PyObject *self, PyObject *args) +{ + gdbpy_ref<> list (PyList_New (0)); + if (list == nullptr) + return nullptr; + + for (process_stratum_target *target : all_non_exited_process_targets ()) + { + gdb_assert (target != nullptr); + + gdbpy_ref<> conn = target_to_connection_object (target); + if (conn == nullptr) + return nullptr; + gdb_assert (conn.get () != Py_None); + + if (PyList_Append (list.get (), conn.get ()) < 0) + return nullptr; + } + + return list.release (); +} + +/* Emit a connection event for TARGET to REGISTRY. Return 0 on success, or + a negative value on error. */ + +static int +emit_connection_event (process_stratum_target *target, + eventregistry_object *registry) +{ + gdbpy_ref<> event_obj + = create_event_object (&connection_event_object_type); + if (event_obj == nullptr) + return -1; + + gdbpy_ref<> conn = target_to_connection_object (target); + if (evpy_add_attribute (event_obj.get (), "connection", conn.get ()) < 0) + return -1; + + return evpy_emit_event (event_obj.get (), registry); +} + +/* Callback for the connection_removed observer. */ + +static void +connpy_connection_removed (process_stratum_target *target) +{ + if (!gdb_python_initialized) + return; + + gdbpy_enter enter_py (get_current_arch (), current_language); + + if (!evregpy_no_listeners_p (gdb_py_events.connection_removed)) + if (emit_connection_event (target, gdb_py_events.connection_removed) < 0) + gdbpy_print_stack (); + + auto conn_obj_iter = all_connection_objects.find (target); + if (conn_obj_iter != all_connection_objects.end ()) + { + gdbpy_ref <connection_object> conn_obj = conn_obj_iter->second; + conn_obj->target = nullptr; + all_connection_objects.erase (target); + } +} + +/* Called when a gdb.TargetConnection object is deallocated. */ + +static void +connpy_connection_dealloc (PyObject *obj) +{ + connection_object *conn_obj = (connection_object *) obj; + + /* As the all_connection_objects map holds a reference to each connection + object we can only enter the dealloc function when the reference in + all_connection_objects has been erased. + + As we always set the target pointer back to nullptr before we erase + items from all_connection_objects then, when we get here, the target + pointer must be nullptr. */ + gdb_assert (conn_obj->target == nullptr); + + Py_TYPE (obj)->tp_free (obj); +} + +/* Implement repr() for gdb.TargetConnection. */ + +static PyObject * +connpy_repr (PyObject *obj) +{ + connection_object *self = (connection_object *) obj; + process_stratum_target *target = self->target; + + if (target == nullptr) + return PyString_FromFormat ("<%s (invalid)>", Py_TYPE (obj)->tp_name); + + return PyString_FromFormat ("<%s num=%d, what=\"%s\">", + Py_TYPE (obj)->tp_name, + target->connection_number, + make_target_connection_string (target).c_str ()); +} + +/* Implementation of gdb.TargetConnection.is_valid() -> Boolean. Returns + True if this connection object is still associated with a + process_stratum_target, otherwise, returns False. */ + +static PyObject * +connpy_is_valid (PyObject *self, PyObject *args) +{ + connection_object *conn = (connection_object *) self; + + if (conn->target == nullptr) + Py_RETURN_FALSE; + + Py_RETURN_TRUE; +} + +/* Return the id number of this connection. */ + +static PyObject * +connpy_get_connection_num (PyObject *self, void *closure) +{ + connection_object *conn = (connection_object *) self; + + CONNPY_REQUIRE_VALID (conn); + + auto num = conn->target->connection_number; + return gdb_py_object_from_longest (num).release (); +} + +/* Return a string that gives the short name for this connection type. */ + +static PyObject * +connpy_get_connection_type (PyObject *self, void *closure) +{ + connection_object *conn = (connection_object *) self; + + CONNPY_REQUIRE_VALID (conn); + + const char *shortname = conn->target->shortname (); + return host_string_to_python_string (shortname).release (); +} + +/* Return a string that gives a longer description of this connection type. */ + +static PyObject * +connpy_get_description (PyObject *self, void *closure) +{ + connection_object *conn = (connection_object *) self; + + CONNPY_REQUIRE_VALID (conn); + + const char *longname = conn->target->longname (); + return host_string_to_python_string (longname).release (); +} + +/* Return a string that gives additional details about this connection, or + None, if there are no additional details for this connection type. */ + +static PyObject * +connpy_get_connection_details (PyObject *self, void *closure) +{ + connection_object *conn = (connection_object *) self; + + CONNPY_REQUIRE_VALID (conn); + + const char *details = conn->target->connection_string (); + if (details != nullptr) + return host_string_to_python_string (details).release (); + else + Py_RETURN_NONE; +} + +/* Python specific initialization for this file. */ + +int +gdbpy_initialize_connection (void) +{ + if (PyType_Ready (&connection_object_type) < 0) + return -1; + + if (gdb_pymodule_addobject (gdb_module, "TargetConnection", + (PyObject *) &connection_object_type) < 0) + return -1; + + return 0; +} + +/* Global initialization for this file. */ + +void _initialize_py_connection (); +void +_initialize_py_connection () +{ + gdb::observers::connection_removed.attach (connpy_connection_removed, + "py-connection"); +} + +/* Methods for the gdb.TargetConnection object type. */ + +static PyMethodDef connection_object_methods[] = +{ + { "is_valid", connpy_is_valid, METH_NOARGS, + "is_valid () -> Boolean.\n\ +Return true if this TargetConnection is valid, false if not." }, + { NULL } +}; + +/* Attributes for the gdb.TargetConnection object type. */ + +static gdb_PyGetSetDef connection_object_getset[] = +{ + { "num", connpy_get_connection_num, NULL, + "ID number of this connection, as assigned by GDB.", NULL }, + { "type", connpy_get_connection_type, NULL, + "A short string that is the name for this connection type.", NULL }, + { "description", connpy_get_description, NULL, + "A longer string describing this connection type.", NULL }, + { "details", connpy_get_connection_details, NULL, + "A string containing additional connection details.", NULL }, + { NULL } +}; + +/* Define the gdb.TargetConnection object type. */ + +PyTypeObject connection_object_type = +{ + PyVarObject_HEAD_INIT (NULL, 0) + "gdb.TargetConnection", /* 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 target connection object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + connection_object_methods, /* tp_methods */ + 0, /* tp_members */ + connection_object_getset, /* tp_getset */ + 0, /* 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/python/py-event-types.def b/gdb/python/py-event-types.def index aeaee02..6a49f2a 100644 --- a/gdb/python/py-event-types.def +++ b/gdb/python/py-event-types.def @@ -110,3 +110,8 @@ GDB_PY_DEFINE_EVENT_TYPE (gdb_exiting, "GdbExitingEvent", "GDB is about to exit", event_object_type); + +GDB_PY_DEFINE_EVENT_TYPE (connection, + "ConnectionEvent", + "GDB connection added or removed object", + event_object_type); diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c index 222e4d9..3b2d470 100644 --- a/gdb/python/py-inferior.c +++ b/gdb/python/py-inferior.c @@ -415,6 +415,20 @@ infpy_get_num (PyObject *self, void *closure) return gdb_py_object_from_longest (inf->inferior->num).release (); } +/* Return the gdb.TargetConnection object for this inferior, or None if a + connection does not exist. */ + +static PyObject * +infpy_get_connection (PyObject *self, void *closure) +{ + inferior_object *inf = (inferior_object *) self; + + INFPY_REQUIRE_VALID (inf); + + process_stratum_target *target = inf->inferior->process_target (); + return target_to_connection_object (target).release (); +} + /* Return the connection number of the given inferior, or None if a connection does not exist. */ @@ -849,6 +863,8 @@ gdbpy_initialize_inferior (void) static gdb_PyGetSetDef inferior_object_getset[] = { { "num", infpy_get_num, NULL, "ID of inferior, as assigned by GDB.", NULL }, + { "connection", infpy_get_connection, NULL, + "The gdb.TargetConnection for this inferior.", NULL }, { "connection_num", infpy_get_connection_num, NULL, "ID of inferior's connection, as assigned by GDB.", NULL }, { "pid", infpy_get_pid, NULL, "PID of inferior, as assigned by the OS.", diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index aece708..211833e 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -482,6 +482,10 @@ gdbpy_ref<inferior_object> inferior_to_inferior_object (inferior *inf); PyObject *gdbpy_buffer_to_membuf (gdb::unique_xmalloc_ptr<gdb_byte> buffer, CORE_ADDR address, ULONGEST length); +struct process_stratum_target; +gdbpy_ref<> target_to_connection_object (process_stratum_target *target); +PyObject *gdbpy_connections (PyObject *self, PyObject *args); + const struct block *block_object_to_block (PyObject *obj); struct symbol *symbol_object_to_symbol (PyObject *obj); struct value *value_object_to_value (PyObject *self); @@ -555,6 +559,8 @@ int gdbpy_initialize_tui () CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; int gdbpy_initialize_membuf () CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; +int gdbpy_initialize_connection () + CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; /* A wrapper for PyErr_Fetch that handles reference counting for the caller. */ diff --git a/gdb/python/python.c b/gdb/python/python.c index bfb691f..82af012 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1876,6 +1876,7 @@ do_start_initialization () || gdbpy_initialize_xmethods () < 0 || gdbpy_initialize_unwind () < 0 || gdbpy_initialize_membuf () < 0 + || gdbpy_initialize_connection () < 0 || gdbpy_initialize_tui () < 0) return false; @@ -2326,6 +2327,10 @@ Register a TUI window constructor." }, "architecture_names () -> List.\n\ Return a list of all the architecture names GDB understands." }, + { "connections", gdbpy_connections, METH_NOARGS, + "connections () -> List.\n\ +Return a list of gdb.TargetConnection objects." }, + {NULL, NULL, 0, NULL} }; diff --git a/gdb/target-connection.c b/gdb/target-connection.c index a649423..da1c9da 100644 --- a/gdb/target-connection.c +++ b/gdb/target-connection.c @@ -24,6 +24,7 @@ #include "inferior.h" #include "target.h" +#include "observable.h" /* A map between connection number and representative process_stratum target. */ @@ -49,6 +50,9 @@ connection_list_add (process_stratum_target *t) void connection_list_remove (process_stratum_target *t) { + /* Notify about the connection being removed before we reset the + connection number to zero. */ + gdb::observers::connection_removed.notify (t); process_targets.erase (t->connection_number); t->connection_number = 0; } diff --git a/gdb/testsuite/gdb.multi/multi-target-info-inferiors.exp b/gdb/testsuite/gdb.multi/multi-target-info-inferiors.exp index 3fd6b15..3f74a47 100644 --- a/gdb/testsuite/gdb.multi/multi-target-info-inferiors.exp +++ b/gdb/testsuite/gdb.multi/multi-target-info-inferiors.exp @@ -15,18 +15,27 @@ # Test "info inferiors" and "info connections" with multiple targets. +load_lib gdb-python.exp + source $srcdir/$subdir/multi-target.exp.tcl if {![multi_target_prepare]} { return } +# Cache the result of calling skip_python_tests into a local variable. +set run_python_tests [expr ! [skip_python_tests]] + # Test "info inferiors" and "info connections". MULTI_PROCESS # indicates whether the multi-process feature of remote targets is # turned off or on. proc test_info_inferiors {multi_process} { setup "off" + if { $::run_python_tests } { + gdb_test_no_output "source ${::remote_python_file}" "load python file" + } + gdb_test_no_output \ "set remote multiprocess-feature-packet $multi_process" @@ -86,6 +95,18 @@ proc test_info_inferiors {multi_process} { "[connection_num 5 $inf]${ws}core${ws}Local core dump file${ws}" \ ] + if { $::run_python_tests } { + gdb_test "python info_connections()" \ + [multi_line \ + "Num${ws}What${ws}Description" \ + "[connection_num 1 $inf]${ws}native${ws}Native process" \ + "[connection_num 2 $inf]${ws}extended-remote localhost:$decimal${ws}Extended remote serial target in gdb-specific protocol" \ + "[connection_num 3 $inf]${ws}core${ws}Local core dump file" \ + "[connection_num 4 $inf]${ws}extended-remote localhost:$decimal${ws}Extended remote serial target in gdb-specific protocol" \ + "[connection_num 5 $inf]${ws}core${ws}Local core dump file" \ + ] + } + gdb_test "info inferiors" \ [multi_line \ "Num${ws}Description${ws}Connection${ws}Executable${ws}" \ @@ -96,10 +117,27 @@ proc test_info_inferiors {multi_process} { "[inf_desc 5 $inf]4 \\(extended-remote localhost:$decimal\\)${ws}${binfile}${ws}" \ "[inf_desc 6 $inf]5 \\(core\\)${ws}${binfile}${ws}" \ ] + + if { $::run_python_tests } { + gdb_test "python info_inferiors()" \ + [multi_line \ + "Inferior 1, Connection #1: native" \ + "Inferior 2, Connection #2: extended-remote localhost:$decimal" \ + "Inferior 3, Connection #3: core" \ + "Inferior 4, Connection #1: native" \ + "Inferior 5, Connection #4: extended-remote localhost:$decimal" \ + "Inferior 6, Connection #5: core" \ + ] + } } } } +if { $run_python_tests } { + set remote_python_file [gdb_remote_download host \ + ${srcdir}/${subdir}/${testfile}.py] +} + # Test "info inferiors" and "info connections" commands. with_test_prefix "info-inferiors" { foreach_with_prefix multi_process {"on" "off"} { diff --git a/gdb/testsuite/gdb.multi/multi-target-info-inferiors.py b/gdb/testsuite/gdb.multi/multi-target-info-inferiors.py new file mode 100644 index 0000000..78c82f2 --- /dev/null +++ b/gdb/testsuite/gdb.multi/multi-target-info-inferiors.py @@ -0,0 +1,64 @@ +# 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 gdb + +# Take a gdb.TargetConnection and return the connection number. +def conn_num(c): + return c.num + +# Takes a gdb.TargetConnection and return a string that is either the +# type, or the type and details (if the details are not None). +def make_target_connection_string(c): + if c.details is None: + return c.type + else: + return "%s %s" % (c.type, c.details) + +# A Python implementation of 'info connections'. Produce output that +# is identical to the output of 'info connections' so we can check +# that aspects of gdb.TargetConnection work correctly. +def info_connections(): + all_connections = sorted(gdb.connections(), key=conn_num) + current_conn = gdb.selected_inferior().connection + what_width = 0 + for c in all_connections: + s = make_target_connection_string(c) + if len(s) > what_width: + what_width = len(s) + + fmt = " Num %%-%ds Description" % what_width + print(fmt % "What") + fmt = "%%s%%-3d %%-%ds %%s" % what_width + for c in all_connections: + if c == current_conn: + prefix = "* " + else: + prefix = " " + + print(fmt % (prefix, c.num, make_target_connection_string(c), + c.description)) + +def inf_num(i): + return i.num + +# Print information about each inferior, and the connection it is +# using. +def info_inferiors(): + all_inferiors = sorted(gdb.inferiors(), key=inf_num) + for i in gdb.inferiors(): + print("Inferior %d, Connection #%d: %s" % + (i.num, i.connection_num, + make_target_connection_string(i.connection))) diff --git a/gdb/testsuite/gdb.python/py-connection.c b/gdb/testsuite/gdb.python/py-connection.c new file mode 100644 index 0000000..4b32e70 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-connection.c @@ -0,0 +1,22 @@ +/* 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/>. */ + +int +main (void) +{ + return 0; +} diff --git a/gdb/testsuite/gdb.python/py-connection.exp b/gdb/testsuite/gdb.python/py-connection.exp new file mode 100644 index 0000000..b805b05 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-connection.exp @@ -0,0 +1,69 @@ +# 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/>. + +# This file is for testing the gdb.TargetConnection API. This API is +# already tested in gdb.multi/multi-target-info-inferiors.exp and +# gdb.python/py-inferior.exp, this file just covers some edge cases +# that are not tested in other places. + +load_lib gdb-python.exp + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return -1 +} + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +if ![runto_main] then { + return 0 +} + +# 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_test "python print(conn.is_valid())" "True" "is_valid returns True" + +# Get the connection again, and ensure we get the exact same object. +gdb_test_no_output "python conn2 = gdb.selected_inferior().connection" +gdb_test "python print('Same object: %s' % (conn is conn2))" "True" + +# Now invalidate the connection, and ensure that the is_valid method +# starts to return False. +gdb_test "info connections" "\r\n\\* 1 .*" \ + "info connections while the connection is still around" +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.is_valid())" "False" "is_valid returns False" + +# Now check that accessing properties of the invalid connection cases +# an error. +gdb_test "python print(conn.num)" \ + "RuntimeError: Connection no longer exists\\.\r\n.*" +gdb_test "python print(conn.type)" \ + "RuntimeError: Connection no longer exists\\.\r\n.*" +gdb_test "python print(conn.description)" \ + "RuntimeError: Connection no longer exists\\.\r\n.*" +gdb_test "python print(conn.details)" \ + "RuntimeError: Connection no longer exists\\.\r\n.*" diff --git a/gdb/testsuite/gdb.python/py-inferior.exp b/gdb/testsuite/gdb.python/py-inferior.exp index 894b90a..d5d284a 100644 --- a/gdb/testsuite/gdb.python/py-inferior.exp +++ b/gdb/testsuite/gdb.python/py-inferior.exp @@ -51,6 +51,8 @@ gdb_py_test_silent_cmd "python i0 = inferiors\[0\]" "get first inferior" 0 gdb_test "python print ('result = %s' % (i0 == inferiors\[0\]))" " = True" "test equality comparison (true)" gdb_test "python print ('result = %s' % i0.num)" " = \[0-9\]+" "test Inferior.num" gdb_test "python print ('result = %s' % i0.connection_num)" " = \[0-9\]+" "test Inferior.connection_num" +gdb_test "python print ('result = %s' % (i0.connection_num == i0.connection.num))" " = True" \ + "Inferior.connection_num equals Inferior.connection.num" gdb_test "python print ('result = %s' % i0.pid)" " = \[0-9\]+" "test Inferior.pid" gdb_test "python print ('result = %s' % i0.was_attached)" " = False" "test Inferior.was_attached" gdb_test "python print (i0.threads ())" "\\(<gdb.InferiorThread object at 0x\[\[:xdigit:\]\]+>,\\)" "test Inferior.threads" @@ -264,6 +266,8 @@ with_test_prefix "is_valid" { "RuntimeError: Inferior no longer exists.*" gdb_test "python print (inf_list\[1\].connection_num)" \ "RuntimeError: Inferior no longer exists.*" + gdb_test "python print (inf_list\[1\].connection)" \ + "RuntimeError: Inferior no longer exists.*" gdb_test "python print (inf_list\[1\].pid)" \ "RuntimeError: Inferior no longer exists.*" gdb_test "python print (inf_list\[1\].was_attached)" \ @@ -281,7 +285,9 @@ with_test_prefix "selected_inferior" { gdb_test "inferior 1" ".*" "switch to first inferior" gdb_test "py print (gdb.selected_inferior().num)" "1" "first inferior selected" gdb_test "py print (gdb.selected_inferior().connection_num)" "1" \ - "first inferior's connection" + "first inferior's connection number" + gdb_test "py print (gdb.selected_inferior().connection.num)" "1" \ + "first inferior's connection number, though connection object" # Figure out if inf 1 has a native target. set inf_1_is_native [gdb_is_target_native] @@ -289,6 +295,8 @@ with_test_prefix "selected_inferior" { gdb_test "inferior 3" ".*" "switch to third inferior" gdb_test "py print (gdb.selected_inferior().num)" "3" "third inferior selected" gdb_test "py print (gdb.selected_inferior().connection_num)" "None" \ + "third inferior's None connection number" + gdb_test "py print (gdb.selected_inferior().connection)" "None" \ "third inferior's None connection" gdb_test "target native" "Done. Use the \"run\" command to start a process." \ "target for the third inferior" @@ -302,7 +310,15 @@ with_test_prefix "selected_inferior" { } gdb_test "py print (gdb.selected_inferior().connection_num)" \ "$expected_connection_num" \ - "third inferior's native connection" + "third inferior's native connection number" + gdb_test "py print (gdb.selected_inferior().connection.num)" \ + "$expected_connection_num" \ + "third inferior's native connection number, though connection object" + + # Test printing of gdb.TargetConnection object. + gdb_test "py print (gdb.selected_inferior().connection)" \ + "<gdb.TargetConnection num=${expected_connection_num}, what=\"\[^\"\]+\">" \ + "print a connection object" gdb_test "inferior 1" ".*" "switch back to first inferior" gdb_test_no_output "remove-inferiors 3" "remove second inferior" |