From patchwork Mon Jan 28 12:41:00 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [v2, 1/2] MI: extract command completion logic from complete_command() X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 14745 Message-Id: <20190128124101.26243-2-jan.vrany@fit.cvut.cz> To: gdb-patches@sourceware.org Cc: Jan Vrany Date: Mon, 28 Jan 2019 12:41:00 +0000 From: Jan Vrany List-Id: Extract completion logic from CLI complete_command() into a new helper function complete(). gdb/Changelog: * completer.h (complete): New function. * completer.c (complete): Likewise. * cli/cli-cmds.c: (complete_command): Update to use new complete() function defined in completer.h --- gdb/ChangeLog | 7 +++++++ gdb/cli/cli-cmds.c | 32 ++------------------------------ gdb/completer.c | 34 ++++++++++++++++++++++++++++++++++ gdb/completer.h | 8 ++++++++ 4 files changed, 51 insertions(+), 30 deletions(-) -- 2.20.1 diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 8e03dbf883..17e29a7807 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,10 @@ +2019-01-24 Jan Vrany + + * completer.h (complete): New function. + * completer.c (complete): Likewise. + * cli/cli-cmds.c: (complete_command): Update to use new complete() + function defined in completer.h + 2019-01-22 Philippe Waroquiers * event-top.c (handle_line_of_input): use unique_xmalloc_ptr for diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c index 57cfad441c..f2c2311e68 100644 --- a/gdb/cli/cli-cmds.c +++ b/gdb/cli/cli-cmds.c @@ -243,41 +243,13 @@ complete_command (const char *arg, int from_tty) if (arg == NULL) arg = ""; - completion_tracker tracker_handle_brkchars; - completion_tracker tracker_handle_completions; - completion_tracker *tracker; - int quote_char = '\0'; const char *word; - - TRY - { - word = completion_find_completion_word (tracker_handle_brkchars, - arg, "e_char); - - /* Completers that provide a custom word point in the - handle_brkchars phase also compute their completions then. - Completers that leave the completion word handling to readline - must be called twice. */ - if (tracker_handle_brkchars.use_custom_word_point ()) - tracker = &tracker_handle_brkchars; - else - { - complete_line (tracker_handle_completions, word, arg, strlen (arg)); - tracker = &tracker_handle_completions; - } - } - CATCH (ex, RETURN_MASK_ALL) - { - return; - } - END_CATCH + + completion_result result = complete (arg, &word, "e_char); std::string arg_prefix (arg, word - arg); - completion_result result - = tracker->build_completion_result (word, word - arg, strlen (arg)); - if (result.number_matches != 0) { if (result.number_matches == 1) diff --git a/gdb/completer.c b/gdb/completer.c index fed815a53c..2f94932184 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -1615,6 +1615,40 @@ make_completion_match_str (gdb::unique_xmalloc_ptr &&match_name, return gdb::unique_xmalloc_ptr (newobj); } +completion_result +complete (const char *line, char const **word, int *quote_char) +{ + completion_tracker tracker_handle_brkchars; + completion_tracker tracker_handle_completions; + completion_tracker *tracker; + + TRY + { + *word = completion_find_completion_word (tracker_handle_brkchars, + line, quote_char); + + /* Completers that provide a custom word point in the + handle_brkchars phase also compute their completions then. + Completers that leave the completion word handling to readline + must be called twice. */ + if (tracker_handle_brkchars.use_custom_word_point ()) + tracker = &tracker_handle_brkchars; + else + { + complete_line (tracker_handle_completions, *word, line, strlen (line)); + tracker = &tracker_handle_completions; + } + } + CATCH (ex, RETURN_MASK_ALL) + { + return {}; + } + END_CATCH + + return tracker->build_completion_result (*word, *word - line, strlen (line)); +} + + /* Generate completions all at once. Does nothing if max_completions is 0. If max_completions is non-negative, this will collect at most max_completions strings. diff --git a/gdb/completer.h b/gdb/completer.h index e81243a721..bfbead235b 100644 --- a/gdb/completer.h +++ b/gdb/completer.h @@ -510,6 +510,14 @@ extern void complete_line (completion_tracker &tracker, const char *line_buffer, int point); +/* Complete LINE and return completion results. For completion purposes, + cursor position is assumed to be at the end of LINE. WORD is set to + the end of word to complete. QUOTE_CHAR is set to the opening quote + character if we found an unclosed quoted substring, '\0' otherwise. + */ +extern completion_result + complete (const char *line, char const **word, int *quote_char); + /* Find the bounds of the word in TEXT for completion purposes, and return a pointer to the end of the word. Calls the completion machinery for a handle_brkchars phase (using TRACKER) to figure out From patchwork Mon Jan 28 12:41:01 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [v2,2/2] MI: Add new command -complete X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 14746 Message-Id: <20190128124101.26243-3-jan.vrany@fit.cvut.cz> To: gdb-patches@sourceware.org Cc: Jan Vrany Date: Mon, 28 Jan 2019 12:41:01 +0000 From: Jan Vrany List-Id: There is a CLI command 'complete' intended to use with emacs. Such a command would also be useful for MI frontends, when separate CLI and MI channels cannot be used. For example, on Windows (because of lack of PTYs) or when GDB is used through SSH session. This commit adds a new '-complete' MI command. gdb/Changelog: 2019-01-28 Jan Vrany * mi/mi-cmds.h (mi_cmd_complete): New function. * mi/mi-main.c (mi_cmd_complete): Likewise. * mi/mi-cmds.c: Define new MI command -complete. * NEWS: Mention new -complete command. gdb/doc/ChangeLog: 2019-01-28 Jan Vrany * gdb.texinfo (Miscellaneous GDB/MI Commands): Document new MI command -complete. gdb/testsuite/ChangeLog: 2019-01-28 Jan Vrany * gdb.mi/mi-complete.exp: New file. --- gdb/ChangeLog | 7 +++ gdb/NEWS | 7 +++ gdb/doc/ChangeLog | 5 ++ gdb/doc/gdb.texinfo | 31 ++++++++++++ gdb/mi/mi-cmds.c | 2 + gdb/mi/mi-cmds.h | 1 + gdb/mi/mi-main.c | 44 +++++++++++++++++ gdb/testsuite/ChangeLog | 4 ++ gdb/testsuite/gdb.mi/mi-complete.exp | 71 ++++++++++++++++++++++++++++ 9 files changed, 172 insertions(+) create mode 100644 gdb/testsuite/gdb.mi/mi-complete.exp -- 2.20.1 diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 17e29a7807..51e62a7d30 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,10 @@ +2019-01-28 Jan Vrany + + * mi/mi-cmds.h (mi_cmd_complete): New function. + * mi/mi-main.c (mi_cmd_complete): Likewise. + * mi/mi-cmds.c: Define new MI command -complete. + * NEWS: Mention new -complete command. + 2019-01-24 Jan Vrany * completer.h (complete): New function. diff --git a/gdb/NEWS b/gdb/NEWS index eaef6aa384..3018313a46 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -96,6 +96,13 @@ maint show dwarf unwinders info proc files Display a list of open files for a process. +* New MI commands + +-complete + This lists all the possible completions for the rest of the line, if it + were to be given as a command itself. This is intended for use by MI frontends + in cases when separate CLI and MI channels cannot be used. + * Changed commands Changes to the "frame", "select-frame", and "info frame" CLI commands. diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index d38d4625c4..669f7e8ce6 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,8 @@ +2019-01-28 Jan Vrany + + * gdb.texinfo (Miscellaneous GDB/MI Commands): Document new + MI command -complete. + 2019-01-16 Simon Marchi * gdb.texinfo (GDB/MI Development and Front Ends): Add table of diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 173d18be6f..d7ef71367e 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -34317,6 +34317,37 @@ fullname="/home/nickrob/myprog.c",line="73",arch="i386:x86_64"@} (gdb) @end smallexample +@subheading The @code{-complete} Command +@findex -complete + +@subheading Synopsis + +@smallexample +-complete @var{command} +@end smallexample + +Show a list of completions for partially typed CLI @var{command}. + +This command is intended for @sc{gdb/mi} frontends that cannot use two separate +CLI and MI channels - for example: because of lack of PTYs like on Windows or +because @sc{gdb} is used remotely via a SSH connection. + +@subheading @value{GDBN} Command + +The corresponding @value{GDBN} command is @samp{complete}. + +@subheading Example + +@smallexample +(gdb) +-complete br +^done,completions=["break","break-range"],max_completions_reached="0" +(gdb) +-complete "b madv" +^done,completions=["b madvise"],max_completions_reached="0" +(gdb) +@end smallexample + @node Annotations @chapter @value{GDBN} Annotations diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index bb7c20c777..683e990cdf 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" + struct mi_cmd; static struct mi_cmd **lookup_table (const char *command); static void build_table (struct mi_cmd *commands); @@ -75,6 +76,7 @@ static struct mi_cmd mi_cmds[] = &mi_suppress_notification.breakpoint), DEF_MI_CMD_MI_1 ("catch-unload", mi_cmd_catch_unload, &mi_suppress_notification.breakpoint), + DEF_MI_CMD_MI ("complete", mi_cmd_complete), DEF_MI_CMD_MI ("data-disassemble", mi_cmd_disassemble), DEF_MI_CMD_MI ("data-evaluate-expression", mi_cmd_data_evaluate_expression), DEF_MI_CMD_MI ("data-list-changed-registers", diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index af38363cfc..32bd747d43 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -125,6 +125,7 @@ extern mi_cmd_argv_ftype mi_cmd_var_update; extern mi_cmd_argv_ftype mi_cmd_enable_pretty_printing; extern mi_cmd_argv_ftype mi_cmd_enable_frame_filters; extern mi_cmd_argv_ftype mi_cmd_var_set_update_range; +extern mi_cmd_argv_ftype mi_cmd_complete; /* Description of a single command. */ diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index 7176963845..b2ff8df8de 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -2709,6 +2709,50 @@ mi_cmd_trace_frame_collected (const char *command, char **argv, int argc) } } +/* Implement the "-complete" command. */ +void +mi_cmd_complete (const char *command, char **argv, int argc) +{ + if (argc != 1) + { + error (_("Usage: -complete COMMAND")); + } + if (max_completions == 0) + { + error (_("max-completions is zero," + " completion is disabled.\n")); + } + + int quote_char = '\0'; + const char *word; + + completion_result result = complete (argv[0], &word, "e_char); + + std::string arg_prefix (argv[0], word - argv[0]); + + struct ui_out *uiout = current_uiout; + { + ui_out_emit_list completions_emitter (uiout, "completions"); + + if (result.number_matches == 1) + { + uiout->field_fmt(NULL, "%s%s", arg_prefix.c_str (), result.match_list[0]); + } + else + { + result.sort_match_list (); + for (size_t i = 0; i < result.number_matches; i++) + { + uiout->field_fmt(NULL, "%s%s", arg_prefix.c_str (), + result.match_list[i + 1]); + } + } + } + uiout->field_string ("max_completions_reached", + result.number_matches == max_completions ? "1" : "0"); +} + + void _initialize_mi_main (void) { diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 7d8c7908fe..f395de64a8 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2019-01-28 Jan Vrany + + * gdb.mi/mi-complete.exp: New file. + 2019-01-21 Alan Hayward * gdb.base/infcall-nested-structs.exp: Test C++ in addition to C. diff --git a/gdb/testsuite/gdb.mi/mi-complete.exp b/gdb/testsuite/gdb.mi/mi-complete.exp new file mode 100644 index 0000000000..2963bd2ac5 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-complete.exp @@ -0,0 +1,71 @@ +# Copyright 2018 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 . + +# Verify -data-evaluate-expression. There are really minimal tests. + +# The goal is not to test gdb functionality, which is done by other tests, +# but to verify the correct output response to MI operations. +# + +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +gdb_exit +if [mi_gdb_start] { + continue +} + +standard_testfile basics.c + +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } { + untested "failed to compile" + return -1 +} + +mi_run_to_main + +mi_gdb_test "1-complete br" \ + "1\\^done,completions=\\\[.*\"break\",.*\"break-range\".*\\\],max_completions_reached=\"0\"" \ + "-complete br" + +# Check empty completion list +mi_gdb_test "5-complete bogus" \ + "5\\^done,completions=\\\[\\\],max_completions_reached=\"0\"" \ + "-complete bogus" + + +# Check completions for commands with space +mi_gdb_test "4-complete \"b mai\"" \ + "4\\^done,completions=\\\[.*\"b main\".*\\\],max_completions_reached=\"0\"" \ + "-complete \"b mai\"" + +mi_gdb_test "-info-gdb-mi-command complete" \ + "\\^done,command=\{exists=\"true\"\}" \ + "-info-gdb-mi-command complete" + +# Limit max completions and check that max_completions_reached=\"0\" is set +# to 1. +send_gdb "set max-completions 1\n" + +mi_gdb_test "2-complete br" \ + ".*2\\^done,completions=\\\[\"br\[A-Za-z0-9-\]+\"\\\],max_completions_reached=\"1\"" \ + "-complete br (max-completions 1)" + +# Disable completions and check an error is returned +send_gdb "set max-completions 0\n" + +mi_gdb_test "3-complete br" \ + ".*3\\^error,msg=\".*" \ + "-complete br (max-completions 0)" From patchwork Thu May 30 13:48:48 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [v3,3/5] Create MI commands using python. X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 19120 Message-Id: <20190530134850.3236-4-jan.vrany@fit.cvut.cz> To: gdb-patches@sourceware.org Cc: Didier Nadeau Date: Thu, 30 May 2019 14:48:48 +0100 From: Jan Vrany List-Id: From: Didier Nadeau 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 * Makefile.in (SUBDIR_PYTHON_SRCS): Add py-micmd.c. * mi/mi-cmds.c (insert_mi_cmd_entry): Remove static. (mi_cmd_table): Remove static. * mi/mi-cmds.h (insert_mi_cmd_entry): New declaration. (mi_cmd_table): New declaration. * python/py-micmd.c: New file (parse_mi_result): New function. (micmdpy_init): New function. (gdbpy_initialize_micommands): New function. (mi_command_py): New class. * python/py-micmd.h: New file (micmdpy_object): New struct. (micmdpy_object): New typedef. (mi_command_py): New class. * python/python-internal.h (gdbpy_initialize_micommands): New declaration. * python/python.c (_initialize_python): New call to gdbpy_initialize_micommands. (finalize_python): Finalize Python MI commands. --- gdb/ChangeLog | 23 +++ gdb/Makefile.in | 1 + gdb/mi/mi-cmds.c | 7 +- gdb/mi/mi-cmds.h | 9 ++ gdb/python/py-micmd.c | 297 +++++++++++++++++++++++++++++++++++ gdb/python/py-micmd.h | 60 +++++++ gdb/python/python-internal.h | 2 + gdb/python/python.c | 13 +- 8 files changed, 407 insertions(+), 5 deletions(-) create mode 100644 gdb/python/py-micmd.c create mode 100644 gdb/python/py-micmd.h -- 2.20.1 diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 994ad947bd..241e5da68f 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,26 @@ +2019-05-02 Didier Nadeau + Jan Vrany + + * Makefile.in (SUBDIR_PYTHON_SRCS): Add py-micmd.c. + * mi/mi-cmds.c (insert_mi_cmd_entry): Remove static. + (mi_cmd_table): Remove static. + * mi/mi-cmds.h (insert_mi_cmd_entry): New declaration. + (mi_cmd_table): New declaration. + * python/py-micmd.c: New file + (parse_mi_result): New function. + (micmdpy_init): New function. + (gdbpy_initialize_micommands): New function. + (mi_command_py): New class. + * python/py-micmd.h: New file + (micmdpy_object): New struct. + (micmdpy_object): New typedef. + (mi_command_py): New class. + * python/python-internal.h + (gdbpy_initialize_micommands): New declaration. + * python/python.c + (_initialize_python): New call to gdbpy_initialize_micommands. + (finalize_python): Finalize Python MI commands. + 2019-05-02 Didier Nadeau Jan Vrany diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 0f49578360..28866962bc 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -382,6 +382,7 @@ SUBDIR_PYTHON_SRCS = \ python/py-instruction.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 2d8863c5f9..0aead954f9 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -28,12 +28,11 @@ /* MI command table (built at run time). */ -static std::map mi_cmd_table; +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); diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index f1b4728bde..5ca7232fca 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -22,6 +22,8 @@ #ifndef MI_MI_CMDS_H #define MI_MI_CMDS_H +#include + enum print_values { PRINT_NO_VALUES, PRINT_ALL_VALUES, @@ -191,9 +193,16 @@ typedef std::unique_ptr mi_cmd_up; extern mi_command *mi_cmd_lookup (const char *command); +extern std::map mi_cmd_table; + /* Debug flag */ 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 /* MI_MI_CMDS_H */ diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c new file mode 100644 index 0000000000..e99632c97a --- /dev/null +++ b/gdb/python/py-micmd.c @@ -0,0 +1,297 @@ +/* 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 . */ + +/* 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 + +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 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 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_cmd_up micommand = mi_cmd_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 (name, NULL), pyobj (object) +{ +} + +void +mi_command_py::do_invoke (struct mi_parse *parse) +{ + 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 ().c_str ()); + + + gdbpy_ref<> argobj (PyList_New (parse->argc)); + if (argobj == nullptr) + { + gdbpy_print_stack (); + error (_("-%s: failed to create the Python arguments list."), + name ().c_str ()); + } + + 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 ().c_str ()); + } + } + + 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 ex_msg (ex.to_string ()); + + if (ex_msg == NULL || *ex_msg == '\0') + error (_("-%s: failed to execute command"), name ().c_str ()); + else + error (_("-%s: %s"), name ().c_str (), 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 0000000000..deadc0116b --- /dev/null +++ b/gdb/python/py-micmd.h @@ -0,0 +1,60 @@ +/* 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 . */ + +#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 (); + + protected: + virtual void do_invoke(struct mi_parse *parse) override; + + private: + gdbpy_ref<> pyobj; +}; + +#endif diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 69ff1fe30d..f28027741e 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -539,6 +539,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; /* 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 4dad8ec10d..901c0a4edc 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -36,6 +36,8 @@ #include #include "location.h" #include "ser-event.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"; @@ -1564,6 +1566,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 (cmd); + if (cmd_py != nullptr) + cmd_py->finalize (); + } + Py_Finalize (); restore_active_ext_lang (previous_active); @@ -1698,7 +1708,8 @@ do_start_initialization () || gdbpy_initialize_event () < 0 || gdbpy_initialize_arch () < 0 || gdbpy_initialize_xmethods () < 0 - || gdbpy_initialize_unwind () < 0) + || gdbpy_initialize_unwind () < 0 + || gdbpy_initialize_micommands () < 0) return false; #define GDB_PY_DEFINE_EVENT_TYPE(name, py_name, doc, base) \ From patchwork Thu May 30 13:48:49 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [v3,4/5] mi/python: Allow redefinition of python MI commands X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 19119 Message-Id: <20190530134850.3236-5-jan.vrany@fit.cvut.cz> To: gdb-patches@sourceware.org Cc: Jan Vrany Date: Thu, 30 May 2019 14:48:49 +0100 From: Jan Vrany List-Id: Redefining python MI commands is especially useful when developing them. gdb/Changelog: * mi/mi-cmds.h (mi_command::can_be_redefined): New method. * mi/mi-cmds.c: (mi_command::can_be_redefined): New method. (insert_mi_cmd_entry): Allow redefinition of python-defined MI commands. * python/py-micmd.h (mi_command_py::can_be_redefined): New method. * python/py-micmd.c: (mi_command_py::can_be_redefined): New method. --- gdb/ChangeLog | 8 ++++++++ gdb/mi/mi-cmds.c | 10 +++++++++- gdb/mi/mi-cmds.h | 3 +++ gdb/python/py-micmd.c | 24 +++++++++++++++++------- gdb/python/py-micmd.h | 2 +- 5 files changed, 38 insertions(+), 9 deletions(-) -- 2.20.1 diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 241e5da68f..1d264c44f7 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,11 @@ +2019-05-02 Jan Vrany + + * mi/mi-cmds.h (mi_command::can_be_redefined): New method. + * mi/mi-cmds.c: (mi_command::can_be_redefined): New method. + (insert_mi_cmd_entry): Allow redefinition of python-defined MI commands. + * python/py-micmd.h (mi_command_py::can_be_redefined): New method. + * python/py-micmd.c: (mi_command_py::can_be_redefined): New method. + 2019-05-02 Didier Nadeau Jan Vrany diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index 0aead954f9..e280e51daf 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -41,7 +41,8 @@ insert_mi_cmd_entry (mi_cmd_up command) const std::string &name = command->name (); if (mi_cmd_table.find (name) != mi_cmd_table.end ()) - return false; + if (! mi_cmd_table[name]->can_be_redefined ()) + return false; mi_cmd_table[name] = std::move (command); @@ -80,6 +81,13 @@ mi_command::mi_command (const char *name, int *suppress_notification) m_suppress_notification (suppress_notification) {} +bool +mi_command::can_be_redefined() +{ + return false; +} + + void mi_command::invoke (struct mi_parse *parse) diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index 5ca7232fca..331b1349df 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -143,6 +143,9 @@ public: /* Execute the MI command. */ void invoke (struct mi_parse *parse); + /* Return TRUE if command can be redefined, FALSE otherwise. */ + virtual bool can_be_redefined(); + protected: gdb::optional> do_suppress_notification (); virtual void do_invoke(struct mi_parse *parse) = 0; diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index e99632c97a..4f2b13f3c7 100644 --- a/gdb/python/py-micmd.c +++ b/gdb/python/py-micmd.c @@ -171,6 +171,12 @@ mi_command_py::mi_command_py (const char *name, gdbpy_ref<> object) { } +bool +mi_command_py::can_be_redefined() +{ + return true; +} + void mi_command_py::do_invoke (struct mi_parse *parse) { @@ -179,6 +185,7 @@ mi_command_py::do_invoke (struct mi_parse *parse) if (parse->argv == NULL) error (_("Problem parsing arguments: %s %s"), parse->command, parse->args); + std::string name (this->name ()); PyObject *obj = this->pyobj.get (); gdbpy_enter enter_py (get_current_arch (), current_language); @@ -187,15 +194,14 @@ mi_command_py::do_invoke (struct mi_parse *parse) if (!PyObject_HasAttr (obj, invoke_cst)) error (_("-%s: Python command object missing 'invoke' method."), - name ().c_str ()); - + name.c_str ()); gdbpy_ref<> argobj (PyList_New (parse->argc)); if (argobj == nullptr) { gdbpy_print_stack (); error (_("-%s: failed to create the Python arguments list."), - name ().c_str ()); + name.c_str ()); } for (int i = 0; i < parse->argc; ++i) @@ -205,11 +211,16 @@ mi_command_py::do_invoke (struct mi_parse *parse) if (PyList_SetItem (argobj.get (), i, str.release ()) != 0) { error (_("-%s: failed to create the Python arguments list."), - name ().c_str ()); + name.c_str ()); } } gdb_assert (PyErr_Occurred () == NULL); + + /* From this point on, THIS must not be used since Python code may replace + the the very same command that is currently executing. This in turn leads + to desctruction of THIS making it invalid. See insert_mi_cmd_entry. */ + gdbpy_ref<> result ( PyObject_CallMethodObjArgs (obj, invoke_cst, argobj.get (), NULL)); if (PyErr_Occurred () != NULL) @@ -218,9 +229,9 @@ mi_command_py::do_invoke (struct mi_parse *parse) gdb::unique_xmalloc_ptr ex_msg (ex.to_string ()); if (ex_msg == NULL || *ex_msg == '\0') - error (_("-%s: failed to execute command"), name ().c_str ()); + error (_("-%s: failed to execute command"), name.c_str ()); else - error (_("-%s: %s"), name ().c_str (), ex_msg.get ()); + error (_("-%s: %s"), name.c_str (), ex_msg.get ()); } else { @@ -233,7 +244,6 @@ void mi_command_py::finalize () { this->pyobj.reset (nullptr); } - /* Initialize the MI command object. */ int diff --git a/gdb/python/py-micmd.h b/gdb/python/py-micmd.h index deadc0116b..0d61069ca5 100644 --- a/gdb/python/py-micmd.h +++ b/gdb/python/py-micmd.h @@ -44,7 +44,7 @@ class mi_command_py : public mi_command mi_command_py (const char *name, gdbpy_ref<> object); - + bool can_be_redefined() override; /* This is called just before shutting down a Python interpreter to release python object implementing the command. */ From patchwork Thu May 30 13:48:50 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [v3,5/5] mi/python: Add tests for python-defined MI commands X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 19122 Message-Id: <20190530134850.3236-6-jan.vrany@fit.cvut.cz> To: gdb-patches@sourceware.org Cc: Jan Vrany Date: Thu, 30 May 2019 14:48:50 +0100 From: Jan Vrany List-Id: gdb/testsuite/Changelog: * gdb.python/py-mi-cmd.exp: New file. * gdb.python/py-mi-cmd.py: New file. --- gdb/testsuite/ChangeLog | 5 + gdb/testsuite/gdb.python/py-mi-cmd.exp | 132 +++++++++++++++++++++++++ gdb/testsuite/gdb.python/py-mi-cmd.py | 54 ++++++++++ 3 files changed, 191 insertions(+) create mode 100644 gdb/testsuite/gdb.python/py-mi-cmd.exp create mode 100644 gdb/testsuite/gdb.python/py-mi-cmd.py -- 2.20.1 diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index ba657cc615..26d14996f4 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2019-05-03 Jan Vrany + + * gdb.python/py-mi-cmd.exp: New file. + * gdb.python/py-mi-cmd.py: New file. + 2019-05-24 Tom de Vries * gdb.dwarf2/gdb-add-index.exp: New file. diff --git a/gdb/testsuite/gdb.python/py-mi-cmd.exp b/gdb/testsuite/gdb.python/py-mi-cmd.exp new file mode 100644 index 0000000000..ba8670d5ea --- /dev/null +++ b/gdb/testsuite/gdb.python/py-mi-cmd.exp @@ -0,0 +1,132 @@ +# Copyright (C) 2018 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 . + +# Test custom MI commands implemented in Python. + +load_lib gdb-python.exp +load_lib mi-support.exp +set MIFLAGS "-i=mi2" + +gdb_exit +if {[mi_gdb_start]} { + continue +} + +if {[lsearch -exact [mi_get_features] python] < 0} { + unsupported "python support is disabled" + return -1 +} + +standard_testfile +# +# Start here +# + + +mi_gdb_test "set python print-stack full" \ + ".*\\^done" \ + "set python print-stack full" + +mi_gdb_test "source ${srcdir}/${subdir}/${testfile}.py" \ + ".*\\^done" \ + "load python file" + +mi_gdb_test "python pycmd1('-pycmd')" \ + ".*\\^done" \ + "Define -pycmd MI command" + + +mi_gdb_test "-pycmd int" \ + "\\^done,result=\"42\"" \ + "-pycmd int" + +mi_gdb_test "-pycmd str" \ + "\\^done,result=\"Hello world!\"" \ + "-pycmd str" + +mi_gdb_test "-pycmd ary" \ + "\\^done,result=\\\[\"Hello\",\"42\"\\\]" \ + "-pycmd ary" + +mi_gdb_test "-pycmd dct" \ + "\\^done,result={hello=\"world\",times=\"42\"}" \ + "-pycmd dct" + +mi_gdb_test "-pycmd bk1" \ + "\\^error,msg=\"Non-string object used as key: Bad Kay.\"" \ + "-pycmd bk1" + +mi_gdb_test "-pycmd bk2" \ + "\\^error,msg=\"Non-string object used as key: 1.\"" \ + "-pycmd bk2" + +mi_gdb_test "-pycmd bk3" \ + "\\^error,msg=\"Non-string object used as key: __repr__ returned non-string .*" \ + "-pycmd bk3" + +mi_gdb_test "-pycmd tpl" \ + "\\^done,result=\\\[\"42\",\"Hello\"\\\]" \ + "-pycmd tpl" + +mi_gdb_test "-pycmd itr" \ + "\\^done,result=\\\[\"1\",\"2\",\"3\"\\\]" \ + "-pycmd itr" + +mi_gdb_test "-pycmd nn1" \ + "\\^done" \ + "-pycmd nn1" + +mi_gdb_test "-pycmd nn2" \ + "\\^done,result=\\\[\"None\"\\\]" \ + "-pycmd nn2" + +mi_gdb_test "-pycmd bogus" \ + "\\^error,msg=\"-pycmd: Invalid parameter: bogus\"" \ + "-pycmd bogus" + +mi_gdb_test "-pycmd exp" \ + "\\^error,msg=\"-pycmd: failed to execute command\"" \ + "-pycmd exp" + +mi_gdb_test "python pycmd2('-pycmd')" \ + ".*\\^done" \ + "Redefine -pycmd MI command from CLI command" + +mi_gdb_test "-pycmd str" \ + "\\^done,result=\"Ciao!\"" \ + "-pycmd str" + +mi_gdb_test "-pycmd int" \ + "\\^error,msg=\"-pycmd: Invalid parameter: int\"" \ + "-pycmd int" + +mi_gdb_test "-pycmd red" \ + "\\^error,msg=\"-pycmd: Command redefined but we failing anyway\"" \ + "Redefine -pycmd MI command from Python MI command" + +mi_gdb_test "-pycmd int" \ + "\\^done,result=\"42\"" \ + "-pycmd int" + +mi_gdb_test "python pycmd1('')" \ + ".*\\^error,msg=\"MI command name is empty.\"" \ + "empty MI command name" + +mi_gdb_test "python pycmd1('-')" \ + ".*\\^error,msg=\"MI command name does not start with '-' followed by at least one letter or digit.\"" \ + "invalid MI command name" + +mi_gdb_test "python pycmd1('-bad-character-@')" \ + ".*\\^error,msg=\"MI command name contains invalid character: @.\"" \ + "invalid character in MI command name" diff --git a/gdb/testsuite/gdb.python/py-mi-cmd.py b/gdb/testsuite/gdb.python/py-mi-cmd.py new file mode 100644 index 0000000000..24aa475095 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-mi-cmd.py @@ -0,0 +1,54 @@ +import gdb + +class BadKey: + def __repr__(self): + return "Bad Kay" + +class ReallyBadKey: + def __repr__(self): + return BadKey() + + +class pycmd1(gdb.MICommand): + def invoke(self, argv): + if argv[0] == 'int': + return 42 + elif argv[0] == 'str': + return "Hello world!" + elif argv[0] == 'ary': + return [ 'Hello', 42 ] + elif argv[0] == "dct": + return { 'hello' : 'world', 'times' : 42} + elif argv[0] == "bk1": + return { BadKey() : 'world' } + elif argv[0] == "bk2": + return { 1 : 'world' } + elif argv[0] == "bk3": + return { ReallyBadKey() : 'world' } + elif argv[0] == 'tpl': + return ( 42 , 'Hello' ) + elif argv[0] == 'itr': + return iter([1,2,3]) + elif argv[0] == 'nn1': + return None + elif argv[0] == 'nn2': + return [ None ] + elif argv[0] == 'red': + pycmd2('-pycmd') + return None + elif argv[0] == 'exp': + raise gdb.GdbError() + else: + raise gdb.GdbError("Invalid parameter: %s" % argv[0]) + + +class pycmd2(gdb.MICommand): + def invoke(self, argv): + if argv[0] == 'str': + return "Ciao!" + elif argv[0] == 'red': + pycmd1('-pycmd') + raise gdb.GdbError("Command redefined but we failing anyway") + else: + raise gdb.GdbError("Invalid parameter: %s" % argv[0]) +