| # Copyright (C) 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/>. | |
| load_lib gdb-python.exp | |
| require allow_python_tests | |
| require {!is_remote host} | |
| standard_testfile .c -lib.c | |
| # Build the library. | |
| set libname ${testfile}-lib | |
| set libfile [standard_output_file $libname] | |
| if { [build_executable "build shlib" $libfile $srcfile2 \ | |
| {debug shlib build-id}] == -1} { | |
| return | |
| } | |
| # Build the executable. | |
| set opts [list debug build-id shlib=${libfile}] | |
| if { [build_executable "build exec" $binfile $srcfile $opts] == -1} { | |
| return | |
| } | |
| # The cc-with-gnu-debuglink board will split the debug out into the | |
| # .debug directory. This test script relies on having GDB lookup the | |
| # objfile and debug via the build-id, which this test sets up. Trying | |
| # to do that, while also supporting the cc-with-gnu-debuglink board is | |
| # just too complicated. | |
| if {[file isdirectory [standard_output_file ".debug"]]} { | |
| unsupported "split debug testing not supported" | |
| return | |
| } | |
| set remote_python_file \ | |
| [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] | |
| # Generate a core file. | |
| set corefile [core_find $binfile {}] | |
| if {$corefile == ""} { | |
| unsupported "core file not generated" | |
| return 0 | |
| } | |
| # Create a directory named DIRNAME for use as the | |
| # debug-file-directory. Populate the directory with links (based on | |
| # the build-ids) to each file in the list FILES. | |
| # | |
| # Return the full filename of DIRNAME on the host. | |
| proc setup_debugdir { dirname files } { | |
| set debugdir [host_standard_output_file $dirname] | |
| # Create basic empty directory structure (in case FILES is empty). | |
| remote_exec host "mkdir -p $debugdir/.build-id/" | |
| foreach file $files { | |
| set build_id_filename [build_id_debug_filename_get $file ""] | |
| remote_exec host "mkdir -p $debugdir/[file dirname $build_id_filename]" | |
| remote_exec host "ln -s $file $debugdir/$build_id_filename" | |
| } | |
| return $debugdir | |
| } | |
| # Query some symbols in the inferior to see if GDB managed to find the | |
| # executable (when EXEC_LOADED is true) and/or the library (when LIB_LOADED | |
| # is true). | |
| proc check_loaded_debug { exec_loaded lib_loaded } { | |
| if { $exec_loaded } { | |
| gdb_test "whatis global_exec_var" "^type = volatile struct exec_type" | |
| if { $lib_loaded } { | |
| gdb_test "whatis global_lib_var" "^type = volatile struct lib_type" | |
| } else { | |
| gdb_test "whatis global_lib_var" \ | |
| "^No symbol \"global_lib_var\" in current context\\." | |
| } | |
| } else { | |
| gdb_test "whatis global_exec_var" \ | |
| "^No symbol table is loaded\\. Use the \"file\" command\\." | |
| gdb_test "whatis global_lib_var" \ | |
| "^No symbol table is loaded\\. Use the \"file\" command\\." | |
| } | |
| } | |
| # Load the global corefile. The EXTRA_RE is checked for prior to GDB | |
| # announcing that the core-file has been loaded. | |
| proc load_core_file { {extra_re ".*"} } { | |
| gdb_test "core-file $::corefile" \ | |
| [multi_line \ | |
| "$extra_re" \ | |
| "Core was generated by \[^\r\n\]+" \ | |
| "Program terminated with signal SIGABRT, Aborted\\." \ | |
| "\[^\r\n\]+(?:\r\n\[^\r\n\]+)?"] \ | |
| "loaded the core file" | |
| } | |
| # Set the debug-file-directory to DIRNAME. | |
| proc set_debug_file_dir { dirname } { | |
| gdb_test_no_output "set debug-file-directory $dirname" \ | |
| "set debug-file-directory" | |
| } | |
| # Restart GDB and load the support Python script. | |
| proc clean_restart_load_python {} { | |
| clean_restart | |
| gdb_test "source $::remote_python_file" "^Success" \ | |
| "load python script" | |
| } | |
| # For sanity, lets check that we can load the specify the executable | |
| # and then load the core-file the easy way. | |
| with_test_prefix "initial sanity check" { | |
| clean_restart $binfile | |
| load_core_file | |
| check_loaded_debug true true | |
| } | |
| # Move the executable and library into a location that the core-file | |
| # can't possibly know about. After this the only way GDB can track | |
| # down these files will be by looking in the debug-file-directory. | |
| set hidden_dir [host_standard_output_file "hidden"] | |
| set hidden_binfile "$hidden_dir/$testfile" | |
| set hidden_libfile "$hidden_dir/$libname" | |
| remote_exec host "mkdir -p $hidden_dir" | |
| remote_exec host "mv $libfile $hidden_libfile" | |
| remote_exec host "mv $binfile $hidden_binfile" | |
| # If using the fission-dwp board then we'll have .dwp files that also | |
| # need to be moved. | |
| if {[remote_file host exists ${libfile}.dwp]} { | |
| remote_exec host "mv ${libfile}.dwp ${hidden_libfile}.dwp" | |
| } | |
| if {[remote_file host exists ${binfile}.dwp]} { | |
| remote_exec host "mv ${binfile}.dwp ${hidden_binfile}.dwp" | |
| } | |
| with_test_prefix "no objfiles, no debug-file-directory" { | |
| clean_restart | |
| load_core_file | |
| check_loaded_debug false false | |
| } | |
| # Setup some debug-file-directories. | |
| set debugdir_no_lib \ | |
| [setup_debugdir "debugdir.no-lib" [list "$hidden_binfile"]] | |
| set debugdir_empty \ | |
| [setup_debugdir "debugdir.empty" {}] | |
| set debugdir_all \ | |
| [setup_debugdir "debugdir.all" [list "$hidden_libfile" \ | |
| "$hidden_binfile"]] | |
| with_test_prefix "no objfiles available" { | |
| # Another sanity check that GDB can find the files via the | |
| # debug-file-directory. | |
| clean_restart | |
| set_debug_file_dir $debugdir_empty | |
| load_core_file | |
| check_loaded_debug false false | |
| } | |
| with_test_prefix "all objfiles available" { | |
| # Another sanity check that GDB can find the files via the | |
| # debug-file-directory. | |
| set_debug_file_dir $debugdir_all | |
| load_core_file | |
| check_loaded_debug true true | |
| } | |
| with_test_prefix "lib objfile missing" { | |
| # Another sanity check that GDB can find the files via the | |
| # debug-file-directory. | |
| set_debug_file_dir $debugdir_no_lib | |
| load_core_file | |
| check_loaded_debug true false | |
| } | |
| with_test_prefix "all objfiles missing, handler returns None" { | |
| clean_restart_load_python | |
| gdb_test_no_output \ | |
| "python gdb.missing_objfile.register_handler(None, handler_obj)" \ | |
| "register initial handler" | |
| load_core_file | |
| check_loaded_debug false false | |
| # The handler should be called three times, once for the | |
| # mapped-file, once for the core-file's exec, and once for the | |
| # shared library. | |
| gdb_test "python print(handler_obj.call_count)" "^3" \ | |
| "check handler was called three times" | |
| } | |
| with_test_prefix "lib objfile missing, handler returns None" { | |
| # Reset handler_obj. | |
| gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_NONE)" | |
| set_debug_file_dir $debugdir_no_lib | |
| load_core_file | |
| check_loaded_debug true false | |
| # The handler will be called twice, once when GDB tries to | |
| # load the shared library during the memory-mapped file phase, | |
| # then again for the shared library loading. | |
| gdb_test "python print(handler_obj.call_count)" "^2" \ | |
| "check handler was called three times" | |
| } | |
| with_test_prefix "handler installs lib objfile" { | |
| set build_id_filename [build_id_debug_filename_get \ | |
| $hidden_libfile ""] | |
| remote_exec host \ | |
| "mkdir -p $debugdir_no_lib/[file dirname $build_id_filename]" | |
| gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \ | |
| \"$hidden_libfile\", \"$debugdir_no_lib/$build_id_filename\")" \ | |
| "configure handler" | |
| load_core_file | |
| check_loaded_debug true true | |
| # Cleanup so the test can be reproduced again later if needed. | |
| remote_exec host "rm $debugdir_no_lib/$build_id_filename" | |
| } | |
| with_test_prefix "handler points to lib objfile" { | |
| set build_id_filename [build_id_debug_filename_get \ | |
| $hidden_libfile ""] | |
| remote_exec host \ | |
| "mkdir -p $debugdir_no_lib/[file dirname $build_id_filename]" | |
| gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \ | |
| \"$hidden_libfile\")" \ | |
| "configure handler" | |
| load_core_file | |
| check_loaded_debug true true | |
| # Cleanup so the test can be reproduced again later if needed. | |
| remote_exec host "rm $debugdir_no_lib/$build_id_filename" | |
| # The handler will only have been called once when loading the | |
| # memory-mapped file. GDB is smart enough to reuse the previously | |
| # discovered BFD object as the shared library. | |
| gdb_test "python print(handler_obj.call_count)" "^1" \ | |
| "check good handler hasn't been called again" | |
| # Validate the filename and build-id arguments passed to the handler. | |
| set expected_buildid [get_build_id $hidden_libfile] | |
| gdb_test "python print(handler_last_buildid)" "^$expected_buildid" | |
| gdb_test "python print(handler_last_filename)" \ | |
| "^[string_to_regexp $libfile]" | |
| } | |
| # Register another global handler, this one raises an exception. Reload the | |
| # core-file, 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_objfile.register_handler(None, rhandler)" | |
| foreach_with_prefix exception_type {gdb.GdbError TypeError} { | |
| gdb_test_no_output \ | |
| "python rhandler.exception_type = $exception_type" | |
| # Load the core file. We expect the exception message to appear at | |
| # least once in the output. | |
| set re [string_to_regexp \ | |
| "Python Exception <class '$exception_type'>: message"] | |
| load_core_file "${re}.*" | |
| # 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" | |
| } | |
| } | |
| # Re-start GDB. | |
| clean_restart_load_python | |
| # 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-objfile-handlers" \ | |
| "check no handlers are registered" | |
| # 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-objfile-handlers" \ | |
| [multi_line \ | |
| "Current Progspace:" \ | |
| " abc-def" \ | |
| " baz-" \ | |
| "Global:" \ | |
| " -bar" \ | |
| " Foo"] | |
| set_debug_file_dir $debugdir_no_lib | |
| load_core_file | |
| # As we perform two look ups, first for the mapped-file then for the | |
| # shared library, each handler will be called twice. | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo', '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-objfile-handler progspace baz-" \ | |
| "^1 missing objfile handler disabled" | |
| gdb_test "info missing-objfile-handlers" \ | |
| [multi_line \ | |
| "Progspace \[^\r\n\]+:" \ | |
| " abc-def" \ | |
| " baz- \\\[disabled\\\]" \ | |
| "Global:" \ | |
| " -bar" \ | |
| " Foo"] | |
| load_core_file | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['abc-def', '-bar', 'Foo', 'abc-def', '-bar', 'Foo']}] | |
| gdb_test_no_output "python handler_call_log = \[\]" \ | |
| "reset call log" | |
| } | |
| with_test_prefix "disable 'Foo'" { | |
| gdb_test "disable missing-objfile-handler .* Foo" \ | |
| "^1 missing objfile handler disabled" | |
| gdb_test "info missing-objfile-handlers" \ | |
| [multi_line \ | |
| "Progspace \[^\r\n\]+:" \ | |
| " abc-def" \ | |
| " baz- \\\[disabled\\\]" \ | |
| "Global:" \ | |
| " -bar" \ | |
| " Foo \\\[disabled\\\]"] | |
| load_core_file | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['abc-def', '-bar', 'abc-def', '-bar']}] | |
| gdb_test_no_output "python handler_call_log = \[\]" \ | |
| "reset call log" | |
| } | |
| with_test_prefix "disable everything" { | |
| gdb_test "disable missing-objfile-handler .* .*" \ | |
| "^2 missing objfile handlers disabled" | |
| gdb_test "info missing-objfile-handlers" \ | |
| [multi_line \ | |
| "Progspace \[^\r\n\]+:" \ | |
| " abc-def \\\[disabled\\\]" \ | |
| " baz- \\\[disabled\\\]" \ | |
| "Global:" \ | |
| " -bar \\\[disabled\\\]" \ | |
| " Foo \\\[disabled\\\]"] | |
| load_core_file | |
| 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 $hidden_binfile] | |
| gdb_test "enable missing-objfile-handler \"$re\" abc-def" \ | |
| "^1 missing objfile handler enabled" \ | |
| "enable missing-objfile-handler" | |
| gdb_test "info missing-objfile-handlers" \ | |
| [multi_line \ | |
| "Progspace \[^\r\n\]+:" \ | |
| " abc-def" \ | |
| " baz- \\\[disabled\\\]" \ | |
| "Global:" \ | |
| " -bar \\\[disabled\\\]" \ | |
| " Foo \\\[disabled\\\]"] | |
| load_core_file | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['abc-def', 'abc-def']}] | |
| gdb_test_no_output "python handler_call_log = \[\]" \ | |
| "reset call log" | |
| } | |
| with_test_prefix "enable global handlers" { | |
| gdb_test "enable missing-objfile-handler global" \ | |
| "^2 missing objfile handlers enabled" | |
| gdb_test "info missing-objfile-handlers" \ | |
| [multi_line \ | |
| "Progspace \[^\r\n\]+:" \ | |
| " abc-def" \ | |
| " baz- \\\[disabled\\\]" \ | |
| "Global:" \ | |
| " -bar" \ | |
| " Foo"] | |
| load_core_file | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['abc-def', '-bar', 'Foo', '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 global list" { | |
| gdb_test "enable missing-objfile-handler progspace" \ | |
| "^1 missing objfile handler enabled" | |
| gdb_test_no_output \ | |
| "python gdb.missing_objfile.register_handler(None, handler_obj)" \ | |
| "register handler_obj in global list" | |
| gdb_test "info missing-objfile-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" | |
| load_core_file | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['abc-def', 'baz-', 'handler', '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 progspace list" { | |
| gdb_test_no_output \ | |
| "python gdb.missing_objfile.register_handler(pspace, handler_obj)" \ | |
| "register handler_obj in progspace list" | |
| gdb_test "info missing-objfile-handlers" \ | |
| [multi_line \ | |
| "Progspace \[^\r\n\]+:" \ | |
| " handler" \ | |
| " abc-def" \ | |
| " baz-" \ | |
| "Global:" \ | |
| " handler" \ | |
| " -bar" \ | |
| " Foo"] | |
| load_core_file | |
| gdb_test "python print(handler_call_log)" \ | |
| [string_to_regexp {['handler', '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-objfile-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_objfile.register_handler(pspace, log_handler(\"Foo\"))" \ | |
| [multi_line \ | |
| "RuntimeError.*: Handler Foo already exists\\." \ | |
| "Error occurred in Python.*"] | |
| gdb_test "python gdb.missing_objfile.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_objfile.register_handler(pspace, log_handler(\"Foo\"), replace=True)" | |
| gdb_test_no_output \ | |
| "python gdb.missing_objfile.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-objfile-handler progspace Foo" \ | |
| "^1 missing objfile handler disabled" | |
| gdb_test "python gdb.missing_objfile.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_objfile.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \ | |
| "can replace a disabled handler" | |
| } |