aboutsummaryrefslogtreecommitdiff
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
parent79730a3b2683dba745663fa3b907f564bee8a0ef (diff)
downloadgdb-d11916aa89c43071c08c1f9b4550a01f8eec78e3.zip
gdb-d11916aa89c43071c08c1f9b4550a01f8eec78e3.tar.gz
gdb-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.
-rw-r--r--gdb/ChangeLog32
-rw-r--r--gdb/Makefile.in6
-rw-r--r--gdb/NEWS1
-rw-r--r--gdb/data-directory/Makefile.in2
-rw-r--r--gdb/doc/ChangeLog5
-rw-r--r--gdb/doc/python.texi143
-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
-rw-r--r--gdb/python/py-objfile.c53
-rw-r--r--gdb/python/py-progspace.c53
-rw-r--r--gdb/python/py-unwind.c788
-rw-r--r--gdb/python/python-internal.h4
-rw-r--r--gdb/python/python.c3
-rw-r--r--gdb/testsuite/ChangeLog9
-rw-r--r--gdb/testsuite/gdb.python/py-unwind-maint.c24
-rw-r--r--gdb/testsuite/gdb.python/py-unwind-maint.exp64
-rw-r--r--gdb/testsuite/gdb.python/py-unwind-maint.py59
-rw-r--r--gdb/testsuite/gdb.python/py-unwind.c81
-rw-r--r--gdb/testsuite/gdb.python/py-unwind.exp54
-rw-r--r--gdb/testsuite/gdb.python/py-unwind.py99
21 files changed, 1808 insertions, 2 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index b85b437..a712076 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,35 @@
+2015-04-01 Sasha Smundak <asmundak@google.com>
+
+ * 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.
+
2015-04-01 Pedro Alves <palves@redhat.com>
* infrun.c (resume): Check currently_stepping after clearing
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index d4d4a3e..8f43617 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -404,6 +404,7 @@ SUBDIR_PYTHON_OBS = \
py-symtab.o \
py-threadevent.o \
py-type.o \
+ py-unwind.o \
py-utils.o \
py-value.o \
py-varobj.o
@@ -443,6 +444,7 @@ SUBDIR_PYTHON_SRCS = \
python/py-symtab.c \
python/py-threadevent.c \
python/py-type.c \
+ python/py-unwind.c \
python/py-utils.c \
python/py-value.c \
python/py-varobj.c
@@ -2639,6 +2641,10 @@ py-type.o: $(srcdir)/python/py-type.c
$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-type.c
$(POSTCOMPILE)
+py-unwind.o: $(srcdir)/python/py-unwind.c
+ $(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-unwind.c
+ $(POSTCOMPILE)
+
py-utils.o: $(srcdir)/python/py-utils.c
$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-utils.c
$(POSTCOMPILE)
diff --git a/gdb/NEWS b/gdb/NEWS
index cd7c2b3..5013484 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -29,6 +29,7 @@
** gdb.Objfile objects have a new attribute "username",
which is the name of the objfile as specified by the user,
without, for example, resolving symlinks.
+ ** You can now write frame unwinders in Python.
* New commands
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index c01b86d..30cfd17 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,11 +62,13 @@ PYTHON_FILE_LIST = \
gdb/FrameDecorator.py \
gdb/types.py \
gdb/printing.py \
+ gdb/unwinder.py \
gdb/prompt.py \
gdb/xmethod.py \
gdb/command/__init__.py \
gdb/command/xmethods.py \
gdb/command/frame_filters.py \
+ gdb/command/unwinders.py \
gdb/command/type_printers.py \
gdb/command/pretty_printers.py \
gdb/command/prompt.py \
diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
index 871b626..8376689 100644
--- a/gdb/doc/ChangeLog
+++ b/gdb/doc/ChangeLog
@@ -1,3 +1,8 @@
+2015-04-01 Sasha Smundak <asmundak@google.com>
+
+ * doc/python.texi (Writing a Frame Unwinder in Python): Add
+ section.
+
2015-03-31 Sergio Durigan Junior <sergiodj@redhat.com>
PR corefiles/16092
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index d725eb0..098d718 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -144,6 +144,7 @@ optional arguments while skipping others. Example:
* Frame Filter API:: Filtering Frames.
* Frame Decorator API:: Decorating Frames.
* Writing a Frame Filter:: Writing a Frame Filter.
+* Unwinding Frames in Python:: Writing frame unwinder.
* Xmethods In Python:: Adding and replacing methods of C++ classes.
* Xmethod API:: Xmethod types.
* Writing an Xmethod:: Writing an xmethod.
@@ -2178,6 +2179,148 @@ printed hierarchically. Another approach would be to combine the
marker in the inlined frame, and also show the hierarchical
relationship.
+@node Unwinding Frames in Python
+@subsubsection Unwinding Frames in Python
+@cindex unwinding frames in Python
+
+In @value{GDBN} terminology ``unwinding'' is the process of finding
+the previous frame (that is, caller's) from the current one. An
+unwinder has three methods. The first one checks if it can handle
+given frame (``sniff'' it). For the frames it can sniff an unwinder
+provides two additional methods: it can return frame's ID, and it can
+fetch registers from the previous frame. A running @value{GDBN}
+mantains a list of the unwinders and calls each unwinder's sniffer in
+turn until it finds the one that recognizes the current frame. There
+is an API to register an unwinder.
+
+The unwinders that come with @value{GDBN} handle standard frames.
+However, mixed language applications (for example, an application
+running Java Virtual Machine) sometimes use frame layouts that cannot
+be handled by the @value{GDBN} unwinders. You can write Python code
+that can handle such custom frames.
+
+You implement a frame unwinder in Python as a class with which has two
+attributes, @code{name} and @code{enabled}, with obvious meanings, and
+a single method @code{__call__}, which examines a given frame and
+returns an object (an instance of @code{gdb.UnwindInfo class)}
+describing it. If an unwinder does not recognize a frame, it should
+return @code{None}. The code in @value{GDBN} that enables writing
+unwinders in Python uses this object to return frame's ID and previous
+frame registers when @value{GDBN} core asks for them.
+
+@subheading Unwinder Input
+
+An object passed to an unwinder (a @code{gdb.PendingFrame} instance)
+provides a method to read frame's registers:
+
+@defun PendingFrame.read_register (reg)
+This method returns the contents of the register @var{regn} in the
+frame as a @code{gdb.Value} object. @var{reg} can be either a
+register number or a register name; the values are platform-specific.
+They are usually found in the corresponding
+@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree.
+@end defun
+
+It also provides a factory method to create a @code{gdb.UnwindInfo}
+instance to be returned to @value{GDBN}:
+
+@defun PendingFrame.create_unwind_info (frame_id)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{frame_id}. The argument is used to build @value{GDBN}'s frame ID
+using one of functions provided by @value{GDBN}. @var{frame_id}'s attributes
+determine which function will be used, as follows:
+
+@table @code
+@item sp, pc, special
+@code{frame_id_build_special (@var{frame_id}.sp, @var{frame_id}.pc, @var{frame_id}.special)}
+
+@item sp, pc
+@code{frame_id_build (@var{frame_id}.sp, @var{frame_id}.pc)}
+
+This is the most common case.
+
+@item sp
+@code{frame_id_build_wild (@var{frame_id}.sp)}
+@end table
+The attribute values should be @code{gdb.Value}
+
+@end defun
+
+@subheading Unwinder Output: UnwindInfo
+
+Use @code{PendingFrame.create_unwind_info} method described above to
+create a @code{gdb.UnwindInfo} instance. Use the following method to
+specify caller registers that have been saved in this frame:
+
+@defun gdb.UnwindInfo.add_saved_register (reg, value)
+@var{reg} identifies the register. It can be a number or a name, just
+as for the @code{PendingFrame.read_register} method above.
+@var{value} is a register value (a @code{gdb.Value} object).
+@end defun
+
+@subheading Unwinder Skeleton Code
+
+@value{GDBN} comes with the module containing the base @code{Unwinder}
+class. Derive your unwinder class from it and structure the code as
+follows:
+
+@smallexample
+from gdb.unwinders import Unwinder
+
+class FrameId(object):
+ def __init__(self, sp, pc):
+ self.sp = sp
+ self.pc = pc
+
+
+class MyUnwinder(Unwinder):
+ def __init__(....):
+ supe(MyUnwinder, self).__init___(<expects unwinder name argument>)
+
+ def __call__(pending_frame):
+ if not <we recognize frame>:
+ return None
+ # Create UnwindInfo. Usually the frame is identified by the stack
+ # pointer and the program counter.
+ sp = pending_frame.read_register(<SP number>)
+ pc = pending_frame.read_register(<PC number>)
+ unwind_info = pending_frame.create_unwind_info(FrameId(sp, pc))
+
+ # Find the values of the registers in the caller's frame and
+ # save them in the result:
+ unwind_info.add_saved_register(<register>, <value>)
+ ....
+
+ # Return the result:
+ return unwind_info
+
+@end smallexample
+
+@subheading Registering a Unwinder
+
+An object file, a program space, and the @value{GDBN} proper can have
+unwinders registered with it.
+
+The @code{gdb.unwinders} module provides the function to register a
+unwinder:
+
+@defun gdb.unwinder.register_unwinder (locus, unwinder, replace=False)
+@var{locus} is specifies an object file or a program space to which
+@var{unwinder} is added. Passing @code{None} or @code{gdb} adds
+@var{unwinder} to the @value{GDBN}'s global unwinder list. The newly
+added @var{unwinder} will be called before any other unwinder from the
+same locus. Two unwinders in the same locus cannot have the same
+name. An attempt to add a unwinder with already existing name raises
+an exception unless @var{replace} is @code{True}, in which case the
+old unwinder is deleted.
+@end defun
+
+@subheading Unwinder Precedence
+
+@value{GDBN} first calls the unwinders from all the object files in no
+particular order, then the unwinders from the current program space,
+and finally the unwinders from @value{GDBN}.
+
@node Xmethods In Python
@subsubsection Xmethods In Python
@cindex xmethods in Python
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)
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 157d200..c9528c3 100644
--- a/gdb/python/py-objfile.c
+++ b/gdb/python/py-objfile.c
@@ -42,6 +42,10 @@ typedef struct
/* The frame filter list of functions. */
PyObject *frame_filters;
+
+ /* The list of frame unwinders. */
+ PyObject *frame_unwinders;
+
/* The type-printer list. */
PyObject *type_printers;
@@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o)
Py_XDECREF (self->dict);
Py_XDECREF (self->printers);
Py_XDECREF (self->frame_filters);
+ Py_XDECREF (self->frame_unwinders);
Py_XDECREF (self->type_printers);
Py_XDECREF (self->xmethods);
Py_TYPE (self)->tp_free (self);
@@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self)
if (self->frame_filters == NULL)
return 0;
+ self->frame_unwinders = PyList_New (0);
+ if (self->frame_unwinders == NULL)
+ return 0;
+
self->type_printers = PyList_New (0);
if (self->type_printers == NULL)
return 0;
@@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
return 0;
}
+/* Return the frame unwinders attribute for this object file. */
+
+PyObject *
+objfpy_get_frame_unwinders (PyObject *o, void *ignore)
+{
+ objfile_object *self = (objfile_object *) o;
+
+ Py_INCREF (self->frame_unwinders);
+ return self->frame_unwinders;
+}
+
+/* Set this object file's frame unwinders list to UNWINDERS. */
+
+static int
+objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
+{
+ PyObject *tmp;
+ objfile_object *self = (objfile_object *) o;
+
+ if (!unwinders)
+ {
+ PyErr_SetString (PyExc_TypeError,
+ _("Cannot delete the frame unwinders attribute."));
+ return -1;
+ }
+
+ if (!PyList_Check (unwinders))
+ {
+ PyErr_SetString (PyExc_TypeError,
+ _("The frame_unwinders attribute must be a list."));
+ return -1;
+ }
+
+ /* Take care in case the LHS and RHS are related somehow. */
+ tmp = self->frame_unwinders;
+ Py_INCREF (unwinders);
+ self->frame_unwinders = unwinders;
+ Py_XDECREF (tmp);
+
+ return 0;
+}
+
/* Get the 'type_printers' attribute. */
static PyObject *
@@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] =
"Pretty printers.", NULL },
{ "frame_filters", objfpy_get_frame_filters,
objfpy_set_frame_filters, "Frame Filters.", NULL },
+ { "frame_unwinders", objfpy_get_frame_unwinders,
+ objfpy_set_frame_unwinders, "Frame Unwinders", NULL },
{ "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
"Type printers.", NULL },
{ "xmethods", objfpy_get_xmethods, NULL,
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index 93fbc14..17da3d1 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -41,6 +41,10 @@ typedef struct
/* The frame filter list of functions. */
PyObject *frame_filters;
+
+ /* The frame unwinder list. */
+ PyObject *frame_unwinders;
+
/* The type-printer list. */
PyObject *type_printers;
@@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self)
Py_XDECREF (ps_self->dict);
Py_XDECREF (ps_self->printers);
Py_XDECREF (ps_self->frame_filters);
+ Py_XDECREF (ps_self->frame_unwinders);
Py_XDECREF (ps_self->type_printers);
Py_XDECREF (ps_self->xmethods);
Py_TYPE (self)->tp_free (self);
@@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self)
if (self->frame_filters == NULL)
return 0;
+ self->frame_unwinders = PyList_New (0);
+ if (self->frame_unwinders == NULL)
+ return 0;
+
self->type_printers = PyList_New (0);
if (self->type_printers == NULL)
return 0;
@@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
return 0;
}
+/* Return the list of the frame unwinders for this program space. */
+
+PyObject *
+pspy_get_frame_unwinders (PyObject *o, void *ignore)
+{
+ pspace_object *self = (pspace_object *) o;
+
+ Py_INCREF (self->frame_unwinders);
+ return self->frame_unwinders;
+}
+
+/* Set this program space's list of the unwinders to UNWINDERS. */
+
+static int
+pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
+{
+ PyObject *tmp;
+ pspace_object *self = (pspace_object *) o;
+
+ if (!unwinders)
+ {
+ PyErr_SetString (PyExc_TypeError,
+ "cannot delete the frame unwinders list");
+ return -1;
+ }
+
+ if (!PyList_Check (unwinders))
+ {
+ PyErr_SetString (PyExc_TypeError,
+ "the frame unwinders attribute must be a list");
+ return -1;
+ }
+
+ /* Take care in case the LHS and RHS are related somehow. */
+ tmp = self->frame_unwinders;
+ Py_INCREF (unwinders);
+ self->frame_unwinders = unwinders;
+ Py_XDECREF (tmp);
+
+ return 0;
+}
+
/* Get the 'type_printers' attribute. */
static PyObject *
@@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] =
"Pretty printers.", NULL },
{ "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
"Frame filters.", NULL },
+ { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders,
+ "Frame unwinders.", NULL },
{ "type_printers", pspy_get_type_printers, pspy_set_type_printers,
"Type printers.", NULL },
{ "xmethods", pspy_get_xmethods, NULL,
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
new file mode 100644
index 0000000..bcfea4b
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,788 @@
+/* Python frame unwinder interface.
+
+ Copyright (C) 2015 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "valprint.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level) \
+ { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+ PyObject_HEAD
+
+ /* Frame we are unwinding. */
+ struct frame_info *frame_info;
+
+ /* Its architecture, passed by the sniffer caller. */
+ struct gdbarch *gdbarch;
+} pending_frame_object;
+
+/* Saved registers array item. */
+
+typedef struct
+{
+ int number;
+ PyObject *value;
+} saved_reg;
+DEF_VEC_O (saved_reg);
+
+/* The data we keep for the PyUnwindInfo: pending_frame, saved registers
+ and frame ID. */
+
+typedef struct
+{
+ PyObject_HEAD
+
+ /* gdb.PendingFrame for the frame we are unwinding. */
+ PyObject *pending_frame;
+
+ /* Its ID. */
+ struct frame_id frame_id;
+
+ /* Saved registers array. */
+ VEC (saved_reg) *saved_regs;
+} unwind_info_object;
+
+/* The data we keep for a frame we can unwind: frame ID and an array of
+ (register_number, register_value) pairs. */
+
+typedef struct
+{
+ /* Frame ID. */
+ struct frame_id frame_id;
+
+ /* GDB Architecture. */
+ struct gdbarch *gdbarch;
+
+ /* Length of the `reg' array below. */
+ int reg_count;
+
+ struct reg_info
+ {
+ /* Register number. */
+ int number;
+
+ /* Register data bytes pointer. */
+ gdb_byte data[MAX_REGISTER_SIZE];
+ } reg[];
+} cached_frame_info;
+
+static PyTypeObject pending_frame_object_type
+ CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("pending_frame_object");
+
+static PyTypeObject unwind_info_object_type
+ CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Parses register id, which can be either a number or a name.
+ Returns 1 on success, 0 otherwise. */
+
+static int
+pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
+ int *reg_num)
+{
+ if (pyo_reg_id == NULL)
+ return 0;
+ if (gdbpy_is_string (pyo_reg_id))
+ {
+ const char *reg_name = gdbpy_obj_to_string (pyo_reg_id);
+
+ if (reg_name == NULL)
+ return 0;
+ *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name,
+ strlen (reg_name));
+ return *reg_num >= 0;
+ }
+ else if (PyInt_Check (pyo_reg_id))
+ {
+ long value;
+ if (gdb_py_int_as_long (pyo_reg_id, &value) && (int) value == value)
+ {
+ *reg_num = (int) value;
+ return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
+ }
+ }
+ return 0;
+}
+
+/* Convert gdb.Value instance to inferior's pointer. Return 1 on success,
+ 0 on failure. */
+
+static int
+pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
+{
+ int rc = 0;
+ struct value *value;
+
+ TRY
+ {
+ if ((value = value_object_to_value (pyo_value)) != NULL)
+ {
+ *addr = unpack_pointer (value_type (value),
+ value_contents (value));
+ rc = 1;
+ }
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ gdbpy_convert_exception (except);
+ }
+ END_CATCH
+ return rc;
+}
+
+/* Get attribute from an object and convert it to the inferior's
+ pointer value. Return 1 if attribute exists and its value can be
+ converted. Otherwise, if attribute does not exist or its value is
+ None, return 0. In all other cases set Python error and return
+ 0. */
+
+static int
+pyuw_object_attribute_to_pointer (PyObject *pyo, const char *attr_name,
+ CORE_ADDR *addr)
+{
+ int rc = 0;
+
+ if (PyObject_HasAttrString (pyo, attr_name))
+ {
+ PyObject *pyo_value = PyObject_GetAttrString (pyo, attr_name);
+ struct value *value;
+
+ if (pyo_value != NULL && pyo_value != Py_None)
+ {
+ rc = pyuw_value_obj_to_pointer (pyo_value, addr);
+ if (!rc)
+ PyErr_Format (
+ PyExc_ValueError,
+ _("The value of the '%s' attribute is not a pointer."),
+ attr_name);
+ }
+ Py_XDECREF (pyo_value);
+ }
+ return rc;
+}
+
+/* Called by the Python interpreter to obtain string representation
+ of the UnwindInfo object. */
+
+static PyObject *
+unwind_infopy_str (PyObject *self)
+{
+ struct ui_file *strfile = mem_fileopen ();
+ unwind_info_object *unwind_info = (unwind_info_object *) self;
+ pending_frame_object *pending_frame
+ = (pending_frame_object *) (unwind_info->pending_frame);
+ PyObject *result;
+
+ fprintf_unfiltered (strfile, "Frame ID: ");
+ fprint_frame_id (strfile, unwind_info->frame_id);
+ {
+ char *sep = "";
+ int i;
+ struct value_print_options opts;
+ saved_reg *reg;
+
+ get_user_print_options (&opts);
+ fprintf_unfiltered (strfile, "\nSaved registers: (");
+ for (i = 0;
+ i < VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg);
+ i++)
+ {
+ struct value *value = value_object_to_value (reg->value);
+
+ fprintf_unfiltered (strfile, "%s(%d, ", sep, reg->number);
+ if (value != NULL)
+ {
+ TRY
+ {
+ value_print (value, strfile, &opts);
+ fprintf_unfiltered (strfile, ")");
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ GDB_PY_HANDLE_EXCEPTION (except);
+ }
+ END_CATCH
+ }
+ else
+ fprintf_unfiltered (strfile, "<BAD>)");
+ sep = ", ";
+ }
+ fprintf_unfiltered (strfile, ")");
+ }
+ {
+ char *s = ui_file_xstrdup (strfile, NULL);
+
+ result = PyString_FromString (s);
+ xfree (s);
+ }
+ ui_file_delete (strfile);
+ return result;
+}
+
+/* Create UnwindInfo instance for given PendingFrame and frame ID.
+ Sets Python error and returns NULL on error. */
+
+static PyObject *
+pyuw_create_unwind_info (PyObject *pyo_pending_frame,
+ struct frame_id frame_id)
+{
+ unwind_info_object *unwind_info
+ = PyObject_New (unwind_info_object, &unwind_info_object_type);
+
+ if (((pending_frame_object *) pyo_pending_frame)->frame_info == NULL)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ "Attempting to use stale PendingFrame");
+ return NULL;
+ }
+ unwind_info->frame_id = frame_id;
+ Py_INCREF (pyo_pending_frame);
+ unwind_info->pending_frame = pyo_pending_frame;
+ unwind_info->saved_regs = VEC_alloc (saved_reg, 4);
+ return (PyObject *) unwind_info;
+}
+
+/* The implementation of
+ gdb.UnwindInfo.add_saved_register (REG, VALUE) -> None. */
+
+static PyObject *
+unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
+{
+ unwind_info_object *unwind_info = (unwind_info_object *) self;
+ pending_frame_object *pending_frame
+ = (pending_frame_object *) (unwind_info->pending_frame);
+ PyObject *pyo_reg_id;
+ PyObject *pyo_reg_value;
+ int regnum;
+
+ if (pending_frame->frame_info == NULL)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ "UnwindInfo instance refers to a stale PendingFrame");
+ return NULL;
+ }
+ if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
+ &pyo_reg_id, &pyo_reg_value))
+ return NULL;
+ if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
+ {
+ PyErr_SetString (PyExc_ValueError, "Bad register");
+ return NULL;
+ }
+ {
+ struct value *value;
+ size_t data_size;
+
+ if (pyo_reg_value == NULL
+ || (value = value_object_to_value (pyo_reg_value)) == NULL)
+ {
+ PyErr_SetString (PyExc_ValueError, "Bad register value");
+ return NULL;
+ }
+ data_size = register_size (pending_frame->gdbarch, regnum);
+ if (data_size != TYPE_LENGTH (value_type (value)))
+ {
+ PyErr_Format (
+ PyExc_ValueError,
+ "The value of the register returned by the Python "
+ "sniffer has unexpected size: %u instead of %u.",
+ (unsigned) TYPE_LENGTH (value_type (value)),
+ (unsigned) data_size);
+ return NULL;
+ }
+ }
+ {
+ int i;
+ saved_reg *reg;
+
+ for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
+ {
+ if (regnum == reg->number)
+ {
+ Py_DECREF (reg->value);
+ break;
+ }
+ }
+ if (reg == NULL)
+ {
+ reg = VEC_safe_push (saved_reg, unwind_info->saved_regs, NULL);
+ reg->number = regnum;
+ }
+ Py_INCREF (pyo_reg_value);
+ reg->value = pyo_reg_value;
+ }
+ Py_RETURN_NONE;
+}
+
+/* UnwindInfo cleanup. */
+
+static void
+unwind_infopy_dealloc (PyObject *self)
+{
+ unwind_info_object *unwind_info = (unwind_info_object *) self;
+ int i;
+ saved_reg *reg;
+
+ Py_XDECREF (unwind_info->pending_frame);
+ for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
+ Py_DECREF (reg->value);
+ VEC_free (saved_reg, unwind_info->saved_regs);
+ Py_TYPE (self)->tp_free (self);
+}
+
+/* Called by the Python interpreter to obtain string representation
+ of the PendingFrame object. */
+
+static PyObject *
+pending_framepy_str (PyObject *self)
+{
+ struct frame_info *frame = ((pending_frame_object *) self)->frame_info;
+ const char *sp_str = NULL;
+ const char *pc_str = NULL;
+
+ if (frame == NULL)
+ return PyString_FromString ("Stale PendingFrame instance");
+ TRY
+ {
+ sp_str = core_addr_to_string_nz (get_frame_sp (frame));
+ pc_str = core_addr_to_string_nz (get_frame_pc (frame));
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ GDB_PY_HANDLE_EXCEPTION (except);
+ }
+ END_CATCH
+
+ return PyString_FromFormat ("SP=%s,PC=%s", sp_str, pc_str);
+}
+
+/* Implementation of gdb.PendingFrame.read_register (self, reg) -> gdb.Value.
+ Returns the value of register REG as gdb.Value instance. */
+
+static PyObject *
+pending_framepy_read_register (PyObject *self, PyObject *args)
+{
+ pending_frame_object *pending_frame = (pending_frame_object *) self;
+ struct value *val = NULL;
+ int regnum;
+ PyObject *pyo_reg_id;
+
+ if (pending_frame->frame_info == NULL)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ "Attempting to read register from stale PendingFrame");
+ return NULL;
+ }
+ if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
+ return NULL;
+ if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
+ {
+ PyErr_SetString (PyExc_ValueError, "Bad register");
+ return NULL;
+ }
+
+ TRY
+ {
+ val = get_frame_register_value (pending_frame->frame_info, regnum);
+ if (val == NULL)
+ PyErr_Format (PyExc_ValueError,
+ "Cannot read register %d from frame.",
+ regnum);
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ GDB_PY_HANDLE_EXCEPTION (except);
+ }
+ END_CATCH
+
+ return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Implementation of
+ PendingFrame.create_unwind_info (self, frameId) -> UnwindInfo. */
+
+static PyObject *
+pending_framepy_create_unwind_info (PyObject *self, PyObject *args)
+{
+ PyObject *pyo_frame_id;
+ CORE_ADDR sp;
+ CORE_ADDR pc;
+ CORE_ADDR special;
+
+ if (!PyArg_ParseTuple (args, "O:create_unwind_info", &pyo_frame_id))
+ return NULL;
+ if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "sp", &sp))
+ {
+ PyErr_SetString (PyExc_ValueError,
+ _("frame_id should have 'sp' attribute."));
+ return NULL;
+ }
+
+ /* The logic of building frame_id depending on the attributes of
+ the frame_id object:
+ Has Has Has Function to call
+ 'sp'? 'pc'? 'special'?
+ ------|------|--------------|-------------------------
+ Y N * frame_id_build_wild (sp)
+ Y Y N frame_id_build (sp, pc)
+ Y Y Y frame_id_build_special (sp, pc, special)
+ */
+ if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "pc", &pc))
+ return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
+ if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "special", &special))
+ return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
+ else
+ return pyuw_create_unwind_info (self,
+ frame_id_build_special (sp, pc, special));
+}
+
+/* Invalidate PendingFrame instance. */
+
+static void
+pending_frame_invalidate (void *pyo_pending_frame)
+{
+ if (pyo_pending_frame != NULL)
+ ((pending_frame_object *) pyo_pending_frame)->frame_info = NULL;
+}
+
+/* frame_unwind.this_id method. */
+
+static void
+pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
+ struct frame_id *this_id)
+{
+ *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
+ if (pyuw_debug >= 1)
+ {
+ fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+ fprint_frame_id (gdb_stdlog, *this_id);
+ fprintf_unfiltered (gdb_stdlog, "\n");
+ }
+}
+
+/* frame_unwind.prev_register. */
+
+static struct value *
+pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
+ int regnum)
+{
+ cached_frame_info *cached_frame = *cache_ptr;
+ struct reg_info *reg_info = cached_frame->reg;
+ struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+ TRACE_PY_UNWIND (1, "%s (frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
+ regnum);
+ for (; reg_info < reg_info_end; ++reg_info)
+ {
+ if (regnum == reg_info->number)
+ return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
+ }
+
+ return frame_unwind_got_optimized (this_frame, regnum);
+}
+
+/* Frame sniffer dispatch. */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+ void **cache_ptr)
+{
+ struct gdbarch *gdbarch = (struct gdbarch *) (self->unwind_data);
+ struct cleanup *cleanups = ensure_python_env (gdbarch, current_language);
+ PyObject *pyo_execute;
+ PyObject *pyo_pending_frame;
+ PyObject *pyo_unwind_info;
+ cached_frame_info *cached_frame;
+
+ TRACE_PY_UNWIND (3, "%s (SP=%s, PC=%s)\n", __FUNCTION__,
+ paddress (gdbarch, get_frame_sp (this_frame)),
+ paddress (gdbarch, get_frame_pc (this_frame)));
+
+ /* Create PendingFrame instance to pass to sniffers. */
+ pyo_pending_frame = (PyObject *) PyObject_New (pending_frame_object,
+ &pending_frame_object_type);
+ if (pyo_pending_frame == NULL)
+ goto error;
+ ((pending_frame_object *) pyo_pending_frame)->gdbarch = gdbarch;
+ ((pending_frame_object *) pyo_pending_frame)->frame_info = this_frame;
+ make_cleanup (pending_frame_invalidate, (void *) pyo_pending_frame);
+ make_cleanup_py_decref (pyo_pending_frame);
+
+ /* Run unwinders. */
+ if (gdb_python_module == NULL
+ || ! PyObject_HasAttrString (gdb_python_module, "execute_unwinders"))
+ {
+ PyErr_SetString (PyExc_NameError,
+ "Installation error: gdb.execute_unwinders function "
+ "is missing");
+ goto error;
+ }
+ pyo_execute = PyObject_GetAttrString (gdb_python_module, "execute_unwinders");
+ if (pyo_execute == NULL)
+ goto error;
+ make_cleanup_py_decref (pyo_execute);
+ pyo_unwind_info
+ = PyObject_CallFunctionObjArgs (pyo_execute, pyo_pending_frame, NULL);
+ if (pyo_unwind_info == NULL)
+ goto error;
+ make_cleanup_py_decref (pyo_unwind_info);
+ if (pyo_unwind_info == Py_None)
+ goto cannot_unwind;
+
+ /* Received UnwindInfo, cache data. */
+ if (PyObject_IsInstance (pyo_unwind_info,
+ (PyObject *) &unwind_info_object_type) <= 0)
+ error (_("A Unwinder should return gdb.UnwindInfo instance."));
+
+ {
+ unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
+ int reg_count = VEC_length (saved_reg, unwind_info->saved_regs);
+ saved_reg *reg;
+ int i;
+
+ cached_frame = xmalloc (sizeof (*cached_frame) +
+ reg_count * sizeof (cached_frame->reg[0]));
+ cached_frame->gdbarch = gdbarch;
+ cached_frame->frame_id = unwind_info->frame_id;
+ cached_frame->reg_count = reg_count;
+
+ /* Populate registers array. */
+ for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
+ {
+ struct value *value = value_object_to_value (reg->value);
+ size_t data_size = register_size (gdbarch, reg->number);
+
+ cached_frame->reg[i].number = reg->number;
+
+ /* `value' validation was done before, just assert. */
+ gdb_assert (value != NULL);
+ gdb_assert (data_size == TYPE_LENGTH (value_type (value)));
+ gdb_assert (data_size <= MAX_REGISTER_SIZE);
+
+ memcpy (cached_frame->reg[i].data, value_contents (value), data_size);
+ }
+ }
+
+ *cache_ptr = cached_frame;
+ do_cleanups (cleanups);
+ return 1;
+
+ error:
+ gdbpy_print_stack ();
+ /* Fallthrough. */
+ cannot_unwind:
+ do_cleanups (cleanups);
+ return 0;
+}
+
+/* Frame cache release shim. */
+
+static void
+pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+ TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
+ xfree (cache);
+}
+
+struct pyuw_gdbarch_data_type
+{
+ /* Has the unwinder shim been prepended? */
+ int unwinder_registered;
+};
+
+static void *
+pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+ return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Python unwinders
+ intermediary. */
+
+static void
+pyuw_on_new_gdbarch (struct gdbarch *newarch)
+{
+ struct pyuw_gdbarch_data_type *data =
+ gdbarch_data (newarch, pyuw_gdbarch_data);
+
+ if (!data->unwinder_registered)
+ {
+ struct frame_unwind *unwinder
+ = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
+
+ unwinder->type = NORMAL_FRAME;
+ unwinder->stop_reason = default_frame_unwind_stop_reason;
+ unwinder->this_id = pyuw_this_id;
+ unwinder->prev_register = pyuw_prev_register;
+ unwinder->unwind_data = (void *) newarch;
+ unwinder->sniffer = pyuw_sniffer;
+ unwinder->dealloc_cache = pyuw_dealloc_cache;
+ frame_unwind_prepend_unwinder (newarch, unwinder);
+ data->unwinder_registered = 1;
+ }
+}
+
+/* Initialize unwind machinery. */
+
+int
+gdbpy_initialize_unwind (void)
+{
+ int rc;
+ add_setshow_zuinteger_cmd
+ ("py-unwind", class_maintenance, &pyuw_debug,
+ _("Set Python unwinder debugging."),
+ _("Show Python unwinder debugging."),
+ _("When non-zero, Python unwinder debugging is enabled."),
+ NULL,
+ NULL,
+ &setdebuglist, &showdebuglist);
+ pyuw_gdbarch_data
+ = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
+ observer_attach_architecture_changed (pyuw_on_new_gdbarch);
+
+ if (PyType_Ready (&pending_frame_object_type) < 0)
+ return -1;
+ rc = gdb_pymodule_addobject (gdb_module, "PendingFrame",
+ (PyObject *) &pending_frame_object_type);
+ if (rc)
+ return rc;
+
+ if (PyType_Ready (&unwind_info_object_type) < 0)
+ return -1;
+ return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
+ (PyObject *) &unwind_info_object_type);
+}
+
+static PyMethodDef pending_frame_object_methods[] =
+{
+ { "read_register", pending_framepy_read_register, METH_VARARGS,
+ "read_register (REG) -> gdb.Value\n"
+ "Return the value of the REG in the frame." },
+ { "create_unwind_info",
+ pending_framepy_create_unwind_info, METH_VARARGS,
+ "create_unwind_info (FRAME_ID) -> gdb.UnwindInfo\n"
+ "Construct UnwindInfo for this PendingFrame, using FRAME_ID\n"
+ "to identify it." },
+ {NULL} /* Sentinel */
+};
+
+static PyTypeObject pending_frame_object_type =
+{
+ PyVarObject_HEAD_INIT (NULL, 0)
+ "gdb.PendingFrame", /* tp_name */
+ sizeof (pending_frame_object), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ pending_framepy_str, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ "GDB PendingFrame object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pending_frame_object_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+};
+
+static PyMethodDef unwind_info_object_methods[] =
+{
+ { "add_saved_register",
+ unwind_infopy_add_saved_register, METH_VARARGS,
+ "add_saved_register (REG, VALUE) -> None\n"
+ "Set the value of the REG in the previous frame to VALUE." },
+ { NULL } /* Sentinel */
+};
+
+static PyTypeObject unwind_info_object_type =
+{
+ PyVarObject_HEAD_INIT (NULL, 0)
+ "gdb.UnwindInfo", /* tp_name */
+ sizeof (unwind_info_object), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ unwind_infopy_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ unwind_infopy_str, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "GDB UnwindInfo object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ unwind_info_object_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+};
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 4c4d32a..0581b33 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -391,12 +391,14 @@ PyObject *pspace_to_pspace_object (struct program_space *)
CPYCHECKER_RETURNS_BORROWED_REF;
PyObject *pspy_get_printers (PyObject *, void *);
PyObject *pspy_get_frame_filters (PyObject *, void *);
+PyObject *pspy_get_frame_unwinders (PyObject *, void *);
PyObject *pspy_get_xmethods (PyObject *, void *);
PyObject *objfile_to_objfile_object (struct objfile *)
CPYCHECKER_RETURNS_BORROWED_REF;
PyObject *objfpy_get_printers (PyObject *, void *);
PyObject *objfpy_get_frame_filters (PyObject *, void *);
+PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
PyObject *objfpy_get_xmethods (PyObject *, void *);
PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
@@ -491,6 +493,8 @@ int gdbpy_initialize_arch (void)
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
int gdbpy_initialize_xmethods (void)
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_unwind (void)
+ CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
struct cleanup *make_cleanup_py_decref (PyObject *py);
struct cleanup *make_cleanup_py_xdecref (PyObject *py);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 58c7c92..1da63fd 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1821,7 +1821,8 @@ message == an error message without a stack will be printed."),
|| gdbpy_initialize_new_objfile_event () < 0
|| gdbpy_initialize_clear_objfiles_event () < 0
|| gdbpy_initialize_arch () < 0
- || gdbpy_initialize_xmethods () < 0)
+ || gdbpy_initialize_xmethods () < 0
+ || gdbpy_initialize_unwind () < 0)
goto fail;
gdbpy_to_string_cst = PyString_FromString ("to_string");
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index dfce0c0..0b6bbca 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,12 @@
+2015-04-01 Sasha Smundak <asmundak@google.com>
+
+ * 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.
+
2015-04-01 Pedro Alves <palves@redhat.com>
* gdb.threads/manythreads.exp (interrupt_and_wait): Pass $message
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
new file mode 100644
index 0000000..8c1d935
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
@@ -0,0 +1,24 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ 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/>. */
+
+int
+main (void)
+{
+ int i = 0;
+
+ return i; /* next-line */
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
new file mode 100644
index 0000000..3b0e021
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
@@ -0,0 +1,64 @@
+# 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/>.
+
+# This file is part of the GDB testsuite. It tests Python-based
+# unwinding CLI.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+ return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+if ![runto_main ] then {
+ fail "Can't run to main"
+ return -1
+}
+
+gdb_test "source ${pyfile}" "Python script imported" "import python scripts"
+
+gdb_test_sequence "info unwinder" "Show all unwinders" {
+ "Global:"
+ " global_unwinder"
+ "Progspace .*py-unwind-maint:"
+ "py_unwind_maint_ps_unwinder"
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
+
+gdb_test_sequence "continue" "Unwinders called" {
+ "py_unwind_maint_ps_unwinder called"
+ "global_unwinder called"
+}
+
+gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled"
+
+gdb_test_sequence "info unwinder" "Show with global unwinder disabled" {
+ "Global:"
+ " global_unwinder \\[disabled\\]"
+ "Progspace .*py-unwind-maint:"
+ " py_unwind_maint_ps_unwinder"
+}
+
+gdb_test_sequence "where" "Global unwinder disabled" {
+ "py_unwind_maint_ps_unwinder called\r\n#0 main"
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py
new file mode 100644
index 0000000..f8c6277
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
@@ -0,0 +1,59 @@
+# 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/>.
+
+# This file is part of the GDB testsuite. It tests python unwinders.
+
+import re
+import gdb.types
+from gdb.unwinder import Unwinder, register_unwinder
+
+class TestGlobalUnwinder(Unwinder):
+ def __init__(self):
+ super(TestGlobalUnwinder, self).__init__("global_unwinder")
+
+ def __call__(self, unwinder_info):
+ print "%s called" % self.name
+ return None
+
+class TestProgspaceUnwinder(Unwinder):
+ def __init__(self, name):
+ super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name)
+
+ def __call__(self, unwinder_info):
+ print "%s called" % self.name
+ return None
+
+class TestObjfileUnwinder(Unwinder):
+ def __init__(self, name):
+ super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name)
+
+ def __call__(self, unwinder_info):
+ print "%s called" % self.name
+ return None
+
+
+
+gdb.unwinder.register_unwinder(None, TestGlobalUnwinder())
+saw_runtime_error = False
+try:
+ gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=False)
+except RuntimeError:
+ saw_runtime_error = True
+if not saw_runtime_error:
+ raise RuntimeError("Missing runtime error from register_unwinder.")
+gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=True)
+gdb.unwinder.register_unwinder(gdb.current_progspace(),
+ TestProgspaceUnwinder("py_unwind_maint"))
+print "Python script imported"
diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c
new file mode 100644
index 0000000..cf41d78
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,81 @@
+/* This test program is part of GDB, the GNU debugger.
+
+ 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/>. */
+
+/* This is the test program loaded into GDB by the py-unwind test. */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void *
+swap_value (void **location, void *new_value)
+{
+ void *old_value = *location;
+ *location = new_value;
+ return old_value;
+}
+
+static void
+bad_layout(void **variable_ptr, void *fp)
+{
+ fprintf (stderr, "First variable should be allocated one word below "
+ "the frame. Got variable's address %p, frame at %p instead.\n",
+ variable_ptr, fp);
+ abort();
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+corrupt_frame_inner (void)
+{
+ /* Save outer frame address, then corrupt the unwind chain by
+ setting the outer frame address in it to self. This is
+ ABI-specific: the first word of the frame contains previous frame
+ address in amd64. */
+ void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+
+ /* Verify the compiler allocates the first local variable one word
+ below frame. This is where the test unwinder expects to find the
+ correct outer frame address. */
+ if (&previous_fp + 1 != (void **) MY_FRAME)
+ bad_layout (&previous_fp + 1, MY_FRAME);
+
+ /* Now restore it so that we can return. The test sets the
+ breakpoint just before this happens, and GDB will not be able to
+ show the backtrace without JIT reader. */
+ swap_value ((void **) MY_FRAME, previous_fp); /* break backtrace-broken */
+}
+
+static void
+corrupt_frame_outer (void)
+{
+ /* See above for the explanation of the code here. This function
+ corrupts its frame, too, and then calls the inner one. */
+ void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+ if (&previous_fp + 1 != (void **) MY_FRAME)
+ bad_layout (&previous_fp, MY_FRAME);
+ corrupt_frame_inner ();
+ swap_value ((void **) MY_FRAME, previous_fp);
+}
+
+int
+main ()
+{
+ corrupt_frame_outer ();
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..53d6746
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@
+# 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/>.
+
+# This file is part of the GDB testsuite. It verifies that frame
+# unwinders can be implemented in Python.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+ return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+# This test runs on a specific platform.
+if { ! [istarget x86_64-*]} { continue }
+
+# The following tests require execution.
+
+if ![runto_main] then {
+ fail "Can't run to main"
+ return 0
+}
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
+
+gdb_test "source ${pyfile}" "Python script imported" \
+ "import python scripts"
+
+gdb_continue_to_breakpoint "break backtrace-broken"
+gdb_test_sequence "where" "Backtrace restored by unwinder" {
+ "\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
+ "\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
+ "\\r\\n#2 .* main \\(.*\\) at"
+}
+
+
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
new file mode 100644
index 0000000..6257fd7
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,99 @@
+# 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/>.
+
+import gdb
+from gdb.unwinder import Unwinder
+
+class FrameId(object):
+
+ def __init__(self, sp, pc):
+ self._sp = sp
+ self._pc = pc
+
+ @property
+ def sp(self):
+ return self._sp
+
+ @property
+ def pc(self):
+ return self._pc
+
+
+class TestUnwinder(Unwinder):
+ AMD64_RBP = 6
+ AMD64_RSP = 7
+ AMD64_RIP = 16
+
+ def __init__(self):
+ Unwinder.__init__(self, "test unwinder")
+ self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
+ self.char_ptr_ptr_t = self.char_ptr_t.pointer()
+
+ def _read_word(self, address):
+ return address.cast(self.char_ptr_ptr_t).dereference()
+
+ def __call__(self, pending_frame):
+ """Test unwinder written in Python.
+
+ This unwinder can unwind the frames that have been deliberately
+ corrupted in a specific way (functions in the accompanying
+ py-unwind.c file do that.)
+ This code is only on AMD64.
+ On AMD64 $RBP points to the innermost frame (unless the code
+ was compiled with -fomit-frame-pointer), which contains the
+ address of the previous frame at offset 0. The functions
+ deliberately corrupt their frames as follows:
+ Before After
+ Corruption: Corruption:
+ +--------------+ +--------------+
+ RBP-8 | | | Previous RBP |
+ +--------------+ +--------------+
+ RBP + Previous RBP | | RBP |
+ +--------------+ +--------------+
+ RBP+8 | Return RIP | | Return RIP |
+ +--------------+ +--------------+
+ Old SP | | | |
+
+ This unwinder recognizes the corrupt frames by checking that
+ *RBP == RBP, and restores previous RBP from the word above it.
+ """
+ try:
+ # NOTE: the registers in Unwinder API can be referenced
+ # either by name or by number. The code below uses both
+ # to achieve more coverage.
+ bp = pending_frame.read_register("rbp").cast(self.char_ptr_t)
+ if self._read_word(bp) != bp:
+ return None
+ # Found the frame that the test program has corrupted for us.
+ # The correct BP for the outer frame has been saved one word
+ # above, previous IP and SP are at the expected places.
+ previous_bp = self._read_word(bp - 8)
+ previous_ip = self._read_word(bp + 8)
+ previous_sp = bp + 16
+
+ frame_id = FrameId(
+ pending_frame.read_register(TestUnwinder.AMD64_RSP),
+ pending_frame.read_register(TestUnwinder.AMD64_RIP))
+ unwind_info = pending_frame.create_unwind_info(frame_id)
+ unwind_info.add_saved_register(TestUnwinder.AMD64_RBP,
+ previous_bp)
+ unwind_info.add_saved_register("rip", previous_ip)
+ unwind_info.add_saved_register("rsp", previous_sp)
+ return unwind_info
+ except (gdb.error, RuntimeError):
+ return None
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
+print("Python script imported")