diff mbox

[v4] Add Guile frame unwinder interface

Message ID 87bnjtc4o4.fsf@igalia.com
State New
Headers show

Commit Message

Andy Wingo March 16, 2015, 4:01 p.m. UTC
Voici a new version of the Guile frame unwinder patch.  Changes:

  * Separate ephemeral frame object info ephemeral frame + unwind info
    objects; see:

      http://article.gmane.org/gmane.comp.gdb.patches/105759

  * Update management of unwinders as in newest frame filter patch
    (http://article.gmane.org/gmane.comp.gdb.patches/105616)

     - Specify #:scope when registering, not when creating unwinders

     - s/add-frame-unwinder!/register-frame-unwinder!/

     - s/remove-frame-unwinder!/unregister-frame-unwinder!/

     - Add s/set-frame-unwinder-enabled!/

  * Adapt for new TRY/CATCH/END_CATCH

Regards,

Andy
From 55dae8b56541ef51a03676ce48ea6c2cdeb25f29 Mon Sep 17 00:00:00 2001
From: Andy Wingo <wingo@igalia.com>
Date: Thu, 5 Mar 2015 16:40:20 +0100
Subject: [PATCH] Add Guile frame unwinder interface

gdb/doc/ChangeLog:

	* guile.texi (Guile Frame Unwinder API): New section.

gdb/ChangeLog:

	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
	is no selected frame and no block is selected; instead, fall back
	to the current frame.
	* guile/scm-frame-unwinder.c: New file.
	* guile/lib/gdb/frame-unwinders.scm: New file.
	* guile/guile.c (initialize_gdb_module): Call
	gdbscm_initialize_frame_unwinders.
	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
	declaration.
	* frame.c (get_prev_frame): Detect recursive unwinds, returning
	NULL in that case.
	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
	(frame_unwind_is_unwinding): New declaration.
	* frame-unwind.c (is_unwinding): New file-local variable.
	(set_is_unwinding, unset_is_unwinding): New file-local helpers.
	(frame_unwind_is_unwinding): New exported predicate.
	(frame_unwind_try_handler): Arrange for
	frame_unwind_is_unwinding to return true when unwinding the
	innermost frame.
	(frame_unwind_got_bytes): Make buf arg const.
	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
	frame-unwinders.scm.
	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
	(scm-frame-unwinder.o): New target.

gdb/testsuite/ChangeLog:

	* gdb.guile/scm-frame-unwinder.exp:
	* gdb.guile/scm-frame-unwinder.c:
	* gdb.guile/scm-frame-unwinder-gdb.scm.in:
	* gdb.guile/scm-frame-unwinder.scm: Add unwinder tests.
---
 gdb/ChangeLog                                      |  29 +
 gdb/Makefile.in                                    |   6 +
 gdb/data-directory/Makefile.in                     |   2 +
 gdb/doc/ChangeLog                                  |   4 +
 gdb/doc/guile.texi                                 | 205 +++++++
 gdb/frame-unwind.c                                 |  38 +-
 gdb/frame-unwind.h                                 |   7 +-
 gdb/frame.c                                        |  16 +
 gdb/guile/guile-internal.h                         |   1 +
 gdb/guile/guile.c                                  |   1 +
 gdb/guile/lib/gdb/frame-unwinders.scm              | 213 ++++++++
 gdb/guile/scm-frame-unwinder.c                     | 593 +++++++++++++++++++++
 gdb/guile/scm-symbol.c                             |   4 +-
 gdb/testsuite/ChangeLog                            |   7 +
 .../gdb.guile/scm-frame-unwinder-gdb.scm.in        |  32 ++
 gdb/testsuite/gdb.guile/scm-frame-unwinder.c       |  30 ++
 gdb/testsuite/gdb.guile/scm-frame-unwinder.exp     |  84 +++
 gdb/testsuite/gdb.guile/scm-frame-unwinder.scm     |  42 ++
 18 files changed, 1311 insertions(+), 3 deletions(-)
 create mode 100644 gdb/guile/lib/gdb/frame-unwinders.scm
 create mode 100644 gdb/guile/scm-frame-unwinder.c
 create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
 create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder.c
 create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
 create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder.scm

Comments

Doug Evans March 18, 2015, 12:45 a.m. UTC | #1
Andy Wingo writes:
 > Voici a new version of the Guile frame unwinder patch.  Changes:
 > 
 >   * Separate ephemeral frame object info ephemeral frame + unwind info
 >     objects; see:
 > 
 >       http://article.gmane.org/gmane.comp.gdb.patches/105759
 > 
 >   * Update management of unwinders as in newest frame filter patch
 >     (http://article.gmane.org/gmane.comp.gdb.patches/105616)
 > 
 >      - Specify #:scope when registering, not when creating unwinders
 > 
 >      - s/add-frame-unwinder!/register-frame-unwinder!/
 > 
 >      - s/remove-frame-unwinder!/unregister-frame-unwinder!/
 > 
 >      - Add s/set-frame-unwinder-enabled!/
 > 
 >   * Adapt for new TRY/CATCH/END_CATCH
 > 
 > Regards,
 > 
 > Andy
 > 
 > >From 55dae8b56541ef51a03676ce48ea6c2cdeb25f29 Mon Sep 17 00:00:00 2001
 > From: Andy Wingo <wingo@igalia.com>
 > Date: Thu, 5 Mar 2015 16:40:20 +0100
 > Subject: [PATCH] Add Guile frame unwinder interface
 > 
 > gdb/doc/ChangeLog:
 > 
 > 	* guile.texi (Guile Frame Unwinder API): New section.
 > 
 > gdb/ChangeLog:
 > 
 > 	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
 > 	is no selected frame and no block is selected; instead, fall back
 > 	to the current frame.
 > 	* guile/scm-frame-unwinder.c: New file.
 > 	* guile/lib/gdb/frame-unwinders.scm: New file.
 > 	* guile/guile.c (initialize_gdb_module): Call
 > 	gdbscm_initialize_frame_unwinders.
 > 	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
 > 	declaration.
 > 	* frame.c (get_prev_frame): Detect recursive unwinds, returning
 > 	NULL in that case.
 > 	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
 > 	(frame_unwind_is_unwinding): New declaration.
 > 	* frame-unwind.c (is_unwinding): New file-local variable.
 > 	(set_is_unwinding, unset_is_unwinding): New file-local helpers.
 > 	(frame_unwind_is_unwinding): New exported predicate.
 > 	(frame_unwind_try_handler): Arrange for
 > 	frame_unwind_is_unwinding to return true when unwinding the
 > 	innermost frame.
 > 	(frame_unwind_got_bytes): Make buf arg const.
 > 	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
 > 	frame-unwinders.scm.
 > 	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
 > 	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
 > 	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
 > 	(scm-frame-unwinder.o): New target.
 > 
 > gdb/testsuite/ChangeLog:
 > 
 > 	* gdb.guile/scm-frame-unwinder.exp:
 > 	* gdb.guile/scm-frame-unwinder.c:
 > 	* gdb.guile/scm-frame-unwinder-gdb.scm.in:
 > 	* gdb.guile/scm-frame-unwinder.scm: Add unwinder tests.
 > ...
 > 
 > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
 > index fed8035..08e08db 100644
 > --- a/gdb/Makefile.in
 > +++ b/gdb/Makefile.in
 > @@ -315,6 +315,7 @@ SUBDIR_GUILE_OBS = \
 >  	scm-exception.o \
 >  	scm-frame.o \
 >  	scm-frame-filter.o \
 > +	scm-frame-unwinder.o \
 >  	scm-gsmob.o \
 >  	scm-iterator.o \
 >  	scm-lazy-string.o \
 > @@ -342,6 +343,7 @@ SUBDIR_GUILE_SRCS = \
 >  	guile/scm-exception.c \
 >  	guile/scm-frame.c \
 >  	guile/scm-frame-filter.c \
 > +	guile/scm-frame-unwinder.c \
 >  	guile/scm-gsmob.c \
 >  	guile/scm-iterator.c \
 >  	guile/scm-lazy-string.c \
 > @@ -2416,6 +2418,10 @@ scm-frame-filter.o: $(srcdir)/guile/scm-frame-filter.c
 >  	$(COMPILE) $(srcdir)/guile/scm-frame-filter.c
 >  	$(POSTCOMPILE)
 >  
 > +scm-frame-unwinder.o: $(srcdir)/guile/scm-frame-unwinder.c
 > +	$(COMPILE) $(srcdir)/guile/scm-frame-unwinder.c
 > +	$(POSTCOMPILE)
 > +
 >  scm-gsmob.o: $(srcdir)/guile/scm-gsmob.c
 >  	$(COMPILE) $(srcdir)/guile/scm-gsmob.c
 >  	$(POSTCOMPILE)
 > diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
 > index b4916b0..14d3653 100644
 > --- a/gdb/data-directory/Makefile.in
 > +++ b/gdb/data-directory/Makefile.in
 > @@ -88,6 +88,7 @@ GUILE_SOURCE_FILES = \
 >  	gdb/boot.scm \
 >  	gdb/experimental.scm \
 >  	gdb/frame-filters.scm \
 > +	gdb/frame-unwinders.scm \
 >  	gdb/init.scm \
 >  	gdb/iterator.scm \
 >  	gdb/printing.scm \
 > @@ -100,6 +101,7 @@ GUILE_NO_UNBOUND_WARNING_COMPILED_FILES = \
 >  GUILE_COMPILED_FILES = \
 >  	gdb/experimental.go \
 >  	gdb/frame-filters.go \
 > +	gdb/frame-unwinders.go \
 >  	gdb/iterator.go \
 >  	gdb/printing.go \
 >  	gdb/support.go \
 > diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
 > index 162ace7..3be1926 100644
 > --- a/gdb/doc/ChangeLog
 > +++ b/gdb/doc/ChangeLog
 > @@ -1,3 +1,7 @@
 > +2015-03-06  Andy Wingo  <wingo@igalia.com>
 > +
 > +	* guile.texi (Guile Frame Unwinder API): New section.
 > +
 >  2015-02-15  Andy Wingo  <wingo@igalia.com>
 >  
 >  	* guile.texi (Guile Frame Filter API)
 > diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
 > index 3045d90..fdcbca3 100644
 > --- a/gdb/doc/guile.texi
 > +++ b/gdb/doc/guile.texi
 > @@ -143,6 +143,7 @@ from the Guile interactive prompt.
 >  * Writing a Guile Pretty-Printer:: Writing a pretty-printer
 >  * Guile Frame Filter API::   Filtering frames.
 >  * Writing a Frame Filter in Guile:: Writing a frame filter.
 > +* Guile Frame Unwinder API:: Programmatically unwinding stack frames
 >  * Commands In Guile::        Implementing new commands in Guile
 >  * Parameters In Guile::      Adding new @value{GDBN} parameters
 >  * Progspaces In Guile::      Program spaces
 > @@ -2137,6 +2138,210 @@ also possible to do the job of an decorator with a filter.  Still,
 >  avoiding the stream interfaces can often be a good reason to use the
 >  simpler decorator layer.
 >  
 > +@node Guile Frame Unwinder API
 > +@subsubsection Unwinding Frames in Guile
 > +@cindex frame unwinder api, guile
 > +
 > +In @value{GDBN} terminology, ``unwinding'' is the process of finding
 > +an older (outer) frame on the stack.  Unwinders form the core of
 > +backtrace computation in @value{GDBN}.  @value{GDBN} comes with
 > +unwinders for each target architecture that it supports, and these
 > +usually suffice to unwind the stack.  However, some target programs
 > +can have non-standard frame layouts that cannot be unwound by the
 > +standard unwinders.  This is often the case when working with
 > +just-in-time compilation environments, for example in JavaScript
 > +implementations.  In such cases, users can define custom code in Guile
 > +to programmatically unwind the problematic stack frames.
 > +
 > +Before getting into the API, we should discuss how unwinders work in
 > +@value{GDBN}.
 > +
 > +As an example, consider a stack in which we have already computed
 > +frame 0 and we want to compute frame 1.  We say that frame 0 is the
 > +``inner'' frame, and frame 1 will be the ``outer'' frame.
 > +
 > +Unwinding starts with a model of the state of all registers in an
 > +inner, already unwound frame.  In our case, we start with frame 0.
 > +@value{GDBN} then constructs a ephemeral frame object for the outer
 > +frame that is being built (frame 1) and links it to the inner frame
 > +(frame 0).  @value{GDBN} then goes through its list of registered
 > +unwinders, searching for one that knows how to unwind the frame.  When
 > +it finds one, @value{GDBN} will ask the unwinder to compute a frame
 > +identifier for the outer frame.  Once the unwinder has done so, the
 > +frame is marked as ``valid'' and can be accessed using the normal
 > +frame API.
 > +
 > +A frame identifier (frame ID) consists of code and data pointers
 > +associated with a frame which will remain valid as long as the frame
 > +is still alive.  Usually a frame ID is a pair of the code and stack
 > +pointers as they were when control entered the function associated
 > +with the frame, though as described below there are other ways to
 > +build a frame ID@.  However as you can see, computing the frame ID
 > +requires some input from the unwinder to determine the start code
 > +address (PC) and the frame pointer (FP), especially on platforms that
 > +don't dedicate a register to the FP.
 > +
 > +(Given this description, you might wonder how the frame ID for the
 > +innermost frame (frame 0) is unwound, given that unwinding requires an
 > +inner frame.  The answer is that internally, @value{GDBN} always has a
 > +``sentinel'' frame that is inner to the innermost frame, and which has
 > +a pre-computed unwinder that just returns the registers as they are,
 > +without unwinding.)
 > +
 > +The Guile frame unwinder API loosely follows this protocol as
 > +described above.  Guile will build a special ``ephemeral frame
 > +object'' corresponding the frame being unwound (in our example, frame
 > +1).  It allows the user to read registers from that ephemeral frame,
 > +which in reality are unwound from the already-existing frame 0.  If
 > +the unwinder decides that it can handle the frame in question, it then
 > +creates and returns an ``unwind info'' object for that frame.  The
 > +unwind info object contains a frame ID for the ephemeral frame.  It
 > +also records the values of any registers saved in the frame, for use
 > +when unwinding its outer frame (frame 2).
 > +
 > +Frame unwinder objects are managed in Guile much in the same way as
 > +frame filters.  Indeed, users will often want to implement both frame
 > +unwinders and frame filters: unwinders will compute the correct
 > +backtrace and register state, and filters can fill in function names,
 > +line numbers, and the like.  @xref{Guile Frame Filter API}, for more
 > +on frame filters.
 > +
 > +As with frame filters, there can be multiple frame unwinders
 > +registered with @value{GDBN}, and each one may be individually enabled
 > +or disabled at will.  The filters will be tried in priority order,
 > +from highest to lowest priority, and the first one that sets the frame
 > +ID will take responsibility for the frame.
 > +
 > +To use frame unwinders, first load the @code{(gdb frame-unwinders)} module
 > +to have access to the procedures that manipulate frame unwinders:
 > +
 > +@example
 > +(use-modules (gdb frame-unwinders))
 > +@end example
 > +
 > +@deffn {Scheme Procedure} make-frame-unwinder name procedure @
 > +       @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]}
 > +Make a new frame unwinder.
 > +
 > +The unwinder will be identified by the string @var{name}.
 > +@var{procedure} should be a function of one argument, taking an
 > +ephemeral frame object.  If the unwinder procedure decides to handle
 > +the frame, it should return an unwind info object.  Otherwise the
 > +unwinder returns @code{#f}, @value{GDBN} will continue to search its
 > +list for an unwinder.
 > +
 > +The unwinder will be initially enabled, unless the keyword argument
 > +@code{#:enabled? #f} is given.  Even if the unwinder is marked as
 > +enabled, it will need to be registered with @value{GDBN} via
 > +@code{register-frame-unwinder!} in order to take effect.  When
 > +registered, the unwinder will be inserted into the list of registered
 > +unwinders with the given @var{priority}, which should be a number, and
 > +which defaults to 20 if not given.  Higher priority unwinders will be
 > +tried before lower-priority unwinders.
 > +@end deffn

Hi.

It would be good to forcefully limit the values of priority such that
a possible future implementation that referenced priorities
in C(/C++) could just use ints (or unsigned ints).

 > +
 > +@deffn {Scheme Procedure} all-frame-unwinders
 > +Return a list of all frame unwinders.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} register-frame-unwinder! unwinder @
 > +       @r{[}#:scope scope@r{]}
 > +Register the frame unwinder @var{unwinder} with @value{GDBN}.
 > +
 > +By default, the scope of the unwinder is global, meaning that it is
 > +associated with all objfiles and progspaces.  Pass an objfile or a
 > +progspace as the @code{#:scope} keyword argument to instead scope the
 > +unwinder into a specific objfile or progspace, respectively.
 > +
 > +The unwinder's name will be checked for uniqueness within its registered
 > +scope.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
 > +@deffnx {Scheme Procedure} enable-frame-unwinder! unwinder
 > +@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
 > +Mark a frame unwinder as enabled, if @var{enabled?} is true, or
 > +as disabled otherwise.

If we have {en,dis}able-frame-unwinder! then we should have
them for all things that can be enabled/disabled.
Such redundancy in the lowest level API bothers me,
and if a particular user wants to provide their own wrappers
it's trivial.

 > +
 > +@var{unwinder} can either be a frame unwinder object, or it can be a
 > +string naming an unwinder in the current scope.  If no such unwinder
 > +is found, an error is signalled.

I think these should operate on just an unwinder object.

There is a convention for naming such objects (pretty-printers,
type-printers, frame-filters, xmethods, and so on) which involves
two pieces: scope(/locus) name and object name.
But that feels like something for a higher level API than
these primitives.

For reference sake, there's a further convention for pretty-printers
that split "object name" into two pieces, but it's not relevant
to this API, the name is still one string.

 > +
 > +@code{enable-frame-unwinder!} and @code{disable-frame-unwinder} are simple
 > +wrappers around @code{set-frame-unwinder-enabled!} which pass @code{#t}
 > +or @code{#f} as the @var{enabled?} argument, respectively.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} frame-unwinder-name unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-enabled? unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-registered? unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-priority unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-procedure unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-scope unwinder
 > +Accessors for a frame unwinder object's fields.  The
 > +@code{registered?}  field indicates whether a unwinder has been added
 > +to @value{GDBN} or not.  @code{scope} is the objfile or progspace in
 > +which the unwinder was registered, or @code{#f} otherwise.
 > +@end deffn
 > +
 > +Frame unwinders operate on ``ephemeral frames''.  Ephemeral frames are
 > +valid only while they are being unwound; any access to an ephemeral
 > +frame outside the extent of their unwind operation will signal an
 > +error.  Currently ephemeral frames can only be used in two limited
 > +ways: to read registers from the frame, and to construct an unwind
 > +info object for a successful unwinder return.
 > +
 > +@deffn {Scheme Procedure} ephemeral-frame-read-register frame register
 > +Return the value of a register in the ephemeral frame @var{frame}.
 > +@var{register} should be given as a string.
 > +@end deffn
 > +
 > +If an unwinder successfully unwinds a frame, it should return an
 > +unwind info object created via the @code{make-unwind-info} procedure.
 > +An unwind info object specifies the frame ID for its associated
 > +ephemeral frame, and also records values of registers that are saved
 > +within the frame.
 > +
 > +@deffn {Scheme Procedure} make-unwind-info frame fp [pc [special]]
 > +Make an unwind info object for the ephemeral frame @var{frame}.

What if we defer adding "special" to a later patch?
It seems like it would be straightforward to add at a later date.
As much as you don't like "sniffer", "special" bugs me 10x more. :-)

 > +
 > +@var{fp}, @var{pc}, and @var{special} are used to build an identifier
 > +(frame ID) for the frame.  A frame ID is a unique name for a frame
 > +that remains valid as long as the frame itself is valid.  Usually the
 > +frame ID is built from from the frame's stack address and code
 > +address.  The stack address @var{fp} should normally be a pointer to
 > +the new end of the stack when the function was called, as a
 > +@value{GDBN} value.  Similarly the code address @var{pc} should be
 > +given as the address of the entry point of the function.
 > +
 > +For most architectures, it is sufficient to just specify just the
 > +stack and code pointers @var{fp} and @var{pc}.  Some architectures
 > +have another stack or some other frame state store, like ia64.  For
 > +these platforms the frame ID needs an additional address, which may be
 > +passed as the @var{special} optional argument.
 > +
 > +It is possible to create a frame ID with just a stack address, but
 > +it's better to specify a code address as well if possible.
 > +@end deffn
 > +
 > +After building an unwind info object for an ephemeral frame, an
 > +unwinder can call @code{unwind-info-add-saved-register!} to record
 > +saved registers.  The values of the saved registers logically belong
 > +to the frame that is older than the ephemeral frame being unwound, not
 > +to the ephemeral frame itself.
 > +
 > +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
 > +Set the saved value of a register in a ephemeral frame.
 > +
 > +@var{register} names a register, and should be a string, for example
 > +@samp{rip}.  @var{value} is the register value, as a @value{GDBN}
 > +value.
 > +@end deffn
 > +
 > +Any register whose value is not recorded as saved via
 > +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
 > +in the outer frame.
 > +
 >  @node Commands In Guile
 >  @subsubsection Commands In Guile
 >  

Bleah.
I need to head out.
I'll get to the code tonight or tomorrow.
Doug Evans March 24, 2015, 10:07 p.m. UTC | #2
Andy Wingo writes:
 > Voici a new version of the Guile frame unwinder patch.  Changes:
 > 
 >   * Separate ephemeral frame object info ephemeral frame + unwind info
 >     objects; see:
 > 
 >       http://article.gmane.org/gmane.comp.gdb.patches/105759
 > 
 >   * Update management of unwinders as in newest frame filter patch
 >     (http://article.gmane.org/gmane.comp.gdb.patches/105616)
 > 
 >      - Specify #:scope when registering, not when creating unwinders
 > 
 >      - s/add-frame-unwinder!/register-frame-unwinder!/
 > 
 >      - s/remove-frame-unwinder!/unregister-frame-unwinder!/
 > 
 >      - Add s/set-frame-unwinder-enabled!/
 > 
 >   * Adapt for new TRY/CATCH/END_CATCH
 > 
 > Regards,
 > 
 > Andy
 > 
 > >From 55dae8b56541ef51a03676ce48ea6c2cdeb25f29 Mon Sep 17 00:00:00 2001
 > From: Andy Wingo <wingo@igalia.com>
 > Date: Thu, 5 Mar 2015 16:40:20 +0100
 > Subject: [PATCH] Add Guile frame unwinder interface
 > 
 > gdb/doc/ChangeLog:
 > 
 > 	* guile.texi (Guile Frame Unwinder API): New section.
 > 
 > gdb/ChangeLog:
 > 
 > 	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
 > 	is no selected frame and no block is selected; instead, fall back
 > 	to the current frame.
 > 	* guile/scm-frame-unwinder.c: New file.
 > 	* guile/lib/gdb/frame-unwinders.scm: New file.
 > 	* guile/guile.c (initialize_gdb_module): Call
 > 	gdbscm_initialize_frame_unwinders.
 > 	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
 > 	declaration.
 > 	* frame.c (get_prev_frame): Detect recursive unwinds, returning
 > 	NULL in that case.
 > 	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
 > 	(frame_unwind_is_unwinding): New declaration.
 > 	* frame-unwind.c (is_unwinding): New file-local variable.
 > 	(set_is_unwinding, unset_is_unwinding): New file-local helpers.
 > 	(frame_unwind_is_unwinding): New exported predicate.
 > 	(frame_unwind_try_handler): Arrange for
 > 	frame_unwind_is_unwinding to return true when unwinding the
 > 	innermost frame.
 > 	(frame_unwind_got_bytes): Make buf arg const.
 > 	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
 > 	frame-unwinders.scm.
 > 	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
 > 	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
 > 	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
 > 	(scm-frame-unwinder.o): New target.
 > 
 > gdb/testsuite/ChangeLog:
 > 
 > 	* gdb.guile/scm-frame-unwinder.exp:
 > 	* gdb.guile/scm-frame-unwinder.c:
 > 	* gdb.guile/scm-frame-unwinder-gdb.scm.in:
 > 	* gdb.guile/scm-frame-unwinder.scm: Add unwinder tests.
 > ...
 > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
 > index fed8035..08e08db 100644
 > --- a/gdb/Makefile.in
 > +++ b/gdb/Makefile.in
 > @@ -315,6 +315,7 @@ SUBDIR_GUILE_OBS = \
 >  	scm-exception.o \
 >  	scm-frame.o \
 >  	scm-frame-filter.o \
 > +	scm-frame-unwinder.o \
 >  	scm-gsmob.o \
 >  	scm-iterator.o \
 >  	scm-lazy-string.o \
 > @@ -342,6 +343,7 @@ SUBDIR_GUILE_SRCS = \
 >  	guile/scm-exception.c \
 >  	guile/scm-frame.c \
 >  	guile/scm-frame-filter.c \
 > +	guile/scm-frame-unwinder.c \
 >  	guile/scm-gsmob.c \
 >  	guile/scm-iterator.c \
 >  	guile/scm-lazy-string.c \
 > @@ -2416,6 +2418,10 @@ scm-frame-filter.o: $(srcdir)/guile/scm-frame-filter.c
 >  	$(COMPILE) $(srcdir)/guile/scm-frame-filter.c
 >  	$(POSTCOMPILE)
 >  
 > +scm-frame-unwinder.o: $(srcdir)/guile/scm-frame-unwinder.c
 > +	$(COMPILE) $(srcdir)/guile/scm-frame-unwinder.c
 > +	$(POSTCOMPILE)
 > +
 >  scm-gsmob.o: $(srcdir)/guile/scm-gsmob.c
 >  	$(COMPILE) $(srcdir)/guile/scm-gsmob.c
 >  	$(POSTCOMPILE)
 > diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
 > index b4916b0..14d3653 100644
 > --- a/gdb/data-directory/Makefile.in
 > +++ b/gdb/data-directory/Makefile.in
 > @@ -88,6 +88,7 @@ GUILE_SOURCE_FILES = \
 >  	gdb/boot.scm \
 >  	gdb/experimental.scm \
 >  	gdb/frame-filters.scm \
 > +	gdb/frame-unwinders.scm \
 >  	gdb/init.scm \
 >  	gdb/iterator.scm \
 >  	gdb/printing.scm \
 > @@ -100,6 +101,7 @@ GUILE_NO_UNBOUND_WARNING_COMPILED_FILES = \
 >  GUILE_COMPILED_FILES = \
 >  	gdb/experimental.go \
 >  	gdb/frame-filters.go \
 > +	gdb/frame-unwinders.go \
 >  	gdb/iterator.go \
 >  	gdb/printing.go \
 >  	gdb/support.go \
 > diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
 > index 162ace7..3be1926 100644
 > --- a/gdb/doc/ChangeLog
 > +++ b/gdb/doc/ChangeLog
 > @@ -1,3 +1,7 @@
 > +2015-03-06  Andy Wingo  <wingo@igalia.com>
 > +
 > +	* guile.texi (Guile Frame Unwinder API): New section.
 > +
 >  2015-02-15  Andy Wingo  <wingo@igalia.com>
 >  
 >  	* guile.texi (Guile Frame Filter API)
 > diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
 > index 3045d90..fdcbca3 100644
 > --- a/gdb/doc/guile.texi
 > +++ b/gdb/doc/guile.texi
 > @@ -143,6 +143,7 @@ from the Guile interactive prompt.
 >  * Writing a Guile Pretty-Printer:: Writing a pretty-printer
 >  * Guile Frame Filter API::   Filtering frames.
 >  * Writing a Frame Filter in Guile:: Writing a frame filter.
 > +* Guile Frame Unwinder API:: Programmatically unwinding stack frames
 >  * Commands In Guile::        Implementing new commands in Guile
 >  * Parameters In Guile::      Adding new @value{GDBN} parameters
 >  * Progspaces In Guile::      Program spaces
 > @@ -2137,6 +2138,210 @@ also possible to do the job of an decorator with a filter.  Still,
 >  avoiding the stream interfaces can often be a good reason to use the
 >  simpler decorator layer.
 >  
 > +@node Guile Frame Unwinder API
 > +@subsubsection Unwinding Frames in Guile
 > +@cindex frame unwinder api, guile
 > +
 > +In @value{GDBN} terminology, ``unwinding'' is the process of finding
 > +an older (outer) frame on the stack.  Unwinders form the core of
 > +backtrace computation in @value{GDBN}.  @value{GDBN} comes with
 > +unwinders for each target architecture that it supports, and these
 > +usually suffice to unwind the stack.  However, some target programs
 > +can have non-standard frame layouts that cannot be unwound by the
 > +standard unwinders.  This is often the case when working with
 > +just-in-time compilation environments, for example in JavaScript
 > +implementations.  In such cases, users can define custom code in Guile
 > +to programmatically unwind the problematic stack frames.
 > +
 > +Before getting into the API, we should discuss how unwinders work in
 > +@value{GDBN}.
 > +
 > +As an example, consider a stack in which we have already computed
 > +frame 0 and we want to compute frame 1.  We say that frame 0 is the
 > +``inner'' frame, and frame 1 will be the ``outer'' frame.
 > +
 > +Unwinding starts with a model of the state of all registers in an
 > +inner, already unwound frame.  In our case, we start with frame 0.
 > +@value{GDBN} then constructs a ephemeral frame object for the outer
 > +frame that is being built (frame 1) and links it to the inner frame
 > +(frame 0).  @value{GDBN} then goes through its list of registered
 > +unwinders, searching for one that knows how to unwind the frame.  When
 > +it finds one, @value{GDBN} will ask the unwinder to compute a frame
 > +identifier for the outer frame.  Once the unwinder has done so, the
 > +frame is marked as ``valid'' and can be accessed using the normal
 > +frame API.
 > +
 > +A frame identifier (frame ID) consists of code and data pointers
 > +associated with a frame which will remain valid as long as the frame
 > +is still alive.  Usually a frame ID is a pair of the code and stack
 > +pointers as they were when control entered the function associated
 > +with the frame, though as described below there are other ways to
 > +build a frame ID@.  However as you can see, computing the frame ID
 > +requires some input from the unwinder to determine the start code
 > +address (PC) and the frame pointer (FP), especially on platforms that
 > +don't dedicate a register to the FP.
 > +
 > +(Given this description, you might wonder how the frame ID for the
 > +innermost frame (frame 0) is unwound, given that unwinding requires an
 > +inner frame.  The answer is that internally, @value{GDBN} always has a
 > +``sentinel'' frame that is inner to the innermost frame, and which has
 > +a pre-computed unwinder that just returns the registers as they are,
 > +without unwinding.)
 > +
 > +The Guile frame unwinder API loosely follows this protocol as
 > +described above.  Guile will build a special ``ephemeral frame
 > +object'' corresponding the frame being unwound (in our example, frame
 > +1).  It allows the user to read registers from that ephemeral frame,
 > +which in reality are unwound from the already-existing frame 0.  If
 > +the unwinder decides that it can handle the frame in question, it then
 > +creates and returns an ``unwind info'' object for that frame.  The
 > +unwind info object contains a frame ID for the ephemeral frame.  It
 > +also records the values of any registers saved in the frame, for use
 > +when unwinding its outer frame (frame 2).
 > +
 > +Frame unwinder objects are managed in Guile much in the same way as
 > +frame filters.  Indeed, users will often want to implement both frame
 > +unwinders and frame filters: unwinders will compute the correct
 > +backtrace and register state, and filters can fill in function names,
 > +line numbers, and the like.  @xref{Guile Frame Filter API}, for more
 > +on frame filters.
 > +
 > +As with frame filters, there can be multiple frame unwinders
 > +registered with @value{GDBN}, and each one may be individually enabled
 > +or disabled at will.  The filters will be tried in priority order,
 > +from highest to lowest priority, and the first one that sets the frame
 > +ID will take responsibility for the frame.
 > +
 > +To use frame unwinders, first load the @code{(gdb frame-unwinders)} module
 > +to have access to the procedures that manipulate frame unwinders:
 > +
 > +@example
 > +(use-modules (gdb frame-unwinders))
 > +@end example
 > +
 > +@deffn {Scheme Procedure} make-frame-unwinder name procedure @
 > +       @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]}
 > +Make a new frame unwinder.
 > +
 > +The unwinder will be identified by the string @var{name}.
 > +@var{procedure} should be a function of one argument, taking an
 > +ephemeral frame object.  If the unwinder procedure decides to handle
 > +the frame, it should return an unwind info object.  Otherwise the
 > +unwinder returns @code{#f}, @value{GDBN} will continue to search its
 > +list for an unwinder.
 > +
 > +The unwinder will be initially enabled, unless the keyword argument
 > +@code{#:enabled? #f} is given.  Even if the unwinder is marked as
 > +enabled, it will need to be registered with @value{GDBN} via
 > +@code{register-frame-unwinder!} in order to take effect.  When
 > +registered, the unwinder will be inserted into the list of registered
 > +unwinders with the given @var{priority}, which should be a number, and
 > +which defaults to 20 if not given.  Higher priority unwinders will be
 > +tried before lower-priority unwinders.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} all-frame-unwinders
 > +Return a list of all frame unwinders.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} register-frame-unwinder! unwinder @
 > +       @r{[}#:scope scope@r{]}
 > +Register the frame unwinder @var{unwinder} with @value{GDBN}.
 > +
 > +By default, the scope of the unwinder is global, meaning that it is
 > +associated with all objfiles and progspaces.  Pass an objfile or a
 > +progspace as the @code{#:scope} keyword argument to instead scope the
 > +unwinder into a specific objfile or progspace, respectively.
 > +
 > +The unwinder's name will be checked for uniqueness within its registered
 > +scope.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
 > +@deffnx {Scheme Procedure} enable-frame-unwinder! unwinder
 > +@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
 > +Mark a frame unwinder as enabled, if @var{enabled?} is true, or
 > +as disabled otherwise.
 > +
 > +@var{unwinder} can either be a frame unwinder object, or it can be a
 > +string naming an unwinder in the current scope.  If no such unwinder
 > +is found, an error is signalled.
 > +
 > +@code{enable-frame-unwinder!} and @code{disable-frame-unwinder} are simple
 > +wrappers around @code{set-frame-unwinder-enabled!} which pass @code{#t}
 > +or @code{#f} as the @var{enabled?} argument, respectively.
 > +@end deffn

Remove enable-frame-unwinder!, disable-frame-unwinder!
and make set-frame-unwinder-enabled! take a frame-unwinder object only.

 > +
 > +@deffn {Scheme Procedure} frame-unwinder-name unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-enabled? unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-registered? unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-priority unwinder

As discussed let's remove priority for now.

 > +@deffnx {Scheme Procedure} frame-unwinder-procedure unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-scope unwinder
 > +Accessors for a frame unwinder object's fields.  The
 > +@code{registered?}  field indicates whether a unwinder has been added
 > +to @value{GDBN} or not.  @code{scope} is the objfile or progspace in
 > +which the unwinder was registered, or @code{#f} otherwise.
 > +@end deffn
 > +
 > +Frame unwinders operate on ``ephemeral frames''.  Ephemeral frames are

s/ephemeral/pending/

 > +valid only while they are being unwound; any access to an ephemeral
 > +frame outside the extent of their unwind operation will signal an
 > +error.  Currently ephemeral frames can only be used in two limited
 > +ways: to read registers from the frame, and to construct an unwind
 > +info object for a successful unwinder return.
 > +
 > +@deffn {Scheme Procedure} ephemeral-frame-read-register frame register
 > +Return the value of a register in the ephemeral frame @var{frame}.
 > +@var{register} should be given as a string.
 > +@end deffn
 > +
 > +If an unwinder successfully unwinds a frame, it should return an
 > +unwind info object created via the @code{make-unwind-info} procedure.
 > +An unwind info object specifies the frame ID for its associated
 > +ephemeral frame, and also records values of registers that are saved
 > +within the frame.
 > +
 > +@deffn {Scheme Procedure} make-unwind-info frame fp [pc [special]]

As discussed make the frame id a parameter,
and have a make-frame-id constructor.

(make-frame-id sp #:pc pc #:special special) -> <gdb:frame-id>

[I'm going to give up trying to defer special for another day.
With adequate documentation it could be tolerable.]

 > +Make an unwind info object for the ephemeral frame @var{frame}.
 > +
 > +@var{fp}, @var{pc}, and @var{special} are used to build an identifier
 > +(frame ID) for the frame.  A frame ID is a unique name for a frame
 > +that remains valid as long as the frame itself is valid.  Usually the
 > +frame ID is built from from the frame's stack address and code
 > +address.  The stack address @var{fp} should normally be a pointer to
 > +the new end of the stack when the function was called, as a
 > +@value{GDBN} value.  Similarly the code address @var{pc} should be
 > +given as the address of the entry point of the function.
 > +
 > +For most architectures, it is sufficient to just specify just the
 > +stack and code pointers @var{fp} and @var{pc}.  Some architectures
 > +have another stack or some other frame state store, like ia64.  For
 > +these platforms the frame ID needs an additional address, which may be
 > +passed as the @var{special} optional argument.
 > +
 > +It is possible to create a frame ID with just a stack address, but
 > +it's better to specify a code address as well if possible.
 > +@end deffn
 > +
 > +After building an unwind info object for an ephemeral frame, an
 > +unwinder can call @code{unwind-info-add-saved-register!} to record
 > +saved registers.  The values of the saved registers logically belong
 > +to the frame that is older than the ephemeral frame being unwound, not
 > +to the ephemeral frame itself.
 > +
 > +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
 > +Set the saved value of a register in a ephemeral frame.
 > +
 > +@var{register} names a register, and should be a string, for example
 > +@samp{rip}.  @var{value} is the register value, as a @value{GDBN}

To be more universal let's replace "rip" here with "pc".

 > +value.
 > +@end deffn
 > +
 > +Any register whose value is not recorded as saved via
 > +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
 > +in the outer frame.

"outer frame" here is confusing.
I'd have thought this would read "``not saved'' in the frame".
[with the frame?]

 > +
 >  @node Commands In Guile
 >  @subsubsection Commands In Guile
 >  
 > diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c
 > index bba1ae7..4347dca 100644
 > --- a/gdb/frame-unwind.c
 > +++ b/gdb/frame-unwind.c
 > @@ -87,6 +87,34 @@ frame_unwind_append_unwinder (struct gdbarch *gdbarch,
 >    (*ip)->unwinder = unwinder;
 >  }
 >  
 > +/* Nonzero if we are finding the unwinder for a frame; see
 > +   frame_unwind_try_handler.  */
 > +static int is_unwinding = 0;
 > +
 > +/* Return nonzero if we are inside a sniffer call.  */
 > +
 > +int
 > +frame_unwind_is_unwinding (void)
 > +{
 > +  return is_unwinding;
 > +}
 > +
 > +/* Cleanup helpers for is_unwinding.  */
 > +
 > +static void
 > +unset_is_unwinding (void *unused)
 > +{
 > +  is_unwinding = 0;
 > +}
 > +
 > +static struct cleanup*
 > +set_is_unwinding (void)
 > +{
 > +  is_unwinding = 1;
 > +
 > +  return make_cleanup (unset_is_unwinding, NULL);
 > +}
 > +
 >  /* Call SNIFFER from UNWINDER.  If it succeeded set UNWINDER for
 >     THIS_FRAME and return 1.  Otherwise the function keeps THIS_FRAME
 >     unchanged and returns 0.  */
 > @@ -98,11 +126,18 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
 >    struct cleanup *old_cleanup;
 >    int res = 0;
 >  
 > +  if (is_unwinding)
 > +    internal_error (__FILE__, __LINE__,
 > +		    _("Recursion detected while finding an unwinder."));
 >    old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder);
 >  
 >    TRY
 >      {
 > +      struct cleanup *cleanup = set_is_unwinding ();
 > +
 >        res = unwinder->sniffer (unwinder, this_frame, this_cache);
 > +
 > +      do_cleanups (cleanup);

I didn't want to impose an order on the commits of python and scheme.
Once both are in we'll want to have python use set_is_unwinding too.

 >      }
 >    CATCH (ex, RETURN_MASK_ERROR)
 >      {
 > @@ -252,7 +287,8 @@ frame_unwind_got_constant (struct frame_info *frame, int regnum,
 >  }
 >  
 >  struct value *
 > -frame_unwind_got_bytes (struct frame_info *frame, int regnum, gdb_byte *buf)
 > +frame_unwind_got_bytes (struct frame_info *frame, int regnum,
 > +			const gdb_byte *buf)
 >  {
 >    struct gdbarch *gdbarch = frame_unwind_arch (frame);
 >    struct value *reg_val;
 > diff --git a/gdb/frame-unwind.h b/gdb/frame-unwind.h
 > index 44add12..3e322f2 100644
 > --- a/gdb/frame-unwind.h
 > +++ b/gdb/frame-unwind.h
 > @@ -179,6 +179,11 @@ extern void frame_unwind_append_unwinder (struct gdbarch *gdbarch,
 >  extern void frame_unwind_find_by_frame (struct frame_info *this_frame,
 >  					void **this_cache);
 >  
 > +/* Return nonzero if we are in the process of finding an unwinder for a frame.
 > +   See the comments in get_current_frame.  */
 > +
 > +extern int frame_unwind_is_unwinding (void);
 > +
 >  /* Helper functions for value-based register unwinding.  These return
 >     a (possibly lazy) value of the appropriate type.  */
 >  
 > @@ -210,7 +215,7 @@ struct value *frame_unwind_got_constant (struct frame_info *frame, int regnum,
 >     inside BUF.  */
 >  
 >  struct value *frame_unwind_got_bytes (struct frame_info *frame, int regnum,
 > -                                      gdb_byte *buf);
 > +                                      const gdb_byte *buf);
 >  
 >  /* Return a value which indicates that FRAME's saved version of REGNUM
 >     has a known constant (computed) value of ADDR.  Convert the
 > diff --git a/gdb/frame.c b/gdb/frame.c
 > index b3cbf23..9e63f21 100644
 > --- a/gdb/frame.c
 > +++ b/gdb/frame.c
 > @@ -2212,6 +2212,22 @@ get_prev_frame (struct frame_info *this_frame)
 >        return NULL;
 >      }
 >  
 > +  /* Unwinders implemented in Python or Scheme could eventually make an API call
 > +     that would cause GDB to try to unwind a frame while unwinding a frame.
 > +     Because already-unwound frames will be found in the frame cache, unwinding
 > +     will only happen at the old end of the stack, which means that any
 > +     recursive unwinding attempt will surely lead to unbounded recursion.  Ways
 > +     this can happen include such common functions as `get_current_arch' or
 > +     `lookup_symbol', via `get_selected_frame', so it's impractical to simply
 > +     declare these an error.  Instead, we detect this case and return NULL,
 > +     indicating that the known stack of frames ends here.  */
 > +  if (frame_unwind_is_unwinding ())
 > +    {
 > +      frame_debug_got_null_frame (this_frame,
 > +				  "get_prev_frame within unwinder sniffer");
 > +      return NULL;
 > +    }
 > +
 >    return get_prev_frame_always (this_frame);
 >  }
 >  
 > diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
 > index d7c166c..9ca807e 100644
 > --- a/gdb/guile/guile-internal.h
 > +++ b/gdb/guile/guile-internal.h
 > @@ -598,6 +598,7 @@ extern void gdbscm_initialize_disasm (void);
 >  extern void gdbscm_initialize_exceptions (void);
 >  extern void gdbscm_initialize_frames (void);
 >  extern void gdbscm_initialize_frame_filters (void);
 > +extern void gdbscm_initialize_frame_unwinders (void);
 >  extern void gdbscm_initialize_iterators (void);
 >  extern void gdbscm_initialize_lazy_strings (void);
 >  extern void gdbscm_initialize_math (void);
 > diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
 > index 176e97e..d1850e7 100644
 > --- a/gdb/guile/guile.c
 > +++ b/gdb/guile/guile.c
 > @@ -670,6 +670,7 @@ initialize_gdb_module (void *data)
 >    gdbscm_initialize_disasm ();
 >    gdbscm_initialize_frames ();
 >    gdbscm_initialize_frame_filters ();
 > +  gdbscm_initialize_frame_unwinders ();
 >    gdbscm_initialize_iterators ();
 >    gdbscm_initialize_lazy_strings ();
 >    gdbscm_initialize_math ();
 > diff --git a/gdb/guile/lib/gdb/frame-unwinders.scm b/gdb/guile/lib/gdb/frame-unwinders.scm
 > new file mode 100644
 > index 0000000..ead2443
 > --- /dev/null
 > +++ b/gdb/guile/lib/gdb/frame-unwinders.scm
 > @@ -0,0 +1,213 @@
 > +;; Frame unwinder support.
 > +;;
 > +;; Copyright (C) 2015 Free Software Foundation, Inc.
 > +;;
 > +;; This file is part of GDB.
 > +;;
 > +;; This program is free software; you can redistribute it and/or modify
 > +;; it under the terms of the GNU General Public License as published by
 > +;; the Free Software Foundation; either version 3 of the License, or
 > +;; (at your option) any later version.
 > +;;
 > +;; This program is distributed in the hope that it will be useful,
 > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +;; GNU General Public License for more details.
 > +;;
 > +;; You should have received a copy of the GNU General Public License
 > +;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +(define-module (gdb frame-unwinders)
 > +  #:use-module ((gdb) #:hide (frame? symbol?))
 > +  #:use-module (srfi srfi-1)
 > +  #:use-module (srfi srfi-9)
 > +  #:use-module (ice-9 match)
 > +  #:export (ephemeral-frame-read-register
 > +
 > +            make-unwind-info
 > +            unwind-info-add-saved-register!
 > +
 > +            make-frame-unwinder
 > +            frame-unwinder?
 > +            frame-unwinder-name
 > +            frame-unwinder-enabled?
 > +            frame-unwinder-registered?
 > +            frame-unwinder-priority
 > +            frame-unwinder-procedure
 > +            frame-unwinder-scope
 > +
 > +            find-frame-unwinder-by-name
 > +
 > +            register-frame-unwinder!
 > +            unregister-frame-unwinder!
 > +            set-frame-unwinder-enabled!
 > +            enable-frame-unwinder!
 > +            disable-frame-unwinder!
 > +
 > +            all-frame-unwinders))
 > +
 > +(define-record-type <frame-unwinder>
 > +  (%make-frame-unwinder name priority enabled? registered? procedure scope)
 > +  frame-unwinder?
 > +  ;; string
 > +  (name frame-unwinder-name)
 > +  ;; real
 > +  (priority frame-unwinder-priority set-priority!)
 > +  ;; bool
 > +  (enabled? frame-unwinder-enabled? set-enabled?!)
 > +  ;; bool
 > +  (registered? frame-unwinder-registered? set-registered?!)
 > +  ;; ephemeral-frame -> unwind-info | #f
 > +  (procedure frame-unwinder-procedure)
 > +  ;; objfile | progspace | #f
 > +  (scope frame-unwinder-scope set-scope!))
 > +
 > +(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))

In addition to removing priority (for now), let's remove enabled? as
a parameter here.  The user can disable the unwinder before registering it.

 > +  "Make and return a new frame unwinder.  NAME and PROCEDURE are
 > +required arguments.  Specify #:priority or #:enabled? to set the
 > +priority and enabled status of the unwinder.
 > +
 > +The unwinder must be registered with GDB via `register-frame-unwinder!'
 > +before it is active."
 > +  (let ((registered? #f) (scope #f))
 > +    (%make-frame-unwinder name priority enabled? registered? procedure scope)))
 > +
 > +;; List of frame unwinders, sorted by priority from highest to lowest.
 > +(define *frame-unwinders* '())
 > +
 > +(define (same-scope? a b)
 > +  "Return #t if A and B represent the same scope, for the purposes of
 > +frame unwinder selection."
 > +  (cond
 > +   ;; If either is the global scope, they share a scope.
 > +   ((or (not a) (not b)) #t)
 > +   ;; If either is an objfile, compare their progspaces.
 > +   ((objfile? a) (same-scope? (objfile-progspace a) b))
 > +   ((objfile? b) (same-scope? a (objfile-progspace b)))
 > +   ;; Otherwise they are progspaces.  If they eq?, it's the same scope.
 > +   (else (eq? a b))))
 > +
 > +(define (is-valid? unwinder)
 > +  "Return #t if the scope of UNWINDER is still valid, or otherwise #f if
 > +the objfile or progspace has been removed from GDB."
 > +  (let ((scope (frame-unwinder-scope unwinder)))
 > +    (cond
 > +     ((progspace? scope) (progspace-valid? scope))
 > +     ((objfile? scope) (objfile-valid? scope))
 > +     (else #t))))
 > +
 > +(define (all-frame-unwinders)
 > +  "Return a list of all active frame unwinders, ordered from highest to
 > +lowest priority."
 > +  ;; Copy the list to prevent callers from mutating our state.
 > +  (list-copy *frame-unwinders*))
 > +
 > +(define* (has-active-frame-unwinders? #:optional
 > +                                      (scope (current-progspace)))
 > +  "Return #t if there are active frame unwinders for the given scope, or
 > +#f otherwise."
 > +  (let lp ((unwinders *frame-unwinders*))
 > +    (match unwinders
 > +      (() #f)
 > +      ((unwinder . unwinders)
 > +       (or (and (frame-unwinder-enabled? unwinder)
 > +                (same-scope? (frame-unwinder-scope unwinder) scope))
 > +           (lp unwinders))))))

Not used anywhere. Delete?

 > +
 > +(define (prune-frame-unwinders!)
 > +  "Prune frame unwinders whose objfile or progspace has gone away,
 > +returning a fresh list of frame unwinders."

s/returning a fresh list of frame unwinders//

 > +  (set! *frame-unwinders*
 > +        (let lp ((unwinders *frame-unwinders*))
 > +          (match unwinders
 > +            (() '())
 > +            ((f . unwinders)
 > +             (cond
 > +              ((is-valid? f)
 > +               (cons f (lp unwinders)))
 > +              (else
 > +               (set-registered?! f #f)
 > +               (lp unwinders))))))))
 > +
 > +(define* (register-frame-unwinder! unwinder #:key scope)
 > +  "Register a frame unwinder with GDB.  Frame unwinders must be
 > +registered before they will be used to unwind backtraces.
 > +
 > +By default, the unwinder will be registered globally.  Specify an
 > +objfile or a progspace as the #:scope keyword argument to limit the
 > +unwinder to a specific scope."
 > +  (define (check-scope!)
 > +    (cond
 > +     ((objfile? scope)
 > +      (unless (objfile-valid? scope)
 > +        (error "Objfile is not valid" scope)))
 > +     ((progspace? scope)
 > +      (unless (progspace-valid? scope)
 > +        (error "Progspace is not valid" scope)))
 > +     (scope
 > +      (error "Invalid scope" scope))))

Guile question: Does scope default to #f?

 > +  (define (duplicate-unwinder? other)
 > +    (and (equal? (frame-unwinder-name other)
 > +                 (frame-unwinder-name unwinder))
 > +         (same-scope? (frame-unwinder-scope other) scope)))
 > +  (define (priority>=? a b)
 > +    (>= (frame-unwinder-priority a) (frame-unwinder-priority b)))
 > +  (define (insert-sorted elt xs <=?)
 > +    (let lp ((xs xs))
 > +      (match xs
 > +        (() (list elt))
 > +        ((x . xs*)
 > +         (if (<=? elt x)
 > +             (cons elt xs)
 > +             (cons x (lp xs*)))))))
 > +
 > +  (check-scope!)
 > +  (prune-frame-unwinders!)
 > +  (when (or-map duplicate-unwinder? *frame-unwinders*)
 > +    (error "Frame unwinder with this name already present in scope"
 > +           (frame-unwinder-name unwinder)))
 > +  (set-registered?! unwinder #t)
 > +  (set-scope! unwinder scope)
 > +  (set! *frame-unwinders*
 > +        (insert-sorted unwinder *frame-unwinders* priority>=?)))
 > +
 > +(define (unregister-frame-unwinder! unwinder)

Executive decision: s/unregister-frame-unwinder!/remove-frame-unwinder!/

I know register/unregister was my suggestion, but "unregister"
doesn't read very well to me.

 > +  "Unregister a frame unwinder."
 > +  (set-registered?! unwinder #f)
 > +  (set-scope! unwinder #f)
 > +  (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
 > +
 > +(define* (find-frame-unwinder-by-name name #:optional
 > +                                      (scope (current-progspace)))

The default scope when registering an unwinder is global.
Probably should have the same default throughout. I like global.

 > +  (prune-frame-unwinders!)
 > +  (or (find (lambda (unwinder)
 > +              (and (equal? name (frame-unwinder-name unwinder))
 > +                   (same-scope? (frame-unwinder-scope unwinder) scope)))
 > +            *frame-unwinders*)
 > +      (error "no frame unwinder found with name" name)))
 > +
 > +(define (set-frame-unwinder-enabled! unwinder enabled?)
 > +  "Enable or disable a frame unwinder."
 > +  (let ((unwinder (if (frame-unwinder? unwinder)
 > +                      unwinder
 > +                      (find-frame-unwinder-by-name unwinder))))

Restrict this to only take an unwinder object.

 > +    (set-enabled?! unwinder enabled?)
 > +    *unspecified*))
 > +
 > +(define (enable-frame-unwinder! unwinder)
 > +  "Mark a frame unwinder as enabled."
 > +  (set-frame-unwinder-enabled! unwinder #t))

Delete.
Though I don't mind them living in some "utils" like module.
These ones can even take a name+scope argument.

 > +
 > +(define (disable-frame-unwinder! unwinder)
 > +  "Mark a frame unwinder as disabled."
 > +  (set-frame-unwinder-enabled! unwinder #f))

Ditto.

 > +
 > +(define (unwind-frame frame)
 > +  (let ((scope (current-progspace)))
 > +    (or-map (lambda (unwinder)
 > +              (and (frame-unwinder-enabled? unwinder)
 > +                   (same-scope? (frame-unwinder-scope unwinder) scope)
 > +                   ((frame-unwinder-procedure unwinder) frame)))
 > +            *frame-unwinders*)))

This needs to first loop over all unwinders in all objfiles in
current-progspace, then current-progspace, then global ones.
[Right?]

I sometimes wonder if all these things (pretty-printers, type-printers,
xmethods, unwinders, etc.) need to first check the current objfile,
and then all remaining objfiles (of the current progspace) but that's
a level of complication I haven't seen a need for yet.

This also needs to confirm each attempted unwinder is still valid.
I still prefer recording unwinders with the gdb:objfile, gdb:progspace
objects, but this is one case where I don't mind the inconsistency:
More boilerplate for different ways of doing things.
Making sure one can later be converted to the other is the hard/risky part.

 > +
 > +(load-extension "gdb" "gdbscm_load_frame_unwinders")
 > diff --git a/gdb/guile/scm-frame-unwinder.c b/gdb/guile/scm-frame-unwinder.c
 > new file mode 100644
 > index 0000000..b57806c
 > --- /dev/null
 > +++ b/gdb/guile/scm-frame-unwinder.c
 > @@ -0,0 +1,593 @@
 > +/* Scheme interface to the JIT reader.
 > +
 > +   Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +   This file is part of GDB.
 > +
 > +   This program is free software; you can redistribute it and/or modify
 > +   it under the terms of the GNU General Public License as published by
 > +   the Free Software Foundation; either version 3 of the License, or
 > +   (at your option) any later version.
 > +
 > +   This program is distributed in the hope that it will be useful,
 > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +   GNU General Public License for more details.
 > +
 > +   You should have received a copy of the GNU General Public License
 > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 > +
 > +/* See README file in this directory for implementation notes, coding
 > +   conventions, et.al.  */
 > +
 > +#include "defs.h"
 > +#include "arch-utils.h"
 > +#include "frame-unwind.h"
 > +#include "gdb_obstack.h"
 > +#include "guile-internal.h"
 > +#include "inferior.h"
 > +#include "language.h"
 > +#include "observer.h"
 > +#include "regcache.h"
 > +#include "user-regs.h"
 > +#include "value.h"
 > +
 > +/* Non-zero if the (gdb frame-unwinders) module has been loaded.  */
 > +static int gdbscm_frame_unwinders_loaded = 0;
 > +
 > +/* The captured apply-frame-filter variable.  */

s/apply-frame-filter/unwind-frame/ ?

 > +static SCM unwind_frame = SCM_BOOL_F;
 > +
 > +/* Key that we use when associating data with an architecture.  */
 > +static struct gdbarch_data *uwscm_gdbarch_data;
 > +
 > +/* The frame unwinder interface makes ephemeral frame objects when trying
 > +   to unwind frames, and unwind info objects when unwinding is successful.
 > +   Here we define the names for the ephemeral frame and unwind info Scheme
 > +   data types.  */
 > +static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
 > +static const char unwind_info_smob_name[] = "gdb:unwind-info";
 > +
 > +/* SMOB tag for ephemeral frames and unwind info.  */
 > +static scm_t_bits ephemeral_frame_smob_tag;
 > +static scm_t_bits unwind_info_smob_tag;
 > +
 > +/* Data associated with a ephemeral frame.  */
 > +struct uwscm_ephemeral_frame
 > +{
 > +  /* The frame being unwound, used for the read-register interface.  */
 > +  struct frame_info *this_frame;
 > +
 > +  /* The architecture of the frame, here for convenience.  */
 > +  struct gdbarch *gdbarch;
 > +};
 > +
 > +/* Data associated with an unwind info object.  */
 > +struct uwscm_unwind_info
 > +{
 > +  /* The associated ephemeral frame.  */
 > +  SCM frame;
 > +
 > +  /* The frame_id for the associated ephemeral frame.  */
 > +  struct frame_id frame_id;
 > +
 > +  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
 > +     associated ephemeral frame.  */
 > +  SCM registers;
 > +};
 > +
 > +/* Type predicate for ephemeral frames.  */
 > +
 > +static int
 > +uwscm_is_ephemeral_frame (SCM obj)
 > +{
 > +  return SCM_SMOB_PREDICATE (ephemeral_frame_smob_tag, obj);
 > +}
 > +
 > +/* Data accessor for ephemeral frames.  */
 > +
 > +static struct uwscm_ephemeral_frame *
 > +uwscm_ephemeral_frame_data (SCM obj)
 > +{
 > +  gdb_assert (uwscm_is_ephemeral_frame (obj));
 > +  return (struct uwscm_ephemeral_frame *) SCM_SMOB_DATA (obj);
 > +}
 > +
 > +/* Type predicate for unwind info.  */
 > +
 > +static int
 > +uwscm_is_unwind_info (SCM obj)
 > +{
 > +  return SCM_SMOB_PREDICATE (unwind_info_smob_tag, obj);
 > +}
 > +
 > +/* Data accessor for unwind_info.  */
 > +
 > +static struct uwscm_unwind_info *
 > +uwscm_unwind_info_data (SCM obj)
 > +{
 > +  gdb_assert (uwscm_is_unwind_info (obj));
 > +  return (struct uwscm_unwind_info *) SCM_SMOB_DATA (obj);
 > +}
 > +
 > +/* Build a ephemeral frame.  */
 > +
 > +static SCM
 > +uwscm_make_ephemeral_frame (struct frame_info *this_frame)
 > +{
 > +  struct uwscm_ephemeral_frame *data;
 > +
 > +  data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
 > +
 > +  data->this_frame = this_frame;
 > +  TRY
 > +    {
 > +      data->gdbarch = get_frame_arch (this_frame);
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  SCM_RETURN_NEWSMOB (ephemeral_frame_smob_tag, data);
 > +}
 > +
 > +/* Ephemeral frames may only be accessed from Scheme within the dynamic
 > +   extent of the unwind callback.  */
 > +
 > +static int
 > +uwscm_ephemeral_frame_is_valid (SCM ephemeral_frame)
 > +{
 > +  return uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame != NULL;
 > +}
 > +
 > +/* Is this an ephemeral frame that is accessible from Scheme?  */
 > +
 > +static int
 > +uwscm_is_valid_ephemeral_frame (SCM obj)
 > +{
 > +  return uwscm_is_ephemeral_frame (obj) && uwscm_ephemeral_frame_is_valid (obj);
 > +}
 > +
 > +/* Is this an unwind info whose associated ephemeral frame is valid?  */
 > +
 > +static int
 > +uwscm_is_valid_unwind_info (SCM obj)
 > +{
 > +  return uwscm_is_unwind_info (obj)
 > +    && uwscm_ephemeral_frame_is_valid (uwscm_unwind_info_data (obj)->frame);
 > +}
 > +
 > +/* Called as the unwind callback finishes to invalidate the ephemeral
 > +   frame.  */
 > +
 > +static void
 > +uwscm_invalidate_ephemeral_frame (SCM ephemeral_frame)
 > +{
 > +  gdb_assert(uwscm_ephemeral_frame_is_valid (ephemeral_frame));

Space before (.

 > +  uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame = NULL;
 > +}
 > +
 > +/* Raise a Scheme exception if OBJ is not a valid ephemeral frame.  */
 > +
 > +static void
 > +uwscm_assert_valid_ephemeral_frame (SCM obj, const char *func_name, int pos)
 > +{
 > +  if (!uwscm_is_valid_ephemeral_frame (obj))
 > +    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
 > +					  "valid <gdb:ephemeral-frame>"));
 > +}
 > +
 > +/* Raise a Scheme exception if OBJ is not an unwind info object whose
 > +   associated ephemeral frame is valid.  */
 > +
 > +static void
 > +uwscm_assert_valid_unwind_info (SCM obj, const char *func_name, int pos)
 > +{
 > +  if (!uwscm_is_valid_unwind_info (obj))
 > +    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
 > +					  "valid <gdb:unwind-info>"));
 > +}
 > +
 > +/* Helper to convert a frame ID component to a CORE_ADDR.  */
 > +
 > +static CORE_ADDR
 > +uwscm_value_to_addr (SCM value, int arg)
 > +{
 > +  struct value *c_value;
 > +  CORE_ADDR ret;
 > +
 > +  if (!vlscm_is_value (value))
 > +    gdbscm_throw (gdbscm_make_type_error ("make-unwind-info",
 > +					  arg, value, "<gdb:value> object"));
 > +
 > +  c_value = vlscm_scm_to_value (value);
 > +
 > +  TRY
 > +    {
 > +      ret = value_as_address (c_value);
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  return ret;
 > +}
 > +
 > +/* (make-unwind-info ephemeral-frame sp [pc [special]])
 > +
 > +   Set the frame ID on this ephemeral frame.  */
 > +
 > +static SCM
 > +gdbscm_make_unwind_info (SCM ephemeral_frame, SCM sp, SCM pc, SCM special)
 > +{
 > +  struct uwscm_unwind_info *data;
 > +  struct frame_id frame_id;
 > +
 > +  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
 > +
 > +  if (SCM_UNBNDP (pc))
 > +    frame_id = frame_id_build_wild (uwscm_value_to_addr (sp, SCM_ARG2));
 > +  if (SCM_UNBNDP (special))
 > +    frame_id = frame_id_build (uwscm_value_to_addr (sp, SCM_ARG2),
 > +			       uwscm_value_to_addr (pc, SCM_ARG3));
 > +  else
 > +    frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
 > +				       uwscm_value_to_addr (pc, SCM_ARG3),
 > +				       uwscm_value_to_addr (special, SCM_ARG4));
 > +
 > +  data = scm_gc_malloc (sizeof (*data), unwind_info_smob_name);
 > +
 > +  data->frame = ephemeral_frame;
 > +  data->frame_id = frame_id;
 > +  data->registers = SCM_EOL;
 > +
 > +  SCM_RETURN_NEWSMOB (unwind_info_smob_tag, data);
 > +}
 > +
 > +/* Convert the string REGISTER_SCM to a register number for the given
 > +   architecture.  */
 > +
 > +static int
 > +uwscm_scm_to_regnum (SCM register_scm, struct gdbarch *gdbarch,
 > +		     const char *func_name, int arg)
 > +{
 > +  int regnum;
 > +

remove blank line

 > +  struct cleanup *cleanup;
 > +  char *register_str;
 > +
 > +  gdbscm_parse_function_args (func_name, arg, NULL,
 > +			      "s", register_scm, &register_str);
 > +  cleanup = make_cleanup (xfree, register_str);
 > +
 > +  TRY
 > +    {
 > +      regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
 > +					    strlen (register_str));
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      do_cleanups (cleanup);
 > +      GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  do_cleanups (cleanup);
 > +
 > +  if (regnum < 0)
 > +    gdbscm_out_of_range_error (func_name, arg,
 > +			       register_scm, _("unknown register"));
 > +
 > +  return regnum;
 > +}
 > +
 > +/* (ephemeral-frame-read-register <gdb:ephemeral-frame> string)
 > +      -> <gdb:value>
 > +
 > +   Sniffs a register value from an ephemeral frame.  */
 > +
 > +static SCM
 > +gdbscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
 > +{
 > +  struct uwscm_ephemeral_frame *data;
 > +  struct value *value = NULL;
 > +  int regnum;
 > +
 > +  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch, FUNC_NAME,
 > +				SCM_ARG2);
 > +
 > +  TRY
 > +    {
 > +      gdb_byte buffer[MAX_REGISTER_SIZE];

buffer is unused

 > +
 > +      value = get_frame_register_value (data->this_frame, regnum);
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  if (value == NULL)
 > +    gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, register_scm,
 > +			       _("Cannot read register from frame."));

The convention I've been following for error text here is no leading
capitalization and no trailing period.

 > +
 > +  return vlscm_scm_from_value (value);
 > +}
 > +
 > +/* (unwind-info-add-saved-register! unwind-info register value)
 > +
 > +   Records the saved value of a particular register in UNWIND_INFO's
 > +   corresponding ephemeral frame.  REGISTER_SCM names the register, as a
 > +   string, and VALUE_SCM is a <gdb:value>.  */
 > +
 > +static SCM
 > +gdbscm_unwind_info_add_saved_register_x (SCM unwind_info, SCM register_scm,
 > +					 SCM value_scm)
 > +{
 > +  struct uwscm_unwind_info *data;
 > +  struct uwscm_ephemeral_frame *frame_data;
 > +  struct value *value;
 > +  int regnum;
 > +  int value_size;
 > +
 > +  uwscm_assert_valid_unwind_info (unwind_info, FUNC_NAME, SCM_ARG1);
 > +  data = uwscm_unwind_info_data (unwind_info);
 > +  frame_data = uwscm_ephemeral_frame_data (data->frame);
 > +  regnum = uwscm_scm_to_regnum (register_scm, frame_data->gdbarch,
 > +				FUNC_NAME, SCM_ARG2);
 > +
 > +  if (!vlscm_is_value (value_scm))
 > +    gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG3,
 > +					  value_scm,
 > +					  "<gdb:value> object"));
 > +
 > +  value = vlscm_scm_to_value (value_scm);
 > +  value_size = TYPE_LENGTH (value_enclosing_type (value));
 > +
 > +  if (value_size != register_size (frame_data->gdbarch, regnum))
 > +    gdbscm_invalid_object_error ("unwind-info-add-saved-register!",
 > +				 SCM_ARG3, value_scm,
 > +				 "wrong sized value for register");
 > +
 > +  data->registers = scm_assv_set_x (data->registers,
 > +				    scm_from_int (regnum),
 > +				    value_scm);
 > +
 > +  return SCM_UNSPECIFIED;
 > +}
 > +
 > +/* frame_unwind.this_id method.  */
 > +
 > +static void
 > +uwscm_this_id (struct frame_info *this_frame, void **cache_ptr,
 > +	       struct frame_id *this_id)
 > +{
 > +  SCM unwind_info = PTR2SCM (*cache_ptr);
 > +  struct uwscm_unwind_info *data;
 > +
 > +  data = uwscm_unwind_info_data (unwind_info);
 > +  *this_id = data->frame_id;
 > +}
 > +
 > +/* frame_unwind.prev_register.  */
 > +
 > +static struct value *
 > +uwscm_prev_register (struct frame_info *this_frame, void **cache_ptr,
 > +		     int regnum)
 > +{
 > +  SCM unwind_info = PTR2SCM (*cache_ptr);
 > +  struct uwscm_unwind_info *data;
 > +  SCM value_scm;
 > +  struct value *c_value;
 > +  const gdb_byte *buf;
 > +
 > +  data = uwscm_unwind_info_data (unwind_info);
 > +  value_scm = scm_assv_ref (data->registers, scm_from_int (regnum));
 > +  if (gdbscm_is_false (value_scm))
 > +    return frame_unwind_got_optimized (this_frame, regnum);
 > +
 > +  c_value = vlscm_scm_to_value (value_scm);
 > +  buf = value_contents (c_value);
 > +
 > +  return frame_unwind_got_bytes (this_frame, regnum, buf);
 > +}
 > +
 > +/* Call unwind-frame on the ephemeral frame and check that the return
 > +   value is either a valid unwind info object, for a successful unwind, or
 > +   #f otherwise.  */
 > +
 > +static SCM
 > +do_call_unwind_frame (void *data)
 > +{
 > +  SCM ephemeral_frame = PTR2SCM (data);
 > +  SCM result;
 > +
 > +  result = scm_call_1 (scm_variable_ref (unwind_frame), ephemeral_frame);
 > +
 > +  if (!gdbscm_is_false (result))
 > +    uwscm_assert_valid_unwind_info (result, "unwind-frame", 0);
 > +
 > +  return result;
 > +}
 > +
 > +/* Sniffer implementation.  */
 > +
 > +static int
 > +uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
 > +	       void **cache_ptr)
 > +{
 > +  SCM ephemeral_frame;
 > +  SCM result;
 > +
 > +  /* Note that it's possible to have loaded the Guile interface, but not yet
 > +     loaded (gdb frame-unwinders), so checking gdb_scheme_initialized is not
 > +     sufficient.  */
 > +  if (!gdbscm_frame_unwinders_loaded)
 > +    return 0;
 > +
 > +  ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
 > +  /* Recurse through gdbscm_call_guile so that we can just throw
 > +     exceptions on error.  */
 > +  result = gdbscm_call_guile (do_call_unwind_frame,
 > +			      SCM2PTR (ephemeral_frame),
 > +			      gdbscm_memory_error_p);
 > +
 > +  /* Drop the reference to this_frame, so that future use of
 > +     ephemeral_frame from Scheme will signal an error.  */
 > +  uwscm_invalidate_ephemeral_frame (ephemeral_frame);
 > +
 > +  if (gdbscm_is_exception (result))
 > +    {
 > +      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
 > +      return 0;
 > +    }
 > +
 > +  if (gdbscm_is_false (result))
 > +    return 0;
 > +
 > +  *cache_ptr = SCM2PTR (scm_gc_protect_object (result));
 > +
 > +  return 1;
 > +}
 > +
 > +/* Frame cache release shim.  */
 > +
 > +static void
 > +uwscm_dealloc_cache (struct frame_info *this_frame, void *cache)
 > +{
 > +  scm_gc_unprotect_object (PTR2SCM (cache));
 > +}
 > +
 > +struct uwscm_gdbarch_data_type
 > +{
 > +  /* Has the unwinder shim been prepended? */
 > +  int unwinder_registered;
 > +};
 > +
 > +static void *
 > +uwscm_gdbarch_data_init (struct gdbarch *gdbarch)
 > +{
 > +  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct uwscm_gdbarch_data_type);
 > +}
 > +
 > +/* New inferior architecture callback: register the Guile sniffers
 > +   intermediary.  */
 > +
 > +static void
 > +uwscm_on_new_gdbarch (struct gdbarch *newarch)
 > +{
 > +  struct uwscm_gdbarch_data_type *data =
 > +      gdbarch_data (newarch, uwscm_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 = uwscm_this_id;
 > +      unwinder->prev_register = uwscm_prev_register;
 > +      unwinder->unwind_data = (void *) newarch;
 > +      unwinder->sniffer = uwscm_sniffer;
 > +      unwinder->dealloc_cache = uwscm_dealloc_cache;
 > +      frame_unwind_prepend_unwinder (newarch, unwinder);
 > +      data->unwinder_registered = 1;
 > +    }
 > +}
 > +
 > +static const scheme_function unwind_functions[] =
 > +{
 > +  { "make-unwind-info", 2, 2, 0, gdbscm_make_unwind_info,
 > +    "\
 > +Make an unwind info object for an ephemeral frame.\n\
 > +\n\
 > +This function takes two required arguments and two optional arguments.\n\
 > +The first argument is the ephemeral frame that is being unwound, as a\n\
 > +<gdb:ephemeral-frame>.  The rest of the arguments are used to build an\n\
 > +identifier for the frame.\n\
 > +\n\
 > +A frame identifier is a unique name for a frame that remains valid as\n\
 > +long as the frame itself is valid.  Usually the frame identifier is\n\
 > +built from from the frame's stack address and code address.  The stack\n\
 > +address, passed as the second argument, should normally be a pointer to\n\
 > +the new end of the stack when the function was called, as a GDB value.\n\
 > +Similarly the code address, the third argument, should be given as the\n\
 > +address of the entry point of the function.\n\
 > +\n\
 > +For most architectures, it is sufficient to just specify just the stack\n\
 > +and code pointers.  Some architectures have another stack or some other\n\
 > +frame state store, like ia64, and they need an additional address, which\n\
 > +may be passed as the fourth argument.\n\
 > +\n\
 > +It is possible to create a frame ID with just a stack address, but it's\n\
 > +better to specify a code address as well if possible."},
 > +
 > +  { "ephemeral-frame-read-register", 2, 0, 0,
 > +    gdbscm_ephemeral_frame_read_register,
 > +    "\
 > +Return the value of a register in an ephemeral frame.\n\
 > +\n\
 > +  Arguments: <gdb:ephemeral-frame> string" },
 > +
 > +  { "unwind-info-add-saved-register!", 3, 0, 0,
 > +    gdbscm_unwind_info_add_saved_register_x,
 > +    "\
 > +Record the saved value of a register for an ephemeral frame.\n\
 > +\n\
 > +After reading an ephemeral frame's registers and determining that it\n\
 > +can handle the frame, an unwinder will create an unwind info object and\n\
 > +call this function to record saved registers for the frame.  The values\n\
 > +of the saved registers logically belong to the frame that is older than\n\
 > +the ephemeral frame being unwound, not to the ephemeral frame itself.\n\
 > +\n\
 > +The first argument should be a <gdb:unwind-info> object.  The second\n\
 > +names a register, and should be a string, for example \"rip\".  The\n\
 > +third argument is the value, as a GDB value.  By default, any register\n\
 > +whose value is not added to the unwind info's saved register set\n\
 > +will be marked as \"not saved\" in the outer frame." },
 > +
 > +  END_FUNCTIONS
 > +};
 > +
 > +/* Called by lib/gdb/frame-unwinders.scm.  */
 > +
 > +static void
 > +gdbscm_load_frame_unwinders (void *unused)
 > +{
 > +  if (gdbscm_frame_unwinders_loaded)
 > +    return;
 > +
 > +  gdbscm_frame_unwinders_loaded = 1;
 > +
 > +  gdbscm_define_functions (unwind_functions, 0);
 > +
 > +  unwind_frame = scm_c_lookup ("unwind-frame");
 > +}
 > +
 > +/* Initialize the opaque ephemeral frame and unwind info types and
 > +   register gdbscm_load_frame_unwinders for calling by (gdb
 > +   frame-unwinders).  */
 > +
 > +void
 > +gdbscm_initialize_frame_unwinders (void)
 > +{
 > +  ephemeral_frame_smob_tag =
 > +    gdbscm_make_smob_type (ephemeral_frame_smob_name, 0);
 > +  unwind_info_smob_tag =
 > +    gdbscm_make_smob_type (unwind_info_smob_name, 0);
 > +
 > +  uwscm_gdbarch_data =
 > +    gdbarch_data_register_post_init (uwscm_gdbarch_data_init);
 > +  observer_attach_architecture_changed (uwscm_on_new_gdbarch);
 > +
 > +  scm_c_register_extension ("gdb", "gdbscm_load_frame_unwinders",
 > +                            gdbscm_load_frame_unwinders, NULL);
 > +}
 > diff --git a/gdb/guile/scm-symbol.c b/gdb/guile/scm-symbol.c
 > index 99ef928..1ad6d0c 100644
 > --- a/gdb/guile/scm-symbol.c
 > +++ b/gdb/guile/scm-symbol.c
 > @@ -605,7 +605,9 @@ gdbscm_lookup_symbol (SCM name_scm, SCM rest)
 >  
 >        TRY
 >  	{
 > -	  selected_frame = get_selected_frame (_("no frame selected"));
 > +	  selected_frame = get_selected_frame_if_set ();
 > +	  if (selected_frame == NULL)
 > +	    selected_frame = get_current_frame ();
 >  	  block = get_frame_block (selected_frame, NULL);
 >  	}
 >        CATCH (except, RETURN_MASK_ALL)
 > diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
 > index 9e94ddf..52a60f8 100644
 > --- a/gdb/testsuite/ChangeLog
 > +++ b/gdb/testsuite/ChangeLog
 > @@ -1,3 +1,10 @@
 > +2015-03-06  Andy Wingo  <wingo@igalia.com>
 > +
 > +	* gdb.guile/scm-frame-unwinder.exp:
 > +	* gdb.guile/scm-frame-unwinder.c:
 > +	* gdb.guile/scm-frame-unwinder-gdb.scm.in:
 > +	* gdb.guile/scm-frame-unwinder.scm: Add unwinder tests.

Convention is to write "New file." for each new file.

 > +
 >  2015-03-05  Andy Wingo  <wingo@igalia.com>
 >  
 >  	* gdb.guile/amd64-scm-frame-filter-invalidarg.S:
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
 > new file mode 100644
 > index 0000000..28f5936
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
 > @@ -0,0 +1,32 @@
 > +;; Copyright (C) 2015 Free Software Foundation, Inc.
 > +;;
 > +;; This program is free software; you can redistribute it and/or modify
 > +;; it under the terms of the GNU General Public License as published by
 > +;; the Free Software Foundation; either version 3 of the License, or
 > +;; (at your option) any later version.
 > +;;
 > +;; This program is distributed in the hope that it will be useful,
 > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +;; GNU General Public License for more details.
 > +;;
 > +;; You should have received a copy of the GNU General Public License
 > +;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +;; This file is part of the GDB test-suite.  It tests Guile-based frame
 > +;; filters.
 > +
 > +(use-modules (gdb)
 > +             (gdb frame-unwinders))
 > +
 > +(define* (install-unwinders! #:optional (objfile (current-objfile)))
 > +  (define (unwind-frame frame)
 > +    #f)
 > +  (define (add-unwinder! name priority)
 > +    (register-frame-unwinder! (make-frame-unwinder name unwind-frame
 > +                                                   #:priority priority)
 > +                              #:scope objfile))
 > +  (add-unwinder! "Auto-loaded dummy" 100)
 > +  (add-unwinder! "Auto-loaded dummy 2" 200))
 > +
 > +(install-unwinders!)
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.c b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
 > new file mode 100644
 > index 0000000..82db341
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
 > @@ -0,0 +1,30 @@

The rule is all test files get a copyright header,
regardless of how trivial the file is.

 > +int f2 (int a)

And must follow gdb coding conventions (unless the test explicitly
needs to do something differently).
->
int
f2 (int a)

 > +{
 > +  return ++a;
 > +}
 > +
 > +int f1 (int a, int b)
 > +{
 > +  return f2(a) + b;
 > +}
 > +
 > +int block (void)
 > +{
 > +  int i = 99;
 > +  {
 > +    double i = 1.1;
 > +    double f = 2.2;
 > +    {
 > +      const char *i = "stuff";
 > +      const char *f = "foo";
 > +      const char *b = "bar";
 > +      return 0; /* Block break here.  */
 > +    }
 > +  }
 > +}
 > +
 > +int main (int argc, char *argv[])
 > +{
 > +  block ();
 > +  return f1 (1, 2);
 > +}
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
 > new file mode 100644
 > index 0000000..e22a8d9
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
 > @@ -0,0 +1,84 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +# This file is part of the GDB testsuite.  It tests Guile-based
 > +# frame-filters.
 > +
 > +load_lib gdb-guile.exp
 > +
 > +standard_testfile
 > +
 > +if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
 > +    return -1
 > +}
 > +
 > +# Skip all tests if Guile scripting is not enabled.
 > +if { [skip_guile_tests] } { continue }
 > +
 > +# Make the -gdb.scm script available to gdb, it is automagically loaded by gdb.
 > +# Care is taken to put it in the same directory as the binary so that
 > +# gdb will find it.
 > +set remote_obj_guile_file \
 > +    [remote_download \
 > +	 host ${srcdir}/${subdir}/${testfile}-gdb.scm.in \
 > +	 [standard_output_file ${testfile}-gdb.scm]]
 > +
 > +gdb_reinitialize_dir $srcdir/$subdir
 > +gdb_test_no_output "set auto-load safe-path ${remote_obj_guile_file}" \
 > +    "set auto-load safe-path"
 > +gdb_load ${binfile}
 > +# Verify gdb loaded the script.
 > +gdb_test "info auto-load guile-scripts" "Yes.*/${testfile}-gdb.scm.*" \
 > +    "Test auto-load had loaded guile scripts"
 > +
 > +if ![runto_main] then {
 > +    perror "couldn't run to breakpoint"

IIRC use of perror if runto_main fails is deprecated,
use fail instead.

I didn't see anything in testsuite/README or
https://sourceware.org/gdb/wiki/Internals%20GDB-Testsuite-Coding-Standards
We should get this written down.

 > +    return
 > +}
 > +
 > +# Load global frame-unwinders
 > +set remote_guile_file [gdb_remote_download host \
 > +			    ${srcdir}/${subdir}/${testfile}.scm]
 > +gdb_scm_load_file ${remote_guile_file}
 > +
 > +# Test query
 > +gdb_test "guile (all-frame-unwinders)" \
 > +    ".*Dummy.*Auto-loaded dummy 2.*Synthetic.*Auto-loaded dummy.*" \

Trailing .* is to be avoided if possible.
Can it be avoided here, and below?

 > +    "all frame unwinders"
 > +gdb_test "guile (map frame-unwinder-priority (all-frame-unwinders))" \
 > +    ".*300 200 150 100.*" \
 > +    "all frame unwinder priorities"
 > +gdb_test "guile (map frame-unwinder-enabled? (all-frame-unwinders))" \
 > +    ".*#t #t #f #t.*" \
 > +    "all frame unwinders enabled?"
 > +
 > +gdb_test_no_output "guile (disable-frame-unwinder! \"Dummy\")" \
 > +    "disable dummy"
 > +gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
 > +    ".*#f.*" \
 > +    "dummy not enabled"
 > +gdb_test_no_output "guile (enable-frame-unwinder! \"Dummy\")" \
 > +    "re-enable dummy"
 > +gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
 > +    ".*#t.*" \
 > +    "dummy re-enabled"
 > +
 > +gdb_test_no_output "guile (enable-frame-unwinder! \"Synthetic\")" \
 > +    "enable synthetic unwinder"
 > +
 > +gdb_breakpoint "f2"
 > +gdb_continue_to_breakpoint "breakpoint at f2"
 > +
 > +gdb_test "bt 10" " f2 .*cabba9e5 .*cabba9e5 .*"

IWBN to test that unwinding handles unwinder enabling.
E.g., try a backtrace with and without an unwinder enabled.

This brings up an issue I haven't seen discussed before.
Apologies if I missed it.
Should enabling/disabling/registering/removing an unwinder
invalidate the frame cache? Seems like it.

 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
 > new file mode 100644
 > index 0000000..e299d49
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
 > @@ -0,0 +1,42 @@
 > +;; Copyright (C) 2015 Free Software Foundation, Inc.
 > +;;
 > +;; This program is free software; you can redistribute it and/or modify
 > +;; it under the terms of the GNU General Public License as published by
 > +;; the Free Software Foundation; either version 3 of the License, or
 > +;; (at your option) any later version.
 > +;;
 > +;; This program is distributed in the hope that it will be useful,
 > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +;; GNU General Public License for more details.
 > +;;
 > +;; You should have received a copy of the GNU General Public License
 > +;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +;; This file is part of the GDB test-suite.  It tests Guile-based frame
 > +;; filters.
 > +
 > +(use-modules (gdb)
 > +             (gdb frame-unwinders))
 > +
 > +(define (dummy-unwinder frame)
 > +  #f)
 > +
 > +(register-frame-unwinder!
 > + (make-frame-unwinder "Dummy" dummy-unwinder #:priority 300))
 > +
 > +
 > +(define (synthetic-unwinder frame)
 > +  ;; Increment the stack pointer, set IP to 0xdeadbeef
 > +  (let* ((this-pc (ephemeral-frame-read-register frame "rip"))
 > +         (this-sp (ephemeral-frame-read-register frame "rsp"))

rip,rsp are architecture-specific names but the .exp file
doesn't check for amd64. IWBN if the test wasn't architecture-specific.
Will "pc" and "sp" work here?

 > +         (prev-pc (value-cast (make-value #xcabba9e5) (value-type this-pc)))
 > +         (prev-sp (value-add this-sp 32))
 > +         (unwind-info (make-unwind-info frame this-sp this-pc)))
 > +    (unwind-info-add-saved-register! unwind-info "rip" prev-pc)
 > +    (unwind-info-add-saved-register! unwind-info "rsp" prev-sp)
 > +    unwind-info))
 > +
 > +(register-frame-unwinder!
 > + (make-frame-unwinder "Synthetic" synthetic-unwinder #:priority 150
 > +                      #:enabled? #f))
 > -- 
 > 2.1.4
 >
Andy Wingo April 9, 2015, 8:02 p.m. UTC | #3
Hi!

I have a new version of this patch that I'll post as a separate thread,
addressing all the issues except a couple that I comment below.

On Tue 24 Mar 2015 23:07, Doug Evans <dje@google.com> writes:

>  > +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
>  > +Set the saved value of a register in a ephemeral frame.
>  > +
>  > +@var{register} names a register, and should be a string, for example
>  > +@samp{rip}.  @var{value} is the register value, as a @value{GDBN}
>
> To be more universal let's replace "rip" here with "pc".

I kept in "rip" because user regs like "pc" don't work on pending
frames, and I didn't want to mislead the reader that user regs would
work.  (OK, "pc" is probably a user reg only on some architectures.)
WDYT?  Still change to "pc"?

>  > +Any register whose value is not recorded as saved via
>  > +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
>  > +in the outer frame.
>
> "outer frame" here is confusing.
> I'd have thought this would read "``not saved'' in the frame".
> [with the frame?]

Let's say you are unwinding pending frame 1, which will be frame-1.  You
add some saved registers to it.  Those registers will become the values
of (frame-read-register (frame-older frame-1) "foo").  If a register
isn't added to the set, then the frame-read-register on the older frame
will return "not saved".  Is that clear?  It's a bit confusing but I
think it reflects the problem space...

>  > +(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
>
> In addition to removing priority (for now), let's remove enabled? as
> a parameter here.  The user can disable the unwinder before registering it.

I kept it in, as noted in the patch set header of the next mail, because
that's more like frame filters which indeed have a priority and also
have an enabled flag.  Please let me know again if this bugs you and
I'll take them both out.

>  > +(define* (register-frame-unwinder! unwinder #:key scope)
>
> Guile question: Does scope default to #f?

Yep.

>  > +gdb_test_no_output "guile (enable-frame-unwinder! \"Synthetic\")" \
>  > +    "enable synthetic unwinder"
>  > +
>  > +gdb_breakpoint "f2"
>  > +gdb_continue_to_breakpoint "breakpoint at f2"
>  > +
>  > +gdb_test "bt 10" " f2 .*cabba9e5 .*cabba9e5 .*"
>
> IWBN to test that unwinding handles unwinder enabling.
> E.g., try a backtrace with and without an unwinder enabled.
>
> This brings up an issue I haven't seen discussed before.
> Apologies if I missed it.
> Should enabling/disabling/registering/removing an unwinder
> invalidate the frame cache? Seems like it.

I can't do it here for the reason you mention.  I have added something
that does a bt from before the enable!, and also doesn't have a trailing
.*.  A followup perhaps?

>  > +(define (synthetic-unwinder frame)
>  > +  ;; Increment the stack pointer, set IP to 0xdeadbeef
>  > +  (let* ((this-pc (ephemeral-frame-read-register frame "rip"))
>  > +         (this-sp (ephemeral-frame-read-register frame "rsp"))
>
> rip,rsp are architecture-specific names but the .exp file
> doesn't check for amd64. IWBN if the test wasn't architecture-specific.
> Will "pc" and "sp" work here?

They won't currently.  Should I look into making user regs work or
somehow mark this test as architecture-specific?

Thanks for your time, Doug!

Andy
Doug Evans April 27, 2015, 5:01 a.m. UTC | #4
Andy Wingo <wingo@igalia.com> writes:
> Hi!

Hi.

When we last left our story ...

> I have a new version of this patch that I'll post as a separate thread,
> addressing all the issues except a couple that I comment below.
>
> On Tue 24 Mar 2015 23:07, Doug Evans <dje@google.com> writes:
>
>>  > +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
>>  > +Set the saved value of a register in a ephemeral frame.
>>  > +
>>  > +@var{register} names a register, and should be a string, for example
>>  > +@samp{rip}.  @var{value} is the register value, as a @value{GDBN}
>>
>> To be more universal let's replace "rip" here with "pc".
>
> I kept in "rip" because user regs like "pc" don't work on pending
> frames, and I didn't want to mislead the reader that user regs would
> work.  (OK, "pc" is probably a user reg only on some architectures.)
> WDYT?  Still change to "pc"?

"rip" is amd64-specific, and we don't really distinguish "rip" from "pc".
They're different, technically (one's a "real" register, ref: amd64-tdep.c,
and the other is a "user" reg - ref: user-regs.c), but if they're not
equivalent I think we've got problems, and not just in the unwinders.

I guess I need to understand better why "rip" works whereas "pc" doesn't work.

>>  > +Any register whose value is not recorded as saved via
>>  > +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
>>  > +in the outer frame.
>>
>> "outer frame" here is confusing.
>> I'd have thought this would read "``not saved'' in the frame".
>> [with the frame?]
>
> Let's say you are unwinding pending frame 1, which will be frame-1.  You
> add some saved registers to it.  Those registers will become the values
> of (frame-read-register (frame-older frame-1) "foo").  If a register
> isn't added to the set, then the frame-read-register on the older frame
> will return "not saved".  Is that clear?  It's a bit confusing but I
> think it reflects the problem space...

It's the "outer" that's confusing, not the function's purpose.
I often come into a manual at the point that describes
the function I'm interested in. If I read the introductory text I can
infer that "outer" here equates to the pending frame we are building,
but when I imagine reading this text "standalone" (so to speak) I wonder
if the reader might think "outer to the pending frame being constructed?".
I dunno.

I think this would be clearer:
"... will be marked as ``not saved'' in the pending frame being unwound."
Or one could say both:
"... will be marked as ``not saved'' in the outer frame (the pending
frame being unwound)."
Or some such.

>>  > +(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
>>
>> In addition to removing priority (for now), let's remove enabled? as
>> a parameter here.  The user can disable the unwinder before registering it.
>
> I kept it in, as noted in the patch set header of the next mail, because
> that's more like frame filters which indeed have a priority and also
> have an enabled flag.  Please let me know again if this bugs you and
> I'll take them both out.

But there isn't a real reason for having it there either.
But whatever, this isn't important enough.

Re: priority: Ok.

>>  > +(define* (register-frame-unwinder! unwinder #:key scope)
>>
>> Guile question: Does scope default to #f?
>
> Yep.
>
>>  > +gdb_test_no_output "guile (enable-frame-unwinder! \"Synthetic\")" \
>>  > +    "enable synthetic unwinder"
>>  > +
>>  > +gdb_breakpoint "f2"
>>  > +gdb_continue_to_breakpoint "breakpoint at f2"
>>  > +
>>  > +gdb_test "bt 10" " f2 .*cabba9e5 .*cabba9e5 .*"
>>
>> IWBN to test that unwinding handles unwinder enabling.
>> E.g., try a backtrace with and without an unwinder enabled.
>>
>> This brings up an issue I haven't seen discussed before.
>> Apologies if I missed it.
>> Should enabling/disabling/registering/removing an unwinder
>> invalidate the frame cache? Seems like it.
>
> I can't do it here for the reason you mention.  I have added something
> that does a bt from before the enable!, and also doesn't have a trailing
> .*.  A followup perhaps?

I guess I'd have to see what happens if I do a backtrace with an
unwinder enabled, then disable it, and then do another backtrace
and see if the result of the second backtrace is identical to a
backtrace where the unwinder was originally disabled.
If your patch has a test for this, great.

And if the test works without invalidating the frame cache
I'd be curious to know why.
[Easy enough to check on my own of course, and will if I get time.]

>>  > +(define (synthetic-unwinder frame)
>>  > +  ;; Increment the stack pointer, set IP to 0xdeadbeef
>>  > +  (let* ((this-pc (ephemeral-frame-read-register frame "rip"))
>>  > +         (this-sp (ephemeral-frame-read-register frame "rsp"))
>>
>> rip,rsp are architecture-specific names but the .exp file
>> doesn't check for amd64. IWBN if the test wasn't architecture-specific.
>> Will "pc" and "sp" work here?
>
> They won't currently.  Should I look into making user regs work or
> somehow mark this test as architecture-specific?

While fetching "pc" does take a different code path than "rip", it
does end up getting mapped to "rip". Plus if they're not equivalent
using this feature across different architectures
is going to be a pain. Users generally just always type "pc" without
having to care what the spelling is of the real h/w register.
Why doesn't pc,sp work here?
I can imagine it not working on arches that don't set the pc,sp regnums,
but that's just a guess.
diff mbox

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 65f2712..bd49503 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,34 @@ 
 2015-03-05  Andy Wingo  <wingo@igalia.com>
 
+	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
+	is no selected frame and no block is selected; instead, fall back
+	to the current frame.
+	* guile/scm-frame-unwinder.c: New file.
+	* guile/lib/gdb/frame-unwinders.scm: New file.
+	* guile/guile.c (initialize_gdb_module): Call
+	gdbscm_initialize_frame_unwinders.
+	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
+	declaration.
+	* frame.c (get_prev_frame): Detect recursive unwinds, returning
+	NULL in that case.
+	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
+	(frame_unwind_is_unwinding): New declaration.
+	* frame-unwind.c (is_unwinding): New file-local variable.
+	(set_is_unwinding, unset_is_unwinding): New file-local helpers.
+	(frame_unwind_is_unwinding): New exported predicate.
+	(frame_unwind_try_handler): Arrange for
+	frame_unwind_is_unwinding to return true when unwinding the
+	innermost frame.
+	(frame_unwind_got_bytes): Make buf arg const.
+	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
+	frame-unwinders.scm.
+	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
+	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
+	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
+	(scm-frame-unwinder.o): New target.
+
+2015-03-05  Andy Wingo  <wingo@igalia.com>
+
 	* guile/scm-frame-filter.c:
 	* guile/lib/gdb/frame-filters.scm: New files.
 	* guile/guile.c (guile_extension_ops): Add the Guile frame
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index fed8035..08e08db 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -315,6 +315,7 @@  SUBDIR_GUILE_OBS = \
 	scm-exception.o \
 	scm-frame.o \
 	scm-frame-filter.o \
+	scm-frame-unwinder.o \
 	scm-gsmob.o \
 	scm-iterator.o \
 	scm-lazy-string.o \
@@ -342,6 +343,7 @@  SUBDIR_GUILE_SRCS = \
 	guile/scm-exception.c \
 	guile/scm-frame.c \
 	guile/scm-frame-filter.c \
+	guile/scm-frame-unwinder.c \
 	guile/scm-gsmob.c \
 	guile/scm-iterator.c \
 	guile/scm-lazy-string.c \
@@ -2416,6 +2418,10 @@  scm-frame-filter.o: $(srcdir)/guile/scm-frame-filter.c
 	$(COMPILE) $(srcdir)/guile/scm-frame-filter.c
 	$(POSTCOMPILE)
 
+scm-frame-unwinder.o: $(srcdir)/guile/scm-frame-unwinder.c
+	$(COMPILE) $(srcdir)/guile/scm-frame-unwinder.c
+	$(POSTCOMPILE)
+
 scm-gsmob.o: $(srcdir)/guile/scm-gsmob.c
 	$(COMPILE) $(srcdir)/guile/scm-gsmob.c
 	$(POSTCOMPILE)
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index b4916b0..14d3653 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -88,6 +88,7 @@  GUILE_SOURCE_FILES = \
 	gdb/boot.scm \
 	gdb/experimental.scm \
 	gdb/frame-filters.scm \
+	gdb/frame-unwinders.scm \
 	gdb/init.scm \
 	gdb/iterator.scm \
 	gdb/printing.scm \
@@ -100,6 +101,7 @@  GUILE_NO_UNBOUND_WARNING_COMPILED_FILES = \
 GUILE_COMPILED_FILES = \
 	gdb/experimental.go \
 	gdb/frame-filters.go \
+	gdb/frame-unwinders.go \
 	gdb/iterator.go \
 	gdb/printing.go \
 	gdb/support.go \
diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
index 162ace7..3be1926 100644
--- a/gdb/doc/ChangeLog
+++ b/gdb/doc/ChangeLog
@@ -1,3 +1,7 @@ 
+2015-03-06  Andy Wingo  <wingo@igalia.com>
+
+	* guile.texi (Guile Frame Unwinder API): New section.
+
 2015-02-15  Andy Wingo  <wingo@igalia.com>
 
 	* guile.texi (Guile Frame Filter API)
diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
index 3045d90..fdcbca3 100644
--- a/gdb/doc/guile.texi
+++ b/gdb/doc/guile.texi
@@ -143,6 +143,7 @@  from the Guile interactive prompt.
 * Writing a Guile Pretty-Printer:: Writing a pretty-printer
 * Guile Frame Filter API::   Filtering frames.
 * Writing a Frame Filter in Guile:: Writing a frame filter.
+* Guile Frame Unwinder API:: Programmatically unwinding stack frames
 * Commands In Guile::        Implementing new commands in Guile
 * Parameters In Guile::      Adding new @value{GDBN} parameters
 * Progspaces In Guile::      Program spaces
@@ -2137,6 +2138,210 @@  also possible to do the job of an decorator with a filter.  Still,
 avoiding the stream interfaces can often be a good reason to use the
 simpler decorator layer.
 
+@node Guile Frame Unwinder API
+@subsubsection Unwinding Frames in Guile
+@cindex frame unwinder api, guile
+
+In @value{GDBN} terminology, ``unwinding'' is the process of finding
+an older (outer) frame on the stack.  Unwinders form the core of
+backtrace computation in @value{GDBN}.  @value{GDBN} comes with
+unwinders for each target architecture that it supports, and these
+usually suffice to unwind the stack.  However, some target programs
+can have non-standard frame layouts that cannot be unwound by the
+standard unwinders.  This is often the case when working with
+just-in-time compilation environments, for example in JavaScript
+implementations.  In such cases, users can define custom code in Guile
+to programmatically unwind the problematic stack frames.
+
+Before getting into the API, we should discuss how unwinders work in
+@value{GDBN}.
+
+As an example, consider a stack in which we have already computed
+frame 0 and we want to compute frame 1.  We say that frame 0 is the
+``inner'' frame, and frame 1 will be the ``outer'' frame.
+
+Unwinding starts with a model of the state of all registers in an
+inner, already unwound frame.  In our case, we start with frame 0.
+@value{GDBN} then constructs a ephemeral frame object for the outer
+frame that is being built (frame 1) and links it to the inner frame
+(frame 0).  @value{GDBN} then goes through its list of registered
+unwinders, searching for one that knows how to unwind the frame.  When
+it finds one, @value{GDBN} will ask the unwinder to compute a frame
+identifier for the outer frame.  Once the unwinder has done so, the
+frame is marked as ``valid'' and can be accessed using the normal
+frame API.
+
+A frame identifier (frame ID) consists of code and data pointers
+associated with a frame which will remain valid as long as the frame
+is still alive.  Usually a frame ID is a pair of the code and stack
+pointers as they were when control entered the function associated
+with the frame, though as described below there are other ways to
+build a frame ID@.  However as you can see, computing the frame ID
+requires some input from the unwinder to determine the start code
+address (PC) and the frame pointer (FP), especially on platforms that
+don't dedicate a register to the FP.
+
+(Given this description, you might wonder how the frame ID for the
+innermost frame (frame 0) is unwound, given that unwinding requires an
+inner frame.  The answer is that internally, @value{GDBN} always has a
+``sentinel'' frame that is inner to the innermost frame, and which has
+a pre-computed unwinder that just returns the registers as they are,
+without unwinding.)
+
+The Guile frame unwinder API loosely follows this protocol as
+described above.  Guile will build a special ``ephemeral frame
+object'' corresponding the frame being unwound (in our example, frame
+1).  It allows the user to read registers from that ephemeral frame,
+which in reality are unwound from the already-existing frame 0.  If
+the unwinder decides that it can handle the frame in question, it then
+creates and returns an ``unwind info'' object for that frame.  The
+unwind info object contains a frame ID for the ephemeral frame.  It
+also records the values of any registers saved in the frame, for use
+when unwinding its outer frame (frame 2).
+
+Frame unwinder objects are managed in Guile much in the same way as
+frame filters.  Indeed, users will often want to implement both frame
+unwinders and frame filters: unwinders will compute the correct
+backtrace and register state, and filters can fill in function names,
+line numbers, and the like.  @xref{Guile Frame Filter API}, for more
+on frame filters.
+
+As with frame filters, there can be multiple frame unwinders
+registered with @value{GDBN}, and each one may be individually enabled
+or disabled at will.  The filters will be tried in priority order,
+from highest to lowest priority, and the first one that sets the frame
+ID will take responsibility for the frame.
+
+To use frame unwinders, first load the @code{(gdb frame-unwinders)} module
+to have access to the procedures that manipulate frame unwinders:
+
+@example
+(use-modules (gdb frame-unwinders))
+@end example
+
+@deffn {Scheme Procedure} make-frame-unwinder name procedure @
+       @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]}
+Make a new frame unwinder.
+
+The unwinder will be identified by the string @var{name}.
+@var{procedure} should be a function of one argument, taking an
+ephemeral frame object.  If the unwinder procedure decides to handle
+the frame, it should return an unwind info object.  Otherwise the
+unwinder returns @code{#f}, @value{GDBN} will continue to search its
+list for an unwinder.
+
+The unwinder will be initially enabled, unless the keyword argument
+@code{#:enabled? #f} is given.  Even if the unwinder is marked as
+enabled, it will need to be registered with @value{GDBN} via
+@code{register-frame-unwinder!} in order to take effect.  When
+registered, the unwinder will be inserted into the list of registered
+unwinders with the given @var{priority}, which should be a number, and
+which defaults to 20 if not given.  Higher priority unwinders will be
+tried before lower-priority unwinders.
+@end deffn
+
+@deffn {Scheme Procedure} all-frame-unwinders
+Return a list of all frame unwinders.
+@end deffn
+
+@deffn {Scheme Procedure} register-frame-unwinder! unwinder @
+       @r{[}#:scope scope@r{]}
+Register the frame unwinder @var{unwinder} with @value{GDBN}.
+
+By default, the scope of the unwinder is global, meaning that it is
+associated with all objfiles and progspaces.  Pass an objfile or a
+progspace as the @code{#:scope} keyword argument to instead scope the
+unwinder into a specific objfile or progspace, respectively.
+
+The unwinder's name will be checked for uniqueness within its registered
+scope.
+@end deffn
+
+@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
+@deffnx {Scheme Procedure} enable-frame-unwinder! unwinder
+@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
+Mark a frame unwinder as enabled, if @var{enabled?} is true, or
+as disabled otherwise.
+
+@var{unwinder} can either be a frame unwinder object, or it can be a
+string naming an unwinder in the current scope.  If no such unwinder
+is found, an error is signalled.
+
+@code{enable-frame-unwinder!} and @code{disable-frame-unwinder} are simple
+wrappers around @code{set-frame-unwinder-enabled!} which pass @code{#t}
+or @code{#f} as the @var{enabled?} argument, respectively.
+@end deffn
+
+@deffn {Scheme Procedure} frame-unwinder-name unwinder
+@deffnx {Scheme Procedure} frame-unwinder-enabled? unwinder
+@deffnx {Scheme Procedure} frame-unwinder-registered? unwinder
+@deffnx {Scheme Procedure} frame-unwinder-priority unwinder
+@deffnx {Scheme Procedure} frame-unwinder-procedure unwinder
+@deffnx {Scheme Procedure} frame-unwinder-scope unwinder
+Accessors for a frame unwinder object's fields.  The
+@code{registered?}  field indicates whether a unwinder has been added
+to @value{GDBN} or not.  @code{scope} is the objfile or progspace in
+which the unwinder was registered, or @code{#f} otherwise.
+@end deffn
+
+Frame unwinders operate on ``ephemeral frames''.  Ephemeral frames are
+valid only while they are being unwound; any access to an ephemeral
+frame outside the extent of their unwind operation will signal an
+error.  Currently ephemeral frames can only be used in two limited
+ways: to read registers from the frame, and to construct an unwind
+info object for a successful unwinder return.
+
+@deffn {Scheme Procedure} ephemeral-frame-read-register frame register
+Return the value of a register in the ephemeral frame @var{frame}.
+@var{register} should be given as a string.
+@end deffn
+
+If an unwinder successfully unwinds a frame, it should return an
+unwind info object created via the @code{make-unwind-info} procedure.
+An unwind info object specifies the frame ID for its associated
+ephemeral frame, and also records values of registers that are saved
+within the frame.
+
+@deffn {Scheme Procedure} make-unwind-info frame fp [pc [special]]
+Make an unwind info object for the ephemeral frame @var{frame}.
+
+@var{fp}, @var{pc}, and @var{special} are used to build an identifier
+(frame ID) for the frame.  A frame ID is a unique name for a frame
+that remains valid as long as the frame itself is valid.  Usually the
+frame ID is built from from the frame's stack address and code
+address.  The stack address @var{fp} should normally be a pointer to
+the new end of the stack when the function was called, as a
+@value{GDBN} value.  Similarly the code address @var{pc} should be
+given as the address of the entry point of the function.
+
+For most architectures, it is sufficient to just specify just the
+stack and code pointers @var{fp} and @var{pc}.  Some architectures
+have another stack or some other frame state store, like ia64.  For
+these platforms the frame ID needs an additional address, which may be
+passed as the @var{special} optional argument.
+
+It is possible to create a frame ID with just a stack address, but
+it's better to specify a code address as well if possible.
+@end deffn
+
+After building an unwind info object for an ephemeral frame, an
+unwinder can call @code{unwind-info-add-saved-register!} to record
+saved registers.  The values of the saved registers logically belong
+to the frame that is older than the ephemeral frame being unwound, not
+to the ephemeral frame itself.
+
+@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
+Set the saved value of a register in a ephemeral frame.
+
+@var{register} names a register, and should be a string, for example
+@samp{rip}.  @var{value} is the register value, as a @value{GDBN}
+value.
+@end deffn
+
+Any register whose value is not recorded as saved via
+@code{unwind-info-add-saved-register!} will be marked as ``not saved''
+in the outer frame.
+
 @node Commands In Guile
 @subsubsection Commands In Guile
 
diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c
index bba1ae7..4347dca 100644
--- a/gdb/frame-unwind.c
+++ b/gdb/frame-unwind.c
@@ -87,6 +87,34 @@  frame_unwind_append_unwinder (struct gdbarch *gdbarch,
   (*ip)->unwinder = unwinder;
 }
 
+/* Nonzero if we are finding the unwinder for a frame; see
+   frame_unwind_try_handler.  */
+static int is_unwinding = 0;
+
+/* Return nonzero if we are inside a sniffer call.  */
+
+int
+frame_unwind_is_unwinding (void)
+{
+  return is_unwinding;
+}
+
+/* Cleanup helpers for is_unwinding.  */
+
+static void
+unset_is_unwinding (void *unused)
+{
+  is_unwinding = 0;
+}
+
+static struct cleanup*
+set_is_unwinding (void)
+{
+  is_unwinding = 1;
+
+  return make_cleanup (unset_is_unwinding, NULL);
+}
+
 /* Call SNIFFER from UNWINDER.  If it succeeded set UNWINDER for
    THIS_FRAME and return 1.  Otherwise the function keeps THIS_FRAME
    unchanged and returns 0.  */
@@ -98,11 +126,18 @@  frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
   struct cleanup *old_cleanup;
   int res = 0;
 
+  if (is_unwinding)
+    internal_error (__FILE__, __LINE__,
+		    _("Recursion detected while finding an unwinder."));
   old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder);
 
   TRY
     {
+      struct cleanup *cleanup = set_is_unwinding ();
+
       res = unwinder->sniffer (unwinder, this_frame, this_cache);
+
+      do_cleanups (cleanup);
     }
   CATCH (ex, RETURN_MASK_ERROR)
     {
@@ -252,7 +287,8 @@  frame_unwind_got_constant (struct frame_info *frame, int regnum,
 }
 
 struct value *
-frame_unwind_got_bytes (struct frame_info *frame, int regnum, gdb_byte *buf)
+frame_unwind_got_bytes (struct frame_info *frame, int regnum,
+			const gdb_byte *buf)
 {
   struct gdbarch *gdbarch = frame_unwind_arch (frame);
   struct value *reg_val;
diff --git a/gdb/frame-unwind.h b/gdb/frame-unwind.h
index 44add12..3e322f2 100644
--- a/gdb/frame-unwind.h
+++ b/gdb/frame-unwind.h
@@ -179,6 +179,11 @@  extern void frame_unwind_append_unwinder (struct gdbarch *gdbarch,
 extern void frame_unwind_find_by_frame (struct frame_info *this_frame,
 					void **this_cache);
 
+/* Return nonzero if we are in the process of finding an unwinder for a frame.
+   See the comments in get_current_frame.  */
+
+extern int frame_unwind_is_unwinding (void);
+
 /* Helper functions for value-based register unwinding.  These return
    a (possibly lazy) value of the appropriate type.  */
 
@@ -210,7 +215,7 @@  struct value *frame_unwind_got_constant (struct frame_info *frame, int regnum,
    inside BUF.  */
 
 struct value *frame_unwind_got_bytes (struct frame_info *frame, int regnum,
-                                      gdb_byte *buf);
+                                      const gdb_byte *buf);
 
 /* Return a value which indicates that FRAME's saved version of REGNUM
    has a known constant (computed) value of ADDR.  Convert the
diff --git a/gdb/frame.c b/gdb/frame.c
index b3cbf23..9e63f21 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -2212,6 +2212,22 @@  get_prev_frame (struct frame_info *this_frame)
       return NULL;
     }
 
+  /* Unwinders implemented in Python or Scheme could eventually make an API call
+     that would cause GDB to try to unwind a frame while unwinding a frame.
+     Because already-unwound frames will be found in the frame cache, unwinding
+     will only happen at the old end of the stack, which means that any
+     recursive unwinding attempt will surely lead to unbounded recursion.  Ways
+     this can happen include such common functions as `get_current_arch' or
+     `lookup_symbol', via `get_selected_frame', so it's impractical to simply
+     declare these an error.  Instead, we detect this case and return NULL,
+     indicating that the known stack of frames ends here.  */
+  if (frame_unwind_is_unwinding ())
+    {
+      frame_debug_got_null_frame (this_frame,
+				  "get_prev_frame within unwinder sniffer");
+      return NULL;
+    }
+
   return get_prev_frame_always (this_frame);
 }
 
diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
index d7c166c..9ca807e 100644
--- a/gdb/guile/guile-internal.h
+++ b/gdb/guile/guile-internal.h
@@ -598,6 +598,7 @@  extern void gdbscm_initialize_disasm (void);
 extern void gdbscm_initialize_exceptions (void);
 extern void gdbscm_initialize_frames (void);
 extern void gdbscm_initialize_frame_filters (void);
+extern void gdbscm_initialize_frame_unwinders (void);
 extern void gdbscm_initialize_iterators (void);
 extern void gdbscm_initialize_lazy_strings (void);
 extern void gdbscm_initialize_math (void);
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index 176e97e..d1850e7 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -670,6 +670,7 @@  initialize_gdb_module (void *data)
   gdbscm_initialize_disasm ();
   gdbscm_initialize_frames ();
   gdbscm_initialize_frame_filters ();
+  gdbscm_initialize_frame_unwinders ();
   gdbscm_initialize_iterators ();
   gdbscm_initialize_lazy_strings ();
   gdbscm_initialize_math ();
diff --git a/gdb/guile/lib/gdb/frame-unwinders.scm b/gdb/guile/lib/gdb/frame-unwinders.scm
new file mode 100644
index 0000000..ead2443
--- /dev/null
+++ b/gdb/guile/lib/gdb/frame-unwinders.scm
@@ -0,0 +1,213 @@ 
+;; Frame unwinder support.
+;;
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+;;
+;; This file is part of GDB.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gdb frame-unwinders)
+  #:use-module ((gdb) #:hide (frame? symbol?))
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (ice-9 match)
+  #:export (ephemeral-frame-read-register
+
+            make-unwind-info
+            unwind-info-add-saved-register!
+
+            make-frame-unwinder
+            frame-unwinder?
+            frame-unwinder-name
+            frame-unwinder-enabled?
+            frame-unwinder-registered?
+            frame-unwinder-priority
+            frame-unwinder-procedure
+            frame-unwinder-scope
+
+            find-frame-unwinder-by-name
+
+            register-frame-unwinder!
+            unregister-frame-unwinder!
+            set-frame-unwinder-enabled!
+            enable-frame-unwinder!
+            disable-frame-unwinder!
+
+            all-frame-unwinders))
+
+(define-record-type <frame-unwinder>
+  (%make-frame-unwinder name priority enabled? registered? procedure scope)
+  frame-unwinder?
+  ;; string
+  (name frame-unwinder-name)
+  ;; real
+  (priority frame-unwinder-priority set-priority!)
+  ;; bool
+  (enabled? frame-unwinder-enabled? set-enabled?!)
+  ;; bool
+  (registered? frame-unwinder-registered? set-registered?!)
+  ;; ephemeral-frame -> unwind-info | #f
+  (procedure frame-unwinder-procedure)
+  ;; objfile | progspace | #f
+  (scope frame-unwinder-scope set-scope!))
+
+(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
+  "Make and return a new frame unwinder.  NAME and PROCEDURE are
+required arguments.  Specify #:priority or #:enabled? to set the
+priority and enabled status of the unwinder.
+
+The unwinder must be registered with GDB via `register-frame-unwinder!'
+before it is active."
+  (let ((registered? #f) (scope #f))
+    (%make-frame-unwinder name priority enabled? registered? procedure scope)))
+
+;; List of frame unwinders, sorted by priority from highest to lowest.
+(define *frame-unwinders* '())
+
+(define (same-scope? a b)
+  "Return #t if A and B represent the same scope, for the purposes of
+frame unwinder selection."
+  (cond
+   ;; If either is the global scope, they share a scope.
+   ((or (not a) (not b)) #t)
+   ;; If either is an objfile, compare their progspaces.
+   ((objfile? a) (same-scope? (objfile-progspace a) b))
+   ((objfile? b) (same-scope? a (objfile-progspace b)))
+   ;; Otherwise they are progspaces.  If they eq?, it's the same scope.
+   (else (eq? a b))))
+
+(define (is-valid? unwinder)
+  "Return #t if the scope of UNWINDER is still valid, or otherwise #f if
+the objfile or progspace has been removed from GDB."
+  (let ((scope (frame-unwinder-scope unwinder)))
+    (cond
+     ((progspace? scope) (progspace-valid? scope))
+     ((objfile? scope) (objfile-valid? scope))
+     (else #t))))
+
+(define (all-frame-unwinders)
+  "Return a list of all active frame unwinders, ordered from highest to
+lowest priority."
+  ;; Copy the list to prevent callers from mutating our state.
+  (list-copy *frame-unwinders*))
+
+(define* (has-active-frame-unwinders? #:optional
+                                      (scope (current-progspace)))
+  "Return #t if there are active frame unwinders for the given scope, or
+#f otherwise."
+  (let lp ((unwinders *frame-unwinders*))
+    (match unwinders
+      (() #f)
+      ((unwinder . unwinders)
+       (or (and (frame-unwinder-enabled? unwinder)
+                (same-scope? (frame-unwinder-scope unwinder) scope))
+           (lp unwinders))))))
+
+(define (prune-frame-unwinders!)
+  "Prune frame unwinders whose objfile or progspace has gone away,
+returning a fresh list of frame unwinders."
+  (set! *frame-unwinders*
+        (let lp ((unwinders *frame-unwinders*))
+          (match unwinders
+            (() '())
+            ((f . unwinders)
+             (cond
+              ((is-valid? f)
+               (cons f (lp unwinders)))
+              (else
+               (set-registered?! f #f)
+               (lp unwinders))))))))
+
+(define* (register-frame-unwinder! unwinder #:key scope)
+  "Register a frame unwinder with GDB.  Frame unwinders must be
+registered before they will be used to unwind backtraces.
+
+By default, the unwinder will be registered globally.  Specify an
+objfile or a progspace as the #:scope keyword argument to limit the
+unwinder to a specific scope."
+  (define (check-scope!)
+    (cond
+     ((objfile? scope)
+      (unless (objfile-valid? scope)
+        (error "Objfile is not valid" scope)))
+     ((progspace? scope)
+      (unless (progspace-valid? scope)
+        (error "Progspace is not valid" scope)))
+     (scope
+      (error "Invalid scope" scope))))
+  (define (duplicate-unwinder? other)
+    (and (equal? (frame-unwinder-name other)
+                 (frame-unwinder-name unwinder))
+         (same-scope? (frame-unwinder-scope other) scope)))
+  (define (priority>=? a b)
+    (>= (frame-unwinder-priority a) (frame-unwinder-priority b)))
+  (define (insert-sorted elt xs <=?)
+    (let lp ((xs xs))
+      (match xs
+        (() (list elt))
+        ((x . xs*)
+         (if (<=? elt x)
+             (cons elt xs)
+             (cons x (lp xs*)))))))
+
+  (check-scope!)
+  (prune-frame-unwinders!)
+  (when (or-map duplicate-unwinder? *frame-unwinders*)
+    (error "Frame unwinder with this name already present in scope"
+           (frame-unwinder-name unwinder)))
+  (set-registered?! unwinder #t)
+  (set-scope! unwinder scope)
+  (set! *frame-unwinders*
+        (insert-sorted unwinder *frame-unwinders* priority>=?)))
+
+(define (unregister-frame-unwinder! unwinder)
+  "Unregister a frame unwinder."
+  (set-registered?! unwinder #f)
+  (set-scope! unwinder #f)
+  (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
+
+(define* (find-frame-unwinder-by-name name #:optional
+                                      (scope (current-progspace)))
+  (prune-frame-unwinders!)
+  (or (find (lambda (unwinder)
+              (and (equal? name (frame-unwinder-name unwinder))
+                   (same-scope? (frame-unwinder-scope unwinder) scope)))
+            *frame-unwinders*)
+      (error "no frame unwinder found with name" name)))
+
+(define (set-frame-unwinder-enabled! unwinder enabled?)
+  "Enable or disable a frame unwinder."
+  (let ((unwinder (if (frame-unwinder? unwinder)
+                      unwinder
+                      (find-frame-unwinder-by-name unwinder))))
+    (set-enabled?! unwinder enabled?)
+    *unspecified*))
+
+(define (enable-frame-unwinder! unwinder)
+  "Mark a frame unwinder as enabled."
+  (set-frame-unwinder-enabled! unwinder #t))
+
+(define (disable-frame-unwinder! unwinder)
+  "Mark a frame unwinder as disabled."
+  (set-frame-unwinder-enabled! unwinder #f))
+
+(define (unwind-frame frame)
+  (let ((scope (current-progspace)))
+    (or-map (lambda (unwinder)
+              (and (frame-unwinder-enabled? unwinder)
+                   (same-scope? (frame-unwinder-scope unwinder) scope)
+                   ((frame-unwinder-procedure unwinder) frame)))
+            *frame-unwinders*)))
+
+(load-extension "gdb" "gdbscm_load_frame_unwinders")
diff --git a/gdb/guile/scm-frame-unwinder.c b/gdb/guile/scm-frame-unwinder.c
new file mode 100644
index 0000000..b57806c
--- /dev/null
+++ b/gdb/guile/scm-frame-unwinder.c
@@ -0,0 +1,593 @@ 
+/* Scheme interface to the JIT reader.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* See README file in this directory for implementation notes, coding
+   conventions, et.al.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "guile-internal.h"
+#include "inferior.h"
+#include "language.h"
+#include "observer.h"
+#include "regcache.h"
+#include "user-regs.h"
+#include "value.h"
+
+/* Non-zero if the (gdb frame-unwinders) module has been loaded.  */
+static int gdbscm_frame_unwinders_loaded = 0;
+
+/* The captured apply-frame-filter variable.  */
+static SCM unwind_frame = SCM_BOOL_F;
+
+/* Key that we use when associating data with an architecture.  */
+static struct gdbarch_data *uwscm_gdbarch_data;
+
+/* The frame unwinder interface makes ephemeral frame objects when trying
+   to unwind frames, and unwind info objects when unwinding is successful.
+   Here we define the names for the ephemeral frame and unwind info Scheme
+   data types.  */
+static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
+static const char unwind_info_smob_name[] = "gdb:unwind-info";
+
+/* SMOB tag for ephemeral frames and unwind info.  */
+static scm_t_bits ephemeral_frame_smob_tag;
+static scm_t_bits unwind_info_smob_tag;
+
+/* Data associated with a ephemeral frame.  */
+struct uwscm_ephemeral_frame
+{
+  /* The frame being unwound, used for the read-register interface.  */
+  struct frame_info *this_frame;
+
+  /* The architecture of the frame, here for convenience.  */
+  struct gdbarch *gdbarch;
+};
+
+/* Data associated with an unwind info object.  */
+struct uwscm_unwind_info
+{
+  /* The associated ephemeral frame.  */
+  SCM frame;
+
+  /* The frame_id for the associated ephemeral frame.  */
+  struct frame_id frame_id;
+
+  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
+     associated ephemeral frame.  */
+  SCM registers;
+};
+
+/* Type predicate for ephemeral frames.  */
+
+static int
+uwscm_is_ephemeral_frame (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (ephemeral_frame_smob_tag, obj);
+}
+
+/* Data accessor for ephemeral frames.  */
+
+static struct uwscm_ephemeral_frame *
+uwscm_ephemeral_frame_data (SCM obj)
+{
+  gdb_assert (uwscm_is_ephemeral_frame (obj));
+  return (struct uwscm_ephemeral_frame *) SCM_SMOB_DATA (obj);
+}
+
+/* Type predicate for unwind info.  */
+
+static int
+uwscm_is_unwind_info (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (unwind_info_smob_tag, obj);
+}
+
+/* Data accessor for unwind_info.  */
+
+static struct uwscm_unwind_info *
+uwscm_unwind_info_data (SCM obj)
+{
+  gdb_assert (uwscm_is_unwind_info (obj));
+  return (struct uwscm_unwind_info *) SCM_SMOB_DATA (obj);
+}
+
+/* Build a ephemeral frame.  */
+
+static SCM
+uwscm_make_ephemeral_frame (struct frame_info *this_frame)
+{
+  struct uwscm_ephemeral_frame *data;
+
+  data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
+
+  data->this_frame = this_frame;
+  TRY
+    {
+      data->gdbarch = get_frame_arch (this_frame);
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDBSCM_HANDLE_GDB_EXCEPTION (except);
+    }
+  END_CATCH
+
+  SCM_RETURN_NEWSMOB (ephemeral_frame_smob_tag, data);
+}
+
+/* Ephemeral frames may only be accessed from Scheme within the dynamic
+   extent of the unwind callback.  */
+
+static int
+uwscm_ephemeral_frame_is_valid (SCM ephemeral_frame)
+{
+  return uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame != NULL;
+}
+
+/* Is this an ephemeral frame that is accessible from Scheme?  */
+
+static int
+uwscm_is_valid_ephemeral_frame (SCM obj)
+{
+  return uwscm_is_ephemeral_frame (obj) && uwscm_ephemeral_frame_is_valid (obj);
+}
+
+/* Is this an unwind info whose associated ephemeral frame is valid?  */
+
+static int
+uwscm_is_valid_unwind_info (SCM obj)
+{
+  return uwscm_is_unwind_info (obj)
+    && uwscm_ephemeral_frame_is_valid (uwscm_unwind_info_data (obj)->frame);
+}
+
+/* Called as the unwind callback finishes to invalidate the ephemeral
+   frame.  */
+
+static void
+uwscm_invalidate_ephemeral_frame (SCM ephemeral_frame)
+{
+  gdb_assert(uwscm_ephemeral_frame_is_valid (ephemeral_frame));
+  uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame = NULL;
+}
+
+/* Raise a Scheme exception if OBJ is not a valid ephemeral frame.  */
+
+static void
+uwscm_assert_valid_ephemeral_frame (SCM obj, const char *func_name, int pos)
+{
+  if (!uwscm_is_valid_ephemeral_frame (obj))
+    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
+					  "valid <gdb:ephemeral-frame>"));
+}
+
+/* Raise a Scheme exception if OBJ is not an unwind info object whose
+   associated ephemeral frame is valid.  */
+
+static void
+uwscm_assert_valid_unwind_info (SCM obj, const char *func_name, int pos)
+{
+  if (!uwscm_is_valid_unwind_info (obj))
+    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
+					  "valid <gdb:unwind-info>"));
+}
+
+/* Helper to convert a frame ID component to a CORE_ADDR.  */
+
+static CORE_ADDR
+uwscm_value_to_addr (SCM value, int arg)
+{
+  struct value *c_value;
+  CORE_ADDR ret;
+
+  if (!vlscm_is_value (value))
+    gdbscm_throw (gdbscm_make_type_error ("make-unwind-info",
+					  arg, value, "<gdb:value> object"));
+
+  c_value = vlscm_scm_to_value (value);
+
+  TRY
+    {
+      ret = value_as_address (c_value);
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDBSCM_HANDLE_GDB_EXCEPTION (except);
+    }
+  END_CATCH
+
+  return ret;
+}
+
+/* (make-unwind-info ephemeral-frame sp [pc [special]])
+
+   Set the frame ID on this ephemeral frame.  */
+
+static SCM
+gdbscm_make_unwind_info (SCM ephemeral_frame, SCM sp, SCM pc, SCM special)
+{
+  struct uwscm_unwind_info *data;
+  struct frame_id frame_id;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+
+  if (SCM_UNBNDP (pc))
+    frame_id = frame_id_build_wild (uwscm_value_to_addr (sp, SCM_ARG2));
+  if (SCM_UNBNDP (special))
+    frame_id = frame_id_build (uwscm_value_to_addr (sp, SCM_ARG2),
+			       uwscm_value_to_addr (pc, SCM_ARG3));
+  else
+    frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
+				       uwscm_value_to_addr (pc, SCM_ARG3),
+				       uwscm_value_to_addr (special, SCM_ARG4));
+
+  data = scm_gc_malloc (sizeof (*data), unwind_info_smob_name);
+
+  data->frame = ephemeral_frame;
+  data->frame_id = frame_id;
+  data->registers = SCM_EOL;
+
+  SCM_RETURN_NEWSMOB (unwind_info_smob_tag, data);
+}
+
+/* Convert the string REGISTER_SCM to a register number for the given
+   architecture.  */
+
+static int
+uwscm_scm_to_regnum (SCM register_scm, struct gdbarch *gdbarch,
+		     const char *func_name, int arg)
+{
+  int regnum;
+
+  struct cleanup *cleanup;
+  char *register_str;
+
+  gdbscm_parse_function_args (func_name, arg, NULL,
+			      "s", register_scm, &register_str);
+  cleanup = make_cleanup (xfree, register_str);
+
+  TRY
+    {
+      regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
+					    strlen (register_str));
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      do_cleanups (cleanup);
+      GDBSCM_HANDLE_GDB_EXCEPTION (except);
+    }
+  END_CATCH
+
+  do_cleanups (cleanup);
+
+  if (regnum < 0)
+    gdbscm_out_of_range_error (func_name, arg,
+			       register_scm, _("unknown register"));
+
+  return regnum;
+}
+
+/* (ephemeral-frame-read-register <gdb:ephemeral-frame> string)
+      -> <gdb:value>
+
+   Sniffs a register value from an ephemeral frame.  */
+
+static SCM
+gdbscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
+{
+  struct uwscm_ephemeral_frame *data;
+  struct value *value = NULL;
+  int regnum;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch, FUNC_NAME,
+				SCM_ARG2);
+
+  TRY
+    {
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      value = get_frame_register_value (data->this_frame, regnum);
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDBSCM_HANDLE_GDB_EXCEPTION (except);
+    }
+  END_CATCH
+
+  if (value == NULL)
+    gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, register_scm,
+			       _("Cannot read register from frame."));
+
+  return vlscm_scm_from_value (value);
+}
+
+/* (unwind-info-add-saved-register! unwind-info register value)
+
+   Records the saved value of a particular register in UNWIND_INFO's
+   corresponding ephemeral frame.  REGISTER_SCM names the register, as a
+   string, and VALUE_SCM is a <gdb:value>.  */
+
+static SCM
+gdbscm_unwind_info_add_saved_register_x (SCM unwind_info, SCM register_scm,
+					 SCM value_scm)
+{
+  struct uwscm_unwind_info *data;
+  struct uwscm_ephemeral_frame *frame_data;
+  struct value *value;
+  int regnum;
+  int value_size;
+
+  uwscm_assert_valid_unwind_info (unwind_info, FUNC_NAME, SCM_ARG1);
+  data = uwscm_unwind_info_data (unwind_info);
+  frame_data = uwscm_ephemeral_frame_data (data->frame);
+  regnum = uwscm_scm_to_regnum (register_scm, frame_data->gdbarch,
+				FUNC_NAME, SCM_ARG2);
+
+  if (!vlscm_is_value (value_scm))
+    gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG3,
+					  value_scm,
+					  "<gdb:value> object"));
+
+  value = vlscm_scm_to_value (value_scm);
+  value_size = TYPE_LENGTH (value_enclosing_type (value));
+
+  if (value_size != register_size (frame_data->gdbarch, regnum))
+    gdbscm_invalid_object_error ("unwind-info-add-saved-register!",
+				 SCM_ARG3, value_scm,
+				 "wrong sized value for register");
+
+  data->registers = scm_assv_set_x (data->registers,
+				    scm_from_int (regnum),
+				    value_scm);
+
+  return SCM_UNSPECIFIED;
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+uwscm_this_id (struct frame_info *this_frame, void **cache_ptr,
+	       struct frame_id *this_id)
+{
+  SCM unwind_info = PTR2SCM (*cache_ptr);
+  struct uwscm_unwind_info *data;
+
+  data = uwscm_unwind_info_data (unwind_info);
+  *this_id = data->frame_id;
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+uwscm_prev_register (struct frame_info *this_frame, void **cache_ptr,
+		     int regnum)
+{
+  SCM unwind_info = PTR2SCM (*cache_ptr);
+  struct uwscm_unwind_info *data;
+  SCM value_scm;
+  struct value *c_value;
+  const gdb_byte *buf;
+
+  data = uwscm_unwind_info_data (unwind_info);
+  value_scm = scm_assv_ref (data->registers, scm_from_int (regnum));
+  if (gdbscm_is_false (value_scm))
+    return frame_unwind_got_optimized (this_frame, regnum);
+
+  c_value = vlscm_scm_to_value (value_scm);
+  buf = value_contents (c_value);
+
+  return frame_unwind_got_bytes (this_frame, regnum, buf);
+}
+
+/* Call unwind-frame on the ephemeral frame and check that the return
+   value is either a valid unwind info object, for a successful unwind, or
+   #f otherwise.  */
+
+static SCM
+do_call_unwind_frame (void *data)
+{
+  SCM ephemeral_frame = PTR2SCM (data);
+  SCM result;
+
+  result = scm_call_1 (scm_variable_ref (unwind_frame), ephemeral_frame);
+
+  if (!gdbscm_is_false (result))
+    uwscm_assert_valid_unwind_info (result, "unwind-frame", 0);
+
+  return result;
+}
+
+/* Sniffer implementation.  */
+
+static int
+uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+	       void **cache_ptr)
+{
+  SCM ephemeral_frame;
+  SCM result;
+
+  /* Note that it's possible to have loaded the Guile interface, but not yet
+     loaded (gdb frame-unwinders), so checking gdb_scheme_initialized is not
+     sufficient.  */
+  if (!gdbscm_frame_unwinders_loaded)
+    return 0;
+
+  ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
+  /* Recurse through gdbscm_call_guile so that we can just throw
+     exceptions on error.  */
+  result = gdbscm_call_guile (do_call_unwind_frame,
+			      SCM2PTR (ephemeral_frame),
+			      gdbscm_memory_error_p);
+
+  /* Drop the reference to this_frame, so that future use of
+     ephemeral_frame from Scheme will signal an error.  */
+  uwscm_invalidate_ephemeral_frame (ephemeral_frame);
+
+  if (gdbscm_is_exception (result))
+    {
+      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+      return 0;
+    }
+
+  if (gdbscm_is_false (result))
+    return 0;
+
+  *cache_ptr = SCM2PTR (scm_gc_protect_object (result));
+
+  return 1;
+}
+
+/* Frame cache release shim.  */
+
+static void
+uwscm_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  scm_gc_unprotect_object (PTR2SCM (cache));
+}
+
+struct uwscm_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+uwscm_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct uwscm_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Guile sniffers
+   intermediary.  */
+
+static void
+uwscm_on_new_gdbarch (struct gdbarch *newarch)
+{
+  struct uwscm_gdbarch_data_type *data =
+      gdbarch_data (newarch, uwscm_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 = uwscm_this_id;
+      unwinder->prev_register = uwscm_prev_register;
+      unwinder->unwind_data = (void *) newarch;
+      unwinder->sniffer = uwscm_sniffer;
+      unwinder->dealloc_cache = uwscm_dealloc_cache;
+      frame_unwind_prepend_unwinder (newarch, unwinder);
+      data->unwinder_registered = 1;
+    }
+}
+
+static const scheme_function unwind_functions[] =
+{
+  { "make-unwind-info", 2, 2, 0, gdbscm_make_unwind_info,
+    "\
+Make an unwind info object for an ephemeral frame.\n\
+\n\
+This function takes two required arguments and two optional arguments.\n\
+The first argument is the ephemeral frame that is being unwound, as a\n\
+<gdb:ephemeral-frame>.  The rest of the arguments are used to build an\n\
+identifier for the frame.\n\
+\n\
+A frame identifier is a unique name for a frame that remains valid as\n\
+long as the frame itself is valid.  Usually the frame identifier is\n\
+built from from the frame's stack address and code address.  The stack\n\
+address, passed as the second argument, should normally be a pointer to\n\
+the new end of the stack when the function was called, as a GDB value.\n\
+Similarly the code address, the third argument, should be given as the\n\
+address of the entry point of the function.\n\
+\n\
+For most architectures, it is sufficient to just specify just the stack\n\
+and code pointers.  Some architectures have another stack or some other\n\
+frame state store, like ia64, and they need an additional address, which\n\
+may be passed as the fourth argument.\n\
+\n\
+It is possible to create a frame ID with just a stack address, but it's\n\
+better to specify a code address as well if possible."},
+
+  { "ephemeral-frame-read-register", 2, 0, 0,
+    gdbscm_ephemeral_frame_read_register,
+    "\
+Return the value of a register in an ephemeral frame.\n\
+\n\
+  Arguments: <gdb:ephemeral-frame> string" },
+
+  { "unwind-info-add-saved-register!", 3, 0, 0,
+    gdbscm_unwind_info_add_saved_register_x,
+    "\
+Record the saved value of a register for an ephemeral frame.\n\
+\n\
+After reading an ephemeral frame's registers and determining that it\n\
+can handle the frame, an unwinder will create an unwind info object and\n\
+call this function to record saved registers for the frame.  The values\n\
+of the saved registers logically belong to the frame that is older than\n\
+the ephemeral frame being unwound, not to the ephemeral frame itself.\n\
+\n\
+The first argument should be a <gdb:unwind-info> object.  The second\n\
+names a register, and should be a string, for example \"rip\".  The\n\
+third argument is the value, as a GDB value.  By default, any register\n\
+whose value is not added to the unwind info's saved register set\n\
+will be marked as \"not saved\" in the outer frame." },
+
+  END_FUNCTIONS
+};
+
+/* Called by lib/gdb/frame-unwinders.scm.  */
+
+static void
+gdbscm_load_frame_unwinders (void *unused)
+{
+  if (gdbscm_frame_unwinders_loaded)
+    return;
+
+  gdbscm_frame_unwinders_loaded = 1;
+
+  gdbscm_define_functions (unwind_functions, 0);
+
+  unwind_frame = scm_c_lookup ("unwind-frame");
+}
+
+/* Initialize the opaque ephemeral frame and unwind info types and
+   register gdbscm_load_frame_unwinders for calling by (gdb
+   frame-unwinders).  */
+
+void
+gdbscm_initialize_frame_unwinders (void)
+{
+  ephemeral_frame_smob_tag =
+    gdbscm_make_smob_type (ephemeral_frame_smob_name, 0);
+  unwind_info_smob_tag =
+    gdbscm_make_smob_type (unwind_info_smob_name, 0);
+
+  uwscm_gdbarch_data =
+    gdbarch_data_register_post_init (uwscm_gdbarch_data_init);
+  observer_attach_architecture_changed (uwscm_on_new_gdbarch);
+
+  scm_c_register_extension ("gdb", "gdbscm_load_frame_unwinders",
+                            gdbscm_load_frame_unwinders, NULL);
+}
diff --git a/gdb/guile/scm-symbol.c b/gdb/guile/scm-symbol.c
index 99ef928..1ad6d0c 100644
--- a/gdb/guile/scm-symbol.c
+++ b/gdb/guile/scm-symbol.c
@@ -605,7 +605,9 @@  gdbscm_lookup_symbol (SCM name_scm, SCM rest)
 
       TRY
 	{
-	  selected_frame = get_selected_frame (_("no frame selected"));
+	  selected_frame = get_selected_frame_if_set ();
+	  if (selected_frame == NULL)
+	    selected_frame = get_current_frame ();
 	  block = get_frame_block (selected_frame, NULL);
 	}
       CATCH (except, RETURN_MASK_ALL)
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 9e94ddf..52a60f8 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,10 @@ 
+2015-03-06  Andy Wingo  <wingo@igalia.com>
+
+	* gdb.guile/scm-frame-unwinder.exp:
+	* gdb.guile/scm-frame-unwinder.c:
+	* gdb.guile/scm-frame-unwinder-gdb.scm.in:
+	* gdb.guile/scm-frame-unwinder.scm: Add unwinder tests.
+
 2015-03-05  Andy Wingo  <wingo@igalia.com>
 
 	* gdb.guile/amd64-scm-frame-filter-invalidarg.S:
diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
new file mode 100644
index 0000000..28f5936
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
@@ -0,0 +1,32 @@ 
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is part of the GDB test-suite.  It tests Guile-based frame
+;; filters.
+
+(use-modules (gdb)
+             (gdb frame-unwinders))
+
+(define* (install-unwinders! #:optional (objfile (current-objfile)))
+  (define (unwind-frame frame)
+    #f)
+  (define (add-unwinder! name priority)
+    (register-frame-unwinder! (make-frame-unwinder name unwind-frame
+                                                   #:priority priority)
+                              #:scope objfile))
+  (add-unwinder! "Auto-loaded dummy" 100)
+  (add-unwinder! "Auto-loaded dummy 2" 200))
+
+(install-unwinders!)
diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.c b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
new file mode 100644
index 0000000..82db341
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
@@ -0,0 +1,30 @@ 
+int f2 (int a)
+{
+  return ++a;
+}
+
+int f1 (int a, int b)
+{
+  return f2(a) + b;
+}
+
+int block (void)
+{
+  int i = 99;
+  {
+    double i = 1.1;
+    double f = 2.2;
+    {
+      const char *i = "stuff";
+      const char *f = "foo";
+      const char *b = "bar";
+      return 0; /* Block break here.  */
+    }
+  }
+}
+
+int main (int argc, char *argv[])
+{
+  block ();
+  return f1 (1, 2);
+}
diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
new file mode 100644
index 0000000..e22a8d9
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
@@ -0,0 +1,84 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests Guile-based
+# frame-filters.
+
+load_lib gdb-guile.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Guile scripting is not enabled.
+if { [skip_guile_tests] } { continue }
+
+# Make the -gdb.scm script available to gdb, it is automagically loaded by gdb.
+# Care is taken to put it in the same directory as the binary so that
+# gdb will find it.
+set remote_obj_guile_file \
+    [remote_download \
+	 host ${srcdir}/${subdir}/${testfile}-gdb.scm.in \
+	 [standard_output_file ${testfile}-gdb.scm]]
+
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_test_no_output "set auto-load safe-path ${remote_obj_guile_file}" \
+    "set auto-load safe-path"
+gdb_load ${binfile}
+# Verify gdb loaded the script.
+gdb_test "info auto-load guile-scripts" "Yes.*/${testfile}-gdb.scm.*" \
+    "Test auto-load had loaded guile scripts"
+
+if ![runto_main] then {
+    perror "couldn't run to breakpoint"
+    return
+}
+
+# Load global frame-unwinders
+set remote_guile_file [gdb_remote_download host \
+			    ${srcdir}/${subdir}/${testfile}.scm]
+gdb_scm_load_file ${remote_guile_file}
+
+# Test query
+gdb_test "guile (all-frame-unwinders)" \
+    ".*Dummy.*Auto-loaded dummy 2.*Synthetic.*Auto-loaded dummy.*" \
+    "all frame unwinders"
+gdb_test "guile (map frame-unwinder-priority (all-frame-unwinders))" \
+    ".*300 200 150 100.*" \
+    "all frame unwinder priorities"
+gdb_test "guile (map frame-unwinder-enabled? (all-frame-unwinders))" \
+    ".*#t #t #f #t.*" \
+    "all frame unwinders enabled?"
+
+gdb_test_no_output "guile (disable-frame-unwinder! \"Dummy\")" \
+    "disable dummy"
+gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
+    ".*#f.*" \
+    "dummy not enabled"
+gdb_test_no_output "guile (enable-frame-unwinder! \"Dummy\")" \
+    "re-enable dummy"
+gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
+    ".*#t.*" \
+    "dummy re-enabled"
+
+gdb_test_no_output "guile (enable-frame-unwinder! \"Synthetic\")" \
+    "enable synthetic unwinder"
+
+gdb_breakpoint "f2"
+gdb_continue_to_breakpoint "breakpoint at f2"
+
+gdb_test "bt 10" " f2 .*cabba9e5 .*cabba9e5 .*"
diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
new file mode 100644
index 0000000..e299d49
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
@@ -0,0 +1,42 @@ 
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is part of the GDB test-suite.  It tests Guile-based frame
+;; filters.
+
+(use-modules (gdb)
+             (gdb frame-unwinders))
+
+(define (dummy-unwinder frame)
+  #f)
+
+(register-frame-unwinder!
+ (make-frame-unwinder "Dummy" dummy-unwinder #:priority 300))
+
+
+(define (synthetic-unwinder frame)
+  ;; Increment the stack pointer, set IP to 0xdeadbeef
+  (let* ((this-pc (ephemeral-frame-read-register frame "rip"))
+         (this-sp (ephemeral-frame-read-register frame "rsp"))
+         (prev-pc (value-cast (make-value #xcabba9e5) (value-type this-pc)))
+         (prev-sp (value-add this-sp 32))
+         (unwind-info (make-unwind-info frame this-sp this-pc)))
+    (unwind-info-add-saved-register! unwind-info "rip" prev-pc)
+    (unwind-info-add-saved-register! unwind-info "rsp" prev-sp)
+    unwind-info))
+
+(register-frame-unwinder!
+ (make-frame-unwinder "Synthetic" synthetic-unwinder #:priority 150
+                      #:enabled? #f))