diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/NEWS | 5 | ||||
-rw-r--r-- | gdb/doc/python.texi | 45 | ||||
-rw-r--r-- | gdb/python/py-mi.c | 74 | ||||
-rw-r--r-- | gdb/python/python-internal.h | 5 | ||||
-rw-r--r-- | gdb/python/python.c | 4 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-mi-notify.exp | 71 |
6 files changed, 204 insertions, 0 deletions
@@ -6,6 +6,11 @@ * GDB index now contains information about the main function. This speeds up startup when it is being used for some large binaries. +* Python API + + ** New function gdb.notify_mi(NAME, DATA), that emits custom + GDB/MI async notification. + *** Changes in GDB 14 * GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index a97e445..546b4d4 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -211,6 +211,7 @@ optional arguments while skipping others. Example: * Recordings In Python:: Accessing recordings from Python. * CLI Commands In Python:: Implementing new CLI commands in Python. * GDB/MI Commands In Python:: Implementing new @sc{gdb/mi} commands in Python. +* GDB/MI Notifications In Python:: Implementing new @sc{gdb/mi} notifications in Python. * Parameters In Python:: Adding new @value{GDBN} parameters. * Functions In Python:: Writing new convenience functions. * Progspaces In Python:: Program spaces. @@ -4804,6 +4805,50 @@ Here is how this works using the commands from the example above: @{'string': 'abc, def, ghi'@} @end smallexample +@node GDB/MI Notifications In Python +@subsubsection @sc{gdb/mi} Notifications In Python + +@cindex MI notifications in python +@cindex notifications in python, GDB/MI +@cindex python notifications, GDB/MI + +It is possible to emit @sc{gdb/mi} notifications from +Python. Use the @code{gdb.notify_mi} function to do that. + +@defun gdb.notify_mi (name @r{[}, data@r{]}) +Emit a @sc{gdb/mi} asynchronous notification. @var{name} is the name of the +notification, consisting of alphanumeric characters and a hyphen (@code{-}). +@var{data} is any additional data to be emitted with the notification, passed +as a Python dictionary. This argument is optional. The dictionary is converted +to a @sc{gdb/mi} @var{result} records (@pxref{GDB/MI Output Syntax}) the same way +as result of Python MI command (@pxref{GDB/MI Commands In Python}). + +If @var{data} is @code{None} then no additional values are emitted. +@end defun + +While using existing notification names (@pxref{GDB/MI Async Records}) with +@code{gdb.notify_mi} is allowed, users are encouraged to prefix user-defined +notification with a hyphen (@code{-}) to avoid possible conflict. +@value{GDBN} will never introduce notification starting with hyphen. + +Here is how to emit @code{=-connection-removed} whenever a connection to remote +GDB server is closed (@pxref{Connections In Python}): + +@smallexample +def notify_connection_removed(event): + data = @{"id": event.connection.num, "type": event.connection.type@} + gdb.notify_mi("-connection-removed", data) + + +gdb.events.connection_removed.connect(notify_connection_removed) +@end smallexample + +Then, each time a connection is closed, there will be a notification on MI channel: + +@smallexample +=-connection-removed,id="1",type="remote" +@end smallexample + @node Parameters In Python @subsubsection Parameters In Python diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c index 36bcb6c..a7b4f4f 100644 --- a/gdb/python/py-mi.c +++ b/gdb/python/py-mi.c @@ -19,8 +19,14 @@ #include "defs.h" #include "python-internal.h" +#include "utils.h" +#include "ui.h" #include "ui-out.h" +#include "interps.h" +#include "target.h" #include "mi/mi-parse.h" +#include "mi/mi-console.h" +#include "mi/mi-interp.h" /* A ui_out subclass that creates a Python object based on the data that is passed in. */ @@ -455,3 +461,71 @@ serialize_mi_results (PyObject *results) serialize_mi_result_1 (value, key_string.get ()); } } + +/* See python-internal.h. */ + +PyObject * +gdbpy_notify_mi (PyObject *self, PyObject *args, PyObject *kwargs) +{ + static const char *keywords[] = { "name", "data", nullptr }; + char *name = nullptr; + PyObject *data = Py_None; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "s|O", keywords, + &name, &data)) + return nullptr; + + /* Validate notification name. */ + const int name_len = strlen (name); + if (name_len == 0) + { + PyErr_SetString (PyExc_ValueError, _("MI notification name is empty.")); + return nullptr; + } + for (int i = 0; i < name_len; i++) + { + if (!isalnum (name[i]) && name[i] != '-') + { + PyErr_Format + (PyExc_ValueError, + _("MI notification name contains invalid character: %c."), + name[i]); + return nullptr; + } + } + + /* Validate additional data. */ + if (!(data == Py_None || PyDict_Check (data))) + { + PyErr_Format + (PyExc_ValueError, + _("MI notification data must be either None or a dictionary, not %s"), + Py_TYPE (data)->tp_name); + return nullptr; + } + + SWITCH_THRU_ALL_UIS () + { + struct mi_interp *mi = as_mi_interp (top_level_interpreter ()); + + if (mi == nullptr) + continue; + + target_terminal::scoped_restore_terminal_state term_state; + target_terminal::ours_for_output (); + + gdb_printf (mi->event_channel, "%s", name); + if (data != Py_None) + { + ui_out *mi_uiout = mi->interp_ui_out (); + ui_out_redirect_pop redir (mi_uiout, mi->event_channel); + scoped_restore restore_uiout + = make_scoped_restore (¤t_uiout, mi_uiout); + + serialize_mi_results (data); + } + gdb_flush (mi->event_channel); + } + + Py_RETURN_NONE; +} diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 60b795f..847bed8 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -499,6 +499,11 @@ extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args, extern void serialize_mi_results (PyObject *results); +/* Implementation of the gdb.notify_mi function. */ + +extern PyObject *gdbpy_notify_mi (PyObject *self, PyObject *args, + PyObject *kw); + /* Convert Python object OBJ to a program_space pointer. OBJ must be a gdb.Progspace reference. Return nullptr if the gdb.Progspace is not valid (see gdb.Progspace.is_valid), otherwise return the program_space diff --git a/gdb/python/python.c b/gdb/python/python.c index 6a978d6..faa7e0c 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -2669,6 +2669,10 @@ Return the name of the currently selected language." }, "print_options () -> dict\n\ Return the current print options." }, + { "notify_mi", (PyCFunction) gdbpy_notify_mi, + METH_VARARGS | METH_KEYWORDS, + "notify_mi (name, data) -> None\n\ +Output async record to MI channels if any." }, {NULL, NULL, 0, NULL} }; diff --git a/gdb/testsuite/gdb.python/py-mi-notify.exp b/gdb/testsuite/gdb.python/py-mi-notify.exp new file mode 100644 index 0000000..8ba7703 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-mi-notify.exp @@ -0,0 +1,71 @@ +# Copyright (C) 2023 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 custom MI notifications implemented in Python. + +load_lib gdb-python.exp +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +gdb_exit +if {[mi_gdb_start]} { + return +} + +if {[lsearch -exact [mi_get_features] python] < 0} { + unsupported "python support is disabled" + return -1 +} + +standard_testfile + +mi_gdb_test "set python print-stack full" \ + ".*\\^done" \ + "set python print-stack full" + +mi_gdb_test "python gdb.notify_mi('-test-notification')" \ + ".*=-test-notification\r\n\\^done" \ + "python notification, no additional data parameter" + +mi_gdb_test "python gdb.notify_mi('-test-notification', None)" \ + ".*=-test-notification\r\n\\^done" \ + "python notification, no additional data" + +mi_gdb_test "python gdb.notify_mi('-test-notification', \{ 'data1' : 1 , 'data2' : 2 })" \ + ".*=-test-notification,data1=\"1\",data2=\"2\"\r\n\\^done" \ + "python notification, with additional data" + +mi_gdb_test "python gdb.notify_mi('-test-notification', 1)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, invalid additional data" + +mi_gdb_test "python gdb.notify_mi('', None)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, empty notification name" + +mi_gdb_test "python gdb.notify_mi('**invalid**', None)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, invalid notification name" + +mi_gdb_test "python gdb.notify_mi(\[1,2,3\], None)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, non-string notification name" + +mi_gdb_test "python gdb.notify_mi()" \ + ".*\\^error,msg=\".*\"" \ + "python notification, no arguments passed" + +mi_gdb_test "python gdb.notify_mi('thread-group-added', \{'id' : 'i2'\})" \ + ".*=thread-group-added,id=\"i2\"\r\n\\^done" \ + "python notification, using existing internal notification name" |