Home | History | Annotate | Line # | Download | only in gdb.python
      1 # Copyright (C) 2023-2024 Free Software Foundation, Inc.
      2 
      3 # This program is free software; you can redistribute it and/or modify
      4 # it under the terms of the GNU General Public License as published by
      5 # the Free Software Foundation; either version 3 of the License, or
      6 # (at your option) any later version.
      7 #
      8 # This program is distributed in the hope that it will be useful,
      9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11 # GNU General Public License for more details.
     12 #
     13 # You should have received a copy of the GNU General Public License
     14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15 
     16 load_lib gdb-python.exp
     17 
     18 require allow_python_tests
     19 
     20 standard_testfile
     21 
     22 if {[build_executable "failed to prepare" ${testfile} ${srcfile}]} {
     23     return -1
     24 }
     25 
     26 # Remove debug information from BINFILE and place it into
     27 # BINFILE.debug.
     28 if {[gdb_gnu_strip_debug $binfile]} {
     29     unsupported "cannot produce separate debug info files"
     30     return -1
     31 }
     32 
     33 set remote_python_file \
     34     [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
     35 
     36 set debug_filename ${binfile}.debug
     37 set hidden_filename ${binfile}.hidden
     38 
     39 # Start GDB.
     40 clean_restart
     41 
     42 # Some initial sanity checks; initially, we can find the debug information
     43 # (this will use the .gnu_debuglink), then after we move the debug
     44 # information, reload the executable, now the debug can't be found.
     45 with_test_prefix "initial checks" {
     46     # Load BINFILE, we should find the separate debug information.
     47     gdb_file_cmd $binfile
     48     gdb_assert {$gdb_file_cmd_debug_info == "debug"} \
     49 	"debug info is found"
     50 
     51     # Rename the debug information file, re-load BINFILE, GDB should fail
     52     # to find the debug information
     53     remote_exec build "mv $debug_filename $hidden_filename"
     54     gdb_file_cmd $binfile
     55     gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
     56 	"debug info no longer found"
     57 }
     58 
     59 # Load the Python script into GDB.
     60 gdb_test "source $remote_python_file" "^Success" \
     61     "source python script"
     62 
     63 # Setup the separate debug info directory.  This isn't actually needed until
     64 # some of the later tests, but might as well get this done now.
     65 set debug_directory [standard_output_file "debug-dir"]
     66 remote_exec build "mkdir -p $debug_directory"
     67 gdb_test_no_output "set debug-file-directory $debug_directory" \
     68     "set debug-file-directory"
     69 
     70 # Initially the missing debug handler we install is in a mode where it
     71 # returns None, indicating that it can't help locate the debug information.
     72 # Check this works as expected.
     73 with_test_prefix "handler returning None" {
     74     gdb_test_no_output \
     75 	"python gdb.missing_debug.register_handler(None, handler_obj)" \
     76 	"register the initial handler"
     77 
     78     gdb_file_cmd $binfile
     79     gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
     80 	"debug info not found"
     81 
     82     # Check the handler was only called once.
     83     gdb_test "python print(handler_obj.call_count)" "^1" \
     84 	"check handler was only called once"
     85 }
     86 
     87 # Now configure the handler to move the debug file back to the
     88 # .gnu_debuglink location and then return True, this will cause GDB to
     89 # recheck, at which point it should find the debug info.
     90 with_test_prefix "handler in gnu_debuglink mode" {
     91     gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
     92 						    \"$hidden_filename\", \
     93 						    \"$debug_filename\")" \
     94 	"confirgure handler"
     95     gdb_file_cmd $binfile
     96     gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
     97 
     98     # Check the handler was only called once.
     99     gdb_test "python print(handler_obj.call_count)" "^1" \
    100 	"check handler was only called once"
    101 }
    102 
    103 # Setup a directory structure based on the build-id of BINFILE, but don't
    104 # move the debug information into place just yet.
    105 #
    106 # Instead, configure the handler to move the debug info into the build-id
    107 # directory.
    108 #
    109 # Reload BINFILE, at which point the handler will move the debug info into
    110 # the build-id directory and return True, GDB will then recheck for the
    111 # debug information, and should find it.
    112 with_test_prefix "handler in build-id mode" {
    113     # Move the debug file out of the way once more.
    114     remote_exec build "mv $debug_filename $hidden_filename"
    115 
    116     # Create the build-id based directory in which the debug information
    117     # will be placed.
    118     set build_id_filename \
    119 	$debug_directory/[build_id_debug_filename_get $binfile]
    120     remote_exec build "mkdir -p [file dirname $build_id_filename]"
    121 
    122     # Configure the handler to move the debug info into the build-id dir.
    123     gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
    124 						    \"$hidden_filename\", \
    125 						    \"$build_id_filename\")" \
    126 	"confirgure handler"
    127 
    128     # Reload the binary and check the debug information is found.
    129     gdb_file_cmd $binfile
    130     gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
    131 
    132     # Check the handler was only called once.
    133     gdb_test "python print(handler_obj.call_count)" "^1" \
    134 	"check handler was only called once"
    135 }
    136 
    137 # Move the debug information back to a hidden location and configure the
    138 # handler to return the filename of the hidden debug info location.  GDB
    139 # should immediately use this file as the debug information.
    140 with_test_prefix "handler returning a string" {
    141     remote_exec build "mv $build_id_filename $hidden_filename"
    142 
    143     # Configure the handler return a filename string.
    144     gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \
    145 						    \"$hidden_filename\")" \
    146 	"confirgure handler"
    147 
    148     # Reload the binary and check the debug information is found.
    149     gdb_file_cmd $binfile
    150     gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
    151 
    152     # Check the handler was only called once.
    153     gdb_test "python print(handler_obj.call_count)" "^1" \
    154 	"check handler was only called once"
    155 }
    156 
    157 # Register another global handler, this one raises an exception.  Reload the
    158 # debug information, the bad handler should be invoked first, which raises
    159 # an excetption, at which point GDB should skip further Python handlers.
    160 with_test_prefix "handler raises an exception" {
    161     gdb_test_no_output \
    162 	"python gdb.missing_debug.register_handler(None, rhandler)"
    163 
    164     foreach_with_prefix exception_type {gdb.GdbError TypeError} {
    165 	gdb_test_no_output \
    166 	    "python rhandler.exception_type = $exception_type"
    167 
    168 	gdb_file_cmd $binfile
    169 	gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
    170 	    "debug info not found"
    171 
    172 	set re [string_to_regexp \
    173 		    "Python Exception <class '$exception_type'>: message"]
    174 	gdb_assert {[regexp $re $gdb_file_cmd_msg]} \
    175 	    "check for exception in file command output"
    176 
    177 	# Our original handler is still registered, but should not have been
    178 	# called again (as the exception occurs first).
    179 	gdb_test "python print(handler_obj.call_count)" "^1" \
    180 	    "check good handler hasn't been called again"
    181     }
    182 }
    183 
    184 gdb_test "info missing-debug-handlers" \
    185     [multi_line \
    186 	 "Global:" \
    187 	 "  exception_handler" \
    188 	 "  handler"] \
    189     "check both handlers are visible"
    190 
    191 # Re-start GDB.
    192 clean_restart
    193 
    194 # Load the Python script into GDB.
    195 gdb_test "source $remote_python_file" "^Success" \
    196     "source python script for bad handler name checks"
    197 
    198 # Attempt to register a missing-debug-handler with NAME.  The expectation is
    199 # that this should fail as NAME contains some invalid characters.
    200 proc check_bad_name {name} {
    201     set name_re [string_to_regexp $name]
    202     set re \
    203 	[multi_line \
    204 	     "ValueError.*: invalid character '.' in handler name: $name_re" \
    205 	     "Error occurred in Python.*"]
    206 
    207     gdb_test "python register(\"$name\")" $re \
    208 	"check that '$name' is not accepted"
    209 }
    210 
    211 # We don't attempt to be exhaustive here, just check a few random examples
    212 # of invalid names.
    213 check_bad_name "!! Bad Name"
    214 check_bad_name "Bad Name"
    215 check_bad_name "(Bad Name)"
    216 check_bad_name "Bad \[Name\]"
    217 check_bad_name "Bad,Name"
    218 check_bad_name "Bad;Name"
    219 
    220 # Check that there are no handlers registered.
    221 gdb_test_no_output "info missing-debug-handlers" \
    222     "check no handlers are registered"
    223 
    224 # Check we can use the enable/disable commands where there are no handlers
    225 # registered.
    226 gdb_test "enable missing-debug-handler foo" \
    227     "^0 missing debug handlers enabled"
    228 gdb_test "disable missing-debug-handler foo" \
    229     "^0 missing debug handlers disabled"
    230 
    231 # Grab the current program space object, used for registering handler later.
    232 gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
    233 
    234 # Now register some handlers.
    235 foreach hspec {{\"Foo\" None}
    236     {\"-bar\" None}
    237     {\"baz-\" pspace}
    238     {\"abc-def\" pspace}} {
    239     lassign $hspec name locus
    240     gdb_test "python register($name, $locus)"
    241 }
    242 
    243 with_test_prefix "all handlers enabled" {
    244     gdb_test "info missing-debug-handlers" \
    245 	[multi_line \
    246 	     "Current Progspace:" \
    247 	     "  abc-def" \
    248 	     "  baz-" \
    249 	     "Global:" \
    250 	     "  -bar" \
    251 	     "  Foo"]
    252 
    253     gdb_file_cmd $binfile
    254     gdb_test "python print(handler_call_log)" \
    255 	[string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo']}]
    256     gdb_test_no_output "python handler_call_log = \[\]" \
    257 	"reset call log"
    258 }
    259 
    260 with_test_prefix "disable 'baz-'" {
    261     gdb_test "disable missing-debug-handler progspace baz-" \
    262 	"^1 missing debug handler disabled"
    263 
    264     gdb_test "info missing-debug-handlers" \
    265 	[multi_line \
    266 	     "Progspace \[^\r\n\]+:" \
    267 	     "  abc-def" \
    268 	     "  baz- \\\[disabled\\\]" \
    269 	     "Global:" \
    270 	     "  -bar" \
    271 	     "  Foo"]
    272 
    273     gdb_file_cmd $binfile
    274     gdb_test "python print(handler_call_log)" \
    275 	[string_to_regexp {['abc-def', '-bar', 'Foo']}]
    276     gdb_test_no_output "python handler_call_log = \[\]" \
    277 	"reset call log"
    278 }
    279 
    280 with_test_prefix "disable 'Foo'" {
    281     gdb_test "disable missing-debug-handler .* Foo" \
    282 	"^1 missing debug handler disabled"
    283 
    284     gdb_test "info missing-debug-handlers" \
    285 	[multi_line \
    286 	     "Progspace \[^\r\n\]+:" \
    287 	     "  abc-def" \
    288 	     "  baz- \\\[disabled\\\]" \
    289 	     "Global:" \
    290 	     "  -bar" \
    291 	     "  Foo \\\[disabled\\\]"]
    292 
    293     gdb_file_cmd $binfile
    294     gdb_test "python print(handler_call_log)" \
    295 	[string_to_regexp {['abc-def', '-bar']}]
    296     gdb_test_no_output "python handler_call_log = \[\]" \
    297 	"reset call log"
    298 }
    299 
    300 with_test_prefix "disable everything" {
    301     gdb_test "disable missing-debug-handler .* .*" \
    302 	"^2 missing debug handlers disabled"
    303 
    304     gdb_test "info missing-debug-handlers" \
    305 	[multi_line \
    306 	     "Progspace \[^\r\n\]+:" \
    307 	     "  abc-def \\\[disabled\\\]" \
    308 	     "  baz- \\\[disabled\\\]" \
    309 	     "Global:" \
    310 	     "  -bar \\\[disabled\\\]" \
    311 	     "  Foo \\\[disabled\\\]"]
    312 
    313     gdb_file_cmd $binfile
    314     gdb_test "python print(handler_call_log)" \
    315 	[string_to_regexp {[]}]
    316     gdb_test_no_output "python handler_call_log = \[\]" \
    317 	"reset call log"
    318 }
    319 
    320 with_test_prefix "enable 'abc-def'" {
    321     set re [string_to_regexp $binfile]
    322 
    323     gdb_test "enable missing-debug-handler \"$re\" abc-def" \
    324 	"^1 missing debug handler enabled" \
    325 	"enable missing-debug-handler"
    326 
    327     gdb_test "info missing-debug-handlers" \
    328 	[multi_line \
    329 	     "Progspace \[^\r\n\]+:" \
    330 	     "  abc-def" \
    331 	     "  baz- \\\[disabled\\\]" \
    332 	     "Global:" \
    333 	     "  -bar \\\[disabled\\\]" \
    334 	     "  Foo \\\[disabled\\\]"]
    335 
    336     gdb_file_cmd $binfile
    337     gdb_test "python print(handler_call_log)" \
    338 	[string_to_regexp {['abc-def']}]
    339     gdb_test_no_output "python handler_call_log = \[\]" \
    340 	"reset call log"
    341 }
    342 
    343 with_test_prefix "enable global handlers" {
    344     set re [string_to_regexp $binfile]
    345 
    346     gdb_test "enable missing-debug-handler global" \
    347 	"^2 missing debug handlers enabled"
    348 
    349     gdb_test "info missing-debug-handlers" \
    350 	[multi_line \
    351 	     "Progspace \[^\r\n\]+:" \
    352 	     "  abc-def" \
    353 	     "  baz- \\\[disabled\\\]" \
    354 	     "Global:" \
    355 	     "  -bar" \
    356 	     "  Foo"]
    357 
    358     gdb_file_cmd $binfile
    359     gdb_test "python print(handler_call_log)" \
    360 	[string_to_regexp {['abc-def', '-bar', 'Foo']}]
    361     gdb_test_no_output "python handler_call_log = \[\]" \
    362 	"reset call log"
    363 }
    364 
    365 # Add handler_obj to the global handler list, and configure it to
    366 # return False.  We should call all of the program space specific
    367 # handlers (which return None), and then call handler_obj from the
    368 # global list, which returns False, at which point we shouldn't call
    369 # anyone else.
    370 with_test_prefix "return False handler in progspace list" {
    371     gdb_test "enable missing-debug-handler progspace" \
    372 	"^1 missing debug handler enabled"
    373 
    374     gdb_test_no_output \
    375 	"python gdb.missing_debug.register_handler(None, handler_obj)" \
    376 	"register the initial handler"
    377 
    378     gdb_test "info missing-debug-handlers" \
    379 	[multi_line \
    380 	     "Progspace \[^\r\n\]+:" \
    381 	     "  abc-def" \
    382 	     "  baz-" \
    383 	     "Global:" \
    384 	     "  handler" \
    385 	     "  -bar" \
    386 	     "  Foo"]
    387 
    388     gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \
    389 	"confirgure handler"
    390 
    391     gdb_file_cmd $binfile
    392     gdb_test "python print(handler_call_log)" \
    393 	[string_to_regexp {['abc-def', 'baz-', 'handler']}]
    394     gdb_test_no_output "python handler_call_log = \[\]" \
    395 	"reset call log"
    396 }
    397 
    398 # Now add handler_obj to the current program space's handler list.  We
    399 # use the same handler object here, that's fine.  We should only see a
    400 # call to the first handler object in the call log.
    401 with_test_prefix "return False handler in global list" {
    402     gdb_test_no_output \
    403 	"python gdb.missing_debug.register_handler(pspace, handler_obj)" \
    404 	"register the initial handler"
    405 
    406     gdb_test "info missing-debug-handlers" \
    407 	[multi_line \
    408 	     "Progspace \[^\r\n\]+:" \
    409 	     "  handler" \
    410 	     "  abc-def" \
    411 	     "  baz-" \
    412 	     "Global:" \
    413 	     "  handler" \
    414 	     "  -bar" \
    415 	     "  Foo"]
    416 
    417     gdb_file_cmd $binfile
    418     gdb_test "python print(handler_call_log)" \
    419 	[string_to_regexp {['handler']}]
    420     gdb_test_no_output "python handler_call_log = \[\]" \
    421 	"reset call log"
    422 }
    423 
    424 with_test_prefix "check handler replacement" {
    425     # First, check we can have the same name appear in both program
    426     # space and global lists without giving an error.
    427     gdb_test_no_output "python register(\"Foo\", pspace)"
    428 
    429     gdb_test "info missing-debug-handlers" \
    430 	[multi_line \
    431 	     "Progspace \[^\r\n\]+:" \
    432 	     "  Foo" \
    433 	     "  handler" \
    434 	     "  abc-def" \
    435 	     "  baz-" \
    436 	     "Global:" \
    437 	     "  handler" \
    438 	     "  -bar" \
    439 	     "  Foo"]
    440 
    441     # Now check that we get an error if we try to add a handler with
    442     # the same name.
    443     gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
    444 	[multi_line \
    445 	     "RuntimeError.*: Handler Foo already exists\\." \
    446 	     "Error occurred in Python.*"]
    447 
    448     gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \
    449 	[multi_line \
    450 	     "RuntimeError.*: Handler Foo already exists\\." \
    451 	     "Error occurred in Python.*"]
    452 
    453     # And now try again, but this time with 'replace=True', we
    454     # shouldn't get an error in this case.
    455     gdb_test_no_output \
    456 	"python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)"
    457 
    458     gdb_test_no_output \
    459 	"python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)"
    460 
    461     # Now disable a handler and check we still need to use 'replace=True'.
    462     gdb_test "disable missing-debug-handler progspace Foo" \
    463 	"^1 missing debug handler disabled"
    464 
    465     gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
    466 	[multi_line \
    467 	     "RuntimeError.*: Handler Foo already exists\\." \
    468 	     "Error occurred in Python.*"] \
    469 	"still get an error when handler is disabled"
    470 
    471     gdb_test_no_output \
    472 	"python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \
    473 	"can replace a disabled handler"
    474 }
    475