blob: 9f24db761e3ec001bab15a63528b89134b5f1179 [file] [log] [blame]
# 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))