From patchwork Fri Feb 10 20:40:53 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: didier.nadeau@gmail.com X-Patchwork-Id: 19222 Received: (qmail 120467 invoked by alias); 10 Feb 2017 20:41:21 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 120443 invoked by uid 89); 10 Feb 2017 20:41:20 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=1.2 required=5.0 tests=AWL, BAYES_50, FREEMAIL_FROM, SPF_HELO_PASS, SPF_SOFTFAIL autolearn=no version=3.3.2 spammy=valueh, value.h, 4556, 1.9.1 X-HELO: smtp.polymtl.ca Received: from smtp.polymtl.ca (HELO smtp.polymtl.ca) (132.207.4.11) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 10 Feb 2017 20:41:10 +0000 Received: from didier-gpu.dorsal.polymtl.ca (dyn48.dorsal.polymtl.ca [132.207.72.48]) by smtp.polymtl.ca (8.14.3/8.14.3) with ESMTP id v1AKf4vI018328; Fri, 10 Feb 2017 15:41:05 -0500 From: Didier Nadeau To: palves@redhat.com Cc: gdb-patches@sourceware.org, Didier Nadeau Subject: [PATCH] Create MI commands using python. Date: Fri, 10 Feb 2017 15:40:53 -0500 Message-Id: <1486759253-26582-1-git-send-email-didier.nadeau@gmail.com> In-Reply-To: References: X-Poly-FromMTA: (dyn48.dorsal.polymtl.ca [132.207.72.48]) at Fri, 10 Feb 2017 20:41:04 +0000 X-IsSubscribed: yes 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/ChangeLog 2017-02-10 Didier Nadeau * Makefile.in (SUBDIR_PYTHON_OBS): Add py-micmd.o. (SUBDIR_PYTHON_SRCS): Add py-micmd.c. * mi/mi-cmds.c (insert_mi_cmd_entry): Remove static. (mi_command_py::mi_command_py): New function. (mi_command_py::invoke): New function. * mi/mi-cmds.h (mi_command_py): New class. (insert_mi_cmd_entry): New declaration. * python/py-micmd.c: New file (micmdpy_object): New struct. (micmdpy_object): New typedef. (parse_mi_result): New function. (py_mi_invoke): New function. (micmdpy_parse_command_name): New function. (micmdpy_init): New function. (gdbpy_initialize_micommands): New function. * python/py-micmd.h: New file (py_mi_invoke): New function. * python/python-internal.h (gdbpy_initialize_micommands): New declaration. * python/python.c (_initialize_python): New call to gdbpy_initialize_micommands. --- gdb/Makefile.in | 2 + gdb/mi/mi-cmds.c | 27 +++- gdb/mi/mi-cmds.h | 19 +++ gdb/python/py-micmd.c | 293 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/py-micmd.h | 6 + gdb/python/python-internal.h | 2 + gdb/python/python.c | 3 +- 7 files changed, 348 insertions(+), 4 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 3ce7d69..ae1f766 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -455,6 +455,7 @@ SUBDIR_PYTHON_OBS = \ py-infthread.o \ py-lazy-string.o \ py-linetable.o \ + py-micmd.o \ py-newobjfileevent.o \ py-objfile.o \ py-param.o \ @@ -495,6 +496,7 @@ SUBDIR_PYTHON_SRCS = \ python/py-infthread.c \ python/py-lazy-string.c \ python/py-linetable.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 160b864..2b4fef5 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -23,6 +23,7 @@ #include "mi-cmds.h" #include "mi-main.h" #include "mi-parse.h" +#include "python/py-micmd.h" #include #include #include @@ -31,10 +32,9 @@ static std::map mi_cmd_table; -/* Insert a new mi-command into the command table. Return true if - insertion was successful. */ +/* See mi-cmds.h. */ -static bool +bool insert_mi_cmd_entry (mi_cmd_up command) { gdb_assert (command != NULL); @@ -132,6 +132,27 @@ mi_command_cli::invoke (struct mi_parse *parse) parse->args); } +mi_command_py::mi_command_py (const char *name, int *suppress_notification, + void *object) + : mi_command (name, suppress_notification), + pyobj (object) +{} + +void +mi_command_py::invoke (struct mi_parse *parse) +{ + std::unique_ptr> restore + = do_suppress_notification (); + + mi_parse_argv (parse->args, parse); + + if (parse->argv == NULL) + error (_("Problem parsing arguments: %s %s"), parse->command, parse->args); + + py_mi_invoke (this->pyobj, parse->argv, parse->argc); + +} + /* Initialize the available MI commands. */ static void diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index c5e596b..9d6ef8c 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -177,6 +177,20 @@ class mi_command_cli : public mi_command int m_args_p; }; +/* MI command implemented on top of a Python command. */ + +class mi_command_py : public mi_command +{ + public: + mi_command_py (const char *name, int *suppress_notification, + void *object); + void invoke (struct mi_parse *parse) override; + + private: + void *pyobj; + +}; + typedef std::unique_ptr mi_cmd_up; /* Lookup a command in the MI command table. */ @@ -188,4 +202,9 @@ extern int mi_debug_p; 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_cmd_up command); + #endif diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c new file mode 100644 index 0000000..b5d2967 --- /dev/null +++ b/gdb/python/py-micmd.c @@ -0,0 +1,293 @@ +/* gdb MI commands implemented in Python */ + +#include + +#include "defs.h" +#include "arch-utils.h" +#include "value.h" +#include "python-internal.h" +#include "charset.h" +#include "gdbcmd.h" +#include "cli/cli-decode.h" +#include "completer.h" +#include "language.h" +#include "mi/mi-cmds.h" + +struct micmdpy_object +{ + PyObject_HEAD +}; + +typedef struct micmdpy_object micmdpy_object; + +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, char *field_name) +{ + struct ui_out *uiout = current_uiout; + + if(!field_name) + field_name = "default"; + + if (PyString_Check(result)) + { + const char *string = gdbpy_obj_to_string (result).release (); + uiout->field_string (field_name, string); + xfree ( (void *)string); + } + else if (PyList_Check (result)) + { + PyObject *item; + Py_ssize_t i = 0; + struct cleanup *cleanups = make_cleanup_ui_out_list_begin_end (uiout, field_name); + for(i = 0; i < PyList_GET_SIZE (result); ++i) + { + struct cleanup *cleanup_item = make_cleanup_ui_out_tuple_begin_end (uiout, NULL); + item = PyList_GetItem (result, i); + parse_mi_result (item, NULL); + do_cleanups (cleanup_item); + } + do_cleanups (cleanups); + } + else if ( PyDict_Check (result)) + { + PyObject *key, *value; + Py_ssize_t pos = 0; + while ( PyDict_Next (result, &pos, &key, &value) ) + { + char *key_string = gdbpy_obj_to_string (key).release (); + parse_mi_result (value, key_string); + xfree ( (void *) key_string); + } + } +} + +/* Called from mi_command_py's invoke to invoke the command. */ + +void +py_mi_invoke (void *py_obj, char **argv, int argc) +{ + micmdpy_object *obj = (micmdpy_object *) py_obj; + PyObject *argobj, *result, **strings; + int i; + + gdbpy_enter enter_py (get_current_arch (), current_language); + + if (! obj) + error(_("Invalid invocation of Python micommand object.")); + + if (! PyObject_HasAttr ((PyObject *) obj, invoke_cst)) + { + error (_("Python command object missing 'invoke' method.")); + } + + strings = (PyObject **) malloc (sizeof(PyObject *) * argc); + argobj = PyList_New (argc); + if ( !argobj) + { + gdbpy_print_stack (); + error (_("Failed to create the python arguments list.")); + } + + for (i = 0; i < argc; ++i) + { + strings[i] = PyUnicode_Decode (argv[i], strlen(argv[i]), host_charset (), NULL); + if (PyList_SetItem (argobj, i, strings[i]) != 0) + { + error (_("Failed to create the python arguments list.")); + } + } + + result = PyObject_CallMethodObjArgs ((PyObject *) obj, invoke_cst, argobj, + NULL); + + if (result) + parse_mi_result (result, NULL); + + Py_DECREF (result); + Py_DECREF (argobj); + for (i = 0; i < argc; ++i) + { + Py_DECREF (strings[i]); + } + free (strings); +} + +/* Parse the name of the MI command to register. + + This function returns the xmalloc()d name of the new command. On error + sets the Python error and returns NULL. */ + +static char * +micmdpy_parse_command_name (const char *name) +{ + int len = strlen (name); + int i, lastchar; + char *result; + + /* Skip trailing whitespaces. */ + for (i = len - 1; i >= 0 && (name[i] == ' ' || name[i] == '\t'); --i) + ; + if (i < 0) + { + PyErr_SetString (PyExc_RuntimeError, _("No command name found.")); + return NULL; + } + lastchar = i; + + /* Skip preceding whitespaces. */ + /* Find first character of the final word. */ + for (; i > 0 && (isalnum (name[i - 1]) + || name[i - 1] == '-' + || name[i - 1] == '_'); + --i) + ; + /* Skip the first dash to have to command name only. + * i.e. -thread-info -> thread-info + */ + if(name[i] == '-' && i < len - 2) + i++; + + if( i == lastchar) + { + PyErr_SetString (PyExc_RuntimeError, _("No command name found.")); + return NULL; + } + + result = (char *) xmalloc (lastchar - i + 2); + memcpy(result, &name[i], lastchar - i + 1); + result[lastchar - i + 1] = '\0'; + + return result; +} + +/* Object initializer; sets up gdb-side structures for MI command. + + Use: __init__(NAME). + + NAME is the name of the MI command to register. It should starts with a dash + as a traditional MI command does. */ + +static int +micmdpy_init (PyObject *self, PyObject *args, PyObject *kw) +{ + micmdpy_object *obj = (micmdpy_object *) self; + static char *keywords[] = { "name", NULL }; + const char *name; + char *cmd_name; + struct mi_cmd *cmd = NULL; + + if(! PyArg_ParseTupleAndKeywords (args, kw, "s", + keywords, &name)) + return -1; + + cmd_name = micmdpy_parse_command_name (name); + if (! cmd_name) + return -1; + + Py_INCREF (self); + + TRY + { + mi_command *micommand = new mi_command_py (cmd_name, NULL, (void *) self); + + bool result = insert_mi_cmd_entry (mi_cmd_up (micommand)); + + if (! result) + { + PyErr_Format (PyExc_RuntimeError, + _("Unable to insert command. The name might already be in use.")); + return -1; + } + } + CATCH (except, RETURN_MASK_ALL) + { + xfree (cmd_name); + Py_DECREF (self); + PyErr_Format (except.reason == RETURN_QUIT + ? PyExc_KeyboardInterrupt : PyExc_RuntimeError, + "%s", except.message); + return -1; + } + END_CATCH + + xfree (cmd_name); + return 0; +} + +/* Initialize the MI command object. */ + +int +gdbpy_initialize_micommands(void) +{ + 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 0000000..98f5305 --- /dev/null +++ b/gdb/python/py-micmd.h @@ -0,0 +1,6 @@ +#ifndef PY_MICMDS_H +#define PY_MICMDS_H + +void py_mi_invoke (void *py_obj, char **argv, int argc); + +#endif diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index e2ebc1b..dafe273 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -504,6 +504,8 @@ int gdbpy_initialize_xmethods (void) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; int gdbpy_initialize_unwind (void) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; +int gdbpy_initialize_micommands (void) + CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; /* Called before entering the Python interpreter to install the current language and architecture to be used for Python values. diff --git a/gdb/python/python.c b/gdb/python/python.c index ab5a6a4..0995e4f 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1728,7 +1728,8 @@ message == an error message without a stack will be printed."), || gdbpy_initialize_clear_objfiles_event () < 0 || gdbpy_initialize_arch () < 0 || gdbpy_initialize_xmethods () < 0 - || gdbpy_initialize_unwind () < 0) + || gdbpy_initialize_unwind () < 0 + || gdbpy_initialize_micommands () < 0) goto fail; gdbpy_to_string_cst = PyString_FromString ("to_string");