From patchwork Thu Feb 26 03:09:18 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Smundak X-Patchwork-Id: 5299 Received: (qmail 75371 invoked by alias); 26 Feb 2015 03:09:32 -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 75349 invoked by uid 89); 26 Feb 2015 03:09:28 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.0 required=5.0 tests=AWL, BAYES_50, RCVD_IN_DNSWL_LOW, SPF_PASS, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-HELO: mail-ig0-f181.google.com Received: from mail-ig0-f181.google.com (HELO mail-ig0-f181.google.com) (209.85.213.181) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES128-GCM-SHA256 encrypted) ESMTPS; Thu, 26 Feb 2015 03:09:21 +0000 Received: by mail-ig0-f181.google.com with SMTP id hn18so11368002igb.2 for ; Wed, 25 Feb 2015 19:09:18 -0800 (PST) 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=D+Gxtv3F8ZtXSVNTXK7hiwGGRpZyxYgt26b5ub/dIIQ=; b=cpdejcsK0zkDCDnvX3XrlHm9f6lKXtzy+q5MX/I0uKJhb3HOByWMnJaU/XoI3Jm9ic lG4kFM6MsEg3q58X+ANjCYM1UxuiCCZQM/Kayp1ujnWS3ZiIkdA4LJmo9m9dhdy9sXfY n1QqffakIKpHBOggLiOqJbwXVCUDWPRlUPKWlI/t6Jrjsq/PW5l2k36sIH051hIxZSZ1 VbRjcYA9e/0r9Us/iM/VDwU7pW9EqXinfRMKXwr/V2k4/C4j+XjaI0ckDIDlb1XMbrX5 SBDBBI+LiusOOFqjbitBf56RBCyZ9WOiIftrLk/yNyceZozn9Beoahr++i+N5AA+QTs6 j1UQ== X-Gm-Message-State: ALoCoQkpbh6Awt3mnQzUc8TLxgHFKUXGDN5DaAdJ687+Soqazx5p9v+VldrUgfVulFdWGXEEkjZC MIME-Version: 1.0 X-Received: by 10.107.137.101 with SMTP id l98mr8905902iod.23.1424920158394; Wed, 25 Feb 2015 19:09:18 -0800 (PST) Received: by 10.64.227.110 with HTTP; Wed, 25 Feb 2015 19:09:18 -0800 (PST) In-Reply-To: <54E71694.1080304@redhat.com> References: <21714.40641.510825.30998@ruffy2.mtv.corp.google.com> <54E71694.1080304@redhat.com> Date: Wed, 25 Feb 2015 19:09:18 -0800 Message-ID: Subject: Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python From: Alexander Smundak To: Phil Muldoon Cc: Doug Evans , gdb-patches Here's another round. I am not sure who will be the reviewer this time. >>> Is it possible to see the code, and example usage, of a real-life use-case >>> of this? That will help folks not familiar with this project to understand >>> the problem we are trying to solve. > > I agree. > >> The full implementation of the combined sniffer/frame filter for OpenJDK >> is about 2500 lines and will eventually become part of it. I am waiting for >> this GDB patch to be reviewed before I can present it to be reviewed by >> the JDK community :-) I am going to publish it on openjdk.java.net site once the site's admin updates my credentials. > You could expand the testcase a little? I have revised it really 'a little', i.e., there is more than one frame with broken previous frame link, causing standard unwinders to fail, but I am not sure it's enough. On the other hand, this is a test, making it too complex would make it harder to debug. > Please make errors a complete sentence with capitalization and > punctuation. Done. > + > + 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) > > I think parse_sniffer_command_args should error if the loci is not > global, progspace or objectfile. Either that or the default to object > file should be documented here, and in the parse_sniffer_command_args > function. Not sure what you mean -- locus can be either a literal "global", a literal "progspace", or a regex to match object file name. > + 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(): > > There seems no way *not* to parse object files for sniffers? Say I set > the loci to "global", that's what I want. Given there could be 1000s > of object files, I think this default is a bit odd? I might be > misunderstanding though. From the point of view of the output, issuing `info sniffer global' will yield confusing result if the current progspace happens to contain an object file with filename 'global' exactly. IMHO it's not a big deal, especially taking into account that object file names tend to be absolute paths (the only one I've seen so far not having an absolute path has filename "system-supplied DSO at 0x7ffff7ffa000". As to the concerns about the performance: frankly, I've patterned the code after pretty_printers.py in the same directory, thinking that if the performance of the 'info sniffer global' will be on par with that of 'info pretty-printer global'. > +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(): > > Again, in an environment where there may be many object files this seems > a rather wasteful search if I set the loci to "global", and then we > search all object files pointlessly? Judging by the experiments dje@ asked me to conduct, the performance impact is fairly small, while the effort to maintain the exact list of the curently enabled sniffers is non-trivial. I can conduct additional experiments with thousands of object files to see if it's worth it. > + 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")) > > > +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 > > I think we may need a priority system. We ran into this with frame > filters. Allow me to explain a scenario, and how a user might solve > it. I don't think there is much common between frame filters and frame sniffers. The output of a filter is passed to the next filter, whereas a sniffer just returns the previous frame, I am not sure why the output of one sniffer may be handed down to the next one. > Say in the future I am debugging openjdk, and the RPM on my Fedora > system installs a bunch of frame sniffers in the GDB auto-loading > path. So they get auto-loaded when the openjdk inferior is > loaded. Good, that's what I want. But what happens if I want to > register my own frame sniffer that does additional stuff? If a sniffer already knows how to unwind a frame, there is nothing more to do, and there is no need to override this (unless you are debugging the sniffer proper, but this IMHO is a corner case). This is quite different from the frame filters behavior. > --- /dev/null > +++ b/gdb/python/lib/gdb/sniffer.py > @@ -0,0 +1,113 @@ > +# Copyright (C) 2013-2015 > > This and others, 2015 only. Done. > + > +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. > > priority: The priority order of the sniffers > (If you go with the idea of a priority based system) > > > + Returns: > + (REG_DATA, FRAME_ID_REGNUMS) tuple, or None if this sniffer cannot > + unwind the frame. > + REG_DATA is a tuple containing the registers in the unwound frame. > + Each element in thr REG_DATA is a (REG_NUM, REG_VALUE) tuple, > + where REG_NUM is platform-specific register number, and REG_VALUE > + is register value (a gdb.Value instance). > > See below > > + FRAME_ID_REGNUMS is a tuple specifying the registers used > + to construct the frame ID. For instance, on x86_64, > + FRAME_ID_REGNUMS is (7, 16), as 7 is the stack pointer > + register (RSP), and 16 is the instruction pointer (RIP). > + FRAME_ID_REGNUMS is a (SP), (SP, PC), or (SP, PC, > SPECIAL) > > This seems like an internal detail exposed a little too much? Why not > return an object with the attributes SP, PC and SPECIAL? These could > be mapped from the Python API register methods talked of earlier in > the previous emails. These three registers are either returned (SP > looks to be the mandatory one), or not. No additional registers other > than the three listed will be included in FRAME_ID_REGNUMS? > > Does the actual number of the register matter here to the > user/implementer? You tell the user the register number for SP, PC > and SPECIAL as you populate them in tuple order. Why make the user > jump through hops to map values to those three register numbers from > REG_DATA when you could do it for them? You could still return an > attribute containing the tuple REG_DATA if they wanted to poke around > the other registers. It seem like, though, we don't tell them what the > other registers are, so only the three named are important. > > There could be a very good reason for this, but I cannot see it > myself. What do you think? > I am struggling to describe platform-specific stuff in a platform-independent manner. Perhaps a better approach would be to spell what a sniffer is expected to return for each platform (... denotes optional (regnum, regvalue) 2-tuples): x86_64: (((#RBP, $RBP), (#RSP, $RSP), (#RIP, $RIP), ...), (#RSP, #RIP)) where #RBP=6, #RSP=7, #RIP=16 x86: (((#EBP, $EBP), (#ESP, $ESP), (#EIP, $EIP), ...), (#ESP, #EIP)) where #EBP=5, #ESP=4, #EIP=8 PPC64: (((#R1, $R1), (#PC, $PC), ...), (#R1, #PC)) where #R1=1, #PC=64) I don't think it's too much to ask unwinder's author to return such tuples (BTW, it also shows the for x86 the sniffer is expected to return xBP in addition to xSP and xIP). Do you think it will be better if the expected values were documented per platform? > > + tuple, where SP, PC, and SPECIAL are register numbers. Depending > + on the tuple, Python sniffer support code uses internal GDB > + functions to construct frame ID as follows: > + (SP) frame_id_build_wild (Value(SP)) > + (SP, PC) frame_id_build (Value(SP), Value(PC)) > + (SP, PC, SPECIAL) frame_id_build_special (Value(SP), > + Value(PC), Value(SPECIAL) > + The registers in the FRAME_ID_REGNUMS tuple should be among those > + returned by REG_DATA. > + The chapter "Stack Frames" in the GDB Internals Guide describes > + frame ID. > + """ > + raise NotImplementedError("Sniffer __call__") > > There might be a super-class provided somewhere the implements the > comments above. So comments might apply there too. Not sure what you mean here, please elaborate. > +typedef struct > +{ > + struct frame_id frame_id; > + struct gdbarch *gdbarch; > + int reg_count; > + struct reg_info > + { > + int number; > + gdb_byte *data; > + } reg[]; > +} cached_frame_info; > > Each field in a struct needs to be documented. Commented cached_frame_info fields. > > +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; > + > + TRY_CATCH (except, RETURN_MASK_ALL) > + { > + /* Calling `value_of_register' will result in infinite recursion. > + Call `deprecated_frame_register_read' instead. */ > > Can you explain this comment in more detail? > > + if (deprecated_frame_register_read (frame, regnum, buffer)) > + val = value_from_contents (register_type (gdbarch, regnum), buffer); > + if (val == NULL) > + { > + char *error_text > + = xstrprintf (_("Cannot read register %d from frame"), regnum); > + PyErr_SetString (PyExc_ValueError, error_text); > + xfree (error_text); > > I believe in this case you are overwriting the GDB generated exception > with a more generic error message. Don't do it. I have changed the code to validate `regnum' by calling `user_ret_map_regnum_to_name', but I am not sure which exception you mean. > +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); > + > + if (sniffer_info != NULL) > > This is a new object so you should unconditionally write the > attribute? Done. > +static int > +pyuw_parse_int (PyObject *pyo_int, int *valuep) > +{ > > There's an issue with Python 2.x and Python 3.x compatibility with > longs. Please see py-value.c:1447 comment and resulting logic to > ensure this works in both cases. Done. > +static int > +pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame, > + void **cache_ptr) > +... > + gdb_assert (*cache_ptr == NULL); > > Can you please document the assert, and the reason for it. Deleted this assert. > > + gdbarch = (void *)(self->unwind_data); > > This cast seems odd to me. Indeed. > + 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); > + } > > Can you please document this assert reason. I assume you mean the one on the line below: > + gdb_assert ((gdb_data_free + data_size) <= gdb_data_end); It's an overflow check -- is it redundant? The ChangeLog files remain unchanged since last round, and the new patch is attached. 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..62537d2 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,101 @@ 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 Returning Previous Frame + +If sniffer's @code{__call__} method recognizes the frame, it should +return a (@var{registers}, @var{frame_id_register_numbers}) tuple. + +@var{registers} describe 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). + +@var{frame_id_register_numbers} is a tuple specifying the registers +used to construct frame ID of the returned frame. It is a (@var{sp}), +(@var{sp}, @var{pc}) or (@var{sp}, @var{pc}, @var{special}) tuple, +where @var{sp}, @var{pc}, @var{special} are register numbers. The +referenced registers should be present in @var{registers} tuple. The +frame ID is constructed by calling +@code{frame_id_build_wild}(@var{value}(@var{sp})), +@code{frame_id_build}(@var{value}(@var{sp}), @var{value}(@var{pc})), +or @code{frame_id_build}(@var{value}(@var{sp}), @var{value}(@var{pc}), +@var{value}(@var{special})) respectively. + +@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..650ad3b --- /dev/null +++ b/gdb/python/lib/gdb/sniffer.py @@ -0,0 +1,110 @@ +# 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: + (REG_DATA, FRAME_ID_REGNUMS) tuple, or None if this sniffer cannot + unwind the frame. + REG_DATA is a tuple containing the registers in the unwound frame. + Each element in thr REG_DATA is a (REG_NUM, REG_VALUE) tuple, + where REG_NUM is platform-specific register number, and REG_VALUE + is register value (a gdb.Value instance). + FRAME_ID_REGNUMS is a tuple specifying the registers used + to construct the frame ID. For instance, on x86_64, + FRAME_ID_REGNUMS is (7, 16), as 7 is the stack pointer + register (RSP), and 16 is the instruction pointer (RIP). + FRAME_ID_REGNUMS is a (SP), (SP, PC), or (SP, PC, SPECIAL) + tuple, where SP, PC, and SPECIAL are register numbers. Depending + on the tuple, Python sniffer support code uses internal GDB + functions to construct frame ID as follows: + (SP) frame_id_build_wild (Value(SP)) + (SP, PC) frame_id_build (Value(SP), Value(PC)) + (SP, PC, SPECIAL) frame_id_build_special (Value(SP), + Value(PC), Value(SPECIAL) + The registers in the FRAME_ID_REGNUMS tuple should be among those + returned by REG_DATA. + The chapter "Stack Frames" in the GDB Internals Guide describes + frame ID. + """ + 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..94b2c57 --- /dev/null +++ b/gdb/python/py-unwind.c @@ -0,0 +1,582 @@ +/* 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 "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 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 unsigned int pyuw_debug = 0; + +static struct gdbarch_data *pyuw_gdbarch_data; + +/* 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; + + 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; + + 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; +} + +/* 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; +} + +/* Parse given tuple of Python Ints into an array. Returns the number + of items in the tuple, or -1 on error (bad tuple element type, + array too small). */ + +static Py_ssize_t +pyuw_parse_ints (PyObject *pyo_sequence, int *values, Py_ssize_t max_values) +{ + Py_ssize_t size; + Py_ssize_t i; + + if (! PyTuple_Check (pyo_sequence)) + return -1; + size = PyTuple_Size (pyo_sequence); + if (size < 0 || size > max_values) + return -1; + for (i = 0; i < size; ++i) + { + if (!pyuw_parse_int (PyTuple_GetItem (pyo_sequence, i), &values[i])) + return -1; + } + return i; +} + +/* Retrieve register value for the cached unwind info as target pointer. + Return 1 on success, 0 on failure. */ + +static int +pyuw_reg_value (cached_frame_info *cached_frame, int regnum, CORE_ADDR *value) +{ + struct reg_info *reg_info = cached_frame->reg; + struct reg_info *reg_info_end = reg_info + cached_frame->reg_count; + + for (; reg_info < reg_info_end; ++reg_info) + { + if (reg_info->number == regnum) + { + *value = unpack_pointer + (register_type (cached_frame->gdbarch, regnum), reg_info->data); + return 1; + } + } + + error (_("Python sniffer uses register #%d for this_id, " + "but this register is not available."), regnum); +} + +/* 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); +} + +/* Parse frame ID tuple returned by the sniffer info GDB's frame_id and + save it in the cached frame. */ + +static void +pyuw_parse_frame_id (cached_frame_info *cached_frame, + PyObject *pyo_frame_id_regs) +{ + int regno[3]; + CORE_ADDR sp, pc, special; + + if (!PyTuple_Check (pyo_frame_id_regs)) + error (_("The second element of the pair returned by a Python " + "sniffer should be a tuple.")); + + switch (pyuw_parse_ints (pyo_frame_id_regs, regno, ARRAY_SIZE (regno))) + { + case 1: + if (pyuw_reg_value (cached_frame, regno[0], &sp)) + { + cached_frame->frame_id = frame_id_build_wild (sp); + return; + } + break; + case 2: + if (pyuw_reg_value (cached_frame, regno[0], &sp) + || pyuw_reg_value (cached_frame, regno[1], &pc)) + { + cached_frame->frame_id = frame_id_build (sp, pc); + return; + } + break; + case 3: + if (pyuw_reg_value (cached_frame, regno[0], &sp) + || pyuw_reg_value (cached_frame, regno[1], &pc) + || pyuw_reg_value (cached_frame, regno[2], &special)) + { + cached_frame->frame_id = frame_id_build_special (sp, pc, special); + return; + } + break; + } + error (_("Unwinder should return a tuple of 1 to 3 ints in the second item.")); +} + +/* 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; + + /* Unwind_info is a (REGISTERS, FRAME_ID_REGNUMS) tuple. REGISTERS + * is a tuple of the (REG_NR, REG_VALUE) tuples. FRAME_ID_REGNUMS is + * the tuple of REGNO values. */ + if (!PyTuple_Check (pyo_unwind_info) + || PyTuple_Size (pyo_unwind_info) != 2) + error (_("Sniffer should return a pair (REGISTERS, FRAME_ID_REGNUMS).")); + + { + PyObject *pyo_registers = PyTuple_GetItem (pyo_unwind_info, 0); + int i; + int reg_count; + size_t cached_frame_size; + size_t gdb_bytes_count; + gdb_byte *gdb_data_free, *gdb_data_end; + + if (pyo_registers == NULL) + goto error; + if (!PyTuple_Check (pyo_registers)) + error (_("The first element of the returned pair 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->reg_count = reg_count; + + /* Populate registers array. */ + for (i = 0; i < reg_count; i++) + { + PyObject *pyo_reg = PyTuple_GetItem (pyo_registers, i); + struct reg_info *reg = &(cached_frame->reg[i]); + + if (pyo_reg == NULL) + goto error; + + if (!PyTuple_Check (pyo_reg) || PyTuple_Size (pyo_reg) != 2 + || !pyuw_parse_int (PyTuple_GetItem (pyo_reg, 0), + ®->number)) + { + error (_("Python sniffer returned bad register tuple: " + "item #%d is not a (reg_no, reg_data) tuple " + "with integer reg_no and reg_data."), i); + } + + { + PyObject *pyo_reg_value = PyTuple_GetItem (pyo_reg, 1); + struct value *value; + size_t data_size; + + if (pyo_reg_value == NULL) + goto error; + + if ((value = value_object_to_value (pyo_reg_value)) == NULL) + error (_("Python sniffer returned bad register tuple: " + "item #%d, register value should have type " + "gdb.Value."), 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; + } + } + } + + { + PyObject *pyo_frame_id_regs = PyTuple_GetItem (pyo_unwind_info, 1); + if (pyo_frame_id_regs == NULL) + goto error; + pyuw_parse_frame_id (cached_frame, pyo_frame_id_regs); + } + + *cache_ptr = cached_frame; + discard_cleanups (cached_frame_cleanups); + do_cleanups (cleanups); + return 1; + +error: + 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 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) +{ + 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; + return gdb_pymodule_addobject (gdb_module, "SnifferInfo", + (PyObject *) &sniffer_info_object_type); +} + +static PyMethodDef sniffer_info_object_methods[] = +{ + { "read_register", sniffer_infopy_read_register, METH_VARARGS, + "read_register (register_name) -> gdb.Value\n\ +Return the value of the register 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 */ +}; 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..866d882 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.py @@ -0,0 +1,74 @@ +# 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. + """ + bp = sniffer_info.read_register( + TestSniffer.AMD64_RBP).cast(self.char_ptr_t) + try: + 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_sp = bp + 16 + return (((TestSniffer.AMD64_RBP, self._read_word(bp - 8)), + (TestSniffer.AMD64_RIP, self._read_word(bp + 8)), + (TestSniffer.AMD64_RSP, bp + 16)), + (TestSniffer.AMD64_RSP, TestSniffer.AMD64_RIP)) + except (gdb.error, RuntimeError): + return None + +gdb.sniffer.register_sniffer(None, TestSniffer(), True) +print("Python script imported")