public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] Add Guile frame unwinder interface
@ 2015-03-05 15:58 Andy Wingo
  2015-03-09 10:34 ` [PATCH v2] " Andy Wingo
  0 siblings, 1 reply; 9+ messages in thread
From: Andy Wingo @ 2015-03-05 15:58 UTC (permalink / raw)
  To: gdb-patches; +Cc: asmundak

[-- Attachment #1: Type: text/plain, Size: 3091 bytes --]

Hi,

This is an implementation of a frame unwinder interface in the spirit of
Alexander's work in this thread:

  http://thread.gmane.org/gmane.comp.gdb.patches/103360/focus=105202

No documentation yet, and I am still wondering how to test it
appropriately.  However it does seem some feedback could be useful
before I document the wrong thing; particualarly I would like feedback
on the changes to frame-unwind.c and frame.c.

However happily it does work in V8; eliding some helpers, the
implementation looks like this:

  (use-modules (gdb frame-unwinders))

  (define (unwind-v8-frame frame)
    (let ((isolate (cached-current-isolate)))
      (when isolate
        (let* ((this-pc (ephemeral-frame-read-register frame "rip"))
               (this-fp (ephemeral-frame-read-register frame "rbp"))
               (code (lookup-code-for-pc this-pc isolate)))
          (when code
            (set-ephemeral-frame-id! frame this-fp (code-instruction-start code))
            (let* ((type (if (code-optimized? code) 'javascript 'optimized))
                   (prev-pc-address (compute-standard-frame-pc-address this-fp))
                   (prev-sp (compute-frame-older-sp this-fp type))
                   (prev-fp (compute-standard-frame-older-fp this-fp))
                   (prev-pc (value-dereference prev-pc-address)))
              (ephemeral-frame-add-saved-register! frame "rsp" prev-sp)
              (ephemeral-frame-add-saved-register! frame "rbp" prev-fp)
              (ephemeral-frame-add-saved-register! frame "rip" prev-pc)))))))

  (define* (install-frame-unwinders #:optional (objfile (current-objfile)))
    (add-frame-unwinder!
     (make-frame-unwinder "guile-v8-frame-unwinder" unwind-v8-frame)))

And most happily, it requires no changes in V8 itself.  Yaaay :)  With
an appropriate frame filter, a backtrace looks like this:

#0  0x00000d3c5b0661a1 in TestCase () at /hack/v8/test/mjsunit/debug-step-4-in-frame.js:94
#1  0x00000d3c5b06a3d3 in  () at /hack/v8/test/mjsunit/debug-step-4-in-frame.js:112
#2  0x00000d3c5b02c620 in [internal frame] ()
#3  0x00000d3c5b014d31 in [entry frame] ()
#4  0x0000000000b4e949 in v8::internal::Invoke([...]) at ../src/execution.cc:128
#5  0x0000000000b4ed23 in v8::internal::Execution::Call([...]) at ../src/execution.cc:179
#6  0x0000000000a3f813 in v8::Script::Run([...]) at ../src/api.cc:1514
#7  0x0000000000a149fa in v8::Shell::ExecuteString([...]) at ../src/d8.cc:281
#8  0x0000000000a194eb in v8::SourceGroup::Execute([...]) at ../src/d8.cc:1213
#9  0x0000000000a1a128 in v8::Shell::RunMain([...]) at ../src/d8.cc:1448
#10 0x0000000000a1efdc in v8::Shell::Main([...]) at ../src/d8.cc:1721
#11 0x0000000000a1f143 in main([...]) at ../src/d8.cc:1757

instead of this:

#0  0x00000d3c5b0661a1 in ?? ()
#1  0x0000000002404940 in ?? ()
#2  0x0000219b8fc5d779 in ?? ()
#3  0x000018a8ddbf01d9 in ?? ()
#4  0x0000219b8fc62a81 in ?? ()
#5  0x000018a8ddbf0179 in ?? ()
#6  0x00007fffffffd500 in ?? ()
#7  0x00000d3c5b06a3d3 in ?? ()
#8  0x00001df7db238fb1 in ?? ()
#9  0x0000000000000000 in ?? ()

Yaaaaaaaaaaay :)

Regards,

Andy


[-- Attachment #2: 0001-Add-Guile-frame-unwinder-interface.patch --]
[-- Type: text/plain, Size: 37573 bytes --]

From 948e1bba3bd08fba22c44e4afe18436d84220147 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/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 an attempt to recursively
	unwind from the sentinel, and return NULL.
	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
	(frame_unwind_is_unwinding_innermost_frame): New declaration.
	* frame-unwind.c (unwinding_innermost_frame): New file-local
	variable.
	(innermost_frame_unwind_begin, innermost_frame_unwind_end): New
	functions.
	(frame_unwind_is_unwinding_innermost_frame): New exported
	predicate.
	(frame_unwind_find_by_frame): Arrange for
	frame_unwind_is_unwinding_innermost_frame to return true when
	unwinding the innermost frame.
	(frame_unwind_got_bytes): Make buf arg const.
	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
	frame-unwinders.scm.
	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
	(scm-frame-unwinder.o): New target.
---
 gdb/ChangeLog                         |  32 ++
 gdb/Makefile.in                       |   6 +
 gdb/data-directory/Makefile.in        |   2 +
 gdb/frame-unwind.c                    |  43 ++-
 gdb/frame-unwind.h                    |   7 +-
 gdb/frame.c                           |  13 +
 gdb/guile/guile-internal.h            |   1 +
 gdb/guile/guile.c                     |   1 +
 gdb/guile/lib/gdb/frame-unwinders.scm | 213 +++++++++++++
 gdb/guile/scm-frame-unwinder.c        | 566 ++++++++++++++++++++++++++++++++++
 gdb/guile/scm-symbol.c                |   4 +-
 11 files changed, 882 insertions(+), 6 deletions(-)
 create mode 100644 gdb/guile/lib/gdb/frame-unwinders.scm
 create mode 100644 gdb/guile/scm-frame-unwinder.c

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index d55daf6..f9ea8e2 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,35 @@
+2015-03-05  Andy Wingo  <wingo@igalia.com>
+
+	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
+	is no selected frame and no block is selected; instead, fall back
+	to the current frame.
+	* guile/scm-frame-unwinder.c: New file.
+	* guile/lib/gdb/frame-unwinders.scm: New file.
+	* guile/guile.c (initialize_gdb_module): Call
+	gdbscm_initialize_frame_unwinders.
+	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
+	declaration.
+	* frame.c (get_prev_frame): Detect an attempt to recursively
+	unwind from the sentinel, and return NULL.
+	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
+	(frame_unwind_is_unwinding_innermost_frame): New declaration.
+	* frame-unwind.c (unwinding_innermost_frame): New file-local
+	variable.
+	(innermost_frame_unwind_begin, innermost_frame_unwind_end): New
+	functions.
+	(frame_unwind_is_unwinding_innermost_frame): New exported
+	predicate.
+	(frame_unwind_find_by_frame): Arrange for
+	frame_unwind_is_unwinding_innermost_frame to return true when
+	unwinding the innermost frame.
+	(frame_unwind_got_bytes): Make buf arg const.
+	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
+	frame-unwinders.scm.
+	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
+	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
+	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
+	(scm-frame-unwinder.o): New target.
+
 2015-02-20  Andy Wingo  <wingo@igalia.com>
 
 	* guile/scm-value.c (gdbscm_value_dynamic_type): Fix typo in which
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 0ab4c51..c9110f0 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -315,6 +315,7 @@ SUBDIR_GUILE_OBS = \
 	scm-exception.o \
 	scm-frame.o \
 	scm-frame-filter.o \
+	scm-frame-unwinder.o \
 	scm-gsmob.o \
 	scm-iterator.o \
 	scm-lazy-string.o \
@@ -342,6 +343,7 @@ SUBDIR_GUILE_SRCS = \
 	guile/scm-exception.c \
 	guile/scm-frame.c \
 	guile/scm-frame-filter.c \
+	guile/scm-frame-unwinder.c \
 	guile/scm-gsmob.c \
 	guile/scm-iterator.c \
 	guile/scm-lazy-string.c \
@@ -2418,6 +2420,10 @@ scm-frame-filter.o: $(srcdir)/guile/scm-frame-filter.c
 	$(COMPILE) $(srcdir)/guile/scm-frame-filter.c
 	$(POSTCOMPILE)
 
+scm-frame-unwinder.o: $(srcdir)/guile/scm-frame-unwinder.c
+	$(COMPILE) $(srcdir)/guile/scm-frame-unwinder.c
+	$(POSTCOMPILE)
+
 scm-gsmob.o: $(srcdir)/guile/scm-gsmob.c
 	$(COMPILE) $(srcdir)/guile/scm-gsmob.c
 	$(POSTCOMPILE)
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 75aab1b..bb2722d 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -91,6 +91,7 @@ GUILE_SOURCE_FILES = \
 	gdb/boot.scm \
 	gdb/experimental.scm \
 	gdb/frame-filters.scm \
+	gdb/frame-unwinders.scm \
 	gdb/init.scm \
 	gdb/iterator.scm \
 	gdb/printing.scm \
@@ -101,6 +102,7 @@ GUILE_COMPILED_FILES = \
 	./gdb.go \
 	gdb/experimental.go \
 	gdb/frame-filters.go \
+	gdb/frame-unwinders.go \
 	gdb/iterator.go \
 	gdb/printing.go \
 	gdb/support.go \
diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c
index e73650a..67f19ec 100644
--- a/gdb/frame-unwind.c
+++ b/gdb/frame-unwind.c
@@ -129,6 +129,33 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
   gdb_assert_not_reached ("frame_unwind_try_unwinder");
 }
 
+/* Nonzero if we are finding the unwinder for the innermost frame.  */
+static int unwinding_innermost_frame = 0;
+
+static void
+innermost_frame_unwind_begin (void)
+{
+  if (unwinding_innermost_frame)
+    internal_error (__FILE__, __LINE__,
+		    _("Recursion detected while unwinding innermost frame."));
+
+  unwinding_innermost_frame = 1;
+}
+
+static void
+innermost_frame_unwind_end (void)
+{
+  gdb_assert (unwinding_innermost_frame);
+
+  unwinding_innermost_frame = 0;
+}
+
+int
+frame_unwind_is_unwinding_innermost_frame (void)
+{
+  return unwinding_innermost_frame;
+}
+
 /* Iterate through sniffers for THIS_FRAME frame until one returns with an
    unwinder implementation.  THIS_FRAME->UNWIND must be NULL, it will get set
    by this function.  Possibly initialize THIS_CACHE.  */
@@ -141,23 +168,30 @@ frame_unwind_find_by_frame (struct frame_info *this_frame, void **this_cache)
   struct frame_unwind_table_entry *entry;
   const struct frame_unwind *unwinder_from_target;
 
+  if (frame_relative_level (this_frame) == 0)
+    innermost_frame_unwind_begin ();
+
   unwinder_from_target = target_get_unwinder ();
   if (unwinder_from_target != NULL
       && frame_unwind_try_unwinder (this_frame, this_cache,
                                    unwinder_from_target))
-    return;
+    goto done;
 
   unwinder_from_target = target_get_tailcall_unwinder ();
   if (unwinder_from_target != NULL
       && frame_unwind_try_unwinder (this_frame, this_cache,
                                    unwinder_from_target))
-    return;
+    goto done;
 
   for (entry = table->list; entry != NULL; entry = entry->next)
     if (frame_unwind_try_unwinder (this_frame, this_cache, entry->unwinder))
-      return;
+      goto done;
 
   internal_error (__FILE__, __LINE__, _("frame_unwind_find_by_frame failed"));
+
+ done:
+  if (frame_relative_level (this_frame) == 0)
+    innermost_frame_unwind_end ();
 }
 
 /* A default frame sniffer which always accepts the frame.  Used by
@@ -249,7 +283,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..7f12211 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 a nonzero if we are in the process of finding an unwinder for the
+   innermost frame.  See the comments in get_current_frame().  */
+
+extern int frame_unwind_is_unwinding_innermost_frame (void);
+
 /* Helper functions for value-based register unwinding.  These return
    a (possibly lazy) value of the appropriate type.  */
 
@@ -210,7 +215,7 @@ struct value *frame_unwind_got_constant (struct frame_info *frame, int regnum,
    inside BUF.  */
 
 struct value *frame_unwind_got_bytes (struct frame_info *frame, int regnum,
-                                      gdb_byte *buf);
+                                      const gdb_byte *buf);
 
 /* Return a value which indicates that FRAME's saved version of REGNUM
    has a known constant (computed) value of ADDR.  Convert the
diff --git a/gdb/frame.c b/gdb/frame.c
index 6b1be94..46bb8a7 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -2209,6 +2209,19 @@ get_prev_frame (struct frame_info *this_frame)
       return NULL;
     }
 
+  /* Unwinders implemented in Python or Scheme could end up calling a GDB
+     function that gets the current frame, for example to get the current
+     architecture.  When in the process of unwinding the innermost frame, this
+     would cause unbounded recursion.  Instead short-circuit the computation,
+     which will cause callers to fall back to the sentinel frame.  */
+  if (this_frame->level == -1
+      && frame_unwind_is_unwinding_innermost_frame ())
+    {
+      frame_debug_got_null_frame (this_frame,
+				  "recursive unwind of innermost frame");
+      return NULL;
+    }
+
   return get_prev_frame_always (this_frame);
 }
 
diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
index 4ed8cbb..5231f93 100644
--- a/gdb/guile/guile-internal.h
+++ b/gdb/guile/guile-internal.h
@@ -610,6 +610,7 @@ extern void gdbscm_initialize_disasm (void);
 extern void gdbscm_initialize_exceptions (void);
 extern void gdbscm_initialize_frames (void);
 extern void gdbscm_initialize_frame_filters (void);
+extern void gdbscm_initialize_frame_unwinders (void);
 extern void gdbscm_initialize_iterators (void);
 extern void gdbscm_initialize_lazy_strings (void);
 extern void gdbscm_initialize_math (void);
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index bbc4340..4726d5f 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -664,6 +664,7 @@ initialize_gdb_module (void *data)
   gdbscm_initialize_disasm ();
   gdbscm_initialize_frames ();
   gdbscm_initialize_frame_filters ();
+  gdbscm_initialize_frame_unwinders ();
   gdbscm_initialize_iterators ();
   gdbscm_initialize_lazy_strings ();
   gdbscm_initialize_math ();
diff --git a/gdb/guile/lib/gdb/frame-unwinders.scm b/gdb/guile/lib/gdb/frame-unwinders.scm
new file mode 100644
index 0000000..494a571
--- /dev/null
+++ b/gdb/guile/lib/gdb/frame-unwinders.scm
@@ -0,0 +1,213 @@
+;; Frame unwinder support.
+;;
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+;;
+;; This file is part of GDB.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gdb frame-unwinders)
+  #:use-module ((gdb) #:hide (frame? symbol?))
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (ice-9 match)
+  #:export (set-ephemeral-frame-id!
+            ephemeral-frame-read-register
+            ephemeral-frame-add-saved-register!
+
+            make-frame-unwinder
+            frame-unwinder?
+            frame-unwinder-name
+            frame-unwinder-enabled?
+            frame-unwinder-registered?
+            frame-unwinder-priority
+            frame-unwinder-procedure
+            frame-unwinder-scope
+
+            find-frame-unwinder-by-name
+
+            add-frame-unwinder!
+            remove-frame-unwinder!
+            enable-frame-unwinder!
+            disable-frame-unwinder!
+
+            all-frame-unwinders))
+
+(define-record-type <frame-unwinder>
+  (%make-frame-unwinder name priority enabled? registered? procedure scope)
+  frame-unwinder?
+  ;; string
+  (name frame-unwinder-name)
+  ;; real
+  (priority frame-unwinder-priority set-priority!)
+  ;; bool
+  (enabled? frame-unwinder-enabled? set-enabled?!)
+  ;; bool
+  (registered? frame-unwinder-registered? set-registered?!)
+  ;; ephemeral-frame -> *
+  (procedure frame-unwinder-procedure)
+  ;; objfile | progspace | #f
+  (scope frame-unwinder-scope))
+
+(define* (make-frame-unwinder name procedure #:key
+                            objfile progspace (priority 20) (enabled? #t))
+  "Make and return a new frame unwinder.  NAME and PROCEDURE are
+required arguments.  Specify #:objfile or #:progspace to limit the frame
+unwinder to a given scope, and #:priority or #:enabled? to set the
+priority and enabled status of the unwinder.
+
+The unwinder must be added to the active set via `add-frame-unwinder!'
+before it is active."
+  (define (compute-scope objfile progspace)
+    (cond
+     (objfile
+      (when progspace
+        (error "Only one of #:objfile or #:progspace may be given"))
+      (unless (objfile? objfile)
+        (error "Not an objfile" objfile))
+      objfile)
+     (progspace
+      (unless (progspace? progspace)
+        (error "Not a progspace" progspace))
+      progspace)
+     (else #f)))
+  (let ((registered? #f)
+        (scope (compute-scope objfile progspace)))
+    (%make-frame-unwinder name priority enabled? registered? procedure scope)))
+
+;; List of frame unwinders, sorted by priority from highest to lowest.
+(define *frame-unwinders* '())
+
+(define (same-scope? a b)
+  "Return #t if A and B represent the same scope, for the purposes of
+frame unwinder selection."
+  (cond
+   ;; If either is the global scope, they share a scope.
+   ((or (not a) (not b)) #t)
+   ;; If either is an objfile, compare their progspaces.
+   ((objfile? a) (same-scope? (objfile-progspace a) b))
+   ((objfile? b) (same-scope? a (objfile-progspace b)))
+   ;; Otherwise they are progspaces.  If they eq?, it's the same scope.
+   (else (eq? a b))))
+
+(define (is-valid? unwinder)
+  "Return #t if the scope of UNWINDER is still valid, or otherwise #f if
+the objfile or progspace has been removed from GDB."
+  (let ((scope (frame-unwinder-scope unwinder)))
+    (cond
+     ((progspace? scope) (progspace-valid? scope))
+     ((objfile? scope) (objfile-valid? scope))
+     (else #t))))
+
+(define (all-frame-unwinders)
+  "Return a list of all active frame unwinders, ordered from highest to
+lowest priority."
+  ;; Copy the list to prevent callers from mutating our state.
+  (list-copy *frame-unwinders*))
+
+(define* (has-active-frame-unwinders? #:optional
+                                      (scope (current-progspace)))
+  "Return #t if there are active frame unwinders for the given scope, or
+#f otherwise."
+  (let lp ((unwinders *frame-unwinders*))
+    (match unwinders
+      (() #f)
+      ((unwinder . unwinders)
+       (or (and (frame-unwinder-enabled? unwinder)
+                (same-scope? (frame-unwinder-scope unwinder) scope))
+           (lp unwinders))))))
+
+(define (prune-frame-unwinders!)
+  "Prune frame unwinders whose objfile or progspace has gone away,
+returning a fresh list of frame unwinders."
+  (set! *frame-unwinders*
+        (let lp ((unwinders *frame-unwinders*))
+          (match unwinders
+            (() '())
+            ((f . unwinders)
+             (cond
+              ((is-valid? f)
+               (cons f (lp unwinders)))
+              (else
+               (set-registered?! f #f)
+               (lp unwinders))))))))
+
+(define (add-frame-unwinder! unwinder)
+  "Add a frame unwinder to the active set.  Frame unwinders must be
+added before they will be used to unwinder backtraces."
+  (define (duplicate-unwinder? other)
+    (and (equal? (frame-unwinder-name other)
+                 (frame-unwinder-name unwinder))
+         (same-scope? (frame-unwinder-scope other)
+                      (frame-unwinder-scope unwinder))))
+  (define (priority>=? a b)
+    (>= (frame-unwinder-priority a) (frame-unwinder-priority b)))
+  (define (insert-sorted elt xs <=?)
+    (let lp ((xs xs))
+      (match xs
+        (() (list elt))
+        ((x . xs*)
+         (if (<=? elt x)
+             (cons elt xs)
+             (cons x (lp xs*)))))))
+
+  (prune-frame-unwinders!)
+  (when (or-map duplicate-unwinder? *frame-unwinders*)
+    (error "Frame unwinder with this name already present in scope"
+           (frame-unwinder-name unwinder)))
+  (set-registered?! unwinder #t)
+  (set! *frame-unwinders*
+        (insert-sorted unwinder *frame-unwinders* priority>=?)))
+
+(define (remove-frame-unwinder! unwinder)
+  "Remove a frame unwinder from the active set."
+  (set-registered?! unwinder #f)
+  (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
+
+(define* (find-frame-unwinder-by-name name #:optional
+                                      (scope (current-progspace)))
+  (prune-frame-unwinders!)
+  (or (find (lambda (unwinder)
+              (and (equal? name (frame-unwinder-name unwinder))
+                   (same-scope? (frame-unwinder-scope unwinder) scope)))
+            *frame-unwinders*)
+      (error "no frame unwinder found with name" name)))
+
+(define (enable-frame-unwinder! unwinder)
+  "Mark a frame unwinder as enabled."
+  (let ((unwinder (if (frame-unwinder? unwinder)
+                    unwinder
+                    (find-frame-unwinder-by-name unwinder))))
+    (set-enabled?! unwinder #t)
+    *unspecified*))
+
+(define (disable-frame-unwinder! unwinder)
+  "Mark a frame unwinder as disabled."
+  (let ((unwinder (if (frame-unwinder? unwinder)
+                    unwinder
+                    (find-frame-unwinder-by-name unwinder))))
+    (set-enabled?! unwinder #f)
+    *unspecified*))
+
+(define (unwind-frame frame)
+  (let ((scope (current-progspace)))
+    (or-map (lambda (unwinder)
+              (and (frame-unwinder-enabled? unwinder)
+                   (same-scope? (frame-unwinder-scope unwinder) scope)
+                   (begin
+                     ((frame-unwinder-procedure unwinder) frame)
+                     (ephemeral-frame-has-id? frame))))
+            *frame-unwinders*)))
+
+(load-extension "gdb" "gdbscm_load_frame_unwinders")
diff --git a/gdb/guile/scm-frame-unwinder.c b/gdb/guile/scm-frame-unwinder.c
new file mode 100644
index 0000000..009b13d
--- /dev/null
+++ b/gdb/guile/scm-frame-unwinder.c
@@ -0,0 +1,566 @@
+/* Scheme interface to the JIT reader.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* See README file in this directory for implementation notes, coding
+   conventions, et.al.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "guile-internal.h"
+#include "inferior.h"
+#include "language.h"
+#include "observer.h"
+#include "regcache.h"
+#include "user-regs.h"
+#include "value.h"
+
+/* Non-zero if the (gdb frame-unwinders) module has been loaded.  */
+static int gdbscm_frame_unwinders_loaded = 0;
+
+/* The captured apply-frame-filter variable.  */
+static SCM unwind_frame = SCM_BOOL_F;
+
+/* Key that we use when associating data with an architecture.  */
+static struct gdbarch_data *uwscm_gdbarch_data;
+
+/* The frame unwinder interface computes ephemeral frame objects when it
+   is able to unwind a frame.  Here we define the name for the ephemeral
+   frame Scheme data type.  */
+static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
+
+/* SMOB tag for ephemeral frames.  */
+static scm_t_bits ephemeral_frame_smob_tag;
+
+/* Data associated with a ephemeral frame.  */
+struct uwscm_ephemeral_frame
+{
+  /* The frame being unwound, used for the read-register interface.  */
+  struct frame_info *this_frame;
+
+  /* The architecture of the frame, here for convenience.  */
+  struct gdbarch *gdbarch;
+
+  /* The frame_id for the ephemeral frame; initially unset.  */
+  struct frame_id frame_id;
+
+  /* Nonzero if the frame_id has been set.  */
+  int has_frame_id;
+
+  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
+     ephemeral frame.  */
+  SCM registers;
+};
+
+/* Type predicate for ephemeral frames.  */
+
+static int
+uwscm_is_ephemeral_frame (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (ephemeral_frame_smob_tag, obj);
+}
+
+/* Data accessor for ephemeral frames.  */
+
+static struct uwscm_ephemeral_frame *
+uwscm_ephemeral_frame_data (SCM obj)
+{
+  gdb_assert (uwscm_is_ephemeral_frame (obj));
+  return (struct uwscm_ephemeral_frame *) SCM_SMOB_DATA (obj);
+}
+
+/* Build a ephemeral frame.  */
+
+static SCM
+uwscm_make_ephemeral_frame (struct frame_info *this_frame)
+{
+  struct uwscm_ephemeral_frame *data;
+  volatile struct gdb_exception except;
+
+  data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
+
+  data->this_frame = this_frame;
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      data->gdbarch = get_frame_arch (this_frame);
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+  data->has_frame_id = 0;
+  data->registers = SCM_EOL;
+
+  SCM_RETURN_NEWSMOB (ephemeral_frame_smob_tag, data);
+}
+
+/* Ephemeral frames may only be accessed from Scheme within the dynamic
+   extent of the unwind callback.  */
+
+static int
+uwscm_ephemeral_frame_is_valid (SCM ephemeral_frame)
+{
+  return uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame != NULL;
+}
+
+/* Is this an ephemeral frame that is accessible from Scheme?  */
+
+static int
+uwscm_is_valid_ephemeral_frame (SCM obj)
+{
+  return uwscm_is_ephemeral_frame (obj) && uwscm_ephemeral_frame_is_valid (obj);
+}
+
+/* Called as the unwind callback finishes to invalidate the ephemeral
+   frame.  */
+
+static void
+uwscm_invalidate_ephemeral_frame (SCM ephemeral_frame)
+{
+  gdb_assert(uwscm_ephemeral_frame_is_valid (ephemeral_frame));
+  uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame = NULL;
+}
+
+/* Raise a Scheme exception if OBJ is not a valid ephemeral frame.  */
+
+static void
+uwscm_assert_valid_ephemeral_frame (SCM obj, const char *func_name, int pos)
+{
+  if (!uwscm_is_valid_ephemeral_frame (obj))
+    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
+					  "valid <gdb:ephemeral-frame>"));
+}
+
+/* (ephemeral-frame-has-id? ephemeral-frame) -> bool
+
+   Has this ephemeral frame been given a frame ID?  */
+
+static SCM
+uwscm_ephemeral_frame_has_id_p (SCM ephemeral_frame)
+{
+  struct uwscm_ephemeral_frame *data;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  return scm_from_bool (data->has_frame_id);
+}
+
+/* Helper to convert a frame ID component to a CORE_ADDR.  */
+
+static CORE_ADDR
+uwscm_value_to_addr (SCM value, int arg)
+{
+  volatile struct gdb_exception except;
+  struct value *c_value;
+  CORE_ADDR ret;
+
+  if (!vlscm_is_value (value))
+    gdbscm_throw (gdbscm_make_type_error ("set-ephemeral-frame-id!",
+					  arg, value, "<gdb:value> object"));
+
+  c_value = vlscm_scm_to_value (value);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      ret = value_as_address (c_value);
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  return ret;
+}
+
+/* (set-ephemeral-frame-id! ephemeral-frame stack-address
+                            [code-address [special-address]])
+
+   Set the frame ID on this ephemeral frame.  */
+
+static SCM
+uwscm_set_ephemeral_frame_id_x (SCM ephemeral_frame, SCM sp, SCM ip,
+				SCM special)
+{
+  struct uwscm_ephemeral_frame *data;
+  struct frame_id frame_id;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+
+  if (SCM_UNBNDP (ip))
+    frame_id = frame_id_build_wild (uwscm_value_to_addr (sp, SCM_ARG2));
+  if (SCM_UNBNDP (special))
+    frame_id = frame_id_build (uwscm_value_to_addr (sp, SCM_ARG2),
+			       uwscm_value_to_addr (ip, SCM_ARG3));
+  else
+    frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
+				       uwscm_value_to_addr (ip, SCM_ARG3),
+				       uwscm_value_to_addr (special, SCM_ARG4));
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  data->frame_id = frame_id;
+  data->has_frame_id = 1;
+
+  return SCM_UNSPECIFIED;
+}
+
+/* Convert the string REGISTER_SCM to a register number for the given
+   architecture.  */
+
+static int
+uwscm_scm_to_regnum (SCM register_scm, struct gdbarch *gdbarch)
+{
+  int regnum;
+
+  volatile struct gdb_exception except;
+  struct cleanup *cleanup;
+  char *register_str;
+
+  gdbscm_parse_function_args ("ephemeral-frame-add-saved-register!", SCM_ARG2,
+			      NULL, "s", register_scm, &register_str);
+  cleanup = make_cleanup (xfree, register_str);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
+					    strlen (register_str));
+    }
+  do_cleanups (cleanup);
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  if (regnum < 0)
+    gdbscm_out_of_range_error ("ephemeral-frame-add-saved-register!", SCM_ARG2,
+			       register_scm, _("unknown register"));
+
+  return regnum;
+}
+
+/* (ephemeral-frame-read-register <gdb:ephemeral-frame> string)
+      -> <gdb:value>
+
+   Sniffs a register value from an ephemeral frame.  */
+
+static SCM
+uwscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
+{
+  volatile struct gdb_exception except;
+  struct uwscm_ephemeral_frame *data;
+  struct value *value = NULL;
+  int regnum;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      value = get_frame_register_value (data->this_frame, regnum);
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  if (value == NULL)
+    gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, register_scm,
+			       _("Cannot read register from frame."));
+
+  return vlscm_scm_from_value (value);
+}
+
+/* (ephemeral-frame-add-saved-register! ephemeral-frame register value)
+
+   Records the saved value of a particular register in EPHEMERAL_FRAME.
+   REGISTER_SCM names the register, as a string, and VALUE_SCM is a
+   <gdb:value>, or #f to indicate that the register was not saved by the
+   ephemeral frame.  */
+
+static SCM
+uwscm_ephemeral_frame_add_saved_register_x (SCM ephemeral_frame,
+					    SCM register_scm,
+					    SCM value_scm)
+{
+  struct uwscm_ephemeral_frame *data;
+  struct value *value;
+  int regnum;
+  int value_size;
+
+  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch);
+
+  if (!gdbscm_is_false (value_scm))
+    {
+      if (!vlscm_is_value (value_scm))
+	gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG3,
+					      value_scm,
+					      "<gdb:value> object"));
+
+      value = vlscm_scm_to_value (value_scm);
+      value_size = TYPE_LENGTH (value_enclosing_type (value));
+
+      if (value_size != register_size (data->gdbarch, regnum))
+	gdbscm_invalid_object_error ("ephemeral-frame-add-saved-register!",
+				     SCM_ARG3, value_scm,
+				     "wrong sized value for register");
+    }
+
+  data->registers = scm_assv_set_x (data->registers,
+				    scm_from_int (regnum),
+				    value_scm);
+
+  return SCM_UNSPECIFIED;
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+uwscm_this_id (struct frame_info *this_frame, void **cache_ptr,
+	       struct frame_id *this_id)
+{
+  SCM ephemeral_frame = PTR2SCM (*cache_ptr);
+  struct uwscm_ephemeral_frame *data;
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  *this_id = data->frame_id;
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+uwscm_prev_register (struct frame_info *this_frame, void **cache_ptr,
+		     int regnum)
+{
+  SCM ephemeral_frame = PTR2SCM (*cache_ptr);
+  struct uwscm_ephemeral_frame *data;
+  SCM value_scm;
+  struct value *c_value;
+  const gdb_byte *buf;
+
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  value_scm = scm_assv_ref (data->registers, scm_from_int (regnum));
+  if (gdbscm_is_false (value_scm))
+    return frame_unwind_got_optimized (this_frame, regnum);
+
+  c_value = vlscm_scm_to_value (value_scm);
+  buf = value_contents (c_value);
+
+  return frame_unwind_got_bytes (this_frame, regnum, buf);
+}
+
+/* Sniffer implementation.  */
+
+static int
+uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+	       void **cache_ptr)
+{
+  static int unwind_active = 0;
+  static int recursive_unwind_detected = 0;
+  struct frame_info *next_frame;
+  struct uwscm_ephemeral_frame *data;
+  SCM ephemeral_frame;
+  SCM result;
+
+  /* Note that it's possible to have loaded the Guile interface, but not yet
+     loaded (gdb frame-unwinders), so checking gdb_scheme_initialized is not
+     sufficient.  */
+  if (!gdbscm_frame_unwinders_loaded)
+    return 0;
+
+  /* Recursively unwinding indicates a problem in the user's frame
+     unwinder.  Detect recursion, and cause it to cancel the unwind that
+     is in progress.  */
+  if (unwind_active)
+    {
+      recursive_unwind_detected = 1;
+      return 0;
+    }
+
+  ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
+  data = uwscm_ephemeral_frame_data (ephemeral_frame);
+  unwind_active = 1;
+  recursive_unwind_detected = 0;
+
+  result = gdbscm_safe_call_1 (scm_variable_ref (unwind_frame),
+			       ephemeral_frame,
+                               gdbscm_memory_error_p);
+
+  /* Drop the reference to this_frame, so that future use of
+     ephemeral_frame from Scheme will signal an error.  */
+  uwscm_invalidate_ephemeral_frame (ephemeral_frame);
+  unwind_active = 0;
+
+  if (gdbscm_is_exception (result))
+    {
+      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+      return 0;
+    }
+
+  if (recursive_unwind_detected)
+    {
+      fprintf_filtered (gdb_stderr,
+			_("Recursion detected while unwinding frame %d."),
+			frame_relative_level (this_frame));
+      return 0;
+    }
+
+  /* The unwinder indicates success by calling
+     set-ephemeral-frame-id!.  */
+  if (uwscm_ephemeral_frame_data (ephemeral_frame)->has_frame_id)
+    {
+      scm_gc_protect_object (ephemeral_frame);
+      *cache_ptr = SCM2PTR (ephemeral_frame);
+      return 1;
+    }
+
+  return 0;
+}
+
+/* Frame cache release shim.  */
+
+static void
+uwscm_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  scm_gc_unprotect_object (PTR2SCM (cache));
+}
+
+struct uwscm_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+uwscm_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct uwscm_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Guile sniffers
+   intermediary.  */
+
+static void
+uwscm_on_new_gdbarch (struct gdbarch *newarch)
+{
+  struct uwscm_gdbarch_data_type *data =
+      gdbarch_data (newarch, uwscm_gdbarch_data);
+
+  if (!data->unwinder_registered)
+    {
+      struct frame_unwind *unwinder
+          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
+
+      unwinder->type = NORMAL_FRAME;
+      unwinder->stop_reason = default_frame_unwind_stop_reason;
+      unwinder->this_id = uwscm_this_id;
+      unwinder->prev_register = uwscm_prev_register;
+      unwinder->unwind_data = (void *) newarch;
+      unwinder->sniffer = uwscm_sniffer;
+      unwinder->dealloc_cache = uwscm_dealloc_cache;
+      frame_unwind_prepend_unwinder (newarch, unwinder);
+      data->unwinder_registered = 1;
+    }
+}
+
+static const scheme_function unwind_functions[] =
+{
+  { "ephemeral-frame-has-id?", 1, 0, 0, uwscm_ephemeral_frame_has_id_p,
+    "\
+Return #t if the given ephemeral frame has been given a frame ID\n\
+already, or #f otherwise." },
+
+  { "set-ephemeral-frame-id!", 2, 2, 0, uwscm_set_ephemeral_frame_id_x,
+    "\
+Set the identifier on an ephemeral frame, thereby taking responsibility for\n\
+unwinding this frame.\n\
+\n\
+This function takes two required arguments and two optional arguments.\n\
+The first argument is the ephemeral frame that is being unwound, as a\n\
+<gdb:ephemeral-frame>.  The rest of the arguments are used to build an\n\
+identifier for the frame.\n\
+\n\
+Ephemeral frame objects are created by the custom unwinder interface, and\n\
+initially have no frame identifier.  A frame identifier is a unique name\n\
+for a frame that remains valid as long as the frame itself is valid.\n\
+Usually the frame identifier is built from from the frame's stack address\n\
+and code address.  The stack address, passed as the second argument,\n\
+should normally be a pointer to the new end of the stack when the function\n\
+was called, as a GDB value.  Similarly the code address, the third\n\
+argument, should be given as the address of the entry point of the\n\
+function.\n\
+\n\
+For most architectures, it is sufficient to just specify just the stack\n\
+and code pointers.  Some architectures have another stack or some other\n\
+frame state store, like ia64, and they need an additional address, which\n\
+may be passed as the fourth argument.\n\
+\n\
+It is possible to create a frame ID with just a stack address, but it's\n\
+better to specify a code address as well if possible."},
+
+  { "ephemeral-frame-read-register", 2, 0, 0,
+    uwscm_ephemeral_frame_read_register,
+    "\
+Return the value of a register in an ephemeral frame.\n\
+\n\
+  Arguments: <gdb:ephemeral-frame> string" },
+
+  { "ephemeral-frame-add-saved-register!", 3, 0, 0,
+    uwscm_ephemeral_frame_add_saved_register_x,
+    "\
+Set the saved value of a register in a ephemeral frame.\n\
+\n\
+After reading an ephemeral frame's registers and determining that it\n\
+can handle the frame, an unwinder will call this function to record\n\
+saved registers.  The values of the saved registers logically belong\n\
+to the frame that is older than the ephemeral frame being unwound, not\n\
+the ephemeral frame itself.\n\
+\n\
+The first argument should be a <gdb:ephemeral-frame> object.  The second\n\
+names a register, and should be a string, for example \"rip\".  The\n\
+third argument is the value, as a GDB value.  Alternately, passing #f\n\
+as the value will mark the register as unavailable." },
+
+  END_FUNCTIONS
+};
+
+/* Called by lib/gdb/frame-unwinders.scm.  */
+
+static void
+gdbscm_load_frame_unwinders (void *unused)
+{
+  if (gdbscm_frame_unwinders_loaded)
+    return;
+
+  gdbscm_frame_unwinders_loaded = 1;
+
+  gdbscm_define_functions (unwind_functions, 0);
+
+  unwind_frame = scm_c_lookup ("unwind-frame");
+}
+
+/* Initialize the opaque ephemeral frame type and register
+   gdbscm_load_frame_unwinders for calling by (gdb frame-unwinders).  */
+
+void
+gdbscm_initialize_frame_unwinders (void)
+{
+  ephemeral_frame_smob_tag =
+    gdbscm_make_smob_type (ephemeral_frame_smob_name, 0);
+
+  uwscm_gdbarch_data =
+    gdbarch_data_register_post_init (uwscm_gdbarch_data_init);
+  observer_attach_architecture_changed (uwscm_on_new_gdbarch);
+
+  scm_c_register_extension ("gdb", "gdbscm_load_frame_unwinders",
+                            gdbscm_load_frame_unwinders, NULL);
+}
diff --git a/gdb/guile/scm-symbol.c b/gdb/guile/scm-symbol.c
index 1891237..9037c92 100644
--- a/gdb/guile/scm-symbol.c
+++ b/gdb/guile/scm-symbol.c
@@ -599,7 +599,9 @@ gdbscm_lookup_symbol (SCM name_scm, SCM rest)
 
       TRY_CATCH (except, RETURN_MASK_ALL)
 	{
-	  selected_frame = get_selected_frame (_("no frame selected"));
+	  selected_frame = get_selected_frame_if_set ();
+	  if (selected_frame == NULL)
+	    selected_frame = get_current_frame ();
 	  block = get_frame_block (selected_frame, NULL);
 	}
       GDBSCM_HANDLE_GDB_EXCEPTION_WITH_CLEANUPS (except, cleanups);
-- 
2.1.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] Add Guile frame unwinder interface
  2015-03-05 15:58 [PATCH] Add Guile frame unwinder interface Andy Wingo
@ 2015-03-09 10:34 ` Andy Wingo
  2015-03-09 15:42   ` Pedro Alves
  0 siblings, 1 reply; 9+ messages in thread
From: Andy Wingo @ 2015-03-09 10:34 UTC (permalink / raw)
  To: gdb-patches; +Cc: asmundak

[-- Attachment #1: Type: text/plain, Size: 204 bytes --]

Hi,

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

WDYT?

Andy


[-- Attachment #2: 0001-Add-Guile-frame-unwinder-interface.patch --]
[-- Type: text/plain, Size: 56159 bytes --]

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

gdb/doc/ChangeLog:

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

gdb/ChangeLog:

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

gdb/testsuite/ChangeLog:

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

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


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] Add Guile frame unwinder interface
  2015-03-09 10:34 ` [PATCH v2] " Andy Wingo
@ 2015-03-09 15:42   ` Pedro Alves
  2015-03-09 16:22     ` Eli Zaretskii
  2015-03-09 18:54     ` Andy Wingo
  0 siblings, 2 replies; 9+ messages in thread
From: Pedro Alves @ 2015-03-09 15:42 UTC (permalink / raw)
  To: Andy Wingo, gdb-patches; +Cc: asmundak

On 03/09/2015 10:34 AM, Andy Wingo wrote:
> +@var{register} names a register, and should be a string, for example
> +@samp{rip}.  @var{value} is the register value, as a @value{GDBN}
> +value.  Alternately, passing @code{#f} as the value will mark the
> +register as unavailable.

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

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

> +@end deffn

> +The first argument should be a <gdb:ephemeral-frame> object.  The second\n\
> +names a register, and should be a string, for example \"rip\".  The\n\
> +third argument is the value, as a GDB value.  Alternately, passing #f\n\
> +as the value will mark the register as unavailable." },

Likewise.

> @@ -99,12 +111,17 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
>    volatile struct gdb_exception ex;
>    int res = 0;
>
> +  if (is_unwinding)
> +    internal_error (__FILE__, __LINE__,
> +		    _("Recursion detected while finding an unwinder."));
>    old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder);
>
> +  is_unwinding = 1;
>    TRY_CATCH (ex, RETURN_MASK_ERROR)
>      {
>        res = unwinder->sniffer (unwinder, this_frame, this_cache);
>      }
> +  is_unwinding = 0;
>    if (ex.reason < 0 && ex.error == NOT_AVAILABLE_ERROR)
>      {
>        /* This usually means that not even the PC is available,
> @@ -249,7 +266,8 @@ frame_unwind_got_constant (struct frame_info *frame, int regnum,
>  }

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

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

No ()'s.

> +
> +extern int frame_unwind_is_unwinding (void);

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] Add Guile frame unwinder interface
  2015-03-09 15:42   ` Pedro Alves
@ 2015-03-09 16:22     ` Eli Zaretskii
  2015-03-09 18:54     ` Andy Wingo
  1 sibling, 0 replies; 9+ messages in thread
From: Eli Zaretskii @ 2015-03-09 16:22 UTC (permalink / raw)
  To: Pedro Alves; +Cc: wingo, gdb-patches, asmundak

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

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

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] Add Guile frame unwinder interface
  2015-03-09 15:42   ` Pedro Alves
  2015-03-09 16:22     ` Eli Zaretskii
@ 2015-03-09 18:54     ` Andy Wingo
  2015-03-10  9:03       ` [PATCH v3] " Andy Wingo
  2015-03-10 15:46       ` [PATCH v2] " Pedro Alves
  1 sibling, 2 replies; 9+ messages in thread
From: Andy Wingo @ 2015-03-09 18:54 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches, asmundak

Hi,

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

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

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

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

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

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

Thanks for the review,

Andy

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3] Add Guile frame unwinder interface
  2015-03-09 18:54     ` Andy Wingo
@ 2015-03-10  9:03       ` Andy Wingo
  2015-03-10 17:48         ` Doug Evans
  2015-03-10 15:46       ` [PATCH v2] " Pedro Alves
  1 sibling, 1 reply; 9+ messages in thread
From: Andy Wingo @ 2015-03-10  9:03 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches, asmundak

[-- Attachment #1: Type: text/plain, Size: 693 bytes --]

Greets,

Attached is a new patch addressing nits.

Still open questions from the other thread:

  * Is is possible to make a Maybe<UnwindInfo>-style interface to
    sniffers, or should extension languages expose the callbacks
    instead because they really need to be called within their specific
    dynamic environments?

    (Clearly it's possible, to the extent that Alexander and I have done
    it, but is it a good idea?  It sure would be nice, if so :)

  * Regarding recursion and selected frames -- what do you think about
    the hack in v2 of this patch, which adds
    frame_unwinder_is_unwinding() ?  It is a hack but it has somewhat
    reasonable semantics.

Regards,

Andy


[-- Attachment #2: 0001-Add-Guile-frame-unwinder-interface.patch --]
[-- Type: text/plain, Size: 56348 bytes --]

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


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] Add Guile frame unwinder interface
  2015-03-09 18:54     ` Andy Wingo
  2015-03-10  9:03       ` [PATCH v3] " Andy Wingo
@ 2015-03-10 15:46       ` Pedro Alves
  1 sibling, 0 replies; 9+ messages in thread
From: Pedro Alves @ 2015-03-10 15:46 UTC (permalink / raw)
  To: Andy Wingo; +Cc: gdb-patches, asmundak

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

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

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

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

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] Add Guile frame unwinder interface
  2015-03-10  9:03       ` [PATCH v3] " Andy Wingo
@ 2015-03-10 17:48         ` Doug Evans
  2015-03-11  9:33           ` Andy Wingo
  0 siblings, 1 reply; 9+ messages in thread
From: Doug Evans @ 2015-03-10 17:48 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Pedro Alves, gdb-patches, asmundak

Andy Wingo writes:
 > Greets,
 > 
 > Attached is a new patch addressing nits.
 > 
 > Still open questions from the other thread:
 > 
 >   * Is is possible to make a Maybe<UnwindInfo>-style interface to
 >     sniffers, or should extension languages expose the callbacks
 >     instead because they really need to be called within their specific
 >     dynamic environments?
 > 
 >     (Clearly it's possible, to the extent that Alexander and I have done
 >     it, but is it a good idea?  It sure would be nice, if so :)
 > 
 >   * Regarding recursion and selected frames -- what do you think about
 >     the hack in v2 of this patch, which adds
 >     frame_unwinder_is_unwinding() ?  It is a hack but it has somewhat
 >     reasonable semantics.
 > 
 > Regards,
 > 
 > Andy
 > 
 > >From 7a04dd3c8b7b61b30497308662ce631779cde51c 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.

Hi.
I've got time for a few administrivia notes.
I need to play with the patch a bit more.

 > 
 > diff --git a/gdb/ChangeLog b/gdb/ChangeLog
 > index d55daf6..a0bfe3d 100644
 > --- a/gdb/ChangeLog
 > +++ b/gdb/ChangeLog
 > @@ -1,3 +1,32 @@
 > +2015-03-05  Andy Wingo  <wingo@igalia.com>
 > +
 > +	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
 > +	is no selected frame and no block is selected; instead, fall back
 > +	to the current frame.
 > +	* guile/scm-frame-unwinder.c: New file.
 > +	* guile/lib/gdb/frame-unwinders.scm: New file.
 > +	* guile/guile.c (initialize_gdb_module): Call
 > +	gdbscm_initialize_frame_unwinders.
 > +	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
 > +	declaration.
 > +	* frame.c (get_prev_frame): Detect recursive unwinds, returning
 > +	NULL in that case.
 > +	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
 > +	(frame_unwind_is_unwinding): New declaration.
 > +	* frame-unwind.c (is_unwinding): New file-local variable.
 > +	(set_is_unwinding, unset_is_unwinding): New file-local helpers.
 > +	(frame_unwind_is_unwinding): New exported predicate.
 > +	(frame_unwind_try_handler): Arrange for
 > +	frame_unwind_is_unwinding to return true when unwinding the
 > +	innermost frame.
 > +	(frame_unwind_got_bytes): Make buf arg const.
 > +	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
 > +	frame-unwinders.scm.
 > +	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
 > +	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
 > +	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
 > +	(scm-frame-unwinder.o): New target.
 > +
 >  2015-02-20  Andy Wingo  <wingo@igalia.com>
 >  
 >  	* guile/scm-value.c (gdbscm_value_dynamic_type): Fix typo in which
 > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
 > index 0ab4c51..c9110f0 100644
 > --- a/gdb/Makefile.in
 > +++ b/gdb/Makefile.in
 > @@ -315,6 +315,7 @@ SUBDIR_GUILE_OBS = \
 >  	scm-exception.o \
 >  	scm-frame.o \
 >  	scm-frame-filter.o \
 > +	scm-frame-unwinder.o \
 >  	scm-gsmob.o \
 >  	scm-iterator.o \
 >  	scm-lazy-string.o \
 > @@ -342,6 +343,7 @@ SUBDIR_GUILE_SRCS = \
 >  	guile/scm-exception.c \
 >  	guile/scm-frame.c \
 >  	guile/scm-frame-filter.c \
 > +	guile/scm-frame-unwinder.c \
 >  	guile/scm-gsmob.c \
 >  	guile/scm-iterator.c \
 >  	guile/scm-lazy-string.c \
 > @@ -2418,6 +2420,10 @@ scm-frame-filter.o: $(srcdir)/guile/scm-frame-filter.c
 >  	$(COMPILE) $(srcdir)/guile/scm-frame-filter.c
 >  	$(POSTCOMPILE)
 >  
 > +scm-frame-unwinder.o: $(srcdir)/guile/scm-frame-unwinder.c
 > +	$(COMPILE) $(srcdir)/guile/scm-frame-unwinder.c
 > +	$(POSTCOMPILE)
 > +
 >  scm-gsmob.o: $(srcdir)/guile/scm-gsmob.c
 >  	$(COMPILE) $(srcdir)/guile/scm-gsmob.c
 >  	$(POSTCOMPILE)
 > diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
 > index 75aab1b..bb2722d 100644
 > --- a/gdb/data-directory/Makefile.in
 > +++ b/gdb/data-directory/Makefile.in
 > @@ -91,6 +91,7 @@ GUILE_SOURCE_FILES = \
 >  	gdb/boot.scm \
 >  	gdb/experimental.scm \
 >  	gdb/frame-filters.scm \
 > +	gdb/frame-unwinders.scm \
 >  	gdb/init.scm \
 >  	gdb/iterator.scm \
 >  	gdb/printing.scm \
 > @@ -101,6 +102,7 @@ GUILE_COMPILED_FILES = \
 >  	./gdb.go \
 >  	gdb/experimental.go \
 >  	gdb/frame-filters.go \
 > +	gdb/frame-unwinders.go \
 >  	gdb/iterator.go \
 >  	gdb/printing.go \
 >  	gdb/support.go \
 > diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
 > index a8cfbd9..8f9faae 100644
 > --- a/gdb/doc/ChangeLog
 > +++ b/gdb/doc/ChangeLog
 > @@ -1,3 +1,7 @@
 > +2015-03-06  Andy Wingo  <wingo@igalia.com>
 > +
 > +	* guile.texi (Guile Frame Unwinder API): New section.
 > +
 >  2015-02-20  Andy Wingo  <wingo@igalia.com>
 >  
 >  	* guile.texi (Frames In Guile): Describe frame-read-register.
 > diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
 > index 05a7806..875cc5d 100644
 > --- a/gdb/doc/guile.texi
 > +++ b/gdb/doc/guile.texi
 > @@ -143,6 +143,7 @@ from the Guile interactive prompt.
 >  * Writing a Guile Pretty-Printer:: Writing a pretty-printer
 >  * Guile Frame Filter API::   Filtering frames.
 >  * Writing a Frame Filter in Guile:: Writing a frame filter.
 > +* Guile Frame Unwinder API:: Programmatically unwinding stack frames
 >  * Commands In Guile::        Implementing new commands in Guile
 >  * Parameters In Guile::      Adding new @value{GDBN} parameters
 >  * Progspaces In Guile::      Program spaces
 > @@ -2125,6 +2126,196 @@ also possible to do the job of an decorator with a filter.  Still,
 >  avoiding the stream interfaces can often be a good reason to use the
 >  simpler decorator layer.
 >  
 > +@node Guile Frame Unwinder API
 > +@subsubsection Unwinding Frames in Guile
 > +@cindex frame unwinder api, guile
 > +
 > +In @value{GDBN} terminology, ``unwinding'' is the process of finding
 > +an older (outer) frame on the stack.  Unwinders form the core of
 > +backtrace computation in @value{GDBN}, computing the register state in
 > +each frame.  @value{GDBN} comes with unwinders for each target
 > +architecture that it supports, and these usually suffice to unwind the
 > +stack.  However, some target programs can have non-standard frame
 > +layouts that cannot be unwound by the standard unwinders.  This is
 > +often the case when working with just-in-time compilation
 > +environments, for example in JavaScript implementations.  In such
 > +cases, users can define custom code in Guile to programmatically
 > +unwind the problematic stack frames.
 > +
 > +Before getting into the API, we should discuss how unwinders work in
 > +@value{GDBN}.
 > +
 > +As an example, consider a stack in which we have already computed
 > +frame 0 and we want to compute frame 1.  We say that frame 0 is the
 > +``inner'' frame, and frame 1 will be the ``outer'' frame.
 > +
 > +Unwinding starts with a model of the state of all registers in an
 > +inner, already unwound frame.  In our case, we start with frame 0.
 > +@value{GDBN} then constructs a ephemeral frame object for the outer
 > +frame that is being built (frame 1) and links it to the inner frame
 > +(frame 0).  @value{GDBN} then goes through its list of registered
 > +unwinders, searching for one that knows how to unwind the frame.  When
 > +it finds one, @value{GDBN} will ask the unwinder to compute a frame
 > +identifier for the outer frame.  Once the unwinder has done so, the
 > +frame is marked as ``valid'' and can be accessed using the normal
 > +frame API.
 > +
 > +A frame identifier (frame ID) consists of code and data pointers
 > +associated with a frame which will remain valid as long as the frame
 > +is still alive.  Usually a frame ID is a pair of the code and stack
 > +pointers as they were when control entered the function associated
 > +with the frame, though as described below there are other ways to
 > +build a frame ID@.  However as you can see, computing the frame ID
 > +requires some input from the unwinder to determine the start code
 > +address (PC) and the frame pointer (FP), especially on platforms that
 > +don't dedicate a register to the FP.
 > +
 > +(Given this description, you might wonder how the frame ID for the
 > +innermost frame (frame 0) is unwound, given that unwinding requires an
 > +inner frame.  The answer is that internally, @value{GDBN} always has a
 > +``sentinel'' frame that is inner to the innermost frame, and which has
 > +a pre-computed unwinder that just returns the registers as they are,
 > +without unwinding.)
 > +
 > +The Guile frame unwinder API loosely follows this protocol as
 > +described above.  Guile will build a special ``ephemeral frame
 > +object'' corresponding the frame being unwound (in our example,
 > +frame 1).  It allows the user to read registers from that ephemeral
 > +frame, which in reality are unwound from the already-existing frame
 > +0.  If the unwinder decides that it can handle the frame in question,
 > +it then sets the frame ID on the ephemeral frame.  It also records the
 > +values of any registers saved in the frame, for use when unwinding
 > +its outer frame (frame 2).
 > +
 > +Frame unwinder objects are managed in Guile much in the same way as
 > +frame filters.  Indeed, users will often want to implement both frame
 > +unwinders and frame filters: unwinders will compute the correct
 > +backtrace and register state, and filters can fill in function names,
 > +line numbers, and the like.  @xref{Guile Frame Filter API}, for more
 > +on frame filters.
 > +
 > +As with frame filters, there can be multiple frame unwinders
 > +registered with @value{GDBN}, and each one may be individually enabled
 > +or disabled at will.  The filters will be tried in priority order,
 > +from highest to lowest priority, and the first one that sets the frame
 > +ID will take responsibility for the frame.
 > +
 > +To use frame unwinders, first load the @code{(gdb frame-unwinders)} module
 > +to have access to the procedures that manipulate frame unwinders:
 > +
 > +@example
 > +(use-modules (gdb frame-unwinders))
 > +@end example
 > +
 > +@deffn {Scheme Procedure} make-frame-unwinder name procedure @
 > +       @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]} @
 > +       @r{[}#:objfile objfile@r{]} @r{[}#:progspace progspace@r{]}
 > +
 > +Make a new frame unwinder.  @var{procedure} should be a function of one
 > +argument, taking an ephemeral frame object.  If the unwinder procedure
 > +decides to handle the frame, it should call
 > +@code{set-ephemeral-frame-id!} to set the frame ID@.  Otherwise,
 > +@value{GDBN} will continue to search its list for an unwinder.
 > +
 > +By default, the scope of the unwinder is global, meaning that it is
 > +associated with all objfiles and progspaces.  Pass one of
 > +@code{#:objfile} or @code{#:progspace} to instead scope the unwinder
 > +into a specific objfile or progspace, respectively.

Instead of both #:objfile and #:progspace I'd rather just provide
say, #:locus or #:scope or some such.
Other APIs specify just one, e.g.,
python/lib/gdb/printing.py:register_pretty_printer,
and this needs to do so as well.
Use #f to specify the global scope (consistent with other uses).

Also, I would expect the scope to instead be specified when the object
is registered, and not in the make-foo routine.

 > +
 > +The unwinder will be initially enabled, unless the keyword argument
 > +@code{#:enabled? #f} is given.  Even if the unwinder is marked as
 > +enabled, it will need to be added to @value{GDBN}'s set of active
 > +unwinders via @code{add-frame-unwinder!} in order to take effect.
 > +When added, the unwinder will be inserted into the list of registered
 > +unwinders with the given @var{priority}, which should be a number, and
 > +which defaults to 20 if not given.  Higher priority unwinders will be
 > +tried before lower-priority unwinders.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} all-frame-unwinders
 > +Return a list of all frame unwinders.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} add-frame-unwinder! unwinder
 > +@deffnx {Scheme Procedure} remove-frame-unwinder! unwinder
 > +Register or unregister the frame unwinder @var{unwinder} with
 > +@value{GDBN}.  Frame unwinders are also implicitly unregistered when
 > +their objfile or progspace goes away.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} enable-frame-unwinder! unwinder
 > +@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
 > +Enable or disable a frame unwinder, respectively.  @var{unwinder} can
 > +either be a frame unwinder object, or it can be a string naming a
 > +unwinder in the current scope.  If no such unwinder is found, an error
 > +is signalled.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} frame-unwinder-name unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-enabled? unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-registered? unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-priority unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-procedure unwinder
 > +@deffnx {Scheme Procedure} frame-unwinder-scope unwinder
 > +Accessors for a frame unwinder object's fields.  The
 > +@code{registered?}  field indicates whether a unwinder has been added
 > +to @value{GDBN} or not.  @code{scope} is the objfile or progspace in
 > +which the unwinder was registered, or @code{#f} otherwise.
 > +@end deffn
 > +
 > +Frame unwinders operate on ``ephemeral frames''.  Ephemeral frames are
 > +valid only while they are being unwound; any access to an ephemeral
 > +frame outside the extent of their unwind operation will signal an
 > +error.  Only three operations are supported on ephemeral frames:
 > +reading their registers, setting their frame ID, and adding saved
 > +register values.
 > +
 > +@deffn {Scheme Procedure} ephemeral-frame-read-register frame register
 > +Return the value of a register in the ephemeral frame @var{frame}.
 > +@var{register} should be given as a string.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} set-ephemeral-frame-id! frame fp [pc [special]]
 > +Set the frame ID on ephemeral frame @var{frame}, thereby taking
 > +responsibility for unwinding the frame.
 > +
 > +@var{fp}, @var{pc}, and @var{special} are used to build the frame ID@.
 > +A frame ID is a unique name for a frame that remains valid as long as
 > +the frame itself is valid.  Usually the frame ID is built from from
 > +the frame's stack address and code address.  The stack address
 > +@var{fp} should normally be a pointer to the new end of the stack when
 > +the function was called, as a @value{GDBN} value.  Similarly the code
 > +address @var{pc} should be given as the address of the entry point of
 > +the function.
 > +
 > +For most architectures, it is sufficient to just specify just the
 > +stack and code pointers @var{fp} and @var{pc}.  Some architectures
 > +have another stack or some other frame state store, like ia64.  For
 > +these platforms the frame ID needs an additional address, which may be
 > +passed as the @var{special} optional argument.
 > +
 > +It is possible to create a frame ID with just a stack address, but
 > +it's better to specify a code address as well if possible.
 > +@end deffn
 > +
 > +@deffn {Scheme Procedure} ephemeral-frame-add-saved-register! frame register value
 > +Set the saved value of a register in a ephemeral frame.
 > +
 > +After reading an ephemeral frame's registers and determining that it
 > +can handle the frame, an unwinder will call this function to record
 > +saved registers.  The values of the saved registers logically belong
 > +to the frame that is older than the ephemeral frame being unwound, not
 > +the ephemeral frame itself.
 > +
 > +@var{register} names a register, and should be a string, for example
 > +@samp{rip}.  @var{value} is the register value, as a @value{GDBN}
 > +value.
 > +@end deffn
 > +
 > +Any register whose value is not recorded as saved via
 > +@code{ephemeral-frame-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 e73650a..b3076fe 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.  */
 > @@ -99,11 +127,18 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
 >    volatile struct gdb_exception ex;
 >    int res = 0;
 >  
 > +  if (is_unwinding)
 > +    internal_error (__FILE__, __LINE__,
 > +		    _("Recursion detected while finding an unwinder."));
 >    old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder);
 >  
 >    TRY_CATCH (ex, RETURN_MASK_ERROR)
 >      {
 > +      struct cleanup *cleanup = set_is_unwinding ();
 > +
 >        res = unwinder->sniffer (unwinder, this_frame, this_cache);
 > +
 > +      do_cleanups (cleanup);
 >      }
 >    if (ex.reason < 0 && ex.error == NOT_AVAILABLE_ERROR)
 >      {
 > @@ -249,7 +284,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 6b1be94..81431bb 100644
 > --- a/gdb/frame.c
 > +++ b/gdb/frame.c
 > @@ -2209,6 +2209,22 @@ get_prev_frame (struct frame_info *this_frame)
 >        return NULL;
 >      }
 >  
 > +  /* Unwinders implemented in Python or Scheme could eventually make an API call
 > +     that would cause GDB to try to unwind a frame while unwinding a frame.
 > +     Because already-unwound frames will be found in the frame cache, unwinding
 > +     will only happen at the old end of the stack, which means that any
 > +     recursive unwinding attempt will surely lead to unbounded recursion.  Ways
 > +     this can happen include such common functions as `get_current_arch' or
 > +     `lookup_symbol', via `get_selected_frame', so it's impractical to simply
 > +     declare these an error.  Instead, we detect this case and return NULL,
 > +     indicating that the known stack of frames ends here.  */
 > +  if (frame_unwind_is_unwinding ())
 > +    {
 > +      frame_debug_got_null_frame (this_frame,
 > +				  "get_prev_frame within unwinder sniffer");
 > +      return NULL;
 > +    }
 > +
 >    return get_prev_frame_always (this_frame);
 >  }
 >  
 > diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
 > index 4ed8cbb..5231f93 100644
 > --- a/gdb/guile/guile-internal.h
 > +++ b/gdb/guile/guile-internal.h
 > @@ -610,6 +610,7 @@ extern void gdbscm_initialize_disasm (void);
 >  extern void gdbscm_initialize_exceptions (void);
 >  extern void gdbscm_initialize_frames (void);
 >  extern void gdbscm_initialize_frame_filters (void);
 > +extern void gdbscm_initialize_frame_unwinders (void);
 >  extern void gdbscm_initialize_iterators (void);
 >  extern void gdbscm_initialize_lazy_strings (void);
 >  extern void gdbscm_initialize_math (void);
 > diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
 > index bbc4340..4726d5f 100644
 > --- a/gdb/guile/guile.c
 > +++ b/gdb/guile/guile.c
 > @@ -664,6 +664,7 @@ initialize_gdb_module (void *data)
 >    gdbscm_initialize_disasm ();
 >    gdbscm_initialize_frames ();
 >    gdbscm_initialize_frame_filters ();
 > +  gdbscm_initialize_frame_unwinders ();
 >    gdbscm_initialize_iterators ();
 >    gdbscm_initialize_lazy_strings ();
 >    gdbscm_initialize_math ();
 > diff --git a/gdb/guile/lib/gdb/frame-unwinders.scm b/gdb/guile/lib/gdb/frame-unwinders.scm
 > new file mode 100644
 > index 0000000..494a571
 > --- /dev/null
 > +++ b/gdb/guile/lib/gdb/frame-unwinders.scm
 > @@ -0,0 +1,213 @@
 > +;; Frame unwinder support.
 > +;;
 > +;; Copyright (C) 2015 Free Software Foundation, Inc.
 > +;;
 > +;; This file is part of GDB.
 > +;;
 > +;; This program is free software; you can redistribute it and/or modify
 > +;; it under the terms of the GNU General Public License as published by
 > +;; the Free Software Foundation; either version 3 of the License, or
 > +;; (at your option) any later version.
 > +;;
 > +;; This program is distributed in the hope that it will be useful,
 > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +;; GNU General Public License for more details.
 > +;;
 > +;; You should have received a copy of the GNU General Public License
 > +;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +(define-module (gdb frame-unwinders)
 > +  #:use-module ((gdb) #:hide (frame? symbol?))
 > +  #:use-module (srfi srfi-1)
 > +  #:use-module (srfi srfi-9)
 > +  #:use-module (ice-9 match)
 > +  #:export (set-ephemeral-frame-id!
 > +            ephemeral-frame-read-register
 > +            ephemeral-frame-add-saved-register!
 > +
 > +            make-frame-unwinder
 > +            frame-unwinder?
 > +            frame-unwinder-name
 > +            frame-unwinder-enabled?
 > +            frame-unwinder-registered?
 > +            frame-unwinder-priority
 > +            frame-unwinder-procedure
 > +            frame-unwinder-scope
 > +
 > +            find-frame-unwinder-by-name
 > +
 > +            add-frame-unwinder!
 > +            remove-frame-unwinder!

As a user who can't remember names very well, consistent naming
is welcome. There is a (relatively) consistent naming thus far:
register-breakpoint!, delete-breakpoint!, register-parameter!,
register-command!.

-> register-frame-unwinder! and delete-frame-unwinder!

But I can quibble over the names too :-).
"delete-breakpoint!" doesn't really delete it so much as remove it
from gdb's tables. So I could go with remove-frame-unwinder!,
add remove-breakpoint! and deprecate delete-breakpoint!.

Plus it's nice to have names that go together: If we're going to have
register-foo, the intuitive opposite is unregister-foo.
But I'm ok with register-foo and remove-foo.

But if you add-frame-unwinder! instead of register-frame-unwinder!,
I can go with that, except that unless we make a complete switch, things
will likely just evolve into a mess.
IOW, if we go with add-frame-unwinder! then we need to also add
add-breakpoint!, add-parameter!, and add-command!, and deprecate
the register-foo! versions. Now's a good a time as any to
lay down how things get deprecated in the Guile API.
We could have a guile/lib/gdb/deprecated.scm file that defined
register-foo! (and others) or some such. As for when to delete them,
I'm ok with anything <= 5 years.

 > +            enable-frame-unwinder!
 > +            disable-frame-unwinder!

Similarly, there is set-breakpoint-enabled!.
-> set-frame-unwinder-enabled!

 > +
 > +            all-frame-unwinders))
 > +
 > +(define-record-type <frame-unwinder>
 > +  (%make-frame-unwinder name priority enabled? registered? procedure scope)
 > +  frame-unwinder?
 > +  ;; string
 > +  (name frame-unwinder-name)
 > +  ;; real
 > +  (priority frame-unwinder-priority set-priority!)
 > +  ;; bool
 > +  (enabled? frame-unwinder-enabled? set-enabled?!)
 > +  ;; bool
 > +  (registered? frame-unwinder-registered? set-registered?!)
 > +  ;; ephemeral-frame -> *
 > +  (procedure frame-unwinder-procedure)
 > +  ;; objfile | progspace | #f
 > +  (scope frame-unwinder-scope))
 > +
 > +(define* (make-frame-unwinder name procedure #:key
 > +                            objfile progspace (priority 20) (enabled? #t))
 > +  "Make and return a new frame unwinder.  NAME and PROCEDURE are
 > +required arguments.  Specify #:objfile or #:progspace to limit the frame
 > +unwinder to a given scope, and #:priority or #:enabled? to set the
 > +priority and enabled status of the unwinder.
 > +
 > +The unwinder must be added to the active set via `add-frame-unwinder!'
 > +before it is active."
 > +  (define (compute-scope objfile progspace)
 > +    (cond
 > +     (objfile
 > +      (when progspace
 > +        (error "Only one of #:objfile or #:progspace may be given"))
 > +      (unless (objfile? objfile)
 > +        (error "Not an objfile" objfile))
 > +      objfile)
 > +     (progspace
 > +      (unless (progspace? progspace)
 > +        (error "Not a progspace" progspace))
 > +      progspace)
 > +     (else #f)))
 > +  (let ((registered? #f)
 > +        (scope (compute-scope objfile progspace)))
 > +    (%make-frame-unwinder name priority enabled? registered? procedure scope)))
 > +
 > +;; List of frame unwinders, sorted by priority from highest to lowest.
 > +(define *frame-unwinders* '())
 > +
 > +(define (same-scope? a b)
 > +  "Return #t if A and B represent the same scope, for the purposes of
 > +frame unwinder selection."
 > +  (cond
 > +   ;; If either is the global scope, they share a scope.
 > +   ((or (not a) (not b)) #t)
 > +   ;; If either is an objfile, compare their progspaces.
 > +   ((objfile? a) (same-scope? (objfile-progspace a) b))
 > +   ((objfile? b) (same-scope? a (objfile-progspace b)))
 > +   ;; Otherwise they are progspaces.  If they eq?, it's the same scope.
 > +   (else (eq? a b))))
 > +
 > +(define (is-valid? unwinder)
 > +  "Return #t if the scope of UNWINDER is still valid, or otherwise #f if
 > +the objfile or progspace has been removed from GDB."
 > +  (let ((scope (frame-unwinder-scope unwinder)))
 > +    (cond
 > +     ((progspace? scope) (progspace-valid? scope))
 > +     ((objfile? scope) (objfile-valid? scope))
 > +     (else #t))))
 > +
 > +(define (all-frame-unwinders)
 > +  "Return a list of all active frame unwinders, ordered from highest to
 > +lowest priority."
 > +  ;; Copy the list to prevent callers from mutating our state.
 > +  (list-copy *frame-unwinders*))
 > +
 > +(define* (has-active-frame-unwinders? #:optional
 > +                                      (scope (current-progspace)))
 > +  "Return #t if there are active frame unwinders for the given scope, or
 > +#f otherwise."
 > +  (let lp ((unwinders *frame-unwinders*))
 > +    (match unwinders
 > +      (() #f)
 > +      ((unwinder . unwinders)
 > +       (or (and (frame-unwinder-enabled? unwinder)
 > +                (same-scope? (frame-unwinder-scope unwinder) scope))
 > +           (lp unwinders))))))
 > +
 > +(define (prune-frame-unwinders!)
 > +  "Prune frame unwinders whose objfile or progspace has gone away,
 > +returning a fresh list of frame unwinders."
 > +  (set! *frame-unwinders*
 > +        (let lp ((unwinders *frame-unwinders*))
 > +          (match unwinders
 > +            (() '())
 > +            ((f . unwinders)
 > +             (cond
 > +              ((is-valid? f)
 > +               (cons f (lp unwinders)))
 > +              (else
 > +               (set-registered?! f #f)
 > +               (lp unwinders))))))))
 > +
 > +(define (add-frame-unwinder! unwinder)
 > +  "Add a frame unwinder to the active set.  Frame unwinders must be
 > +added before they will be used to unwinder backtraces."

s/unwinder/unwind/ ?

 > +  (define (duplicate-unwinder? other)
 > +    (and (equal? (frame-unwinder-name other)
 > +                 (frame-unwinder-name unwinder))
 > +         (same-scope? (frame-unwinder-scope other)
 > +                      (frame-unwinder-scope unwinder))))
 > +  (define (priority>=? a b)
 > +    (>= (frame-unwinder-priority a) (frame-unwinder-priority b)))
 > +  (define (insert-sorted elt xs <=?)
 > +    (let lp ((xs xs))
 > +      (match xs
 > +        (() (list elt))
 > +        ((x . xs*)
 > +         (if (<=? elt x)
 > +             (cons elt xs)
 > +             (cons x (lp xs*)))))))
 > +
 > +  (prune-frame-unwinders!)
 > +  (when (or-map duplicate-unwinder? *frame-unwinders*)
 > +    (error "Frame unwinder with this name already present in scope"
 > +           (frame-unwinder-name unwinder)))
 > +  (set-registered?! unwinder #t)
 > +  (set! *frame-unwinders*
 > +        (insert-sorted unwinder *frame-unwinders* priority>=?)))
 > +
 > +(define (remove-frame-unwinder! unwinder)
 > +  "Remove a frame unwinder from the active set."
 > +  (set-registered?! unwinder #f)
 > +  (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
 > +
 > +(define* (find-frame-unwinder-by-name name #:optional
 > +                                      (scope (current-progspace)))
 > +  (prune-frame-unwinders!)
 > +  (or (find (lambda (unwinder)
 > +              (and (equal? name (frame-unwinder-name unwinder))
 > +                   (same-scope? (frame-unwinder-scope unwinder) scope)))
 > +            *frame-unwinders*)
 > +      (error "no frame unwinder found with name" name)))
 > +
 > +(define (enable-frame-unwinder! unwinder)
 > +  "Mark a frame unwinder as enabled."
 > +  (let ((unwinder (if (frame-unwinder? unwinder)
 > +                    unwinder
 > +                    (find-frame-unwinder-by-name unwinder))))
 > +    (set-enabled?! unwinder #t)
 > +    *unspecified*))
 > +
 > +(define (disable-frame-unwinder! unwinder)
 > +  "Mark a frame unwinder as disabled."
 > +  (let ((unwinder (if (frame-unwinder? unwinder)
 > +                    unwinder
 > +                    (find-frame-unwinder-by-name unwinder))))
 > +    (set-enabled?! unwinder #f)
 > +    *unspecified*))
 > +
 > +(define (unwind-frame frame)
 > +  (let ((scope (current-progspace)))
 > +    (or-map (lambda (unwinder)
 > +              (and (frame-unwinder-enabled? unwinder)
 > +                   (same-scope? (frame-unwinder-scope unwinder) scope)
 > +                   (begin
 > +                     ((frame-unwinder-procedure unwinder) frame)
 > +                     (ephemeral-frame-has-id? frame))))
 > +            *frame-unwinders*)))
 > +
 > +(load-extension "gdb" "gdbscm_load_frame_unwinders")
 > diff --git a/gdb/guile/scm-frame-unwinder.c b/gdb/guile/scm-frame-unwinder.c
 > new file mode 100644
 > index 0000000..9d3c74a
 > --- /dev/null
 > +++ b/gdb/guile/scm-frame-unwinder.c
 > @@ -0,0 +1,563 @@
 > +/* Scheme interface to the JIT reader.
 > +
 > +   Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +   This file is part of GDB.
 > +
 > +   This program is free software; you can redistribute it and/or modify
 > +   it under the terms of the GNU General Public License as published by
 > +   the Free Software Foundation; either version 3 of the License, or
 > +   (at your option) any later version.
 > +
 > +   This program is distributed in the hope that it will be useful,
 > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +   GNU General Public License for more details.
 > +
 > +   You should have received a copy of the GNU General Public License
 > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 > +
 > +/* See README file in this directory for implementation notes, coding
 > +   conventions, et.al.  */
 > +
 > +#include "defs.h"
 > +#include "arch-utils.h"
 > +#include "frame-unwind.h"
 > +#include "gdb_obstack.h"
 > +#include "guile-internal.h"
 > +#include "inferior.h"
 > +#include "language.h"
 > +#include "observer.h"
 > +#include "regcache.h"
 > +#include "user-regs.h"
 > +#include "value.h"
 > +
 > +/* Non-zero if the (gdb frame-unwinders) module has been loaded.  */
 > +static int gdbscm_frame_unwinders_loaded = 0;
 > +
 > +/* The captured apply-frame-filter variable.  */
 > +static SCM unwind_frame = SCM_BOOL_F;
 > +
 > +/* Key that we use when associating data with an architecture.  */
 > +static struct gdbarch_data *uwscm_gdbarch_data;
 > +
 > +/* The frame unwinder interface computes ephemeral frame objects when it
 > +   is able to unwind a frame.  Here we define the name for the ephemeral
 > +   frame Scheme data type.  */
 > +static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
 > +
 > +/* SMOB tag for ephemeral frames.  */
 > +static scm_t_bits ephemeral_frame_smob_tag;
 > +
 > +/* Data associated with a ephemeral frame.  */
 > +struct uwscm_ephemeral_frame
 > +{
 > +  /* The frame being unwound, used for the read-register interface.  */
 > +  struct frame_info *this_frame;
 > +
 > +  /* The architecture of the frame, here for convenience.  */
 > +  struct gdbarch *gdbarch;
 > +
 > +  /* The frame_id for the ephemeral frame; initially unset.  */
 > +  struct frame_id frame_id;
 > +
 > +  /* Nonzero if the frame_id has been set.  */
 > +  int has_frame_id;
 > +
 > +  /* A list of (REGNUM . VALUE) pairs, indicating register values for the
 > +     ephemeral frame.  */
 > +  SCM registers;
 > +};
 > +
 > +/* Type predicate for ephemeral frames.  */
 > +
 > +static int
 > +uwscm_is_ephemeral_frame (SCM obj)
 > +{
 > +  return SCM_SMOB_PREDICATE (ephemeral_frame_smob_tag, obj);
 > +}
 > +
 > +/* Data accessor for ephemeral frames.  */
 > +
 > +static struct uwscm_ephemeral_frame *
 > +uwscm_ephemeral_frame_data (SCM obj)
 > +{
 > +  gdb_assert (uwscm_is_ephemeral_frame (obj));
 > +  return (struct uwscm_ephemeral_frame *) SCM_SMOB_DATA (obj);
 > +}
 > +
 > +/* Build a ephemeral frame.  */

s/a/an/

 > +
 > +static SCM
 > +uwscm_make_ephemeral_frame (struct frame_info *this_frame)
 > +{
 > +  struct uwscm_ephemeral_frame *data;
 > +  volatile struct gdb_exception except;
 > +
 > +  data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
 > +
 > +  data->this_frame = this_frame;
 > +  TRY_CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      data->gdbarch = get_frame_arch (this_frame);
 > +    }
 > +  GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +  data->has_frame_id = 0;
 > +  data->registers = SCM_EOL;
 > +
 > +  SCM_RETURN_NEWSMOB (ephemeral_frame_smob_tag, data);
 > +}
 > +
 > +/* Ephemeral frames may only be accessed from Scheme within the dynamic
 > +   extent of the unwind callback.  */
 > +
 > +static int
 > +uwscm_ephemeral_frame_is_valid (SCM ephemeral_frame)
 > +{
 > +  return uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame != NULL;
 > +}
 > +
 > +/* Is this an ephemeral frame that is accessible from Scheme?  */
 > +
 > +static int
 > +uwscm_is_valid_ephemeral_frame (SCM obj)
 > +{
 > +  return uwscm_is_ephemeral_frame (obj) && uwscm_ephemeral_frame_is_valid (obj);
 > +}
 > +
 > +/* Called as the unwind callback finishes to invalidate the ephemeral
 > +   frame.  */
 > +
 > +static void
 > +uwscm_invalidate_ephemeral_frame (SCM ephemeral_frame)
 > +{
 > +  gdb_assert(uwscm_ephemeral_frame_is_valid (ephemeral_frame));
 > +  uwscm_ephemeral_frame_data (ephemeral_frame)->this_frame = NULL;
 > +}
 > +
 > +/* Raise a Scheme exception if OBJ is not a valid ephemeral frame.  */
 > +
 > +static void
 > +uwscm_assert_valid_ephemeral_frame (SCM obj, const char *func_name, int pos)
 > +{
 > +  if (!uwscm_is_valid_ephemeral_frame (obj))
 > +    gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
 > +					  "valid <gdb:ephemeral-frame>"));
 > +}
 > +
 > +/* (ephemeral-frame-has-id? ephemeral-frame) -> bool
 > +
 > +   Has this ephemeral frame been given a frame ID?  */
 > +
 > +static SCM
 > +uwscm_ephemeral_frame_has_id_p (SCM ephemeral_frame)
 > +{
 > +  struct uwscm_ephemeral_frame *data;
 > +
 > +  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
 > +
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  return scm_from_bool (data->has_frame_id);
 > +}
 > +
 > +/* Helper to convert a frame ID component to a CORE_ADDR.  */
 > +
 > +static CORE_ADDR
 > +uwscm_value_to_addr (SCM value, int arg)
 > +{
 > +  volatile struct gdb_exception except;
 > +  struct value *c_value;
 > +  CORE_ADDR ret;
 > +
 > +  if (!vlscm_is_value (value))
 > +    gdbscm_throw (gdbscm_make_type_error ("set-ephemeral-frame-id!",
 > +					  arg, value, "<gdb:value> object"));
 > +
 > +  c_value = vlscm_scm_to_value (value);
 > +
 > +  TRY_CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      ret = value_as_address (c_value);
 > +    }
 > +  GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +
 > +  return ret;
 > +}
 > +
 > +/* (set-ephemeral-frame-id! ephemeral-frame stack-address
 > +                            [code-address [special-address]])
 > +
 > +   Set the frame ID on this ephemeral frame.  */
 > +
 > +static SCM
 > +uwscm_set_ephemeral_frame_id_x (SCM ephemeral_frame, SCM sp, SCM ip,
 > +				SCM special)

Functions that get exposed to scheme have gdbscm_ prefix.

Also, I'm not sold on exporting the term "special" into the API.
It conveys no information to the reader. "How special?" "Special in what way?"
Internally we can call it whatever we like, but we should have a
better name in the published API.
I realize we don't want to pick an architecture-specific name,
but as data for the discussion, is this only used by ia64?

 > +{
 > +  struct uwscm_ephemeral_frame *data;
 > +  struct frame_id frame_id;
 > +
 > +  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
 > +
 > +  if (SCM_UNBNDP (ip))
 > +    frame_id = frame_id_build_wild (uwscm_value_to_addr (sp, SCM_ARG2));
 > +  if (SCM_UNBNDP (special))
 > +    frame_id = frame_id_build (uwscm_value_to_addr (sp, SCM_ARG2),
 > +			       uwscm_value_to_addr (ip, SCM_ARG3));
 > +  else
 > +    frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
 > +				       uwscm_value_to_addr (ip, SCM_ARG3),
 > +				       uwscm_value_to_addr (special, SCM_ARG4));
 > +
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  data->frame_id = frame_id;
 > +  data->has_frame_id = 1;
 > +
 > +  return SCM_UNSPECIFIED;
 > +}
 > +
 > +/* Convert the string REGISTER_SCM to a register number for the given
 > +   architecture.  */
 > +
 > +static int
 > +uwscm_scm_to_regnum (SCM register_scm, struct gdbarch *gdbarch)
 > +{
 > +  int regnum;
 > +
 > +  volatile struct gdb_exception except;
 > +  struct cleanup *cleanup;
 > +  char *register_str;
 > +
 > +  gdbscm_parse_function_args ("ephemeral-frame-add-saved-register!", SCM_ARG2,
 > +			      NULL, "s", register_scm, &register_str);
 > +  cleanup = make_cleanup (xfree, register_str);
 > +
 > +  TRY_CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
 > +					    strlen (register_str));
 > +    }
 > +  do_cleanups (cleanup);
 > +  GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +
 > +  if (regnum < 0)
 > +    gdbscm_out_of_range_error ("ephemeral-frame-add-saved-register!", SCM_ARG2,
 > +			       register_scm, _("unknown register"));
 > +
 > +  return regnum;
 > +}
 > +
 > +/* (ephemeral-frame-read-register <gdb:ephemeral-frame> string)
 > +      -> <gdb:value>
 > +
 > +   Sniffs a register value from an ephemeral frame.  */
 > +
 > +static SCM
 > +uwscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
 > +{
 > +  volatile struct gdb_exception except;
 > +  struct uwscm_ephemeral_frame *data;
 > +  struct value *value = NULL;
 > +  int regnum;
 > +
 > +  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch);
 > +
 > +  TRY_CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      gdb_byte buffer[MAX_REGISTER_SIZE];
 > +
 > +      value = get_frame_register_value (data->this_frame, regnum);
 > +    }
 > +  GDBSCM_HANDLE_GDB_EXCEPTION (except);
 > +
 > +  if (value == NULL)
 > +    gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, register_scm,
 > +			       _("Cannot read register from frame."));
 > +
 > +  return vlscm_scm_from_value (value);
 > +}
 > +
 > +/* (ephemeral-frame-add-saved-register! ephemeral-frame register value)
 > +
 > +   Records the saved value of a particular register in EPHEMERAL_FRAME.
 > +   REGISTER_SCM names the register, as a string, and VALUE_SCM is a
 > +   <gdb:value>.  */
 > +
 > +static SCM
 > +uwscm_ephemeral_frame_add_saved_register_x (SCM ephemeral_frame,
 > +					    SCM register_scm,
 > +					    SCM value_scm)
 > +{
 > +  struct uwscm_ephemeral_frame *data;
 > +  struct value *value;
 > +  int regnum;
 > +  int value_size;
 > +
 > +  uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  regnum = uwscm_scm_to_regnum (register_scm, data->gdbarch);
 > +
 > +  if (!vlscm_is_value (value_scm))
 > +    gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG3,
 > +					  value_scm,
 > +					  "<gdb:value> object"));
 > +
 > +  value = vlscm_scm_to_value (value_scm);
 > +  value_size = TYPE_LENGTH (value_enclosing_type (value));
 > +
 > +  if (value_size != register_size (data->gdbarch, regnum))
 > +    gdbscm_invalid_object_error ("ephemeral-frame-add-saved-register!",
 > +				 SCM_ARG3, value_scm,
 > +				 "wrong sized value for register");
 > +
 > +  data->registers = scm_assv_set_x (data->registers,
 > +				    scm_from_int (regnum),
 > +				    value_scm);
 > +
 > +  return SCM_UNSPECIFIED;
 > +}
 > +
 > +/* frame_unwind.this_id method.  */
 > +
 > +static void
 > +uwscm_this_id (struct frame_info *this_frame, void **cache_ptr,
 > +	       struct frame_id *this_id)
 > +{
 > +  SCM ephemeral_frame = PTR2SCM (*cache_ptr);
 > +  struct uwscm_ephemeral_frame *data;
 > +
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  *this_id = data->frame_id;
 > +}
 > +
 > +/* frame_unwind.prev_register.  */
 > +
 > +static struct value *
 > +uwscm_prev_register (struct frame_info *this_frame, void **cache_ptr,
 > +		     int regnum)
 > +{
 > +  SCM ephemeral_frame = PTR2SCM (*cache_ptr);
 > +  struct uwscm_ephemeral_frame *data;
 > +  SCM value_scm;
 > +  struct value *c_value;
 > +  const gdb_byte *buf;
 > +
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  value_scm = scm_assv_ref (data->registers, scm_from_int (regnum));
 > +  if (gdbscm_is_false (value_scm))
 > +    return frame_unwind_got_optimized (this_frame, regnum);
 > +
 > +  c_value = vlscm_scm_to_value (value_scm);
 > +  buf = value_contents (c_value);
 > +
 > +  return frame_unwind_got_bytes (this_frame, regnum, buf);
 > +}
 > +
 > +/* Sniffer implementation.  */
 > +
 > +static int
 > +uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
 > +	       void **cache_ptr)
 > +{
 > +  static int unwind_active = 0;
 > +  static int recursive_unwind_detected = 0;
 > +  struct frame_info *next_frame;
 > +  struct uwscm_ephemeral_frame *data;
 > +  SCM ephemeral_frame;
 > +  SCM result;
 > +
 > +  /* Note that it's possible to have loaded the Guile interface, but not yet
 > +     loaded (gdb frame-unwinders), so checking gdb_scheme_initialized is not
 > +     sufficient.  */
 > +  if (!gdbscm_frame_unwinders_loaded)
 > +    return 0;
 > +
 > +  /* Recursively unwinding indicates a problem in the user's frame
 > +     unwinder.  Detect recursion, and cause it to cancel the unwind that
 > +     is in progress.  */
 > +  if (unwind_active)
 > +    {
 > +      recursive_unwind_detected = 1;
 > +      return 0;
 > +    }
 > +
 > +  ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
 > +  data = uwscm_ephemeral_frame_data (ephemeral_frame);
 > +  unwind_active = 1;
 > +  recursive_unwind_detected = 0;
 > +
 > +  result = gdbscm_safe_call_1 (scm_variable_ref (unwind_frame),
 > +			       ephemeral_frame,
 > +                               gdbscm_memory_error_p);
 > +
 > +  /* Drop the reference to this_frame, so that future use of
 > +     ephemeral_frame from Scheme will signal an error.  */
 > +  uwscm_invalidate_ephemeral_frame (ephemeral_frame);
 > +  unwind_active = 0;
 > +
 > +  if (gdbscm_is_exception (result))
 > +    {
 > +      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
 > +      return 0;
 > +    }
 > +
 > +  if (recursive_unwind_detected)
 > +    {
 > +      fprintf_filtered (gdb_stderr,
 > +			_("Recursion detected while unwinding frame %d."),
 > +			frame_relative_level (this_frame));
 > +      return 0;
 > +    }
 > +
 > +  /* The unwinder indicates success by calling
 > +     set-ephemeral-frame-id!.  */
 > +  if (uwscm_ephemeral_frame_data (ephemeral_frame)->has_frame_id)
 > +    {
 > +      scm_gc_protect_object (ephemeral_frame);
 > +      *cache_ptr = SCM2PTR (ephemeral_frame);
 > +      return 1;
 > +    }
 > +
 > +  return 0;
 > +}
 > +
 > +/* Frame cache release shim.  */
 > +
 > +static void
 > +uwscm_dealloc_cache (struct frame_info *this_frame, void *cache)
 > +{
 > +  scm_gc_unprotect_object (PTR2SCM (cache));
 > +}
 > +
 > +struct uwscm_gdbarch_data_type
 > +{
 > +  /* Has the unwinder shim been prepended? */
 > +  int unwinder_registered;
 > +};
 > +
 > +static void *
 > +uwscm_gdbarch_data_init (struct gdbarch *gdbarch)
 > +{
 > +  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct uwscm_gdbarch_data_type);
 > +}
 > +
 > +/* New inferior architecture callback: register the Guile sniffers
 > +   intermediary.  */
 > +
 > +static void
 > +uwscm_on_new_gdbarch (struct gdbarch *newarch)
 > +{
 > +  struct uwscm_gdbarch_data_type *data =
 > +      gdbarch_data (newarch, uwscm_gdbarch_data);
 > +
 > +  if (!data->unwinder_registered)
 > +    {
 > +      struct frame_unwind *unwinder
 > +          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
 > +
 > +      unwinder->type = NORMAL_FRAME;
 > +      unwinder->stop_reason = default_frame_unwind_stop_reason;
 > +      unwinder->this_id = uwscm_this_id;
 > +      unwinder->prev_register = uwscm_prev_register;
 > +      unwinder->unwind_data = (void *) newarch;
 > +      unwinder->sniffer = uwscm_sniffer;
 > +      unwinder->dealloc_cache = uwscm_dealloc_cache;
 > +      frame_unwind_prepend_unwinder (newarch, unwinder);
 > +      data->unwinder_registered = 1;
 > +    }
 > +}
 > +
 > +static const scheme_function unwind_functions[] =
 > +{
 > +  { "ephemeral-frame-has-id?", 1, 0, 0, uwscm_ephemeral_frame_has_id_p,
 > +    "\
 > +Return #t if the given ephemeral frame has been given a frame ID\n\
 > +already, or #f otherwise." },
 > +
 > +  { "set-ephemeral-frame-id!", 2, 2, 0, uwscm_set_ephemeral_frame_id_x,
 > +    "\
 > +Set the identifier on an ephemeral frame, thereby taking responsibility for\n\
 > +unwinding this frame.\n\
 > +\n\
 > +This function takes two required arguments and two optional arguments.\n\
 > +The first argument is the ephemeral frame that is being unwound, as a\n\
 > +<gdb:ephemeral-frame>.  The rest of the arguments are used to build an\n\
 > +identifier for the frame.\n\
 > +\n\
 > +Ephemeral frame objects are created by the custom unwinder interface, and\n\
 > +initially have no frame identifier.  A frame identifier is a unique name\n\
 > +for a frame that remains valid as long as the frame itself is valid.\n\
 > +Usually the frame identifier is built from from the frame's stack address\n\
 > +and code address.  The stack address, passed as the second argument,\n\
 > +should normally be a pointer to the new end of the stack when the function\n\
 > +was called, as a GDB value.  Similarly the code address, the third\n\
 > +argument, should be given as the address of the entry point of the\n\
 > +function.\n\
 > +\n\
 > +For most architectures, it is sufficient to just specify just the stack\n\
 > +and code pointers.  Some architectures have another stack or some other\n\
 > +frame state store, like ia64, and they need an additional address, which\n\
 > +may be passed as the fourth argument.\n\
 > +\n\
 > +It is possible to create a frame ID with just a stack address, but it's\n\
 > +better to specify a code address as well if possible."},
 > +
 > +  { "ephemeral-frame-read-register", 2, 0, 0,
 > +    uwscm_ephemeral_frame_read_register,
 > +    "\
 > +Return the value of a register in an ephemeral frame.\n\
 > +\n\
 > +  Arguments: <gdb:ephemeral-frame> string" },
 > +
 > +  { "ephemeral-frame-add-saved-register!", 3, 0, 0,
 > +    uwscm_ephemeral_frame_add_saved_register_x,
 > +    "\
 > +Set the saved value of a register in a ephemeral frame.\n\
 > +\n\
 > +After reading an ephemeral frame's registers and determining that it\n\
 > +can handle the frame, an unwinder will call this function to record\n\
 > +saved registers.  The values of the saved registers logically belong\n\
 > +to the frame that is older than the ephemeral frame being unwound, not\n\
 > +the ephemeral frame itself.\n\
 > +\n\
 > +The first argument should be a <gdb:ephemeral-frame> object.  The second\n\
 > +names a register, and should be a string, for example \"rip\".  The\n\
 > +third argument is the value, as a GDB value.  By default, any register\n\
 > +whose value is not added to the saved register set of the ephemeral\n\
 > +frame will be marked as \"not saved\" in the outer frame." },
 > +
 > +  END_FUNCTIONS
 > +};
 > +
 > +/* Called by lib/gdb/frame-unwinders.scm.  */
 > +
 > +static void
 > +gdbscm_load_frame_unwinders (void *unused)
 > +{
 > +  if (gdbscm_frame_unwinders_loaded)
 > +    return;
 > +
 > +  gdbscm_frame_unwinders_loaded = 1;
 > +
 > +  gdbscm_define_functions (unwind_functions, 0);
 > +
 > +  unwind_frame = scm_c_lookup ("unwind-frame");
 > +}
 > +
 > +/* Initialize the opaque ephemeral frame type and register
 > +   gdbscm_load_frame_unwinders for calling by (gdb frame-unwinders).  */
 > +
 > +void
 > +gdbscm_initialize_frame_unwinders (void)
 > +{
 > +  ephemeral_frame_smob_tag =
 > +    gdbscm_make_smob_type (ephemeral_frame_smob_name, 0);
 > +
 > +  uwscm_gdbarch_data =
 > +    gdbarch_data_register_post_init (uwscm_gdbarch_data_init);
 > +  observer_attach_architecture_changed (uwscm_on_new_gdbarch);
 > +
 > +  scm_c_register_extension ("gdb", "gdbscm_load_frame_unwinders",
 > +                            gdbscm_load_frame_unwinders, NULL);
 > +}
 > diff --git a/gdb/guile/scm-symbol.c b/gdb/guile/scm-symbol.c
 > index 1891237..9037c92 100644
 > --- a/gdb/guile/scm-symbol.c
 > +++ b/gdb/guile/scm-symbol.c
 > @@ -599,7 +599,9 @@ gdbscm_lookup_symbol (SCM name_scm, SCM rest)
 >  
 >        TRY_CATCH (except, RETURN_MASK_ALL)
 >  	{
 > -	  selected_frame = get_selected_frame (_("no frame selected"));
 > +	  selected_frame = get_selected_frame_if_set ();
 > +	  if (selected_frame == NULL)
 > +	    selected_frame = get_current_frame ();
 >  	  block = get_frame_block (selected_frame, NULL);
 >  	}
 >        GDBSCM_HANDLE_GDB_EXCEPTION_WITH_CLEANUPS (except, cleanups);
 > diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
 > index 2265764..6666896 100644
 > --- a/gdb/testsuite/ChangeLog
 > +++ b/gdb/testsuite/ChangeLog
 > @@ -1,3 +1,10 @@
 > +2015-03-06  Andy Wingo  <wingo@igalia.com>
 > +
 > +	* gdb.guile/scm-frame-unwinder.exp:
 > +	* gdb.guile/scm-frame-unwinder.c:
 > +	* gdb.guile/scm-frame-unwinder-gdb.scm.in:
 > +	* gdb.guile/scm-frame-unwinder.scm: Add unwinder tests.
 > +
 >  2015-02-20  Andy Wingo  <wingo@igalia.com>
 >  
 >  	* gdb.guile/scm-frame.exp: Add frame-read-register tests, modelled
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
 > new file mode 100644
 > index 0000000..96981ed
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
 > @@ -0,0 +1,32 @@
 > +;; Copyright (C) 2015 Free Software Foundation, Inc.
 > +;;
 > +;; This program is free software; you can redistribute it and/or modify
 > +;; it under the terms of the GNU General Public License as published by
 > +;; the Free Software Foundation; either version 3 of the License, or
 > +;; (at your option) any later version.
 > +;;
 > +;; This program is distributed in the hope that it will be useful,
 > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +;; GNU General Public License for more details.
 > +;;
 > +;; You should have received a copy of the GNU General Public License
 > +;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +;; This file is part of the GDB test-suite.  It tests Guile-based frame
 > +;; filters.
 > +
 > +(use-modules (gdb)
 > +             (gdb frame-unwinders))
 > +
 > +(define* (install-unwinders! #:optional (objfile (current-objfile)))
 > +  (define (unwind-frame frame)
 > +    #f)
 > +  (define (add-unwinder! name priority)
 > +    (add-frame-unwinder! (make-frame-unwinder name unwind-frame
 > +                                              #:priority priority
 > +                                              #:objfile objfile)))
 > +  (add-unwinder! "Auto-loaded dummy" 100)
 > +  (add-unwinder! "Auto-loaded dummy 2" 200))
 > +
 > +(install-unwinders!)
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.c b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
 > new file mode 100644
 > index 0000000..82db341
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.c
 > @@ -0,0 +1,30 @@
 > +int f2 (int a)
 > +{
 > +  return ++a;
 > +}
 > +
 > +int f1 (int a, int b)
 > +{
 > +  return f2(a) + b;
 > +}
 > +
 > +int block (void)
 > +{
 > +  int i = 99;
 > +  {
 > +    double i = 1.1;
 > +    double f = 2.2;
 > +    {
 > +      const char *i = "stuff";
 > +      const char *f = "foo";
 > +      const char *b = "bar";
 > +      return 0; /* Block break here.  */
 > +    }
 > +  }
 > +}
 > +
 > +int main (int argc, char *argv[])
 > +{
 > +  block ();
 > +  return f1 (1, 2);
 > +}
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
 > new file mode 100644
 > index 0000000..699b170
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
 > @@ -0,0 +1,84 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +# This file is part of the GDB testsuite.  It tests Guile-based
 > +# frame-filters.
 > +
 > +load_lib gdb-guile.exp
 > +
 > +standard_testfile
 > +
 > +if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
 > +    return -1
 > +}
 > +
 > +# Skip all tests if Guile scripting is not enabled.
 > +if { [skip_guile_tests] } { continue }
 > +
 > +# Make the -gdb.scm script available to gdb, it is automagically loaded by gdb.
 > +# Care is taken to put it in the same directory as the binary so that
 > +# gdb will find it.
 > +set remote_obj_guile_file \
 > +    [remote_download \
 > +	 host ${srcdir}/${subdir}/${testfile}-gdb.scm.in \
 > +	 [standard_output_file ${testfile}-gdb.scm]]
 > +
 > +gdb_reinitialize_dir $srcdir/$subdir
 > +gdb_test_no_output "set auto-load safe-path ${remote_obj_guile_file}" \
 > +    "set auto-load safe-path"
 > +gdb_load ${binfile}
 > +# Verify gdb loaded the script.
 > +gdb_test "info auto-load guile-scripts" "Yes.*/${testfile}-gdb.scm.*" \
 > +    "Test auto-load had loaded guile scripts"
 > +
 > +if ![runto_main] then {
 > +    perror "couldn't run to breakpoint"
 > +    return
 > +}
 > +
 > +# Load global frame-unwinders
 > +set remote_guile_file [gdb_remote_download host \
 > +			    ${srcdir}/${subdir}/${testfile}.scm]
 > +gdb_scm_load_file ${remote_guile_file}
 > +
 > +# Test query
 > +gdb_test "guile (all-frame-unwinders)" \
 > +    ".*Dummy.*Auto-loaded dummy 2.*Synthetic.*Auto-loaded dummy.*" \
 > +    "all frame unwinders"
 > +gdb_test "guile (map frame-unwinder-priority (all-frame-unwinders))" \
 > +    ".*300 200 150 100.*" \
 > +    "all frame unwinder priorities"
 > +gdb_test "guile (map frame-unwinder-enabled? (all-frame-unwinders))" \
 > +    ".*#t #t #f #t.*" \
 > +    "all frame unwinders enabled?"
 > +
 > +gdb_test_no_output "guile (disable-frame-unwinder! \"Dummy\")" \
 > +    "disable dummy"
 > +gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
 > +    ".*#f.*" \
 > +    "dummy not enabled"
 > +gdb_test_no_output "guile (enable-frame-unwinder! \"Dummy\")" \
 > +    "re-enable dummy"
 > +gdb_test "guile (frame-unwinder-enabled? (find-frame-unwinder-by-name \"Dummy\"))"\
 > +    ".*#t.*" \
 > +    "dummy re-enabled"
 > +
 > +gdb_test_no_output "guile (enable-frame-unwinder! \"Synthetic\")" \
 > +    "enable synthetic unwinder"
 > +
 > +gdb_breakpoint "f2"
 > +gdb_continue_to_breakpoint "breakpoint at f2"
 > +
 > +gdb_test "bt 10" " f2 .*deadbeef .*deadbeef .*"
 > diff --git a/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
 > new file mode 100644
 > index 0000000..e5cf2fa
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
 > @@ -0,0 +1,41 @@
 > +;; Copyright (C) 2015 Free Software Foundation, Inc.
 > +;;
 > +;; This program is free software; you can redistribute it and/or modify
 > +;; it under the terms of the GNU General Public License as published by
 > +;; the Free Software Foundation; either version 3 of the License, or
 > +;; (at your option) any later version.
 > +;;
 > +;; This program is distributed in the hope that it will be useful,
 > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +;; GNU General Public License for more details.
 > +;;
 > +;; You should have received a copy of the GNU General Public License
 > +;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +;; This file is part of the GDB test-suite.  It tests Guile-based frame
 > +;; filters.
 > +
 > +(use-modules (gdb)
 > +             (gdb frame-unwinders))
 > +
 > +(define (dummy-unwinder frame)
 > +  #f)
 > +
 > +(add-frame-unwinder!
 > + (make-frame-unwinder "Dummy" dummy-unwinder #:priority 300))
 > +
 > +
 > +(define (synthetic-unwinder frame)
 > +  ;; Increment the stack pointer, set IP to 0xdeadbeef
 > +  (let* ((this-pc (ephemeral-frame-read-register frame "rip"))
 > +         (this-sp (ephemeral-frame-read-register frame "rsp")))
 > +    (set-ephemeral-frame-id! frame this-sp this-pc)
 > +    (ephemeral-frame-add-saved-register! frame "rip"
 > +                                         (value-cast (make-value #xdeadbeef)
 > +                                                     (value-type this-pc)))
 > +    (ephemeral-frame-add-saved-register! frame "rsp" (value-add this-sp 32))))
 > +
 > +(add-frame-unwinder!
 > + (make-frame-unwinder "Synthetic" synthetic-unwinder #:priority 150
 > +                      #:enabled? #f))
 > -- 
 > 2.1.4
 > 

-- 
/dje

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] Add Guile frame unwinder interface
  2015-03-10 17:48         ` Doug Evans
@ 2015-03-11  9:33           ` Andy Wingo
  0 siblings, 0 replies; 9+ messages in thread
From: Andy Wingo @ 2015-03-11  9:33 UTC (permalink / raw)
  To: Doug Evans; +Cc: Pedro Alves, gdb-patches

Hi :)

[-asmundak, as he probably doesn't care about Guile API things]

A couple quick replies.  ACK to all the other things.

On Tue 10 Mar 2015 18:48, Doug Evans <dje@google.com> writes:

>  > +@deffn {Scheme Procedure} make-frame-unwinder name procedure @
>  > +       @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]} @
>  > +       @r{[}#:objfile objfile@r{]} @r{[}#:progspace progspace@r{]}
>
> Instead of both #:objfile and #:progspace I'd rather just provide
> say, #:locus or #:scope or some such.

Let's do #:scope then.  Note that I copied this infrastructure from the
frame filter patch, which should also be ready for review :)  I'll apply
corresponding changes there and update both patches.

>  > +            add-frame-unwinder!
>  > +            remove-frame-unwinder!
>
> As a user who can't remember names very well, consistent naming
> is welcome. There is a (relatively) consistent naming thus far:
> register-breakpoint!, delete-breakpoint!, register-parameter!,
> register-command!.
>
> -> register-frame-unwinder! and delete-frame-unwinder!
>
> But I can quibble over the names too :-).
> "delete-breakpoint!" doesn't really delete it so much as remove it
> from gdb's tables. So I could go with remove-frame-unwinder!,
> add remove-breakpoint! and deprecate delete-breakpoint!.
>
> Plus it's nice to have names that go together: If we're going to have
> register-foo, the intuitive opposite is unregister-foo.
> But I'm ok with register-foo and remove-foo.

How about register-frame-unwinder! and unregister-frame-unwinder!.  It
matches the frame-unwinder-registered? predicate.  "Add" and "remove"
already raised the question of "to what?", and in that regard "register"
might be a bit clearer.

> Now's a good a time as any to lay down how things get deprecated in
> the Guile API.  We could have a guile/lib/gdb/deprecated.scm file that
> defined register-foo! (and others) or some such. As for when to delete
> them, I'm ok with anything <= 5 years.

Sure.  Or, we leave the interfaces where they are and just issue
deprecation warnings when they are used.

>
>  > +            enable-frame-unwinder!
>  > +            disable-frame-unwinder!
>
> Similarly, there is set-breakpoint-enabled!.
> -> set-frame-unwinder-enabled!

Uf, that's terrible :)  In Guile the convention is mostly
set-TYPE-FIELD!, and here TYPE is "frame-unwinder" and FIELD is
"enabled?", so the usual production is "set-frame-unwinder-enabled?!".
Really.  It's ugly but it does avoid the ambiguous reading of
"set-frame-unwinder-enabled!" as "set the frame unwinder to be enabled".

Actually it's so ugly that historically set-foo-enabled?! sometimes gets
replaced by more verby forms (cf in Guile's NEWS, `set-batch-mode?!'
replaced by `ensure-batch-mode!').

There is also the issue that without a good story yet on how to control
enabled/disabled status from the console, a common thing a user might
want to do would be to say "%&^%*&%@@@!  It's not working, so let's try
again with all the unwinders disabled."  It's much easier to do:

  (gdb) guile (for-each disable-frame-unwinder! (all-frame-unwinders))

than to

  (gdb) guile (for-each (lambda (uw) (set-frame-unwinder-enabled! uw #f)) (all-frame-unwinders))

(Should enabling / disabling unwinders invalidate the frame cache, I
wonder?)

What's the right way out of here? :)  I can change, it doesn't matter
much, but I wanted to argue a bit for the status quo.

What about adding a set-frame-unwinder-enabled! setter for consistency
and also keeping enable-frame-unwinder! / disable-frame-unwinder!.  I
don't know, none of the options are perfect :)

>  > +/* (set-ephemeral-frame-id! ephemeral-frame stack-address
>  > +                            [code-address [special-address]])
>  > +
>  > +   Set the frame ID on this ephemeral frame.  */
>  > +
>  > +static SCM
>  > +uwscm_set_ephemeral_frame_id_x (SCM ephemeral_frame, SCM sp, SCM ip,
>  > +				SCM special)
>
> I'm not sold on exporting the term "special" into the API.
> It conveys no information to the reader. "How special?" "Special in what way?"
> Internally we can call it whatever we like, but we should have a
> better name in the published API.
> I realize we don't want to pick an architecture-specific name,
> but as data for the discussion, is this only used by ia64?

It's "special" all through the internal API, of course.  However it does
appear to only be called by ia64-hpux-tdep.c and ia64-tdep.c.

We could simply not support "special" in the Guile and/or Python APIs,
for now at least.  I hope to live a long life and, when I die, to look
back in satisfaction that I never worked on an IA64 system :-))

Thanks for the review, will update this and the frame filter patch.

Andy

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2015-03-11  9:33 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-05 15:58 [PATCH] Add Guile frame unwinder interface Andy Wingo
2015-03-09 10:34 ` [PATCH v2] " Andy Wingo
2015-03-09 15:42   ` Pedro Alves
2015-03-09 16:22     ` Eli Zaretskii
2015-03-09 18:54     ` Andy Wingo
2015-03-10  9:03       ` [PATCH v3] " Andy Wingo
2015-03-10 17:48         ` Doug Evans
2015-03-11  9:33           ` Andy Wingo
2015-03-10 15:46       ` [PATCH v2] " Pedro Alves

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).