| # 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/>. |
| |
| """ |
| MissingDebugHandler base class, and register_handler function. |
| """ |
| |
| import gdb |
| import sys |
| |
| 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: |
| # Fall back to curses.ascii.isascii() and curses.ascii.isalnum() for |
| # earlier versions. |
| from curses.ascii import isascii, isalnum |
| |
| |
| 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): |
| """Base class for missing debug handlers written in Python. |
| |
| A missing debug 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 __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. |
| |
| Arguments: |
| objfile: A gdb.Objfile for which GDB could not find any |
| debug information. |
| |
| Returns: |
| True: GDB should try again to locate the debug information |
| for objfile, the handler may have installed the |
| missing information. |
| False: GDB should move on without the debug information |
| for objfile. |
| A string: GDB should load the file at the given path; it |
| contains the debug information for objfile. |
| None: This handler can't help with objfile. GDB should |
| try any other registered handlers. |
| """ |
| raise NotImplementedError("MissingDebugHandler.__call__()") |
| |
| |
| 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) |