[v2] Add Guile frame unwinder interface

Message ID 87zj7msbly.fsf@igalia.com
State New, archived
Headers

Commit Message

Andy Wingo March 9, 2015, 10:34 a.m. UTC
  Hi,

New patch adds documentation and tests and cleans up the
frame_unwind_is_unwinding() hack.  At this point I am actually OK with
the hack; it's simple enough and has a sensible meaning.

WDYT?

Andy
From 6cefad33932909549cfbba7e9c1364a2f8279854 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.
	(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                                      |  32 ++
 gdb/Makefile.in                                    |   6 +
 gdb/data-directory/Makefile.in                     |   2 +
 gdb/doc/ChangeLog                                  |   4 +
 gdb/doc/guile.texi                                 | 188 +++++++
 gdb/frame-unwind.c                                 |  20 +-
 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                     | 566 +++++++++++++++++++++
 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     |  41 ++
 18 files changed, 1251 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

Pedro Alves March 9, 2015, 3:41 p.m. UTC | #1
On 03/09/2015 10:34 AM, Andy Wingo wrote:
> +@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.  Alternately, passing @code{#f} as the value will mark the
> +register as unavailable.

From a glimpse over the code, I think this actually marks it as
"<not saved>" (optimized out), right?  That would be the correct
thing to do.  Marking a register as "<unavailable>" is also possible,
but it is a different thing -- it means the value exists, but gdb
couln't get to it, because e.g., the core file is trimmed, or the
ptrace interface is missing access to some registers.

That said, you may want to consider how you'd expand the API
to allow marking registers as unavailable.

> +@end deffn

> +The first argument should be a <gdb:ephemeral-frame> 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.  Alternately, passing #f\n\
> +as the value will mark the register as unavailable." },

Likewise.

> @@ -99,12 +111,17 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
>    volatile struct gdb_exception ex;
>    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);
>
> +  is_unwinding = 1;
>    TRY_CATCH (ex, RETURN_MASK_ERROR)
>      {
>        res = unwinder->sniffer (unwinder, this_frame, this_cache);
>      }
> +  is_unwinding = 0;
>    if (ex.reason < 0 && ex.error == NOT_AVAILABLE_ERROR)
>      {
>        /* This usually means that not even the PC is available,
> @@ -249,7 +266,8 @@ frame_unwind_got_constant (struct frame_info *frame, int regnum,
>  }

Note that RETURN_MASK_ERROR does not catch all exceptions (notably,
a ctrl-c/QUIT passes right through uncaught).

> +/* Return nonzero if we are in the process of finding an unwinder for a frame.
> +   See the comments in get_current_frame().  */

No ()'s.

> +
> +extern int frame_unwind_is_unwinding (void);

Thanks,
Pedro Alves
  
Eli Zaretskii March 9, 2015, 4:22 p.m. UTC | #2
> Date: Mon, 09 Mar 2015 15:41:21 +0000
> From: Pedro Alves <palves@redhat.com>
> CC: asmundak@google.com
> 
> >From a glimpse over the code, I think this actually marks it as
> "<not saved>" (optimized out), right?  That would be the correct
> thing to do.  Marking a register as "<unavailable>" is also possible,
> but it is a different thing -- it means the value exists, but gdb
> couln't get to it, because e.g., the core file is trimmed, or the
> ptrace interface is missing access to some registers.

Actually, from past discussions I understand that "optimized out" more
often than not means "value exists, but GDB is not smart enough to
interpret the DWARF info to find it and/or GCC didn't put enough of
the info there to begin with".  So I wish we would at some point says
something different when this is the case, instead of hiding behind
unnamed "optimizations".
  
Andy Wingo March 9, 2015, 6:54 p.m. UTC | #3
Hi,

On Mon 09 Mar 2015 16:41, Pedro Alves <palves@redhat.com> writes:

> On 03/09/2015 10:34 AM, Andy Wingo wrote:
>> +@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.  Alternately, passing @code{#f} as the value will mark the
>> +register as unavailable.
>
> From a glimpse over the code, I think this actually marks it as
> "<not saved>" (optimized out), right?  That would be the correct
> thing to do.  Marking a register as "<unavailable>" is also possible,
> but it is a different thing -- it means the value exists, but gdb
> couln't get to it, because e.g., the core file is trimmed, or the
> ptrace interface is missing access to some registers.
>
> That said, you may want to consider how you'd expand the API
> to allow marking registers as unavailable.

I didn't realize that "unavailable" and "not saved" were different
things, thanks for the pointer.  I guess given that the default is a
"not saved" result, I can just document this default state, and that
ephemeral-frame-add-saved-value! adds a value.  We remove the #f case.

If we need to support other states like "unavailable", we can add other
API like ephemeral-frame-mark-unavailable! or similar.

>> +  is_unwinding = 1;
>>    TRY_CATCH (ex, RETURN_MASK_ERROR)
>>      {
>>        res = unwinder->sniffer (unwinder, this_frame, this_cache);
>>      }
>> +  is_unwinding = 0;
>
> Note that RETURN_MASK_ERROR does not catch all exceptions (notably,
> a ctrl-c/QUIT passes right through uncaught).

Ah indeed.  I'll move the is_unwinding=1 to a cleanup inside the TRY_CATCH.

Thanks for the review,

Andy
  
Pedro Alves March 10, 2015, 3:45 p.m. UTC | #4
On 03/09/2015 06:54 PM, Andy Wingo wrote:
> Hi,
> 
> On Mon 09 Mar 2015 16:41, Pedro Alves <palves@redhat.com> writes:
> 
>> On 03/09/2015 10:34 AM, Andy Wingo wrote:
>>> +@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.  Alternately, passing @code{#f} as the value will mark the
>>> +register as unavailable.
>>
>> From a glimpse over the code, I think this actually marks it as
>> "<not saved>" (optimized out), right?  That would be the correct
>> thing to do.  Marking a register as "<unavailable>" is also possible,
>> but it is a different thing -- it means the value exists, but gdb
>> couln't get to it, because e.g., the core file is trimmed, or the
>> ptrace interface is missing access to some registers.
>>
>> That said, you may want to consider how you'd expand the API
>> to allow marking registers as unavailable.
> 
> I didn't realize that "unavailable" and "not saved" were different
> things, thanks for the pointer.  I guess given that the default is a
> "not saved" result, I can just document this default state, and that
> ephemeral-frame-add-saved-value! adds a value.  We remove the #f case.

I wonder whether that's the best default though.  That forces the
unwinder to always handle all registers, even random i/o registers, etc
the machine may happen to have/expose?  An alternative would be
assume the register is found unmodified in this_frame.  You'd need
a ephemeral-frame-mark-not-saved! then, of course.

> 
> If we need to support other states like "unavailable", we can add other
> API like ephemeral-frame-mark-unavailable! or similar.

Yeah.  Note that GDB can mark _parts_ of registers not saved
or unavailable, down to the bit level (mark_value_bits_optimized_out /
mark_value_bits_unavailable).

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index d55daf6..f9ea8e2 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,35 @@ 
+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 an attempt to recursively
+	unwind from the sentinel, and return NULL.
+	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
+	(frame_unwind_is_unwinding_innermost_frame): New declaration.
+	* frame-unwind.c (unwinding_innermost_frame): New file-local
+	variable.
+	(innermost_frame_unwind_begin, innermost_frame_unwind_end): New
+	functions.
+	(frame_unwind_is_unwinding_innermost_frame): New exported
+	predicate.
+	(frame_unwind_find_by_frame): Arrange for
+	frame_unwind_is_unwinding_innermost_frame 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-02-20  Andy Wingo  <wingo@igalia.com>
 
 	* guile/scm-value.c (gdbscm_value_dynamic_type): Fix typo in which
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 0ab4c51..c9110f0 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 \
@@ -2418,6 +2420,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 75aab1b..bb2722d 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -91,6 +91,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 \
@@ -101,6 +102,7 @@  GUILE_COMPILED_FILES = \
 	./gdb.go \
 	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 a8cfbd9..8f9faae 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-20  Andy Wingo  <wingo@igalia.com>
 
 	* guile.texi (Frames In Guile): Describe frame-read-register.
diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
index 05a7806..cc9a332 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
@@ -2125,6 +2126,193 @@  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}, computing the register state in
+each frame.  @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 sets the frame ID on 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{]} @
+       @r{[}#:objfile objfile@r{]} @r{[}#:progspace progspace@r{]}
+
+Make a new frame unwinder.  @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 call
+@code{set-ephemeral-frame-id!} to set the frame ID@.  Otherwise,
+@value{GDBN} will continue to search its list for an unwinder.
+
+By default, the scope of the unwinder is global, meaning that it is
+associated with all objfiles and progspaces.  Pass one of
+@code{#:objfile} or @code{#:progspace} to instead scope the unwinder
+into a specific objfile or progspace, respectively.
+
+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 added to @value{GDBN}'s set of active
+unwinders via @code{add-frame-unwinder!} in order to take effect.
+When added, 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} add-frame-unwinder! unwinder
+@deffnx {Scheme Procedure} remove-frame-unwinder! unwinder
+Register or unregister the frame unwinder @var{unwinder} with
+@value{GDBN}.  Frame unwinders are also implicitly unregistered when
+their objfile or progspace goes away.
+@end deffn
+
+@deffn {Scheme Procedure} enable-frame-unwinder! unwinder
+@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
+Enable or disable a frame unwinder, respectively.  @var{unwinder} can
+either be a frame unwinder object, or it can be a string naming a
+unwinder in the current scope.  If no such unwinder is found, an error
+is signalled.
+@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.  Only three operations are supported on ephemeral frames:
+reading their registers, setting their frame ID, and adding saved
+register values.
+
+@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
+
+@deffn {Scheme Procedure} set-ephemeral-frame-id! frame fp [pc [special]]
+Set the frame ID on ephemeral frame @var{frame}, thereby taking
+responsibility for unwinding the frame.
+
+@var{fp}, @var{pc}, and @var{special} are used to build the frame ID@.
+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
+
+@deffn {Scheme Procedure} ephemeral-frame-add-saved-register! frame register value
+Set the saved value of a register in a ephemeral frame.
+
+After reading an ephemeral frame's registers and determining that it
+can handle the frame, an unwinder will call this function 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
+the ephemeral frame itself.
+
+@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.  Alternately, passing @code{#f} as the value will mark the
+register as unavailable.
+@end deffn
+
 @node Commands In Guile
 @subsubsection Commands In Guile
 
diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c
index e73650a..6507ee1 100644
--- a/gdb/frame-unwind.c
+++ b/gdb/frame-unwind.c
@@ -87,6 +87,18 @@  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;
+}
+
 /* 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.  */
@@ -99,12 +111,17 @@  frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
   volatile struct gdb_exception ex;
   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);
 
+  is_unwinding = 1;
   TRY_CATCH (ex, RETURN_MASK_ERROR)
     {
       res = unwinder->sniffer (unwinder, this_frame, this_cache);
     }
+  is_unwinding = 0;
   if (ex.reason < 0 && ex.error == NOT_AVAILABLE_ERROR)
     {
       /* This usually means that not even the PC is available,
@@ -249,7 +266,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..f706e54 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 6b1be94..81431bb 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -2209,6 +2209,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 4ed8cbb..5231f93 100644
--- a/gdb/guile/guile-internal.h
+++ b/gdb/guile/guile-internal.h
@@ -610,6 +610,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 bbc4340..4726d5f 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -664,6 +664,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..494a571
--- /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 (set-ephemeral-frame-id!
+            ephemeral-frame-read-register
+            ephemeral-frame-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
+
+            add-frame-unwinder!
+            remove-frame-unwinder!
+            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 -> *
+  (procedure frame-unwinder-procedure)
+  ;; objfile | progspace | #f
+  (scope frame-unwinder-scope))
+
+(define* (make-frame-unwinder name procedure #:key
+                            objfile progspace (priority 20) (enabled? #t))
+  "Make and return a new frame unwinder.  NAME and PROCEDURE are
+required arguments.  Specify #:objfile or #:progspace to limit the frame
+unwinder to a given scope, and #:priority or #:enabled? to set the
+priority and enabled status of the unwinder.
+
+The unwinder must be added to the active set via `add-frame-unwinder!'
+before it is active."
+  (define (compute-scope objfile progspace)
+    (cond
+     (objfile
+      (when progspace
+        (error "Only one of #:objfile or #:progspace may be given"))
+      (unless (objfile? objfile)
+        (error "Not an objfile" objfile))
+      objfile)
+     (progspace
+      (unless (progspace? progspace)
+        (error "Not a progspace" progspace))
+      progspace)
+     (else #f)))
+  (let ((registered? #f)
+        (scope (compute-scope objfile progspace)))
+    (%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 (add-frame-unwinder! unwinder)
+  "Add a frame unwinder to the active set.  Frame unwinders must be
+added before they will be used to unwinder backtraces."
+  (define (duplicate-unwinder? other)
+    (and (equal? (frame-unwinder-name other)
+                 (frame-unwinder-name unwinder))
+         (same-scope? (frame-unwinder-scope other)
+                      (frame-unwinder-scope unwinder))))
+  (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*)))))))
+
+  (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! *frame-unwinders*
+        (insert-sorted unwinder *frame-unwinders* priority>=?)))
+
+(define (remove-frame-unwinder! unwinder)
+  "Remove a frame unwinder from the active set."
+  (set-registered?! 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 (enable-frame-unwinder! unwinder)
+  "Mark a frame unwinder as enabled."
+  (let ((unwinder (if (frame-unwinder? unwinder)
+                    unwinder
+                    (find-frame-unwinder-by-name unwinder))))
+    (set-enabled?! unwinder #t)
+    *unspecified*))
+
+(define (disable-frame-unwinder! unwinder)
+  "Mark a frame unwinder as disabled."
+  (let ((unwinder (if (frame-unwinder? unwinder)
+                    unwinder
+                    (find-frame-unwinder-by-name unwinder))))
+    (set-enabled?! unwinder #f)
+    *unspecified*))
+
+(define (unwind-frame frame)
+  (let ((scope (current-progspace)))
+    (or-map (lambda (unwinder)
+              (and (frame-unwinder-enabled? unwinder)
+                   (same-scope? (frame-unwinder-scope unwinder) scope)
+                   (begin
+                     ((frame-unwinder-procedure unwinder) frame)
+                     (ephemeral-frame-has-id? 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..009b13d
--- /dev/null
+++ b/gdb/guile/scm-frame-unwinder.c
@@ -0,0 +1,566 @@ 
+/* 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 computes ephemeral frame objects when it
+   is able to unwind a frame.  Here we define the name for the ephemeral
+   frame Scheme data type.  */
+static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
+
+/* SMOB tag for ephemeral frames.  */
+static scm_t_bits ephemeral_frame_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;
+
+  /* The frame_id for the ephemeral frame; initially unset.  */
+  struct frame_id frame_id;
+
+  /* Nonzero if the frame_id has been set.  */
+  int has_frame_id;
+
+  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
+     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);
+}
+
+/* Build a ephemeral frame.  */
+
+static SCM
+uwscm_make_ephemeral_frame (struct frame_info *this_frame)
+{
+  struct uwscm_ephemeral_frame *data;
+  volatile struct gdb_exception except;
+
+  data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
+
+  data->this_frame = this_frame;
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      data->gdbarch = get_frame_arch (this_frame);
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+  data->has_frame_id = 0;
+  data->registers = SCM_EOL;
+
+  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);
+}
+
+/* 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>"));
+}
+
+/* (ephemeral-frame-has-id? ephemeral-frame) -> bool
+
+   Has this ephemeral frame been given a frame ID?  */
+
+static SCM
+uwscm_ephemeral_frame_has_id_p (SCM ephemeral_frame)
+{
+  struct uwscm_ephemeral_frame *data;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  return scm_from_bool (data->has_frame_id);
+}
+
+/* Helper to convert a frame ID component to a CORE_ADDR.  */
+
+static CORE_ADDR
+uwscm_value_to_addr (SCM value, int arg)
+{
+  volatile struct gdb_exception except;
+  struct value *c_value;
+  CORE_ADDR ret;
+
+  if (!vlscm_is_value (value))
+    gdbscm_throw (gdbscm_make_type_error ("set-ephemeral-frame-id!",
+					  arg, value, "<gdb:value> object"));
+
+  c_value = vlscm_scm_to_value (value);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      ret = value_as_address (c_value);
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  return ret;
+}
+
+/* (set-ephemeral-frame-id! ephemeral-frame stack-address
+                            [code-address [special-address]])
+
+   Set the frame ID on this ephemeral frame.  */
+
+static SCM
+uwscm_set_ephemeral_frame_id_x (SCM ephemeral_frame, SCM sp, SCM ip,
+				SCM special)
+{
+  struct uwscm_ephemeral_frame *data;
+  struct frame_id frame_id;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+
+  if (SCM_UNBNDP (ip))
+    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 (ip, SCM_ARG3));
+  else
+    frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
+				       uwscm_value_to_addr (ip, SCM_ARG3),
+				       uwscm_value_to_addr (special, SCM_ARG4));
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  data->frame_id = frame_id;
+  data->has_frame_id = 1;
+
+  return SCM_UNSPECIFIED;
+}
+
+/* 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)
+{
+  int regnum;
+
+  volatile struct gdb_exception except;
+  struct cleanup *cleanup;
+  char *register_str;
+
+  gdbscm_parse_function_args ("ephemeral-frame-add-saved-register!", SCM_ARG2,
+			      NULL, "s", register_scm, &register_str);
+  cleanup = make_cleanup (xfree, register_str);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
+					    strlen (register_str));
+    }
+  do_cleanups (cleanup);
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  if (regnum < 0)
+    gdbscm_out_of_range_error ("ephemeral-frame-add-saved-register!", SCM_ARG2,
+			       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
+uwscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
+{
+  volatile struct gdb_exception except;
+  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);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      value = get_frame_register_value (data->this_frame, regnum);
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  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);
+}
+
+/* (ephemeral-frame-add-saved-register! ephemeral-frame register value)
+
+   Records the saved value of a particular register in EPHEMERAL_FRAME.
+   REGISTER_SCM names the register, as a string, and VALUE_SCM is a
+   <gdb:value>, or #f to indicate that the register was not saved by the
+   ephemeral frame.  */
+
+static SCM
+uwscm_ephemeral_frame_add_saved_register_x (SCM ephemeral_frame,
+					    SCM register_scm,
+					    SCM value_scm)
+{
+  struct uwscm_ephemeral_frame *data;
+  struct value *value;
+  int regnum;
+  int value_size;
+
+  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);
+
+  if (!gdbscm_is_false (value_scm))
+    {
+      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 (data->gdbarch, regnum))
+	gdbscm_invalid_object_error ("ephemeral-frame-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 ephemeral_frame = PTR2SCM (*cache_ptr);
+  struct uwscm_ephemeral_frame *data;
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  *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 ephemeral_frame = PTR2SCM (*cache_ptr);
+  struct uwscm_ephemeral_frame *data;
+  SCM value_scm;
+  struct value *c_value;
+  const gdb_byte *buf;
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  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);
+}
+
+/* Sniffer implementation.  */
+
+static int
+uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+	       void **cache_ptr)
+{
+  static int unwind_active = 0;
+  static int recursive_unwind_detected = 0;
+  struct frame_info *next_frame;
+  struct uwscm_ephemeral_frame *data;
+  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;
+
+  /* Recursively unwinding indicates a problem in the user's frame
+     unwinder.  Detect recursion, and cause it to cancel the unwind that
+     is in progress.  */
+  if (unwind_active)
+    {
+      recursive_unwind_detected = 1;
+      return 0;
+    }
+
+  ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  unwind_active = 1;
+  recursive_unwind_detected = 0;
+
+  result = gdbscm_safe_call_1 (scm_variable_ref (unwind_frame),
+			       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);
+  unwind_active = 0;
+
+  if (gdbscm_is_exception (result))
+    {
+      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+      return 0;
+    }
+
+  if (recursive_unwind_detected)
+    {
+      fprintf_filtered (gdb_stderr,
+			_("Recursion detected while unwinding frame %d."),
+			frame_relative_level (this_frame));
+      return 0;
+    }
+
+  /* The unwinder indicates success by calling
+     set-ephemeral-frame-id!.  */
+  if (uwscm_ephemeral_frame_data (ephemeral_frame)->has_frame_id)
+    {
+      scm_gc_protect_object (ephemeral_frame);
+      *cache_ptr = SCM2PTR (ephemeral_frame);
+      return 1;
+    }
+
+  return 0;
+}
+
+/* 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[] =
+{
+  { "ephemeral-frame-has-id?", 1, 0, 0, uwscm_ephemeral_frame_has_id_p,
+    "\
+Return #t if the given ephemeral frame has been given a frame ID\n\
+already, or #f otherwise." },
+
+  { "set-ephemeral-frame-id!", 2, 2, 0, uwscm_set_ephemeral_frame_id_x,
+    "\
+Set the identifier on an ephemeral frame, thereby taking responsibility for\n\
+unwinding this 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\
+Ephemeral frame objects are created by the custom unwinder interface, and\n\
+initially have no frame identifier.  A frame identifier is a unique name\n\
+for a frame that remains valid as long as the frame itself is valid.\n\
+Usually the frame identifier is built from from the frame's stack address\n\
+and code address.  The stack address, passed as the second argument,\n\
+should normally be a pointer to the new end of the stack when the function\n\
+was called, as a GDB value.  Similarly the code address, the third\n\
+argument, should be given as the address of the entry point of the\n\
+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,
+    uwscm_ephemeral_frame_read_register,
+    "\
+Return the value of a register in an ephemeral frame.\n\
+\n\
+  Arguments: <gdb:ephemeral-frame> string" },
+
+  { "ephemeral-frame-add-saved-register!", 3, 0, 0,
+    uwscm_ephemeral_frame_add_saved_register_x,
+    "\
+Set the saved value of a register in a ephemeral frame.\n\
+\n\
+After reading an ephemeral frame's registers and determining that it\n\
+can handle the frame, an unwinder will call this function to record\n\
+saved registers.  The values of the saved registers logically belong\n\
+to the frame that is older than the ephemeral frame being unwound, not\n\
+the ephemeral frame itself.\n\
+\n\
+The first argument should be a <gdb:ephemeral-frame> 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.  Alternately, passing #f\n\
+as the value will mark the register as unavailable." },
+
+  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 type 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);
+
+  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 1891237..9037c92 100644
--- a/gdb/guile/scm-symbol.c
+++ b/gdb/guile/scm-symbol.c
@@ -599,7 +599,9 @@  gdbscm_lookup_symbol (SCM name_scm, SCM rest)
 
       TRY_CATCH (except, RETURN_MASK_ALL)
 	{
-	  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);
 	}
       GDBSCM_HANDLE_GDB_EXCEPTION_WITH_CLEANUPS (except, cleanups);
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 2265764..6666896 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-02-20  Andy Wingo  <wingo@igalia.com>
 
 	* gdb.guile/scm-frame.exp: Add frame-read-register tests, modelled
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..96981ed
--- /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)
+    (add-frame-unwinder! (make-frame-unwinder name unwind-frame
+                                              #:priority priority
+                                              #:objfile 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..699b170
--- /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 .*deadbeef .*deadbeef .*"
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..e5cf2fa
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
@@ -0,0 +1,41 @@ 
+;; 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)
+
+(add-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")))
+    (set-ephemeral-frame-id! frame this-sp this-pc)
+    (ephemeral-frame-add-saved-register! frame "rip"
+                                         (value-cast (make-value #xdeadbeef)
+                                                     (value-type this-pc)))
+    (ephemeral-frame-add-saved-register! frame "rsp" (value-add this-sp 32))))
+
+(add-frame-unwinder!
+ (make-frame-unwinder "Synthetic" synthetic-unwinder #:priority 150
+                      #:enabled? #f))