aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
Diffstat (limited to 'gdb')
-rw-r--r--gdb/NEWS5
-rw-r--r--gdb/doc/python.texi45
-rw-r--r--gdb/python/py-mi.c74
-rw-r--r--gdb/python/python-internal.h5
-rw-r--r--gdb/python/python.c4
-rw-r--r--gdb/testsuite/gdb.python/py-mi-notify.exp71
6 files changed, 204 insertions, 0 deletions
diff --git a/gdb/NEWS b/gdb/NEWS
index 0f75abe..81264c0 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -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 (&current_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"