# Copyright (C) 2015-2023 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 .
# 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: " \
"Error while executing Python code\\."]
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 while executing Python code\\."]
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: " \
"Error while executing Python code\\."]
# 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))" \
""
# 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))" \
""
# 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)" \
""
# 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)" \
""
# 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 while executing Python code\\."]
}
# 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 : 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 : 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 : 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 while executing Python code\\."]
# Check we can't directly instantiate a gdb.UnwindInfo.
gdb_test "python uw = gdb.UnwindInfo()" \
[multi_line \
"TypeError: cannot create 'gdb\\.UnwindInfo' instances" \
"Error while executing Python code\\."]