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.
This commit is contained in:
Sasha Smundak 2015-04-01 11:49:12 -07:00 committed by Doug Evans
parent 79730a3b26
commit d11916aa89
21 changed files with 1808 additions and 2 deletions

View file

@ -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__))

View file

@ -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()

View file

@ -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)