diff options
Diffstat (limited to 'gdb/python')
-rw-r--r-- | gdb/python/lib/gdb/__init__.py | 110 | ||||
-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.py | 161 | ||||
-rw-r--r-- | gdb/python/lib/gdb/missing_files.py | 204 | ||||
-rw-r--r-- | gdb/python/lib/gdb/missing_objfile.py | 67 | ||||
-rw-r--r-- | gdb/python/py-progspace.c | 26 | ||||
-rw-r--r-- | gdb/python/python.c | 108 |
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. |