From d8906c6f0e46480df2c8ad6aec13e57c9af084f9 Mon Sep 17 00:00:00 2001 From: Thiago Jung Bauermann Date: Fri, 6 Feb 2009 21:33:59 +0000 Subject: gdb/ 2009-02-06 Tom Tromey * Makefile.in (SUBDIR_PYTHON_OBS): Add python-cmd.o. (SUBDIR_PYTHON_SRCS): Add python-cmd.c. (python-cmd.o): New target. * cli/cli-decode.c (set_cmd_completer): Add self parameter to completer prototype. (add_cmd): Initialize destroyer member of cmd_list_element. Use make_symbol_completion_list_fn as completer. (delete_cmd): Call destroyer if one is set. * cli/cli-decode.h (cmd_list_element): Add cmd parameter to completer member. Add destroyer member. (set_cmd_completer): Add self parameter to completer prototype. * command.h (set_cmd_completer): Add cmd parameter to completer prototype. * completer.c (noop_completer, filename_completer, location_completer, expression_completer, command_completer): Adapt to new completer prototype. (complete_line_internal): Pass new parameter to completer function. * completer.h (noop_completer, filename_completer, location_completer, expression_completer, command_completer): Adapt prototypes to new completer prototype. * interps.c (interpreter_completer): Adapt to new completer prototype. * python/python-cmd.c: New file. * python/python-internal.h (gdbpy_initialize_commands): Add prototype. (gdbpy_doc_cst): Add forward declaration. * python/python.c (gdbpy_doc_cst): Declare. (_initialize_python): Call gdbpy_initialize_commands. Initialize gdbpy_doc_cst. * symtab.c (make_symbol_completion_list_fn): New function. * symtab.h (make_symbol_completion_list_fn): Add prototype. gdb/doc/ 2009-02-06 Tom Tromey * gdb.texinfo (Python API): Add entry for Commands In Python. (Commands In Python): New node. gdb/testsuite/ 2009-02-06 Thiago Jung Bauermann * gdb.python/python-cmd.exp: New file. --- gdb/python/python-cmd.c | 585 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/python-internal.h | 3 + gdb/python/python.c | 5 + 3 files changed, 593 insertions(+) create mode 100644 gdb/python/python-cmd.c (limited to 'gdb/python') diff --git a/gdb/python/python-cmd.c b/gdb/python/python-cmd.c new file mode 100644 index 0000000..36cde34 --- /dev/null +++ b/gdb/python/python-cmd.c @@ -0,0 +1,585 @@ +/* gdb commands implemented in Python + + Copyright (C) 2008, 2009 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 . */ + + +#include "defs.h" +#include "value.h" +#include "exceptions.h" +#include "python-internal.h" +#include "charset.h" +#include "gdbcmd.h" +#include "cli/cli-decode.h" +#include "completer.h" + +/* Struct representing built-in completion types. */ +struct cmdpy_completer +{ + /* Python symbol name. */ + char *name; + /* Completion function. */ + char **(*completer) (struct cmd_list_element *, char *, char *); +}; + +static struct cmdpy_completer completers[] = +{ + { "COMPLETE_NONE", noop_completer }, + { "COMPLETE_FILENAME", filename_completer }, + { "COMPLETE_LOCATION", location_completer }, + { "COMPLETE_COMMAND", command_completer }, + { "COMPLETE_SYMBOL", make_symbol_completion_list_fn }, +}; + +#define N_COMPLETERS (sizeof (completers) / sizeof (completers[0])) + +/* A gdb command. For the time being only ordinary commands (not + set/show commands) are allowed. */ +struct cmdpy_object +{ + PyObject_HEAD + + /* The corresponding gdb command object, or NULL if the command is + no longer installed. */ + struct cmd_list_element *command; + + /* A prefix command requires storage for a list of its sub-commands. + A pointer to this is passed to add_prefix_command, and to add_cmd + for sub-commands of that prefix. If this Command is not a prefix + command, then this field is unused. */ + struct cmd_list_element *sub_list; +}; + +typedef struct cmdpy_object cmdpy_object; + +static PyTypeObject cmdpy_object_type; + + +/* Constants used by this module. */ +static PyObject *invoke_cst; +static PyObject *complete_cst; + + + +/* Python function which wraps dont_repeat. */ +static PyObject * +cmdpy_dont_repeat (PyObject *self, PyObject *args) +{ + dont_repeat (); + Py_RETURN_NONE; +} + + + +/* Called if the gdb cmd_list_element is destroyed. */ +static void +cmdpy_destroyer (struct cmd_list_element *self, void *context) +{ + cmdpy_object *cmd; + PyGILState_STATE state; + + state = PyGILState_Ensure (); + + /* Release our hold on the command object. */ + cmd = (cmdpy_object *) context; + cmd->command = NULL; + Py_DECREF (cmd); + + /* We allocated the name, doc string, and perhaps the prefix + name. */ + xfree (self->name); + xfree (self->doc); + xfree (self->prefixname); + + PyGILState_Release (state); +} + +/* Called by gdb to invoke the command. */ +static void +cmdpy_function (struct cmd_list_element *command, char *args, int from_tty) +{ + cmdpy_object *obj = (cmdpy_object *) get_cmd_context (command); + PyObject *argobj, *ttyobj, *result; + struct cleanup *cleanup; + PyGILState_STATE state; + + state = PyGILState_Ensure (); + cleanup = make_cleanup_py_restore_gil (&state); + + if (! obj) + error (_("Invalid invocation of Python command object.")); + if (! PyObject_HasAttr ((PyObject *) obj, invoke_cst)) + { + if (obj->command->prefixname) + { + /* A prefix command does not need an invoke method. */ + do_cleanups (cleanup); + return; + } + error (_("Python command object missing 'invoke' method.")); + } + + if (! args) + args = ""; + argobj = PyUnicode_Decode (args, strlen (args), host_charset (), NULL); + if (! argobj) + error (_("Could not convert arguments to Python string.")); + + ttyobj = from_tty ? Py_True : Py_False; + Py_INCREF (ttyobj); + result = PyObject_CallMethodObjArgs ((PyObject *) obj, invoke_cst, argobj, + ttyobj, NULL); + Py_DECREF (argobj); + Py_DECREF (ttyobj); + if (! result) + { + PyObject *ptype, *pvalue, *ptraceback; + char *s, *str; + + PyErr_Fetch (&ptype, &pvalue, &ptraceback); + + if (pvalue && PyString_Check (pvalue)) + { + /* Make a temporary copy of the string data. */ + char *s = PyString_AsString (pvalue); + char *copy = alloca (strlen (s) + 1); + strcpy (copy, s); + + PyErr_Restore (ptype, pvalue, ptraceback); + gdbpy_print_stack (); + error (_("Error occurred in Python command: %s"), copy); + } + else + { + PyErr_Restore (ptype, pvalue, ptraceback); + gdbpy_print_stack (); + error (_("Error occurred in Python command.")); + } + } + Py_DECREF (result); + do_cleanups (cleanup); +} + +/* Called by gdb for command completion. */ +static char ** +cmdpy_completer (struct cmd_list_element *command, char *text, char *word) +{ + cmdpy_object *obj = (cmdpy_object *) get_cmd_context (command); + PyObject *textobj, *wordobj, *resultobj = NULL; + char **result = NULL; + struct cleanup *cleanup; + PyGILState_STATE state; + + state = PyGILState_Ensure (); + cleanup = make_cleanup_py_restore_gil (&state); + + if (! obj) + error (_("Invalid invocation of Python command object.")); + if (! PyObject_HasAttr ((PyObject *) obj, complete_cst)) + { + /* If there is no complete method, don't error -- instead, just + say that there are no completions. */ + goto done; + } + + textobj = PyUnicode_Decode (text, strlen (text), host_charset (), NULL); + if (! textobj) + error (_("Could not convert argument to Python string.")); + wordobj = PyUnicode_Decode (word, strlen (word), host_charset (), NULL); + if (! wordobj) + error (_("Could not convert argument to Python string.")); + + resultobj = PyObject_CallMethodObjArgs ((PyObject *) obj, complete_cst, + textobj, wordobj, NULL); + Py_DECREF (textobj); + Py_DECREF (wordobj); + if (! resultobj) + { + /* Just swallow errors here. */ + PyErr_Clear (); + goto done; + } + make_cleanup_py_decref (resultobj); + + result = NULL; + if (PySequence_Check (resultobj)) + { + Py_ssize_t i, len = PySequence_Size (resultobj); + Py_ssize_t out; + if (len < 0) + goto done; + + result = (char **) xmalloc ((len + 1) * sizeof (char *)); + for (i = out = 0; i < len; ++i) + { + int l; + PyObject *elt = PySequence_GetItem (resultobj, i); + if (elt == NULL || ! gdbpy_is_string (elt)) + { + /* Skip problem elements. */ + PyErr_Clear (); + continue; + } + result[out] = python_string_to_host_string (elt); + ++out; + } + result[out] = NULL; + } + else if (PyInt_Check (resultobj)) + { + /* User code may also return one of the completion constants, + thus requesting that sort of completion. */ + long value = PyInt_AsLong (resultobj); + if (value >= 0 && value < (long) N_COMPLETERS) + result = completers[value].completer (command, text, word); + } + + done: + + do_cleanups (cleanup); + + return result; +} + +/* Helper for cmdpy_init which locates the command list to use and + pulls out the command name. + + TEXT is the command name list. The final word in the list is the + name of the new command. All earlier words must be existing prefix + commands. + + *BASE_LIST is set to the final prefix command's list of + *sub-commands. + + This function returns the xmalloc()d name of the new command. On + error sets the Python error and returns NULL. */ +static char * +parse_command_name (char *text, struct cmd_list_element ***base_list) +{ + struct cmd_list_element *elt; + int len = strlen (text); + int i, lastchar; + char *prefix_text; + char *result; + + /* Skip trailing whitespace. */ + for (i = len - 1; i >= 0 && (text[i] == ' ' || text[i] == '\t'); --i) + ; + if (i < 0) + { + PyErr_SetString (PyExc_RuntimeError, _("no command name found")); + return NULL; + } + lastchar = i; + + /* Find first character of the final word. */ + for (; i > 0 && (isalnum (text[i - 1]) + || text[i - 1] == '-' + || text[i - 1] == '_'); + --i) + ; + result = xmalloc (lastchar - i + 2); + memcpy (result, &text[i], lastchar - i + 1); + result[lastchar - i + 1] = '\0'; + + /* Skip whitespace again. */ + for (--i; i >= 0 && (text[i] == ' ' || text[i] == '\t'); --i) + ; + if (i < 0) + { + *base_list = &cmdlist; + return result; + } + + prefix_text = xmalloc (i + 2); + memcpy (prefix_text, text, i + 1); + prefix_text[i + 1] = '\0'; + + text = prefix_text; + elt = lookup_cmd_1 (&text, cmdlist, NULL, 1); + if (!elt || elt == (struct cmd_list_element *) -1) + { + PyErr_Format (PyExc_RuntimeError, _("could not find command prefix %s"), + prefix_text); + xfree (prefix_text); + xfree (result); + return NULL; + } + + if (elt->prefixlist) + { + xfree (prefix_text); + *base_list = elt->prefixlist; + return result; + } + + PyErr_Format (PyExc_RuntimeError, _("'%s' is not a prefix command"), + prefix_text); + xfree (prefix_text); + xfree (result); + return NULL; +} + +/* Object initializer; sets up gdb-side structures for command. + + Use: __init__(NAME, CMDCLASS, [COMPLETERCLASS, [PREFIX]]). + + NAME is the name of the command. It may consist of multiple words, + in which case the final word is the name of the new command, and + earlier words must be prefix commands. + + CMDCLASS is the kind of command. It should be one of the COMMAND_* + constants defined in the gdb module. + + COMPLETERCLASS is the kind of completer. If not given, the + "complete" method will be used. Otherwise, it should be one of the + COMPLETE_* constants defined in the gdb module. + + If PREFIX is True, then this command is a prefix command. + + The documentation for the command is taken from the doc string for + the python class. + +*/ +static int +cmdpy_init (PyObject *self, PyObject *args, PyObject *kwds) +{ + cmdpy_object *obj = (cmdpy_object *) self; + char *name; + int cmdtype; + int completetype = -1; + char *docstring = NULL; + volatile struct gdb_exception except; + struct cmd_list_element **cmd_list; + char *cmd_name, *pfx_name; + PyObject *is_prefix = NULL; + int cmp; + + if (obj->command) + { + /* Note: this is apparently not documented in Python. We return + 0 for success, -1 for failure. */ + PyErr_Format (PyExc_RuntimeError, + _("command object already initialized")); + return -1; + } + + if (! PyArg_ParseTuple (args, "si|iO", &name, &cmdtype, + &completetype, &is_prefix)) + return -1; + + if (cmdtype != no_class && cmdtype != class_run + && cmdtype != class_vars && cmdtype != class_stack + && cmdtype != class_files && cmdtype != class_support + && cmdtype != class_info && cmdtype != class_breakpoint + && cmdtype != class_trace && cmdtype != class_obscure + && cmdtype != class_maintenance) + { + PyErr_Format (PyExc_RuntimeError, _("invalid command class argument")); + return -1; + } + + if (completetype < -1 || completetype >= (int) N_COMPLETERS) + { + PyErr_Format (PyExc_RuntimeError, _("invalid completion type argument")); + return -1; + } + + cmd_name = parse_command_name (name, &cmd_list); + if (! cmd_name) + return -1; + + pfx_name = NULL; + if (is_prefix != NULL) + { + cmp = PyObject_IsTrue (is_prefix); + if (cmp == 1) + { + int i, out; + + /* Make a normalized form of the command name. */ + pfx_name = xmalloc (strlen (name) + 2); + + i = 0; + out = 0; + while (name[i]) + { + /* Skip whitespace. */ + while (name[i] == ' ' || name[i] == '\t') + ++i; + /* Copy non-whitespace characters. */ + while (name[i] && name[i] != ' ' && name[i] != '\t') + pfx_name[out++] = name[i++]; + /* Add a single space after each word -- including the final + word. */ + pfx_name[out++] = ' '; + } + pfx_name[out] = '\0'; + } + else if (cmp < 0) + return -1; + } + if (PyObject_HasAttr (self, gdbpy_doc_cst)) + { + PyObject *ds_obj = PyObject_GetAttr (self, gdbpy_doc_cst); + if (ds_obj && gdbpy_is_string (ds_obj)) + docstring = python_string_to_host_string (ds_obj); + } + if (! docstring) + docstring = xstrdup (_("This command is not documented.")); + + Py_INCREF (self); + + TRY_CATCH (except, RETURN_MASK_ALL) + { + struct cmd_list_element *cmd; + + if (pfx_name) + { + int allow_unknown; + + /* If we have our own "invoke" method, then allow unknown + sub-commands. */ + allow_unknown = PyObject_HasAttr (self, invoke_cst); + cmd = add_prefix_cmd (cmd_name, (enum command_class) cmdtype, + NULL, docstring, &obj->sub_list, + pfx_name, allow_unknown, cmd_list); + } + else + cmd = add_cmd (cmd_name, (enum command_class) cmdtype, NULL, + docstring, cmd_list); + + /* There appears to be no API to set this. */ + cmd->func = cmdpy_function; + cmd->destroyer = cmdpy_destroyer; + + obj->command = cmd; + set_cmd_context (cmd, self); + set_cmd_completer (cmd, ((completetype == -1) ? cmdpy_completer + : completers[completetype].completer)); + } + if (except.reason < 0) + { + xfree (cmd_name); + xfree (docstring); + xfree (pfx_name); + Py_DECREF (self); + PyErr_Format (except.reason == RETURN_QUIT + ? PyExc_KeyboardInterrupt : PyExc_RuntimeError, + "%s", except.message); + return -1; + } + return 0; +} + + + +/* Initialize the 'commands' code. */ +void +gdbpy_initialize_commands (void) +{ + int i; + + if (PyType_Ready (&cmdpy_object_type) < 0) + return; + + /* Note: alias and user are special; pseudo appears to be unused, + and there is no reason to expose tui or xdb, I think. */ + if (PyModule_AddIntConstant (gdb_module, "COMMAND_NONE", no_class) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_RUNNING", class_run) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_DATA", class_vars) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_STACK", class_stack) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_FILES", class_files) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_SUPPORT", + class_support) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_STATUS", class_info) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_BREAKPOINTS", + class_breakpoint) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_TRACEPOINTS", + class_trace) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_OBSCURE", + class_obscure) < 0 + || PyModule_AddIntConstant (gdb_module, "COMMAND_MAINTENANCE", + class_maintenance) < 0) + return; + + for (i = 0; i < N_COMPLETERS; ++i) + { + if (PyModule_AddIntConstant (gdb_module, completers[i].name, i) < 0) + return; + } + + Py_INCREF (&cmdpy_object_type); + PyModule_AddObject (gdb_module, "Command", + (PyObject *) &cmdpy_object_type); + + invoke_cst = PyString_FromString ("invoke"); + complete_cst = PyString_FromString ("complete"); +} + + + +static PyMethodDef cmdpy_object_methods[] = +{ + { "dont_repeat", cmdpy_dont_repeat, METH_NOARGS, + "Prevent command repetition when user enters empty line." }, + + { 0 } +}; + +static PyTypeObject cmdpy_object_type = +{ + PyObject_HEAD_INIT (NULL) + 0, /*ob_size*/ + "gdb.Command", /*tp_name*/ + sizeof (cmdpy_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 command object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + cmdpy_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 */ + cmdpy_init, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew /* tp_new */ +}; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 1457928..02dbfc4 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -70,6 +70,7 @@ PyObject *value_to_value_object (struct value *v); struct value *convert_value_from_python (PyObject *obj); void gdbpy_initialize_values (void); +void gdbpy_initialize_commands (void); struct cleanup *make_cleanup_py_decref (PyObject *py); struct cleanup *make_cleanup_py_restore_gil (PyGILState_STATE *state); @@ -94,4 +95,6 @@ char *python_string_to_host_string (PyObject *obj); PyObject *target_string_to_unicode (const gdb_byte *str, int length); int gdbpy_is_string (PyObject *obj); +extern PyObject *gdbpy_doc_cst; + #endif /* GDB_PYTHON_INTERNAL_H */ diff --git a/gdb/python/python.c b/gdb/python/python.c index 96bb5f5..4f97416 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -46,6 +46,8 @@ static PyMethodDef GdbMethods[]; PyObject *gdb_module; +PyObject *gdbpy_doc_cst; + /* Given a command_line, return a command string suitable for passing to Python. Lines in the string are separated by newlines. The return value is allocated using xmalloc and the caller is @@ -407,9 +409,12 @@ Enables or disables printing of Python stack traces."), PyModule_AddStringConstant (gdb_module, "TARGET_CONFIG", (char*) target_name); gdbpy_initialize_values (); + gdbpy_initialize_commands (); PyRun_SimpleString ("import gdb"); + gdbpy_doc_cst = PyString_FromString ("__doc__"); + /* Create a couple objects which are used for Python's stdout and stderr. */ PyRun_SimpleString ("\ -- cgit v1.1