forked from Imagelibrary/binutils-gdb
Consider GDB's builtin prefix set/show prefix sub-commands, if they
are invoked with no sub-command name then they work like this:
(gdb) show print
print address: Printing of addresses is on.
print array: Pretty formatting of arrays is off.
print array-indexes: Printing of array indexes is off.
print asm-demangle: Demangling of C++/ObjC names in disassembly listings is off.
... cut lots of lines ...
(gdb) set print
List of set print subcommands:
set print address -- Set printing of addresses.
set print array -- Set pretty formatting of arrays.
set print array-indexes -- Set printing of array indexes.
set print asm-demangle -- Set demangling of C++/ObjC names in disassembly listings.
... cut lots of lines ...
Type "help set print" followed by set print subcommand name for full documentation.
Type "apropos word" to search for commands related to "word".
Type "apropos -v word" for full documentation of commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb)
That is 'show print' lists the values of all settings under the
'print' prefix, and 'set print' lists the help text for all settings
under the 'set print' prefix.
Now, if we try to create something similar using the Python API:
(gdb) python gdb.ParameterPrefix("my-prefix", gdb.COMMAND_NONE)
(gdb) python gdb.Parameter("my-prefix foo", gdb.COMMAND_OBSCURE, gdb.PARAM_BOOLEAN)
(gdb) show my-prefix
(gdb) set my-prefix
Neither 'show my-prefix' or 'set my-prefix' gives us the same details
relating to the sub-commands that we get with the builtin prefix
commands.
This commit aims to address this.
Currently, in cmdpy_init, when a new command is created, we always set
the commands callback function to cmdpy_function. It is within
cmdpy_function that we spot that the command is a prefix command, and
that there is no gdb.Command.invoke method, and so return early.
This commit changes things so that the rules are now:
1. For NON prefix commands, we continue to use cmdpy_function.
2. For prefix commands that do have a gdb.Command.invoke
method (i.e. can handle unknown sub-commands), continue to use
cmdpy_function.
3. For all other prefix commands, don't use cmdpy_function, instead
use GDB's normal callback function for set/show prefixes.
This requires splitting the current call to add_prefix_cmd into either
a call to add_prefix_cmd, add_show_prefix_cmd, or
add_basic_prefix_cmd, as appropriate.
After these changes, we now see this:
(gdb) python gdb.ParameterPrefix("my-prefix", gdb.COMMAND_NONE) │
(gdb) python gdb.Parameter("my-prefix foo", gdb.COMMAND_OBSCURE, gdb.PARAM_BOOLEAN)
(gdb) show my-prefix │
my-prefix foo: The current value of 'my-prefix foo' is "off".
(gdb) set my-prefix
List of "set my-prefix" subcommands:
set my-prefix foo -- Set the current value of 'my-prefix foo'.
Type "help set my-prefix" followed by subcommand name for full documentation.
Type "apropos word" to search for commands related to "word".
Type "apropos -v word" for full documentation of commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb)
Which matches how a prefix defined within GDB would act.
I have made the same changes to the Guile API.
417 lines
14 KiB
Plaintext
417 lines
14 KiB
Plaintext
# Copyright (C) 2009-2025 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 <http://www.gnu.org/licenses/>.
|
|
|
|
# This file is part of the GDB testsuite. It tests the mechanism
|
|
# for defining new GDB commands in Python.
|
|
|
|
load_lib gdb-python.exp
|
|
|
|
require allow_python_tests
|
|
|
|
standard_testfile
|
|
|
|
if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
|
|
return -1
|
|
}
|
|
|
|
if {![runto_main]} {
|
|
return 0
|
|
}
|
|
|
|
# Test a simple command.
|
|
|
|
gdb_test_multiline "input simple command" \
|
|
"python" "" \
|
|
"class test_cmd (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (test_cmd, self).__init__ (\"test_cmd\", gdb.COMMAND_OBSCURE)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"test_cmd output, arg = %s\" % arg)" "" \
|
|
"test_cmd ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "test_cmd ugh" "test_cmd output, arg = ugh" "call simple command"
|
|
|
|
# Test a prefix command, and a subcommand within it.
|
|
|
|
gdb_test_multiline "input prefix command" \
|
|
"python" "" \
|
|
"class prefix_cmd (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (prefix_cmd, self).__init__ (\"prefix_cmd\", gdb.COMMAND_OBSCURE, gdb.COMPLETE_NONE, True)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"prefix_cmd output, arg = %s\" % arg)" "" \
|
|
"prefix_cmd ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "prefix_cmd ugh" "prefix_cmd output, arg = ugh" "call prefix command"
|
|
|
|
gdb_test_multiline "input subcommand" \
|
|
"python" "" \
|
|
"class subcmd (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (subcmd, self).__init__ (\"prefix_cmd subcmd\", gdb.COMMAND_OBSCURE)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"subcmd output, arg = %s\" % arg)" "" \
|
|
"subcmd ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "prefix_cmd subcmd ugh" "subcmd output, arg = ugh" "call subcmd"
|
|
|
|
# Test prefix command using keyword arguments.
|
|
|
|
gdb_test_multiline "input prefix command, keyword arguments" \
|
|
"python" "" \
|
|
"class prefix_cmd2 (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (prefix_cmd2, self).__init__ (\"prefix_cmd2\", gdb.COMMAND_OBSCURE, prefix = True, completer_class = gdb.COMPLETE_FILENAME)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"prefix_cmd2 output, arg = %s\" % arg)" "" \
|
|
"prefix_cmd2 ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "prefix_cmd2 argh" "prefix_cmd2 output, arg = argh" "call prefix command, keyword arguments"
|
|
|
|
gdb_test_multiline "input subcommand under prefix_cmd2" \
|
|
"python" "" \
|
|
"class subcmd (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (subcmd, self).__init__ (\"prefix_cmd2 subcmd\", gdb.COMMAND_OBSCURE)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"subcmd output, arg = %s\" % arg)" "" \
|
|
"subcmd ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "prefix_cmd2 subcmd ugh" "subcmd output, arg = ugh" "call subcmd under prefix_cmd2"
|
|
|
|
# Test a subcommand in an existing GDB prefix.
|
|
|
|
gdb_test_multiline "input new subcommand" \
|
|
"python" "" \
|
|
"class newsubcmd (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (newsubcmd, self).__init__ (\"info newsubcmd\", gdb.COMMAND_OBSCURE)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"newsubcmd output, arg = %s\" % arg)" "" \
|
|
"newsubcmd ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "info newsubcmd ugh" "newsubcmd output, arg = ugh" "call newsubcmd"
|
|
|
|
# Test a command that throws gdb.GdbError.
|
|
|
|
gdb_test_multiline "input command to throw error" \
|
|
"python" "" \
|
|
"class test_error_cmd (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (test_error_cmd, self).__init__ (\"test_error_cmd\", gdb.COMMAND_OBSCURE)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" raise gdb.GdbError ('you lose!')" "" \
|
|
"test_error_cmd ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "test_error_cmd ugh" "you lose!" "call error command"
|
|
|
|
# Test gdb.string_to_argv.
|
|
|
|
gdb_test "python print (gdb.string_to_argv (\"1 2 3\"))" \
|
|
{\['1', '2', '3'\]} \
|
|
"string_to_argv (\"1 2 3\"), case 1"
|
|
|
|
gdb_test "python print (gdb.string_to_argv (\"'1 2' 3\"))" \
|
|
{\['1 2', '3'\]} \
|
|
"string_to_argv (\"'1 2' 3\"), case 2"
|
|
|
|
gdb_test "python print (gdb.string_to_argv ('\"1 2\" 3'))" \
|
|
{\['1 2', '3'\]} \
|
|
"string_to_argv ('\"1 2\" 3'), case 3"
|
|
|
|
gdb_test "python print (gdb.string_to_argv ('1\\ 2 3'))" \
|
|
{\['1 2', '3'\]} \
|
|
"string_to_argv ('1\\ 2 3'), case 4"
|
|
|
|
# Test user-defined python commands.
|
|
gdb_test_multiline "input simple user-defined command" \
|
|
"python" "" \
|
|
"class test_help (gdb.Command):" "" \
|
|
" \"\"\"Docstring\"\"\"" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (test_help, self).__init__ (\"test_help\", gdb.COMMAND_USER)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"test_cmd output, arg = %s\" % arg)" "" \
|
|
"test_help ()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "test_help ugh" "test_cmd output, arg = ugh" "call simple user-defined command"
|
|
|
|
# Make sure the command shows up in `help user-defined`.
|
|
test_user_defined_class_help {"test_help -- Docstring[\r\n]"}
|
|
|
|
# Make sure the command does not show up in `show user`.
|
|
gdb_test "show user test_help" "Not a user command\." \
|
|
"don't show user-defined python command in `show user command`"
|
|
|
|
# Test expression completion on fields
|
|
gdb_test_multiline "expression completion command" \
|
|
"python" "" \
|
|
"class expr_test (gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (expr_test, self).__init__ (\"expr_test\", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"invoked on = %s\" % arg)" "" \
|
|
"expr_test ()" "" \
|
|
"end" ""
|
|
|
|
|
|
gdb_test "complete expr_test bar\." \
|
|
"expr_test bar\.bc.*expr_test bar\.ij.*" \
|
|
"test completion through complete command"
|
|
|
|
# Test that the "python" command is correctly recognized as
|
|
# inline/multi-line when entering a sequence of commands.
|
|
#
|
|
# This proc tests PR cli/21688. The PR is not language-specific, but
|
|
# the easiest way is just to test with Python.
|
|
proc test_python_inline_or_multiline { } {
|
|
global gdb_prompt
|
|
set end "\r\n$gdb_prompt $"
|
|
|
|
set define_cmd_not_inline [ list \
|
|
[ list "if 1" " >$" "multi-line if 1" ] \
|
|
[ list "python" " >$" "multi-line python command" ] \
|
|
[ list "print ('hello')" " >$" "multi-line print" ] \
|
|
[ list "end" " >$" "multi-line first end" ] \
|
|
[ list "end" "hello$end" "multi-line last end" ] ]
|
|
|
|
# This also tests trailing whitespace on the command.
|
|
set define_cmd_alias_not_inline [ list \
|
|
[ list "if 1" " >$" "multi-line if 1 alias" ] \
|
|
[ list "py " " >$" "multi-line python command alias" ] \
|
|
[ list "print ('hello')" " >$" "multi-line print alias" ] \
|
|
[ list "end" " >$" "multi-line first end alias" ] \
|
|
[ list "end" "hello$end" "multi-line last end alias" ] ]
|
|
|
|
set define_cmd_alias_foo_not_inline [ list \
|
|
[ list "alias foo=python" "$end" "multi-line alias foo" ] \
|
|
[ list "if 1" " >$" "multi-line if 1 alias foo" ] \
|
|
[ list "foo " " >$" "multi-line python command alias foo" ] \
|
|
[ list "print ('hello')" " >$" "multi-line print alias foo" ] \
|
|
[ list "end" " >$" "multi-line first end alias foo" ] \
|
|
[ list "end" "hello$end" "multi-line last end alias foo" ] ]
|
|
|
|
set define_cmd_inline [ list \
|
|
[ list "if 1" " >$" "inline if 1" ] \
|
|
[ list "python print ('hello')" " >$" "inline python command" ] \
|
|
[ list "end" "hello$end" "inline end" ] ]
|
|
|
|
set define_cmd_alias_inline [ list \
|
|
[ list "if 1" " >$" "inline if 1 alias" ] \
|
|
[ list "py print ('hello')" " >$" "inline python command alias" ] \
|
|
[ list "end" "hello$end" "inline end alias" ] ]
|
|
|
|
set define_cmd_alias_foo_inline [ list \
|
|
[ list "if 1" " >$" "inline if 1 alias foo" ] \
|
|
[ list "foo print ('hello')" " >$" "inline python command alias foo" ] \
|
|
[ list "end" "hello$end" "inline end alias foo" ] ]
|
|
|
|
foreach t [list $define_cmd_not_inline \
|
|
$define_cmd_alias_not_inline \
|
|
$define_cmd_alias_foo_not_inline \
|
|
$define_cmd_inline \
|
|
$define_cmd_alias_inline \
|
|
$define_cmd_alias_foo_inline] {
|
|
foreach l $t {
|
|
lassign $l command regex testmsg
|
|
gdb_test_multiple "$command" "$testmsg" {
|
|
-re "$regex" {
|
|
pass "$testmsg"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test_python_inline_or_multiline
|
|
|
|
if { [readline_is_used] } {
|
|
set test "complete 'expr_test bar.i'"
|
|
send_gdb "expr_test bar\.i\t\t"
|
|
gdb_test_multiple "" "$test" {
|
|
-re "expr_test bar\.ij \\\x07$" {
|
|
send_gdb "\n"
|
|
gdb_test_multiple "" $test {
|
|
-re "invoked on = bar.ij.*$gdb_prompt $" {
|
|
pass "$test"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
# Test that interrupting pagination throws a gdb quit.
|
|
gdb_test_no_output "set height 10"
|
|
|
|
gdb_test_multiline "input multi-line-output command" \
|
|
"python" "" \
|
|
"class test_mline (gdb.Command):" "" \
|
|
" \"\"\"Docstring\"\"\"" "" \
|
|
" def __init__ (self):" "" \
|
|
" super (test_mline, self).__init__ (\"test_multiline\", gdb.COMMAND_USER)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" for f in range(20):" "" \
|
|
" print (\"test_multiline output\")" "" \
|
|
"test_mline ()" "" \
|
|
"end" ""
|
|
|
|
set test "verify pagination from test_multiline"
|
|
gdb_test_multiple "test_multiline" $test {
|
|
-re "--Type <RET>" {
|
|
exp_continue
|
|
}
|
|
-re " for more, q to quit" {
|
|
exp_continue
|
|
}
|
|
-re ", c to continue without paging--$" {
|
|
pass $test
|
|
}
|
|
}
|
|
|
|
send_gdb "q\n"
|
|
set test "verify pagination from test_multiline: q"
|
|
gdb_test_multiple "" $test {
|
|
-re "Error occurred in Python" {
|
|
fail $test
|
|
}
|
|
-re "Quit" {
|
|
pass $test
|
|
}
|
|
}
|
|
|
|
# Test command redefining itself
|
|
|
|
proc_with_prefix test_command_redefining_itself {} {
|
|
# Start with a fresh gdb
|
|
clean_restart
|
|
|
|
|
|
gdb_test_multiline "input command redefining itself" \
|
|
"python" "" \
|
|
"class redefine_cmd (gdb.Command):" "" \
|
|
" def __init__ (self, msg):" "" \
|
|
" super (redefine_cmd, self).__init__ (\"redefine_cmd\", gdb.COMMAND_OBSCURE)" "" \
|
|
" self._msg = msg" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print (\"redefine_cmd output, msg = %s\" % self._msg)" "" \
|
|
" redefine_cmd (arg)" "" \
|
|
"redefine_cmd (\"XXX\")" "" \
|
|
"end" ""
|
|
|
|
gdb_test "redefine_cmd AAA" \
|
|
"redefine_cmd output, msg = XXX" \
|
|
"call command redefining itself 1"
|
|
|
|
gdb_test "redefine_cmd BBB" \
|
|
"redefine_cmd output, msg = AAA" \
|
|
"call command redefining itself 2"
|
|
}
|
|
|
|
# Try to create commands using unknown prefixes and check GDB gives an
|
|
# error. There's also a test in here for an ambiguous prefix, which
|
|
# gives the same error.
|
|
proc_with_prefix test_unknown_prefix {} {
|
|
clean_restart
|
|
|
|
gdb_test_no_output "python gdb.Command('foo1', gdb.COMMAND_NONE, prefix=True)"
|
|
gdb_test_no_output "python gdb.Command('foo cmd', gdb.COMMAND_NONE)"
|
|
|
|
foreach prefix { "xxx" "foo xxx" "foo1 xxx" } {
|
|
gdb_test "python gdb.Command('$prefix cmd', gdb.COMMAND_NONE)" \
|
|
[multi_line \
|
|
"Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \
|
|
"Error occurred in Python: Could not find command prefix $prefix\\."]
|
|
}
|
|
|
|
gdb_test_no_output "python gdb.Command('foo2', gdb.COMMAND_NONE, prefix=True)"
|
|
|
|
foreach prefix { "foo" "foo xxx" "foo1 xxx" "foo2 xxx" } {
|
|
gdb_test "python gdb.Command('$prefix cmd2', gdb.COMMAND_NONE)" \
|
|
[multi_line \
|
|
"Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \
|
|
"Error occurred in Python: Could not find command prefix $prefix\\."]
|
|
}
|
|
}
|
|
|
|
# Check what happens if a command object is called without an 'invoke'
|
|
# method.
|
|
proc_with_prefix test_deleting_invoke_methods {} {
|
|
clean_restart
|
|
|
|
gdb_test_multiline "create 'foo' prefix command" \
|
|
"python" "" \
|
|
"class test_prefix(gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super().__init__ (\"foo\", gdb.COMMAND_USER, prefix=True)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print(\"In 'foo' invoke: %s\" % arg)" "" \
|
|
"foo = test_prefix()" "" \
|
|
"end" ""
|
|
|
|
gdb_test_multiline "create 'foo bar' command" \
|
|
"python" "" \
|
|
"class test_cmd(gdb.Command):" "" \
|
|
" def __init__ (self):" "" \
|
|
" super().__init__ (\"foo bar\", gdb.COMMAND_USER)" "" \
|
|
" def invoke (self, arg, from_tty):" "" \
|
|
" print(\"In 'foo bar' invoke: %s\" % arg)" "" \
|
|
"foo_bar = test_cmd()" "" \
|
|
"end" ""
|
|
|
|
gdb_test "foo def" "In 'foo' invoke: def" \
|
|
"call 'foo' with an unknown sub-command"
|
|
|
|
gdb_test "foo bar def" "In 'foo bar' invoke: def" \
|
|
"call 'foo bar' with arguments"
|
|
|
|
gdb_test_no_output "python del(foo_bar.__class__.invoke)" \
|
|
"delete invoke from test_cmd class"
|
|
|
|
with_test_prefix "after deleting test_cmd.invoke" {
|
|
gdb_test "foo def" "In 'foo' invoke: def" \
|
|
"call 'foo' with an unknown sub-command"
|
|
|
|
gdb_test "foo bar def" \
|
|
"^Python command object missing 'invoke' method\\." \
|
|
"call 'foo bar' with arguments"
|
|
}
|
|
|
|
gdb_test_no_output "python del(foo.__class__.invoke)" \
|
|
"delete invoke from test_prefix class"
|
|
|
|
with_test_prefix "after deleting test_prefix.invoke" {
|
|
gdb_test "foo def" \
|
|
"^Python command object missing 'invoke' method\\." \
|
|
"call 'foo' with an unknown sub-command"
|
|
|
|
gdb_test "foo bar def" \
|
|
"^Python command object missing 'invoke' method\\." \
|
|
"call 'foo bar' with arguments"
|
|
}
|
|
}
|
|
|
|
test_command_redefining_itself
|
|
test_unknown_prefix
|
|
test_deleting_invoke_methods
|