From d11916aa89c43071c08c1f9b4550a01f8eec78e3 Mon Sep 17 00:00:00 2001 From: Sasha Smundak Date: Wed, 1 Apr 2015 11:49:12 -0700 Subject: [PATCH] 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. --- gdb/ChangeLog | 32 + gdb/Makefile.in | 6 + gdb/NEWS | 1 + gdb/data-directory/Makefile.in | 2 + gdb/doc/ChangeLog | 5 + gdb/doc/python.texi | 143 ++++ gdb/python/lib/gdb/__init__.py | 38 +- gdb/python/lib/gdb/command/unwinders.py | 198 +++++ gdb/python/lib/gdb/unwinder.py | 94 +++ gdb/python/py-objfile.c | 53 ++ gdb/python/py-progspace.c | 53 ++ gdb/python/py-unwind.c | 788 +++++++++++++++++++ gdb/python/python-internal.h | 4 + gdb/python/python.c | 3 +- gdb/testsuite/ChangeLog | 9 + gdb/testsuite/gdb.python/py-unwind-maint.c | 24 + gdb/testsuite/gdb.python/py-unwind-maint.exp | 64 ++ gdb/testsuite/gdb.python/py-unwind-maint.py | 59 ++ gdb/testsuite/gdb.python/py-unwind.c | 81 ++ gdb/testsuite/gdb.python/py-unwind.exp | 54 ++ gdb/testsuite/gdb.python/py-unwind.py | 99 +++ 21 files changed, 1808 insertions(+), 2 deletions(-) create mode 100644 gdb/python/lib/gdb/command/unwinders.py create mode 100644 gdb/python/lib/gdb/unwinder.py create mode 100644 gdb/python/py-unwind.c create mode 100644 gdb/testsuite/gdb.python/py-unwind-maint.c create mode 100644 gdb/testsuite/gdb.python/py-unwind-maint.exp create mode 100644 gdb/testsuite/gdb.python/py-unwind-maint.py create mode 100644 gdb/testsuite/gdb.python/py-unwind.c create mode 100644 gdb/testsuite/gdb.python/py-unwind.exp create mode 100644 gdb/testsuite/gdb.python/py-unwind.py diff --git a/gdb/ChangeLog b/gdb/ChangeLog index b85b43723c1..a7120769bbd 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,35 @@ +2015-04-01 Sasha Smundak + + * 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 * infrun.c (resume): Check currently_stepping after clearing diff --git a/gdb/Makefile.in b/gdb/Makefile.in index d4d4a3e6f9b..8f436173171 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 cd7c2b3cdc4..5013484c6e2 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 c01b86d224a..30cfd177bd1 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 871b626c631..83766897e81 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,8 @@ +2015-04-01 Sasha Smundak + + * doc/python.texi (Writing a Frame Unwinder in Python): Add + section. + 2015-03-31 Sergio Durigan Junior PR corefiles/16092 diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index d725eb00a15..098d7180edc 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___() + + def __call__(pending_frame): + if not : + return None + # Create UnwindInfo. Usually the frame is identified by the stack + # pointer and the program counter. + sp = pending_frame.read_register() + pc = pending_frame.read_register() + 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(, ) + .... + + # 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 92b06f27e8d..81789e5f6a4 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 00000000000..8530b35d438 --- /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 . + +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 00000000000..3554e9c2bec --- /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 . + +"""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 157d200b433..c9528c31e25 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 93fbc14d097..17da3d17e81 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 00000000000..bcfea4bbe87 --- /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 . */ + +#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, ")"); + 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, ®num)) + { + 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, ®num)) + { + 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 4c4d32a5ad5..0581b33f4df 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 58c7c92312e..1da63fd5c0f 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 dfce0c0fc5d..0b6bbcaf25f 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,12 @@ +2015-04-01 Sasha Smundak + + * 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 * 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 00000000000..8c1d935fdeb --- /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 . */ + +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 00000000000..3b0e02163ac --- /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 . + +# 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 00000000000..f8c627719b1 --- /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 . + +# 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 00000000000..cf41d788a56 --- /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 . */ + +/* This is the test program loaded into GDB by the py-unwind test. */ + +#include +#include +#include + +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 00000000000..53d6746a680 --- /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 . + +# 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 00000000000..6257fd74782 --- /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 . + +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") -- 2.30.2