[PATCH 2/5] gdb/python: create GDB/MI commands using python.
Jan Vrany
jan.vrany@labware.com
Mon Jan 17 12:44:22 GMT 2022
This commit allows an user to create custom MI commands using Python
similarly to what is possible for Python CLI commands.
A new subclass of mi_command is defined for Python MI commands,
mi_command_py. A new file, py-micmd.c contains the logic for Python
MI commands.
---
gdb/Makefile.in | 1 +
gdb/mi/mi-cmds.c | 13 +-
gdb/mi/mi-cmds.h | 14 ++
gdb/python/py-micmd.c | 300 +++++++++++++++++++++++++++++++++++
gdb/python/py-micmd.h | 63 ++++++++
gdb/python/python-internal.h | 2 +
gdb/python/python.c | 13 +-
7 files changed, 396 insertions(+), 10 deletions(-)
create mode 100644 gdb/python/py-micmd.c
create mode 100644 gdb/python/py-micmd.h
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index d0db5fbdee1..5cb428459c3 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -409,6 +409,7 @@ SUBDIR_PYTHON_SRCS = \
python/py-lazy-string.c \
python/py-linetable.c \
python/py-membuf.c \
+ python/py-micmd.c \
python/py-newobjfileevent.c \
python/py-objfile.c \
python/py-param.c \
diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c
index 57fe32c1cc6..c82bc4810df 100644
--- a/gdb/mi/mi-cmds.c
+++ b/gdb/mi/mi-cmds.c
@@ -26,13 +26,11 @@
#include <map>
#include <string>
-/* A command held in the MI_CMD_TABLE. */
+/* See mi-cmds.h. */
-using mi_command_up = std::unique_ptr<struct mi_command>;
+std::map<std::string, mi_command_up> mi_cmd_table;
-/* MI command table (built at run time). */
-static std::map<std::string, mi_command_up> mi_cmd_table;
/* The abstract base class for all built-in MI command types. */
@@ -134,12 +132,9 @@ struct mi_command_cli : public mi_command_builtin
bool m_args_p;
};
-/* Insert COMMAND into the global mi_cmd_table. Return false if
- COMMAND->name already exists in mi_cmd_table, in which case COMMAND will
- not have been added to mi_cmd_table. Otherwise, return true, and
- COMMAND was added to mi_cmd_table. */
+/* See mi-cmds.h. */
-static bool
+bool
insert_mi_cmd_entry (mi_command_up command)
{
gdb_assert (command != nullptr);
diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h
index 94ecb271d48..75c5927e3cb 100644
--- a/gdb/mi/mi-cmds.h
+++ b/gdb/mi/mi-cmds.h
@@ -22,6 +22,7 @@
#ifndef MI_MI_CMDS_H
#define MI_MI_CMDS_H
+#include <map>
#include "gdbsupport/gdb_optional.h"
enum print_values {
@@ -183,6 +184,14 @@ struct mi_command
int *m_suppress_notification;
};
+/* A command held in the MI_CMD_TABLE. */
+
+using mi_command_up = std::unique_ptr<struct mi_command>;
+
+/* MI command table (built at run time). */
+
+extern std::map<std::string, mi_command_up> mi_cmd_table;
+
/* Lookup a command in the MI command table, returns nullptr if COMMAND is
not found. */
@@ -190,4 +199,9 @@ extern mi_command *mi_cmd_lookup (const char *command);
extern void mi_execute_command (const char *cmd, int from_tty);
+/* Insert a new mi-command into the command table. Return true if
+ insertion was successful. */
+
+extern bool insert_mi_cmd_entry (mi_command_up command);
+
#endif /* MI_MI_CMDS_H */
diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c
new file mode 100644
index 00000000000..b94b748b25c
--- /dev/null
+++ b/gdb/python/py-micmd.c
@@ -0,0 +1,300 @@
+/* MI Command Set for GDB, the GNU debugger.
+
+ Copyright (C) 2019 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/>. */
+
+/* gdb MI commands implemented in Python */
+
+#include "defs.h"
+#include "python-internal.h"
+#include "python/py-micmd.h"
+#include "arch-utils.h"
+#include "charset.h"
+#include "language.h"
+
+#include <string>
+
+static PyObject *invoke_cst;
+
+extern PyTypeObject
+ micmdpy_object_type CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("micmdpy_object");
+
+/* If the command invoked returns a list, this function parses it and create an
+ appropriate MI out output.
+
+ The returned values must be Python string, and can be contained within Python
+ lists and dictionaries. It is possible to have a multiple levels of lists
+ and/or dictionaries. */
+
+static void
+parse_mi_result (PyObject *result, const char *field_name)
+{
+ struct ui_out *uiout = current_uiout;
+
+ if (PyDict_Check (result))
+ {
+ PyObject *key, *value;
+ Py_ssize_t pos = 0;
+ ui_out_emit_tuple tuple_emitter (uiout, field_name);
+ while (PyDict_Next (result, &pos, &key, &value))
+ {
+ if (!PyString_Check (key))
+ {
+ gdbpy_ref<> key_repr (PyObject_Repr (key));
+ if (PyErr_Occurred () != NULL)
+ {
+ gdbpy_err_fetch ex;
+ gdb::unique_xmalloc_ptr<char> ex_msg (ex.to_string ());
+
+ if (ex_msg == NULL || *ex_msg == '\0')
+ error (_("Non-string object used as key."));
+ else
+ error (_("Non-string object used as key: %s."),
+ ex_msg.get ());
+ }
+ else
+ {
+ auto key_repr_string
+ = python_string_to_target_string (key_repr.get ());
+ error (_("Non-string object used as key: %s."),
+ key_repr_string.get ());
+ }
+ }
+
+ auto key_string = python_string_to_target_string (key);
+ parse_mi_result (value, key_string.get ());
+ }
+ }
+ else if (PySequence_Check (result) && !PyString_Check (result))
+ {
+ ui_out_emit_list list_emitter (uiout, field_name);
+ for (Py_ssize_t i = 0; i < PySequence_Size (result); ++i)
+ {
+ gdbpy_ref<> item (PySequence_ITEM (result, i));
+ parse_mi_result (item.get (), NULL);
+ }
+ }
+ else if (PyIter_Check (result))
+ {
+ gdbpy_ref<> item;
+ ui_out_emit_list list_emitter (uiout, field_name);
+ while (item.reset (PyIter_Next (result)), item != nullptr)
+ parse_mi_result (item.get (), NULL);
+ }
+ else
+ {
+ gdb::unique_xmalloc_ptr<char> string (gdbpy_obj_to_string (result));
+ uiout->field_string (field_name, string.get ());
+ }
+}
+
+/* Object initializer; sets up gdb-side structures for MI command.
+
+ Use: __init__(NAME).
+
+ NAME is the name of the MI command to register. It must start with a dash
+ as traditional MI commands do. */
+
+static int
+micmdpy_init (PyObject *self, PyObject *args, PyObject *kw)
+{
+ const char *name;
+ gdbpy_ref<> self_ref = gdbpy_ref<>::new_reference (self);
+
+ if (!PyArg_ParseTuple (args, "s", &name))
+ return -1;
+
+ /* Validate command name */
+ const int name_len = strlen (name);
+ if (name_len == 0)
+ {
+ error (_("MI command name is empty."));
+ return -1;
+ }
+ else if ((name_len < 2) || (name[0] != '-') || !isalnum (name[1]))
+ {
+ error (_("MI command name does not start with '-'"
+ " followed by at least one letter or digit."));
+ return -1;
+ }
+ else
+ for (int i = 2; i < name_len; i++)
+ {
+ if (!isalnum (name[i]) && name[i] != '-')
+ {
+ error (_("MI command name contains invalid character: %c."),
+ name[i]);
+ return -1;
+ }
+ }
+
+ if (!PyObject_HasAttr (self_ref.get (), invoke_cst))
+ error (_("-%s: Python command object missing 'invoke' method."), name);
+
+ try
+ {
+ mi_command_up micommand = mi_command_up(new mi_command_py (name + 1, self_ref));
+
+ bool result = insert_mi_cmd_entry (std::move (micommand));
+
+ if (!result)
+ {
+ error (_("Unable to insert command."
+ "The name might already be in use."));
+ return -1;
+ }
+ }
+ catch (const gdb_exception &except)
+ {
+ GDB_PY_SET_HANDLE_EXCEPTION (except);
+ }
+
+ return 0;
+}
+
+mi_command_py::mi_command_py (const char *name, gdbpy_ref<> object)
+ : mi_command (NULL),
+ pyobj (object)
+{
+ gdb_assert (name != nullptr && name[0] != '\0' && name[0] != '-');
+ m_name = name;
+}
+
+void
+mi_command_py::do_invoke (struct mi_parse *parse) const
+{
+ mi_parse_argv (parse->args, parse);
+
+ if (parse->argv == NULL)
+ error (_("Problem parsing arguments: %s %s"), parse->command, parse->args);
+
+ PyObject *obj = this->pyobj.get ();
+
+ gdbpy_enter enter_py (get_current_arch (), current_language);
+
+ gdb_assert (obj != nullptr);
+
+ if (!PyObject_HasAttr (obj, invoke_cst))
+ error (_("-%s: Python command object missing 'invoke' method."),
+ name ());
+
+
+ gdbpy_ref<> argobj (PyList_New (parse->argc));
+ if (argobj == nullptr)
+ {
+ gdbpy_print_stack ();
+ error (_("-%s: failed to create the Python arguments list."),
+ name ());
+ }
+
+ for (int i = 0; i < parse->argc; ++i)
+ {
+ gdbpy_ref<> str (PyUnicode_Decode (parse->argv[i], strlen (parse->argv[i]),
+ host_charset (), NULL));
+ if (PyList_SetItem (argobj.get (), i, str.release ()) != 0)
+ {
+ error (_("-%s: failed to create the Python arguments list."),
+ name ());
+ }
+ }
+
+ gdb_assert (PyErr_Occurred () == NULL);
+ gdbpy_ref<> result (
+ PyObject_CallMethodObjArgs (obj, invoke_cst, argobj.get (), NULL));
+ if (PyErr_Occurred () != NULL)
+ {
+ gdbpy_err_fetch ex;
+ gdb::unique_xmalloc_ptr<char> ex_msg (ex.to_string ());
+
+ if (ex_msg == NULL || *ex_msg == '\0')
+ error (_("-%s: failed to execute command"), name ());
+ else
+ error (_("-%s: %s"), name (), ex_msg.get ());
+ }
+ else
+ {
+ if (Py_None != result)
+ parse_mi_result (result.get (), "result");
+ }
+}
+
+void mi_command_py::finalize ()
+{
+ this->pyobj.reset (nullptr);
+}
+
+/* Initialize the MI command object. */
+
+int
+gdbpy_initialize_micommands ()
+{
+ micmdpy_object_type.tp_new = PyType_GenericNew;
+ if (PyType_Ready (&micmdpy_object_type) < 0)
+ return -1;
+
+ if (gdb_pymodule_addobject (gdb_module, "MICommand",
+ (PyObject *) &micmdpy_object_type)
+ < 0)
+ return -1;
+
+ invoke_cst = PyString_FromString ("invoke");
+ if (invoke_cst == NULL)
+ return -1;
+
+ return 0;
+}
+
+static PyMethodDef micmdpy_object_methods[] = {{0}};
+
+PyTypeObject micmdpy_object_type = {
+ PyVarObject_HEAD_INIT (NULL, 0) "gdb.MICommand", /*tp_name */
+ sizeof (micmdpy_object), /*tp_basicsize */
+ 0, /*tp_itemsize */
+ 0, /*tp_dealloc */
+ 0, /*tp_print */
+ 0, /*tp_getattr */
+ 0, /*tp_setattr */
+ 0, /*tp_compare */
+ 0, /*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 | Py_TPFLAGS_BASETYPE, /*tp_flags */
+ "GDB mi-command object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ micmdpy_object_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ micmdpy_init, /* tp_init */
+ 0, /* tp_alloc */
+};
diff --git a/gdb/python/py-micmd.h b/gdb/python/py-micmd.h
new file mode 100644
index 00000000000..b6b25351ac7
--- /dev/null
+++ b/gdb/python/py-micmd.h
@@ -0,0 +1,63 @@
+/* MI Command Set for GDB, the GNU debugger.
+
+ Copyright (C) 2019 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/>. */
+
+#ifndef PY_MICMDS_H
+#define PY_MICMDS_H
+
+#include "mi/mi-cmds.h"
+#include "mi/mi-parse.h"
+#include "python-internal.h"
+#include "python/py-ref.h"
+
+struct micmdpy_object
+{
+ PyObject_HEAD
+};
+
+typedef struct micmdpy_object micmdpy_object;
+
+/* MI command implemented in Python. */
+
+class mi_command_py : public mi_command
+{
+ public:
+ /* Constructs a new mi_command_py object. NAME is command name without
+ leading dash. OBJECT is a reference to a Python object implementing
+ the command. This object should inherit from gdb.MICommand and should
+ implement method invoke (args). */
+ mi_command_py (const char *name, gdbpy_ref<> object);
+
+
+ /* This is called just before shutting down a Python interpreter
+ to release python object implementing the command. */
+ void finalize ();
+
+ virtual const char *name () const override
+ { return m_name.c_str(); }
+
+ protected:
+ virtual void do_invoke(struct mi_parse *parse) const override;
+
+ private:
+ std::string m_name;
+
+ gdbpy_ref<> pyobj;
+};
+
+#endif
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 583989c5a6d..d5a0b4a4a91 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -561,6 +561,8 @@ int gdbpy_initialize_membuf ()
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
int gdbpy_initialize_connection ()
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_micommands (void)
+ 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 4dcda53d9ab..6e2ed32bf90 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -37,6 +37,8 @@
#include "run-on-main-thread.h"
#include "gdbsupport/selftest.h"
#include "observable.h"
+#include "mi/mi-cmds.h"
+#include "py-micmd.h"
/* Declared constants and enum for python stack printing. */
static const char python_excp_none[] = "none";
@@ -1699,6 +1701,14 @@ finalize_python (void *ignore)
python_gdbarch = target_gdbarch ();
python_language = current_language;
+ for (const auto& name_and_cmd : mi_cmd_table)
+ {
+ mi_command *cmd = name_and_cmd.second.get ();
+ mi_command_py *cmd_py = dynamic_cast<mi_command_py*> (cmd);
+ if (cmd_py != nullptr)
+ cmd_py->finalize ();
+ }
+
Py_Finalize ();
gdb_python_initialized = false;
@@ -1887,7 +1897,8 @@ do_start_initialization ()
|| gdbpy_initialize_unwind () < 0
|| gdbpy_initialize_membuf () < 0
|| gdbpy_initialize_connection () < 0
- || gdbpy_initialize_tui () < 0)
+ || gdbpy_initialize_tui () < 0
+ || gdbpy_initialize_micommands () < 0)
return false;
#define GDB_PY_DEFINE_EVENT_TYPE(name, py_name, doc, base) \
--
2.30.2
More information about the Gdb-patches
mailing list