diff options
author | Sasha Smundak <asmundak@google.com> | 2015-04-01 11:49:12 -0700 |
---|---|---|
committer | Doug Evans <dje@google.com> | 2015-04-01 11:49:12 -0700 |
commit | d11916aa89c43071c08c1f9b4550a01f8eec78e3 (patch) | |
tree | 7befd0c4b5e47eba48fec1b2b03888dcffbadd7b /gdb/python/lib | |
parent | 79730a3b2683dba745663fa3b907f564bee8a0ef (diff) | |
download | binutils-d11916aa89c43071c08c1f9b4550a01f8eec78e3.zip binutils-d11916aa89c43071c08c1f9b4550a01f8eec78e3.tar.gz binutils-d11916aa89c43071c08c1f9b4550a01f8eec78e3.tar.bz2 |
Add support for writing unwinders in Python.
gdb/ChangeLog:
* Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
(SUBDIR_PYTHON_SRCS): Add py-unwind.c.
(py-unwind.o): New recipe.
* NEWS: mention Python frame unwinding.
* data-directory/Makefile.in (PYTHON_FILE_LIST): Add
gdb/unwinder.py and gdb/command/unwinder.py
* python/lib/gdb/__init__.py (packages): Add frame_unwinders
list.
(execute_unwinders): New function.
* python/lib/gdb/command/unwinders.py: New file.
* python/lib/gdb/unwinder.py: New file.
* python/py-objfile.c (objfile_object): Add frame_unwinders field.
(objfpy_dealloc): Decrement frame_unwinders reference count.
(objfpy_initialize): Create frame_unwinders list.
(objfpy_get_frame_unwinders): New function.
(objfpy_set_frame_unwinders): Ditto.
(objfile_getset): Add frame_unwinders attribute to Objfile.
* python/py-progspace.c (pspace_object): Add frame_unwinders field.
(pspy_dealloc): Decrement frame_unwinders reference count.
(pspy_initialize): Create frame_unwinders list.
(pspy_get_frame_unwinders): New function.
(pspy_set_frame_unwinders): Ditto.
(pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
* python/py-unwind.c: New file.
* python/python-internal.h (pspy_get_name_unwinders): New prototype.
(objpy_get_frame_unwinders): New prototype.
(gdbpy_initialize_unwind): New prototype.
* python/python.c (gdbpy_apply_type_printers): Call
gdbpy_initialize_unwind.
gdb/doc/ChangeLog:
* doc/python.texi (Writing a Frame Unwinder in Python): Add
section.
gdb/testsuite/ChangeLog:
* gdb.python/py-unwind-maint.c: New file.
* gdb.python/py-unwind-maint.exp: New test.
* gdb.python/py-unwind-maint.py: New file.
* gdb.python/py-unwind.c: New file.
* gdb.python/py-unwind.exp: New test.
* gdb.python/py-unwind.py: New test.
Diffstat (limited to 'gdb/python/lib')
-rw-r--r-- | gdb/python/lib/gdb/__init__.py | 38 | ||||
-rw-r--r-- | gdb/python/lib/gdb/command/unwinders.py | 198 | ||||
-rw-r--r-- | gdb/python/lib/gdb/unwinder.py | 94 |
3 files changed, 329 insertions, 1 deletions
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 92b06f2..81789e5 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -28,7 +28,7 @@ class _GdbFile (object): # These two are needed in Python 3 encoding = "UTF-8" errors = "strict" - + def close(self): # Do nothing. return None @@ -71,6 +71,42 @@ type_printers = [] 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: + gdb.UnwindInfo instance or None. + """ + for objfile in _gdb.objfiles(): + for unwinder in objfile.frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder(pending_frame) + if unwind_info is not None: + return unwind_info + + current_progspace = _gdb.current_progspace() + for unwinder in current_progspace.frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder(pending_frame) + if unwind_info is not None: + return unwind_info + + for unwinder in frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder(pending_frame) + if unwind_info is not None: + return unwind_info + + return None + # Convenience variable to GDB's python directory PYTHONDIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py new file mode 100644 index 0000000..8530b35 --- /dev/null +++ b/gdb/python/lib/gdb/command/unwinders.py @@ -0,0 +1,198 @@ +# Unwinder commands. +# Copyright 2015 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) + 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/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py new file mode 100644 index 0000000..3554e9c --- /dev/null +++ b/gdb/python/lib/gdb/unwinder.py @@ -0,0 +1,94 @@ +# Copyright (C) 2015 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) |