diff options
author | alk3pInjection <webmaster@raspii.tech> | 2024-02-04 16:16:35 +0800 |
---|---|---|
committer | alk3pInjection <webmaster@raspii.tech> | 2024-02-04 16:16:35 +0800 |
commit | abdaadbcae30fe0c9a66c7516798279fdfd97750 (patch) | |
tree | 00a54a6e25601e43876d03c1a4a12a749d4a914c /share/gdb/python |
https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
Change-Id: I7303388733328cd98ab9aa3c30236db67f2e9e9c
Diffstat (limited to 'share/gdb/python')
25 files changed, 4822 insertions, 0 deletions
diff --git a/share/gdb/python/gdb/FrameDecorator.py b/share/gdb/python/gdb/FrameDecorator.py new file mode 100644 index 0000000..82be4fc --- /dev/null +++ b/share/gdb/python/gdb/FrameDecorator.py @@ -0,0 +1,300 @@ +# Copyright (C) 2013-2023 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/>. + +import gdb + + +class FrameDecorator(object): + """Basic implementation of a Frame Decorator""" + + """ This base frame decorator decorates a frame or another frame + decorator, and provides convenience methods. If this object is + wrapping a frame decorator, defer to that wrapped object's method + if it has one. This allows for frame decorators that have + sub-classed FrameDecorator object, but also wrap other frame + decorators on the same frame to correctly execute. + + E.g + + If the result of frame filters running means we have one gdb.Frame + wrapped by multiple frame decorators, all sub-classed from + FrameDecorator, the resulting hierarchy will be: + + Decorator1 + -- (wraps) Decorator2 + -- (wraps) FrameDecorator + -- (wraps) gdb.Frame + + In this case we have two frame decorators, both of which are + sub-classed from FrameDecorator. If Decorator1 just overrides the + 'function' method, then all of the other methods are carried out + by the super-class FrameDecorator. But Decorator2 may have + overriden other methods, so FrameDecorator will look at the + 'base' parameter and defer to that class's methods. And so on, + down the chain.""" + + # 'base' can refer to a gdb.Frame or another frame decorator. In + # the latter case, the child class will have called the super + # method and _base will be an object conforming to the Frame Filter + # class. + def __init__(self, base): + self._base = base + + @staticmethod + def _is_limited_frame(frame): + """Internal utility to determine if the frame is special or + limited.""" + sal = frame.find_sal() + + if ( + not sal.symtab + or not sal.symtab.filename + or frame.type() == gdb.DUMMY_FRAME + or frame.type() == gdb.SIGTRAMP_FRAME + ): + + return True + + return False + + def elided(self): + """Return any elided frames that this class might be + wrapping, or None.""" + if hasattr(self._base, "elided"): + return self._base.elided() + + return None + + def function(self): + """Return the name of the frame's function or an address of + the function of the frame. First determine if this is a + special frame. If not, try to determine filename from GDB's + frame internal function API. Finally, if a name cannot be + determined return the address. If this function returns an + address, GDB will attempt to determine the function name from + its internal minimal symbols store (for example, for inferiors + without debug-info).""" + + # Both gdb.Frame, and FrameDecorator have a method called + # "function", so determine which object this is. + if not isinstance(self._base, gdb.Frame): + if hasattr(self._base, "function"): + # If it is not a gdb.Frame, and there is already a + # "function" method, use that. + return self._base.function() + + frame = self.inferior_frame() + + if frame.type() == gdb.DUMMY_FRAME: + return "<function called from gdb>" + elif frame.type() == gdb.SIGTRAMP_FRAME: + return "<signal handler called>" + + func = frame.function() + + # If we cannot determine the function name, return the + # address. If GDB detects an integer value from this function + # it will attempt to find the function name from minimal + # symbols via its own internal functions. + if func is None: + pc = frame.pc() + return pc + + return str(func) + + def address(self): + """Return the address of the frame's pc""" + + if hasattr(self._base, "address"): + return self._base.address() + + frame = self.inferior_frame() + return frame.pc() + + def filename(self): + """Return the filename associated with this frame, detecting + and returning the appropriate library name is this is a shared + library.""" + + if hasattr(self._base, "filename"): + return self._base.filename() + + frame = self.inferior_frame() + sal = frame.find_sal() + if not sal.symtab or not sal.symtab.filename: + pc = frame.pc() + return gdb.solib_name(pc) + else: + return sal.symtab.filename + + def frame_args(self): + """Return an iterable of frame arguments for this frame, if + any. The iterable object contains objects conforming with the + Symbol/Value interface. If there are no frame arguments, or + if this frame is deemed to be a special case, return None.""" + + if hasattr(self._base, "frame_args"): + return self._base.frame_args() + + frame = self.inferior_frame() + if self._is_limited_frame(frame): + return None + + args = FrameVars(frame) + return args.fetch_frame_args() + + def frame_locals(self): + """Return an iterable of local variables for this frame, if + any. The iterable object contains objects conforming with the + Symbol/Value interface. If there are no frame locals, or if + this frame is deemed to be a special case, return None.""" + + if hasattr(self._base, "frame_locals"): + return self._base.frame_locals() + + frame = self.inferior_frame() + if self._is_limited_frame(frame): + return None + + args = FrameVars(frame) + return args.fetch_frame_locals() + + def line(self): + """Return line number information associated with the frame's + pc. If symbol table/line information does not exist, or if + this frame is deemed to be a special case, return None""" + + if hasattr(self._base, "line"): + return self._base.line() + + frame = self.inferior_frame() + if self._is_limited_frame(frame): + return None + + sal = frame.find_sal() + if sal: + return sal.line + else: + return None + + def inferior_frame(self): + """Return the gdb.Frame underpinning this frame decorator.""" + + # If 'base' is a frame decorator, we want to call its inferior + # frame method. If '_base' is a gdb.Frame, just return that. + if hasattr(self._base, "inferior_frame"): + return self._base.inferior_frame() + return self._base + + +class SymValueWrapper(object): + """A container class conforming to the Symbol/Value interface + which holds frame locals or frame arguments.""" + + def __init__(self, symbol, value): + self.sym = symbol + self.val = value + + def value(self): + """Return the value associated with this symbol, or None""" + return self.val + + def symbol(self): + """Return the symbol, or Python text, associated with this + symbol, or None""" + return self.sym + + +class FrameVars(object): + + """Utility class to fetch and store frame local variables, or + frame arguments.""" + + def __init__(self, frame): + self.frame = frame + self.symbol_class = { + gdb.SYMBOL_LOC_STATIC: True, + gdb.SYMBOL_LOC_REGISTER: True, + gdb.SYMBOL_LOC_ARG: True, + gdb.SYMBOL_LOC_REF_ARG: True, + gdb.SYMBOL_LOC_LOCAL: True, + gdb.SYMBOL_LOC_REGPARM_ADDR: True, + gdb.SYMBOL_LOC_COMPUTED: True, + } + + def fetch_b(self, sym): + """Local utility method to determine if according to Symbol + type whether it should be included in the iterator. Not all + symbols are fetched, and only symbols that return + True from this method should be fetched.""" + + # SYM may be a string instead of a symbol in the case of + # synthetic local arguments or locals. If that is the case, + # always fetch. + if isinstance(sym, str): + return True + + sym_type = sym.addr_class + + return self.symbol_class.get(sym_type, False) + + def fetch_frame_locals(self): + """Public utility method to fetch frame local variables for + the stored frame. Frame arguments are not fetched. If there + are no frame local variables, return an empty list.""" + lvars = [] + + try: + block = self.frame.block() + except RuntimeError: + block = None + + while block is not None: + if block.is_global or block.is_static: + break + for sym in block: + if sym.is_argument: + continue + if self.fetch_b(sym): + lvars.append(SymValueWrapper(sym, None)) + + block = block.superblock + + return lvars + + def fetch_frame_args(self): + """Public utility method to fetch frame arguments for the + stored frame. Frame arguments are the only type fetched. If + there are no frame argument variables, return an empty list.""" + + args = [] + + try: + block = self.frame.block() + except RuntimeError: + block = None + + while block is not None: + if block.function is not None: + break + block = block.superblock + + if block is not None: + for sym in block: + if not sym.is_argument: + continue + args.append(SymValueWrapper(sym, None)) + + return args diff --git a/share/gdb/python/gdb/FrameIterator.py b/share/gdb/python/gdb/FrameIterator.py new file mode 100644 index 0000000..190ec94 --- /dev/null +++ b/share/gdb/python/gdb/FrameIterator.py @@ -0,0 +1,49 @@ +# Copyright (C) 2013-2023 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/>. + + +class FrameIterator(object): + """A gdb.Frame iterator. Iterates over gdb.Frames or objects that + conform to that interface.""" + + def __init__(self, frame_obj): + """Initialize a FrameIterator. + + Arguments: + frame_obj the starting frame.""" + + super(FrameIterator, self).__init__() + self.frame = frame_obj + + def __iter__(self): + return self + + def next(self): + """next implementation. + + Returns: + The next oldest frame.""" + + result = self.frame + if result is None: + raise StopIteration + self.frame = result.older() + return result + + # Python 3.x requires __next__(self) while Python 2.x requires + # next(self). Define next(self), and for Python 3.x create this + # wrapper. + def __next__(self): + return self.next() diff --git a/share/gdb/python/gdb/__init__.py b/share/gdb/python/gdb/__init__.py new file mode 100644 index 0000000..6f3f194 --- /dev/null +++ b/share/gdb/python/gdb/__init__.py @@ -0,0 +1,261 @@ +# Copyright (C) 2010-2023 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/>. + +import traceback +import os +import sys +import _gdb +from contextlib import contextmanager + +# Python 3 moved "reload" +if sys.version_info >= (3, 4): + from importlib import reload +else: + from imp import reload + +from _gdb import * + +# Historically, gdb.events was always available, so ensure it's +# still available without an explicit import. +import _gdbevents as events + +sys.modules["gdb.events"] = events + + +class _GdbFile(object): + # These two are needed in Python 3 + encoding = "UTF-8" + errors = "strict" + + def __init__(self, stream): + self.stream = stream + + def close(self): + # Do nothing. + return None + + def isatty(self): + return False + + def writelines(self, iterable): + for line in iterable: + self.write(line) + + def flush(self): + flush(stream=self.stream) + + def write(self, s): + write(s, stream=self.stream) + + +sys.stdout = _GdbFile(STDOUT) + +sys.stderr = _GdbFile(STDERR) + +# Default prompt hook does nothing. +prompt_hook = None + +# Ensure that sys.argv is set to something. +# We do not use PySys_SetArgvEx because it did not appear until 2.6.6. +sys.argv = [""] + +# Initial pretty printers. +pretty_printers = [] + +# Initial type printers. +type_printers = [] +# Initial xmethod matchers. +xmethods = [] +# Initial frame filters. +frame_filters = {} +# Initial frame unwinders. +frame_unwinders = [] + + +def _execute_unwinders(pending_frame): + """Internal function called from GDB to execute all unwinders. + + Runs each currently enabled unwinder until it finds the one that + can unwind given frame. + + Arguments: + pending_frame: gdb.PendingFrame instance. + + Returns: + Tuple with: + + [0] gdb.UnwindInfo instance + [1] Name of unwinder that claimed the frame (type `str`) + + or None, if no unwinder has claimed the frame. + """ + for objfile in objfiles(): + for unwinder in objfile.frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder(pending_frame) + if unwind_info is not None: + return (unwind_info, unwinder.name) + + for unwinder in current_progspace().frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder(pending_frame) + if unwind_info is not None: + return (unwind_info, unwinder.name) + + for unwinder in frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder(pending_frame) + if unwind_info is not None: + return (unwind_info, unwinder.name) + + return None + + +def _execute_file(filepath): + """This function is used to replace Python 2's PyRun_SimpleFile. + + Loads and executes the given file. + + We could use the runpy module, but its documentation says: + "Furthermore, any functions and classes defined by the executed code are + not guaranteed to work correctly after a runpy function has returned." + """ + globals = sys.modules["__main__"].__dict__ + set_file = False + # Set file (if not set) so that the imported file can use it (e.g. to + # access file-relative paths). This matches what PyRun_SimpleFile does. + if not hasattr(globals, "__file__"): + globals["__file__"] = filepath + set_file = True + try: + with open(filepath, "rb") as file: + # We pass globals also as locals to match what Python does + # in PyRun_SimpleFile. + compiled = compile(file.read(), filepath, "exec") + exec(compiled, globals, globals) + finally: + if set_file: + del globals["__file__"] + + +# Convenience variable to GDB's python directory +PYTHONDIR = os.path.dirname(os.path.dirname(__file__)) + +# Auto-load all functions/commands. + +# Packages to auto-load. + +packages = ["function", "command", "printer"] + +# pkgutil.iter_modules is not available prior to Python 2.6. Instead, +# manually iterate the list, collating the Python files in each module +# path. Construct the module name, and import. + + +def _auto_load_packages(): + for package in packages: + location = os.path.join(os.path.dirname(__file__), package) + if os.path.exists(location): + py_files = filter( + lambda x: x.endswith(".py") and x != "__init__.py", os.listdir(location) + ) + + for py_file in py_files: + # Construct from foo.py, gdb.module.foo + modname = "%s.%s.%s" % (__name__, package, py_file[:-3]) + try: + if modname in sys.modules: + # reload modules with duplicate names + reload(__import__(modname)) + else: + __import__(modname) + except: + sys.stderr.write(traceback.format_exc() + "\n") + + +_auto_load_packages() + + +def GdbSetPythonDirectory(dir): + """Update sys.path, reload gdb and auto-load packages.""" + global PYTHONDIR + + try: + sys.path.remove(PYTHONDIR) + except ValueError: + pass + sys.path.insert(0, dir) + + PYTHONDIR = dir + + # note that reload overwrites the gdb module without deleting existing + # attributes + reload(__import__(__name__)) + _auto_load_packages() + + +def current_progspace(): + "Return the current Progspace." + return selected_inferior().progspace + + +def objfiles(): + "Return a sequence of the current program space's objfiles." + return current_progspace().objfiles() + + +def solib_name(addr): + """solib_name (Long) -> String.\n\ +Return the name of the shared library holding a given address, or None.""" + return current_progspace().solib_name(addr) + + +def block_for_pc(pc): + "Return the block containing the given pc value, or None." + return current_progspace().block_for_pc(pc) + + +def find_pc_line(pc): + """find_pc_line (pc) -> Symtab_and_line. + Return the gdb.Symtab_and_line object corresponding to the pc value.""" + return current_progspace().find_pc_line(pc) + + +def set_parameter(name, value): + """Set the GDB parameter NAME to VALUE.""" + # Handle the specific cases of None and booleans here, because + # gdb.parameter can return them, but they can't be passed to 'set' + # this way. + if value is None: + value = "unlimited" + elif isinstance(value, bool): + if value: + value = "on" + else: + value = "off" + execute("set " + name + " " + str(value), to_string=True) + + +@contextmanager +def with_parameter(name, value): + """Temporarily set the GDB parameter NAME to VALUE. + Note that this is a context manager.""" + old_value = parameter(name) + set_parameter(name, value) + try: + # Nothing that useful to return. + yield None + finally: + set_parameter(name, old_value) diff --git a/share/gdb/python/gdb/command/__init__.py b/share/gdb/python/gdb/command/__init__.py new file mode 100644 index 0000000..5f369bf --- /dev/null +++ b/share/gdb/python/gdb/command/__init__.py @@ -0,0 +1,14 @@ +# Copyright (C) 2010-2023 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/>. diff --git a/share/gdb/python/gdb/command/explore.py b/share/gdb/python/gdb/command/explore.py new file mode 100644 index 0000000..db72f33 --- /dev/null +++ b/share/gdb/python/gdb/command/explore.py @@ -0,0 +1,785 @@ +# GDB 'explore' command. +# Copyright (C) 2012-2023 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/>. + +"""Implementation of the GDB 'explore' command using the GDB Python API.""" + +import gdb +import sys + + +class Explorer(object): + """Internal class which invokes other explorers.""" + + # This map is filled by the Explorer.init_env() function + type_code_to_explorer_map = {} + + _SCALAR_TYPE_LIST = ( + gdb.TYPE_CODE_CHAR, + gdb.TYPE_CODE_INT, + gdb.TYPE_CODE_BOOL, + gdb.TYPE_CODE_FLT, + gdb.TYPE_CODE_VOID, + gdb.TYPE_CODE_ENUM, + ) + + @staticmethod + def guard_expr(expr): + length = len(expr) + guard = False + + if expr[0] == "(" and expr[length - 1] == ")": + pass + else: + i = 0 + while i < length: + c = expr[i] + if ( + c == "_" + or ("a" <= c and c <= "z") + or ("A" <= c and c <= "Z") + or ("0" <= c and c <= "9") + ): + pass + else: + guard = True + break + i += 1 + + if guard: + return "(" + expr + ")" + else: + return expr + + @staticmethod + def explore_expr(expr, value, is_child): + """Main function to explore an expression value. + + Arguments: + expr: The expression string that is being explored. + value: The gdb.Value value of the expression. + is_child: Boolean value to indicate if the expression is a child. + An expression is a child if it is derived from the main + expression entered by the user. For example, if the user + entered an expression which evaluates to a struct, then + when exploring the fields of the struct, is_child is set + to True internally. + + Returns: + No return value. + """ + type_code = value.type.code + if type_code in Explorer.type_code_to_explorer_map: + explorer_class = Explorer.type_code_to_explorer_map[type_code] + while explorer_class.explore_expr(expr, value, is_child): + pass + else: + print("Explorer for type '%s' not yet available.\n" % str(value.type)) + + @staticmethod + def explore_type(name, datatype, is_child): + """Main function to explore a data type. + + Arguments: + name: The string representing the path to the data type being + explored. + datatype: The gdb.Type value of the data type being explored. + is_child: Boolean value to indicate if the name is a child. + A name is a child if it is derived from the main name + entered by the user. For example, if the user entered + the name of struct type, then when exploring the fields + of the struct, is_child is set to True internally. + + Returns: + No return value. + """ + type_code = datatype.code + if type_code in Explorer.type_code_to_explorer_map: + explorer_class = Explorer.type_code_to_explorer_map[type_code] + while explorer_class.explore_type(name, datatype, is_child): + pass + else: + print("Explorer for type '%s' not yet available.\n" % str(datatype)) + + @staticmethod + def init_env(): + """Initializes the Explorer environment. + This function should be invoked before starting any exploration. If + invoked before an exploration, it need not be invoked for subsequent + explorations. + """ + Explorer.type_code_to_explorer_map = { + gdb.TYPE_CODE_CHAR: ScalarExplorer, + gdb.TYPE_CODE_INT: ScalarExplorer, + gdb.TYPE_CODE_BOOL: ScalarExplorer, + gdb.TYPE_CODE_FLT: ScalarExplorer, + gdb.TYPE_CODE_VOID: ScalarExplorer, + gdb.TYPE_CODE_ENUM: ScalarExplorer, + gdb.TYPE_CODE_STRUCT: CompoundExplorer, + gdb.TYPE_CODE_UNION: CompoundExplorer, + gdb.TYPE_CODE_PTR: PointerExplorer, + gdb.TYPE_CODE_REF: ReferenceExplorer, + gdb.TYPE_CODE_RVALUE_REF: ReferenceExplorer, + gdb.TYPE_CODE_TYPEDEF: TypedefExplorer, + gdb.TYPE_CODE_ARRAY: ArrayExplorer, + } + + @staticmethod + def is_scalar_type(type): + """Checks whether a type is a scalar type. + A type is a scalar type of its type is + gdb.TYPE_CODE_CHAR or + gdb.TYPE_CODE_INT or + gdb.TYPE_CODE_BOOL or + gdb.TYPE_CODE_FLT or + gdb.TYPE_CODE_VOID or + gdb.TYPE_CODE_ENUM. + + Arguments: + type: The type to be checked. + + Returns: + 'True' if 'type' is a scalar type. 'False' otherwise. + """ + return type.code in Explorer._SCALAR_TYPE_LIST + + @staticmethod + def return_to_parent_value(): + """A utility function which prints that the current exploration session + is returning to the parent value. Useful when exploring values. + """ + print("\nReturning to parent value...\n") + + @staticmethod + def return_to_parent_value_prompt(): + """A utility function which prompts the user to press the 'enter' key + so that the exploration session can shift back to the parent value. + Useful when exploring values. + """ + input("\nPress enter to return to parent value: ") + + @staticmethod + def return_to_enclosing_type(): + """A utility function which prints that the current exploration session + is returning to the enclosing type. Useful when exploring types. + """ + print("\nReturning to enclosing type...\n") + + @staticmethod + def return_to_enclosing_type_prompt(): + """A utility function which prompts the user to press the 'enter' key + so that the exploration session can shift back to the enclosing type. + Useful when exploring types. + """ + input("\nPress enter to return to enclosing type: ") + + +class ScalarExplorer(object): + """Internal class used to explore scalar values.""" + + @staticmethod + def explore_expr(expr, value, is_child): + """Function to explore scalar values. + See Explorer.explore_expr and Explorer.is_scalar_type for more + information. + """ + print("'%s' is a scalar value of type '%s'." % (expr, value.type)) + print("%s = %s" % (expr, str(value))) + + if is_child: + Explorer.return_to_parent_value_prompt() + Explorer.return_to_parent_value() + + return False + + @staticmethod + def explore_type(name, datatype, is_child): + """Function to explore scalar types. + See Explorer.explore_type and Explorer.is_scalar_type for more + information. + """ + if datatype.code == gdb.TYPE_CODE_ENUM: + if is_child: + print("%s is of an enumerated type '%s'." % (name, str(datatype))) + else: + print("'%s' is an enumerated type." % name) + else: + if is_child: + print("%s is of a scalar type '%s'." % (name, str(datatype))) + else: + print("'%s' is a scalar type." % name) + + if is_child: + Explorer.return_to_enclosing_type_prompt() + Explorer.return_to_enclosing_type() + + return False + + +class PointerExplorer(object): + """Internal class used to explore pointer values.""" + + @staticmethod + def explore_expr(expr, value, is_child): + """Function to explore pointer values. + See Explorer.explore_expr for more information. + """ + print( + "'%s' is a pointer to a value of type '%s'" + % (expr, str(value.type.target())) + ) + option = input( + "Continue exploring it as a pointer to a single " "value [y/n]: " + ) + if option == "y": + deref_value = None + try: + deref_value = value.dereference() + str(deref_value) + except gdb.MemoryError: + print( + "'%s' a pointer pointing to an invalid memory " "location." % expr + ) + if is_child: + Explorer.return_to_parent_value_prompt() + return False + Explorer.explore_expr( + "*%s" % Explorer.guard_expr(expr), deref_value, is_child + ) + return False + + option = input("Continue exploring it as a pointer to an " "array [y/n]: ") + if option == "y": + while True: + index = 0 + try: + index = int( + input( + "Enter the index of the element you " + "want to explore in '%s': " % expr + ) + ) + except ValueError: + break + element_expr = "%s[%d]" % (Explorer.guard_expr(expr), index) + element = value[index] + try: + str(element) + except gdb.MemoryError: + print("Cannot read value at index %d." % index) + continue + Explorer.explore_expr(element_expr, element, True) + return False + + if is_child: + Explorer.return_to_parent_value() + return False + + @staticmethod + def explore_type(name, datatype, is_child): + """Function to explore pointer types. + See Explorer.explore_type for more information. + """ + target_type = datatype.target() + print("\n%s is a pointer to a value of type '%s'." % (name, str(target_type))) + + Explorer.explore_type("the pointee type of %s" % name, target_type, is_child) + return False + + +class ReferenceExplorer(object): + """Internal class used to explore reference (TYPE_CODE_REF) values.""" + + @staticmethod + def explore_expr(expr, value, is_child): + """Function to explore array values. + See Explorer.explore_expr for more information. + """ + referenced_value = value.referenced_value() + Explorer.explore_expr(expr, referenced_value, is_child) + return False + + @staticmethod + def explore_type(name, datatype, is_child): + """Function to explore pointer types. + See Explorer.explore_type for more information. + """ + target_type = datatype.target() + Explorer.explore_type(name, target_type, is_child) + return False + + +class ArrayExplorer(object): + """Internal class used to explore arrays.""" + + @staticmethod + def explore_expr(expr, value, is_child): + """Function to explore array values. + See Explorer.explore_expr for more information. + """ + target_type = value.type.target() + print("'%s' is an array of '%s'." % (expr, str(target_type))) + index = 0 + try: + index = int( + input( + "Enter the index of the element you want to " + "explore in '%s': " % expr + ) + ) + except ValueError: + if is_child: + Explorer.return_to_parent_value() + return False + + element = None + try: + element = value[index] + str(element) + except gdb.MemoryError: + print("Cannot read value at index %d." % index) + input("Press enter to continue... ") + return True + + Explorer.explore_expr( + "%s[%d]" % (Explorer.guard_expr(expr), index), element, True + ) + return True + + @staticmethod + def explore_type(name, datatype, is_child): + """Function to explore array types. + See Explorer.explore_type for more information. + """ + target_type = datatype.target() + print("%s is an array of '%s'." % (name, str(target_type))) + + Explorer.explore_type("the array element of %s" % name, target_type, is_child) + return False + + +class CompoundExplorer(object): + """Internal class used to explore struct, classes and unions.""" + + @staticmethod + def _print_fields(print_list): + """Internal function which prints the fields of a struct/class/union.""" + max_field_name_length = 0 + for pair in print_list: + if max_field_name_length < len(pair[0]): + max_field_name_length = len(pair[0]) + + for pair in print_list: + print(" %*s = %s" % (max_field_name_length, pair[0], pair[1])) + + @staticmethod + def _get_real_field_count(fields): + real_field_count = 0 + for field in fields: + if not field.artificial: + real_field_count = real_field_count + 1 + + return real_field_count + + @staticmethod + def explore_expr(expr, value, is_child): + """Function to explore structs/classes and union values. + See Explorer.explore_expr for more information. + """ + datatype = value.type + type_code = datatype.code + fields = datatype.fields() + + if type_code == gdb.TYPE_CODE_STRUCT: + type_desc = "struct/class" + else: + type_desc = "union" + + if CompoundExplorer._get_real_field_count(fields) == 0: + print( + "The value of '%s' is a %s of type '%s' with no fields." + % (expr, type_desc, str(value.type)) + ) + if is_child: + Explorer.return_to_parent_value_prompt() + return False + + print( + "The value of '%s' is a %s of type '%s' with the following " + "fields:\n" % (expr, type_desc, str(value.type)) + ) + + has_explorable_fields = False + choice_to_compound_field_map = {} + current_choice = 0 + print_list = [] + for field in fields: + if field.artificial: + continue + field_full_name = Explorer.guard_expr(expr) + "." + field.name + if field.is_base_class: + field_value = value.cast(field.type) + else: + field_value = value[field.name] + literal_value = "" + if type_code == gdb.TYPE_CODE_UNION: + literal_value = "<Enter %d to explore this field of type " "'%s'>" % ( + current_choice, + str(field.type), + ) + has_explorable_fields = True + else: + if Explorer.is_scalar_type(field.type): + literal_value = "%s .. (Value of type '%s')" % ( + str(field_value), + str(field.type), + ) + else: + if field.is_base_class: + field_desc = "base class" + else: + field_desc = "field" + literal_value = "<Enter %d to explore this %s of type " "'%s'>" % ( + current_choice, + field_desc, + str(field.type), + ) + has_explorable_fields = True + + choice_to_compound_field_map[str(current_choice)] = ( + field_full_name, + field_value, + ) + current_choice = current_choice + 1 + + print_list.append((field.name, literal_value)) + + CompoundExplorer._print_fields(print_list) + print("") + + if has_explorable_fields: + choice = input("Enter the field number of choice: ") + if choice in choice_to_compound_field_map: + Explorer.explore_expr( + choice_to_compound_field_map[choice][0], + choice_to_compound_field_map[choice][1], + True, + ) + return True + else: + if is_child: + Explorer.return_to_parent_value() + else: + if is_child: + Explorer.return_to_parent_value_prompt() + + return False + + @staticmethod + def explore_type(name, datatype, is_child): + """Function to explore struct/class and union types. + See Explorer.explore_type for more information. + """ + type_code = datatype.code + type_desc = "" + if type_code == gdb.TYPE_CODE_STRUCT: + type_desc = "struct/class" + else: + type_desc = "union" + + fields = datatype.fields() + if CompoundExplorer._get_real_field_count(fields) == 0: + if is_child: + print( + "%s is a %s of type '%s' with no fields." + % (name, type_desc, str(datatype)) + ) + Explorer.return_to_enclosing_type_prompt() + else: + print("'%s' is a %s with no fields." % (name, type_desc)) + return False + + if is_child: + print( + "%s is a %s of type '%s' " + "with the following fields:\n" % (name, type_desc, str(datatype)) + ) + else: + print("'%s' is a %s with the following " "fields:\n" % (name, type_desc)) + + current_choice = 0 + choice_to_compound_field_map = {} + print_list = [] + for field in fields: + if field.artificial: + continue + if field.is_base_class: + field_desc = "base class" + else: + field_desc = "field" + rhs = "<Enter %d to explore this %s of type '%s'>" % ( + current_choice, + field_desc, + str(field.type), + ) + print_list.append((field.name, rhs)) + choice_to_compound_field_map[str(current_choice)] = ( + field.name, + field.type, + field_desc, + ) + current_choice = current_choice + 1 + + CompoundExplorer._print_fields(print_list) + print("") + + if len(choice_to_compound_field_map) > 0: + choice = input("Enter the field number of choice: ") + if choice in choice_to_compound_field_map: + if is_child: + new_name = "%s '%s' of %s" % ( + choice_to_compound_field_map[choice][2], + choice_to_compound_field_map[choice][0], + name, + ) + else: + new_name = "%s '%s' of '%s'" % ( + choice_to_compound_field_map[choice][2], + choice_to_compound_field_map[choice][0], + name, + ) + Explorer.explore_type( + new_name, choice_to_compound_field_map[choice][1], True + ) + return True + else: + if is_child: + Explorer.return_to_enclosing_type() + else: + if is_child: + Explorer.return_to_enclosing_type_prompt() + + return False + + +class TypedefExplorer(object): + """Internal class used to explore values whose type is a typedef.""" + + @staticmethod + def explore_expr(expr, value, is_child): + """Function to explore typedef values. + See Explorer.explore_expr for more information. + """ + actual_type = value.type.strip_typedefs() + print( + "The value of '%s' is of type '%s' " + "which is a typedef of type '%s'" + % (expr, str(value.type), str(actual_type)) + ) + + Explorer.explore_expr(expr, value.cast(actual_type), is_child) + return False + + @staticmethod + def explore_type(name, datatype, is_child): + """Function to explore typedef types. + See Explorer.explore_type for more information. + """ + actual_type = datatype.strip_typedefs() + if is_child: + print( + "The type of %s is a typedef of type '%s'." % (name, str(actual_type)) + ) + else: + print("The type '%s' is a typedef of type '%s'." % (name, str(actual_type))) + + Explorer.explore_type(name, actual_type, is_child) + return False + + +class ExploreUtils(object): + """Internal class which provides utilities for the main command classes.""" + + @staticmethod + def check_args(name, arg_str): + """Utility to check if adequate number of arguments are passed to an + explore command. + + Arguments: + name: The name of the explore command. + arg_str: The argument string passed to the explore command. + + Returns: + True if adequate arguments are passed, false otherwise. + + Raises: + gdb.GdbError if adequate arguments are not passed. + """ + if len(arg_str) < 1: + raise gdb.GdbError("ERROR: '%s' requires an argument." % name) + return False + else: + return True + + @staticmethod + def get_type_from_str(type_str): + """A utility function to deduce the gdb.Type value from a string + representing the type. + + Arguments: + type_str: The type string from which the gdb.Type value should be + deduced. + + Returns: + The deduced gdb.Type value if possible, None otherwise. + """ + try: + # Assume the current language to be C/C++ and make a try. + return gdb.parse_and_eval("(%s *)0" % type_str).type.target() + except RuntimeError: + # If assumption of current language to be C/C++ was wrong, then + # lookup the type using the API. + try: + return gdb.lookup_type(type_str) + except RuntimeError: + return None + + @staticmethod + def get_value_from_str(value_str): + """A utility function to deduce the gdb.Value value from a string + representing the value. + + Arguments: + value_str: The value string from which the gdb.Value value should + be deduced. + + Returns: + The deduced gdb.Value value if possible, None otherwise. + """ + try: + return gdb.parse_and_eval(value_str) + except RuntimeError: + return None + + +class ExploreCommand(gdb.Command): + """Explore a value or a type valid in the current context. + + Usage: explore ARG + + - ARG is either a valid expression or a type name. + - At any stage of exploration, hit the return key (instead of a + choice, if any) to return to the enclosing type or value.""" + + def __init__(self): + super(ExploreCommand, self).__init__( + name="explore", command_class=gdb.COMMAND_DATA, prefix=True + ) + + def invoke(self, arg_str, from_tty): + if ExploreUtils.check_args("explore", arg_str) is False: + return + + # Check if it is a value + value = ExploreUtils.get_value_from_str(arg_str) + if value is not None: + Explorer.explore_expr(arg_str, value, False) + return + + # If it is not a value, check if it is a type + datatype = ExploreUtils.get_type_from_str(arg_str) + if datatype is not None: + Explorer.explore_type(arg_str, datatype, False) + return + + # If it is neither a value nor a type, raise an error. + raise gdb.GdbError( + ( + "'%s' neither evaluates to a value nor is a type " + "in the current context." % arg_str + ) + ) + + +class ExploreValueCommand(gdb.Command): + """Explore value of an expression valid in the current context. + + Usage: explore value ARG + + - ARG is a valid expression. + - At any stage of exploration, hit the return key (instead of a + choice, if any) to return to the enclosing value.""" + + def __init__(self): + super(ExploreValueCommand, self).__init__( + name="explore value", command_class=gdb.COMMAND_DATA + ) + + def invoke(self, arg_str, from_tty): + if ExploreUtils.check_args("explore value", arg_str) is False: + return + + value = ExploreUtils.get_value_from_str(arg_str) + if value is None: + raise gdb.GdbError( + ( + " '%s' does not evaluate to a value in the current " + "context." % arg_str + ) + ) + return + + Explorer.explore_expr(arg_str, value, False) + + +class ExploreTypeCommand(gdb.Command): + """Explore a type or the type of an expression. + + Usage: explore type ARG + + - ARG is a valid expression or a type name. + - At any stage of exploration, hit the return key (instead of a + choice, if any) to return to the enclosing type.""" + + def __init__(self): + super(ExploreTypeCommand, self).__init__( + name="explore type", command_class=gdb.COMMAND_DATA + ) + + def invoke(self, arg_str, from_tty): + if ExploreUtils.check_args("explore type", arg_str) is False: + return + + datatype = ExploreUtils.get_type_from_str(arg_str) + if datatype is not None: + Explorer.explore_type(arg_str, datatype, False) + return + + value = ExploreUtils.get_value_from_str(arg_str) + if value is not None: + print("'%s' is of type '%s'." % (arg_str, str(value.type))) + Explorer.explore_type(str(value.type), value.type, False) + return + + raise gdb.GdbError( + ("'%s' is not a type or value in the current " "context." % arg_str) + ) + + +Explorer.init_env() + +ExploreCommand() +ExploreValueCommand() +ExploreTypeCommand() diff --git a/share/gdb/python/gdb/command/frame_filters.py b/share/gdb/python/gdb/command/frame_filters.py new file mode 100644 index 0000000..0fca0e7 --- /dev/null +++ b/share/gdb/python/gdb/command/frame_filters.py @@ -0,0 +1,479 @@ +# Frame-filter commands. +# Copyright (C) 2013-2023 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/>. + +"""GDB commands for working with frame-filters.""" + +import sys +import gdb +import gdb.frames + +# GDB Commands. +class SetFilterPrefixCmd(gdb.Command): + """Prefix command for 'set' frame-filter related operations.""" + + def __init__(self): + super(SetFilterPrefixCmd, self).__init__( + "set frame-filter", gdb.COMMAND_OBSCURE, gdb.COMPLETE_NONE, True + ) + + +class ShowFilterPrefixCmd(gdb.Command): + """Prefix command for 'show' frame-filter related operations.""" + + def __init__(self): + super(ShowFilterPrefixCmd, self).__init__( + "show frame-filter", gdb.COMMAND_OBSCURE, gdb.COMPLETE_NONE, True + ) + + +class InfoFrameFilter(gdb.Command): + """List all registered Python frame-filters. + + Usage: info frame-filters""" + + def __init__(self): + super(InfoFrameFilter, self).__init__("info frame-filter", gdb.COMMAND_DATA) + + @staticmethod + def enabled_string(state): + """Return "Yes" if filter is enabled, otherwise "No".""" + if state: + return "Yes" + else: + return "No" + + def print_list(self, title, frame_filters, blank_line): + sorted_frame_filters = sorted( + frame_filters.items(), + key=lambda i: gdb.frames.get_priority(i[1]), + reverse=True, + ) + + if len(sorted_frame_filters) == 0: + return 0 + + print(title) + print(" Priority Enabled Name") + for frame_filter in sorted_frame_filters: + name = frame_filter[0] + try: + priority = "{:<8}".format(str(gdb.frames.get_priority(frame_filter[1]))) + enabled = "{:<7}".format( + self.enabled_string(gdb.frames.get_enabled(frame_filter[1])) + ) + print(" %s %s %s" % (priority, enabled, name)) + except Exception: + e = sys.exc_info()[1] + print(" Error printing filter '" + name + "': " + str(e)) + if blank_line: + print("") + return 1 + + def invoke(self, arg, from_tty): + any_printed = self.print_list("global frame-filters:", gdb.frame_filters, True) + + cp = gdb.current_progspace() + any_printed += self.print_list( + "progspace %s frame-filters:" % cp.filename, cp.frame_filters, True + ) + + for objfile in gdb.objfiles(): + any_printed += self.print_list( + "objfile %s frame-filters:" % objfile.filename, + objfile.frame_filters, + False, + ) + + if any_printed == 0: + print("No frame filters.") + + +# Internal enable/disable functions. + + +def _enable_parse_arg(cmd_name, arg): + """Internal worker function to take an argument from + enable/disable and return a tuple of arguments. + + Arguments: + cmd_name: Name of the command invoking this function. + args: The argument as a string. + + Returns: + A tuple containing the dictionary, and the argument, or just + the dictionary in the case of "all". + """ + + argv = gdb.string_to_argv(arg) + argc = len(argv) + if argc == 0: + raise gdb.GdbError(cmd_name + " requires an argument") + if argv[0] == "all": + if argc > 1: + raise gdb.GdbError( + cmd_name + ": with 'all' " "you may not specify a filter." + ) + elif argc != 2: + raise gdb.GdbError(cmd_name + " takes exactly two arguments.") + + return argv + + +def _do_enable_frame_filter(command_tuple, flag): + """Worker for enabling/disabling frame_filters. + + Arguments: + command_type: A tuple with the first element being the + frame filter dictionary, and the second being + the frame filter name. + flag: True for Enable, False for Disable. + """ + + list_op = command_tuple[0] + op_list = gdb.frames.return_list(list_op) + + if list_op == "all": + for item in op_list: + gdb.frames.set_enabled(item, flag) + else: + frame_filter = command_tuple[1] + try: + ff = op_list[frame_filter] + except KeyError: + msg = "frame-filter '" + str(frame_filter) + "' not found." + raise gdb.GdbError(msg) + + gdb.frames.set_enabled(ff, flag) + + +def _complete_frame_filter_list(text, word, all_flag): + """Worker for frame filter dictionary name completion. + + Arguments: + text: The full text of the command line. + word: The most recent word of the command line. + all_flag: Whether to include the word "all" in completion. + + Returns: + A list of suggested frame filter dictionary name completions + from text/word analysis. This list can be empty when there + are no suggestions for completion. + """ + if all_flag: + filter_locations = ["all", "global", "progspace"] + else: + filter_locations = ["global", "progspace"] + for objfile in gdb.objfiles(): + filter_locations.append(objfile.filename) + + # If the user just asked for completions with no completion + # hints, just return all the frame filter dictionaries we know + # about. + if text == "": + return filter_locations + + # Otherwise filter on what we know. + flist = filter(lambda x, y=text: x.startswith(y), filter_locations) + + # If we only have one completion, complete it and return it. + if len(flist) == 1: + flist[0] = flist[0][len(text) - len(word) :] + + # Otherwise, return an empty list, or a list of frame filter + # dictionaries that the previous filter operation returned. + return flist + + +def _complete_frame_filter_name(word, printer_dict): + """Worker for frame filter name completion. + + Arguments: + + word: The most recent word of the command line. + + printer_dict: The frame filter dictionary to search for frame + filter name completions. + + Returns: A list of suggested frame filter name completions + from word analysis of the frame filter dictionary. This list + can be empty when there are no suggestions for completion. + """ + + printer_keys = printer_dict.keys() + if word == "": + return printer_keys + + flist = filter(lambda x, y=word: x.startswith(y), printer_keys) + return flist + + +class EnableFrameFilter(gdb.Command): + """GDB command to enable the specified frame-filter. + + Usage: enable frame-filter DICTIONARY [NAME] + + DICTIONARY is the name of the frame filter dictionary on which to + operate. If dictionary is set to "all", perform operations on all + dictionaries. Named dictionaries are: "global" for the global + frame filter dictionary, "progspace" for the program space's frame + filter dictionary. If either all, or the two named dictionaries + are not specified, the dictionary name is assumed to be the name + of an "objfile" -- a shared library or an executable. + + NAME matches the name of the frame-filter to operate on.""" + + def __init__(self): + super(EnableFrameFilter, self).__init__("enable frame-filter", gdb.COMMAND_DATA) + + def complete(self, text, word): + """Completion function for both frame filter dictionary, and + frame filter name.""" + if text.count(" ") == 0: + return _complete_frame_filter_list(text, word, True) + else: + printer_list = gdb.frames.return_list(text.split()[0].rstrip()) + return _complete_frame_filter_name(word, printer_list) + + def invoke(self, arg, from_tty): + command_tuple = _enable_parse_arg("enable frame-filter", arg) + _do_enable_frame_filter(command_tuple, True) + + +class DisableFrameFilter(gdb.Command): + """GDB command to disable the specified frame-filter. + + Usage: disable frame-filter DICTIONARY [NAME] + + DICTIONARY is the name of the frame filter dictionary on which to + operate. If dictionary is set to "all", perform operations on all + dictionaries. Named dictionaries are: "global" for the global + frame filter dictionary, "progspace" for the program space's frame + filter dictionary. If either all, or the two named dictionaries + are not specified, the dictionary name is assumed to be the name + of an "objfile" -- a shared library or an executable. + + NAME matches the name of the frame-filter to operate on.""" + + def __init__(self): + super(DisableFrameFilter, self).__init__( + "disable frame-filter", gdb.COMMAND_DATA + ) + + def complete(self, text, word): + """Completion function for both frame filter dictionary, and + frame filter name.""" + if text.count(" ") == 0: + return _complete_frame_filter_list(text, word, True) + else: + printer_list = gdb.frames.return_list(text.split()[0].rstrip()) + return _complete_frame_filter_name(word, printer_list) + + def invoke(self, arg, from_tty): + command_tuple = _enable_parse_arg("disable frame-filter", arg) + _do_enable_frame_filter(command_tuple, False) + + +class SetFrameFilterPriority(gdb.Command): + """GDB command to set the priority of the specified frame-filter. + + Usage: set frame-filter priority DICTIONARY NAME PRIORITY + + DICTIONARY is the name of the frame filter dictionary on which to + operate. Named dictionaries are: "global" for the global frame + filter dictionary, "progspace" for the program space's framefilter + dictionary. If either of these two are not specified, the + dictionary name is assumed to be the name of an "objfile" -- a + shared library or an executable. + + NAME matches the name of the frame filter to operate on. + + PRIORITY is the an integer to assign the new priority to the frame + filter.""" + + def __init__(self): + super(SetFrameFilterPriority, self).__init__( + "set frame-filter " "priority", gdb.COMMAND_DATA + ) + + def _parse_pri_arg(self, arg): + """Internal worker to parse a priority from a tuple. + + Arguments: + arg: Tuple which contains the arguments from the command. + + Returns: + A tuple containing the dictionary, name and priority from + the arguments. + + Raises: + gdb.GdbError: An error parsing the arguments. + """ + + argv = gdb.string_to_argv(arg) + argc = len(argv) + if argc != 3: + print("set frame-filter priority " "takes exactly three arguments.") + return None + + return argv + + def _set_filter_priority(self, command_tuple): + """Internal worker for setting priority of frame-filters, by + parsing a tuple and calling _set_priority with the parsed + tuple. + + Arguments: + command_tuple: Tuple which contains the arguments from the + command. + """ + + list_op = command_tuple[0] + frame_filter = command_tuple[1] + + # GDB returns arguments as a string, so convert priority to + # a number. + priority = int(command_tuple[2]) + + op_list = gdb.frames.return_list(list_op) + + try: + ff = op_list[frame_filter] + except KeyError: + msg = "frame-filter '" + str(frame_filter) + "' not found." + raise gdb.GdbError(msg) + + gdb.frames.set_priority(ff, priority) + + def complete(self, text, word): + """Completion function for both frame filter dictionary, and + frame filter name.""" + if text.count(" ") == 0: + return _complete_frame_filter_list(text, word, False) + else: + printer_list = gdb.frames.return_list(text.split()[0].rstrip()) + return _complete_frame_filter_name(word, printer_list) + + def invoke(self, arg, from_tty): + command_tuple = self._parse_pri_arg(arg) + if command_tuple is not None: + self._set_filter_priority(command_tuple) + + +class ShowFrameFilterPriority(gdb.Command): + """GDB command to show the priority of the specified frame-filter. + + Usage: show frame-filter priority DICTIONARY NAME + + DICTIONARY is the name of the frame filter dictionary on which to + operate. Named dictionaries are: "global" for the global frame + filter dictionary, "progspace" for the program space's framefilter + dictionary. If either of these two are not specified, the + dictionary name is assumed to be the name of an "objfile" -- a + shared library or an executable. + + NAME matches the name of the frame-filter to operate on.""" + + def __init__(self): + super(ShowFrameFilterPriority, self).__init__( + "show frame-filter " "priority", gdb.COMMAND_DATA + ) + + def _parse_pri_arg(self, arg): + """Internal worker to parse a dictionary and name from a + tuple. + + Arguments: + arg: Tuple which contains the arguments from the command. + + Returns: + A tuple containing the dictionary, and frame filter name. + + Raises: + gdb.GdbError: An error parsing the arguments. + """ + + argv = gdb.string_to_argv(arg) + argc = len(argv) + if argc != 2: + print("show frame-filter priority " "takes exactly two arguments.") + return None + + return argv + + def get_filter_priority(self, frame_filters, name): + """Worker for retrieving the priority of frame_filters. + + Arguments: + frame_filters: Name of frame filter dictionary. + name: object to select printers. + + Returns: + The priority of the frame filter. + + Raises: + gdb.GdbError: A frame filter cannot be found. + """ + + op_list = gdb.frames.return_list(frame_filters) + + try: + ff = op_list[name] + except KeyError: + msg = "frame-filter '" + str(name) + "' not found." + raise gdb.GdbError(msg) + + return gdb.frames.get_priority(ff) + + def complete(self, text, word): + """Completion function for both frame filter dictionary, and + frame filter name.""" + + if text.count(" ") == 0: + return _complete_frame_filter_list(text, word, False) + else: + printer_list = frame._return_list(text.split()[0].rstrip()) + return _complete_frame_filter_name(word, printer_list) + + def invoke(self, arg, from_tty): + command_tuple = self._parse_pri_arg(arg) + if command_tuple is None: + return + filter_name = command_tuple[1] + list_name = command_tuple[0] + try: + priority = self.get_filter_priority(list_name, filter_name) + except Exception: + e = sys.exc_info()[1] + print("Error printing filter priority for '" + name + "':" + str(e)) + else: + print( + "Priority of filter '" + + filter_name + + "' in list '" + + list_name + + "' is: " + + str(priority) + ) + + +# Register commands +SetFilterPrefixCmd() +ShowFilterPrefixCmd() +InfoFrameFilter() +EnableFrameFilter() +DisableFrameFilter() +SetFrameFilterPriority() +ShowFrameFilterPriority() diff --git a/share/gdb/python/gdb/command/pretty_printers.py b/share/gdb/python/gdb/command/pretty_printers.py new file mode 100644 index 0000000..a3b582e --- /dev/null +++ b/share/gdb/python/gdb/command/pretty_printers.py @@ -0,0 +1,395 @@ +# Pretty-printer commands. +# Copyright (C) 2010-2023 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/>. + +"""GDB commands for working with pretty-printers.""" + +import copy +import gdb +import re + + +def parse_printer_regexps(arg): + """Internal utility to parse a pretty-printer command argv. + + Arguments: + arg: The arguments to the command. The format is: + [object-regexp [name-regexp]]. + Individual printers in a collection are named as + printer-name;subprinter-name. + + Returns: + The result is a 3-tuple of compiled regular expressions, except that + the resulting compiled subprinter regexp is None if not provided. + + Raises: + SyntaxError: an error processing ARG + """ + + argv = gdb.string_to_argv(arg) + argc = len(argv) + object_regexp = "" # match everything + name_regexp = "" # match everything + subname_regexp = None + if argc > 3: + raise SyntaxError("too many arguments") + if argc >= 1: + object_regexp = argv[0] + if argc >= 2: + name_subname = argv[1].split(";", 1) + name_regexp = name_subname[0] + if len(name_subname) == 2: + subname_regexp = name_subname[1] + # That re.compile raises SyntaxError was determined empirically. + # We catch it and reraise it to provide a slightly more useful + # error message for the user. + try: + object_re = re.compile(object_regexp) + except SyntaxError: + raise SyntaxError("invalid object regexp: %s" % object_regexp) + try: + name_re = re.compile(name_regexp) + except SyntaxError: + raise SyntaxError("invalid name regexp: %s" % name_regexp) + if subname_regexp is not None: + try: + subname_re = re.compile(subname_regexp) + except SyntaxError: + raise SyntaxError("invalid subname regexp: %s" % subname_regexp) + else: + subname_re = None + return (object_re, name_re, subname_re) + + +def printer_enabled_p(printer): + """Internal utility to see if printer (or subprinter) is enabled.""" + if hasattr(printer, "enabled"): + return printer.enabled + else: + return True + + +class InfoPrettyPrinter(gdb.Command): + """GDB command to list all registered pretty-printers. + + Usage: info pretty-printer [OBJECT-REGEXP [NAME-REGEXP]] + + OBJECT-REGEXP is a regular expression matching the objects to list. + Objects are "global", the program space's file, and the objfiles within + that program space. + + NAME-REGEXP matches the name of the pretty-printer. + Individual printers in a collection are named as + printer-name;subprinter-name.""" + + def __init__(self): + super(InfoPrettyPrinter, self).__init__("info pretty-printer", gdb.COMMAND_DATA) + + @staticmethod + def enabled_string(printer): + """Return "" if PRINTER is enabled, otherwise " [disabled]".""" + if printer_enabled_p(printer): + return "" + else: + return " [disabled]" + + @staticmethod + def printer_name(printer): + """Return the printer's name.""" + if hasattr(printer, "name"): + return printer.name + if hasattr(printer, "__name__"): + return printer.__name__ + # This "shouldn't happen", but the public API allows for + # direct additions to the pretty-printer list, and we shouldn't + # crash because someone added a bogus printer. + # Plus we want to give the user a way to list unknown printers. + return "unknown" + + def list_pretty_printers(self, pretty_printers, name_re, subname_re): + """Print a list of pretty-printers.""" + # A potential enhancement is to provide an option to list printers in + # "lookup order" (i.e. unsorted). + sorted_pretty_printers = sorted( + copy.copy(pretty_printers), key=self.printer_name + ) + for printer in sorted_pretty_printers: + name = self.printer_name(printer) + enabled = self.enabled_string(printer) + if name_re.match(name): + print(" %s%s" % (name, enabled)) + if hasattr(printer, "subprinters") and printer.subprinters is not None: + sorted_subprinters = sorted( + copy.copy(printer.subprinters), key=self.printer_name + ) + for subprinter in sorted_subprinters: + if not subname_re or subname_re.match(subprinter.name): + print( + " %s%s" + % (subprinter.name, self.enabled_string(subprinter)) + ) + + def invoke1( + self, title, printer_list, obj_name_to_match, object_re, name_re, subname_re + ): + """Subroutine of invoke to simplify it.""" + if printer_list and object_re.match(obj_name_to_match): + print(title) + self.list_pretty_printers(printer_list, name_re, subname_re) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + (object_re, name_re, subname_re) = parse_printer_regexps(arg) + self.invoke1( + "global pretty-printers:", + gdb.pretty_printers, + "global", + object_re, + name_re, + subname_re, + ) + cp = gdb.current_progspace() + self.invoke1( + "progspace %s pretty-printers:" % cp.filename, + cp.pretty_printers, + "progspace", + object_re, + name_re, + subname_re, + ) + for objfile in gdb.objfiles(): + self.invoke1( + "objfile %s pretty-printers:" % objfile.filename, + objfile.pretty_printers, + objfile.filename, + object_re, + name_re, + subname_re, + ) + + +def count_enabled_printers(pretty_printers): + """Return a 2-tuple of number of enabled and total printers.""" + enabled = 0 + total = 0 + for printer in pretty_printers: + if hasattr(printer, "subprinters") and printer.subprinters is not None: + if printer_enabled_p(printer): + for subprinter in printer.subprinters: + if printer_enabled_p(subprinter): + enabled += 1 + total += len(printer.subprinters) + else: + if printer_enabled_p(printer): + enabled += 1 + total += 1 + return (enabled, total) + + +def count_all_enabled_printers(): + """Return a 2-tuble of the enabled state and total number of all printers. + This includes subprinters. + """ + enabled_count = 0 + total_count = 0 + (t_enabled, t_total) = count_enabled_printers(gdb.pretty_printers) + enabled_count += t_enabled + total_count += t_total + (t_enabled, t_total) = count_enabled_printers( + gdb.current_progspace().pretty_printers + ) + enabled_count += t_enabled + total_count += t_total + for objfile in gdb.objfiles(): + (t_enabled, t_total) = count_enabled_printers(objfile.pretty_printers) + enabled_count += t_enabled + total_count += t_total + return (enabled_count, total_count) + + +def pluralize(text, n, suffix="s"): + """Return TEXT pluralized if N != 1.""" + if n != 1: + return "%s%s" % (text, suffix) + else: + return text + + +def show_pretty_printer_enabled_summary(): + """Print the number of printers enabled/disabled. + We count subprinters individually. + """ + (enabled_count, total_count) = count_all_enabled_printers() + print("%d of %d printers enabled" % (enabled_count, total_count)) + + +def do_enable_pretty_printer_1(pretty_printers, name_re, subname_re, flag): + """Worker for enabling/disabling pretty-printers. + + Arguments: + pretty_printers: list of pretty-printers + name_re: regular-expression object to select printers + subname_re: regular expression object to select subprinters or None + if all are affected + flag: True for Enable, False for Disable + + Returns: + The number of printers affected. + This is just for informational purposes for the user. + """ + total = 0 + for printer in pretty_printers: + if ( + hasattr(printer, "name") + and name_re.match(printer.name) + or hasattr(printer, "__name__") + and name_re.match(printer.__name__) + ): + if hasattr(printer, "subprinters") and printer.subprinters is not None: + if not subname_re: + # Only record printers that change state. + if printer_enabled_p(printer) != flag: + for subprinter in printer.subprinters: + if printer_enabled_p(subprinter): + total += 1 + # NOTE: We preserve individual subprinter settings. + printer.enabled = flag + else: + # NOTE: Whether this actually disables the subprinter + # depends on whether the printer's lookup function supports + # the "enable" API. We can only assume it does. + for subprinter in printer.subprinters: + if subname_re.match(subprinter.name): + # Only record printers that change state. + if ( + printer_enabled_p(printer) + and printer_enabled_p(subprinter) != flag + ): + total += 1 + subprinter.enabled = flag + else: + # This printer has no subprinters. + # If the user does "disable pretty-printer .* .* foo" + # should we disable printers that don't have subprinters? + # How do we apply "foo" in this context? Since there is no + # "foo" subprinter it feels like we should skip this printer. + # There's still the issue of how to handle + # "disable pretty-printer .* .* .*", and every other variation + # that can match everything. For now punt and only support + # "disable pretty-printer .* .*" (i.e. subname is elided) + # to disable everything. + if not subname_re: + # Only record printers that change state. + if printer_enabled_p(printer) != flag: + total += 1 + printer.enabled = flag + return total + + +def do_enable_pretty_printer(arg, flag): + """Internal worker for enabling/disabling pretty-printers.""" + (object_re, name_re, subname_re) = parse_printer_regexps(arg) + + total = 0 + if object_re.match("global"): + total += do_enable_pretty_printer_1( + gdb.pretty_printers, name_re, subname_re, flag + ) + cp = gdb.current_progspace() + if object_re.match("progspace"): + total += do_enable_pretty_printer_1( + cp.pretty_printers, name_re, subname_re, flag + ) + for objfile in gdb.objfiles(): + if object_re.match(objfile.filename): + total += do_enable_pretty_printer_1( + objfile.pretty_printers, name_re, subname_re, flag + ) + + if flag: + state = "enabled" + else: + state = "disabled" + print("%d %s %s" % (total, pluralize("printer", total), state)) + + # Print the total list of printers currently enabled/disabled. + # This is to further assist the user in determining whether the result + # is expected. Since we use regexps to select it's useful. + show_pretty_printer_enabled_summary() + + +# Enable/Disable one or more pretty-printers. +# +# This is intended for use when a broken pretty-printer is shipped/installed +# and the user wants to disable that printer without disabling all the other +# printers. +# +# A useful addition would be -v (verbose) to show each printer affected. + + +class EnablePrettyPrinter(gdb.Command): + """GDB command to enable the specified pretty-printer. + + Usage: enable pretty-printer [OBJECT-REGEXP [NAME-REGEXP]] + + OBJECT-REGEXP is a regular expression matching the objects to examine. + Objects are "global", the program space's file, and the objfiles within + that program space. + + NAME-REGEXP matches the name of the pretty-printer. + Individual printers in a collection are named as + printer-name;subprinter-name.""" + + def __init__(self): + super(EnablePrettyPrinter, self).__init__( + "enable pretty-printer", gdb.COMMAND_DATA + ) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_pretty_printer(arg, True) + + +class DisablePrettyPrinter(gdb.Command): + """GDB command to disable the specified pretty-printer. + + Usage: disable pretty-printer [OBJECT-REGEXP [NAME-REGEXP]] + + OBJECT-REGEXP is a regular expression matching the objects to examine. + Objects are "global", the program space's file, and the objfiles within + that program space. + + NAME-REGEXP matches the name of the pretty-printer. + Individual printers in a collection are named as + printer-name;subprinter-name.""" + + def __init__(self): + super(DisablePrettyPrinter, self).__init__( + "disable pretty-printer", gdb.COMMAND_DATA + ) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_pretty_printer(arg, False) + + +def register_pretty_printer_commands(): + """Call from a top level script to install the pretty-printer commands.""" + InfoPrettyPrinter() + EnablePrettyPrinter() + DisablePrettyPrinter() + + +register_pretty_printer_commands() diff --git a/share/gdb/python/gdb/command/prompt.py b/share/gdb/python/gdb/command/prompt.py new file mode 100644 index 0000000..775fa1a --- /dev/null +++ b/share/gdb/python/gdb/command/prompt.py @@ -0,0 +1,66 @@ +# Extended prompt. +# Copyright (C) 2011-2023 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/>. + +"""GDB command for working with extended prompts.""" + +import gdb +import gdb.prompt + + +class _ExtendedPrompt(gdb.Parameter): + + """Set the extended prompt. + + Usage: set extended-prompt VALUE + + Substitutions are applied to VALUE to compute the real prompt. + + The currently defined substitutions are:""" + + # Add the prompt library's dynamically generated help to the + # __doc__ string. + __doc__ = __doc__ + "\n" + gdb.prompt.prompt_help() + + set_doc = "Set the extended prompt." + show_doc = "Show the extended prompt." + + def __init__(self): + super(_ExtendedPrompt, self).__init__( + "extended-prompt", gdb.COMMAND_SUPPORT, gdb.PARAM_STRING_NOESCAPE + ) + self.value = "" + self.hook_set = False + + def get_show_string(self, pvalue): + if self.value: + return "The extended prompt is: " + self.value + else: + return "The extended prompt is not set." + + def get_set_string(self): + if self.hook_set is False: + gdb.prompt_hook = self.before_prompt_hook + self.hook_set = True + return "" + + def before_prompt_hook(self, current): + if self.value: + return gdb.prompt.substitute_prompt(self.value) + else: + return None + + +_ExtendedPrompt() diff --git a/share/gdb/python/gdb/command/type_printers.py b/share/gdb/python/gdb/command/type_printers.py new file mode 100644 index 0000000..35e6201 --- /dev/null +++ b/share/gdb/python/gdb/command/type_printers.py @@ -0,0 +1,125 @@ +# Type printer commands. +# Copyright (C) 2010-2023 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/>. + +import copy +import gdb + +"""GDB commands for working with type-printers.""" + + +class InfoTypePrinter(gdb.Command): + """GDB command to list all registered type-printers. + + Usage: info type-printers""" + + def __init__(self): + super(InfoTypePrinter, self).__init__("info type-printers", gdb.COMMAND_DATA) + + def list_type_printers(self, type_printers): + """Print a list of type printers.""" + # A potential enhancement is to provide an option to list printers in + # "lookup order" (i.e. unsorted). + sorted_type_printers = sorted(copy.copy(type_printers), key=lambda x: x.name) + for printer in sorted_type_printers: + if printer.enabled: + enabled = "" + else: + enabled = " [disabled]" + print(" %s%s" % (printer.name, enabled)) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + sep = "" + for objfile in gdb.objfiles(): + if objfile.type_printers: + print("%sType printers for %s:" % (sep, objfile.filename)) + self.list_type_printers(objfile.type_printers) + sep = "\n" + if gdb.current_progspace().type_printers: + print("%sType printers for program space:" % sep) + self.list_type_printers(gdb.current_progspace().type_printers) + sep = "\n" + if gdb.type_printers: + print("%sGlobal type printers:" % sep) + self.list_type_printers(gdb.type_printers) + + +class _EnableOrDisableCommand(gdb.Command): + def __init__(self, setting, name): + super(_EnableOrDisableCommand, self).__init__(name, gdb.COMMAND_DATA) + self.setting = setting + + def set_some(self, name, printers): + result = False + for p in printers: + if name == p.name: + p.enabled = self.setting + result = True + return result + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + for name in arg.split(): + ok = False + for objfile in gdb.objfiles(): + if self.set_some(name, objfile.type_printers): + ok = True + if self.set_some(name, gdb.current_progspace().type_printers): + ok = True + if self.set_some(name, gdb.type_printers): + ok = True + if not ok: + print("No type printer named '%s'" % name) + + def add_some(self, result, word, printers): + for p in printers: + if p.name.startswith(word): + result.append(p.name) + + def complete(self, text, word): + result = [] + for objfile in gdb.objfiles(): + self.add_some(result, word, objfile.type_printers) + self.add_some(result, word, gdb.current_progspace().type_printers) + self.add_some(result, word, gdb.type_printers) + return result + + +class EnableTypePrinter(_EnableOrDisableCommand): + """GDB command to enable the specified type printer. + + Usage: enable type-printer NAME + + NAME is the name of the type-printer.""" + + def __init__(self): + super(EnableTypePrinter, self).__init__(True, "enable type-printer") + + +class DisableTypePrinter(_EnableOrDisableCommand): + """GDB command to disable the specified type-printer. + + Usage: disable type-printer NAME + + NAME is the name of the type-printer.""" + + def __init__(self): + super(DisableTypePrinter, self).__init__(False, "disable type-printer") + + +InfoTypePrinter() +EnableTypePrinter() +DisableTypePrinter() diff --git a/share/gdb/python/gdb/command/unwinders.py b/share/gdb/python/gdb/command/unwinders.py new file mode 100644 index 0000000..68087aa --- /dev/null +++ b/share/gdb/python/gdb/command/unwinders.py @@ -0,0 +1,199 @@ +# Unwinder commands. +# Copyright 2015-2023 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/>. + +import gdb +import re + + +def validate_regexp(exp, idstring): + try: + return re.compile(exp) + except SyntaxError: + raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp)) + + +def parse_unwinder_command_args(arg): + """Internal utility to parse unwinder command argv. + + Arguments: + arg: The arguments to the command. The format is: + [locus-regexp [name-regexp]] + + Returns: + A 2-tuple of compiled regular expressions. + + Raises: + SyntaxError: an error processing ARG + """ + + argv = gdb.string_to_argv(arg) + argc = len(argv) + if argc > 2: + raise SyntaxError("Too many arguments.") + locus_regexp = "" + name_regexp = "" + if argc >= 1: + locus_regexp = argv[0] + if argc >= 2: + name_regexp = argv[1] + return ( + validate_regexp(locus_regexp, "locus"), + validate_regexp(name_regexp, "unwinder"), + ) + + +class InfoUnwinder(gdb.Command): + """GDB command to list unwinders. + + Usage: info unwinder [LOCUS-REGEXP [NAME-REGEXP]] + + LOCUS-REGEXP is a regular expression matching the location of the + unwinder. If it is omitted, all registered unwinders from all + loci are listed. A locus can be 'global', 'progspace' to list + the unwinders from the current progspace, or a regular expression + matching filenames of objfiles. + + NAME-REGEXP is a regular expression to filter unwinder names. If + this omitted for a specified locus, then all registered unwinders + in the locus are listed.""" + + def __init__(self): + super(InfoUnwinder, self).__init__("info unwinder", gdb.COMMAND_STACK) + + def list_unwinders(self, title, unwinders, name_re): + """Lists the unwinders whose name matches regexp. + + Arguments: + title: The line to print before the list. + unwinders: The list of the unwinders. + name_re: unwinder name filter. + """ + if not unwinders: + return + print(title) + for unwinder in unwinders: + if name_re.match(unwinder.name): + print( + " %s%s" + % (unwinder.name, "" if unwinder.enabled else " [disabled]") + ) + + def invoke(self, arg, from_tty): + locus_re, name_re = parse_unwinder_command_args(arg) + if locus_re.match("global"): + self.list_unwinders("Global:", gdb.frame_unwinders, name_re) + if locus_re.match("progspace"): + cp = gdb.current_progspace() + self.list_unwinders( + "Progspace %s:" % cp.filename, cp.frame_unwinders, name_re + ) + for objfile in gdb.objfiles(): + if locus_re.match(objfile.filename): + self.list_unwinders( + "Objfile %s:" % objfile.filename, objfile.frame_unwinders, name_re + ) + + +def do_enable_unwinder1(unwinders, name_re, flag): + """Enable/disable unwinders whose names match given regex. + + Arguments: + unwinders: The list of unwinders. + name_re: Unwinder name filter. + flag: Enable/disable. + + Returns: + The number of unwinders affected. + """ + total = 0 + for unwinder in unwinders: + if name_re.match(unwinder.name): + unwinder.enabled = flag + total += 1 + return total + + +def do_enable_unwinder(arg, flag): + """Enable/disable unwinder(s).""" + (locus_re, name_re) = parse_unwinder_command_args(arg) + total = 0 + if locus_re.match("global"): + total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag) + if locus_re.match("progspace"): + total += do_enable_unwinder1( + gdb.current_progspace().frame_unwinders, name_re, flag + ) + for objfile in gdb.objfiles(): + if locus_re.match(objfile.filename): + total += do_enable_unwinder1(objfile.frame_unwinders, name_re, flag) + if total > 0: + gdb.invalidate_cached_frames() + print( + "%d unwinder%s %s" + % (total, "" if total == 1 else "s", "enabled" if flag else "disabled") + ) + + +class EnableUnwinder(gdb.Command): + """GDB command to enable unwinders. + + Usage: enable unwinder [LOCUS-REGEXP [NAME-REGEXP]] + + LOCUS-REGEXP is a regular expression specifying the unwinders to + enable. It can 'global', 'progspace', or the name of an objfile + within that progspace. + + NAME_REGEXP is a regular expression to filter unwinder names. If + this omitted for a specified locus, then all registered unwinders + in the locus are affected.""" + + def __init__(self): + super(EnableUnwinder, self).__init__("enable unwinder", gdb.COMMAND_STACK) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_unwinder(arg, True) + + +class DisableUnwinder(gdb.Command): + """GDB command to disable the specified unwinder. + + Usage: disable unwinder [LOCUS-REGEXP [NAME-REGEXP]] + + LOCUS-REGEXP is a regular expression specifying the unwinders to + disable. It can 'global', 'progspace', or the name of an objfile + within that progspace. + + NAME_REGEXP is a regular expression to filter unwinder names. If + this omitted for a specified locus, then all registered unwinders + in the locus are affected.""" + + def __init__(self): + super(DisableUnwinder, self).__init__("disable unwinder", gdb.COMMAND_STACK) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_unwinder(arg, False) + + +def register_unwinder_commands(): + """Installs the unwinder commands.""" + InfoUnwinder() + EnableUnwinder() + DisableUnwinder() + + +register_unwinder_commands() diff --git a/share/gdb/python/gdb/command/xmethods.py b/share/gdb/python/gdb/command/xmethods.py new file mode 100644 index 0000000..4bf8969 --- /dev/null +++ b/share/gdb/python/gdb/command/xmethods.py @@ -0,0 +1,270 @@ +# Xmethod commands. +# Copyright 2013-2023 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/>. + +import gdb +import re + +"""GDB commands for working with xmethods.""" + + +def validate_xm_regexp(part_name, regexp): + try: + return re.compile(regexp) + except SyntaxError: + raise SyntaxError("Invalid %s regexp: %s", part_name, regexp) + + +def parse_xm_command_args(arg): + """Parses the arguments passed to a xmethod command. + + Arguments: + arg: The argument string passed to a xmethod command. + + Returns: + A 3-tuple: (<locus matching regular expression>, + <matcher matching regular expression>, + <name matching regular experession>) + """ + argv = gdb.string_to_argv(arg) + argc = len(argv) + if argc > 2: + raise SyntaxError("Too many arguments to command.") + locus_regexp = "" + matcher_name_regexp = "" + xm_name_regexp = None + if argc >= 1: + locus_regexp = argv[0] + if argc == 2: + parts = argv[1].split(";", 1) + matcher_name_regexp = parts[0] + if len(parts) > 1: + xm_name_regexp = parts[1] + if xm_name_regexp: + name_re = validate_xm_regexp("xmethod name", xm_name_regexp) + else: + name_re = None + return ( + validate_xm_regexp("locus", locus_regexp), + validate_xm_regexp("matcher name", matcher_name_regexp), + name_re, + ) + + +def get_global_method_matchers(locus_re, matcher_re): + """Returns a dict of matching globally registered xmethods. + + Arguments: + locus_re: Even though only globally registered xmethods are + looked up, they will be looked up only if 'global' matches + LOCUS_RE. + matcher_re: The regular expression matching the names of xmethods. + + Returns: + A dict of matching globally registered xmethod matchers. The only + key in the dict will be 'global'. + """ + locus_str = "global" + xm_dict = {locus_str: []} + if locus_re.match("global"): + xm_dict[locus_str].extend([m for m in gdb.xmethods if matcher_re.match(m.name)]) + return xm_dict + + +def get_method_matchers_in_loci(loci, locus_re, matcher_re): + """Returns a dict of matching registered xmethods in the LOCI. + + Arguments: + loci: The list of loci to lookup matching xmethods in. + locus_re: If a locus is an objfile, then xmethod matchers will be + looked up in it only if its filename matches the regular + expression LOCUS_RE. If a locus is the current progspace, + then xmethod matchers will be looked up in it only if the + string "progspace" matches LOCUS_RE. + matcher_re: The regular expression to match the xmethod matcher + names. + + Returns: + A dict of matching xmethod matchers. The keys of the dict are the + filenames of the loci the xmethod matchers belong to. + """ + xm_dict = {} + for locus in loci: + if isinstance(locus, gdb.Progspace): + if not locus_re.match("progspace"): + continue + locus_type = "progspace" + else: + if not locus_re.match(locus.filename): + continue + locus_type = "objfile" + locus_str = "%s %s" % (locus_type, locus.filename) + xm_dict[locus_str] = [m for m in locus.xmethods if matcher_re.match(m.name)] + return xm_dict + + +def print_xm_info(xm_dict, name_re): + """Print a dictionary of xmethods.""" + + def get_status_string(m): + if not m.enabled: + return " [disabled]" + else: + return "" + + if not xm_dict: + return + for locus_str in xm_dict: + if not xm_dict[locus_str]: + continue + print("Xmethods in %s:" % locus_str) + for matcher in xm_dict[locus_str]: + print(" %s%s" % (matcher.name, get_status_string(matcher))) + if not matcher.methods: + continue + for m in matcher.methods: + if name_re is None or name_re.match(m.name): + print(" %s%s" % (m.name, get_status_string(m))) + + +def set_xm_status1(xm_dict, name_re, status): + """Set the status (enabled/disabled) of a dictionary of xmethods.""" + for locus_str, matchers in xm_dict.items(): + for matcher in matchers: + if not name_re: + # If the name regex is missing, then set the status of the + # matcher and move on. + matcher.enabled = status + continue + if not matcher.methods: + # The methods attribute could be None. Move on. + continue + for m in matcher.methods: + if name_re.match(m.name): + m.enabled = status + + +def set_xm_status(arg, status): + """Set the status (enabled/disabled) of xmethods matching ARG. + This is a helper function for enable/disable commands. ARG is the + argument string passed to the commands. + """ + locus_re, matcher_re, name_re = parse_xm_command_args(arg) + set_xm_status1(get_global_method_matchers(locus_re, matcher_re), name_re, status) + set_xm_status1( + get_method_matchers_in_loci([gdb.current_progspace()], locus_re, matcher_re), + name_re, + status, + ) + set_xm_status1( + get_method_matchers_in_loci(gdb.objfiles(), locus_re, matcher_re), + name_re, + status, + ) + + +class InfoXMethod(gdb.Command): + """GDB command to list registered xmethod matchers. + + Usage: info xmethod [LOCUS-REGEXP [NAME-REGEXP]] + + LOCUS-REGEXP is a regular expression matching the location of the + xmethod matchers. If it is omitted, all registered xmethod matchers + from all loci are listed. A locus could be 'global', a regular expression + matching the current program space's filename, or a regular expression + matching filenames of objfiles. Locus could be 'progspace' to specify that + only xmethods from the current progspace should be listed. + + NAME-REGEXP is a regular expression matching the names of xmethod + matchers. If this omitted for a specified locus, then all registered + xmethods in the locus are listed. To list only a certain xmethods + managed by a single matcher, the name regexp can be specified as + matcher-name-regexp;xmethod-name-regexp.""" + + def __init__(self): + super(InfoXMethod, self).__init__("info xmethod", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + locus_re, matcher_re, name_re = parse_xm_command_args(arg) + print_xm_info(get_global_method_matchers(locus_re, matcher_re), name_re) + print_xm_info( + get_method_matchers_in_loci( + [gdb.current_progspace()], locus_re, matcher_re + ), + name_re, + ) + print_xm_info( + get_method_matchers_in_loci(gdb.objfiles(), locus_re, matcher_re), name_re + ) + + +class EnableXMethod(gdb.Command): + """GDB command to enable a specified (group of) xmethod(s). + + Usage: enable xmethod [LOCUS-REGEXP [NAME-REGEXP]] + + LOCUS-REGEXP is a regular expression matching the location of the + xmethod matchers. If it is omitted, all registered xmethods matchers + from all loci are enabled. A locus could be 'global', a regular expression + matching the current program space's filename, or a regular expression + matching filenames of objfiles. Locus could be 'progspace' to specify that + only xmethods from the current progspace should be enabled. + + NAME-REGEXP is a regular expression matching the names of xmethods + within a given locus. If this omitted for a specified locus, then all + registered xmethod matchers in the locus are enabled. To enable only + a certain xmethods managed by a single matcher, the name regexp can be + specified as matcher-name-regexp;xmethod-name-regexp.""" + + def __init__(self): + super(EnableXMethod, self).__init__("enable xmethod", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + set_xm_status(arg, True) + + +class DisableXMethod(gdb.Command): + """GDB command to disable a specified (group of) xmethod(s). + + Usage: disable xmethod [LOCUS-REGEXP [NAME-REGEXP]] + + LOCUS-REGEXP is a regular expression matching the location of the + xmethod matchers. If it is omitted, all registered xmethod matchers + from all loci are disabled. A locus could be 'global', a regular + expression matching the current program space's filename, or a regular + expression filenames of objfiles. Locus could be 'progspace' to specify + that only xmethods from the current progspace should be disabled. + + NAME-REGEXP is a regular expression matching the names of xmethods + within a given locus. If this omitted for a specified locus, then all + registered xmethod matchers in the locus are disabled. To disable + only a certain xmethods managed by a single matcher, the name regexp + can be specified as matcher-name-regexp;xmethod-name-regexp.""" + + def __init__(self): + super(DisableXMethod, self).__init__("disable xmethod", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + set_xm_status(arg, False) + + +def register_xmethod_commands(): + """Installs the xmethod commands.""" + InfoXMethod() + EnableXMethod() + DisableXMethod() + + +register_xmethod_commands() diff --git a/share/gdb/python/gdb/disassembler.py b/share/gdb/python/gdb/disassembler.py new file mode 100644 index 0000000..88f65f5 --- /dev/null +++ b/share/gdb/python/gdb/disassembler.py @@ -0,0 +1,178 @@ +# Copyright (C) 2021-2023 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/>. + +"""Disassembler related module.""" + +import gdb +import _gdb.disassembler + +# Re-export everything from the _gdb.disassembler module, which is +# defined within GDB's C++ code. +from _gdb.disassembler import * + +# Module global dictionary of gdb.disassembler.Disassembler objects. +# The keys of this dictionary are bfd architecture names, or the +# special value None. +# +# When a request to disassemble comes in we first lookup the bfd +# architecture name from the gdbarch, if that name exists in this +# dictionary then we use that Disassembler object. +# +# If there's no architecture specific disassembler then we look for +# the key None in this dictionary, and if that key exists, we use that +# disassembler. +# +# If none of the above checks found a suitable disassembler, then no +# disassembly is performed in Python. +_disassemblers_dict = {} + + +class Disassembler(object): + """A base class from which all user implemented disassemblers must + inherit.""" + + def __init__(self, name): + """Constructor. Takes a name, which should be a string, which can be + used to identify this disassembler in diagnostic messages.""" + self.name = name + + def __call__(self, info): + """A default implementation of __call__. All sub-classes must + override this method. Calling this default implementation will throw + a NotImplementedError exception.""" + raise NotImplementedError("Disassembler.__call__") + + +def register_disassembler(disassembler, architecture=None): + """Register a disassembler. DISASSEMBLER is a sub-class of + gdb.disassembler.Disassembler. ARCHITECTURE is either None or a + string, the name of an architecture known to GDB. + + DISASSEMBLER is registered as a disassembler for ARCHITECTURE, or + all architectures when ARCHITECTURE is None. + + Returns the previous disassembler registered with this + ARCHITECTURE value. + """ + + if not isinstance(disassembler, Disassembler) and disassembler is not None: + raise TypeError("disassembler should sub-class gdb.disassembler.Disassembler") + + old = None + if architecture in _disassemblers_dict: + old = _disassemblers_dict[architecture] + del _disassemblers_dict[architecture] + if disassembler is not None: + _disassemblers_dict[architecture] = disassembler + + # Call the private _set_enabled function within the + # _gdb.disassembler module. This function sets a global flag + # within GDB's C++ code that enables or dissables the Python + # disassembler functionality, this improves performance of the + # disassembler by avoiding unneeded calls into Python when we know + # that no disassemblers are registered. + _gdb.disassembler._set_enabled(len(_disassemblers_dict) > 0) + return old + + +def _print_insn(info): + """This function is called by GDB when it wants to disassemble an + instruction. INFO describes the instruction to be + disassembled.""" + + def lookup_disassembler(arch): + try: + name = arch.name() + if name is None: + return None + if name in _disassemblers_dict: + return _disassemblers_dict[name] + if None in _disassemblers_dict: + return _disassemblers_dict[None] + return None + except: + # It's pretty unlikely this exception case will ever + # trigger, one situation would be if the user somehow + # corrupted the _disassemblers_dict variable such that it + # was no longer a dictionary. + return None + + disassembler = lookup_disassembler(info.architecture) + if disassembler is None: + return None + return disassembler(info) + + +class maint_info_py_disassemblers_cmd(gdb.Command): + """ + List all registered Python disassemblers. + + List the name of all registered Python disassemblers, next to the + name of the architecture for which the disassembler is registered. + + The global Python disassembler is listed next to the string + 'GLOBAL'. + + The disassembler that matches the architecture of the currently + selected inferior will be marked, this is an indication of which + disassembler will be invoked if any disassembly is performed in + the current inferior. + """ + + def __init__(self): + super().__init__("maintenance info python-disassemblers", gdb.COMMAND_USER) + + def invoke(self, args, from_tty): + # If no disassemblers are registered, tell the user. + if len(_disassemblers_dict) == 0: + print("No Python disassemblers registered.") + return + + # Figure out the longest architecture name, so we can + # correctly format the table of results. + longest_arch_name = 0 + for architecture in _disassemblers_dict: + if architecture is not None: + name = _disassemblers_dict[architecture].name + if len(name) > longest_arch_name: + longest_arch_name = len(name) + + # Figure out the name of the current architecture. There + # should always be a current inferior, but if, somehow, there + # isn't, then leave curr_arch as the empty string, which will + # not then match agaisnt any architecture in the dictionary. + curr_arch = "" + if gdb.selected_inferior() is not None: + curr_arch = gdb.selected_inferior().architecture().name() + + # Now print the dictionary of registered disassemblers out to + # the user. + match_tag = "\t(Matches current architecture)" + fmt_len = max(longest_arch_name, len("Architecture")) + format_string = "{:" + str(fmt_len) + "s} {:s}" + print(format_string.format("Architecture", "Disassember Name")) + for architecture in _disassemblers_dict: + if architecture is not None: + name = _disassemblers_dict[architecture].name + if architecture == curr_arch: + name += match_tag + match_tag = "" + print(format_string.format(architecture, name)) + if None in _disassemblers_dict: + name = _disassemblers_dict[None].name + match_tag + print(format_string.format("GLOBAL", name)) + + +maint_info_py_disassemblers_cmd() diff --git a/share/gdb/python/gdb/frames.py b/share/gdb/python/gdb/frames.py new file mode 100644 index 0000000..5f8119c --- /dev/null +++ b/share/gdb/python/gdb/frames.py @@ -0,0 +1,233 @@ +# Frame-filter commands. +# Copyright (C) 2013-2023 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/>. + +"""Internal functions for working with frame-filters.""" + +import gdb +from gdb.FrameIterator import FrameIterator +from gdb.FrameDecorator import FrameDecorator +import itertools +import collections + + +def get_priority(filter_item): + """Internal worker function to return the frame-filter's priority + from a frame filter object. This is a fail free function as it is + used in sorting and filtering. If a badly implemented frame + filter does not implement the priority attribute, return zero + (otherwise sorting/filtering will fail and prevent other frame + filters from executing). + + Arguments: + filter_item: An object conforming to the frame filter + interface. + + Returns: + The priority of the frame filter from the "priority" + attribute, or zero. + """ + # Do not fail here, as the sort will fail. If a filter has not + # (incorrectly) set a priority, set it to zero. + return getattr(filter_item, "priority", 0) + + +def set_priority(filter_item, priority): + """Internal worker function to set the frame-filter's priority. + + Arguments: + filter_item: An object conforming to the frame filter + interface. + priority: The priority to assign as an integer. + """ + + filter_item.priority = priority + + +def get_enabled(filter_item): + """Internal worker function to return a filter's enabled state + from a frame filter object. This is a fail free function as it is + used in sorting and filtering. If a badly implemented frame + filter does not implement the enabled attribute, return False + (otherwise sorting/filtering will fail and prevent other frame + filters from executing). + + Arguments: + filter_item: An object conforming to the frame filter + interface. + + Returns: + The enabled state of the frame filter from the "enabled" + attribute, or False. + """ + + # If the filter class is badly implemented when called from the + # Python filter command, do not cease filter operations, just set + # enabled to False. + return getattr(filter_item, "enabled", False) + + +def set_enabled(filter_item, state): + """Internal Worker function to set the frame-filter's enabled + state. + + Arguments: + filter_item: An object conforming to the frame filter + interface. + state: True or False, depending on desired state. + """ + + filter_item.enabled = state + + +def return_list(name): + """Internal Worker function to return the frame filter + dictionary, depending on the name supplied as an argument. If the + name is not "all", "global" or "progspace", it is assumed to name + an object-file. + + Arguments: + name: The name of the list, as specified by GDB user commands. + + Returns: + A dictionary object for a single specified dictionary, or a + list containing all the items for "all" + + Raises: + gdb.GdbError: A dictionary of that name cannot be found. + """ + + # If all dictionaries are wanted in the case of "all" we + # cannot return a combined dictionary as keys() may clash in + # between different dictionaries. As we just want all the frame + # filters to enable/disable them all, just return the combined + # items() as a chained iterator of dictionary values. + if name == "all": + glob = gdb.frame_filters.values() + prog = gdb.current_progspace().frame_filters.values() + return_iter = itertools.chain(glob, prog) + for objfile in gdb.objfiles(): + return_iter = itertools.chain(return_iter, objfile.frame_filters.values()) + + return return_iter + + if name == "global": + return gdb.frame_filters + else: + if name == "progspace": + cp = gdb.current_progspace() + return cp.frame_filters + else: + for objfile in gdb.objfiles(): + if name == objfile.filename: + return objfile.frame_filters + + msg = "Cannot find frame-filter dictionary for '" + name + "'" + raise gdb.GdbError(msg) + + +def _sort_list(): + """Internal Worker function to merge all known frame-filter + lists, prune any filters with the state set to "disabled", and + sort the list on the frame-filter's "priority" attribute. + + Returns: + sorted_list: A sorted, pruned list of frame filters to + execute. + """ + + all_filters = return_list("all") + sorted_frame_filters = sorted(all_filters, key=get_priority, reverse=True) + + sorted_frame_filters = filter(get_enabled, sorted_frame_filters) + + return sorted_frame_filters + + +def execute_frame_filters(frame, frame_low, frame_high): + """Internal function called from GDB that will execute the chain + of frame filters. Each filter is executed in priority order. + After the execution completes, slice the iterator to frame_low - + frame_high range. + + Arguments: + frame: The initial frame. + + frame_low: The low range of the slice. If this is a negative + integer then it indicates a backward slice (ie bt -4) which + counts backward from the last frame in the backtrace. + + frame_high: The high range of the slice. If this is -1 then + it indicates all frames until the end of the stack from + frame_low. + + Returns: + frame_iterator: The sliced iterator after all frame + filters have had a change to execute, or None if no frame + filters are registered. + """ + + # Get a sorted list of frame filters. + sorted_list = list(_sort_list()) + + # Check to see if there are any frame-filters. If not, just + # return None and let default backtrace printing occur. + if len(sorted_list) == 0: + return None + + frame_iterator = FrameIterator(frame) + + # Apply a basic frame decorator to all gdb.Frames. This unifies + # the interface. Python 3.x moved the itertools.imap + # functionality to map(), so check if it is available. + if hasattr(itertools, "imap"): + frame_iterator = itertools.imap(FrameDecorator, frame_iterator) + else: + frame_iterator = map(FrameDecorator, frame_iterator) + + for ff in sorted_list: + frame_iterator = ff.filter(frame_iterator) + + # Slicing + + # Is this a slice from the end of the backtrace, ie bt -2? + if frame_low < 0: + count = 0 + slice_length = abs(frame_low) + # We cannot use MAXLEN argument for deque as it is 2.6 onwards + # and some GDB versions might be < 2.6. + sliced = collections.deque() + + for frame_item in frame_iterator: + if count >= slice_length: + sliced.popleft() + count = count + 1 + sliced.append(frame_item) + + return iter(sliced) + + # -1 for frame_high means until the end of the backtrace. Set to + # None if that is the case, to indicate to itertools.islice to + # slice to the end of the iterator. + if frame_high == -1: + frame_high = None + else: + # As frames start from 0, add one to frame_high so islice + # correctly finds the end + frame_high = frame_high + 1 + + sliced = itertools.islice(frame_iterator, frame_low, frame_high) + + return sliced diff --git a/share/gdb/python/gdb/function/__init__.py b/share/gdb/python/gdb/function/__init__.py new file mode 100644 index 0000000..34496c1 --- /dev/null +++ b/share/gdb/python/gdb/function/__init__.py @@ -0,0 +1,14 @@ +# Copyright (C) 2012-2023 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/>. diff --git a/share/gdb/python/gdb/function/as_string.py b/share/gdb/python/gdb/function/as_string.py new file mode 100644 index 0000000..4bd6579 --- /dev/null +++ b/share/gdb/python/gdb/function/as_string.py @@ -0,0 +1,38 @@ +# Copyright (C) 2016-2023 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/>. + +import gdb + + +class _AsString(gdb.Function): + """Return the string representation of a value. + + Usage: $_as_string (VALUE) + + Arguments: + + VALUE: any value + + Returns: + The string representation of the value.""" + + def __init__(self): + super(_AsString, self).__init__("_as_string") + + def invoke(self, val): + return str(val) + + +_AsString() diff --git a/share/gdb/python/gdb/function/caller_is.py b/share/gdb/python/gdb/function/caller_is.py new file mode 100644 index 0000000..220b222 --- /dev/null +++ b/share/gdb/python/gdb/function/caller_is.py @@ -0,0 +1,157 @@ +# Caller-is functions. +# Copyright (C) 2008-2023 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/>. + +import gdb +import re + + +class CallerIs(gdb.Function): + """Check the calling function's name. + + Usage: $_caller_is (NAME [, NUMBER-OF-FRAMES]) + + Arguments: + + NAME: The name of the function to search for. + + NUMBER-OF-FRAMES: How many stack frames to traverse back from the currently + selected frame to compare with. If the value is greater than the depth of + the stack from that point then the result is False. + The default is 1. + + Returns: + True if the function's name at the specified frame is equal to NAME.""" + + def __init__(self): + super(CallerIs, self).__init__("_caller_is") + + def invoke(self, name, nframes=1): + if nframes < 0: + raise ValueError("nframes must be >= 0") + frame = gdb.selected_frame() + while nframes > 0: + frame = frame.older() + if frame is None: + return False + nframes = nframes - 1 + return frame.name() == name.string() + + +class CallerMatches(gdb.Function): + """Compare the calling function's name with a regexp. + + Usage: $_caller_matches (REGEX [, NUMBER-OF-FRAMES]) + + Arguments: + + REGEX: The regular expression to compare the function's name with. + + NUMBER-OF-FRAMES: How many stack frames to traverse back from the currently + selected frame to compare with. If the value is greater than the depth of + the stack from that point then the result is False. + The default is 1. + + Returns: + True if the function's name at the specified frame matches REGEX.""" + + def __init__(self): + super(CallerMatches, self).__init__("_caller_matches") + + def invoke(self, name, nframes=1): + if nframes < 0: + raise ValueError("nframes must be >= 0") + frame = gdb.selected_frame() + while nframes > 0: + frame = frame.older() + if frame is None: + return False + nframes = nframes - 1 + return re.match(name.string(), frame.name()) is not None + + +class AnyCallerIs(gdb.Function): + """Check all calling function's names. + + Usage: $_any_caller_is (NAME [, NUMBER-OF-FRAMES]) + + Arguments: + + NAME: The name of the function to search for. + + NUMBER-OF-FRAMES: How many stack frames to traverse back from the currently + selected frame to compare with. If the value is greater than the depth of + the stack from that point then the result is False. + The default is 1. + + Returns: + True if any function's name is equal to NAME.""" + + def __init__(self): + super(AnyCallerIs, self).__init__("_any_caller_is") + + def invoke(self, name, nframes=1): + if nframes < 0: + raise ValueError("nframes must be >= 0") + frame = gdb.selected_frame() + while nframes >= 0: + if frame.name() == name.string(): + return True + frame = frame.older() + if frame is None: + return False + nframes = nframes - 1 + return False + + +class AnyCallerMatches(gdb.Function): + """Compare all calling function's names with a regexp. + + Usage: $_any_caller_matches (REGEX [, NUMBER-OF-FRAMES]) + + Arguments: + + REGEX: The regular expression to compare the function's name with. + + NUMBER-OF-FRAMES: How many stack frames to traverse back from the currently + selected frame to compare with. If the value is greater than the depth of + the stack from that point then the result is False. + The default is 1. + + Returns: + True if any function's name matches REGEX.""" + + def __init__(self): + super(AnyCallerMatches, self).__init__("_any_caller_matches") + + def invoke(self, name, nframes=1): + if nframes < 0: + raise ValueError("nframes must be >= 0") + frame = gdb.selected_frame() + name_re = re.compile(name.string()) + while nframes >= 0: + if name_re.match(frame.name()) is not None: + return True + frame = frame.older() + if frame is None: + return False + nframes = nframes - 1 + return False + + +CallerIs() +CallerMatches() +AnyCallerIs() +AnyCallerMatches() diff --git a/share/gdb/python/gdb/function/strfns.py b/share/gdb/python/gdb/function/strfns.py new file mode 100644 index 0000000..7dc464b --- /dev/null +++ b/share/gdb/python/gdb/function/strfns.py @@ -0,0 +1,104 @@ +# Useful gdb string convenience functions. +# Copyright (C) 2012-2023 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/>. + +"""$_memeq, $_strlen, $_streq, $_regex""" + +import gdb +import re + + +class _MemEq(gdb.Function): + """$_memeq - compare bytes of memory. + + Usage: $_memeq (A, B, LEN) + + Returns: + True if LEN bytes at A and B compare equally.""" + + def __init__(self): + super(_MemEq, self).__init__("_memeq") + + def invoke(self, a, b, length): + if length < 0: + raise ValueError("length must be non-negative") + if length == 0: + return True + # The argument(s) to vector are [low_bound,]high_bound. + byte_vector = gdb.lookup_type("char").vector(length - 1) + ptr_byte_vector = byte_vector.pointer() + a_ptr = a.reinterpret_cast(ptr_byte_vector) + b_ptr = b.reinterpret_cast(ptr_byte_vector) + return a_ptr.dereference() == b_ptr.dereference() + + +class _StrLen(gdb.Function): + """$_strlen - compute string length. + + Usage: $_strlen (A) + + Returns: + Length of string A, assumed to be a string in the current language.""" + + def __init__(self): + super(_StrLen, self).__init__("_strlen") + + def invoke(self, a): + s = a.string() + return len(s) + + +class _StrEq(gdb.Function): + """$_streq - check string equality. + + Usage: $_streq (A, B) + + Returns: + True if A and B are identical strings in the current language. + + Example (amd64-linux): + catch syscall open + cond $bpnum $_streq((char*) $rdi, "foo")""" + + def __init__(self): + super(_StrEq, self).__init__("_streq") + + def invoke(self, a, b): + return a.string() == b.string() + + +class _RegEx(gdb.Function): + """$_regex - check if a string matches a regular expression. + + Usage: $_regex (STRING, REGEX) + + Returns: + True if string STRING (in the current language) matches the + regular expression REGEX.""" + + def __init__(self): + super(_RegEx, self).__init__("_regex") + + def invoke(self, string, regex): + s = string.string() + r = re.compile(regex.string()) + return bool(r.match(s)) + + +# GDB will import us automagically via gdb/__init__.py. +_MemEq() +_StrLen() +_StrEq() +_RegEx() diff --git a/share/gdb/python/gdb/printer/__init__.py b/share/gdb/python/gdb/printer/__init__.py new file mode 100644 index 0000000..b25b7ea --- /dev/null +++ b/share/gdb/python/gdb/printer/__init__.py @@ -0,0 +1,14 @@ +# Copyright (C) 2014-2023 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/>. diff --git a/share/gdb/python/gdb/printer/bound_registers.py b/share/gdb/python/gdb/printer/bound_registers.py new file mode 100644 index 0000000..e156675 --- /dev/null +++ b/share/gdb/python/gdb/printer/bound_registers.py @@ -0,0 +1,40 @@ +# Pretty-printers for bounds registers. +# Copyright (C) 2013-2023 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/>. + +import sys + +import gdb.printing + + +class MpxBound128Printer: + """Adds size field to a mpx __gdb_builtin_type_bound128 type.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + upper = self.val["ubound"] + lower = self.val["lbound"] + size = upper - lower + if size > -1: + size = size + 1 + result = "{lbound = %s, ubound = %s} : size %s" % (lower, upper, size) + return result + + +gdb.printing.add_builtin_pretty_printer( + "mpx_bound128", "^builtin_type_bound128", MpxBound128Printer +) diff --git a/share/gdb/python/gdb/printing.py b/share/gdb/python/gdb/printing.py new file mode 100644 index 0000000..d6b7b85 --- /dev/null +++ b/share/gdb/python/gdb/printing.py @@ -0,0 +1,285 @@ +# Pretty-printer utilities. +# Copyright (C) 2010-2023 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/>. + +"""Utilities for working with pretty-printers.""" + +import gdb +import gdb.types +import re +import sys + + +class PrettyPrinter(object): + """A basic pretty-printer. + + Attributes: + name: A unique string among all printers for the context in which + it is defined (objfile, progspace, or global(gdb)), and should + meaningfully describe what can be pretty-printed. + E.g., "StringPiece" or "protobufs". + subprinters: An iterable object with each element having a `name' + attribute, and, potentially, "enabled" attribute. + Or this is None if there are no subprinters. + enabled: A boolean indicating if the printer is enabled. + + Subprinters are for situations where "one" pretty-printer is actually a + collection of several printers. E.g., The libstdc++ pretty-printer has + a pretty-printer for each of several different types, based on regexps. + """ + + # While one might want to push subprinters into the subclass, it's + # present here to formalize such support to simplify + # commands/pretty_printers.py. + + def __init__(self, name, subprinters=None): + self.name = name + self.subprinters = subprinters + self.enabled = True + + def __call__(self, val): + # The subclass must define this. + raise NotImplementedError("PrettyPrinter __call__") + + +class SubPrettyPrinter(object): + """Baseclass for sub-pretty-printers. + + Sub-pretty-printers needn't use this, but it formalizes what's needed. + + Attributes: + name: The name of the subprinter. + enabled: A boolean indicating if the subprinter is enabled. + """ + + def __init__(self, name): + self.name = name + self.enabled = True + + +def register_pretty_printer(obj, printer, replace=False): + """Register pretty-printer PRINTER with OBJ. + + The printer is added to the front of the search list, thus one can override + an existing printer if one needs to. Use a different name when overriding + an existing printer, otherwise an exception will be raised; multiple + printers with the same name are disallowed. + + Arguments: + obj: Either an objfile, progspace, or None (in which case the printer + is registered globally). + printer: Either a function of one argument (old way) or any object + which has attributes: name, enabled, __call__. + replace: If True replace any existing copy of the printer. + Otherwise if the printer already exists raise an exception. + + Returns: + Nothing. + + Raises: + TypeError: A problem with the type of the printer. + ValueError: The printer's name contains a semicolon ";". + RuntimeError: A printer with the same name is already registered. + + If the caller wants the printer to be listable and disableable, it must + follow the PrettyPrinter API. This applies to the old way (functions) too. + If printer is an object, __call__ is a method of two arguments: + self, and the value to be pretty-printed. See PrettyPrinter. + """ + + # Watch for both __name__ and name. + # Functions get the former for free, but we don't want to use an + # attribute named __foo__ for pretty-printers-as-objects. + # If printer has both, we use `name'. + if not hasattr(printer, "__name__") and not hasattr(printer, "name"): + raise TypeError("printer missing attribute: name") + if hasattr(printer, "name") and not hasattr(printer, "enabled"): + raise TypeError("printer missing attribute: enabled") + if not hasattr(printer, "__call__"): + raise TypeError("printer missing attribute: __call__") + + if hasattr(printer, "name"): + name = printer.name + else: + name = printer.__name__ + if obj is None or obj is gdb: + if gdb.parameter("verbose"): + gdb.write("Registering global %s pretty-printer ...\n" % name) + obj = gdb + else: + if gdb.parameter("verbose"): + gdb.write( + "Registering %s pretty-printer for %s ...\n" % (name, obj.filename) + ) + + # Printers implemented as functions are old-style. In order to not risk + # breaking anything we do not check __name__ here. + if hasattr(printer, "name"): + if not isinstance(printer.name, str): + raise TypeError("printer name is not a string") + # If printer provides a name, make sure it doesn't contain ";". + # Semicolon is used by the info/enable/disable pretty-printer commands + # to delimit subprinters. + if printer.name.find(";") >= 0: + raise ValueError("semicolon ';' in printer name") + # Also make sure the name is unique. + # Alas, we can't do the same for functions and __name__, they could + # all have a canonical name like "lookup_function". + # PERF: gdb records printers in a list, making this inefficient. + i = 0 + for p in obj.pretty_printers: + if hasattr(p, "name") and p.name == printer.name: + if replace: + del obj.pretty_printers[i] + break + else: + raise RuntimeError( + "pretty-printer already registered: %s" % printer.name + ) + i = i + 1 + + obj.pretty_printers.insert(0, printer) + + +class RegexpCollectionPrettyPrinter(PrettyPrinter): + """Class for implementing a collection of regular-expression based pretty-printers. + + Intended usage: + + pretty_printer = RegexpCollectionPrettyPrinter("my_library") + pretty_printer.add_printer("myclass1", "^myclass1$", MyClass1Printer) + ... + pretty_printer.add_printer("myclassN", "^myclassN$", MyClassNPrinter) + register_pretty_printer(obj, pretty_printer) + """ + + class RegexpSubprinter(SubPrettyPrinter): + def __init__(self, name, regexp, gen_printer): + super(RegexpCollectionPrettyPrinter.RegexpSubprinter, self).__init__(name) + self.regexp = regexp + self.gen_printer = gen_printer + self.compiled_re = re.compile(regexp) + + def __init__(self, name): + super(RegexpCollectionPrettyPrinter, self).__init__(name, []) + + def add_printer(self, name, regexp, gen_printer): + """Add a printer to the list. + + The printer is added to the end of the list. + + Arguments: + name: The name of the subprinter. + regexp: The regular expression, as a string. + gen_printer: A function/method that given a value returns an + object to pretty-print it. + + Returns: + Nothing. + """ + + # NOTE: A previous version made the name of each printer the regexp. + # That makes it awkward to pass to the enable/disable commands (it's + # cumbersome to make a regexp of a regexp). So now the name is a + # separate parameter. + + self.subprinters.append(self.RegexpSubprinter(name, regexp, gen_printer)) + + def __call__(self, val): + """Lookup the pretty-printer for the provided value.""" + + # Get the type name. + typename = gdb.types.get_basic_type(val.type).tag + if not typename: + typename = val.type.name + if not typename: + return None + + # Iterate over table of type regexps to determine + # if a printer is registered for that type. + # Return an instantiation of the printer if found. + for printer in self.subprinters: + if printer.enabled and printer.compiled_re.search(typename): + return printer.gen_printer(val) + + # Cannot find a pretty printer. Return None. + return None + + +# A helper class for printing enum types. This class is instantiated +# with a list of enumerators to print a particular Value. +class _EnumInstance: + def __init__(self, enumerators, val): + self.enumerators = enumerators + self.val = val + + def to_string(self): + flag_list = [] + v = int(self.val) + any_found = False + for (e_name, e_value) in self.enumerators: + if v & e_value != 0: + flag_list.append(e_name) + v = v & ~e_value + any_found = True + if not any_found or v != 0: + # Leftover value. + flag_list.append("<unknown: 0x%x>" % v) + return "0x%x [%s]" % (int(self.val), " | ".join(flag_list)) + + +class FlagEnumerationPrinter(PrettyPrinter): + """A pretty-printer which can be used to print a flag-style enumeration. + A flag-style enumeration is one where the enumerators are or'd + together to create values. The new printer will print these + symbolically using '|' notation. The printer must be registered + manually. This printer is most useful when an enum is flag-like, + but has some overlap. GDB's built-in printing will not handle + this case, but this printer will attempt to.""" + + def __init__(self, enum_type): + super(FlagEnumerationPrinter, self).__init__(enum_type) + self.initialized = False + + def __call__(self, val): + if not self.initialized: + self.initialized = True + flags = gdb.lookup_type(self.name) + self.enumerators = [] + for field in flags.fields(): + self.enumerators.append((field.name, field.enumval)) + # Sorting the enumerators by value usually does the right + # thing. + self.enumerators.sort(key=lambda x: x[1]) + + if self.enabled: + return _EnumInstance(self.enumerators, val) + else: + return None + + +# Builtin pretty-printers. +# The set is defined as empty, and files in printing/*.py add their printers +# to this with add_builtin_pretty_printer. + +_builtin_pretty_printers = RegexpCollectionPrettyPrinter("builtin") + +register_pretty_printer(None, _builtin_pretty_printers) + +# Add a builtin pretty-printer. + + +def add_builtin_pretty_printer(name, regexp, printer): + _builtin_pretty_printers.add_printer(name, regexp, printer) diff --git a/share/gdb/python/gdb/prompt.py b/share/gdb/python/gdb/prompt.py new file mode 100644 index 0000000..9bbfcb9 --- /dev/null +++ b/share/gdb/python/gdb/prompt.py @@ -0,0 +1,163 @@ +# Extended prompt utilities. +# Copyright (C) 2011-2023 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/>. + +""" Extended prompt library functions.""" + +import gdb +import os + + +def _prompt_pwd(ignore): + "The current working directory." + return os.getcwd() + + +def _prompt_object_attr(func, what, attr, nattr): + """Internal worker for fetching GDB attributes.""" + if attr is None: + attr = nattr + try: + obj = func() + except gdb.error: + return "<no %s>" % what + if hasattr(obj, attr): + result = getattr(obj, attr) + if callable(result): + result = result() + return result + else: + return "<no attribute %s on current %s>" % (attr, what) + + +def _prompt_frame(attr): + "The selected frame; an argument names a frame parameter." + return _prompt_object_attr(gdb.selected_frame, "frame", attr, "name") + + +def _prompt_thread(attr): + "The selected thread; an argument names a thread parameter." + return _prompt_object_attr(gdb.selected_thread, "thread", attr, "num") + + +def _prompt_version(attr): + "The version of GDB." + return gdb.VERSION + + +def _prompt_esc(attr): + "The ESC character." + return "\033" + + +def _prompt_bs(attr): + "A backslash." + return "\\" + + +def _prompt_n(attr): + "A newline." + return "\n" + + +def _prompt_r(attr): + "A carriage return." + return "\r" + + +def _prompt_param(attr): + "A parameter's value; the argument names the parameter." + return gdb.parameter(attr) + + +def _prompt_noprint_begin(attr): + "Begins a sequence of non-printing characters." + return "\001" + + +def _prompt_noprint_end(attr): + "Ends a sequence of non-printing characters." + return "\002" + + +prompt_substitutions = { + "e": _prompt_esc, + "\\": _prompt_bs, + "n": _prompt_n, + "r": _prompt_r, + "v": _prompt_version, + "w": _prompt_pwd, + "f": _prompt_frame, + "t": _prompt_thread, + "p": _prompt_param, + "[": _prompt_noprint_begin, + "]": _prompt_noprint_end, +} + + +def prompt_help(): + """Generate help dynamically from the __doc__ strings of attribute + functions.""" + + result = "" + keys = sorted(prompt_substitutions.keys()) + for key in keys: + result += " \\%s\t%s\n" % (key, prompt_substitutions[key].__doc__) + result += """ +A substitution can be used in a simple form, like "\\f". +An argument can also be passed to it, like "\\f{name}". +The meaning of the argument depends on the particular substitution.""" + return result + + +def substitute_prompt(prompt): + "Perform substitutions on PROMPT." + + result = "" + plen = len(prompt) + i = 0 + while i < plen: + if prompt[i] == "\\": + i = i + 1 + if i >= plen: + break + cmdch = prompt[i] + + if cmdch in prompt_substitutions: + cmd = prompt_substitutions[cmdch] + + if i + 1 < plen and prompt[i + 1] == "{": + j = i + 1 + while j < plen and prompt[j] != "}": + j = j + 1 + # Just ignore formatting errors. + if j >= plen or prompt[j] != "}": + arg = None + else: + arg = prompt[i + 2 : j] + i = j + else: + arg = None + result += str(cmd(arg)) + else: + # Unrecognized escapes are turned into the escaped + # character itself. + result += prompt[i] + else: + result += prompt[i] + + i = i + 1 + + return result diff --git a/share/gdb/python/gdb/styling.py b/share/gdb/python/gdb/styling.py new file mode 100644 index 0000000..8540ab2 --- /dev/null +++ b/share/gdb/python/gdb/styling.py @@ -0,0 +1,101 @@ +# Styling related hooks. +# Copyright (C) 2010-2023 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/>. + +"""Utilities for styling.""" + +import gdb + +try: + from pygments import formatters, lexers, highlight + from pygments.token import Error, Comment, Text + from pygments.filters import TokenMergeFilter + + _formatter = None + + def get_formatter(): + global _formatter + if _formatter is None: + _formatter = formatters.TerminalFormatter() + return _formatter + + def colorize(filename, contents): + # Don't want any errors. + try: + lexer = lexers.get_lexer_for_filename(filename, stripnl=False) + formatter = get_formatter() + return highlight(contents, lexer, formatter).encode( + gdb.host_charset(), "backslashreplace" + ) + except: + return None + + class HandleNasmComments(TokenMergeFilter): + @staticmethod + def fix_comments(lexer, stream): + in_comment = False + for ttype, value in stream: + if ttype is Error and value == "#": + in_comment = True + if in_comment: + if ttype is Text and value == "\n": + in_comment = False + else: + ttype = Comment.Single + yield ttype, value + + def filter(self, lexer, stream): + f = HandleNasmComments.fix_comments + return super().filter(lexer, f(lexer, stream)) + + _asm_lexers = {} + + def __get_asm_lexer(gdbarch): + lexer_type = "asm" + try: + # For an i386 based architecture, in 'intel' mode, use the nasm + # lexer. + flavor = gdb.parameter("disassembly-flavor") + if flavor == "intel" and gdbarch.name()[:4] == "i386": + lexer_type = "nasm" + except: + # If GDB is built without i386 support then attempting to fetch + # the 'disassembly-flavor' parameter will throw an error, which we + # ignore. + pass + + global _asm_lexers + if lexer_type not in _asm_lexers: + _asm_lexers[lexer_type] = lexers.get_lexer_by_name(lexer_type) + _asm_lexers[lexer_type].add_filter(HandleNasmComments()) + _asm_lexers[lexer_type].add_filter("raiseonerror") + return _asm_lexers[lexer_type] + + def colorize_disasm(content, gdbarch): + # Don't want any errors. + try: + lexer = __get_asm_lexer(gdbarch) + formatter = get_formatter() + return highlight(content, lexer, formatter).rstrip().encode() + except: + return content + +except: + + def colorize(filename, contents): + return None + + def colorize_disasm(content, gdbarch): + return None diff --git a/share/gdb/python/gdb/types.py b/share/gdb/python/gdb/types.py new file mode 100644 index 0000000..dcec301 --- /dev/null +++ b/share/gdb/python/gdb/types.py @@ -0,0 +1,183 @@ +# Type utilities. +# Copyright (C) 2010-2023 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/>. + +"""Utilities for working with gdb.Types.""" + +import gdb + + +def get_basic_type(type_): + """Return the "basic" type of a type. + + Arguments: + type_: The type to reduce to its basic type. + + Returns: + type_ with const/volatile is stripped away, + and typedefs/references converted to the underlying type. + """ + + while ( + type_.code == gdb.TYPE_CODE_REF + or type_.code == gdb.TYPE_CODE_RVALUE_REF + or type_.code == gdb.TYPE_CODE_TYPEDEF + ): + if type_.code == gdb.TYPE_CODE_REF or type_.code == gdb.TYPE_CODE_RVALUE_REF: + type_ = type_.target() + else: + type_ = type_.strip_typedefs() + return type_.unqualified() + + +def has_field(type_, field): + """Return True if a type has the specified field. + + Arguments: + type_: The type to examine. + It must be one of gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION. + field: The name of the field to look up. + + Returns: + True if the field is present either in type_ or any baseclass. + + Raises: + TypeError: The type is not a struct or union. + """ + + type_ = get_basic_type(type_) + if type_.code != gdb.TYPE_CODE_STRUCT and type_.code != gdb.TYPE_CODE_UNION: + raise TypeError("not a struct or union") + for f in type_.fields(): + if f.is_base_class: + if has_field(f.type, field): + return True + else: + # NOTE: f.name could be None + if f.name == field: + return True + return False + + +def make_enum_dict(enum_type): + """Return a dictionary from a program's enum type. + + Arguments: + enum_type: The enum to compute the dictionary for. + + Returns: + The dictionary of the enum. + + Raises: + TypeError: The type is not an enum. + """ + + if enum_type.code != gdb.TYPE_CODE_ENUM: + raise TypeError("not an enum type") + enum_dict = {} + for field in enum_type.fields(): + # The enum's value is stored in "enumval". + enum_dict[field.name] = field.enumval + return enum_dict + + +def deep_items(type_): + """Return an iterator that recursively traverses anonymous fields. + + Arguments: + type_: The type to traverse. It should be one of + gdb.TYPE_CODE_STRUCT or gdb.TYPE_CODE_UNION. + + Returns: + an iterator similar to gdb.Type.iteritems(), i.e., it returns + pairs of key, value, but for any anonymous struct or union + field that field is traversed recursively, depth-first. + """ + for k, v in type_.iteritems(): + if k: + yield k, v + else: + for i in deep_items(v.type): + yield i + + +class TypePrinter(object): + """The base class for type printers. + + Instances of this type can be used to substitute type names during + 'ptype'. + + A type printer must have at least 'name' and 'enabled' attributes, + and supply an 'instantiate' method. + + The 'instantiate' method must either return None, or return an + object which has a 'recognize' method. This method must accept a + gdb.Type argument and either return None, meaning that the type + was not recognized, or a string naming the type. + """ + + def __init__(self, name): + self.name = name + self.enabled = True + + def instantiate(self): + return None + + +# Helper function for computing the list of type recognizers. +def _get_some_type_recognizers(result, plist): + for printer in plist: + if printer.enabled: + inst = printer.instantiate() + if inst is not None: + result.append(inst) + return None + + +def get_type_recognizers(): + "Return a list of the enabled type recognizers for the current context." + result = [] + + # First try the objfiles. + for objfile in gdb.objfiles(): + _get_some_type_recognizers(result, objfile.type_printers) + # Now try the program space. + _get_some_type_recognizers(result, gdb.current_progspace().type_printers) + # Finally, globals. + _get_some_type_recognizers(result, gdb.type_printers) + + return result + + +def apply_type_recognizers(recognizers, type_obj): + """Apply the given list of type recognizers to the type TYPE_OBJ. + If any recognizer in the list recognizes TYPE_OBJ, returns the name + given by the recognizer. Otherwise, this returns None.""" + for r in recognizers: + result = r.recognize(type_obj) + if result is not None: + return result + return None + + +def register_type_printer(locus, printer): + """Register a type printer. + PRINTER is the type printer instance. + LOCUS is either an objfile, a program space, or None, indicating + global registration.""" + + if locus is None: + locus = gdb + locus.type_printers.insert(0, printer) diff --git a/share/gdb/python/gdb/unwinder.py b/share/gdb/python/gdb/unwinder.py new file mode 100644 index 0000000..a854d8d --- /dev/null +++ b/share/gdb/python/gdb/unwinder.py @@ -0,0 +1,95 @@ +# Copyright (C) 2015-2023 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/>. + +"""Unwinder class and register_unwinder function.""" + +import gdb + + +class Unwinder(object): + """Base class (or a template) for frame unwinders written in Python. + + An unwinder has a single method __call__ and the attributes + described below. + + Attributes: + name: The name of the unwinder. + enabled: A boolean indicating whether the unwinder is enabled. + """ + + def __init__(self, name): + """Constructor. + + Args: + name: An identifying name for the unwinder. + """ + self.name = name + self.enabled = True + + def __call__(self, pending_frame): + """GDB calls this method to unwind a frame. + + Arguments: + pending_frame: gdb.PendingFrame instance. + + Returns: + gdb.UnwindInfo instance. + """ + raise NotImplementedError("Unwinder __call__.") + + +def register_unwinder(locus, unwinder, replace=False): + """Register unwinder in given locus. + + The unwinder is prepended to the locus's unwinders list. Unwinder + name should be unique. + + Arguments: + locus: Either an objfile, progspace, or None (in which case + the unwinder is registered globally). + unwinder: An object of a gdb.Unwinder subclass + replace: If True, replaces existing unwinder with the same name. + Otherwise, raises exception if unwinder with the same + name already exists. + + Returns: + Nothing. + + Raises: + RuntimeError: Unwinder name is not unique + TypeError: Bad locus type + """ + if locus is None: + if gdb.parameter("verbose"): + gdb.write("Registering global %s unwinder ...\n" % unwinder.name) + locus = gdb + elif isinstance(locus, gdb.Objfile) or isinstance(locus, gdb.Progspace): + if gdb.parameter("verbose"): + gdb.write( + "Registering %s unwinder for %s ...\n" % (unwinder.name, locus.filename) + ) + else: + raise TypeError("locus should be gdb.Objfile or gdb.Progspace or None") + + i = 0 + for needle in locus.frame_unwinders: + if needle.name == unwinder.name: + if replace: + del locus.frame_unwinders[i] + else: + raise RuntimeError("Unwinder %s already exists." % unwinder.name) + i += 1 + locus.frame_unwinders.insert(0, unwinder) + gdb.invalidate_cached_frames() diff --git a/share/gdb/python/gdb/xmethod.py b/share/gdb/python/gdb/xmethod.py new file mode 100644 index 0000000..ab6cd28 --- /dev/null +++ b/share/gdb/python/gdb/xmethod.py @@ -0,0 +1,274 @@ +# Python side of the support for xmethods. +# Copyright (C) 2013-2023 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/>. + +"""Utilities for defining xmethods""" + +import gdb +import re +import sys + + +class XMethod(object): + """Base class (or a template) for an xmethod description. + + Currently, the description requires only the 'name' and 'enabled' + attributes. Description objects are managed by 'XMethodMatcher' + objects (see below). Note that this is only a template for the + interface of the XMethodMatcher.methods objects. One could use + this class or choose to use an object which supports this exact same + interface. Also, an XMethodMatcher can choose not use it 'methods' + attribute. In such cases this class (or an equivalent) is not used. + + Attributes: + name: The name of the xmethod. + enabled: A boolean indicating if the xmethod is enabled. + """ + + def __init__(self, name): + self.name = name + self.enabled = True + + +class XMethodMatcher(object): + """Abstract base class for matching an xmethod. + + When looking for xmethods, GDB invokes the `match' method of a + registered xmethod matcher to match the object type and method name. + The `match' method in concrete classes derived from this class should + return an `XMethodWorker' object, or a list of `XMethodWorker' + objects if there is a match (see below for 'XMethodWorker' class). + + Attributes: + name: The name of the matcher. + enabled: A boolean indicating if the matcher is enabled. + methods: A sequence of objects of type 'XMethod', or objects + which have at least the attributes of an 'XMethod' object. + This list is used by the 'enable'/'disable'/'info' commands to + enable/disable/list the xmethods registered with GDB. See + the 'match' method below to know how this sequence is used. + This attribute is None if the matcher chooses not have any + xmethods managed by it. + """ + + def __init__(self, name): + """ + Args: + name: An identifying name for the xmethod or the group of + xmethods returned by the `match' method. + """ + self.name = name + self.enabled = True + self.methods = None + + def match(self, class_type, method_name): + """Match class type and method name. + + In derived classes, it should return an XMethodWorker object, or a + sequence of 'XMethodWorker' objects. Only those xmethod workers + whose corresponding 'XMethod' descriptor object is enabled should be + returned. + + Args: + class_type: The class type (gdb.Type object) to match. + method_name: The name (string) of the method to match. + """ + raise NotImplementedError("XMethodMatcher match") + + +class XMethodWorker(object): + """Base class for all xmethod workers defined in Python. + + An xmethod worker is an object which matches the method arguments, and + invokes the method when GDB wants it to. Internally, GDB first invokes the + 'get_arg_types' method to perform overload resolution. If GDB selects to + invoke this Python xmethod, then it invokes it via the overridden + '__call__' method. The 'get_result_type' method is used to implement + 'ptype' on the xmethod. + + Derived classes should override the 'get_arg_types', 'get_result_type' + and '__call__' methods. + """ + + def get_arg_types(self): + """Return arguments types of an xmethod. + + A sequence of gdb.Type objects corresponding to the arguments of the + xmethod are returned. If the xmethod takes no arguments, then 'None' + or an empty sequence is returned. If the xmethod takes only a single + argument, then a gdb.Type object or a sequence with a single gdb.Type + element is returned. + """ + raise NotImplementedError("XMethodWorker get_arg_types") + + def get_result_type(self, *args): + """Return the type of the result of the xmethod. + + Args: + args: Arguments to the method. Each element of the tuple is a + gdb.Value object. The first element is the 'this' pointer + value. These are the same arguments passed to '__call__'. + + Returns: + A gdb.Type object representing the type of the result of the + xmethod. + """ + raise NotImplementedError("XMethodWorker get_result_type") + + def __call__(self, *args): + """Invoke the xmethod. + + Args: + args: Arguments to the method. Each element of the tuple is a + gdb.Value object. The first element is the 'this' pointer + value. + + Returns: + A gdb.Value corresponding to the value returned by the xmethod. + Returns 'None' if the method does not return anything. + """ + raise NotImplementedError("XMethodWorker __call__") + + +class SimpleXMethodMatcher(XMethodMatcher): + """A utility class to implement simple xmethod mathers and workers. + + See the __init__ method below for information on how instances of this + class can be used. + + For simple classes and methods, one can choose to use this class. For + complex xmethods, which need to replace/implement template methods on + possibly template classes, one should implement their own xmethod + matchers and workers. See py-xmethods.py in testsuite/gdb.python + directory of the GDB source tree for examples. + """ + + class SimpleXMethodWorker(XMethodWorker): + def __init__(self, method_function, arg_types): + self._arg_types = arg_types + self._method_function = method_function + + def get_arg_types(self): + return self._arg_types + + def __call__(self, *args): + return self._method_function(*args) + + def __init__( + self, name, class_matcher, method_matcher, method_function, *arg_types + ): + """ + Args: + name: Name of the xmethod matcher. + class_matcher: A regular expression used to match the name of the + class whose method this xmethod is implementing/replacing. + method_matcher: A regular expression used to match the name of the + method this xmethod is implementing/replacing. + method_function: A Python callable which would be called via the + 'invoke' method of the worker returned by the objects of this + class. This callable should accept the object (*this) as the + first argument followed by the rest of the arguments to the + method. All arguments to this function should be gdb.Value + objects. + arg_types: The gdb.Type objects corresponding to the arguments that + this xmethod takes. It can be None, or an empty sequence, + or a single gdb.Type object, or a sequence of gdb.Type objects. + """ + XMethodMatcher.__init__(self, name) + assert callable(method_function), ( + "The 'method_function' argument to 'SimpleXMethodMatcher' " + "__init__ method should be a callable." + ) + self._method_function = method_function + self._class_matcher = class_matcher + self._method_matcher = method_matcher + self._arg_types = arg_types + + def match(self, class_type, method_name): + cm = re.match(self._class_matcher, str(class_type.unqualified().tag)) + mm = re.match(self._method_matcher, method_name) + if cm and mm: + return SimpleXMethodMatcher.SimpleXMethodWorker( + self._method_function, self._arg_types + ) + + +# A helper function for register_xmethod_matcher which returns an error +# object if MATCHER is not having the requisite attributes in the proper +# format. + + +def _validate_xmethod_matcher(matcher): + if not hasattr(matcher, "match"): + return TypeError("Xmethod matcher is missing method: match") + if not hasattr(matcher, "name"): + return TypeError("Xmethod matcher is missing attribute: name") + if not hasattr(matcher, "enabled"): + return TypeError("Xmethod matcher is missing attribute: enabled") + if not isinstance(matcher.name, str): + return TypeError("Attribute 'name' of xmethod matcher is not a " "string") + if matcher.name.find(";") >= 0: + return ValueError("Xmethod matcher name cannot contain ';' in it") + + +# A helper function for register_xmethod_matcher which looks up an +# xmethod matcher with NAME in LOCUS. Returns the index of the xmethod +# matcher in 'xmethods' sequence attribute of the LOCUS. If NAME is not +# found in LOCUS, then -1 is returned. + + +def _lookup_xmethod_matcher(locus, name): + for i in range(0, len(locus.xmethods)): + if locus.xmethods[i].name == name: + return i + return -1 + + +def register_xmethod_matcher(locus, matcher, replace=False): + """Registers a xmethod matcher MATCHER with a LOCUS. + + Arguments: + locus: The locus in which the xmethods should be registered. + It can be 'None' to indicate that the xmethods should be + registered globally. Or, it could be a gdb.Objfile or a + gdb.Progspace object in which the xmethods should be + registered. + matcher: The xmethod matcher to register with the LOCUS. It + should be an instance of 'XMethodMatcher' class. + replace: If True, replace any existing xmethod matcher with the + same name in the locus. Otherwise, if a matcher with the same name + exists in the locus, raise an exception. + """ + err = _validate_xmethod_matcher(matcher) + if err: + raise err + if not locus: + locus = gdb + if locus == gdb: + locus_name = "global" + else: + locus_name = locus.filename + index = _lookup_xmethod_matcher(locus, matcher.name) + if index >= 0: + if replace: + del locus.xmethods[index] + else: + raise RuntimeError( + "Xmethod matcher already registered with " + "%s: %s" % (locus_name, matcher.name) + ) + if gdb.parameter("verbose"): + gdb.write("Registering xmethod matcher '%s' with %s' ...\n") + locus.xmethods.insert(0, matcher) |