aboutsummaryrefslogtreecommitdiff
path: root/gdb/python/lib
diff options
context:
space:
mode:
authorSasha Smundak <asmundak@google.com>2015-04-01 11:49:12 -0700
committerDoug Evans <dje@google.com>2015-04-01 11:49:12 -0700
commitd11916aa89c43071c08c1f9b4550a01f8eec78e3 (patch)
tree7befd0c4b5e47eba48fec1b2b03888dcffbadd7b /gdb/python/lib
parent79730a3b2683dba745663fa3b907f564bee8a0ef (diff)
downloadbinutils-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__.py38
-rw-r--r--gdb/python/lib/gdb/command/unwinders.py198
-rw-r--r--gdb/python/lib/gdb/unwinder.py94
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)