diff options
author | Andrew Burgess <aburgess@redhat.com> | 2024-07-31 15:55:57 +0100 |
---|---|---|
committer | Andrew Burgess <aburgess@redhat.com> | 2024-11-10 10:18:23 +0000 |
commit | 5cabc8098e65ac22d4245232ad20b19fa4729802 (patch) | |
tree | bdbda2463e12abd122031830602df12ed43f3a28 /gdb/testsuite/gdb.python | |
parent | ef1a41f20b7ea4799e09b37312dc051245435fd1 (diff) | |
download | gdb-5cabc8098e65ac22d4245232ad20b19fa4729802.zip gdb-5cabc8098e65ac22d4245232ad20b19fa4729802.tar.gz gdb-5cabc8098e65ac22d4245232ad20b19fa4729802.tar.bz2 |
gdb/python: implement Python find_exec_by_build_id hook
Implement extension_language_ops::find_objfile_from_buildid within
GDB's Python API. Doing this allows users to write Python extensions
that can help locate missing objfiles when GDB opens a core file. A
handler might perform some project- or site-specific actions to find a
missing objfile. Or might provide some project- or site-specific
advice to the user on how they can obtain the missing objfile.
The implementation is very similar to the approach taken in:
commit 8f6c452b5a4e50fbb55ff1d13328b392ad1fd416
Date: Sun Oct 15 22:48:42 2023 +0100
gdb: implement missing debug handler hook for Python
The following new commands are added as commands implemented in
Python, this is similar to how the Python missing debug and unwinder
commands are implemented:
info missing-objfile-handlers
enable missing-objfile-handler LOCUS HANDLER
disable missing-objfile-handler LOCUS HANDLER
To make use of this extension hook a user will create missing objfile
handler objects, and registers these handlers with GDB. When GDB
opens a core file and encounters a missing objfile each handler is
called in turn until one is able to help. Here is a minimal handler
that does nothing useful:
import gdb
import gdb.missing_objfile
class MyFirstHandler(gdb.missing_objfile.MissingObjfileHandler):
def __init__(self):
super().__init__("my_first_handler")
def __call__(self, pspace, build_id, filename):
# This handler does nothing useful.
return None
gdb.missing_objfile.register_handler(None, MyFirstHandler())
Returning None from the __call__ method tells GDB that this handler
was unable to find the missing objfile, and GDB should ask any other
registered handlers.
Possible return values from a handler:
- None: This means the handler couldn't help. GDB will call other
registered handlers to see if they can help instead.
- False: The handler has done all it can, but the objfile couldn't
be found. GDB will not call any other handlers, and will
continue without the objfile.
- True: The handler has installed the objfile into a location where
GDB would normally expect to find it. GDB should repeat its
normal lookup process and the objfile should now be found.
- A string: The handler can return a filename, which is the missing
objfile. GDB will load this file.
Handlers can be registered globally, or per program space. GDB checks
the handlers for the current program space first, and then all of the
global handles. The first handler that returns a value that is not
None, has "handled" the missing objfile, at which point GDB continues.
The implementation of this feature is mostly straight forward. I have
reworked some of the missing debug file related code so that it can be
shared with this feature. E.g. gdb/python/lib/gdb/missing_files.py is
mostly content moved from gdb/python/lib/gdb/missing_debug.py, but
updated to be more generic. Now gdb/python/lib/gdb/missing_debug.py
and the new file gdb/python/lib/gdb/missing_objfile.py both call into
the missing_files.py file.
For gdb/python/lib/gdb/command/missing_files.py this is even more
extreme, gdb/python/lib/gdb/command/missing_debug.py is completely
gone now and gdb/python/lib/gdb/command/missing_files.py provides all
of the new commands in a generic way.
I have made one change to the existing Python API, I renamed the
attribute Progspace.missing_debug_handlers to
Progspace.missing_file_handlers. I don't see this as too
problematic. This attribute was only used to implement the missing
debug feature and was never documented beyond the fact that it
existed. There was no reason for users to be touching this attribute.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Diffstat (limited to 'gdb/testsuite/gdb.python')
-rw-r--r-- | gdb/testsuite/gdb.python/py-missing-objfile-lib.c | 35 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-missing-objfile.c | 49 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-missing-objfile.exp | 565 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-missing-objfile.py | 167 |
4 files changed, 816 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.python/py-missing-objfile-lib.c b/gdb/testsuite/gdb.python/py-missing-objfile-lib.c new file mode 100644 index 0000000..8d740b4 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile-lib.c @@ -0,0 +1,35 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 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/>. */ + +struct lib_type +{ + int a; + int b; +}; + +volatile struct lib_type global_lib_var = { 0, 0 }; + +int +foo (void) +{ + int res = 0; + + res += global_lib_var.a; + res += global_lib_var.b; + + return 0; +} diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.c b/gdb/testsuite/gdb.python/py-missing-objfile.c new file mode 100644 index 0000000..953e1c0 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile.c @@ -0,0 +1,49 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 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/>. */ + +#include <stdlib.h> + +struct exec_type +{ + int a; + int b; + int c; +}; + +volatile struct exec_type global_exec_var = { 0, 0, 0 }; + +extern int foo (void); + +void +dump_core (void) +{ + abort (); +} + +int +main (void) +{ + int res = foo (); + + res += global_exec_var.a; + res += global_exec_var.b; + res += global_exec_var.c; + + dump_core (); + + return res; +} diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.exp b/gdb/testsuite/gdb.python/py-missing-objfile.exp new file mode 100644 index 0000000..15a6952 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile.exp @@ -0,0 +1,565 @@ +# 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 == ""} { + unsupport "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" +} diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.py b/gdb/testsuite/gdb.python/py-missing-objfile.py new file mode 100644 index 0000000..9b15896 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile.py @@ -0,0 +1,167 @@ +# 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/>. + +import shutil +import os +from enum import Enum + +import gdb +from gdb.missing_objfile import MissingObjfileHandler + +# A global log that is filled in by instances of the LOG_HANDLER class +# when they are called. +handler_call_log = [] + +# A global holding a string, the build-id of the last missing objfile +# which triggered the 'handler' class below. This is set in the +# __call__ method of the 'handler' class and then checked from the +# expect script. +handler_last_buildid = None + + +# A global holding a string, the filename of the last missing objfile +# which triggered the 'handler' class below. This is set in the +# __call__ method of the 'handler' class and then checked from the +# expect script. +handler_last_filename = None + + +# A helper function that makes some assertions about the arguments +# passed to a MissingObjfileHandler.__call__() method. +def check_args(pspace, buildid, filename): + assert type(filename) == str + assert filename != "" + assert type(pspace) == gdb.Progspace + assert type(buildid) == str + assert buildid != "" + + +# Enum used to configure the 'handler' class from the test script. +class Mode(Enum): + RETURN_NONE = 0 + RETURN_TRUE = 1 + RETURN_FALSE = 2 + RETURN_STRING = 3 + + +# A missing objfile handler which can be configured to return each of +# the different possible return types. +class handler(MissingObjfileHandler): + def __init__(self): + super().__init__("handler") + self._call_count = 0 + self._mode = Mode.RETURN_NONE + + def __call__(self, pspace, buildid, filename): + global handler_call_log, handler_last_buildid, handler_last_filename + check_args(pspace, buildid, filename) + handler_call_log.append(self.name) + handler_last_buildid = buildid + handler_last_filename = filename + self._call_count += 1 + if self._mode == Mode.RETURN_NONE: + return None + + if self._mode == Mode.RETURN_TRUE: + shutil.copy(self._src, self._dest) + + # If we're using the fission-dwp board then there will + # also be a .dwp file that needs to be copied. + dwp_src = self._src + ".dwp" + if os.path.exists(dwp_src): + dwp_dest = self._dest + ".dwp" + shutil.copy(dwp_src, dwp_dest) + + return True + + if self._mode == Mode.RETURN_FALSE: + return False + + if self._mode == Mode.RETURN_STRING: + return self._dest + + assert False + + @property + def call_count(self): + """Return a count, the number of calls to __call__ since the last + call to set_mode. + """ + return self._call_count + + def set_mode(self, mode, *args): + self._call_count = 0 + self._mode = mode + + if mode == Mode.RETURN_NONE: + assert len(args) == 0 + return + + if mode == Mode.RETURN_TRUE: + assert len(args) == 2 + self._src = args[0] + self._dest = args[1] + return + + if mode == Mode.RETURN_FALSE: + assert len(args) == 0 + return + + if mode == Mode.RETURN_STRING: + assert len(args) == 1 + self._dest = args[0] + return + + assert False + + +# A missing objfile handler which raises an exception. The type of +# exception to be raised is configured from the test script. +class exception_handler(MissingObjfileHandler): + def __init__(self): + super().__init__("exception_handler") + self.exception_type = None + + def __call__(self, pspace, buildid, filename): + global handler_call_log + check_args(pspace, buildid, filename) + handler_call_log.append(self.name) + assert self.exception_type is not None + raise self.exception_type("message") + + +# A very simple logging missing objfile handler. Always returns None +# so that GDB will try any other registered handlers, but first logs +# the name of this handler into the global HANDLER_CALL_LOG, which can +# then be checked from the test script. +class log_handler(MissingObjfileHandler): + def __call__(self, pspace, buildid, filename): + global handler_call_log + check_args(pspace, buildid, filename) + handler_call_log.append(self.name) + return None + + +# A basic helper function, this keeps lines shorter in the TCL script. +def register(name, locus=None): + gdb.missing_objfile.register_handler(locus, log_handler(name)) + + +# Create instances of the handlers, but don't install any. We install +# these as needed from the TCL script. +rhandler = exception_handler() +handler_obj = handler() + +print("Success") |