# 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 . 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 : 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" }