| # Copyright (C) 2023-2025 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)) |