From patchwork Wed Mar 11 02:22:00 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Smundak X-Patchwork-Id: 5565 Received: (qmail 25408 invoked by alias); 11 Mar 2015 02:22:15 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 25383 invoked by uid 89); 11 Mar 2015 02:22:11 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.4 required=5.0 tests=AWL, BAYES_00, RCVD_IN_DNSWL_LOW, SPF_PASS, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-HELO: mail-ie0-f182.google.com Received: from mail-ie0-f182.google.com (HELO mail-ie0-f182.google.com) (209.85.223.182) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES128-GCM-SHA256 encrypted) ESMTPS; Wed, 11 Mar 2015 02:22:03 +0000 Received: by iecsl2 with SMTP id sl2so6304545iec.1 for ; Tue, 10 Mar 2015 19:22:00 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:in-reply-to:references:date :message-id:subject:from:to:cc:content-type; bh=8HcC/3KXp7GbjH/V6x5Srklr9AUTzdO3pmJYQf34tiU=; b=XhTJSsFlKBgMh5PYFvhHwmhXI0XqTy792pTZ1fB+RAorySYA4R9BiNqG1twXj4AJyI pi4uEkQPb0iaJEYcSDgrw4QaFrTmUom60ginLgOed/j1jklHV/eAxN3NPg3ug17pCzd7 Njiu0erd0GwKaGz9QK/Bep0WcR2mBwYgoExSOz1n3iP1QYfHRFPCF0pV7gk0tnbp5zw3 iKkKSyV+1eGQWyl/Mm79mnHqpt3lheQw6voK+boORnCzkwixflb4Q/uz9Kh1siRkwAEI o11EceSe8oL/5Y3xLz/ReE9RVvv7f3o33roCC9Tj2LX+MjwiWxZZ8OaY+RhN2KVbsicd ZaJA== X-Gm-Message-State: ALoCoQnYdygoavGR87w/samdx1gjyQLgKDwVKrIFdsVZJeDnOQAYX9N05KLayY4YDYzsIGZdkOH+ MIME-Version: 1.0 X-Received: by 10.50.122.68 with SMTP id lq4mr5706302igb.10.1426040520533; Tue, 10 Mar 2015 19:22:00 -0700 (PDT) Received: by 10.64.227.110 with HTTP; Tue, 10 Mar 2015 19:22:00 -0700 (PDT) In-Reply-To: <54FD7DAA.7010603@redhat.com> References: <21714.40641.510825.30998@ruffy2.mtv.corp.google.com> <54E71694.1080304@redhat.com> <87ioei31uj.fsf@igalia.com> <87d24p19tt.fsf@igalia.com> <54FD7DAA.7010603@redhat.com> Date: Tue, 10 Mar 2015 19:22:00 -0700 Message-ID: Subject: Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python From: Alexander Smundak To: Phil Muldoon Cc: Andy Wingo , Doug Evans , gdb-patches This take revises the API by introducing UnwindInfo class. The main difference with the proposed Guile interface is that previous frame registers are passed to the constructor as a tuple and cannot be revised later. gdb/ChangeLog: 2015-02-30 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 sniffers.py. * doc/python.texi (Writing a Frame Unwinder in Python): Add section. * python/lib/gdb/__init__.py (packages): Add frame_sniffers list. * python/lib/gdb/command/sniffers.py: New file, implements GDB commands to list/enable/disable Python sniffers. * python/lib/gdb/function/sniffers.py: New file, implements execute_sniffers function. * python/lib/gdb/sniffer.py: New file, contains Sniffer class and register_sniffer function. * python/py-objfile.c (objfile_object): Add frame_sniffers field. (objfpy_dealloc): Decrement frame_sniffers reference count. (objfpy_initialize): Create frame_sniffers list. (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers getter. (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers setter. (objfile_getset): Add frame_sniffers attribute to Objfile. * python/py-progspace.c (pspace_object): Add frame_sniffers field. (pspy_dealloc): Decrement frame_sniffers reference count. (pspy_initialize): Create frame_sniffers list. (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers getter. (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers setter. (pspy_getset): Add frame_sniffers attribute to gdb.Progspace. * python/py-unwind.c: New file, implements Python frame sniffers interface. * python/python-internal.h (pspy_get_name_sniffers): New prototype. (objpy_get_frame_sniffers): New prototype. (gdbpy_initialize_unwind): New prototype. * python/python.c (gdbpy_apply_type_printers): Call gdbpy_initialize_unwind. gdb/testsuite/ChangeLog: 2014-02-30 Sasha Smundak * gdb.python/py-unwind-maint.c: Test program for py-unwind-maint. * gdb.python/py-unwind-maint.exp: Tests sniffer-related GDB commands. * gdb.python/py-unwind-maint.py: Pythons sniffers for the test. On Mon, Mar 9, 2015 at 4:02 AM, Phil Muldoon wrote: > On 04/03/15 07:49, Andy Wingo wrote: >> Howdy :) >> >> Reordering some of the replies. >> >> On Wed 04 Mar 2015 03:36, Alexander Smundak writes: > > Just a note. I am generally satisfied with the patch and your answers > to my questions. Just now need to get the nod off Doug, and coordinate > with Andy and/or other guile people to harmonious the design. > > Cheers > > Phil > diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 8addef4..3773e2c 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -394,6 +394,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 @@ -433,6 +434,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 @@ -2608,6 +2610,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 f19577a..89157a4 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -12,6 +12,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..47b4957 100644 --- a/gdb/data-directory/Makefile.in +++ b/gdb/data-directory/Makefile.in @@ -62,17 +62,20 @@ PYTHON_FILE_LIST = \ gdb/FrameDecorator.py \ gdb/types.py \ gdb/printing.py \ + gdb/sniffer.py \ gdb/prompt.py \ gdb/xmethod.py \ gdb/command/__init__.py \ gdb/command/xmethods.py \ gdb/command/frame_filters.py \ + gdb/command/sniffers.py \ gdb/command/type_printers.py \ gdb/command/pretty_printers.py \ gdb/command/prompt.py \ gdb/command/explore.py \ gdb/function/__init__.py \ gdb/function/caller_is.py \ + gdb/function/sniffers.py \ gdb/function/strfns.py \ gdb/printer/__init__.py \ gdb/printer/bound_registers.py diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index d725eb0..44683bd 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 a frame unwinder in Python. * Xmethods In Python:: Adding and replacing methods of C++ classes. * Xmethod API:: Xmethod types. * Writing an Xmethod:: Writing an xmethod. @@ -2178,6 +2179,111 @@ 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 GDB terminology ``unwinding'' is the process of finding the +previous frame (that is, caller's) from the current one. A running GDB +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 GDB handle standard frames for each +platform where GDB is running. However, mixed language applications +(for example, and application running Java Virtual Machine) sometimes +use frame layouts that cannot be handled by the GDB unwinders. You +can write Python code that can handle such custom frames. + +You implement a sniffer 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 the data +describing it (that is, when it recognizes it). GDB comes with the module +containing the base @code{Sniffer} class for that. The sniffer looks as +follows: +@smallexample +from gdb.sniffers import Sniffer + +class MySniffer(Sniffer): + def __init__(....): + super(MySniffer, self).__init___() + def __call__(sniffer_info): + if not : + return None + + + return +@end smallexample + +@subheading Examining The Current Frame + +@value{GDBN} passes a @code{gdb.SnifferInfo} instance when it calls +sniffer's @code{__call__} method. This class has a single method: + +@defun SnifferInfo.read_register (self, regnum) +This method returns the contents of the register @var{regnum} in the +frame as a @code{gdb.Value} object. @var{regnum} values are +platform-specific. They are usually defined in the corresponding +xxx-@code{tdep.h} file in the gdb source tree. +@end defun + +@subheading Sniffer return: UnwindInfo + +If sniffer's @code{__call__} method recognizes the frame, it should +return an instance of @code{gdb.UnwindInfo} class describing it. + +@code{UnwindInfo} has the following methods: + +@defun gdb.UnwindInfo.__init__ (sniffer_info, previous_frame_registers) +@var{sniffer_info} is the SnifferInfo instance that has been passed to +a sniffer on invocation. +@var{previous_frame_registers} describes the registers that can be +unwound (i.e., the registers from the previous frame that have been +saved in the current frame described by @var{sniffer_info}). It is a +tuple where each element is a (@var{regnum}, @var{regdata}) 2-tuple. +@var{regnum} is a register number, and @var{regdata} is register +contents (a @code{gdb.Value} object). +@end defun + +@defun gdb.UnwindInfo.frame_id_build(sp, pc) +Builds frame ID from the given @var{sp} and @var{pc} values. +@end defun + +@defun gdb.UnwindInfo.frame_id_build_special(sp, pc, special) +Builds frame ID from the given @var{sp}, @var{pc}, and @var{special} +values. +@end defun + +@defun gdb.UnwindInfo.frame_id_build_wild(sp) +Builds frame ID from the given @var{sp} value. +@end defun + +@subheading Registering a Sniffer + +An object file, a program space, and the @value{GDBN} proper can have +sniffers registered with it. + +The @code{gdb.sniffers} module provides the function to register a +sniffer: + +@defun gdb.sniffer.register_sniffer (locus, sniffer, replace=False) +@var{locus} is specifies an object file or a program space to which +@var{sniffer} is added. Passing @code{None} or @code{gdb} adds +@var{sniffer} to the @value{GDBN}'s global sniffer list. The newly +added @var{sniffer} will be called before any other sniffer from the +same locus. Two sniffers in the same locus cannot have the same +name. An attempt to add a sniffer with already existing name raises an +exception unless @var{replace} is @code{True}, in which case the old +sniffer is deleted. +@end defun + +@subheading Sniffer Precedence + +@value{GDBN} first calls the sniffers from all the object files in no +particular order, then the sniffers from the current program space, +and finally the sniffers 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..8d7f651 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -71,6 +71,8 @@ type_printers = [] xmethods = [] # Initial frame filters. frame_filters = {} +# Initial frame sniffers. +frame_sniffers = [] # Convenience variable to GDB's python directory PYTHONDIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/gdb/python/lib/gdb/command/sniffers.py b/gdb/python/lib/gdb/command/sniffers.py new file mode 100644 index 0000000..ad7f693 --- /dev/null +++ b/gdb/python/lib/gdb/command/sniffers.py @@ -0,0 +1,198 @@ +# Sniffer 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_sniffer_command_args(arg): + """Internal utility to parse sniffer 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, "sniffer")) + + +class InfoSniffer(gdb.Command): + """GDB command to list sniffers. + + Usage: info sniffer [locus-regexp [name-regexp]] + + LOCUS-REGEXP is a regular expression matching the location of the + sniffer. If it is omitted, all registered sniffers from all loci + are listed. A locus could be 'global', a regular expression + matching the current program space's filename, or a regular + expression matching filenames of objfiles. Locus could be + 'progspace' to specify that only sniffers from the current + progspace should be listed. + + NAME-REGEXP is a regular expression to filter sniffer names. + If this omitted for a specified locus, then all registered + sniffers in the locus are listed. + """ + + def __init__(self): + super(InfoSniffer, self).__init__("info sniffer", + gdb.COMMAND_DATA) + + def list_sniffers(self, title, sniffers, name_re): + """Lists the sniffers whose name matches regexp. + + Arguments: + title: The line to print before the list. + sniffers: The list of the sniffers. + name_re: sniffer name filter. + """ + if not sniffers: + return + print title + for sniffer in sniffers: + if name_re.match(sniffer.name): + print(" %s%s" % (sniffer.name, + "" if sniffer.enabled else "[disabled]")) + + def invoke(self, arg, from_tty): + locus_re, name_re = parse_sniffer_command_args(arg) + if locus_re.match("global"): + self.list_sniffers("global sniffers:", gdb.frame_sniffers, + name_re) + if locus_re.match("progspace"): + cp = gdb.current_progspace() + self.list_sniffers("progspace %s sniffers:" % cp.filename, + cp.frame_sniffers, name_re) + for objfile in gdb.objfiles(): + if locus_re.match(objfile.filename): + self.list_sniffers("objfile %s sniffers:" % objfile.filename, + objfile.frame_sniffers, name_re) + + +def do_enable_sniffer1(sniffers, name_re, flag): + """Enable/disable sniffers whose names match given regex. + + Arguments: + sniffers: The list of sniffers. + name_re: Sniffer name filter. + flag: Enable/disable. + + Returns: + The number of sniffers affected. + """ + total = 0 + for sniffer in sniffers: + if name_re.match(sniffer.name): + sniffer.enabled = flag + total += 1 + return total + + +def do_enable_sniffer(arg, flag): + """Enable/disable sniffer(s).""" + (locus_re, name_re) = parse_sniffer_command_args(arg) + total = 0 + if locus_re.match("global"): + total += do_enable_sniffer1(gdb.frame_sniffers, name_re, flag) + if locus_re.match("progspace"): + total += do_enable_sniffer1(gdb.current_progspace().frame_sniffers, + name_re, flag) + for objfile in gdb.objfiles(): + if locus_re.match(objfile.filename): + total += do_enable_sniffer1(objfile.frame_sniffers, name_re, + flag) + print("%d sniffer%s %s" % (total, "" if total == 1 else "s", + "enabled" if flag else "disabled")) + + +class EnableSniffer(gdb.Command): + """GDB command to enable sniffers. + + Usage: enable sniffer [locus-regexp [name-regexp]] + + LOCUS-REGEXP is a regular expression matching the objects to examine. + Loci are "global", the program space's file, and the objfiles within + that program space. + + NAME_REGEXP is a regular expression to filter sniffer names. + If this omitted for a specified locus, then all registered + sniffers in the locus are affected. + """ + + def __init__(self): + super(EnableSniffer, self).__init__("enable sniffer", + gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_sniffer(arg, True) + + +class DisableSniffer(gdb.Command): + """GDB command to disable the specified sniffer. + + Usage: disable sniffer [locus-regexp [name-regexp]] + + LOCUS-REGEXP is a regular expression matching the objects to examine. + Loci are "global", the program space's file, and the objfiles within + that program space. + + NAME_REGEXP is a regular expression to filter sniffer names. + If this omitted for a specified locus, then all registered + sniffers in the locus are affected. + """ + + def __init__(self): + super(DisableSniffer, self).__init__("disable sniffer", + gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_sniffer(arg, False) + + +def register_sniffer_commands(): + """Installs the sniffer commands.""" + InfoSniffer() + EnableSniffer() + DisableSniffer() + + +register_sniffer_commands() diff --git a/gdb/python/lib/gdb/function/sniffers.py b/gdb/python/lib/gdb/function/sniffers.py new file mode 100644 index 0000000..9bdca62 --- /dev/null +++ b/gdb/python/lib/gdb/function/sniffers.py @@ -0,0 +1,53 @@ +# 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 . + +"""Internal functions for working with frame sniffers.""" + +import gdb + + +def execute_sniffers(sniffer_info): + """Internal function called from GDB to execute all sniffers. + + Runs each currently enabled sniffer until it finds the one that can + unwind given frame. + + Arguments: + sniffer_info: an instance of gdb.SnifferInfo. + Returns: + Unwind info or None. See the description of the value returned + by Sniffer.__call__ below. + """ + for objfile in gdb.objfiles(): + for sniffer in objfile.frame_sniffers: + if sniffer.enabled: + unwind_info = sniffer.__call__(sniffer_info) + if unwind_info is not None: + return unwind_info + + current_progspace = gdb.current_progspace() + for sniffer in current_progspace.frame_sniffers: + if sniffer.enabled: + unwind_info = sniffer.__call__(sniffer_info) + if unwind_info is not None: + return unwind_info + + for sniffer in gdb.frame_sniffers: + if sniffer.enabled: + unwind_info = sniffer.__call__(sniffer_info) + if unwind_info is not None: + return unwind_info + + return None diff --git a/gdb/python/lib/gdb/sniffer.py b/gdb/python/lib/gdb/sniffer.py new file mode 100644 index 0000000..fbd519d --- /dev/null +++ b/gdb/python/lib/gdb/sniffer.py @@ -0,0 +1,89 @@ +# 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 . + +"""Sniffer class and register_sniffer function.""" + +import gdb + + +class Sniffer(object): + """Base class (or a template) for frame sniffers written in Python. + + A sniffer has a single method __call__ and the attributes described below. + + Attributes: + name: The name of the sniffer. + enabled: A boolean indicating whether the sniffer is enabled. + """ + + def __init__(self, name): + """Constructor. + + Args: + name: An identifying name for the sniffer. + """ + self.name = name + self.enabled = True + + def __call__(self, sniffer_info): + """GDB calls this method to unwind a frame. + + Arguments: + sniffer_info: An instance of gdb.SnifferInfo describing the frame. + + Returns: + gdb.UnwindInfo instance. + """ + raise NotImplementedError("Sniffer __call__.") + + +def register_sniffer(locus, sniffer, replace=False): + """Register sniffer in given locus. + + The sniffer is prepended to the locus's sniffers list. Sniffer + name should be unique. + + Arguments: + locus: Either an objfile, progspace, or None (in which case + the sniffer is registered globally). + sniffer: An object of a gdb.Sniffer subclass + replace: If True, replaces existing sniffer with the same name. + Otherwise, raises exception if sniffer with the same + name already exists. + + Returns: + Nothing. + + Raises: + RuntimeError: Sniffer name is not unique. + + """ + if locus is None: + if gdb.parameter("verbose"): + gdb.write("Registering global %s sniffer ...\n" % sniffer.name) + locus = gdb + else: + if gdb.parameter("verbose"): + gdb.write("Registering %s sniffer for %s ...\n" % + (sniffer.name, locus.filename)) + i = 0 + for needle in locus.frame_sniffers: + if needle.name == sniffer.name: + if replace: + del locus.frame_sniffers[i] + else: + raise RuntimeError("Sniffer %s already exists." % sniffer.name) + i += 1 + locus.frame_sniffers.insert(0, sniffer) diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c index 0aecaf6..a7bb7cd 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 frame sniffers list of functions. */ + PyObject *frame_sniffers; + /* The type-printer list. */ PyObject *type_printers; @@ -181,6 +185,7 @@ objfpy_dealloc (PyObject *o) Py_XDECREF (self->dict); Py_XDECREF (self->printers); Py_XDECREF (self->frame_filters); + Py_XDECREF (self->frame_sniffers); Py_XDECREF (self->type_printers); Py_XDECREF (self->xmethods); Py_TYPE (self)->tp_free (self); @@ -203,6 +208,10 @@ objfpy_initialize (objfile_object *self) if (self->frame_filters == NULL) return 0; + self->frame_sniffers = PyList_New (0); + if (self->frame_sniffers == NULL) + return 0; + self->type_printers = PyList_New (0); if (self->type_printers == NULL) return 0; @@ -310,6 +319,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore) return 0; } +/* Return the frame sniffers attribute for this object file. */ + +PyObject * +objfpy_get_frame_sniffers (PyObject *o, void *ignore) +{ + objfile_object *self = (objfile_object *) o; + + Py_INCREF (self->frame_sniffers); + return self->frame_sniffers; +} + +/* Set this object file's frame sniffers list to SNIFFERS. */ + +static int +objfpy_set_frame_sniffers (PyObject *o, PyObject *sniffers, void *ignore) +{ + PyObject *tmp; + objfile_object *self = (objfile_object *) o; + + if (!sniffers) + { + PyErr_SetString (PyExc_TypeError, + _("Cannot delete the frame sniffers attribute.")); + return -1; + } + + if (!PyList_Check (sniffers)) + { + PyErr_SetString (PyExc_TypeError, + _("The frame_sniffers attribute must be a list.")); + return -1; + } + + /* Take care in case the LHS and RHS are related somehow. */ + tmp = self->frame_sniffers; + Py_INCREF (sniffers); + self->frame_sniffers = sniffers; + Py_XDECREF (tmp); + + return 0; +} + /* Get the 'type_printers' attribute. */ static PyObject * @@ -645,6 +696,8 @@ static PyGetSetDef objfile_getset[] = "Pretty printers.", NULL }, { "frame_filters", objfpy_get_frame_filters, objfpy_set_frame_filters, "Frame Filters.", NULL }, + { "frame_sniffers", objfpy_get_frame_sniffers, + objfpy_set_frame_sniffers, "Frame Sniffers", 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 29b9f96..ce85b1a 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 sniffer list. */ + PyObject *frame_sniffers; + /* 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_sniffers); 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_sniffers = PyList_New (0); + if (self->frame_sniffers == 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 sniffers for this program space. */ + +PyObject * +pspy_get_frame_sniffers (PyObject *o, void *ignore) +{ + pspace_object *self = (pspace_object *) o; + + Py_INCREF (self->frame_sniffers); + return self->frame_sniffers; +} + +/* Set this program space's list of the sniffers to SNIFFERS. */ + +static int +pspy_set_frame_sniffers (PyObject *o, PyObject *sniffers, void *ignore) +{ + PyObject *tmp; + pspace_object *self = (pspace_object *) o; + + if (!sniffers) + { + PyErr_SetString (PyExc_TypeError, + "cannot delete the frame sniffers list"); + return -1; + } + + if (!PyList_Check (sniffers)) + { + PyErr_SetString (PyExc_TypeError, + "the frame sniffers attribute must be a list"); + return -1; + } + + /* Take care in case the LHS and RHS are related somehow. */ + tmp = self->frame_sniffers; + Py_INCREF (sniffers); + self->frame_sniffers = sniffers; + 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_sniffers", pspy_get_frame_sniffers, pspy_set_frame_sniffers, + "Frame sniffers.", 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..3e49766 --- /dev/null +++ b/gdb/python/py-unwind.c @@ -0,0 +1,743 @@ +/* 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 + struct frame_info *frame_info; +} sniffer_info_object; + +/* The data we keep for the PyUnwindInfo: sniffer_info, previous + * frame's register set and frame ID. */ + +typedef struct +{ + PyObject_HEAD + + /* gdb.SnifferInfo for the frame we are unwinding. */ + PyObject *sniffer_info; + + /* Previous frame registers tuple (2-tuple per register). */ + PyObject *previous_frame_registers; + + /* Its ID. */ + struct frame_id frame_id; +} 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; + } reg[]; +} cached_frame_info; + +static PyTypeObject sniffer_info_object_type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_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; + +/* Parse Python Int, saving it at the given address. Returns 1 on success, + 0 otherwise. */ + +static int +pyuw_parse_int (PyObject *pyo_int, int *valuep) +{ + long long_value; + + if (pyo_int == NULL) + return 0; + /* Make a long logic check first. In Python 3.x, internally, all + integers are represented as longs. In Python 2.x, there is still + a differentiation internally between a PyInt and a PyLong. + Explicitly do this long check conversion first. In GDB, for + Python 3.x, we #ifdef PyInt = PyLong. This check has to be done + first to ensure we do not lose information in the conversion + process. */ + else if (PyLong_Check (pyo_int)) + { + LONGEST l = PyLong_AsLongLong (pyo_int); + + if (PyErr_Occurred ()) + return 0; + long_value = (long)l; + if (l != long_value) + return 0; + } + else if (PyInt_Check (pyo_int)) + { + long_value = PyInt_AsLong (pyo_int); + if (PyErr_Occurred ()) + return 0; + } + else + return 0; + if (long_value != (int) long_value) + return 0; + *valuep = (int) long_value; + return 1; +} + +/* Parses (REGNUM, REGVALUE) tuple. Returns 1 on success, 0 otherwise. */ +static int +pyuw_parse_register_tuple(PyObject *pyo_reg, + int *reg_num, struct value **reg_value) +{ + + if (pyo_reg != NULL && PyTuple_Check (pyo_reg) + && PyTuple_Size (pyo_reg) == 2 + && pyuw_parse_int (PyTuple_GetItem (pyo_reg, 0), reg_num)) + { + PyObject *pyo_reg_value = PyTuple_GetItem (pyo_reg, 1); + if (pyo_reg_value != NULL + && (*reg_value = value_object_to_value (pyo_reg_value)) != NULL) + return 1; + } + return 0; +} + +/* Called by the Python interpreter to obtain string representation + of the SnifferInfo object. */ + +static PyObject * +sniffer_infopy_str (PyObject *self) +{ + char *s; + PyObject *result; + struct frame_info *frame = ((sniffer_info_object *)self)->frame_info; + + if (frame == NULL) + return PyString_FromString ("Stale SnifferInfo instance"); + s = xstrprintf ("SP=%s,PC=%s", core_addr_to_string_nz (get_frame_sp (frame)), + core_addr_to_string_nz (get_frame_pc (frame))); + result = PyString_FromString (s); + xfree (s); + + return result; +} + +/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value. + Returns the value of register REGNUM as gdb.Value instance. */ + +static PyObject * +sniffer_infopy_read_register (PyObject *self, PyObject *args) +{ + volatile struct gdb_exception except; + int regnum; + struct value *val = NULL; + + if (!PyArg_ParseTuple (args, "i", ®num)) + return NULL; + if (((sniffer_info_object *) self)->frame_info == NULL) + error (_("Attempting to read register from stale SnifferInfo")); + + TRY_CATCH (except, RETURN_MASK_ALL) + { + struct frame_info *frame = ((sniffer_info_object *) self)->frame_info; + struct gdbarch *gdbarch = get_frame_arch (frame); + char *error_text = NULL; + + /* Validate regnum to prevent assert in `regcache_cooked_read_value'. */ + if (user_reg_map_regnum_to_name (gdbarch, regnum) == NULL) + error_text = xstrprintf (_("Bad register number: %d."), regnum); + else + { + gdb_byte buffer[MAX_REGISTER_SIZE]; + + /* Call `deprecated_frame_register_read' -- calling + `value_of_register' would an assert in `get_frame_id' + because our frame is incomplete. */ + if (deprecated_frame_register_read (frame, regnum, buffer)) + val = value_from_contents (register_type (gdbarch, regnum), + buffer); + if (val == NULL) + error_text = xstrprintf (_("Cannot read register %d from frame."), + regnum); + } + if (error_text != NULL) + { + PyErr_SetString (PyExc_ValueError, error_text); + xfree (error_text); + } + } + GDB_PY_HANDLE_EXCEPTION (except); + + return val == NULL ? NULL : value_to_value_object (val); +} + +/* Create Python SnifferInfo object. */ + +static PyObject * +frame_info_to_sniffer_info_object (struct frame_info *frame) +{ + sniffer_info_object *sniffer_info + = PyObject_New (sniffer_info_object, &sniffer_info_object_type); + + sniffer_info->frame_info = frame; + return (PyObject *) sniffer_info; +} + +/* Invalidate SnifferInfo object. */ +static void +sniffer_info_invalidate (PyObject *pyo_sniffer_info) +{ + if (pyo_sniffer_info == NULL) + return; + ((sniffer_info_object *) pyo_sniffer_info)->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); +} + +/* Called by the Python interpreter to obtain string representation + if the UnwindInfo object. */ +static PyObject * +unwind_infopy_str (PyObject *self) +{ + PyObject *result; + struct ui_file *strfile = mem_fileopen (); + unwind_info_object *pyo_unwind_info = (unwind_info_object *) self; + + fprintf_unfiltered (strfile, "Frame ID: "); + fprint_frame_id (strfile, pyo_unwind_info->frame_id); + { + int reg_count = PyTuple_Size (pyo_unwind_info->previous_frame_registers); + int i; + char *sep = ""; + struct value_print_options opts; + + get_user_print_options (&opts); + fprintf_unfiltered (strfile, "\nPrevious frame registers: ("); + for (i = 0; i < reg_count; i++) + { + int reg_num; + struct value *reg_value; + PyObject *pyo_reg = PyTuple_GetItem ( + pyo_unwind_info->previous_frame_registers,i); + + if (pyuw_parse_register_tuple (pyo_reg, ®_num, ®_value)) + { + fprintf_unfiltered (strfile, "%s(%d, ", sep, reg_num); + value_print (reg_value, strfile, &opts); + fprintf_unfiltered (strfile, ")"); + } + 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; +} + +/* Convert gdb.Value object to COREADDR. */ +static int +pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr) +{ + struct value *value = value_object_to_value (pyo_value); + + if (value == NULL) + return 0; + *addr = unpack_pointer (value_type (value), value_contents (value)); + return 1; +} + +/* Implementation of gdb.UnwindInfo.frame_id_build (self, SP, PC) -> None. */ + +static PyObject * +unwind_infopy_frame_id_build (PyObject *self, PyObject *args) +{ + PyObject *pyo_sp; + PyObject *pyo_pc; + CORE_ADDR sp; + CORE_ADDR pc; + + if (!PyArg_ParseTuple (args, "OO:frame_id_build", &pyo_sp, &pyo_pc) + || !pyuw_value_obj_to_pointer (pyo_sp, &sp) + || !pyuw_value_obj_to_pointer (pyo_pc, &pc)) + return NULL; + + ((unwind_info_object *) self)->frame_id = frame_id_build (sp, pc); + Py_INCREF (Py_None); + return Py_None; +} + +/* Implementation of + gdb.UnwindInfo.frame_id_build_special (self, SP, PC, SPECIAL) -> None. */ + +static PyObject * +unwind_infopy_frame_id_build_special (PyObject *self, PyObject *args) +{ + PyObject *pyo_sp; + PyObject *pyo_pc; + PyObject *pyo_special; + CORE_ADDR sp; + CORE_ADDR pc; + CORE_ADDR special; + + if (!PyArg_ParseTuple (args, "OOO:frame_id_build_special", + &pyo_sp, &pyo_pc, &pyo_special) + || !pyuw_value_obj_to_pointer (pyo_sp, &sp) + || !pyuw_value_obj_to_pointer (pyo_pc, &pc) + || !pyuw_value_obj_to_pointer (pyo_special, &special)) + return NULL; + + ((unwind_info_object *) self)->frame_id + = frame_id_build_special (sp, pc, special); + Py_INCREF (Py_None); + return Py_None; +} + +/* Implementation of gdb.UnwindInfo.frame_build_id_wild (self, SP) -> None. */ + +static PyObject * +unwind_infopy_frame_id_build_wild (PyObject *self, PyObject *args) +{ + PyObject *pyo_sp; + CORE_ADDR sp; + + if (!PyArg_ParseTuple (args, "O:frame_id_build_wild", &pyo_sp) + || !pyuw_value_obj_to_pointer (pyo_sp, &sp)) + return NULL; + + ((unwind_info_object *) self)->frame_id = frame_id_build_wild (sp); + Py_INCREF (Py_None); + return Py_None; +} + +/* Initialize new UnwindInfo object. */ +static int +unwind_infopy_init (PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *pyo_sniffer_info; + PyObject *pyo_regs; + + if (PyArg_UnpackTuple (args, "__init__", 2, 2, &pyo_sniffer_info, &pyo_regs) + && (pyo_sniffer_info != NULL + && PyObject_IsInstance (pyo_sniffer_info, + (PyObject *) &sniffer_info_object_type) > 0) + && pyo_regs != NULL + && PyTuple_Check (pyo_regs)) + { + unwind_info_object *unwind_info = (unwind_info_object *) self; + unwind_info->frame_id = null_frame_id; + unwind_info->sniffer_info = pyo_sniffer_info; + Py_INCREF (pyo_sniffer_info); + unwind_info->previous_frame_registers = pyo_regs; + Py_INCREF (pyo_regs); + return 0; + } + return -1; +} + +static void +unwind_infopy_dealloc (PyObject *self) +{ + unwind_info_object *unwind_info = (unwind_info_object *) self; + Py_XDECREF (unwind_info->sniffer_info); + Py_XDECREF (unwind_info->previous_frame_registers); + Py_TYPE (self)->tp_free (self); +} + +/* Frame sniffer dispatch. */ + +static int +pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame, + void **cache_ptr) +{ + struct gdbarch *gdbarch; + struct cleanup *cleanups; + struct cleanup *cached_frame_cleanups; + PyObject *pyo_module; + PyObject *pyo_execute; + PyObject *pyo_sniffer_info; + PyObject *pyo_unwind_info; + cached_frame_info *cached_frame = NULL; + + gdbarch = (struct gdbarch *) (self->unwind_data); + cleanups = ensure_python_env (gdbarch, current_language); + 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))); + pyo_sniffer_info = frame_info_to_sniffer_info_object (this_frame); + if (pyo_sniffer_info == NULL) + goto error; + make_cleanup_py_decref (pyo_sniffer_info); + + if ((pyo_module = PyImport_ImportModule ("gdb.function.sniffers")) == NULL) + goto error; + make_cleanup_py_decref (pyo_module); + + pyo_execute = PyObject_GetAttrString (pyo_module, "execute_sniffers"); + if (pyo_execute == NULL) + goto error; + make_cleanup_py_decref (pyo_execute); + + pyo_unwind_info + = PyObject_CallFunctionObjArgs (pyo_execute, pyo_sniffer_info, NULL); + if (pyo_unwind_info == NULL) + goto error; + make_cleanup_py_decref (pyo_unwind_info); + if (pyo_unwind_info == Py_None) + goto error; + if (PyObject_IsInstance (pyo_unwind_info, + (PyObject *) &unwind_info_object_type) <= 0) + error (_("A Sniffer should return gdb.UnwindInfo instance.")); + + { + unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info; + PyObject *pyo_registers = unwind_info->previous_frame_registers; + int i; + int reg_count; + size_t cached_frame_size; + size_t gdb_bytes_count; + gdb_byte *gdb_data_free, *gdb_data_end; + + if (frame_id_eq (unwind_info->frame_id, null_frame_id)) + error (_("Sniffer should call one of the frame_id_build methods " + "on UnwindInfo instance before returning it.")); + if (pyo_registers == NULL) + goto error; + if (!PyTuple_Check (pyo_registers)) + error (_("Registers passed to UnwindInfo constructor should be a tuple.")); + + /* Figure out how much space we need to allocate. */ + reg_count = PyTuple_Size (pyo_registers); + if (reg_count <= 0) + error (_("Register list should not be empty.")); + /* We might overestimate `gdb_bytes_count', but it's not worth + parsing register numbers twice to calculate the exact number of + bytes needed. */ + gdb_bytes_count = reg_count * MAX_REGISTER_SIZE; + cached_frame_size = sizeof (*cached_frame) + + reg_count * sizeof (cached_frame->reg[0]) + + gdb_bytes_count * sizeof (gdb_byte); + + cached_frame = xmalloc (cached_frame_size); + cached_frame_cleanups = make_cleanup (xfree, cached_frame); + /* Allocations after this point will be discarded! */ + + gdb_data_end = (gdb_byte *)((char *) cached_frame + cached_frame_size); + gdb_data_free = gdb_data_end - gdb_bytes_count; + + cached_frame->gdbarch = gdbarch; + cached_frame->frame_id = unwind_info->frame_id; + cached_frame->reg_count = reg_count; + + /* Populate registers array. */ + for (i = 0; i < reg_count; i++) + { + struct reg_info *reg = &(cached_frame->reg[i]); + struct value *value; + size_t data_size; + + if (!pyuw_parse_register_tuple (PyTuple_GetItem (pyo_registers, i), + ®->number, &value)) + error (_("Python sniffer returned bad register tuple: " + "item #%d is not a (reg_no, reg_data) tuple " + "with integer reg_no and gdb.Value reg_data."), i); + + data_size = register_size (gdbarch, reg->number); + if (data_size != TYPE_LENGTH (value_enclosing_type (value))) + error (_("The value of the register #%d returned by the " + "Python sniffer has unexpected size: %u instead " + "of %u."), reg->number, + (unsigned)(TYPE_LENGTH (value_enclosing_type (value))), + (unsigned)data_size); + + /* Should not overflow the buffer. */ + gdb_assert ((gdb_data_free + data_size) <= gdb_data_end); + memcpy (gdb_data_free, value_contents (value), data_size); + reg->data = gdb_data_free; + gdb_data_free += data_size; + } + } + + *cache_ptr = cached_frame; + discard_cleanups (cached_frame_cleanups); + do_cleanups (cleanups); + sniffer_info_invalidate (pyo_sniffer_info); + return 1; + +error: + do_cleanups (cleanups); + sniffer_info_invalidate (pyo_sniffer_info); + 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 sniffers + 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); + TRACE_PY_UNWIND (1, "%s: registered unwinder for %s\n", __FUNCTION__, + gdbarch_bfd_arch_info (newarch)->printable_name); + 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, Pythin 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 (&sniffer_info_object_type) < 0) + return -1; + rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo", + (PyObject *) &sniffer_info_object_type); + if (rc) + return rc; + + unwind_info_object_type.tp_new = PyType_GenericNew; + if (PyType_Ready (&unwind_info_object_type) < 0) + return -1; + return gdb_pymodule_addobject (gdb_module, "UnwindInfo", + (PyObject *) &unwind_info_object_type); +} + +static PyMethodDef sniffer_info_object_methods[] = +{ + { "read_register", sniffer_infopy_read_register, METH_VARARGS, + "read_register (REGNUM) -> gdb.Value\n\ +Return the value of the REGNUM in the frame." }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject sniffer_info_object_type = +{ + PyVarObject_HEAD_INIT (NULL, 0) + "gdb.SnifferInfo", /* tp_name */ + sizeof (sniffer_info_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 */ + sniffer_infopy_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "GDB snifferInfo object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + sniffer_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 */ +}; + + +static PyMethodDef unwind_info_object_methods[] = +{ + { "frame_id_build", unwind_infopy_frame_id_build, METH_VARARGS, + "frame_id_build (SP, PC) -> None\n" + "Build frame ID from given SP and PC." }, + { "frame_id_build_special", unwind_infopy_frame_id_build_special, + METH_VARARGS, + "frame_id_build_special (SP, PC, SPECIAL) -> None\n" + "Build frame ID from given SP, PC and SPECIAL." }, + { "frame_id_build_wild", unwind_infopy_frame_id_build_wild, METH_VARARGS, + "frame_id_build_wild (SP) -> None\nBuild frame ID from given SP." }, + { 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 */ + unwind_infopy_init, /* tp_init */ + 0, /* tp_alloc */ +}; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index a77f5a6..34179de 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -390,12 +390,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_sniffers (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_sniffers (PyObject *, void *); PyObject *objfpy_get_xmethods (PyObject *, void *); PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw); @@ -490,6 +492,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 344d8d2..3e079b5 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1790,7 +1790,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/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 . */ + +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..a10e1aa --- /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 sniffer" "Show all sniffers" { + "global sniffers:" + " global_sniffer" + "progspace.*sniffers:" + "py_unwind_maint_ps_sniffer" +} + +gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"] + +gdb_test_sequence "continue" "Sniffers called" { + "py_unwind_maint_ps_sniffer called" + "global_sniffer called" +} + +gdb_test "disable sniffer global .*" "1 sniffer disabled" "Sniffer disabled" + +gdb_test_sequence "info sniffer" "Show with global sniffer disabled" { + "global sniffers:" + " global_sniffer\\[disabled\\]" + "progspace.*sniffers:" + " py_unwind_maint_ps_sniffer" +} + +gdb_test_sequence "where" "Global sniffer disabled" { + "py_unwind_maint_ps_sniffer 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..74b702c --- /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 sniffers. + +import re +import gdb.types +from gdb.sniffer import Sniffer, register_sniffer + +class TestGlobalSniffer(Sniffer): + def __init__(self): + super(TestGlobalSniffer, self).__init__("global_sniffer") + + def __call__(self, sniffer_info): + print "%s called" % self.name + return None + +class TestProgspaceSniffer(Sniffer): + def __init__(self, name): + super(TestProgspaceSniffer, self).__init__("%s_ps_sniffer" % name) + + def __call__(self, sniffer_info): + print "%s called" % self.name + return None + +class TestObjfileSniffer(Sniffer): + def __init__(self, name): + super(TestObjfileSniffer, self).__init__("%s_obj_sniffer" % name) + + def __call__(self, sniffer_info): + print "%s called" % self.name + return None + + + +gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer()) +saw_runtime_error = False +try: + gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer(), replace=False) +except RuntimeError: + saw_runtime_error = True +if not saw_runtime_error: + raise RuntimeError("Missing runtime error from register_sniffer.") +gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer(), replace=True) +gdb.sniffer.register_sniffer(gdb.current_progspace(), + TestProgspaceSniffer("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 . */ + +/* 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 0000000..4bf09b3 --- /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 +# sniffers 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 sniffer" { + "\\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..a8d591f --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.py @@ -0,0 +1,81 @@ +# 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.sniffer import Sniffer + +class TestSniffer(Sniffer): + AMD64_RBP = 6 + AMD64_RSP = 7 + AMD64_RIP = 16 + + def __init__(self): + Sniffer.__init__(self, "test sniffer") + 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, sniffer_info): + """Test sniffer written in Python. + + This sniffer 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 sniffer recognizes the corrupt frames by checking that + *RBP == RBP, and restores previous RBP from the word above it. + """ + try: + bp = sniffer_info.read_register( + TestSniffer.AMD64_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 + unwind_info = gdb.UnwindInfo( + sniffer_info, + ((TestSniffer.AMD64_RBP, previous_bp), + (TestSniffer.AMD64_RIP, previous_ip), + (TestSniffer.AMD64_RSP, previous_sp))) + sp = sniffer_info.read_register(TestSniffer.AMD64_RSP) + ip = sniffer_info.read_register(TestSniffer.AMD64_RIP) + unwind_info.frame_id_build(sp, ip) + return unwind_info + except (gdb.error, RuntimeError): + return None + +gdb.sniffer.register_sniffer(None, TestSniffer(), True) +print("Python script imported")