diff options
Diffstat (limited to 'gdb/python/py-micmd.c')
-rw-r--r-- | gdb/python/py-micmd.c | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c new file mode 100644 index 00000000000..79e2575f789 --- /dev/null +++ b/gdb/python/py-micmd.c @@ -0,0 +1,739 @@ +/* MI Command Set for GDB, the GNU debugger. + + Copyright (C) 2019-2022 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 "arch-utils.h" +#include "charset.h" +#include "language.h" +#include "mi/mi-cmds.h" +#include "mi/mi-parse.h" +#include "cli/cli-cmds.h" +#include <string> + +/* Debugging of Python MI commands. */ + +static bool pymicmd_debug; + +/* Implementation of "show debug py-micmd". */ + +static void +show_pymicmd_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + gdb_printf (file, _("Python MI command debugging is %s.\n"), value); +} + +/* Print a "py-micmd" debug statement. */ + +#define pymicmd_debug_printf(fmt, ...) \ + debug_prefixed_printf_cond (pymicmd_debug, "py-micmd", fmt, ##__VA_ARGS__) + +/* Print a "py-micmd" enter/exit debug statements. */ + +#define PYMICMD_SCOPED_DEBUG_ENTER_EXIT \ + scoped_debug_enter_exit (pymicmd_debug, "py-micmd") + +struct mi_command_py; + +/* Representation of a Python gdb.MICommand object. */ + +struct micmdpy_object +{ + PyObject_HEAD + + /* The object representing this command in the MI command table. This + pointer can be nullptr if the command is not currently installed into + the MI command table (see gdb.MICommand.installed property). */ + struct mi_command_py *mi_command; + + /* The string representing the name of this command, without the leading + dash. This string is never nullptr once the Python object has been + initialised. + + The memory for this string was allocated with malloc, and needs to be + deallocated with free when the Python object is deallocated. + + When the MI_COMMAND field is not nullptr, then the mi_command_py + object's name will point back to this string. */ + char *mi_command_name; +}; + +/* The MI command implemented in Python. */ + +struct mi_command_py : public mi_command +{ + /* 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 must inherit from gdb.MICommand and must + implement the invoke method. */ + + mi_command_py (const char *name, micmdpy_object *object) + : mi_command (name, nullptr), + m_pyobj (gdbpy_ref<micmdpy_object>::new_reference (object)) + { + pymicmd_debug_printf ("this = %p", this); + m_pyobj->mi_command = this; + } + + ~mi_command_py () + { + /* The Python object representing a MI command contains a pointer back + to this c++ object. We can safely set this pointer back to nullptr + now, to indicate the Python object no longer references a valid c++ + object. + + However, the Python object also holds the storage for our name + string. We can't clear that here as our parent's destructor might + still want to reference that string. Instead we rely on the Python + object deallocator to free that memory, and reset the pointer. */ + m_pyobj->mi_command = nullptr; + + pymicmd_debug_printf ("this = %p", this); + }; + + /* Validate that CMD_OBJ, a non-nullptr pointer, is installed into the MI + command table correctly. This function looks up the command in the MI + command table and checks that the object we get back references + CMD_OBJ. This function is only intended for calling within a + gdb_assert. This function performs many assertions internally, and + then always returns true. */ + static void validate_installation (micmdpy_object *cmd_obj); + + /* Update M_PYOBJ to NEW_PYOBJ. The pointer from M_PYOBJ that points + back to this object is swapped with the pointer in NEW_PYOBJ, which + must be nullptr, so that NEW_PYOBJ now points back to this object. + Additionally our parent's name string is stored in M_PYOBJ, so we + swap the name string with NEW_PYOBJ. + + Before this call M_PYOBJ is the Python object representing this MI + command object. After this call has completed, NEW_PYOBJ now + represents this MI command object. */ + void swap_python_object (micmdpy_object *new_pyobj) + { + /* Current object has a backlink, new object doesn't have a backlink. */ + gdb_assert (m_pyobj->mi_command != nullptr); + gdb_assert (new_pyobj->mi_command == nullptr); + + /* Clear the current M_PYOBJ's backlink, set NEW_PYOBJ's backlink. */ + std::swap (new_pyobj->mi_command, m_pyobj->mi_command); + + /* Both object have names. */ + gdb_assert (m_pyobj->mi_command_name != nullptr); + gdb_assert (new_pyobj->mi_command_name != nullptr); + + /* mi_command::m_name is the string owned by the current object. */ + gdb_assert (m_pyobj->mi_command_name == this->name ()); + + /* The name in mi_command::m_name is owned by the current object. Rather + than changing the value of mi_command::m_name (which is not accessible + from here) to point to the name owned by the new object, swap the names + of the two objects, since we know they are identical strings. */ + gdb_assert (strcmp (new_pyobj->mi_command_name, + m_pyobj->mi_command_name) == 0); + std::swap (new_pyobj->mi_command_name, m_pyobj->mi_command_name); + + /* Take a reference to the new object, drop the reference to the current + object. */ + m_pyobj = gdbpy_ref<micmdpy_object>::new_reference (new_pyobj); + } + + /* Called when the MI command is invoked. */ + virtual void invoke(struct mi_parse *parse) const override; + +private: + /* The Python object representing this MI command. */ + gdbpy_ref<micmdpy_object> m_pyobj; +}; + +using mi_command_py_up = std::unique_ptr<mi_command_py>; + +extern PyTypeObject micmdpy_object_type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("micmdpy_object"); + +/* Holds a Python object containing the string 'invoke'. */ + +static PyObject *invoke_cst; + +/* Convert KEY_OBJ into a string that can be used as a field name in MI + output. KEY_OBJ must be a Python string object, and must only contain + characters suitable for use as an MI field name. + + If KEY_OBJ is not a string, or if KEY_OBJ contains invalid characters, + then an error is thrown. Otherwise, KEY_OBJ is converted to a string + and returned. */ + +static gdb::unique_xmalloc_ptr<char> +py_object_to_mi_key (PyObject *key_obj) +{ + /* The key must be a string. */ + if (!PyUnicode_Check (key_obj)) + { + gdbpy_ref<> key_repr (PyObject_Repr (key_obj)); + gdb::unique_xmalloc_ptr<char> key_repr_string; + if (key_repr != nullptr) + key_repr_string = python_string_to_target_string (key_repr.get ()); + if (key_repr_string == nullptr) + gdbpy_handle_exception (); + + gdbpy_error (_("non-string object used as key: %s"), + key_repr_string.get ()); + } + + gdb::unique_xmalloc_ptr<char> key_string + = python_string_to_target_string (key_obj); + if (key_string == nullptr) + gdbpy_handle_exception (); + + /* Predicate function, returns true if NAME is a valid field name for use + in MI result output, otherwise, returns false. */ + auto is_valid_key_name = [] (const char *name) -> bool + { + gdb_assert (name != nullptr); + + if (*name == '\0' || !isalpha (*name)) + return false; + + for (; *name != '\0'; ++name) + if (!isalnum (*name) && *name != '_' && *name != '-') + return false; + + return true; + }; + + if (!is_valid_key_name (key_string.get ())) + { + if (*key_string.get () == '\0') + gdbpy_error (_("Invalid empty key in MI result")); + else + gdbpy_error (_("Invalid key in MI result: %s"), key_string.get ()); + } + + return key_string; +} + +/* Serialize RESULT and print it in MI format to the current_uiout. + FIELD_NAME is used as the name of this result field. + + RESULT can be a dictionary, a sequence, an iterator, or an object that + can be converted to a string, these are converted to the matching MI + output format (dictionaries as tuples, sequences and iterators as lists, + and strings as named fields). + + If anything goes wrong while formatting the output then an error is + thrown. + + This function is the recursive inner core of serialize_mi_result, and + should only be called from that function. */ + +static void +serialize_mi_result_1 (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)) + { + gdb::unique_xmalloc_ptr<char> key_string + (py_object_to_mi_key (key)); + serialize_mi_result_1 (value, key_string.get ()); + } + } + else if (PySequence_Check (result) && !PyUnicode_Check (result)) + { + ui_out_emit_list list_emitter (uiout, field_name); + Py_ssize_t len = PySequence_Size (result); + if (len == -1) + gdbpy_handle_exception (); + for (Py_ssize_t i = 0; i < len; ++i) + { + gdbpy_ref<> item (PySequence_ITEM (result, i)); + if (item == nullptr) + gdbpy_handle_exception (); + serialize_mi_result_1 (item.get (), nullptr); + } + } + else if (PyIter_Check (result)) + { + gdbpy_ref<> item; + ui_out_emit_list list_emitter (uiout, field_name); + while (true) + { + item.reset (PyIter_Next (result)); + if (item == nullptr) + { + if (PyErr_Occurred () != nullptr) + gdbpy_handle_exception (); + break; + } + serialize_mi_result_1 (item.get (), nullptr); + } + } + else + { + gdb::unique_xmalloc_ptr<char> string (gdbpy_obj_to_string (result)); + if (string == nullptr) + gdbpy_handle_exception (); + uiout->field_string (field_name, string.get ()); + } +} + +/* Serialize RESULT and print it in MI format to the current_uiout. + + This function handles the top-level result initially returned from the + invoke method of the Python command implementation. At the top-level + the result must be a dictionary. The values within this dictionary can + be a wider range of types. Handling the values of the top-level + dictionary is done by serialize_mi_result_1, see that function for more + details. + + If anything goes wrong while parsing and printing the MI output then an + error is thrown. */ + +static void +serialize_mi_result (PyObject *result) +{ + /* At the top-level, the result must be a dictionary. */ + + if (!PyDict_Check (result)) + gdbpy_error (_("Result from invoke must be a dictionary")); + + PyObject *key, *value; + Py_ssize_t pos = 0; + while (PyDict_Next (result, &pos, &key, &value)) + { + gdb::unique_xmalloc_ptr<char> key_string + (py_object_to_mi_key (key)); + serialize_mi_result_1 (value, key_string.get ()); + } +} + +/* Called when the MI command is invoked. PARSE contains the parsed + command line arguments from the user. */ + +void +mi_command_py::invoke (struct mi_parse *parse) const +{ + PYMICMD_SCOPED_DEBUG_ENTER_EXIT; + + pymicmd_debug_printf ("this = %p, name = %s", this, name ()); + + mi_parse_argv (parse->args, parse); + + if (parse->argv == nullptr) + error (_("Problem parsing arguments: %s %s"), parse->command, parse->args); + + + gdbpy_enter enter_py; + + /* Place all the arguments into a list which we pass as a single argument + to the MI command's invoke method. */ + gdbpy_ref<> argobj (PyList_New (parse->argc)); + if (argobj == nullptr) + gdbpy_handle_exception (); + + for (int i = 0; i < parse->argc; ++i) + { + gdbpy_ref<> str (PyUnicode_Decode (parse->argv[i], + strlen (parse->argv[i]), + host_charset (), nullptr)); + if (PyList_SetItem (argobj.get (), i, str.release ()) < 0) + gdbpy_handle_exception (); + } + + gdb_assert (this->m_pyobj != nullptr); + gdb_assert (PyErr_Occurred () == nullptr); + gdbpy_ref<> result + (PyObject_CallMethodObjArgs ((PyObject *) this->m_pyobj.get (), invoke_cst, + argobj.get (), nullptr)); + if (result == nullptr) + gdbpy_handle_exception (); + + if (result != Py_None) + serialize_mi_result (result.get ()); +} + +/* See declaration above. */ + +void +mi_command_py::validate_installation (micmdpy_object *cmd_obj) +{ + gdb_assert (cmd_obj != nullptr); + mi_command_py *cmd = cmd_obj->mi_command; + gdb_assert (cmd != nullptr); + const char *name = cmd_obj->mi_command_name; + gdb_assert (name != nullptr); + gdb_assert (name == cmd->name ()); + mi_command *mi_cmd = mi_cmd_lookup (name); + gdb_assert (mi_cmd == cmd); + gdb_assert (cmd->m_pyobj == cmd_obj); +} + +/* Return CMD as an mi_command_py if it is a Python MI command, else + nullptr. */ + +static mi_command_py * +as_mi_command_py (mi_command *cmd) +{ + return dynamic_cast<mi_command_py *> (cmd); +} + +/* Uninstall OBJ, making the MI command represented by OBJ unavailable for + use by the user. On success 0 is returned, otherwise -1 is returned + and a Python exception will be set. */ + +static int +micmdpy_uninstall_command (micmdpy_object *obj) +{ + PYMICMD_SCOPED_DEBUG_ENTER_EXIT; + + gdb_assert (obj->mi_command != nullptr); + gdb_assert (obj->mi_command_name != nullptr); + + pymicmd_debug_printf ("name = %s", obj->mi_command_name); + + /* Remove the command from the internal MI table of commands. This will + cause the mi_command_py object to be deleted, which will clear the + backlink in OBJ. */ + bool removed = remove_mi_cmd_entry (obj->mi_command->name ()); + gdb_assert (removed); + gdb_assert (obj->mi_command == nullptr); + + return 0; +} + +/* Install OBJ as a usable MI command. Return 0 on success, and -1 on + error, in which case, a Python error will have been set. + + After successful completion the command name associated with OBJ will + be installed in the MI command table (so it can be found if the user + enters that command name), additionally, OBJ will have been added to + the gdb._mi_commands dictionary (using the command name as its key), + this will ensure that OBJ remains live even if the user gives up all + references. */ + +static int +micmdpy_install_command (micmdpy_object *obj) +{ + PYMICMD_SCOPED_DEBUG_ENTER_EXIT; + + gdb_assert (obj->mi_command == nullptr); + gdb_assert (obj->mi_command_name != nullptr); + + pymicmd_debug_printf ("name = %s", obj->mi_command_name); + + /* Look up this command name in MI_COMMANDS, a command with this name may + already exist. */ + mi_command *cmd = mi_cmd_lookup (obj->mi_command_name); + mi_command_py *cmd_py = as_mi_command_py (cmd); + + if (cmd != nullptr && cmd_py == nullptr) + { + /* There is already an MI command registered with that name, and it's not + a Python one. Forbid replacing a non-Python MI command. */ + PyErr_SetString (PyExc_RuntimeError, + _("unable to add command, name is already in use")); + return -1; + } + + if (cmd_py != nullptr) + { + /* There is already a Python MI command registered with that name, swap + in the new gdb.MICommand implementation. */ + cmd_py->swap_python_object (obj); + } + else + { + /* There's no MI command registered with that name at all, create one. */ + mi_command_py_up mi_cmd (new mi_command_py (obj->mi_command_name, obj)); + + /* Add the command to the gdb internal MI command table. */ + bool result = insert_mi_cmd_entry (std::move (mi_cmd)); + gdb_assert (result); + } + + return 0; +} + +/* Implement gdb.MICommand.__init__. The init method takes the name of + the MI command as the first argument, which must be a string, starting + with a single dash. */ + +static int +micmdpy_init (PyObject *self, PyObject *args, PyObject *kwargs) +{ + PYMICMD_SCOPED_DEBUG_ENTER_EXIT; + + micmdpy_object *cmd = (micmdpy_object *) self; + + static const char *keywords[] = { "name", nullptr }; + const char *name; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "s", keywords, + &name)) + return -1; + + /* Validate command name */ + const int name_len = strlen (name); + if (name_len == 0) + { + PyErr_SetString (PyExc_ValueError, _("MI command name is empty.")); + return -1; + } + else if ((name_len < 2) || (name[0] != '-') || !isalnum (name[1])) + { + PyErr_SetString (PyExc_ValueError, + _("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] != '-') + { + PyErr_Format + (PyExc_ValueError, + _("MI command name contains invalid character: %c."), + name[i]); + return -1; + } + } + + /* Skip over the leading dash. For the rest of this function the + dash is not important. */ + ++name; + } + + /* If this object already has a name set, then this object has been + initialized before. We handle this case a little differently. */ + if (cmd->mi_command_name != nullptr) + { + /* First, we don't allow the user to change the MI command name. + Supporting this would be tricky as we would need to delete the + mi_command_py from the MI command table, however, the user might + be trying to perform this reinitialization from within the very + command we're about to delete... it all gets very messy. + + So, for now at least, we don't allow this. This doesn't seem like + an excessive restriction. */ + if (strcmp (cmd->mi_command_name, name) != 0) + { + PyErr_SetString + (PyExc_ValueError, + _("can't reinitialize object with a different command name")); + return -1; + } + + /* If there's already an object registered with the MI command table, + then we're done. That object must be a mi_command_py, which + should reference back to this micmdpy_object. */ + if (cmd->mi_command != nullptr) + { + mi_command_py::validate_installation (cmd); + return 0; + } + } + else + cmd->mi_command_name = xstrdup (name); + + /* Now we can install this mi_command_py in the MI command table. */ + return micmdpy_install_command (cmd); +} + +/* Called when a gdb.MICommand object is deallocated. */ + +static void +micmdpy_dealloc (PyObject *obj) +{ + PYMICMD_SCOPED_DEBUG_ENTER_EXIT; + + micmdpy_object *cmd = (micmdpy_object *) obj; + + /* If the Python object failed to initialize, then the name field might + be nullptr. */ + pymicmd_debug_printf ("obj = %p, name = %s", cmd, + (cmd->mi_command_name == nullptr + ? "(null)" : cmd->mi_command_name)); + + /* As the mi_command_py object holds a reference to the micmdpy_object, + the only way the dealloc function can be called is if the mi_command_py + object has been deleted, in which case the following assert will + hold. */ + gdb_assert (cmd->mi_command == nullptr); + + /* Free the memory that holds the command name. */ + xfree (cmd->mi_command_name); + cmd->mi_command_name = nullptr; + + /* Finally, free the memory for this Python object. */ + Py_TYPE (obj)->tp_free (obj); +} + +/* Python initialization for the MI commands components. */ + +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 = PyUnicode_FromString ("invoke"); + if (invoke_cst == nullptr) + return -1; + + return 0; +} + +void +gdbpy_finalize_micommands () +{ + /* mi_command_py objects hold references to micmdpy_object objects. They must + be dropped before the Python interpreter is finalized. Do so by removing + those MI command entries, thus deleting the mi_command_py objects. */ + remove_mi_cmd_entries ([] (mi_command *cmd) + { + return as_mi_command_py (cmd) != nullptr; + }); +} + +/* Get the gdb.MICommand.name attribute, returns a string, the name of this + MI command. */ + +static PyObject * +micmdpy_get_name (PyObject *self, void *closure) +{ + struct micmdpy_object *micmd_obj = (struct micmdpy_object *) self; + + gdb_assert (micmd_obj->mi_command_name != nullptr); + std::string name_str = string_printf ("-%s", micmd_obj->mi_command_name); + return PyUnicode_FromString (name_str.c_str ()); +} + +/* Get the gdb.MICommand.installed property. Returns true if this MI + command is installed into the MI command table, otherwise returns + false. */ + +static PyObject * +micmdpy_get_installed (PyObject *self, void *closure) +{ + struct micmdpy_object *micmd_obj = (struct micmdpy_object *) self; + + if (micmd_obj->mi_command == nullptr) + Py_RETURN_FALSE; + Py_RETURN_TRUE; +} + +/* Set the gdb.MICommand.installed property. The property can be set to + either true or false. Setting the property to true will cause the + command to be installed into the MI command table (if it isn't + already), while setting this property to false will cause the command + to be removed from the MI command table (if it is present). */ + +static int +micmdpy_set_installed (PyObject *self, PyObject *newvalue, void *closure) +{ + struct micmdpy_object *micmd_obj = (struct micmdpy_object *) self; + + bool installed_p = PyObject_IsTrue (newvalue); + if (installed_p == (micmd_obj->mi_command != nullptr)) + return 0; + + if (installed_p) + return micmdpy_install_command (micmd_obj); + else + return micmdpy_uninstall_command (micmd_obj); +} + +/* The gdb.MICommand properties. */ + +static gdb_PyGetSetDef micmdpy_object_getset[] = { + { "name", micmdpy_get_name, nullptr, "The command's name.", nullptr }, + { "installed", micmdpy_get_installed, micmdpy_set_installed, + "Is this command installed for use.", nullptr }, + { nullptr } /* Sentinel. */ +}; + +/* The gdb.MICommand descriptor. */ + +PyTypeObject micmdpy_object_type = { + PyVarObject_HEAD_INIT (nullptr, 0) "gdb.MICommand", /*tp_name */ + sizeof (micmdpy_object), /*tp_basicsize */ + 0, /*tp_itemsize */ + micmdpy_dealloc, /*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 */ + 0, /* tp_methods */ + 0, /* tp_members */ + micmdpy_object_getset, /* 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 */ +}; + +void _initialize_py_micmd (); +void +_initialize_py_micmd () +{ + add_setshow_boolean_cmd + ("py-micmd", class_maintenance, &pymicmd_debug, + _("Set Python micmd debugging."), + _("Show Python micmd debugging."), + _("When on, Python micmd debugging is enabled."), + nullptr, + show_pymicmd_debug, + &setdebuglist, &showdebuglist); +} |