diff mbox

Add Guile frame unwinder interface

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

Commit Message

Andy Wingo March 5, 2015, 3:57 p.m. UTC
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
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 mbox

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index d55daf6..f9ea8e2 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,35 @@ 
+2015-03-05  Andy Wingo  <wingo@igalia.com>
+
+	* guile/scm-symbol.c (gdbscm_lookup_symbol): Don't error if there
+	is no selected frame and no block is selected; instead, fall back
+	to the current frame.
+	* guile/scm-frame-unwinder.c: New file.
+	* guile/lib/gdb/frame-unwinders.scm: New file.
+	* guile/guile.c (initialize_gdb_module): Call
+	gdbscm_initialize_frame_unwinders.
+	* guile/guile-internal.h (gdbscm_initialize_frame_unwinders): New
+	declaration.
+	* frame.c (get_prev_frame): Detect an attempt to recursively
+	unwind from the sentinel, and return NULL.
+	* frame-unwind.h (frame_unwind_got_bytes): Make buf arg const.
+	(frame_unwind_is_unwinding_innermost_frame): New declaration.
+	* frame-unwind.c (unwinding_innermost_frame): New file-local
+	variable.
+	(innermost_frame_unwind_begin, innermost_frame_unwind_end): New
+	functions.
+	(frame_unwind_is_unwinding_innermost_frame): New exported
+	predicate.
+	(frame_unwind_find_by_frame): Arrange for
+	frame_unwind_is_unwinding_innermost_frame to return true when
+	unwinding the innermost frame.
+	(frame_unwind_got_bytes): Make buf arg const.
+	* data-directory/Makefile.in (GUILE_SOURCE_FILES): Add
+	frame-unwinders.scm.
+	(GUILE_COMPILED_FILES): Add frame-unwinders.go.
+	* Makefile.in (SUBDIR_GUILE_OBS): Add scm-frame-unwinder.o.
+	(SUBDIR_GUILE_SRCS): Add scm-frame-unwinder.c
+	(scm-frame-unwinder.o): New target.
+
 2015-02-20  Andy Wingo  <wingo@igalia.com>
 
 	* guile/scm-value.c (gdbscm_value_dynamic_type): Fix typo in which
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 0ab4c51..c9110f0 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -315,6 +315,7 @@  SUBDIR_GUILE_OBS = \
 	scm-exception.o \
 	scm-frame.o \
 	scm-frame-filter.o \
+	scm-frame-unwinder.o \
 	scm-gsmob.o \
 	scm-iterator.o \
 	scm-lazy-string.o \
@@ -342,6 +343,7 @@  SUBDIR_GUILE_SRCS = \
 	guile/scm-exception.c \
 	guile/scm-frame.c \
 	guile/scm-frame-filter.c \
+	guile/scm-frame-unwinder.c \
 	guile/scm-gsmob.c \
 	guile/scm-iterator.c \
 	guile/scm-lazy-string.c \
@@ -2418,6 +2420,10 @@  scm-frame-filter.o: $(srcdir)/guile/scm-frame-filter.c
 	$(COMPILE) $(srcdir)/guile/scm-frame-filter.c
 	$(POSTCOMPILE)
 
+scm-frame-unwinder.o: $(srcdir)/guile/scm-frame-unwinder.c
+	$(COMPILE) $(srcdir)/guile/scm-frame-unwinder.c
+	$(POSTCOMPILE)
+
 scm-gsmob.o: $(srcdir)/guile/scm-gsmob.c
 	$(COMPILE) $(srcdir)/guile/scm-gsmob.c
 	$(POSTCOMPILE)
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 75aab1b..bb2722d 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -91,6 +91,7 @@  GUILE_SOURCE_FILES = \
 	gdb/boot.scm \
 	gdb/experimental.scm \
 	gdb/frame-filters.scm \
+	gdb/frame-unwinders.scm \
 	gdb/init.scm \
 	gdb/iterator.scm \
 	gdb/printing.scm \
@@ -101,6 +102,7 @@  GUILE_COMPILED_FILES = \
 	./gdb.go \
 	gdb/experimental.go \
 	gdb/frame-filters.go \
+	gdb/frame-unwinders.go \
 	gdb/iterator.go \
 	gdb/printing.go \
 	gdb/support.go \
diff --git a/gdb/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);