aboutsummaryrefslogtreecommitdiff
path: root/gdb/python
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/python')
-rw-r--r--gdb/python/lib/gdb/__init__.py110
-rw-r--r--gdb/python/lib/gdb/command/missing_files.py (renamed from gdb/python/lib/gdb/command/missing_debug.py)135
-rw-r--r--gdb/python/lib/gdb/missing_debug.py161
-rw-r--r--gdb/python/lib/gdb/missing_files.py204
-rw-r--r--gdb/python/lib/gdb/missing_objfile.py67
-rw-r--r--gdb/python/py-progspace.c26
-rw-r--r--gdb/python/python.c108
7 files changed, 581 insertions, 230 deletions
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 6c3e241..146a963 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -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)
+ )
diff --git a/gdb/python/lib/gdb/command/missing_debug.py b/gdb/python/lib/gdb/command/missing_files.py
index 313b88c..463853b 100644
--- a/gdb/python/lib/gdb/command/missing_debug.py
+++ b/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()
diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py
index 7ccc4fe..2c2ceba 100644
--- a/gdb/python/lib/gdb/missing_debug.py
+++ b/gdb/python/lib/gdb/missing_debug.py
@@ -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)
diff --git a/gdb/python/lib/gdb/missing_files.py b/gdb/python/lib/gdb/missing_files.py
new file mode 100644
index 0000000..5f2df88c
--- /dev/null
+++ b/gdb/python/lib/gdb/missing_files.py
@@ -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))
diff --git a/gdb/python/lib/gdb/missing_objfile.py b/gdb/python/lib/gdb/missing_objfile.py
new file mode 100644
index 0000000..ace0e13
--- /dev/null
+++ b/gdb/python/lib/gdb/missing_objfile.py
@@ -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)
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index aa1e713..bb44aa4 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -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 }
};
diff --git a/gdb/python/python.c b/gdb/python/python.c
index cceb7aa..b0de48d 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -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.