binutils-gdb/gdb/testsuite/gdb.python/py-unwind.exp
Tom Tromey a207f6b3a3 Rewrite "python" command exception handling
The "python" command (and the Python implementation of the gdb
"source" command) does not handle Python exceptions in the same way as
other gdb-facing Python code.  In particular, exceptions are turned
into a generic error rather than being routed through
gdbpy_handle_exception, which takes care of converting to 'quit' as
appropriate.

I think this was done this way because PyRun_SimpleFile and friends do
not propagate the Python exception -- they simply indicate that one
occurred.

This patch reimplements these functions to respect the general gdb
convention here.  As a bonus, some Windows-specific code can be
removed, as can the _execute_file function.

The bulk of this change is tweaking the test suite to match the new
way that exceptions are displayed.  These changes are largely
uninteresting.  However, it's worth pointing out the py-error.exp
change.  Here, the failure changes because the test changes the host
charset to something that isn't supported by Python.  This then
results in a weird error in the new setup.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31354
Acked-By: Tom de Vries <tdevries@suse.de>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
2024-02-27 09:46:31 -07:00

276 lines
10 KiB
Plaintext

# Copyright (C) 2015-2024 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 verifies that frame
# unwinders can be implemented in Python.
load_lib gdb-python.exp
require allow_python_tests
standard_testfile
# Stack protection can make the stack look a bit different, breaking the
# assumptions this test has about its layout.
set flags "additional_flags=-fno-stack-protector"
if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} "debug $flags"] } {
return -1
}
# This test runs on a specific platform.
require is_x86_64_m64_target
# The following tests require execution.
if {![runto_main]} {
return 0
}
# Check for the corrupt backtrace.
proc check_for_broken_backtrace {testname} {
gdb_test_sequence "where" $testname {
"\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
"\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
"Backtrace stopped: frame did not save the PC"
}
}
# Check for the correct backtrace.
proc check_for_fixed_backtrace {testname} {
gdb_test_sequence "where" $testname {
"\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
"\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
"\\r\\n#2 .* main \\(.*\\) at"
}
}
# Check the 'info unwinder' output.
proc check_info_unwinder {testname enabled} {
if {$enabled} {
set suffix ""
} else {
set suffix " \\\[disabled\\\]"
}
gdb_test_sequence "info unwinder" $testname \
[list \
"Global:" \
" test unwinder${suffix}"]
}
set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
gdb_continue_to_breakpoint "break backtrace-broken"
check_for_broken_backtrace "Backtrace is initially broken"
gdb_test "source ${pyfile}" "Python script imported" \
"import python scripts"
check_info_unwinder "info unwinder after loading script" on
check_for_fixed_backtrace "check backtrace after loading unwinder"
# Check that the Python unwinder frames can be flushed / released.
gdb_test "maint flush register-cache" "Register cache flushed\\." "flush frames"
check_for_fixed_backtrace "check backtrace after flush"
# Try to disable the unwinder but instead set the enabled field to a
# non boolean value. This should fail. Check the 'info unwinder'
# output to be sure.
gdb_test "python global_test_unwinder.enabled = \"off\"" \
[multi_line \
"TypeError.*: incorrect type for enabled attribute: <class 'str'>" \
"Error occurred in Python.*"]
check_info_unwinder "info unwinder after failed disable" on
# While we're doing silly stuff, lets try to change the name of this
# unwider. Doing this is bad as the new name might clash with an
# already registered name, which violates the promises made during
# 'register_unwinder'.
set pattern_1 "can't set attribute(?: 'name')?"
set pattern_2 "property 'name' of 'TestUnwinder' object has no setter"
gdb_test "python global_test_unwinder.name = \"foo\"" \
[multi_line \
"AttributeError.*: (?:${pattern_1}|${pattern_2})" \
"Error occurred in Python.*"]
check_info_unwinder "info unwinder after failed name change" on
# Now actually disable the unwinder by manually adjusting the
# 'enabled' attribute. Check that the stack is once again broken, and
# that the unwinder shows as disabled in the 'info unwinder' output.
gdb_test_no_output "python global_test_unwinder.enabled = False"
check_for_broken_backtrace "stack is broken after disabling"
check_info_unwinder "info unwinder after manually disabling" off
# Now enable the unwinder using the 'enable unwinder' command.
gdb_test "enable unwinder global \"test unwinder\"" \
"1 unwinder enabled"
check_for_fixed_backtrace "check backtrace after enabling with command"
check_info_unwinder "info unwinder after command enabled" on
# And now disable using the command and check the stack is once again
# broken, and that the 'info unwinder' output updates correctly.
gdb_test "disable unwinder global \"test unwinder\"" \
"1 unwinder disabled"
check_for_broken_backtrace "stack is broken after command disabling"
check_info_unwinder "info unwinder after command disabling" off
# Check that invalid register names and values cause errors.
gdb_test "python print(add_saved_register_errors\[\"unknown_name\"\])" \
"Bad register" \
"add_saved_register error when an unknown register name is used"
gdb_test "python print(add_saved_register_errors\[\"unknown_number\"\])" \
"Bad register" \
"add_saved_register error when an unknown register number is used"
gdb_test "python print(add_saved_register_errors\[\"bad_value\"\])" \
"argument 2 must be gdb.Value, not int" \
"add_saved_register error when invalid register value is used"
gdb_test "python print(read_register_error)" "Bad register" \
"read_register error"
# Try to create an unwinder object with a non-string name.
gdb_test "python obj = simple_unwinder(True)" \
[multi_line \
"TypeError.*: incorrect type for name: <class 'bool'>" \
"Error occurred in Python.*"]
# Now register the simple_unwinder with a valid name, and use the
# unwinder to capture a PendingFrame object.
gdb_test_no_output "python obj = simple_unwinder(\"simple\")"
gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj)"
check_for_broken_backtrace "backtrace to capture a PendingFrame object"
# Check the captured PendingFrame is not valid.
gdb_test "python print(captured_pending_frame.is_valid())" "False"
# Check the __repr__ of an invalid PendingFrame.
gdb_test "python print(repr(captured_pending_frame))" \
"<gdb.PendingFrame \\(invalid\\)>"
# Check the __repr__ of an UnwindInfo for an invalid PendingFrame.
gdb_test "python print(captured_unwind_info)"
gdb_test "python print(repr(captured_unwind_info))" \
"<gdb.UnwindInfo for an invalid frame>"
# Check the repr of a PendingFrame that was copied (as a string) at a
# time the PendingFrame was valid.
gdb_test "python print(captured_pending_frame_repr)" \
"<gdb.PendingFrame level=0, sp=$hex, pc=$hex>"
# Check the repr of an UnwindInfo that was copied (as a string) at a
# time the UnwindInfo was valid.
gdb_test "python print(captured_unwind_info_repr)" \
"<gdb.UnwindInfo frame #0, saved_regs=\\(rip, rbp, rsp\\)>"
# Call methods on the captured gdb.PendingFrame and check we see the
# expected error.
gdb_test_no_output "python pf = captured_pending_frame"
foreach cmd {"pf.read_register(\"pc\")" \
"pf.create_unwind_info(None)" \
"pf.architecture()" \
"pf.level()" \
"pf.name()" \
"pf.pc()" \
"pf.language()" \
"pf.find_sal()" \
"pf.block()" \
"pf.function()" } {
gdb_test "python $cmd" \
[multi_line \
"ValueError.*: gdb\\.PendingFrame is invalid\\." \
"Error occurred in Python.*"]
}
# Turn on the useful unwinder so we have the full backtrace again, and
# disable the simple unwinder -- because we can!
gdb_test "enable unwinder global \"test unwinder\"" \
"1 unwinder enabled" \
"re-enable 'test unwinder' so we can check PendingFrame methods"
gdb_test "disable unwinder global \"simple\"" \
"1 unwinder disabled"
check_for_fixed_backtrace \
"check backtrace before testing PendingFrame methods"
# Turn the 'simple' unwinder back on.
gdb_test "enable unwinder global \"simple\"" \
"1 unwinder enabled"
# Replace the "simple" unwinder with a new version that doesn't set
# the 'sp' attribute. Also the 'pc' attribute is invalid, but we'll
# hit the missing 'sp' error first.
with_test_prefix "frame-id 'sp' is None" {
gdb_test_no_output "python obj = simple_unwinder(\"simple\", None, \"xyz\")"
gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)"
gdb_test_no_output "python captured_pending_frame = None"
gdb_test "backtrace" \
"Python Exception <class 'ValueError'>: frame_id should have 'sp' attribute\\.\r\n.*"
}
# Replace the "simple" unwinder with a new version that sets the 'sp'
# attribute to an invalid value. Also the 'pc' attribute is invalid, but we'll
# hit the invalid 'sp' error first.
with_test_prefix "frame-id 'sp' is invalid" {
gdb_test_no_output "python obj = simple_unwinder(\"simple\", \"jkl\", \"xyz\")"
gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)"
gdb_test_no_output "python captured_pending_frame = None"
gdb_test "backtrace" \
"Python Exception <class 'ValueError'>: invalid literal for int\\(\\) with base 10: 'jkl'\r\n.*"
}
# Replace the "simple" unwinder with a new version that sets the 'sp'
# to a valid value, but set the 'pc' attribute to an invalid value.
with_test_prefix "frame-id 'pc' is invalid" {
gdb_test_no_output "python obj = simple_unwinder(\"simple\", 0x123, \"xyz\")"
gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)"
gdb_test_no_output "python captured_pending_frame = None"
gdb_test "backtrace" \
"Python Exception <class 'ValueError'>: invalid literal for int\\(\\) with base 10: 'xyz'\r\n.*"
}
# Gather information about every frame.
gdb_test_no_output "python capture_all_frame_information()"
gdb_test_no_output "python gdb.newest_frame().select()"
gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
gdb_test_no_output "python obj = validating_unwinder()"
gdb_test_no_output "python gdb.unwinder.register_unwinder(pspace, obj)"
check_for_fixed_backtrace \
"check backtrace to validate all information"
gdb_test_no_output "python check_all_frame_information_matched()"
# Check we can't sub-class from gdb.UnwindInfo.
gdb_test_multiline "Sub-class gdb.UnwindInfo " \
"python" "" \
"class my_unwind_info(gdb.UnwindInfo):" "" \
" def __init__(self):" "" \
" pass" "" \
"end" \
[multi_line \
"TypeError.*: type 'gdb\\.UnwindInfo' is not an acceptable base type" \
"Error occurred in Python.*"]
# Check we can't directly instantiate a gdb.UnwindInfo.
gdb_test "python uw = gdb.UnwindInfo()" \
[multi_line \
"TypeError.*: cannot create 'gdb\\.UnwindInfo' instances" \
"Error occurred in Python.*"]