Patchwork [v5] Add Guile frame unwinder interface

login
register
mail settings
Submitter Andy Wingo
Date April 9, 2015, 8:04 p.m.
Message ID <87zj6hdq6b.fsf@igalia.com>
Download mbox | patch
Permalink /patch/6120/
State New
Headers show

Comments

Andy Wingo - April 9, 2015, 8:04 p.m.
Hi,

Changes in this patch:

  * Remove enable-frame-unwinder!, disable-frame-unwinder!

  * set-frame-unwinder-enabled! takes frame unwinder object only, not a
    name

  * Removed priority; instead, first the unwinders for the current
    objfile are run, then the ones from the current progspace, then the
    global ones, but in an unspecified order within each locus

  * Renamed "scope" to "locus"

  * Names required to be unique only within locus

  * s/ephemeral frame/pending frame/

  * Documentation updates

  * make-unwind-info takes a frame-id object; added a make-frame-id
    constructor.

  * There's no recursion check in scm-frame-unwinder.c; instead the
    check in frame_unwind_is_unwinding is sufficient.

  * I kept the #:enabled init keyword to make-frame-unwinder, as it
    matches what frame filters do, as well as python filters/unwinders.
    Let me know if that was an error.

  * Removed dead "has-active-frame-unwinders?"

  * Fixed docstring on "all-frame-unwinders"

  * (prune-frame-unwinders!) doesn't cons if all unwinders are valid
    (i.e. their loci haven't gone away), and fix prune-frame-unwinders!
    docstring.

  * Renamed unregister-frame-unwinder! to remove-frame-unwinder!, as
    requested.

  * Fixed test coding style

  * Fixed some trailing .* in pattern

  * Test unwind before and after enabling unwinder

Still to do:

  * Make the test architecture-independent if possible

There were a couple of questions on the review from v4 of this patch
that I address specifically in the thread for v4 and don't mention
above.

WDYT? :)

Andy
From 898b8fd80dc5b533be9219472af7645e91d37985 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                                 | 210 ++++++++
 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              | 235 +++++++++
 gdb/guile/scm-frame-unwinder.c                     | 570 +++++++++++++++++++++
 gdb/guile/scm-symbol.c                             |   4 +-
 gdb/testsuite/ChangeLog                            |   7 +
 .../gdb.guile/scm-frame-unwinder-gdb.scm.in        |  31 ++
 gdb/testsuite/gdb.guile/scm-frame-unwinder.c       |  35 ++
 gdb/testsuite/gdb.guile/scm-frame-unwinder.exp     |  90 ++++
 gdb/testsuite/gdb.guile/scm-frame-unwinder.scm     |  41 ++
 18 files changed, 1324 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
Eli Zaretskii - April 10, 2015, 7:25 a.m.
> From: Andy Wingo <wingo@igalia.com>
> Cc: dje@google.com
> Date: Thu, 09 Apr 2015 22:04:12 +0200
> 
> >From 898b8fd80dc5b533be9219472af7645e91d37985 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.

OK for this part, thanks.
Doug Evans - April 27, 2015, 7 a.m.
Andy Wingo <wingo@igalia.com> writes:
> Hi,

When we last left our story ...

Just a few more nits.

>
> Changes in this patch:
>
>   * Remove enable-frame-unwinder!, disable-frame-unwinder!
>
>   * set-frame-unwinder-enabled! takes frame unwinder object only, not a
>     name
>
>   * Removed priority; instead, first the unwinders for the current
>     objfile are run, then the ones from the current progspace, then the
>     global ones, but in an unspecified order within each locus
>
>   * Renamed "scope" to "locus"
>
>   * Names required to be unique only within locus
>
>   * s/ephemeral frame/pending frame/
>
>   * Documentation updates
>
>   * make-unwind-info takes a frame-id object; added a make-frame-id
>     constructor.
>
>   * There's no recursion check in scm-frame-unwinder.c; instead the
>     check in frame_unwind_is_unwinding is sufficient.
>
>   * I kept the #:enabled init keyword to make-frame-unwinder, as it
>     matches what frame filters do, as well as python filters/unwinders.
>     Let me know if that was an error.
>
>   * Removed dead "has-active-frame-unwinders?"
>
>   * Fixed docstring on "all-frame-unwinders"
>
>   * (prune-frame-unwinders!) doesn't cons if all unwinders are valid
>     (i.e. their loci haven't gone away), and fix prune-frame-unwinders!
>     docstring.
>
>   * Renamed unregister-frame-unwinder! to remove-frame-unwinder!, as
>     requested.
>
>   * Fixed test coding style
>
>   * Fixed some trailing .* in pattern
>
>   * Test unwind before and after enabling unwinder
>
> Still to do:
>
>   * Make the test architecture-independent if possible
>
> There were a couple of questions on the review from v4 of this patch
> that I address specifically in the thread for v4 and don't mention
> above.
>
> WDYT? :)
>
> Andy

Copying over from the other thread, if you want to keep priorities, ok.
Otherwise, you'll need to remove them from the documentation below.

>
> From 898b8fd80dc5b533be9219472af7645e91d37985 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                                 | 210 ++++++++
>  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              | 235 +++++++++
>  gdb/guile/scm-frame-unwinder.c                     | 570 +++++++++++++++++++++
>  gdb/guile/scm-symbol.c                             |   4 +-
>  gdb/testsuite/ChangeLog                            |   7 +
>  .../gdb.guile/scm-frame-unwinder-gdb.scm.in        |  31 ++
>  gdb/testsuite/gdb.guile/scm-frame-unwinder.c       |  35 ++
>  gdb/testsuite/gdb.guile/scm-frame-unwinder.exp     |  90 ++++
>  gdb/testsuite/gdb.guile/scm-frame-unwinder.scm     |  41 ++
>  18 files changed, 1324 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
>
> diff --git a/gdb/ChangeLog b/gdb/ChangeLog
> index 1f15ae6..9af328d 100644
> --- a/gdb/ChangeLog
> +++ b/gdb/ChangeLog
> @@ -1,5 +1,34 @@
>  2015-04-09  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-04-09  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 4c664ae..08f432b 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -322,6 +322,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 \
> @@ -349,6 +350,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 \
> @@ -2440,6 +2442,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 21e44b9..b36506c 100644
> --- a/gdb/data-directory/Makefile.in
> +++ b/gdb/data-directory/Makefile.in
> @@ -90,6 +90,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 \
> @@ -102,6 +103,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 bcff616..d637482 100644
> --- a/gdb/doc/ChangeLog
> +++ b/gdb/doc/ChangeLog
> @@ -1,5 +1,9 @@
>  2015-04-09  Andy Wingo  <wingo@igalia.com>
>  
> +	* guile.texi (Guile Frame Unwinder API): New section.
> +
> +2015-04-09  Andy Wingo  <wingo@igalia.com>
> +
>  	* guile.texi (Guile Frame Filter API)
>  	(Writing a Frame Filter in Guile): New sections.
>  
> diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
> index e2826f0..6c850a4 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
> @@ -2119,6 +2120,215 @@ 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 provisional 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 compute the register
> +state in frame 1, given the state of frame 0.  @value{GDBN} will then
> +ask the chosen 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 built from 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 ``pending frame object''
> +corresponding the frame being unwound (in our example, frame 1).  It

to the frame

> +allows the user to read registers from that pending 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 pending 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 a pending
> +frame object.  If the unwinder procedure decides to handle the frame,
> +it should return an unwind info object.  Otherwise if 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.
> +@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{[}#:locus locus@r{]}
> +Register the frame unwinder @var{unwinder} with @value{GDBN}.
> +
> +The unwinder will be associated with a specific ``locus'', which may
> +be an objfile, a progspace, or the ``global'' locus, indicating that
> +the unwinder should run for all progspaces.  The default value of
> +@code{#f} for @var{locus} indicates that the unwinder should be
> +registered globally.  Pass an objfile or a progspace as the
> +@code{#:locus} keyword argument to associate the unwinder with a
> +specific objfile or progspace, respectively.
> +@end deffn
> +
> +Registering an unwinder on an objfile or a progspace locus has the
> +advantage that the unwinder will go away when the objfile or progspace
> +is unloaded.  To explicitly remove an unwinder from GDB, use
> +@code{remove-frame-unwinder!}.
> +
> +@deffn {Scheme Procedure} remove-frame-unwinder! unwinder
> +Removes the frame unwinder @var{unwinder} from @value{GDBN}.
> +@end deffn
> +
> +@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
> +Mark a frame unwinder as enabled, if @var{enabled?} is true, or as
> +disabled otherwise.
> +@end deffn
> +
> +Unwinders are enabled when they are created, unless @code{#:enabled?
> +#f} is passed to @code{make-frame-unwinder}.

This sentence can be deleted, just repeats what's said above.

> +
> +@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-locus 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{locus} is the objfile or progspace in
> +which the unwinder was registered, or @code{#f} otherwise.
> +@end deffn
> +
> +Frame unwinders operate on ``pending frames''.  Pending frames are
> +valid only while they are being unwound; any access to a pending frame
> +outside the extent of their unwind operation will signal an error.
> +Currently, pending 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} pending-frame-read-register frame register
> +Return the value of a register in the pending 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
> +pending frame, and also records values of registers that are saved
> +within the frame.
> +
> +@deffn {Scheme Procedure} make-unwind-info frame frame-id
> +Make an unwind info object for the pending frame @var{frame},
> +specifying @var{frame-id} as its frame ID.  @var{frame-id} should be
> +the result of a call to @code{make-frame-id}.
> +@end deffn
> +
> +@deffn {Scheme Procedure} make-frame-id fp @r{[}#:pc pc@r{]} @r{[}#:special special@r{]}
> +Build a frame ID object from the given @var{fp}, @var{pc}, and
> +@var{special} values.
> +
> +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

s/from 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.
> +
> +The @var{fp} argument is required.  While it is a good idea to specify
> +a @var{pc}, a code address is not required to build a frame ID, and so
> +@var{pc} is a keyword argument.  Call @code{make-frame-id} with
> +a @code{#:pc @var{pc}} argument to specify a code address.
> +
> +Some architectures have another stack or some other frame state store;
> +currently this is only the case for ia64.  For these odd platforms the
> +frame ID needs an additional address, which may be passed as the
> +@var{special} argument, via the @code{#:special} keyword.
> +@end deffn.
> +
> +After building an unwind info object for a pending 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 pending frame being unwound, not to the
> +pending frame itself.
> +
> +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
> +Set the saved value of a register in a pending 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.

Copying from discussion in the other thread, how about:
"... 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.

> +
>  @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..d668d01
> --- /dev/null
> +++ b/gdb/guile/lib/gdb/frame-unwinders.scm
> @@ -0,0 +1,235 @@
> +;; 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 (pending-frame-read-register
> +
> +            make-frame-id
> +
> +            make-unwind-info
> +            unwind-info-add-saved-register!
> +
> +            make-frame-unwinder
> +            frame-unwinder?
> +            frame-unwinder-name
> +            frame-unwinder-enabled?
> +            frame-unwinder-registered?
> +            frame-unwinder-procedure
> +            frame-unwinder-locus
> +
> +            find-frame-unwinder-by-name
> +
> +            register-frame-unwinder!
> +            remove-frame-unwinder!
> +            set-frame-unwinder-enabled!
> +
> +            all-frame-unwinders))
> +
> +(define-record-type <frame-id>
> +  (%make-frame-id sp pc special)
> +  frame-id?
> +  (sp frame-id-sp)
> +  (pc frame-id-pc)
> +  (special frame-id-special))
> +
> +(define* (make-frame-id sp #:key pc special)
> +  "Make a new frame identifier, or \"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, SP, should normally be a pointer to
> +the new end of the stack when the function was called, as a GDB value.
> +
> +It is possible to create a frame ID with just an SP, but it's better to
> +specify a code address (PC) via the #:pc keyword argument.  The PC
> +indicates the address of the entry point of the function.
> +
> +Some architectures have another stack or some other frame state store,
> +like ia64, and they need an additional address, which may be passed as
> +the #:special keyword argument."
> +  (%make-frame-id sp pc special))
> +
> +;; Silence unbound-toplevel warnings until we can load the extension.
> +(define %make-unwind-info #f)
> +
> +(define (make-unwind-info pending-frame frame-id)
> +  "Make an unwind info object for a given pending frame.  FRAME-ID will
> +the frame ID of the frame."

be the ...

> +  (%make-unwind-info pending-frame
> +                     (frame-id-sp frame-id)
> +                     (frame-id-pc frame-id)
> +                     (frame-id-special frame-id)))
> +
> +(define-record-type <frame-unwinder>
> +  (%make-frame-unwinder name enabled? registered? procedure locus)
> +  frame-unwinder?
> +  ;; string
> +  (name frame-unwinder-name)
> +  ;; bool
> +  (enabled? frame-unwinder-enabled? set-enabled?!)
> +  ;; bool
> +  (registered? frame-unwinder-registered? set-registered?!)
> +  ;; pending-frame -> unwind-info | #f
> +  (procedure frame-unwinder-procedure)
> +  ;; objfile | progspace | #f
> +  (locus frame-unwinder-locus set-locus!))
> +
> +(define* (make-frame-unwinder name procedure #:key (enabled? #t))
> +  "Make and return a new frame unwinder.  NAME and PROCEDURE are
> +required arguments.  Specify #:enabled? to set the enabled status of the
> +unwinder.
> +
> +The unwinder must be registered with GDB via `register-frame-unwinder!'
> +before it is active."
> +  (let ((registered? #f) (locus #f))
> +    (%make-frame-unwinder name enabled? registered? procedure locus)))
> +
> +;; List of frame unwinders.
> +(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 locus, they share a scope.
> +   ((or (not a) (not b)) #t)
> +   ;; If either is an objfile, compare their progspaces, unless the
> +   ;; objfile is invalid.
> +   ((objfile? a) (and (objfile-valid? a)
> +                      (same-scope? (objfile-progspace a) b)))
> +   ((objfile? b) (and (objfile-valid? 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 locus of UNWINDER is still valid, or otherwise #f if
> +the objfile or progspace has been removed from GDB."
> +  (let ((locus (frame-unwinder-locus unwinder)))
> +    (cond
> +     ((progspace? locus) (progspace-valid? locus))
> +     ((objfile? locus) (objfile-valid? locus))
> +     (else #t))))
> +
> +(define (prune-frame-unwinders!)
> +  "Prune frame unwinders whose objfile or progspace has gone away."
> +  (unless (and-map is-valid? *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 (all-frame-unwinders)
> +  "Return a list of all registered frame unwinders."
> +  (prune-frame-unwinders!)
> +  ;; Copy the list to prevent callers from mutating our state.
> +  (list-copy *frame-unwinders*))
> +
> +(define* (register-frame-unwinder! unwinder #:key locus replace?)
> +  "Register a frame unwinder with GDB.  Frame unwinders must be
> +registered before they will be used to unwinder backtraces.
> +
> +By default, the unwinder will be registered globally.  Specify an
> +objfile or a progspace as the #:locus keyword argument to instead
> +register the unwinder as belonging to a specific objfile or progspace.
> +
> +The unwinder's name must be unique within its locus.  GDB will raise an
> +exception if the locus already has a unwinder registered with the given
> +name, unless a true value is passed as the #:replace? keyword argument,
> +in which case any existing unwinder will be removed from its locus."
> +  (define (check-locus!)
> +    (cond
> +     ((objfile? locus)
> +      (unless (objfile-valid? locus)
> +        (error "Objfile is not valid" locus)))
> +     ((progspace? locus)
> +      (unless (progspace-valid? locus)
> +        (error "Progspace is not valid" locus)))
> +     (locus
> +      (error "Invalid locus" locus))))
> +  (define (duplicate-unwinder? other)
> +    (and (equal? (frame-unwinder-name other)
> +                 (frame-unwinder-name unwinder))
> +         (eq? (frame-unwinder-locus other) locus)))
> +
> +  (when (frame-unwinder-registered? unwinder)
> +    (error "Frame unwinder is already registered with GDB" unwinder))
> +  (check-locus!)
> +  (prune-frame-unwinders!)
> +  (let ((found (find duplicate-unwinder? *frame-unwinders*)))
> +    (when found
> +      (if replace?
> +          (remove-frame-unwinder! found)
> +          (error "Frame unwinder with this name already present in locus"
> +                 (frame-unwinder-name filter)))))
> +  (set-registered?! unwinder #t)
> +  (set-locus! unwinder locus)
> +  (set! *frame-unwinders* (cons unwinder *frame-unwinders*)))
> +
> +(define (unregister-frame-unwinder! unwinder)

s/unregister/remove/

> +  "Unregister a frame unwinder."
> +  (set-registered?! unwinder #f)
> +  (set-locus! unwinder #f)
> +  (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
> +
> +(define* (find-frame-unwinder-by-name name #:optional locus)
> +  (prune-frame-unwinders!)
> +  ;; First check for names within the locus, then within the scope.
> +  (or (find (lambda (unwinder)
> +              (and (equal? name (frame-unwinder-name unwinder))
> +                   (eq? (frame-unwinder-locus unwinder) locus)))
> +            *frame-unwinders*)
> +      (find (lambda (unwinder)
> +              (and (equal? name (frame-unwinder-name unwinder))
> +                   (same-scope? (frame-unwinder-locus unwinder) locus)))
> +            *frame-unwinders*)
> +      (error "no frame unwinder found with name" name)))
> +
> +(define (set-frame-unwinder-enabled! unwinder enabled?)
> +  "Enable or disable a frame unwinder."
> +  (set-enabled?! unwinder enabled?)
> +  *unspecified*)
> +
> +(define (unwind-frame frame)
> +  (define (try-unwind locus)
> +    (or-map (lambda (unwinder)
> +              (and (frame-unwinder-enabled? unwinder)
> +                   (eq? (frame-unwinder-locus unwinder) locus)
> +                   ((frame-unwinder-procedure unwinder) frame)))
> +            *frame-unwinders*))
> +  (prune-frame-unwinders!)
> +  (or (try-unwind (current-objfile))
> +      (try-unwind (current-progspace))
> +      ;; Try global unwinders last.
> +      (try-unwind #f)))
> +
> +(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..363830c
> --- /dev/null
> +++ b/gdb/guile/scm-frame-unwinder.c
> @@ -0,0 +1,570 @@
> +/* Scheme frame unwinding interface.
> +
> +   Copyright (C) 2015 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <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 unwind-frame 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 pending frame objects when trying
> +   to unwind frames, and unwind info objects when unwinding is successful.
> +   Here we define the names for the pending frame and unwind info Scheme
> +   data types.  */
> +static const char pending_frame_smob_name[] = "gdb:pending-frame";
> +static const char unwind_info_smob_name[] = "gdb:unwind-info";
> +
> +/* SMOB tag for pending frames and unwind info.  */
> +static scm_t_bits pending_frame_smob_tag;
> +static scm_t_bits unwind_info_smob_tag;
> +
> +/* Data associated with a pending frame.  */
> +struct uwscm_pending_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 pending frame.  */
> +  SCM frame;
> +
> +  /* The frame_id for the associated pending frame.  */
> +  struct frame_id frame_id;
> +
> +  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
> +     associated pending frame.  */
> +  SCM registers;
> +};
> +
> +/* Type predicate for pending frames.  */
> +
> +static int
> +uwscm_is_pending_frame (SCM obj)
> +{
> +  return SCM_SMOB_PREDICATE (pending_frame_smob_tag, obj);
> +}
> +
> +/* Data accessor for pending frames.  */
> +
> +static struct uwscm_pending_frame *
> +uwscm_pending_frame_data (SCM obj)
> +{
> +  gdb_assert (uwscm_is_pending_frame (obj));
> +  return (struct uwscm_pending_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 pending frame.  */
> +
> +static SCM
> +uwscm_make_pending_frame (struct frame_info *this_frame)
> +{
> +  struct uwscm_pending_frame *data;
> +
> +  data = scm_gc_malloc (sizeof (*data), pending_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 (pending_frame_smob_tag, data);
> +}
> +
> +/* Pending frames may only be accessed from Scheme within the dynamic
> +   extent of the unwind callback.  */
> +
> +static int
> +uwscm_pending_frame_is_valid (SCM pending_frame)
> +{
> +  return uwscm_pending_frame_data (pending_frame)->this_frame != NULL;
> +}
> +
> +/* Is this a pending frame that is accessible from Scheme?  */
> +
> +static int
> +uwscm_is_valid_pending_frame (SCM obj)
> +{
> +  return uwscm_is_pending_frame (obj) && uwscm_pending_frame_is_valid (obj);
> +}
> +
> +/* Is this an unwind info whose associated pending frame is valid?  */
> +
> +static int
> +uwscm_is_valid_unwind_info (SCM obj)
> +{
> +  return uwscm_is_unwind_info (obj)
> +    && uwscm_pending_frame_is_valid (uwscm_unwind_info_data (obj)->frame);
> +}
> +
> +/* Called as the unwind callback finishes to invalidate the pending
> +   frame.  */
> +
> +static void
> +uwscm_invalidate_pending_frame (SCM pending_frame)
> +{
> +  gdb_assert (uwscm_pending_frame_is_valid (pending_frame));
> +  uwscm_pending_frame_data (pending_frame)->this_frame = NULL;
> +}
> +
> +/* Raise a Scheme exception if OBJ is not a valid pending frame.  */
> +
> +static void
> +uwscm_assert_valid_pending_frame (SCM obj, const char *func_name, int pos)
> +{
> +  if (!uwscm_is_valid_pending_frame (obj))
> +    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
> +					  "valid <gdb:pending-frame>"));
> +}
> +
> +/* Raise a Scheme exception if OBJ is not an unwind info object whose
> +   associated pending 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 pending-frame sp pc special)
> +
> +   Create an unwind info structure for this pending frame.  Wrapped in Scheme to
> +   take a frame ID record.  */
> +
> +static SCM
> +gdbscm_make_unwind_info (SCM pending_frame, SCM sp, SCM pc, SCM special)
> +{
> +  struct uwscm_unwind_info *data;
> +  struct frame_id frame_id;
> +
> +  uwscm_assert_valid_pending_frame (pending_frame, FUNC_NAME, SCM_ARG1);
> +
> +  if (gdbscm_is_false (pc))
> +    frame_id = frame_id_build_wild (uwscm_value_to_addr (sp, SCM_ARG2));
> +  else if (gdbscm_is_false (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 = pending_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;
> +}
> +
> +/* (pending-frame-read-register <gdb:pending-frame> string)
> +      -> <gdb:value>
> +
> +   Sniffs a register value from a pending frame.  */
> +
> +static SCM
> +gdbscm_pending_frame_read_register (SCM pending_frame, SCM register_scm)
> +{
> +  struct uwscm_pending_frame *data;
> +  struct value *value = NULL;
> +  int regnum;
> +
> +  uwscm_assert_valid_pending_frame (pending_frame, FUNC_NAME, SCM_ARG1);
> +  data = uwscm_pending_frame_data (pending_frame);
> +  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch, FUNC_NAME,
> +				SCM_ARG2);
> +
> +  TRY
> +    {
> +      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 pending 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_pending_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_pending_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 pending 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 pending_frame = PTR2SCM (data);
> +  SCM result;
> +
> +  result = scm_call_1 (scm_variable_ref (unwind_frame), pending_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 pending_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;
> +
> +  pending_frame = uwscm_make_pending_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 (pending_frame),
> +			      gdbscm_memory_error_p);
> +
> +  /* Drop the reference to this_frame, so that future use of
> +     pending_frame from Scheme will signal an error.  */
> +  uwscm_invalidate_pending_frame (pending_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", 4, 0, 0, gdbscm_make_unwind_info,
> +    /* Wrapped and documented in Scheme; no need for docs here.  */
> +    ""},
> +
> +  { "pending-frame-read-register", 2, 0, 0,
> +    gdbscm_pending_frame_read_register,
> +    "\
> +Return the value of a register in a pending frame.\n\
> +\n\
> +  Arguments: <gdb:pending-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 a pending frame.\n\
> +\n\
> +After reading a pending 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 pending frame being unwound, not to the pending 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 pending frame and unwind info types and
> +   register gdbscm_load_frame_unwinders for calling by (gdb
> +   frame-unwinders).  */
> +
> +void
> +gdbscm_initialize_frame_unwinders (void)
> +{
> +  pending_frame_smob_tag =
> +    gdbscm_make_smob_type (pending_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 e156f57..20e6a70 100644
> --- a/gdb/testsuite/ChangeLog
> +++ b/gdb/testsuite/ChangeLog
> @@ -1,5 +1,12 @@
>  2015-04-09  Andy Wingo  <wingo@igalia.com>
>  
> +	* gdb.guile/scm-frame-unwinder.exp: New file.
> +	* gdb.guile/scm-frame-unwinder.c: New file.
> +	* gdb.guile/scm-frame-unwinder-gdb.scm.in: New file.
> +	* gdb.guile/scm-frame-unwinder.scm: New file.
> +
> +2015-04-09  Andy Wingo  <wingo@igalia.com>
> +
>  	* gdb.guile/amd64-scm-frame-filter-invalidarg.S:
>  	* gdb.guile/scm-frame-filter-gdb.scm.in:
>  	* gdb.guile/scm-frame-filter-invalidarg-gdb.scm.in:
> 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..5c658d5
> --- /dev/null
> +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
> @@ -0,0 +1,31 @@
> +;; 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.

pasto: s/filters/unwinders/

> +
> +(use-modules (gdb)
> +             (gdb frame-unwinders))
> +
> +(define* (install-unwinders! #:optional (locus (current-objfile)))
> +  (define (unwind-frame frame)
> +    #f)
> +  (define (add-unwinder! name)
> +    (register-frame-unwinder! (make-frame-unwinder name unwind-frame)
> +                              #:locus locus))
> +  (add-unwinder! "Auto-loaded dummy")
> +  (add-unwinder! "Auto-loaded dummy 2"))
> +
> +(install-unwinders!)

Can we get by with one scm file, instead of the two?
[auto-loaded and manual-loaded]

> 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..6546c67
> --- /dev/null
> +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
> @@ -0,0 +1,35 @@
> +/* Test file for the Scheme frame unwinding interface.
> +
> +   Copyright (C) 2015 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +int
> +f2 (int a)
> +{
> +  return ++a;
> +}
> +
> +int
> +f1 (int a, int b)
> +{
> +  return f2 (a) + b;
> +}
> +
> +int main (int argc, char *argv[])

Nit:

int
main (int argc, char *argv[])

> +{
> +  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..6fbe48a
> --- /dev/null
> +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
> @@ -0,0 +1,90 @@
> +# 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.

[for completeness sake]
Given the use of rip here, this test needs to check for amd64 target.
It's still an outstanding question as to whether we can easily make this
target independent.

> +
> +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 {
> +    return -1
> +}
> +
> +# 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 (map frame-unwinder-name (all-frame-unwinders))" \
> +    "\\(\"Synthetic\" \"Dummy\" \"Auto-loaded dummy 2\" \"Auto-loaded dummy\"\\)" \
> +    "all frame unwinders"
> +gdb_test "guile (map frame-unwinder-enabled? (all-frame-unwinders))" \
> +    "\\(#f #t #t #t\\)" \
> +    "all frame unwinders enabled?"
> +
> +gdb_test_no_output "guile (set-frame-unwinder-enabled! (find-frame-unwinder-by-name \"Dummy\") #f)" \
> +    "disable dummy"
> +gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
> +    "#f" \
> +    "dummy not enabled"
> +gdb_test_no_output "guile (set-frame-unwinder-enabled! (find-frame-unwinder-by-name \"Dummy\") #t)" \
> +    "re-enable dummy"
> +gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
> +    "#t" \
> +    "dummy re-enabled"
> +
> +gdb_test "bt 10" \
> +    "\#0  main .*scm-frame-unwinder.c:34" \
> +    "backtrace from main"
> +
> +gdb_test_no_output "guile (set-frame-unwinder-enabled! (find-frame-unwinder-by-name \"Synthetic\") #t)" \
> +    "enable synthetic unwinder"
> +
> +gdb_breakpoint "f2"
> +gdb_continue_to_breakpoint "breakpoint at f2"
> +
> +# It would be nice to the backtrace at this position with and without
> +# the unwinder, but currently enabling an unwinder doesn't invalidate
> +# the frame cache.

Ah, ok.
We can fix this as a followup.

> +
> +gdb_test "bt 10"\
> +    "\#0  f2 .*scm-frame-unwinder.c:23\r\n\#1 .*\#9  0x0*cabba9e5 in \\?\\? \\(\\)\r\n\\(More stack frames follow...\\)" \

Fetch the line number with gdb_get_line_number.

> +    "backtrace full of cabbages"
> 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..8f91527
> --- /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)
> +
> +(register-frame-unwinder! (make-frame-unwinder "Dummy" dummy-unwinder))
> +
> +
> +(define (synthetic-unwinder frame)
> +  ;; Increment the stack pointer, set IP to 0xdeadbeef
> +  (let* ((this-pc (pending-frame-read-register frame "rip"))
> +         (this-sp (pending-frame-read-register frame "rsp"))
> +         (prev-pc (value-cast (make-value #xcabba9e5) (value-type this-pc)))
> +         (prev-sp (value-add this-sp 32))
> +         (frame-id (make-frame-id this-sp #:pc this-pc))
> +         (unwind-info (make-unwind-info frame frame-id)))
> +    (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 #:enabled? #f))

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 1f15ae6..9af328d 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,34 @@ 
 2015-04-09  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-04-09  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 4c664ae..08f432b 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -322,6 +322,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 \
@@ -349,6 +350,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 \
@@ -2440,6 +2442,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 21e44b9..b36506c 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -90,6 +90,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 \
@@ -102,6 +103,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 bcff616..d637482 100644
--- a/gdb/doc/ChangeLog
+++ b/gdb/doc/ChangeLog
@@ -1,5 +1,9 @@ 
 2015-04-09  Andy Wingo  <wingo@igalia.com>
 
+	* guile.texi (Guile Frame Unwinder API): New section.
+
+2015-04-09  Andy Wingo  <wingo@igalia.com>
+
 	* guile.texi (Guile Frame Filter API)
 	(Writing a Frame Filter in Guile): New sections.
 
diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
index e2826f0..6c850a4 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
@@ -2119,6 +2120,215 @@  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 provisional 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 compute the register
+state in frame 1, given the state of frame 0.  @value{GDBN} will then
+ask the chosen 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 built from 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 ``pending frame object''
+corresponding the frame being unwound (in our example, frame 1).  It
+allows the user to read registers from that pending 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 pending 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 a pending
+frame object.  If the unwinder procedure decides to handle the frame,
+it should return an unwind info object.  Otherwise if 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.
+@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{[}#:locus locus@r{]}
+Register the frame unwinder @var{unwinder} with @value{GDBN}.
+
+The unwinder will be associated with a specific ``locus'', which may
+be an objfile, a progspace, or the ``global'' locus, indicating that
+the unwinder should run for all progspaces.  The default value of
+@code{#f} for @var{locus} indicates that the unwinder should be
+registered globally.  Pass an objfile or a progspace as the
+@code{#:locus} keyword argument to associate the unwinder with a
+specific objfile or progspace, respectively.
+@end deffn
+
+Registering an unwinder on an objfile or a progspace locus has the
+advantage that the unwinder will go away when the objfile or progspace
+is unloaded.  To explicitly remove an unwinder from GDB, use
+@code{remove-frame-unwinder!}.
+
+@deffn {Scheme Procedure} remove-frame-unwinder! unwinder
+Removes the frame unwinder @var{unwinder} from @value{GDBN}.
+@end deffn
+
+@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
+Mark a frame unwinder as enabled, if @var{enabled?} is true, or as
+disabled otherwise.
+@end deffn
+
+Unwinders are enabled when they are created, unless @code{#:enabled?
+#f} is passed to @code{make-frame-unwinder}.
+
+@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-locus 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{locus} is the objfile or progspace in
+which the unwinder was registered, or @code{#f} otherwise.
+@end deffn
+
+Frame unwinders operate on ``pending frames''.  Pending frames are
+valid only while they are being unwound; any access to a pending frame
+outside the extent of their unwind operation will signal an error.
+Currently, pending 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} pending-frame-read-register frame register
+Return the value of a register in the pending 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
+pending frame, and also records values of registers that are saved
+within the frame.
+
+@deffn {Scheme Procedure} make-unwind-info frame frame-id
+Make an unwind info object for the pending frame @var{frame},
+specifying @var{frame-id} as its frame ID.  @var{frame-id} should be
+the result of a call to @code{make-frame-id}.
+@end deffn
+
+@deffn {Scheme Procedure} make-frame-id fp @r{[}#:pc pc@r{]} @r{[}#:special special@r{]}
+Build a frame ID object from the given @var{fp}, @var{pc}, and
+@var{special} values.
+
+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.
+
+The @var{fp} argument is required.  While it is a good idea to specify
+a @var{pc}, a code address is not required to build a frame ID, and so
+@var{pc} is a keyword argument.  Call @code{make-frame-id} with
+a @code{#:pc @var{pc}} argument to specify a code address.
+
+Some architectures have another stack or some other frame state store;
+currently this is only the case for ia64.  For these odd platforms the
+frame ID needs an additional address, which may be passed as the
+@var{special} argument, via the @code{#:special} keyword.
+@end deffn.
+
+After building an unwind info object for a pending 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 pending frame being unwound, not to the
+pending frame itself.
+
+@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
+Set the saved value of a register in a pending 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..d668d01
--- /dev/null
+++ b/gdb/guile/lib/gdb/frame-unwinders.scm
@@ -0,0 +1,235 @@ 
+;; 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 (pending-frame-read-register
+
+            make-frame-id
+
+            make-unwind-info
+            unwind-info-add-saved-register!
+
+            make-frame-unwinder
+            frame-unwinder?
+            frame-unwinder-name
+            frame-unwinder-enabled?
+            frame-unwinder-registered?
+            frame-unwinder-procedure
+            frame-unwinder-locus
+
+            find-frame-unwinder-by-name
+
+            register-frame-unwinder!
+            remove-frame-unwinder!
+            set-frame-unwinder-enabled!
+
+            all-frame-unwinders))
+
+(define-record-type <frame-id>
+  (%make-frame-id sp pc special)
+  frame-id?
+  (sp frame-id-sp)
+  (pc frame-id-pc)
+  (special frame-id-special))
+
+(define* (make-frame-id sp #:key pc special)
+  "Make a new frame identifier, or \"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, SP, should normally be a pointer to
+the new end of the stack when the function was called, as a GDB value.
+
+It is possible to create a frame ID with just an SP, but it's better to
+specify a code address (PC) via the #:pc keyword argument.  The PC
+indicates the address of the entry point of the function.
+
+Some architectures have another stack or some other frame state store,
+like ia64, and they need an additional address, which may be passed as
+the #:special keyword argument."
+  (%make-frame-id sp pc special))
+
+;; Silence unbound-toplevel warnings until we can load the extension.
+(define %make-unwind-info #f)
+
+(define (make-unwind-info pending-frame frame-id)
+  "Make an unwind info object for a given pending frame.  FRAME-ID will
+the frame ID of the frame."
+  (%make-unwind-info pending-frame
+                     (frame-id-sp frame-id)
+                     (frame-id-pc frame-id)
+                     (frame-id-special frame-id)))
+
+(define-record-type <frame-unwinder>
+  (%make-frame-unwinder name enabled? registered? procedure locus)
+  frame-unwinder?
+  ;; string
+  (name frame-unwinder-name)
+  ;; bool
+  (enabled? frame-unwinder-enabled? set-enabled?!)
+  ;; bool
+  (registered? frame-unwinder-registered? set-registered?!)
+  ;; pending-frame -> unwind-info | #f
+  (procedure frame-unwinder-procedure)
+  ;; objfile | progspace | #f
+  (locus frame-unwinder-locus set-locus!))
+
+(define* (make-frame-unwinder name procedure #:key (enabled? #t))
+  "Make and return a new frame unwinder.  NAME and PROCEDURE are
+required arguments.  Specify #:enabled? to set the enabled status of the
+unwinder.
+
+The unwinder must be registered with GDB via `register-frame-unwinder!'
+before it is active."
+  (let ((registered? #f) (locus #f))
+    (%make-frame-unwinder name enabled? registered? procedure locus)))
+
+;; List of frame unwinders.
+(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 locus, they share a scope.
+   ((or (not a) (not b)) #t)
+   ;; If either is an objfile, compare their progspaces, unless the
+   ;; objfile is invalid.
+   ((objfile? a) (and (objfile-valid? a)
+                      (same-scope? (objfile-progspace a) b)))
+   ((objfile? b) (and (objfile-valid? 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 locus of UNWINDER is still valid, or otherwise #f if
+the objfile or progspace has been removed from GDB."
+  (let ((locus (frame-unwinder-locus unwinder)))
+    (cond
+     ((progspace? locus) (progspace-valid? locus))
+     ((objfile? locus) (objfile-valid? locus))
+     (else #t))))
+
+(define (prune-frame-unwinders!)
+  "Prune frame unwinders whose objfile or progspace has gone away."
+  (unless (and-map is-valid? *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 (all-frame-unwinders)
+  "Return a list of all registered frame unwinders."
+  (prune-frame-unwinders!)
+  ;; Copy the list to prevent callers from mutating our state.
+  (list-copy *frame-unwinders*))
+
+(define* (register-frame-unwinder! unwinder #:key locus replace?)
+  "Register a frame unwinder with GDB.  Frame unwinders must be
+registered before they will be used to unwinder backtraces.
+
+By default, the unwinder will be registered globally.  Specify an
+objfile or a progspace as the #:locus keyword argument to instead
+register the unwinder as belonging to a specific objfile or progspace.
+
+The unwinder's name must be unique within its locus.  GDB will raise an
+exception if the locus already has a unwinder registered with the given
+name, unless a true value is passed as the #:replace? keyword argument,
+in which case any existing unwinder will be removed from its locus."
+  (define (check-locus!)
+    (cond
+     ((objfile? locus)
+      (unless (objfile-valid? locus)
+        (error "Objfile is not valid" locus)))
+     ((progspace? locus)
+      (unless (progspace-valid? locus)
+        (error "Progspace is not valid" locus)))
+     (locus
+      (error "Invalid locus" locus))))
+  (define (duplicate-unwinder? other)
+    (and (equal? (frame-unwinder-name other)
+                 (frame-unwinder-name unwinder))
+         (eq? (frame-unwinder-locus other) locus)))
+
+  (when (frame-unwinder-registered? unwinder)
+    (error "Frame unwinder is already registered with GDB" unwinder))
+  (check-locus!)
+  (prune-frame-unwinders!)
+  (let ((found (find duplicate-unwinder? *frame-unwinders*)))
+    (when found
+      (if replace?
+          (remove-frame-unwinder! found)
+          (error "Frame unwinder with this name already present in locus"
+                 (frame-unwinder-name filter)))))
+  (set-registered?! unwinder #t)
+  (set-locus! unwinder locus)
+  (set! *frame-unwinders* (cons unwinder *frame-unwinders*)))
+
+(define (unregister-frame-unwinder! unwinder)
+  "Unregister a frame unwinder."
+  (set-registered?! unwinder #f)
+  (set-locus! unwinder #f)
+  (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
+
+(define* (find-frame-unwinder-by-name name #:optional locus)
+  (prune-frame-unwinders!)
+  ;; First check for names within the locus, then within the scope.
+  (or (find (lambda (unwinder)
+              (and (equal? name (frame-unwinder-name unwinder))
+                   (eq? (frame-unwinder-locus unwinder) locus)))
+            *frame-unwinders*)
+      (find (lambda (unwinder)
+              (and (equal? name (frame-unwinder-name unwinder))
+                   (same-scope? (frame-unwinder-locus unwinder) locus)))
+            *frame-unwinders*)
+      (error "no frame unwinder found with name" name)))
+
+(define (set-frame-unwinder-enabled! unwinder enabled?)
+  "Enable or disable a frame unwinder."
+  (set-enabled?! unwinder enabled?)
+  *unspecified*)
+
+(define (unwind-frame frame)
+  (define (try-unwind locus)
+    (or-map (lambda (unwinder)
+              (and (frame-unwinder-enabled? unwinder)
+                   (eq? (frame-unwinder-locus unwinder) locus)
+                   ((frame-unwinder-procedure unwinder) frame)))
+            *frame-unwinders*))
+  (prune-frame-unwinders!)
+  (or (try-unwind (current-objfile))
+      (try-unwind (current-progspace))
+      ;; Try global unwinders last.
+      (try-unwind #f)))
+
+(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..363830c
--- /dev/null
+++ b/gdb/guile/scm-frame-unwinder.c
@@ -0,0 +1,570 @@ 
+/* Scheme frame unwinding interface.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <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 unwind-frame 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 pending frame objects when trying
+   to unwind frames, and unwind info objects when unwinding is successful.
+   Here we define the names for the pending frame and unwind info Scheme
+   data types.  */
+static const char pending_frame_smob_name[] = "gdb:pending-frame";
+static const char unwind_info_smob_name[] = "gdb:unwind-info";
+
+/* SMOB tag for pending frames and unwind info.  */
+static scm_t_bits pending_frame_smob_tag;
+static scm_t_bits unwind_info_smob_tag;
+
+/* Data associated with a pending frame.  */
+struct uwscm_pending_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 pending frame.  */
+  SCM frame;
+
+  /* The frame_id for the associated pending frame.  */
+  struct frame_id frame_id;
+
+  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
+     associated pending frame.  */
+  SCM registers;
+};
+
+/* Type predicate for pending frames.  */
+
+static int
+uwscm_is_pending_frame (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (pending_frame_smob_tag, obj);
+}
+
+/* Data accessor for pending frames.  */
+
+static struct uwscm_pending_frame *
+uwscm_pending_frame_data (SCM obj)
+{
+  gdb_assert (uwscm_is_pending_frame (obj));
+  return (struct uwscm_pending_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 pending frame.  */
+
+static SCM
+uwscm_make_pending_frame (struct frame_info *this_frame)
+{
+  struct uwscm_pending_frame *data;
+
+  data = scm_gc_malloc (sizeof (*data), pending_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 (pending_frame_smob_tag, data);
+}
+
+/* Pending frames may only be accessed from Scheme within the dynamic
+   extent of the unwind callback.  */
+
+static int
+uwscm_pending_frame_is_valid (SCM pending_frame)
+{
+  return uwscm_pending_frame_data (pending_frame)->this_frame != NULL;
+}
+
+/* Is this a pending frame that is accessible from Scheme?  */
+
+static int
+uwscm_is_valid_pending_frame (SCM obj)
+{
+  return uwscm_is_pending_frame (obj) && uwscm_pending_frame_is_valid (obj);
+}
+
+/* Is this an unwind info whose associated pending frame is valid?  */
+
+static int
+uwscm_is_valid_unwind_info (SCM obj)
+{
+  return uwscm_is_unwind_info (obj)
+    && uwscm_pending_frame_is_valid (uwscm_unwind_info_data (obj)->frame);
+}
+
+/* Called as the unwind callback finishes to invalidate the pending
+   frame.  */
+
+static void
+uwscm_invalidate_pending_frame (SCM pending_frame)
+{
+  gdb_assert (uwscm_pending_frame_is_valid (pending_frame));
+  uwscm_pending_frame_data (pending_frame)->this_frame = NULL;
+}
+
+/* Raise a Scheme exception if OBJ is not a valid pending frame.  */
+
+static void
+uwscm_assert_valid_pending_frame (SCM obj, const char *func_name, int pos)
+{
+  if (!uwscm_is_valid_pending_frame (obj))
+    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
+					  "valid <gdb:pending-frame>"));
+}
+
+/* Raise a Scheme exception if OBJ is not an unwind info object whose
+   associated pending 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 pending-frame sp pc special)
+
+   Create an unwind info structure for this pending frame.  Wrapped in Scheme to
+   take a frame ID record.  */
+
+static SCM
+gdbscm_make_unwind_info (SCM pending_frame, SCM sp, SCM pc, SCM special)
+{
+  struct uwscm_unwind_info *data;
+  struct frame_id frame_id;
+
+  uwscm_assert_valid_pending_frame (pending_frame, FUNC_NAME, SCM_ARG1);
+
+  if (gdbscm_is_false (pc))
+    frame_id = frame_id_build_wild (uwscm_value_to_addr (sp, SCM_ARG2));
+  else if (gdbscm_is_false (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 = pending_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;
+}
+
+/* (pending-frame-read-register <gdb:pending-frame> string)
+      -> <gdb:value>
+
+   Sniffs a register value from a pending frame.  */
+
+static SCM
+gdbscm_pending_frame_read_register (SCM pending_frame, SCM register_scm)
+{
+  struct uwscm_pending_frame *data;
+  struct value *value = NULL;
+  int regnum;
+
+  uwscm_assert_valid_pending_frame (pending_frame, FUNC_NAME, SCM_ARG1);
+  data = uwscm_pending_frame_data (pending_frame);
+  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch, FUNC_NAME,
+				SCM_ARG2);
+
+  TRY
+    {
+      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 pending 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_pending_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_pending_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 pending 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 pending_frame = PTR2SCM (data);
+  SCM result;
+
+  result = scm_call_1 (scm_variable_ref (unwind_frame), pending_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 pending_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;
+
+  pending_frame = uwscm_make_pending_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 (pending_frame),
+			      gdbscm_memory_error_p);
+
+  /* Drop the reference to this_frame, so that future use of
+     pending_frame from Scheme will signal an error.  */
+  uwscm_invalidate_pending_frame (pending_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", 4, 0, 0, gdbscm_make_unwind_info,
+    /* Wrapped and documented in Scheme; no need for docs here.  */
+    ""},
+
+  { "pending-frame-read-register", 2, 0, 0,
+    gdbscm_pending_frame_read_register,
+    "\
+Return the value of a register in a pending frame.\n\
+\n\
+  Arguments: <gdb:pending-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 a pending frame.\n\
+\n\
+After reading a pending 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 pending frame being unwound, not to the pending 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 pending frame and unwind info types and
+   register gdbscm_load_frame_unwinders for calling by (gdb
+   frame-unwinders).  */
+
+void
+gdbscm_initialize_frame_unwinders (void)
+{
+  pending_frame_smob_tag =
+    gdbscm_make_smob_type (pending_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 e156f57..20e6a70 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,5 +1,12 @@ 
 2015-04-09  Andy Wingo  <wingo@igalia.com>
 
+	* gdb.guile/scm-frame-unwinder.exp: New file.
+	* gdb.guile/scm-frame-unwinder.c: New file.
+	* gdb.guile/scm-frame-unwinder-gdb.scm.in: New file.
+	* gdb.guile/scm-frame-unwinder.scm: New file.
+
+2015-04-09  Andy Wingo  <wingo@igalia.com>
+
 	* gdb.guile/amd64-scm-frame-filter-invalidarg.S:
 	* gdb.guile/scm-frame-filter-gdb.scm.in:
 	* gdb.guile/scm-frame-filter-invalidarg-gdb.scm.in:
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..5c658d5
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
@@ -0,0 +1,31 @@ 
+;; 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 (locus (current-objfile)))
+  (define (unwind-frame frame)
+    #f)
+  (define (add-unwinder! name)
+    (register-frame-unwinder! (make-frame-unwinder name unwind-frame)
+                              #:locus locus))
+  (add-unwinder! "Auto-loaded dummy")
+  (add-unwinder! "Auto-loaded dummy 2"))
+
+(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..6546c67
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
@@ -0,0 +1,35 @@ 
+/* Test file for the Scheme frame unwinding interface.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+f2 (int a)
+{
+  return ++a;
+}
+
+int
+f1 (int a, int b)
+{
+  return f2 (a) + b;
+}
+
+int main (int argc, char *argv[])
+{
+  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..6fbe48a
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
@@ -0,0 +1,90 @@ 
+# 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 {
+    return -1
+}
+
+# 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 (map frame-unwinder-name (all-frame-unwinders))" \
+    "\\(\"Synthetic\" \"Dummy\" \"Auto-loaded dummy 2\" \"Auto-loaded dummy\"\\)" \
+    "all frame unwinders"
+gdb_test "guile (map frame-unwinder-enabled? (all-frame-unwinders))" \
+    "\\(#f #t #t #t\\)" \
+    "all frame unwinders enabled?"
+
+gdb_test_no_output "guile (set-frame-unwinder-enabled! (find-frame-unwinder-by-name \"Dummy\") #f)" \
+    "disable dummy"
+gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
+    "#f" \
+    "dummy not enabled"
+gdb_test_no_output "guile (set-frame-unwinder-enabled! (find-frame-unwinder-by-name \"Dummy\") #t)" \
+    "re-enable dummy"
+gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
+    "#t" \
+    "dummy re-enabled"
+
+gdb_test "bt 10" \
+    "\#0  main .*scm-frame-unwinder.c:34" \
+    "backtrace from main"
+
+gdb_test_no_output "guile (set-frame-unwinder-enabled! (find-frame-unwinder-by-name \"Synthetic\") #t)" \
+    "enable synthetic unwinder"
+
+gdb_breakpoint "f2"
+gdb_continue_to_breakpoint "breakpoint at f2"
+
+# It would be nice to the backtrace at this position with and without
+# the unwinder, but currently enabling an unwinder doesn't invalidate
+# the frame cache.
+
+gdb_test "bt 10"\
+    "\#0  f2 .*scm-frame-unwinder.c:23\r\n\#1 .*\#9  0x0*cabba9e5 in \\?\\? \\(\\)\r\n\\(More stack frames follow...\\)" \
+    "backtrace full of cabbages"
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..8f91527
--- /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)
+
+(register-frame-unwinder! (make-frame-unwinder "Dummy" dummy-unwinder))
+
+
+(define (synthetic-unwinder frame)
+  ;; Increment the stack pointer, set IP to 0xdeadbeef
+  (let* ((this-pc (pending-frame-read-register frame "rip"))
+         (this-sp (pending-frame-read-register frame "rsp"))
+         (prev-pc (value-cast (make-value #xcabba9e5) (value-type this-pc)))
+         (prev-sp (value-add this-sp 32))
+         (frame-id (make-frame-id this-sp #:pc this-pc))
+         (unwind-info (make-unwind-info frame frame-id)))
+    (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 #:enabled? #f))