From patchwork Wed Mar 18 22:57:35 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Smundak X-Patchwork-Id: 5690 Received: (qmail 106342 invoked by alias); 18 Mar 2015 22:57:48 -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 106324 invoked by uid 89); 18 Mar 2015 22:57:46 -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-ig0-f170.google.com Received: from mail-ig0-f170.google.com (HELO mail-ig0-f170.google.com) (209.85.213.170) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES128-GCM-SHA256 encrypted) ESMTPS; Wed, 18 Mar 2015 22:57:39 +0000 Received: by igcqo1 with SMTP id qo1so59007896igc.0 for ; Wed, 18 Mar 2015 15:57:37 -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=0130AglD/pTXRHIZqbiy19yl9rzJuHvRZfE7WCQOamM=; b=VsfBZ2nOyG2wMm4erTkNWgGQ3usZ9VPPOWBRZipJ79BNGcLlMw6Ustk6zAiWkfYrKn M+0Kl9sjNTQes0IVNAL5OpfxYkFTZXC06YLEzbT9rSmY0YKc2loDzzDfpNvAdX179C1i 4iv7wpPe908hwBEh31cNCXeUxmMyGxjWLK5L33jfmmJ8muTA05FKm8Oe0i8u1KosK3ks tgma5CRXsHSZPtHnoXsgcye5ByTTUjfnOO8XJ2EuSOlgVP268iDIY/mx120KHhO06chV QjN9VCSFGTh907x/PFJLpuf3BTK+gc7RUhD1NRHMB5L5IR+loZZRNMJPSOM9PtDcQ8U2 GQYg== X-Gm-Message-State: ALoCoQn9ElmTHwAN1fcEBjvVrMTtGhx7ZQtYlpnHDkkd5rmeALKspx04IANwfNXi9v0dmnrQ/kwb MIME-Version: 1.0 X-Received: by 10.50.143.42 with SMTP id sb10mr11189162igb.49.1426719457085; Wed, 18 Mar 2015 15:57:37 -0700 (PDT) Received: by 10.64.98.103 with HTTP; Wed, 18 Mar 2015 15:57:35 -0700 (PDT) In-Reply-To: <87r3smado6.fsf@igalia.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> <87twxrncld.fsf@igalia.com> <87ioe1dvu2.fsf@igalia.com> <87sid4atms.fsf@igalia.com> <87r3smado6.fsf@igalia.com> Date: Wed, 18 Mar 2015 15:57:35 -0700 Message-ID: Subject: Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python From: Alexander Smundak To: Andy Wingo Cc: Doug Evans , gdb-patches > Nit: we are setting the register here. Fixed. >> + _("When non-zero, Pythin unwinder debugging is enabled."), > > "Python" Fixed. > Still no support for register names. Registers can be retrieved by name, please see gdb/testsuite/gdb.python/py-unwind.py as an example. >> +import gdb >> +from gdb.sniffer import Sniffer >> + >> +class TestSniffer(Sniffer): > > I still think it's much better to call these "unwinders". You say that > it's the terminology that GDB uses but that's not really the case -- > "sniffer" names part of the unwinder interface, which Python and Guile > implement the whole of. You chose "unwinder" as the documentation > heading and the file name; why introduce a new term to the user? Renamed all but SnifferInfo. 2015-03-28 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 unwinders.py. * doc/python.texi (Writing a Frame Unwinder in Python): Add section. * python/lib/gdb/__init__.py (packages): Add frame_unwinders list. * python/lib/gdb/command/unwinders.py: New file, implements GDB commands to list/enable/disable Python unwinders. * python/lib/gdb/function/unwinders.py: New file, implements execute_unwinders function. * python/lib/gdb/unwinder.py: New file, contains Unwinder class and register_unwinder function. * python/py-objfile.c (objfile_object): Add frame_unwinders field. (objfpy_dealloc): Decrement frame_unwinders reference count. (objfpy_initialize): Create frame_unwinders list. (objfpy_get_frame_unwinders): Implement Objfile.frame_unwinders getter. (objfpy_set_frame_unwinders): Implement Objfile.frame_unwinders setter. (objfile_getset): Add frame_unwinders attribute to Objfile. * python/py-progspace.c (pspace_object): Add frame_unwinders field. (pspy_dealloc): Decrement frame_unwinders reference count. (pspy_initialize): Create frame_unwinders list. (pspy_get_frame_unwinders): Implement gdb.Progspace.frame_unwinders getter. (pspy_set_frame_unwinders): Implement gdb.Progspace.frame_unwinders setter. (pspy_getset): Add frame_unwinders attribute to gdb.Progspace. * python/py-unwind.c: New file, implements Python frame unwinders interface. * python/python-internal.h (pspy_get_name_unwinders): New prototype. (objpy_get_frame_unwinders): New prototype. (gdbpy_initialize_unwind): New prototype. * python/python.c (gdbpy_apply_type_printers): Call gdbpy_initialize_unwind. 2015-03-28 Sasha Smundak * gdb.python/py-unwind-maint.c: Test program for py-unwind-maint. * gdb.python/py-unwind-maint.exp: Tests unwinder-related GDB commands. * gdb.python/py-unwind-maint.py: Pythons frame unwinders for the test. * gdb.python/py-unwind.c: Test program for the py-unwind test. * gdb.python/py-unwind.exp: Python frame unwinders test. * gdb.python/py-unwind.py: Python frame unwinder tested by py-unwind test. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index dbace2d..0bd3738 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -398,6 +398,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 @@ -437,6 +438,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 @@ -2622,6 +2624,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 bda4a35..ac994d9 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..049aa05 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/unwinder.py \ gdb/prompt.py \ gdb/xmethod.py \ gdb/command/__init__.py \ gdb/command/xmethods.py \ gdb/command/frame_filters.py \ + gdb/command/unwinders.py \ gdb/command/type_printers.py \ gdb/command/pretty_printers.py \ gdb/command/prompt.py \ gdb/command/explore.py \ gdb/function/__init__.py \ gdb/function/caller_is.py \ + gdb/function/unwinders.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..a2685e8 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -144,6 +144,7 @@ optional arguments while skipping others. Example: * Frame Filter API:: Filtering Frames. * Frame Decorator API:: Decorating Frames. * Writing a Frame Filter:: Writing a Frame Filter. +* Unwinding Frames in Python:: Writing frame unwinder. * Xmethods In Python:: Adding and replacing methods of C++ classes. * Xmethod API:: Xmethod types. * Writing an Xmethod:: Writing an xmethod. @@ -2178,6 +2179,132 @@ 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. An unwinder +has three methods. The first one checks if it can handle given frame +(``sniff'' it). For the frames it can sniff an unwinder provides two +additional methods: it can return frame's ID, and it can fetch +registers from the previous frame. A running 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 frame unwinder in Python as a class with which has two +attributes, @code{name} and @code{enabled}, with obvious meanings, and +a single method @code{__call__}, which examines a given frame and +returns an object (an instance of gdb.UnwindInfo class) describing +it. If an unwinder does not recognize a frame, it should return +@code{None}. The code in GDB that enables writing unwinders in Python +uses this object to return frame's ID and previous frame registers +when GDB core asks for them. + +@subheading Unwinder Input + +An object passed to an unwinder (a @code{SnifferInfo} instance) provides +a method to read frame's registers: + +@defun SnifferInfo.read_register (reg) +This method returns the contents of the register @var{regn} in the +frame as a @code{gdb.Value} object. @var{reg} can be either a register +number or a register name; the values are platform-specific. They are +usually found in the corresponding xxx-@code{tdep.h} file in the gdb +source tree. +@end defun + +It also provides several factory methods. If an unwinder recognizes +the frame, it should invoke one of them to create a gdb.UnwindInfo +instance to be returned to GDB: + +@defun SnifferInfo.unwind_info_with_id(sp, pc) +Returns a new @code{gdb.UnwindInfo} instance identified by given +@var{sp} and @var{pc} values. This is the most common way of creating +instance result. +@end defun + +@defun SnifferInfo.frame_id_build_special(sp, pc, special) +Returns a new @code{gdb.UnwindInfo} instance identitified by given +@var{sp}, @var{pc}, and @var{special} values. +@end defun + +@defun gdb.UnwindInfo.frame_id_build_wild(sp) +Returns a new @code{gdb.UnwindInfo} instance identified by given +@var{sp} value. +@end defun + +@subheading Unwinder Output: UnwindInfo + +A @code{gdb.UnwindInfo} object can be constructed by one of the +methods described above. Use the following method to set the caller +frame's registers: + +@defun gdb.UnwindInfo.set_previous_frame_register(reg, value) +@var{reg} identifies the register. It can be a number or a name, just +as for the @code{SnifferInfo.read_register} method above. @var{value} +is a register value (a @code{gdb.Value} object). + +@subheading Unwinder Skeleton Code + +GDB comes with the module containing the base @code{Unwinder} class. +Derive your unwinder class from it and structure the code as follows: + +@smallexample +from gdb.unwinders import Unwinder + +class MyUnwinder(Unwinder): + def __init__(....): + super(MyUnwinder, self).__init___() + def __call__(sniffer_info): + if not : + return None + # Create unwinder result. The most common way to achieve this is + # to find SP (stack pointer) and PC (program counter) values + # in the current frame and then call unwind_info_with_id method: + unwind_info = sniffer_info.unwind_info_with_id(sp, pc) + + # Find the values of the registers in the caller's frame and + # save them in the result: + unwind_info.set_previous_frame_register(, ) + + # Return the result: + return unwind_instance + +@end smallexample + +@subheading Registering a Unwinder + +An object file, a program space, and the @value{GDBN} proper can have +unwinders registered with it. + +The @code{gdb.unwinders} module provides the function to register a +unwinder: + +@defun gdb.unwinder.register_unwinder (locus, unwinder, replace=False) +@var{locus} is specifies an object file or a program space to which +@var{unwinder} is added. Passing @code{None} or @code{gdb} adds +@var{unwinder} to the @value{GDBN}'s global unwinder list. The newly +added @var{unwinder} will be called before any other unwinder from the +same locus. Two unwinders in the same locus cannot have the same +name. An attempt to add a unwinder with already existing name raises an +exception unless @var{replace} is @code{True}, in which case the old +unwinder is deleted. +@end defun + +@subheading Unwinder Precedence + +@value{GDBN} first calls the unwinders from all the object files in no +particular order, then the unwinders from the current program space, +and finally the unwinders from @value{GDBN}. + @node Xmethods In Python @subsubsection Xmethods In Python @cindex xmethods in Python diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 92b06f2..734e5ca 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 unwinders. +frame_unwinders = [] # Convenience variable to GDB's python directory PYTHONDIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py new file mode 100644 index 0000000..1f0c58b --- /dev/null +++ b/gdb/python/lib/gdb/command/unwinders.py @@ -0,0 +1,199 @@ +# Unwinder commands. +# Copyright 2015 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gdb +import re + + +def validate_regexp(exp, idstring): + try: + return re.compile(exp) + except SyntaxError: + raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp)) + + +def parse_unwinder_command_args(arg): + """Internal utility to parse unwinder command argv. + + Arguments: + arg: The arguments to the command. The format is: + [locus-regexp [name-regexp]] + + Returns: + A 2-tuple of compiled regular expressions. + + Raises: + SyntaxError: an error processing ARG + """ + + argv = gdb.string_to_argv(arg) + argc = len(argv) + if argc > 2: + raise SyntaxError("Too many arguments.") + locus_regexp = "" + name_regexp = "" + if argc >= 1: + locus_regexp = argv[0] + if argc >= 2: + name_regexp = argv[1] + return (validate_regexp(locus_regexp, "locus"), + validate_regexp(name_regexp, "unwinder")) + + +class InfoUnwinder(gdb.Command): + """GDB command to list unwinders. + + Usage: info unwinder [locus-regexp [name-regexp]] + + LOCUS-REGEXP is a regular expression matching the location of the + unwinder. If it is omitted, all registered unwinders from all + loci are listed. A locus 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 unwinders from the current + progspace should be listed. + + NAME-REGEXP is a regular expression to filter unwinder names. If + this omitted for a specified locus, then all registered unwinders + in the locus are listed. + + """ + + def __init__(self): + super(InfoUnwinder, self).__init__("info unwinder", + gdb.COMMAND_DATA) + + def list_unwinders(self, title, unwinders, name_re): + """Lists the unwinders whose name matches regexp. + + Arguments: + title: The line to print before the list. + unwinders: The list of the unwinders. + name_re: unwinder name filter. + """ + if not unwinders: + return + print title + for unwinder in unwinders: + if name_re.match(unwinder.name): + print(" %s%s" % (unwinder.name, + "" if unwinder.enabled else "[disabled]")) + + def invoke(self, arg, from_tty): + locus_re, name_re = parse_unwinder_command_args(arg) + if locus_re.match("global"): + self.list_unwinders("global unwinders:", gdb.frame_unwinders, + name_re) + if locus_re.match("progspace"): + cp = gdb.current_progspace() + self.list_unwinders("progspace %s unwinders:" % cp.filename, + cp.frame_unwinders, name_re) + for objfile in gdb.objfiles(): + if locus_re.match(objfile.filename): + self.list_unwinders("objfile %s unwinders:" % objfile.filename, + objfile.frame_unwinders, name_re) + + +def do_enable_unwinder1(unwinders, name_re, flag): + """Enable/disable unwinders whose names match given regex. + + Arguments: + unwinders: The list of unwinders. + name_re: Unwinder name filter. + flag: Enable/disable. + + Returns: + The number of unwinders affected. + """ + total = 0 + for unwinder in unwinders: + if name_re.match(unwinder.name): + unwinder.enabled = flag + total += 1 + return total + + +def do_enable_unwinder(arg, flag): + """Enable/disable unwinder(s).""" + (locus_re, name_re) = parse_unwinder_command_args(arg) + total = 0 + if locus_re.match("global"): + total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag) + if locus_re.match("progspace"): + total += do_enable_unwinder1(gdb.current_progspace().frame_unwinders, + name_re, flag) + for objfile in gdb.objfiles(): + if locus_re.match(objfile.filename): + total += do_enable_unwinder1(objfile.frame_unwinders, name_re, + flag) + print("%d unwinder%s %s" % (total, "" if total == 1 else "s", + "enabled" if flag else "disabled")) + + +class EnableUnwinder(gdb.Command): + """GDB command to enable unwinders. + + Usage: enable unwinder [locus-regexp [name-regexp]] + + LOCUS-REGEXP is a regular expression 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 unwinder names. + If this omitted for a specified locus, then all registered + unwinders in the locus are affected. + """ + + def __init__(self): + super(EnableUnwinder, self).__init__("enable unwinder", + gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_unwinder(arg, True) + + +class DisableUnwinder(gdb.Command): + """GDB command to disable the specified unwinder. + + Usage: disable unwinder [locus-regexp [name-regexp]] + + LOCUS-REGEXP is a regular expression 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 unwinder names. + If this omitted for a specified locus, then all registered + unwinders in the locus are affected. + """ + + def __init__(self): + super(DisableUnwinder, self).__init__("disable unwinder", + gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + """GDB calls this to perform the command.""" + do_enable_unwinder(arg, False) + + +def register_unwinder_commands(): + """Installs the unwinder commands.""" + InfoUnwinder() + EnableUnwinder() + DisableUnwinder() + + +register_unwinder_commands() diff --git a/gdb/python/lib/gdb/function/unwinders.py b/gdb/python/lib/gdb/function/unwinders.py new file mode 100644 index 0000000..9be8b8b --- /dev/null +++ b/gdb/python/lib/gdb/function/unwinders.py @@ -0,0 +1,52 @@ +# 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 unwinders.""" + +import gdb + + +def execute_unwinders(sniffer_info): + """Internal function called from GDB to execute all unwinders. + + Runs each currently enabled unwinder until it finds the one that can + unwind given frame. + + Arguments: + sniffer_info: an instance of gdb.SnifferInfo. + Returns: + UnwindInfo instance or None. + """ + for objfile in gdb.objfiles(): + for unwinder in objfile.frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder.__call__(sniffer_info) + if unwind_info is not None: + return unwind_info + + current_progspace = gdb.current_progspace() + for unwinder in current_progspace.frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder.__call__(sniffer_info) + if unwind_info is not None: + return unwind_info + + for unwinder in gdb.frame_unwinders: + if unwinder.enabled: + unwind_info = unwinder.__call__(sniffer_info) + if unwind_info is not None: + return unwind_info + + return None diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py new file mode 100644 index 0000000..2d65a5e --- /dev/null +++ b/gdb/python/lib/gdb/unwinder.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 . + +"""Unwinder class and register_unwinder function.""" + +import gdb + + +class Unwinder(object): + """Base class (or a template) for frame unwinders written in Python. + + A unwinder has a single method __call__ and the attributes described below. + + Attributes: + name: The name of the unwinder. + enabled: A boolean indicating whether the unwinder is enabled. + """ + + def __init__(self, name): + """Constructor. + + Args: + name: An identifying name for the unwinder. + """ + self.name = name + self.enabled = True + + def __call__(self, 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("Unwinder __call__.") + + +def register_unwinder(locus, unwinder, replace=False): + """Register unwinder in given locus. + + The unwinder is prepended to the locus's unwinders list. Unwinder + name should be unique. + + Arguments: + locus: Either an objfile, progspace, or None (in which case + the unwinder is registered globally). + unwinder: An object of a gdb.Unwinder subclass + replace: If True, replaces existing unwinder with the same name. + Otherwise, raises exception if unwinder with the same + name already exists. + + Returns: + Nothing. + + Raises: + RuntimeError: Unwinder name is not unique. + + """ + if locus is None: + if gdb.parameter("verbose"): + gdb.write("Registering global %s unwinder ...\n" % unwinder.name) + locus = gdb + else: + if gdb.parameter("verbose"): + gdb.write("Registering %s unwinder for %s ...\n" % + (unwinder.name, locus.filename)) + i = 0 + for needle in locus.frame_unwinders: + if needle.name == unwinder.name: + if replace: + del locus.frame_unwinders[i] + else: + raise RuntimeError("Unwinder %s already exists." % unwinder.name) + i += 1 + locus.frame_unwinders.insert(0, unwinder) diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c index 157d200..c9528c3 100644 --- a/gdb/python/py-objfile.c +++ b/gdb/python/py-objfile.c @@ -42,6 +42,10 @@ typedef struct /* The frame filter list of functions. */ PyObject *frame_filters; + + /* The list of frame unwinders. */ + PyObject *frame_unwinders; + /* The type-printer list. */ PyObject *type_printers; @@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o) Py_XDECREF (self->dict); Py_XDECREF (self->printers); Py_XDECREF (self->frame_filters); + Py_XDECREF (self->frame_unwinders); Py_XDECREF (self->type_printers); Py_XDECREF (self->xmethods); Py_TYPE (self)->tp_free (self); @@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self) if (self->frame_filters == NULL) return 0; + self->frame_unwinders = PyList_New (0); + if (self->frame_unwinders == NULL) + return 0; + self->type_printers = PyList_New (0); if (self->type_printers == NULL) return 0; @@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore) return 0; } +/* Return the frame unwinders attribute for this object file. */ + +PyObject * +objfpy_get_frame_unwinders (PyObject *o, void *ignore) +{ + objfile_object *self = (objfile_object *) o; + + Py_INCREF (self->frame_unwinders); + return self->frame_unwinders; +} + +/* Set this object file's frame unwinders list to UNWINDERS. */ + +static int +objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore) +{ + PyObject *tmp; + objfile_object *self = (objfile_object *) o; + + if (!unwinders) + { + PyErr_SetString (PyExc_TypeError, + _("Cannot delete the frame unwinders attribute.")); + return -1; + } + + if (!PyList_Check (unwinders)) + { + PyErr_SetString (PyExc_TypeError, + _("The frame_unwinders attribute must be a list.")); + return -1; + } + + /* Take care in case the LHS and RHS are related somehow. */ + tmp = self->frame_unwinders; + Py_INCREF (unwinders); + self->frame_unwinders = unwinders; + Py_XDECREF (tmp); + + return 0; +} + /* Get the 'type_printers' attribute. */ static PyObject * @@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] = "Pretty printers.", NULL }, { "frame_filters", objfpy_get_frame_filters, objfpy_set_frame_filters, "Frame Filters.", NULL }, + { "frame_unwinders", objfpy_get_frame_unwinders, + objfpy_set_frame_unwinders, "Frame Unwinders", NULL }, { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers, "Type printers.", NULL }, { "xmethods", objfpy_get_xmethods, NULL, diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c index 93fbc14..17da3d1 100644 --- a/gdb/python/py-progspace.c +++ b/gdb/python/py-progspace.c @@ -41,6 +41,10 @@ typedef struct /* The frame filter list of functions. */ PyObject *frame_filters; + + /* The frame unwinder list. */ + PyObject *frame_unwinders; + /* The type-printer list. */ PyObject *type_printers; @@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self) Py_XDECREF (ps_self->dict); Py_XDECREF (ps_self->printers); Py_XDECREF (ps_self->frame_filters); + Py_XDECREF (ps_self->frame_unwinders); Py_XDECREF (ps_self->type_printers); Py_XDECREF (ps_self->xmethods); Py_TYPE (self)->tp_free (self); @@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self) if (self->frame_filters == NULL) return 0; + self->frame_unwinders = PyList_New (0); + if (self->frame_unwinders == NULL) + return 0; + self->type_printers = PyList_New (0); if (self->type_printers == NULL) return 0; @@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore) return 0; } +/* Return the list of the frame unwinders for this program space. */ + +PyObject * +pspy_get_frame_unwinders (PyObject *o, void *ignore) +{ + pspace_object *self = (pspace_object *) o; + + Py_INCREF (self->frame_unwinders); + return self->frame_unwinders; +} + +/* Set this program space's list of the unwinders to UNWINDERS. */ + +static int +pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore) +{ + PyObject *tmp; + pspace_object *self = (pspace_object *) o; + + if (!unwinders) + { + PyErr_SetString (PyExc_TypeError, + "cannot delete the frame unwinders list"); + return -1; + } + + if (!PyList_Check (unwinders)) + { + PyErr_SetString (PyExc_TypeError, + "the frame unwinders attribute must be a list"); + return -1; + } + + /* Take care in case the LHS and RHS are related somehow. */ + tmp = self->frame_unwinders; + Py_INCREF (unwinders); + self->frame_unwinders = unwinders; + Py_XDECREF (tmp); + + return 0; +} + /* Get the 'type_printers' attribute. */ static PyObject * @@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] = "Pretty printers.", NULL }, { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters, "Frame filters.", NULL }, + { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders, + "Frame unwinders.", NULL }, { "type_printers", pspy_get_type_printers, pspy_set_type_printers, "Type printers.", NULL }, { "xmethods", pspy_get_xmethods, NULL, diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c new file mode 100644 index 0000000..128d710 --- /dev/null +++ b/gdb/python/py-unwind.c @@ -0,0 +1,831 @@ +/* Python frame unwinder interface. + + Copyright (C) 2015 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include "defs.h" +#include "arch-utils.h" +#include "frame-unwind.h" +#include "gdb_obstack.h" +#include "gdbcmd.h" +#include "language.h" +#include "observer.h" +#include "python-internal.h" +#include "regcache.h" +#include "valprint.h" +#include "user-regs.h" + +#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level) \ + { fprintf_unfiltered (gdb_stdlog, args); } + +typedef struct +{ + PyObject_HEAD + + /* Frame we are unwinding. */ + struct frame_info *frame_info; + + /* Its architecture, passed by the sniffer caller. */ + struct gdbarch *gdbarch; +} 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; + + /* Its ID. */ + struct frame_id frame_id; + + /* Previous frame registers array. */ + struct reg_pydata + { + int number; + PyObject *value; + } *prev_frame_regs; + + /* The current size of the array above. */ + int prev_frame_regs_size; + + /* And its capacity. */ + int prev_frame_regs_capacity; + +} 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 register id, which can be either a number or a name. + Returns 1 on success, 0 otherwise. */ + +static int +pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id, + int *reg_num) +{ + if (pyo_reg_id == NULL) + return 0; + if (PyString_Check (pyo_reg_id)) + { + const char *reg_name = PyString_AS_STRING (pyo_reg_id); + if (reg_name == NULL) + return 0; + *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name, + strlen (reg_name)); + return *reg_num >= 0; + } + else if (pyuw_parse_int (pyo_reg_id, reg_num)) + return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL; + else + return 0; +} + +/* 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; +} + +/* Called by the Python interpreter to obtain string representation + of the UnwindInfo object. */ + +static PyObject * +unwind_infopy_str (PyObject *self) +{ + PyObject *result; + struct ui_file *strfile = mem_fileopen (); + unwind_info_object *unwind_info = (unwind_info_object *) self; + sniffer_info_object *sniffer_info + = (sniffer_info_object *) (unwind_info->sniffer_info); + + fprintf_unfiltered (strfile, "Frame ID: "); + fprint_frame_id (strfile, unwind_info->frame_id); + { + int i; + char *sep = ""; + struct value_print_options opts; + + get_user_print_options (&opts); + fprintf_unfiltered (strfile, "\nPrevious frame registers: ("); + for (i = 0; i < unwind_info->prev_frame_regs_size; i++) + { + struct value *value + = value_object_to_value (unwind_info->prev_frame_regs[i].value); + + fprintf_unfiltered (strfile, "%s(%d, ", sep, + unwind_info->prev_frame_regs[i].number); + if (value != NULL) + { + value_print (value, strfile, &opts); + fprintf_unfiltered (strfile, ")"); + } + else + fprintf_unfiltered (strfile, ")"); + sep = ", "; + } + fprintf_unfiltered (strfile, ")"); + } + { + char *s = ui_file_xstrdup (strfile, NULL); + + result = PyString_FromString (s); + xfree (s); + } + ui_file_delete (strfile); + return result; +} + +/* Create UnwindInfo instance for given SnifferInfo and frame ID. */ + +static PyObject * +pyuw_create_unwind_info (PyObject *pyo_sniffer_info, + struct frame_id frame_id) +{ + unwind_info_object *unwind_info + = PyObject_New (unwind_info_object, &unwind_info_object_type); + + if (((sniffer_info_object *) pyo_sniffer_info)->frame_info == NULL) + { + PyErr_SetString (PyExc_ValueError, + "Attempting to use stale SnifferInfo"); + return NULL; + } + unwind_info->frame_id = frame_id; + Py_INCREF (pyo_sniffer_info); + unwind_info->sniffer_info = pyo_sniffer_info; + unwind_info->prev_frame_regs_size = 0; + unwind_info->prev_frame_regs_capacity = 4; + unwind_info->prev_frame_regs = + xmalloc (unwind_info->prev_frame_regs_capacity * + sizeof (unwind_info->prev_frame_regs[0])); + return (PyObject *) unwind_info; +} + +/* The implementation of + gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None. */ + +static PyObject * +unwind_infopy_set_previous_frame_register (PyObject *self, PyObject *args) +{ + unwind_info_object *unwind_info = (unwind_info_object *) self; + sniffer_info_object *sniffer_info + = (sniffer_info_object *) (unwind_info->sniffer_info); + PyObject *pyo_reg_id; + PyObject *pyo_reg_value; + int regnum; + + if (sniffer_info->frame_info == NULL) + { + PyErr_SetString (PyExc_ValueError, + "UnwindInfo instance refers to a stale SnifferInfo"); + return NULL; + } + if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2, + &pyo_reg_id, &pyo_reg_value)) + return NULL; + if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, ®num)) + { + PyErr_SetString (PyExc_ValueError, "Bad register"); + return NULL; + } + { + struct value *value; + size_t data_size; + + if (pyo_reg_value == NULL + || (value = value_object_to_value (pyo_reg_value)) == NULL) + { + PyErr_SetString (PyExc_ValueError, "Bad register value"); + return NULL; + } + data_size = register_size (sniffer_info->gdbarch, regnum); + if (data_size != TYPE_LENGTH (value_enclosing_type (value))) + { + PyErr_Format ( + PyExc_ValueError, + "The value of the register returned by the Python " + "sniffer has unexpected size: %u instead of %u.", + (unsigned) (TYPE_LENGTH (value_enclosing_type (value))), + (unsigned) data_size); + return NULL; + } + } + { + int i; + + for (i = 0; + (i < unwind_info->prev_frame_regs_size) + && regnum != unwind_info->prev_frame_regs[i].number; i++) + ; + if (i < unwind_info->prev_frame_regs_size) + Py_DECREF (unwind_info->prev_frame_regs[i].value); + else + { + if (i >= unwind_info->prev_frame_regs_capacity) + { + unwind_info->prev_frame_regs_capacity *= 2; + unwind_info->prev_frame_regs = xrealloc + (unwind_info->prev_frame_regs, + unwind_info->prev_frame_regs_capacity + * sizeof (unwind_info->prev_frame_regs[0])); + } + unwind_info->prev_frame_regs_size++; + unwind_info->prev_frame_regs[i].number = regnum; + } + Py_INCREF (pyo_reg_value); + unwind_info->prev_frame_regs[i].value = pyo_reg_value; + } + Py_INCREF (Py_None); + return Py_None; +} + +/* UnwindInfo cleanup. */ + +static void +unwind_infopy_dealloc (PyObject *self) +{ + unwind_info_object *unwind_info = (unwind_info_object *) self; + int i; + + Py_XDECREF (unwind_info->sniffer_info); + for (i = 0; i < unwind_info->prev_frame_regs_size; i++) + Py_DECREF (unwind_info->prev_frame_regs[i].value); + xfree (unwind_info->prev_frame_regs); + Py_TYPE (self)->tp_free (self); +} + +/* Called by the Python interpreter to obtain string representation + of the SnifferInfo object. */ + +static PyObject * +sniffer_infopy_str (PyObject *self) +{ + struct frame_info *frame = ((sniffer_info_object *) self)->frame_info; + + if (frame == NULL) + return PyString_FromString ("Stale SnifferInfo instance"); + return PyString_FromFormat ("SP=%s,PC=%s", + core_addr_to_string_nz (get_frame_sp (frame)), + core_addr_to_string_nz (get_frame_pc (frame))); +} + +/* 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; + sniffer_info_object *sniffer_info = (sniffer_info_object *) self; + PyObject *pyo_reg_id; + + if (sniffer_info->frame_info == NULL) + { + PyErr_SetString (PyExc_ValueError, + "Attempting to read register from stale SnifferInfo"); + return NULL; + } + + if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id)) + return NULL; + if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, ®num)) + { + PyErr_SetString (PyExc_ValueError, "Bad register"); + return NULL; + } + TRY + { + gdb_byte buffer[MAX_REGISTER_SIZE]; + + val = get_frame_register_value ( + ((sniffer_info_object *) self)->frame_info, regnum); + if (val == NULL) + PyErr_Format (PyExc_ValueError, + "Cannot read register %d from frame.", + regnum); + } + CATCH (except, RETURN_MASK_ALL) + { + GDB_PY_HANDLE_EXCEPTION (except); + } + END_CATCH + + return val == NULL ? NULL : value_to_value_object (val); +} + +/* Implementation of + gdb.SnifferInfo.unwind_info_with_id (self, SP, PC) -> None. */ + +static PyObject * +sniffer_infopy_unwind_info_with_id (PyObject *self, PyObject *args) +{ + PyObject *pyo_sp; + PyObject *pyo_pc; + CORE_ADDR sp; + CORE_ADDR pc; + + if (!PyArg_ParseTuple (args, "OO:unwind_info_with_id", &pyo_sp, &pyo_pc) + || !pyuw_value_obj_to_pointer (pyo_sp, &sp) + || !pyuw_value_obj_to_pointer (pyo_pc, &pc)) + return NULL; + + return pyuw_create_unwind_info (self, frame_id_build (sp, pc)); +} + +/* Implementation of + gdb.SnifferInfo.unwind_info_with_id_special (self, SP, PC, SPECIAL) -> None. */ + +static PyObject * +sniffer_infopy_unwind_info_with_id_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:unwind_info_with_id_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; + + return pyuw_create_unwind_info (self, + frame_id_build_special (sp, pc, special)); +} + +/* Implementation of + gdb.SnifferInfo.unwind_info_with_id_wild (self, SP) -> None. */ + +static PyObject * +sniffer_infopy_unwind_info_with_id_wild (PyObject *self, PyObject *args) +{ + PyObject *pyo_sp; + CORE_ADDR sp; + + if (!PyArg_ParseTuple (args, "O:unwind_info_with_id_wild", &pyo_sp) + || !pyuw_value_obj_to_pointer (pyo_sp, &sp)) + return NULL; + + return pyuw_create_unwind_info (self, frame_id_build_wild (sp)); +} + +/* Create Python SnifferInfo object. */ + +static PyObject * +frame_info_to_sniffer_info_object (struct gdbarch *gdbarch, + struct frame_info *frame) +{ + sniffer_info_object *sniffer_info + = PyObject_New (sniffer_info_object, &sniffer_info_object_type); + + sniffer_info->gdbarch = gdbarch; + 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); +} + +/* 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 (gdbarch, this_frame); + if (pyo_sniffer_info == NULL) + goto error; + make_cleanup_py_decref (pyo_sniffer_info); + + if ((pyo_module = PyImport_ImportModule ("gdb.function.unwinders")) == NULL) + goto error; + make_cleanup_py_decref (pyo_module); + + pyo_execute = PyObject_GetAttrString (pyo_module, "execute_unwinders"); + 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 Unwinder should return gdb.UnwindInfo instance.")); + + { + unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info; + int i; + int reg_count; + size_t cached_frame_size; + size_t gdb_bytes_count; + gdb_byte *gdb_data_free, *gdb_data_end; + + /* Figure out how much space we need to allocate. */ + reg_count = unwind_info->prev_frame_regs_size; + 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 + = value_object_to_value (unwind_info->prev_frame_regs[i].value); + size_t data_size; + + reg->number = unwind_info->prev_frame_regs[i].number; + /* `value' validation was done before, just assert. */ + gdb_assert (value != NULL); + data_size = register_size (gdbarch, reg->number); + gdb_assert (data_size == TYPE_LENGTH (value_enclosing_type (value))); + /* 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 unwinders + intermediary. */ + +static void +pyuw_on_new_gdbarch (struct gdbarch *newarch) +{ + struct pyuw_gdbarch_data_type *data = + gdbarch_data (newarch, pyuw_gdbarch_data); + + if (!data->unwinder_registered) + { + struct frame_unwind *unwinder + = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind); + + unwinder->type = NORMAL_FRAME; + unwinder->stop_reason = default_frame_unwind_stop_reason; + unwinder->this_id = pyuw_this_id; + unwinder->prev_register = pyuw_prev_register; + unwinder->unwind_data = (void *) newarch; + unwinder->sniffer = pyuw_sniffer; + unwinder->dealloc_cache = pyuw_dealloc_cache; + frame_unwind_prepend_unwinder (newarch, unwinder); + 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, Python unwinder debugging is enabled."), + NULL, + NULL, + &setdebuglist, &showdebuglist); + pyuw_gdbarch_data + = gdbarch_data_register_post_init (pyuw_gdbarch_data_init); + observer_attach_architecture_changed (pyuw_on_new_gdbarch); + + if (PyType_Ready (&sniffer_info_object_type) < 0) + return -1; + rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo", + (PyObject *) &sniffer_info_object_type); + if (rc) + return rc; + + if (PyType_Ready (&unwind_info_object_type) < 0) + return -1; + return gdb_pymodule_addobject (gdb_module, "UnwindInfo", + (PyObject *) &unwind_info_object_type); +} + +static PyMethodDef sniffer_info_object_methods[] = +{ + { "read_register", sniffer_infopy_read_register, METH_VARARGS, + "read_register (REG) -> gdb.Value\n" + "Return the value of the REG in the frame." }, + { "unwind_info_with_id", + sniffer_infopy_unwind_info_with_id, METH_VARARGS, + "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n" + "Construct UnwindInfo for this FrameData, using given SP and PC registers \n" + "to identify the frame." }, + { "unwind_info_with_id_special", + sniffer_infopy_unwind_info_with_id_special, METH_VARARGS, + "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n" + "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL " + "registers to identify the frame." }, + { "unwind_info_with_id_wild", + sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS, + "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n" + "Construct UnwindInfo for this FrameData, using given SP register to \n" + "identify 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[] = +{ + { "set_previous_frame_register", + unwind_infopy_set_previous_frame_register, METH_VARARGS, + "set_previous_frame_register (REG, VALUE) -> None\n" + "Set the value of the REG in the previous frame to VALUE." }, + { NULL } /* Sentinel */ +}; + +static PyTypeObject unwind_info_object_type = +{ + PyVarObject_HEAD_INIT (NULL, 0) + "gdb.UnwindInfo", /* tp_name */ + sizeof (unwind_info_object), /* tp_basicsize */ + 0, /* tp_itemsize */ + unwind_infopy_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + unwind_infopy_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + "GDB UnwindInfo object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + unwind_info_object_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ +}; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 4c4d32a..0581b33 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -391,12 +391,14 @@ PyObject *pspace_to_pspace_object (struct program_space *) CPYCHECKER_RETURNS_BORROWED_REF; PyObject *pspy_get_printers (PyObject *, void *); PyObject *pspy_get_frame_filters (PyObject *, void *); +PyObject *pspy_get_frame_unwinders (PyObject *, void *); PyObject *pspy_get_xmethods (PyObject *, void *); PyObject *objfile_to_objfile_object (struct objfile *) CPYCHECKER_RETURNS_BORROWED_REF; PyObject *objfpy_get_printers (PyObject *, void *); PyObject *objfpy_get_frame_filters (PyObject *, void *); +PyObject *objfpy_get_frame_unwinders (PyObject *, void *); PyObject *objfpy_get_xmethods (PyObject *, void *); PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw); @@ -491,6 +493,8 @@ int gdbpy_initialize_arch (void) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; int gdbpy_initialize_xmethods (void) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; +int gdbpy_initialize_unwind (void) + CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; struct cleanup *make_cleanup_py_decref (PyObject *py); struct cleanup *make_cleanup_py_xdecref (PyObject *py); diff --git a/gdb/python/python.c b/gdb/python/python.c index 58c7c92..1da63fd 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1821,7 +1821,8 @@ message == an error message without a stack will be printed."), || gdbpy_initialize_new_objfile_event () < 0 || gdbpy_initialize_clear_objfiles_event () < 0 || gdbpy_initialize_arch () < 0 - || gdbpy_initialize_xmethods () < 0) + || gdbpy_initialize_xmethods () < 0 + || gdbpy_initialize_unwind () < 0) goto fail; gdbpy_to_string_cst = PyString_FromString ("to_string"); diff --git a/gdb/testsuite/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..df2168b --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp @@ -0,0 +1,64 @@ +# Copyright (C) 2015 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This file is part of the GDB testsuite. It tests Python-based +# unwinding CLI. + +load_lib gdb-python.exp + +standard_testfile + +if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } { + return -1 +} + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + +if ![runto_main ] then { + fail "Can't run to main" + return -1 +} + +gdb_test "source ${pyfile}" "Python script imported" "import python scripts" + +gdb_test_sequence "info unwinder" "Show all unwinders" { + "global unwinders:" + " global_unwinder" + "progspace.*unwinders:" + "py_unwind_maint_ps_unwinder" +} + +gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"] + +gdb_test_sequence "continue" "Unwinders called" { + "py_unwind_maint_ps_unwinder called" + "global_unwinder called" +} + +gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled" + +gdb_test_sequence "info unwinder" "Show with global unwinder disabled" { + "global unwinders:" + " global_unwinder\\[disabled\\]" + "progspace.*unwinders:" + " py_unwind_maint_ps_unwinder" +} + +gdb_test_sequence "where" "Global unwinder disabled" { + "py_unwind_maint_ps_unwinder called\r\n#0 main" +} diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py new file mode 100644 index 0000000..35d5313 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind-maint.py @@ -0,0 +1,59 @@ +# Copyright (C) 2015 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This file is part of the GDB testsuite. It tests python unwinders. + +import re +import gdb.types +from gdb.unwinder import Unwinder, register_unwinder + +class TestGlobalUnwinder(Unwinder): + def __init__(self): + super(TestGlobalUnwinder, self).__init__("global_unwinder") + + def __call__(self, unwinder_info): + print "%s called" % self.name + return None + +class TestProgspaceUnwinder(Unwinder): + def __init__(self, name): + super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name) + + def __call__(self, unwinder_info): + print "%s called" % self.name + return None + +class TestObjfileUnwinder(Unwinder): + def __init__(self, name): + super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name) + + def __call__(self, unwinder_info): + print "%s called" % self.name + return None + + + +gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder()) +saw_runtime_error = False +try: + gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder(), replace=False) +except RuntimeError: + saw_runtime_error = True +if not saw_runtime_error: + raise RuntimeError("Missing runtime error from register_unwinder.") +gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder(), replace=True) +gdb.unwinder.register_unwinder(gdb.current_progspace(), + TestProgspaceUnwinder("py_unwind_maint")) +print "Python script imported" diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c new file mode 100644 index 0000000..cf41d78 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.c @@ -0,0 +1,81 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2015 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* 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..53d6746 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.exp @@ -0,0 +1,54 @@ +# Copyright (C) 2015 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This file is part of the GDB testsuite. It verifies that frame +# unwinders can be implemented in Python. + +load_lib gdb-python.exp + +standard_testfile + +if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } { + return -1 +} + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +# This test runs on a specific platform. +if { ! [istarget x86_64-*]} { continue } + +# The following tests require execution. + +if ![runto_main] then { + fail "Can't run to main" + return 0 +} + +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + +gdb_breakpoint [gdb_get_line_number "break backtrace-broken"] + +gdb_test "source ${pyfile}" "Python script imported" \ + "import python scripts" + +gdb_continue_to_breakpoint "break backtrace-broken" +gdb_test_sequence "where" "Backtrace restored by unwinder" { + "\\r\\n#0 .* corrupt_frame_inner \\(\\) at " + "\\r\\n#1 .* corrupt_frame_outer \\(\\) at " + "\\r\\n#2 .* main \\(.*\\) at" +} + + diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py new file mode 100644 index 0000000..8770578 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.py @@ -0,0 +1,83 @@ +# Copyright (C) 2015 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gdb +from gdb.unwinder import Unwinder + +class TestUnwinder(Unwinder): + AMD64_RBP = 6 + AMD64_RSP = 7 + AMD64_RIP = 16 + + def __init__(self): + Unwinder.__init__(self, "test unwinder") + self.char_ptr_t = gdb.lookup_type("unsigned char").pointer() + self.char_ptr_ptr_t = self.char_ptr_t.pointer() + + def _read_word(self, address): + return address.cast(self.char_ptr_ptr_t).dereference() + + def __call__(self, sniffer_info): + """Test unwinder written in Python. + + This unwinder can unwind the frames that have been deliberately + corrupted in a specific way (functions in the accompanying + py-unwind.c file do that.) + This code is only on AMD64. + On AMD64 $RBP points to the innermost frame (unless the code + was compiled with -fomit-frame-pointer), which contains the + address of the previous frame at offset 0. The functions + deliberately corrupt their frames as follows: + Before After + Corruption: Corruption: + +--------------+ +--------------+ + RBP-8 | | | Previous RBP | + +--------------+ +--------------+ + RBP + Previous RBP | | RBP | + +--------------+ +--------------+ + RBP+8 | Return RIP | | Return RIP | + +--------------+ +--------------+ + Old SP | | | | + + This unwinder recognizes the corrupt frames by checking that + *RBP == RBP, and restores previous RBP from the word above it. + """ + try: + # NOTE: the registers in Unwinder API can be referenced + # either by name or by number. The code below uses both + # to achieve more coverage. + bp = sniffer_info.read_register("rbp").cast(self.char_ptr_t) + if self._read_word(bp) != bp: + return None + # Found the frame that the test program has corrupted for us. + # The correct BP for the outer frame has been saved one word + # above, previous IP and SP are at the expected places. + previous_bp = self._read_word(bp - 8) + previous_ip = self._read_word(bp + 8) + previous_sp = bp + 16 + + sp = sniffer_info.read_register(TestUnwinder.AMD64_RSP) + ip = sniffer_info.read_register(TestUnwinder.AMD64_RIP) + unwind_info = sniffer_info.unwind_info_with_id(sp, ip) + unwind_info.set_previous_frame_register(TestUnwinder.AMD64_RBP, + previous_bp) + unwind_info.set_previous_frame_register("rip", previous_ip) + unwind_info.set_previous_frame_register("rsp", previous_sp) + return unwind_info + except (gdb.error, RuntimeError): + return None + +gdb.unwinder.register_unwinder(None, TestUnwinder(), True) +print("Python script imported")