# 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\\."]