# Copyright (C) 2023-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 . load_lib gdb-python.exp require allow_python_tests standard_testfile if {[build_executable "failed to prepare" ${testfile} ${srcfile} \ {debug build-id}]} { return -1 } # Remove debug information from BINFILE and place it into # BINFILE.debug. if {[gdb_gnu_strip_debug $binfile]} { unsupported "cannot produce separate debug info files" return -1 } set remote_python_file \ [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] set debug_filename ${binfile}.debug set hidden_filename ${binfile}.hidden # Start GDB. clean_restart # Some initial sanity checks; initially, we can find the debug information # (this will use the .gnu_debuglink), then after we move the debug # information, reload the executable, now the debug can't be found. with_test_prefix "initial checks" { # Load BINFILE, we should find the separate debug information. gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "debug"} \ "debug info is found" # Rename the debug information file, re-load BINFILE, GDB should fail # to find the debug information remote_exec build "mv $debug_filename $hidden_filename" gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \ "debug info no longer found" } # Load the Python script into GDB. gdb_test "source $remote_python_file" "^Success" \ "source python script" # Setup the separate debug info directory. This isn't actually needed until # some of the later tests, but might as well get this done now. set debug_directory [standard_output_file "debug-dir"] remote_exec build "mkdir -p $debug_directory" gdb_test_no_output "set debug-file-directory $debug_directory" \ "set debug-file-directory" # Initially the missing debug handler we install is in a mode where it # returns None, indicating that it can't help locate the debug information. # Check this works as expected. with_test_prefix "handler returning None" { gdb_test_no_output \ "python gdb.missing_debug.register_handler(None, handler_obj)" \ "register the initial handler" gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \ "debug info not found" # Check the handler was only called once. gdb_test "python print(handler_obj.call_count)" "^1" \ "check handler was only called once" } # Now configure the handler to move the debug file back to the # .gnu_debuglink location and then return True, this will cause GDB to # recheck, at which point it should find the debug info. with_test_prefix "handler in gnu_debuglink mode" { gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \ \"$hidden_filename\", \ \"$debug_filename\")" \ "confirgure handler" gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found" # Check the handler was only called once. gdb_test "python print(handler_obj.call_count)" "^1" \ "check handler was only called once" } # Setup a directory structure based on the build-id of BINFILE, but don't # move the debug information into place just yet. # # Instead, configure the handler to move the debug info into the build-id # directory. # # Reload BINFILE, at which point the handler will move the debug info into # the build-id directory and return True, GDB will then recheck for the # debug information, and should find it. with_test_prefix "handler in build-id mode" { # Move the debug file out of the way once more. remote_exec build "mv $debug_filename $hidden_filename" # Create the build-id based directory in which the debug information # will be placed. set build_id_filename \ $debug_directory/[build_id_debug_filename_get $binfile] remote_exec build "mkdir -p [file dirname $build_id_filename]" # Configure the handler to move the debug info into the build-id dir. gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \ \"$hidden_filename\", \ \"$build_id_filename\")" \ "confirgure handler" # Reload the binary and check the debug information is found. gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found" # Check the handler was only called once. gdb_test "python print(handler_obj.call_count)" "^1" \ "check handler was only called once" } # Move the debug information back to a hidden location and configure the # handler to return the filename of the hidden debug info location. GDB # should immediately use this file as the debug information. with_test_prefix "handler returning a string" { remote_exec build "mv $build_id_filename $hidden_filename" # Configure the handler return a filename string. gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \ \"$hidden_filename\")" \ "confirgure handler" # Reload the binary and check the debug information is found. gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found" # Check the handler was only called once. gdb_test "python print(handler_obj.call_count)" "^1" \ "check handler was only called once" } # Register another global handler, this one raises an exception. Reload the # debug information, the bad handler should be invoked first, which raises # an excetption, at which point GDB should skip further Python handlers. with_test_prefix "handler raises an exception" { gdb_test_no_output \ "python gdb.missing_debug.register_handler(None, rhandler)" foreach_with_prefix exception_type {gdb.GdbError TypeError} { gdb_test_no_output \ "python rhandler.exception_type = $exception_type" gdb_file_cmd $binfile gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \ "debug info not found" set re [string_to_regexp \ "Python Exception : message"] gdb_assert {[regexp $re $gdb_file_cmd_msg]} \ "check for exception in file command output" # Our original handler is still registered, but should not have been # called again (as the exception occurs first). gdb_test "python print(handler_obj.call_count)" "^1" \ "check good handler hasn't been called again" } } gdb_test "info missing-debug-handlers" \ [multi_line \ "Global:" \ " exception_handler" \ " handler"] \ "check both handlers are visible" # Re-start GDB. clean_restart # Load the Python script into GDB. gdb_test "source $remote_python_file" "^Success" \ "source python script for bad handler name checks" # Attempt to register a missing-debug-handler with NAME. The expectation is # that this should fail as NAME contains some invalid characters. proc check_bad_name {name} { set name_re [string_to_regexp $name] set re \ [multi_line \ "ValueError.*: invalid character '.' in handler name: $name_re" \ "Error occurred in Python.*"] gdb_test "python register(\"$name\")" $re \ "check that '$name' is not accepted" } # We don't attempt to be exhaustive here, just check a few random examples # of invalid names. check_bad_name "!! Bad Name" check_bad_name "Bad Name" check_bad_name "(Bad Name)" check_bad_name "Bad \[Name\]" check_bad_name "Bad,Name" check_bad_name "Bad;Name" # Check that there are no handlers registered. gdb_test_no_output "info missing-debug-handlers" \ "check no handlers are registered" # Check we can use the enable/disable commands where there are no handlers # registered. gdb_test "enable missing-debug-handler foo" \ "^0 missing debug handlers enabled" gdb_test "disable missing-debug-handler foo" \ "^0 missing debug handlers disabled" # Grab the current program space object, used for registering handler later. gdb_test_no_output "python pspace = gdb.selected_inferior().progspace" # Now register some handlers. foreach hspec {{\"Foo\" None} {\"-bar\" None} {\"baz-\" pspace} {\"abc-def\" pspace}} { lassign $hspec name locus gdb_test "python register($name, $locus)" } with_test_prefix "all handlers enabled" { gdb_test "info missing-debug-handlers" \ [multi_line \ "Current Progspace:" \ " abc-def" \ " baz-" \ "Global:" \ " -bar" \ " Foo"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } with_test_prefix "disable 'baz-'" { gdb_test "disable missing-debug-handler progspace baz-" \ "^1 missing debug handler disabled" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " abc-def" \ " baz- \\\[disabled\\\]" \ "Global:" \ " -bar" \ " Foo"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['abc-def', '-bar', 'Foo']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } with_test_prefix "disable 'Foo'" { gdb_test "disable missing-debug-handler .* Foo" \ "^1 missing debug handler disabled" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " abc-def" \ " baz- \\\[disabled\\\]" \ "Global:" \ " -bar" \ " Foo \\\[disabled\\\]"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['abc-def', '-bar']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } with_test_prefix "disable everything" { gdb_test "disable missing-debug-handler .* .*" \ "^2 missing debug handlers disabled" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " abc-def \\\[disabled\\\]" \ " baz- \\\[disabled\\\]" \ "Global:" \ " -bar \\\[disabled\\\]" \ " Foo \\\[disabled\\\]"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {[]}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } with_test_prefix "enable 'abc-def'" { set re [string_to_regexp $binfile] gdb_test "enable missing-debug-handler \"$re\" abc-def" \ "^1 missing debug handler enabled" \ "enable missing-debug-handler" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " abc-def" \ " baz- \\\[disabled\\\]" \ "Global:" \ " -bar \\\[disabled\\\]" \ " Foo \\\[disabled\\\]"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['abc-def']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } with_test_prefix "enable global handlers" { set re [string_to_regexp $binfile] gdb_test "enable missing-debug-handler global" \ "^2 missing debug handlers enabled" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " abc-def" \ " baz- \\\[disabled\\\]" \ "Global:" \ " -bar" \ " Foo"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['abc-def', '-bar', 'Foo']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } # Add handler_obj to the global handler list, and configure it to # return False. We should call all of the program space specific # handlers (which return None), and then call handler_obj from the # global list, which returns False, at which point we shouldn't call # anyone else. with_test_prefix "return False handler in progspace list" { gdb_test "enable missing-debug-handler progspace" \ "^1 missing debug handler enabled" gdb_test_no_output \ "python gdb.missing_debug.register_handler(None, handler_obj)" \ "register the initial handler" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " abc-def" \ " baz-" \ "Global:" \ " handler" \ " -bar" \ " Foo"] gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \ "confirgure handler" gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['abc-def', 'baz-', 'handler']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } # Now add handler_obj to the current program space's handler list. We # use the same handler object here, that's fine. We should only see a # call to the first handler object in the call log. with_test_prefix "return False handler in global list" { gdb_test_no_output \ "python gdb.missing_debug.register_handler(pspace, handler_obj)" \ "register the initial handler" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " handler" \ " abc-def" \ " baz-" \ "Global:" \ " handler" \ " -bar" \ " Foo"] gdb_file_cmd $binfile gdb_test "python print(handler_call_log)" \ [string_to_regexp {['handler']}] gdb_test_no_output "python handler_call_log = \[\]" \ "reset call log" } with_test_prefix "check handler replacement" { # First, check we can have the same name appear in both program # space and global lists without giving an error. gdb_test_no_output "python register(\"Foo\", pspace)" gdb_test "info missing-debug-handlers" \ [multi_line \ "Progspace \[^\r\n\]+:" \ " Foo" \ " handler" \ " abc-def" \ " baz-" \ "Global:" \ " handler" \ " -bar" \ " Foo"] # Now check that we get an error if we try to add a handler with # the same name. gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \ [multi_line \ "RuntimeError.*: Handler Foo already exists\\." \ "Error occurred in Python.*"] gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \ [multi_line \ "RuntimeError.*: Handler Foo already exists\\." \ "Error occurred in Python.*"] # And now try again, but this time with 'replace=True', we # shouldn't get an error in this case. gdb_test_no_output \ "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" gdb_test_no_output \ "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)" # Now disable a handler and check we still need to use 'replace=True'. gdb_test "disable missing-debug-handler progspace Foo" \ "^1 missing debug handler disabled" gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \ [multi_line \ "RuntimeError.*: Handler Foo already exists\\." \ "Error occurred in Python.*"] \ "still get an error when handler is disabled" gdb_test_no_output \ "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \ "can replace a disabled handler" }