@@ -56,6 +56,17 @@
** Added gdb.record.clear. Clears the trace data of the current recording.
This forces re-decoding of the trace for successive commands.
+ ** New module gdb.missing_objfile that facilitates dealing with
+ missing objfiles when opening a core-file.
+
+ ** New function gdb.missing_objfile.register_handler that can
+ register an instance of a sub-class of
+ gdb.missing_debug.MissingObjfileHandler as a handler for missing
+ objfiles.
+
+ ** New class gdb.missing_objfile.MissingObjfileHandler which can be
+ sub-classed to create handlers for missing objfiles.
+
* Debugger Adapter Protocol changes
** The "scopes" request will now return a scope holding global
@@ -83,6 +94,19 @@ maintenance info blocks [ADDRESS]
are listed starting at the inner global block out to the most inner
block.
+info missing-objfile-handlers
+ List all the registered missing-objfile handlers.
+
+enable missing-objfile-handler LOCUS HANDLER
+disable missing-objfile-handler LOCUS HANDLER
+ Enable or disable a missing-objfile handler with a name matching the
+ regular expression HANDLER, in LOCUS.
+
+ LOCUS can be 'global' to operate on global missing-objfile handler,
+ 'progspace' to operate on handlers within the current program space,
+ or can be a regular expression which is matched against the filename
+ of the primary executable in each program space.
+
* Changed commands
remove-symbol-file
@@ -77,6 +77,8 @@ PYTHON_FILE_LIST = \
gdb/FrameIterator.py \
gdb/frames.py \
gdb/missing_debug.py \
+ gdb/missing_objfile.py \
+ gdb/missing_files.py \
gdb/printing.py \
gdb/prompt.py \
gdb/ptwrite.py \
@@ -87,7 +89,7 @@ PYTHON_FILE_LIST = \
gdb/command/__init__.py \
gdb/command/explore.py \
gdb/command/frame_filters.py \
- gdb/command/missing_debug.py \
+ gdb/command/missing_files.py \
gdb/command/pretty_printers.py \
gdb/command/prompt.py \
gdb/command/type_printers.py \
@@ -21792,6 +21792,7 @@
@c (eg rooted in val of env var GDBSYMS) could exist for mappable symbol
@c files.
+@anchor{core-file command}
@kindex core-file
@item core-file @r{[}@var{filename}@r{]}
@itemx core
@@ -231,6 +231,7 @@
* TUI Windows In Python:: Implementing new TUI windows.
* Disassembly In Python:: Instruction Disassembly In Python
* Missing Debug Info In Python:: Handle missing debug info from Python.
+* Missing Objfiles In Python:: Handle objfiles from Python.
@end menu
@node Basic Python
@@ -5388,10 +5389,11 @@
objects. @xref{Frame Filter API}, for more information.
@end defvar
-@defvar Progspace.missing_debug_handlers
-The @code{missing_debug_handlers} attribute is a list of the missing
-debug handler objects for this program space. @xref{Missing Debug
-Info In Python}, for more information.
+@defvar Progspace.missing_file_handlers
+The @code{missing_file_handlers} attribute is a list of tuples. Each
+tuple holds a missing-file handler object for this program space. For
+more information, @pxref{Missing Debug Info In Python}, and
+@ref{Missing Objfiles In Python}.
@end defvar
A program space has the following methods:
@@ -5567,6 +5569,7 @@
@code{gdb.Objfile.add_separate_debug_file} method, described below.
@end defvar
+@anchor{Objfile.build_id}
@defvar Objfile.build_id
The build ID of the objfile as a string.
If the objfile does not have a build ID then the value is @code{None}.
@@ -8155,6 +8158,189 @@
for this objfile.
@end defun
+@node Missing Objfiles In Python
+@subsubsection Missing Objfiles In Python
+@cindex python, handle missing objfiles
+
+When @value{GDBN} opens a core file, for example with the
+@kbd{core-file} command (@pxref{core-file command}), @value{GDBN} will
+attempt to load the corresponding executable and shared libraries.
+Often these files can be found on the local machine, but sometimes
+these files cannot be found, in which case the debugging experience
+will be restricted.
+
+If @value{GDBN} fails to locate a particular file then there is an
+opportunity for a Python extension to step in. A Python extension can
+potentially locate the missing file using some platform- or
+project-specific steps, and inform @value{GDBN} of its location. Or a
+Python extension might provide some platform- or project-specific
+advice to the user about how to obtain the missing file.
+
+A missing-objfile Python extension consists of a handler object which
+has the @code{name} and @code{enabled} attributes, and implements the
+@code{__call__} method. When @value{GDBN} encounters a situation
+where a file cannot be found, but the build-id (@pxref{build ID}) for
+the missing file is known, then the @code{__call__} method is invoked
+to try and find the file. Full details of how handlers are written
+can be found below.
+
+@subheading The @code{gdb.missing_objfile} Module
+
+@value{GDBN} comes with a @code{gdb.missing_objfile} module which
+contains the following class and global function:
+
+@deftp{class} gdb.missing_objfile.MissingObjfileHandler
+
+@code{MissingObjfileHandler} is a base class from which user-created
+handlers can derive, though it is not required that handlers derive
+from this class, so long as any user created handler has the
+@code{name} and @code{enabled} attributes, and implements the
+@code{__call__} method.
+
+@defun MissingObjfileHandler.__init__ (name)
+The @var{name} is a string used to reference this missing-objfile
+handler within some @value{GDBN} commands. Valid names consist of the
+characters @samp{[-_a-zA-Z0-9]}, creating a handler with an invalid
+name raises a @code{ValueError} exception.
+@end defun
+
+@defun MissingObjfileHandler.__call__ (pspace, build_id, filename)
+
+Sub-classes must override the @code{__call__} method. The
+@var{pspace} argument will be a @code{gdb.Progspace}
+(@pxref{Progspaces In Python}), this is the program space in which
+@value{GDBN} is looking for the missing file.
+
+The @var{build_id} argument is a string containing the build-id of the
+file that is missing, this will be in the same format as returned by
+@code{Objfile.build_id} (@pxref{Objfile.build_id}).
+
+The @var{filename} argument contains the name of the file that
+@value{GDBN} is looking for. This information is provided to allow
+handlers to generate informative messages for the user. A handler is
+not required to place the missing file at this location. There might
+already be a file present at this location, but it might not match the
+required build-id, in which case @value{GDBN} will have ignored it.
+In some limited cases @value{GDBN} might not be able to establish the
+@var{filename} of the file it is searching for, in this case
+@value{GDBN} will use a string @samp{with build-id @var{build_id}} as a
+replacement.
+
+The return value from the @code{__call__} method indicates what
+@value{GDBN} should do next. The possible return values are:
+
+@itemize @bullet
+@item @code{None}
+
+This indicates that this handler could not locate the missing file and
+@value{GDBN} should call any other registered handlers.
+
+@item @code{True}
+
+This indicates that this handler has installed the missing file into a
+location where @value{GDBN} would normally expect to find it. The
+only location in which @value{GDBN} will look is within the
+@file{.build-id} sub-directory within the @var{debug-file-directory}
+(@pxref{debug-file-directory}).
+
+@value{GDBN} will repeat the normal lookup process, which should now
+find the previously missing file.
+
+If @value{GDBN} still doesn't find file after this second attempt,
+then the Python missing-objfile handlers are not invoked a second
+time, this prevents a badly behaved handler causing @value{GDBN} to
+get stuck in a loop. @value{GDBN} will continue without the missing
+file, though this will degrade the debugging experience.
+
+@item @code{False}
+
+This indicates that this handler has done everything that it intends
+to do but the missing file could not be found. @value{GDBN} will not
+call any other registered handlers to look for the missing file.
+@value{GDBN} will continue without the missing file, though this will
+degrade the debugging experience.
+
+@item A string
+
+The returned string should contain a filename. @value{GDBN} will not
+call any further registered handlers, and will instead use the
+returned filename as the missing file.
+@end itemize
+
+Invoking the @code{__call__} method from this base class will raise a
+@code{NotImplementedError} exception.
+@end defun
+
+@defvar MissingObjfileHandler.name
+A read-only attribute which is a string, the name of this handler
+passed to the @code{__init__} method.
+@end defvar
+
+@defvar MissingObjfileHandler.enabled
+A modifiable attribute containing a boolean; when @code{True}, the
+handler is enabled, and will be used by @value{GDBN}. When
+@code{False}, the handler has been disabled, and will not be used.
+@end defvar
+@end deftp
+
+@defun gdb.missing_objfile.register_handler (locus, handler, replace=@code{False})
+Register a new missing-objfile handler with @value{GDBN}.
+
+@var{handler} is an instance of a sub-class of
+@code{MissingObjfileHandler}, or at least an instance of an object that
+has the same attributes and methods as @code{MissingObjfileHandler}.
+
+@var{locus} specifies to which handler list to prepend @var{handler}.
+It can be either a @code{gdb.Progspace} (@pxref{Progspaces In Python})
+or @code{None}, in which case the handler is registered globally. The
+newly registered @var{handler} will be called before any other handler
+from the same locus. Two handlers in the same locus cannot have the
+same name, an attempt to add a handler with an already existing name
+raises an exception unless @var{replace} is @code{True}, in which case
+the old handler is deleted and the new handler is prepended to the
+selected handler list.
+
+@value{GDBN} first calls the handlers for the current program space,
+and then the globally registered handlers. As soon as a handler
+returns a value other than @code{None}, no further handlers are
+called.
+@end defun
+
+@subheading Managing Missing-Objfile Handlers
+
+@value{GDBN} defines the following commands to manage registered
+missing-objfile handlers:
+
+@table @code
+
+@kindex info missing-objfile-handlers
+@item info missing-objfile-handlers @r{[} @var{locus} @r{[} @var{name-regexp} @r{]} @r{]}
+Lists all registered missing-objfile handlers. Arguments @var{locus}
+and @var{name-regexp} are both optional and can be used to filter
+which handlers are listed.
+
+The @var{locus} argument should be either @kbd{global},
+@kbd{progspace}, or the name of an object file. Only handlers
+registered for the specified locus will be listed.
+
+The @var{name-regexp} is a regular expression used to match against
+handler names.
+
+@kindex disable missing-objfile-handler
+@item disable missing-objfile-handler @r{[} @var{locus} @r{[} @var{name-regexp} @r{]} @r{]}
+The @var{locus} and @var{name-regexp} are interpreted as in @kbd{info
+missing-objfile-handlers} above, but instead of listing the matching
+handlers, all of the matching handlers are disabled. The
+@code{enabled} field of each matching handler is set to @code{False}.
+
+@kindex enable missing-objfile-handler
+@item enable missing-objfile-handler @r{[} @var{locus} @r{[} @var{name-regexp} @r{]} @r{]}
+The @var{locus} and @var{name-regexp} are interpreted as in @kbd{info
+missing-objfile-handlers} above, but instead of listing the matching
+handlers, all of the matching handlers are enabled. The
+@code{enabled} field of each matching handler is set to @code{True}.
+@end table
+
@node Python Auto-loading
@subsection Python Auto-loading
@cindex Python auto-loading
@@ -87,8 +87,9 @@ xmethods = []
frame_filters = {}
# Initial frame unwinders.
frame_unwinders = []
-# Initial missing debug handlers.
-missing_debug_handlers = []
+# The missing file handlers. Each item is a tuple with the form
+# (TYPE, HANDLER) where TYPE is a string either 'debug' or 'objfile'.
+missing_file_handlers = []
def _execute_unwinders(pending_frame):
@@ -271,6 +272,61 @@ class Thread(threading.Thread):
super().start()
+def _filter_missing_file_handlers(handlers, handler_type):
+ """Each list of missing file handlers is a list of tuples, the first
+ item in the tuple is a string either 'debug' or 'objfile' to
+ indicate what type of handler it is. The second item in the tuple
+ is the actual handler object.
+
+ This function takes HANDLER_TYPE which is a string, either 'debug'
+ or 'objfile' and HANDLERS, a list of tuples. The function returns
+ an iterable over all of the handler objects (extracted from the
+ tuples) which match HANDLER_TYPE.
+ """
+
+ return map(lambda t: t[1], filter(lambda t: t[0] == handler_type, handlers))
+
+
+def _handle_missing_files(pspace, handler_type, cb):
+ """Helper for _handle_missing_debuginfo and _handle_missing_objfile.
+
+ Arguments:
+ pspace: The gdb.Progspace in which we're operating. Used to
+ lookup program space specific handlers.
+ handler_type: A string, either 'debug' or 'objfile', this is the
+ type of handler we're looking for.
+ cb: A callback which takes a handler and returns the result of
+ calling the handler.
+
+ Returns:
+ None: No suitable file could be found.
+ False: A handler has decided that the requested file cannot be
+ found, and no further searching should be done.
+ True: The file has been found and installed in a location
+ where GDB would normally look for it. GDB should
+ repeat its lookup process, the file should now be in
+ place.
+ A string: This is the filename of where the missing file can
+ be found.
+ """
+
+ for handler in _filter_missing_file_handlers(
+ pspace.missing_file_handlers, handler_type
+ ):
+ if handler.enabled:
+ result = cb(handler)
+ if result is not None:
+ return result
+
+ for handler in _filter_missing_file_handlers(missing_file_handlers, handler_type):
+ if handler.enabled:
+ result = cb(handler)
+ if result is not None:
+ return result
+
+ return None
+
+
def _handle_missing_debuginfo(objfile):
"""Internal function called from GDB to execute missing debug
handlers.
@@ -293,18 +349,46 @@ def _handle_missing_debuginfo(objfile):
A string: This is the filename of a file containing the
required debug information.
"""
+
pspace = objfile.progspace
- for handler in pspace.missing_debug_handlers:
- if handler.enabled:
- result = handler(objfile)
- if result is not None:
- return result
+ return _handle_missing_files(pspace, "debug", lambda h: h(objfile))
- for handler in missing_debug_handlers:
- if handler.enabled:
- result = handler(objfile)
- if result is not None:
- return result
- return None
+def _handle_missing_objfile(pspace, buildid, filename):
+ """Internal function called from GDB to execute missing objfile
+ handlers.
+
+ Run each of the currently registered, and enabled missing objfile
+ handler objects for the gdb.Progspace passed in as an argument,
+ and then from the global list. Stop after the first handler that
+ returns a result other than None.
+
+ Arguments:
+ pspace: A gdb.Progspace for which the missing objfile handlers
+ should be run. This is the program space in which an
+ objfile was found to be missing.
+ buildid: A string containing the build-id we're looking for.
+ filename: The filename of the file GDB tried to find but
+ couldn't. This is not where the file should be
+ placed if found, in fact, this file might already
+ exist on disk but have the wrong build-id. This is
+ mostly provided in order to be used in messages to
+ the user.
+
+ Returns:
+ None: No objfile could be found for this build-id.
+ False: A handler has done all it can with for this build-id,
+ but no objfile could be found.
+ True: An objfile might have been installed by a handler, GDB
+ should check again. The only place GDB checks is within
+ the .build-id sub-directory within the
+ debug-file-directory. If the required file was not
+ installed there then GDB will not find it.
+ A string: This is the filename of a file containing the
+ missing objfile.
+ """
+
+ return _handle_missing_files(
+ pspace, "objfile", lambda h: h(pspace, buildid, filename)
+ )
similarity index 54%
rename from gdb/python/lib/gdb/command/missing_debug.py
rename to gdb/python/lib/gdb/command/missing_files.py
@@ -1,4 +1,4 @@
-# Missing debug related commands.
+# Missing debug and objfile related commands.
#
# Copyright 2023-2024 Free Software Foundation, Inc.
#
@@ -21,7 +21,7 @@ import gdb
def validate_regexp(exp, idstring):
- """Compile exp into a compiler regular expression object.
+ """Compile exp into a compiled regular expression object.
Arguments:
exp: The string to compile into a re.Pattern object.
@@ -33,14 +33,15 @@ def validate_regexp(exp, idstring):
Raises:
SyntaxError: If exp is an invalid regexp.
"""
+
try:
return re.compile(exp)
except SyntaxError:
raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
-def parse_missing_debug_command_args(arg):
- """Internal utility to parse missing debug handler command argv.
+def parse_missing_file_command_args(arg):
+ """Internal utility to parse missing file handler command argv.
Arguments:
arg: The arguments to the command. The format is:
@@ -52,6 +53,7 @@ def parse_missing_debug_command_args(arg):
Raises:
SyntaxError: an error processing ARG
"""
+
argv = gdb.string_to_argv(arg)
argc = len(argv)
if argc > 2:
@@ -68,10 +70,10 @@ def parse_missing_debug_command_args(arg):
)
-class InfoMissingDebugHanders(gdb.Command):
- """GDB command to list missing debug handlers.
+class InfoMissingFileHandlers(gdb.Command):
+ """GDB command to list missing HTYPE handlers.
- Usage: info missing-debug-handlers [LOCUS-REGEXP [NAME-REGEXP]]
+ Usage: info missing-HTYPE-handlers [LOCUS-REGEXP [NAME-REGEXP]]
LOCUS-REGEXP is a regular expression matching the location of the
handler. If it is omitted, all registered handlers from all
@@ -79,38 +81,47 @@ class InfoMissingDebugHanders(gdb.Command):
the handlers from the current progspace, or a regular expression
matching filenames of progspaces.
- NAME-REGEXP is a regular expression to filter missing debug
+ NAME-REGEXP is a regular expression to filter missing HTYPE
handler names. If this omitted for a specified locus, then all
registered handlers in the locus are listed.
"""
- def __init__(self):
- super().__init__("info missing-debug-handlers", gdb.COMMAND_FILES)
+ def __init__(self, handler_type):
+ # Update the doc string before calling the parent constructor,
+ # replacing the string 'HTYPE' with the value of HANDLER_TYPE.
+ # The parent constructor will grab a copy of this string to
+ # use as the commands help text.
+ self.__doc__ = self.__doc__.replace("HTYPE", handler_type)
+ super().__init__(
+ "info missing-" + handler_type + "-handlers", gdb.COMMAND_FILES
+ )
+ self.handler_type = handler_type
def list_handlers(self, title, handlers, name_re):
- """Lists the missing debug handlers whose name matches regexp.
+ """Lists the missing file handlers whose name matches regexp.
Arguments:
title: The line to print before the list.
- handlers: The list of the missing debug handlers.
+ handlers: The list of the missing file handlers.
name_re: handler name filter.
"""
+
if not handlers:
return
print(title)
- for handler in handlers:
+ for handler in gdb._filter_missing_file_handlers(handlers, self.handler_type):
if name_re.match(handler.name):
print(
" %s%s" % (handler.name, "" if handler.enabled else " [disabled]")
)
def invoke(self, arg, from_tty):
- locus_re, name_re = parse_missing_debug_command_args(arg)
+ locus_re, name_re = parse_missing_file_command_args(arg)
if locus_re.match("progspace") and locus_re.pattern != "":
cp = gdb.current_progspace()
self.list_handlers(
- "Progspace %s:" % cp.filename, cp.missing_debug_handlers, name_re
+ "Progspace %s:" % cp.filename, cp.missing_file_handlers, name_re
)
for progspace in gdb.progspaces():
@@ -125,58 +136,71 @@ class InfoMissingDebugHanders(gdb.Command):
msg = "Progspace %s:" % filename
self.list_handlers(
msg,
- progspace.missing_debug_handlers,
+ progspace.missing_file_handlers,
name_re,
)
# Print global handlers last, as these are invoked last.
if locus_re.match("global"):
- self.list_handlers("Global:", gdb.missing_debug_handlers, name_re)
+ self.list_handlers("Global:", gdb.missing_file_handlers, name_re)
-def do_enable_handler1(handlers, name_re, flag):
- """Enable/disable missing debug handlers whose names match given regex.
+def do_enable_handler1(handlers, name_re, flag, handler_type):
+ """Enable/disable missing file handlers whose names match given regex.
Arguments:
- handlers: The list of missing debug handlers.
+ handlers: The list of missing file handlers.
name_re: Handler name filter.
flag: A boolean indicating if we should enable or disable.
+ handler_type: A string, either 'debug' or 'objfile', use to control
+ which handlers are modified.
Returns:
The number of handlers affected.
"""
+
total = 0
- for handler in handlers:
+ for handler in gdb._filter_missing_file_handlers(handlers, handler_type):
if name_re.match(handler.name) and handler.enabled != flag:
handler.enabled = flag
total += 1
return total
-def do_enable_handler(arg, flag):
- """Enable or disable missing debug handlers."""
- (locus_re, name_re) = parse_missing_debug_command_args(arg)
+def do_enable_handler(arg, flag, handler_type):
+ """Enable or disable missing file handlers."""
+
+ (locus_re, name_re) = parse_missing_file_command_args(arg)
total = 0
if locus_re.match("global"):
- total += do_enable_handler1(gdb.missing_debug_handlers, name_re, flag)
+ total += do_enable_handler1(
+ gdb.missing_file_handlers, name_re, flag, handler_type
+ )
if locus_re.match("progspace") and locus_re.pattern != "":
total += do_enable_handler1(
- gdb.current_progspace().missing_debug_handlers, name_re, flag
+ gdb.current_progspace().missing_file_handlers, name_re, flag, handler_type
)
for progspace in gdb.progspaces():
filename = progspace.filename or ""
if locus_re.match(filename):
- total += do_enable_handler1(progspace.missing_debug_handlers, name_re, flag)
+ total += do_enable_handler1(
+ progspace.missing_file_handlers, name_re, flag, handler_type
+ )
print(
- "%d missing debug handler%s %s"
- % (total, "" if total == 1 else "s", "enabled" if flag else "disabled")
+ "%d missing %s handler%s %s"
+ % (
+ total,
+ handler_type,
+ "" if total == 1 else "s",
+ "enabled" if flag else "disabled",
+ )
)
-class EnableMissingDebugHandler(gdb.Command):
- """GDB command to enable missing debug handlers.
+class EnableMissingFileHandler(gdb.Command):
+ """GDB command to enable missing HTYPE handlers.
- Usage: enable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+ Usage: enable missing-HTYPE-handler [LOCUS-REGEXP [NAME-REGEXP]]
LOCUS-REGEXP is a regular expression specifying the handlers to
enable. It can be 'global', 'progspace' for the current
@@ -187,18 +211,26 @@ class EnableMissingDebugHandler(gdb.Command):
in the locus are affected.
"""
- def __init__(self):
- super().__init__("enable missing-debug-handler", gdb.COMMAND_FILES)
+ def __init__(self, handler_type):
+ # Update the doc string before calling the parent constructor,
+ # replacing the string 'HTYPE' with the value of HANDLER_TYPE.
+ # The parent constructor will grab a copy of this string to
+ # use as the commands help text.
+ self.__doc__ = self.__doc__.replace("HTYPE", handler_type)
+ super().__init__(
+ "enable missing-" + handler_type + "-handler", gdb.COMMAND_FILES
+ )
+ self.handler_type = handler_type
def invoke(self, arg, from_tty):
"""GDB calls this to perform the command."""
- do_enable_handler(arg, True)
+ do_enable_handler(arg, True, self.handler_type)
-class DisableMissingDebugHandler(gdb.Command):
- """GDB command to disable missing debug handlers.
+class DisableMissingFileHandler(gdb.Command):
+ """GDB command to disable missing HTYPE handlers.
- Usage: disable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+ Usage: disable missing-HTYPE-handler [LOCUS-REGEXP [NAME-REGEXP]]
LOCUS-REGEXP is a regular expression specifying the handlers to
enable. It can be 'global', 'progspace' for the current
@@ -209,19 +241,28 @@ class DisableMissingDebugHandler(gdb.Command):
in the locus are affected.
"""
- def __init__(self):
- super().__init__("disable missing-debug-handler", gdb.COMMAND_FILES)
+ def __init__(self, handler_type):
+ # Update the doc string before calling the parent constructor,
+ # replacing the string 'HTYPE' with the value of HANDLER_TYPE.
+ # The parent constructor will grab a copy of this string to
+ # use as the commands help text.
+ self.__doc__ = self.__doc__.replace("HTYPE", handler_type)
+ super().__init__(
+ "disable missing-" + handler_type + "-handler", gdb.COMMAND_FILES
+ )
+ self.handler_type = handler_type
def invoke(self, arg, from_tty):
"""GDB calls this to perform the command."""
- do_enable_handler(arg, False)
+ do_enable_handler(arg, False, self.handler_type)
-def register_missing_debug_handler_commands():
- """Installs the missing debug handler commands."""
- InfoMissingDebugHanders()
- EnableMissingDebugHandler()
- DisableMissingDebugHandler()
+def register_missing_file_handler_commands():
+ """Installs the missing file handler commands."""
+ for handler_type in ["debug", "objfile"]:
+ InfoMissingFileHandlers(handler_type)
+ EnableMissingFileHandler(handler_type)
+ DisableMissingFileHandler(handler_type)
-register_missing_debug_handler_commands()
+register_missing_file_handler_commands()
@@ -17,72 +17,11 @@
MissingDebugHandler base class, and register_handler function.
"""
-import sys
-
import gdb
+from gdb.missing_files import MissingFileHandler
-if sys.version_info >= (3, 7):
- # Functions str.isascii() and str.isalnum are available starting Python
- # 3.7.
- def isascii(ch):
- return ch.isascii()
-
- def isalnum(ch):
- return ch.isalnum()
-
-else:
- # Older version of Python doesn't have str.isascii() and
- # str.isalnum() so provide our own.
- #
- # We could import isalnum() and isascii() from the curses library,
- # but that adds an extra dependency. Given these functions are
- # both small and trivial lets implement them here.
- #
- # These definitions are based on those in the curses library, but
- # simplified as we know C will always be a single character 'str'.
-
- def isdigit(c):
- return 48 <= ord(c) <= 57
-
- def islower(c):
- return 97 <= ord(c) <= 122
-
- def isupper(c):
- return 65 <= ord(c) <= 90
-
- def isalpha(c):
- return isupper(c) or islower(c)
-
- def isalnum(c):
- return isalpha(c) or isdigit(c)
-
- def isascii(c):
- return 0 <= ord(c) <= 127
-
-def _validate_name(name):
- """Validate a missing debug handler name string.
-
- If name is valid as a missing debug handler name, then this
- function does nothing. If name is not valid then an exception is
- raised.
-
- Arguments:
- name: A string, the name of a missing debug handler.
-
- Returns:
- Nothing.
-
- Raises:
- ValueError: If name is invalid as a missing debug handler
- name.
- """
- for ch in name:
- if not isascii(ch) or not (isalnum(ch) or ch in "_-"):
- raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
-
-
-class MissingDebugHandler(object):
+class MissingDebugHandler(MissingFileHandler):
"""Base class for missing debug handlers written in Python.
A missing debug handler has a single method __call__ along with
@@ -93,41 +32,8 @@ class MissingDebugHandler(object):
enabled: When true this handler is enabled.
"""
- def __init__(self, name):
- """Constructor.
-
- Args:
- name: An identifying name for this handler.
-
- Raises:
- TypeError: name is not a string.
- ValueError: name contains invalid characters.
- """
-
- if not isinstance(name, str):
- raise TypeError("incorrect type for name: %s" % type(name))
-
- _validate_name(name)
-
- self._name = name
- self._enabled = True
-
- @property
- def name(self):
- return self._name
-
- @property
- def enabled(self):
- return self._enabled
-
- @enabled.setter
- def enabled(self, value):
- if not isinstance(value, bool):
- raise TypeError("incorrect type for enabled attribute: %s" % type(value))
- self._enabled = value
-
def __call__(self, objfile):
- """GDB handle missing debug information for an objfile.
+ """Handle missing debug information for an objfile.
Arguments:
objfile: A gdb.Objfile for which GDB could not find any
@@ -148,62 +54,5 @@ class MissingDebugHandler(object):
def register_handler(locus, handler, replace=False):
- """Register handler in given locus.
-
- The handler is prepended to the locus's missing debug handlers
- list. The name of handler should be unique (or replace must be
- True).
-
- Arguments:
- locus: Either a progspace, or None (in which case the unwinder
- is registered globally).
- handler: An object of a gdb.MissingDebugHandler subclass.
-
- replace: If True, replaces existing handler with the same name
- within locus. Otherwise, raises RuntimeException if
- unwinder with the same name already exists.
-
- Returns:
- Nothing.
-
- Raises:
- RuntimeError: The name of handler is not unique.
- TypeError: Bad locus type.
- AttributeError: Required attributes of handler are missing.
- """
-
- if locus is None:
- if gdb.parameter("verbose"):
- gdb.write("Registering global %s handler ...\n" % handler.name)
- locus = gdb
- elif isinstance(locus, gdb.Progspace):
- if gdb.parameter("verbose"):
- gdb.write(
- "Registering %s handler for %s ...\n" % (handler.name, locus.filename)
- )
- else:
- raise TypeError("locus should be gdb.Progspace or None")
-
- # Some sanity checks on HANDLER. Calling getattr will raise an
- # exception if the attribute doesn't exist, which is what we want.
- # These checks are not exhaustive; we don't check the attributes
- # have the correct types, or the method has the correct signature,
- # but this should catch some basic mistakes.
- getattr(handler, "name")
- getattr(handler, "enabled")
- call_method = getattr(handler, "__call__")
- if not callable(call_method):
- raise AttributeError(
- "'%s' object's '__call__' attribute is not callable"
- % type(handler).__name__
- )
-
- i = 0
- for needle in locus.missing_debug_handlers:
- if needle.name == handler.name:
- if replace:
- del locus.missing_debug_handlers[i]
- else:
- raise RuntimeError("Handler %s already exists." % handler.name)
- i += 1
- locus.missing_debug_handlers.insert(0, handler)
+ """See gdb.missing_files.register_handler."""
+ gdb.missing_files.register_handler("debug", locus, handler, replace)
new file mode 100644
@@ -0,0 +1,204 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+MissingFileHandler base class, and support functions used by the
+missing_debug.py and missing_objfile.py modules.
+"""
+
+import sys
+
+import gdb
+
+if sys.version_info >= (3, 7):
+ # Functions str.isascii() and str.isalnum are available starting Python
+ # 3.7.
+ def isascii(ch):
+ return ch.isascii()
+
+ def isalnum(ch):
+ return ch.isalnum()
+
+else:
+ # Older version of Python doesn't have str.isascii() and
+ # str.isalnum() so provide our own.
+ #
+ # We could import isalnum() and isascii() from the curses library,
+ # but that adds an extra dependency. Given these functions are
+ # both small and trivial lets implement them here.
+ #
+ # These definitions are based on those in the curses library, but
+ # simplified as we know C will always be a single character 'str'.
+
+ def isdigit(c):
+ return 48 <= ord(c) <= 57
+
+ def islower(c):
+ return 97 <= ord(c) <= 122
+
+ def isupper(c):
+ return 65 <= ord(c) <= 90
+
+ def isalpha(c):
+ return isupper(c) or islower(c)
+
+ def isalnum(c):
+ return isalpha(c) or isdigit(c)
+
+ def isascii(c):
+ return 0 <= ord(c) <= 127
+
+
+def _validate_name(name):
+ """Validate a missing file handler name string.
+
+ If name is valid as a missing file handler name, then this
+ function does nothing. If name is not valid then an exception is
+ raised.
+
+ Arguments:
+ name: A string, the name of a missing file handler.
+
+ Returns:
+ Nothing.
+
+ Raises:
+ ValueError: If name is invalid as a missing file handler
+ name.
+ """
+
+ for ch in name:
+ if not isascii(ch) or not (isalnum(ch) or ch in "_-"):
+ raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
+
+
+class MissingFileHandler(object):
+ """Base class for missing file handlers written in Python.
+
+ A missing file handler has a single method __call__ along with the
+ read/write attribute enabled, and a read-only attribute name. The
+ attributes are provided by this class while the __call__ method is
+ provided by a sub-class. Each sub-classes __call__ method will
+ have a different signature.
+
+ Attributes:
+ name: Read-only attribute, the name of this handler.
+ enabled: When true this handler is enabled.
+ """
+
+ def __init__(self, name):
+ """Constructor.
+
+ Args:
+ name: An identifying name for this handler.
+
+ Raises:
+ TypeError: name is not a string.
+ ValueError: name contains invalid characters.
+ """
+
+ if not isinstance(name, str):
+ raise TypeError("incorrect type for name: %s" % type(name))
+
+ _validate_name(name)
+
+ self._name = name
+ self._enabled = True
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, value):
+ if not isinstance(value, bool):
+ raise TypeError("incorrect type for enabled attribute: %s" % type(value))
+ self._enabled = value
+
+
+def register_handler(handler_type, locus, handler, replace=False):
+ """Register handler in given locus.
+
+ The handler is prepended to the locus's missing file handlers
+ list. The name of handler should be unique (or replace must be
+ True), and the name must pass the _validate_name check.
+
+ Arguments:
+ handler_type: A string, either 'debug' or 'objfile' indicating the
+ type of handler to be registered.
+ locus: Either a progspace, or None (in which case the unwinder
+ is registered globally).
+ handler: An object used as a missing file handler. Usually a
+ sub-class of MissingFileHandler.
+ replace: If True, replaces existing handler with the same name
+ within locus. Otherwise, raises RuntimeException if
+ unwinder with the same name already exists.
+
+ Returns:
+ Nothing.
+
+ Raises:
+ RuntimeError: The name of handler is not unique.
+ TypeError: Bad locus type.
+ AttributeError: Required attributes of handler are missing.
+ ValueError: If the name of the handler is invalid, or if
+ handler_type is neither 'debug' or 'objfile'.
+ """
+
+ if handler_type != "debug" and handler_type != "objfile":
+ raise ValueError("handler_type must be 'debug' or 'objfile'")
+
+ if locus is None:
+ if gdb.parameter("verbose"):
+ gdb.write("Registering global %s handler ...\n" % handler.name)
+ locus = gdb
+ elif isinstance(locus, gdb.Progspace):
+ if gdb.parameter("verbose"):
+ gdb.write(
+ "Registering %s handler for %s ...\n" % (handler.name, locus.filename)
+ )
+ else:
+ raise TypeError("locus should be gdb.Progspace or None")
+
+ # Some sanity checks on HANDLER. Calling getattr will raise an
+ # exception if the attribute doesn't exist, which is what we want.
+ # These checks are not exhaustive; we don't check the attributes
+ # have the correct types, or the method has the correct signature,
+ # but this should catch some basic mistakes.
+ name = getattr(handler, "name")
+ _validate_name(name)
+
+ getattr(handler, "enabled")
+
+ call_method = getattr(handler, "__call__")
+ if not callable(call_method):
+ raise AttributeError(
+ "'%s' object's '__call__' attribute is not callable"
+ % type(handler).__name__
+ )
+
+ i = 0
+ for needle in locus.missing_file_handlers:
+ if needle[0] == handler_type and needle[1].name == handler.name:
+ if replace:
+ del locus.missing_file_handlers[i]
+ else:
+ raise RuntimeError("Handler %s already exists." % handler.name)
+ i += 1
+ locus.missing_file_handlers.insert(0, (handler_type, handler))
new file mode 100644
@@ -0,0 +1,67 @@
+# 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/>.
+
+"""
+MissingObjfileHandler base class, and register_handler function.
+"""
+
+import gdb
+from gdb.missing_files import MissingFileHandler
+
+
+class MissingObjfileHandler(MissingFileHandler):
+ """Base class for missing objfile handlers written in Python.
+
+ A missing objfile handler has a single method __call__ along with
+ the read/write attribute enabled, and a read-only attribute name.
+
+ Attributes:
+ name: Read-only attribute, the name of this handler.
+ enabled: When true this handler is enabled.
+ """
+
+ def __call__(self, buildid, filename):
+ """Handle a missing objfile when GDB can knows the build-id.
+
+ Arguments:
+
+ buildid: A string containing the build-id for the objfile
+ GDB is searching for.
+ filename: A string containing the name of the file GDB is
+ searching for. This is provided only for the purpose
+ of creating diagnostic messages. If the file is found
+ it does not have to be placed here, and this file
+ might already exist but GDB has determined it is not
+ suitable for use, e.g. if the build-id doesn't match.
+
+ Returns:
+
+ True: GDB should try again to locate the missing objfile,
+ the handler may have installed the missing file.
+ False: GDB should move on without the objfile. The
+ handler has determined that this objfile is not
+ available.
+ A string: GDB should load the file at the given path; it
+ contains the requested objfile.
+ None: This handler can't help with this objfile. GDB
+ should try any other registered handlers.
+
+ """
+ raise NotImplementedError("MissingObjfileHandler.__call__()")
+
+
+def register_handler(locus, handler, replace=False):
+ """See gdb.missing_files.register_handler."""
+ gdb.missing_files.register_handler("objfile", locus, handler, replace)
@@ -55,8 +55,8 @@ struct pspace_object
/* The debug method list. */
PyObject *xmethods;
- /* The missing debug handler list. */
- PyObject *missing_debug_handlers;
+ /* The missing file handler list. */
+ PyObject *missing_file_handlers;
};
extern PyTypeObject pspace_object_type
@@ -166,7 +166,7 @@ pspy_dealloc (PyObject *self)
Py_XDECREF (ps_self->frame_unwinders);
Py_XDECREF (ps_self->type_printers);
Py_XDECREF (ps_self->xmethods);
- Py_XDECREF (ps_self->missing_debug_handlers);
+ Py_XDECREF (ps_self->missing_file_handlers);
Py_TYPE (self)->tp_free (self);
}
@@ -202,8 +202,8 @@ pspy_initialize (pspace_object *self)
if (self->xmethods == NULL)
return 0;
- self->missing_debug_handlers = PyList_New (0);
- if (self->missing_debug_handlers == nullptr)
+ self->missing_file_handlers = PyList_New (0);
+ if (self->missing_file_handlers == nullptr)
return 0;
return 1;
@@ -349,18 +349,18 @@ pspy_get_xmethods (PyObject *o, void *ignore)
/* Return the list of missing debug handlers for this program space. */
static PyObject *
-pspy_get_missing_debug_handlers (PyObject *o, void *ignore)
+pspy_get_missing_file_handlers (PyObject *o, void *ignore)
{
pspace_object *self = (pspace_object *) o;
- Py_INCREF (self->missing_debug_handlers);
- return self->missing_debug_handlers;
+ Py_INCREF (self->missing_file_handlers);
+ return self->missing_file_handlers;
}
/* Set this program space's list of missing debug handlers to HANDLERS. */
static int
-pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers,
+pspy_set_missing_file_handlers (PyObject *o, PyObject *handlers,
void *ignore)
{
pspace_object *self = (pspace_object *) o;
@@ -380,9 +380,9 @@ pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers,
}
/* Take care in case the LHS and RHS are related somehow. */
- gdbpy_ref<> tmp (self->missing_debug_handlers);
+ gdbpy_ref<> tmp (self->missing_file_handlers);
Py_INCREF (handlers);
- self->missing_debug_handlers = handlers;
+ self->missing_file_handlers = handlers;
return 0;
}
@@ -778,8 +778,8 @@ static gdb_PyGetSetDef pspace_getset[] =
"Type printers.", NULL },
{ "xmethods", pspy_get_xmethods, NULL,
"Debug methods.", NULL },
- { "missing_debug_handlers", pspy_get_missing_debug_handlers,
- pspy_set_missing_debug_handlers, "Missing debug handlers.", NULL },
+ { "missing_file_handlers", pspy_get_missing_file_handlers,
+ pspy_set_missing_file_handlers, "Missing file handlers.", NULL },
{ NULL }
};
@@ -35,6 +35,7 @@
#include "location.h"
#include "run-on-main-thread.h"
#include "observable.h"
+#include "build-id.h"
#if GDB_SELF_TEST
#include "gdbsupport/selftest.h"
@@ -130,6 +131,9 @@ static std::optional<std::string> gdbpy_colorize_disasm
(const std::string &content, gdbarch *gdbarch);
static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo
(const struct extension_language_defn *extlang, struct objfile *objfile);
+static ext_lang_missing_file_result gdbpy_find_objfile_from_buildid
+ (const struct extension_language_defn *extlang, program_space *pspace,
+ const struct bfd_build_id *build_id, const char *missing_filename);
/* The interface between gdb proper and loading of python scripts. */
@@ -179,7 +183,8 @@ static const struct extension_language_ops python_extension_ops =
gdbpy_print_insn,
- gdbpy_handle_missing_debuginfo
+ gdbpy_handle_missing_debuginfo,
+ gdbpy_find_objfile_from_buildid
};
#endif /* HAVE_PYTHON */
@@ -1829,6 +1834,107 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang,
return ext_lang_missing_file_result (std::string (filename.get ()));
}
+/* Implement the find_objfile_from_buildid hook for Python. PSPACE is the
+ program space in which GDB is trying to find an objfile, BUILD_ID is the
+ build-id for the missing objfile, and EXPECTED_FILENAME is a non-NULL
+ string which can be used (if needed) in messages to the user, and
+ represents the file GDB is looking for. */
+
+static ext_lang_missing_file_result
+gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang,
+ program_space *pspace,
+ const struct bfd_build_id *build_id,
+ const char *missing_filename)
+{
+ gdb_assert (pspace != nullptr);
+ gdb_assert (build_id != nullptr);
+ gdb_assert (missing_filename != nullptr);
+
+ /* Early exit if Python is not initialised. */
+ if (!gdb_python_initialized || gdb_python_module == nullptr)
+ return {};
+
+ gdbpy_enter enter_py;
+
+ /* Convert BUILD_ID into a Python object. */
+ std::string hex_form = bin2hex (build_id->data, build_id->size);
+ gdbpy_ref<> pyo_buildid = host_string_to_python_string (hex_form.c_str ());
+ if (pyo_buildid == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ /* Convert MISSING_FILENAME to a Python object. */
+ gdbpy_ref<> pyo_filename = host_string_to_python_string (missing_filename);
+ if (pyo_filename == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ /* Convert PSPACE to a Python object. */
+ gdbpy_ref<> pyo_pspace = pspace_to_pspace_object (pspace);
+ if (pyo_pspace == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ /* Lookup the helper function within the GDB module. */
+ gdbpy_ref<> pyo_handler
+ (PyObject_GetAttrString (gdb_python_module, "_handle_missing_objfile"));
+ if (pyo_handler == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ /* Call the function, passing in the Python objfile object. */
+ gdbpy_ref<> pyo_execute_ret
+ (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_pspace.get (),
+ pyo_buildid.get (), pyo_filename.get (),
+ nullptr));
+ if (pyo_execute_ret == nullptr)
+ {
+ /* If the handler is cancelled due to a Ctrl-C, then propagate
+ the Ctrl-C as a GDB exception instead of swallowing it. */
+ gdbpy_print_stack_or_quit ();
+ return {};
+ }
+
+ /* Parse the result, and convert it back to the C++ object. */
+ if (pyo_execute_ret == Py_None)
+ return {};
+
+ if (PyBool_Check (pyo_execute_ret.get ()))
+ {
+ /* We know the value is a bool, so it must be either Py_True or
+ Py_False. Anything else would not get past the above check. */
+ bool try_again = pyo_execute_ret.get () == Py_True;
+ return ext_lang_missing_file_result (try_again);
+ }
+
+ if (!gdbpy_is_string (pyo_execute_ret.get ()))
+ {
+ PyErr_SetString (PyExc_ValueError,
+ "return value from _find_objfile_by_buildid should "
+ "be None, a bool, or a str");
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ gdb::unique_xmalloc_ptr<char> filename
+ = python_string_to_host_string (pyo_execute_ret.get ());
+ if (filename == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ return ext_lang_missing_file_result (std::string (filename.get ()));
+}
+
/* Compute the list of active python type printers and store them in
EXT_PRINTERS->py_type_printers. The product of this function is used by
gdbpy_apply_type_printers, and freed by gdbpy_free_type_printers.
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -0,0 +1,544 @@
+# 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
+
+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
+}
+
+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"
+
+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"
+}
new file mode 100644
@@ -0,0 +1,158 @@
+# 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
+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)
+ 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")