diff mbox

Add Guile JIT reader interface

Message ID 8761asy1rr.fsf@igalia.com
State New
Headers show

Commit Message

Andy Wingo Feb. 23, 2015, 5:29 p.m. UTC
Hi,

This patch lets users unwind the stack from Guile.  It is built on the
custom JIT debug info reader API.  Thoughts?

Andy
From 186b133a4a4a55db5b7bc2ad4d1f7f00930e0303 Mon Sep 17 00:00:00 2001
From: Andy Wingo <wingo@igalia.com>
Date: Mon, 23 Feb 2015 18:26:54 +0100
Subject: [PATCH] Add Guile JIT reader interface

gdb/ChangeLog:
2015-02-23  Andy Wingo  <wingo@igalia.com>

	* Makefile.in (scm-jit-reader.o, SUBDIR_GUILE_SRCS)
	(SUBDIR_GUILE_OBS): Add scm-jit-reader.c to the build.
	* guile/guile-internal.h: JIT reader initialization declaration.
	* guile/guile.c (initialize_gdb_module): Initialize the JIT
	reader.
	* guile/lib/gdb.scm: Add jit-reader exports.
	* guile/scm-jit-reader.c: New file.
	* jit.h (jit_reader_loaded, jit_reader_load_funcs)
	(jit_reader_unload): New declarations.
	* jit.c (jit_reader_loaded): New function.
	(jit_reader_load_funcs_with_handle): New helper.
	(jit_reader_load_funcs): New function, exported to the rest of
	GDB.
	(jit_reader_load, jit_reader_load_command): Refactor to call new
	helpers.
	(jit_reader_unload): Use jit_reader_loaded, adapt to not
	require a shared library handle, and export to the rest of GDB.
	(jit_reader_unload_command): Call jit_reader_unload.
	(jit_reader_try_read_symtab, jit_frame_sniffer)
	(jit_frame_this_id): Use jit_reader_loaded.

gdb/testsuite/ChangeLog:
2015-02-23  Andy Wingo  <wingo@igalia.com>

	* gdb.guile/scm-jit-reader.scm:
	* gdb.guile/scm-jit-reader.exp:
	* gdb.guile/scm-jit-reader.c:
	* gdb.guile/jit-protocol.h: New files.

gdb/doc/ChangeLog:
2015-02-23  Andy Wingo  <wingo@igalia.com>

	* guile.texi (Custom Debug Info In Guile): New section.
---
 gdb/ChangeLog                              |  23 +
 gdb/Makefile.in                            |   6 +
 gdb/doc/ChangeLog                          |   4 +
 gdb/doc/guile.texi                         | 108 +++++
 gdb/guile/guile-internal.h                 |   1 +
 gdb/guile/guile.c                          |   1 +
 gdb/guile/lib/gdb.scm                      |   9 +
 gdb/guile/scm-jit-reader.c                 | 712 +++++++++++++++++++++++++++++
 gdb/jit.c                                  |  70 ++-
 gdb/jit.h                                  |  16 +
 gdb/testsuite/ChangeLog                    |   7 +
 gdb/testsuite/gdb.guile/jit-protocol.h     |  54 +++
 gdb/testsuite/gdb.guile/scm-jit-reader.c   |  55 +++
 gdb/testsuite/gdb.guile/scm-jit-reader.exp |  53 +++
 gdb/testsuite/gdb.guile/scm-jit-reader.scm |  48 ++
 15 files changed, 1150 insertions(+), 17 deletions(-)
 create mode 100644 gdb/guile/scm-jit-reader.c
 create mode 100644 gdb/testsuite/gdb.guile/jit-protocol.h
 create mode 100644 gdb/testsuite/gdb.guile/scm-jit-reader.c
 create mode 100644 gdb/testsuite/gdb.guile/scm-jit-reader.exp
 create mode 100644 gdb/testsuite/gdb.guile/scm-jit-reader.scm

Comments

Eli Zaretskii Feb. 23, 2015, 7:53 p.m. UTC | #1
> From: Andy Wingo <wingo@igalia.com>
> Date: Mon, 23 Feb 2015 18:29:44 +0100
> 
> This patch lets users unwind the stack from Guile.  It is built on the
> custom JIT debug info reader API.  Thoughts?

Thanks.  A few comments about the documentation parts.

> +@var{get-frame-id} computes and returns a frame ID, which is a pair
> +whose car is a code address and whose cdr is a frame pointer.  A frame

I think "car" and "cdr" should be in @code (they are symbols).

> +@deffn {Scheme Procedure} make-jit-reader-block name begin end children
> +Construct and return a new JIT reader block describing the code region
> +between @var{begin} and @var{end}, both integers.  @var{name} is a

The usual question that comes to mind when I read about such
interfaces is whether the region includes END or not.  How about
stating that explicitly?

> +If the value of the register is not known, this function returns
> +@code{#f}.  Otherwise, it returns the value as a bytevector.

Why a bytevector?  Isn't the value a single scalar?

> +@deffn {Scheme Procedure} jit-reader-write-register register value
> +Set the previous value of @var{register} to @var{value}.  As in
> +@code{jit-reader-read-register}, this function may only be called
> +within an @code{unwind-frame} or @code{get-frame-info} callback.
> +@var{register} may be a string or an integer, and @var{value} may be a
> +bytevector or @code{#f}.

I think this begs for describing the semantics of writing #f into a
register.  I don't think it's self-explanatory.

The log message promises a NEWS entry, but I didn't see diffs for
that.

Otherwise, OK for the documentation.
diff mbox

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 40dc8e7..52a12cb 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,28 @@ 
 2015-02-23  Andy Wingo  <wingo@igalia.com>
 
+	* Makefile.in (scm-jit-reader.o, SUBDIR_GUILE_SRCS)
+	(SUBDIR_GUILE_OBS): Add scm-jit-reader.c to the build.
+	* guile/guile-internal.h: JIT reader initialization declaration.
+	* guile/guile.c (initialize_gdb_module): Initialize the JIT
+	reader.
+	* guile/lib/gdb.scm: Add jit-reader exports.
+	* guile/scm-jit-reader.c: New file.
+	* jit.h (jit_reader_loaded, jit_reader_load_funcs)
+	(jit_reader_unload): New declarations.
+	* jit.c (jit_reader_loaded): New function.
+	(jit_reader_load_funcs_with_handle): New helper.
+	(jit_reader_load_funcs): New function, exported to the rest of
+	GDB.
+	(jit_reader_load, jit_reader_load_command): Refactor to call new
+	helpers.
+	(jit_reader_unload): Use jit_reader_loaded, adapt to not
+	require a shared library handle, and export to the rest of GDB.
+	(jit_reader_unload_command): Call jit_reader_unload.
+	(jit_reader_try_read_symtab, jit_frame_sniffer)
+	(jit_frame_this_id): Use jit_reader_loaded.
+
+2015-02-23  Andy Wingo  <wingo@igalia.com>
+
 	* NEWS: Announce JIT reader interface change.
 	* jit-reader.in (GDB_READER_INTERFACE_VERSION): Bump to 2.
 	(gdb_read_debug_info): Update typedef to express the memory as a
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 7f1a8a6..5dfa876 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -313,6 +313,7 @@  SUBDIR_GUILE_OBS = \
 	scm-frame-filter.o \
 	scm-gsmob.o \
 	scm-iterator.o \
+	scm-jit-reader.o \
 	scm-lazy-string.o \
 	scm-objfile.o \
 	scm-math.o \
@@ -340,6 +341,7 @@  SUBDIR_GUILE_SRCS = \
 	guile/scm-frame-filter.c \
 	guile/scm-gsmob.c \
 	guile/scm-iterator.c \
+	guile/scm-jit-reader.c \
 	guile/scm-lazy-string.c \
 	guile/scm-objfile.c \
 	guile/scm-math.c \
@@ -2421,6 +2423,10 @@  scm-iterator.o: $(srcdir)/guile/scm-iterator.c
 	$(COMPILE) $(srcdir)/guile/scm-iterator.c
 	$(POSTCOMPILE)
 
+scm-jit-reader.o: $(srcdir)/guile/scm-jit-reader.c
+	$(COMPILE) $(srcdir)/guile/scm-jit-reader.c
+	$(POSTCOMPILE)
+
 scm-lazy-string.o: $(srcdir)/guile/scm-lazy-string.c
 	$(COMPILE) $(srcdir)/guile/scm-lazy-string.c
 	$(POSTCOMPILE)
diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
index 017bc82..313f95a 100644
--- a/gdb/doc/ChangeLog
+++ b/gdb/doc/ChangeLog
@@ -1,3 +1,7 @@ 
+2015-02-23  Andy Wingo  <wingo@igalia.com>
+
+	* guile.texi (Custom Debug Info In Guile): 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 7bfdda8..21249cf 100644
--- a/gdb/doc/guile.texi
+++ b/gdb/doc/guile.texi
@@ -158,6 +158,7 @@  from the Guile interactive prompt.
 * I/O Ports in Guile::       GDB I/O ports
 * Memory Ports in Guile::    Accessing memory through ports and bytevectors
 * Iterators In Guile::       Basic iterator support
+* Custom Debug Info In Guile:: Using Guile to read JIT debug info
 @end menu
 
 @node Basic Guile
@@ -4093,6 +4094,113 @@  Run @var{iterator} until the result of @code{(pred element)} is true
 and return that as the result.  Otherwise return @code{#f}.
 @end deffn
 
+@node Custom Debug Info In Guile
+@subsubsection Custom Debug Info in Guile
+@cindex custom JIT debug info, Guile
+@cindex JIT debug info reader, Guile
+
+Getting good backtraces out of a target that does just-in-time
+compilation can be tricky.  One of the possible solutions offered by
+@value{GDBN} is to use custom JIT debug info readers, which are
+loadable modules that teach GDB about the internal details of the
+target's code generator.  From there good line number and stack traces
+become possible.  @xref{Custom Debug Info}, for more on custom debug
+info readers.
+
+@value{GDBN} also supports custom debug info readers in Guile.  This
+is pleasant because you don't have to build a specialized shared
+object; you can just write Scheme.  It's also more expressive, because
+Guile extensions have access to the full range of GDB's capabilities,
+unlike the limited interface exposed to C debug info readers.
+
+Only one JIT reader may be active at a time, whether loaded from a
+shared library or implemented as a Guile extension.  To load an
+extension with Guile, use the @code{jit-reader-load} Guile function:
+
+@deffn {Scheme Procedure} jit-reader-load read-debug-info unwind-frame get-frame-id
+Install a custom JIT debug info reader.
+
+@var{read-debug-info} is called whenever GDB becomes aware of new JIT
+code, usually when the target registers a new JIT symfile.
+@var{read-debug-info} will then be called with two arguments: the
+address of the symfile on the target, and its size in bytes.  Both
+values are integers.  The composition of the symfile depends on the
+target.
+
+The @var{read-debug-info} procedure should return debugging
+information for the given symfile, or @code{#f} to fall back and treat
+the target symfile as a platform-native object.  Otherwise, the return
+value should be a list of JIT reader objects, created with
+@code{make-jit-reader-object}.
+
+@var{unwind-frame} is called to unwind a frame, and is called with no
+arguments.  While in the @var{unwind-frame} callback, the user can
+call @code{jit-reader-read-register} to read registers from the
+current frame, and @code{jit-reader-write-register} to inform GDB of
+computed values of registers in the next oldest frame.
+
+@var{get-frame-id} computes and returns a frame ID, which is a pair
+whose car is a code address and whose cdr is a frame pointer.  A frame
+ID should be unique among frames while the frame is active.  Usually
+the code address should correspond to the entry point of the function,
+and the frame pointer should be the stack address at the entry point
+of the function.  Both addresses should be integers.
+@var{get-frame-id} takes no arguments.
+@end deffn
+
+@deffn {Scheme Procedure} jit-reader-unload
+Remove the current JIT debug info reader.
+@end deffn
+
+@deffn {Scheme Procedure} make-jit-reader-object symtabs
+Construct and return a new JIT reader object.  The object will contain
+the JIT reader symtabs given in the @var{symtabs} list.
+@end deffn
+
+@deffn {Scheme Procedure} make-jit-reader-symtab file-name blocks line-map
+Construct and return a new JIT reader symtab.  The symtab will contain
+the JIT reader symtabs given in the @var{blocks} list.
+@var{file-name} is a string that names the compilation unit of the
+blocks, or @code{#f} if the file name is unknown.
+
+@var{line-map} establishes mappings between code addresses and line
+numbers.  It is a vector of @samp{(@var{pc} . @var{line})} pairs,
+where both the @var{pc} and the @var{line} are integers.
+@end deffn
+
+@deffn {Scheme Procedure} make-jit-reader-block name begin end children
+Construct and return a new JIT reader block describing the code region
+between @var{begin} and @var{end}, both integers.  @var{name} is a
+string naming the block, or @code{#f} if the name is unknown.
+@var{children} is a list of child blocks.
+@end deffn
+
+@deffn {Scheme Procedure} jit-reader-read-register register
+Read a register from the current frame in the target.  This function
+may only be called while within an @code{unwind-frame} or
+@code{get-frame-info} callback, and will raise an error otherwise.  If
+@var{register} is given as a string, it should name a register for the
+current architecture.  @var{register} may also be given as an integer
+corresponding to the DWARF register number for the target.
+
+If the value of the register is not known, this function returns
+@code{#f}.  Otherwise, it returns the value as a bytevector.
+@end deffn
+
+@deffn {Scheme Procedure} jit-reader-write-register register value
+Set the previous value of @var{register} to @var{value}.  As in
+@code{jit-reader-read-register}, this function may only be called
+within an @code{unwind-frame} or @code{get-frame-info} callback.
+@var{register} may be a string or an integer, and @var{value} may be a
+bytevector or @code{#f}.
+@end deffn
+
+As a convenience to users, the return value from a
+@code{read-debug-info} callback can be abbreviated.  If the return
+value is a JIT reader block, it is wrapped in an anonymous JIT reader
+symtab; if it is a symtab, it is wrapped in a JIT reader object; and
+finally, a JIT reader object is wrapped in a list.
+
 @node Guile Auto-loading
 @subsection Guile Auto-loading
 @cindex guile auto-loading
diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
index 9733e20..fb3b399 100644
--- a/gdb/guile/guile-internal.h
+++ b/gdb/guile/guile-internal.h
@@ -605,6 +605,7 @@  extern void gdbscm_initialize_exceptions (void);
 extern void gdbscm_initialize_frames (void);
 extern void gdbscm_initialize_frame_filters (void);
 extern void gdbscm_initialize_iterators (void);
+extern void gdbscm_initialize_jit_readers (void);
 extern void gdbscm_initialize_lazy_strings (void);
 extern void gdbscm_initialize_math (void);
 extern void gdbscm_initialize_objfiles (void);
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index 0533ba8..1ef5128 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -665,6 +665,7 @@  initialize_gdb_module (void *data)
   gdbscm_initialize_frames ();
   gdbscm_initialize_frame_filters ();
   gdbscm_initialize_iterators ();
+  gdbscm_initialize_jit_readers ();
   gdbscm_initialize_lazy_strings ();
   gdbscm_initialize_math ();
   gdbscm_initialize_objfiles ();
diff --git a/gdb/guile/lib/gdb.scm b/gdb/guile/lib/gdb.scm
index 2eb05ea..043dbbc 100644
--- a/gdb/guile/lib/gdb.scm
+++ b/gdb/guile/lib/gdb.scm
@@ -238,6 +238,15 @@ 
  end-of-iteration
  end-of-iteration?
 
+ ;; scm-jit-reader.c
+ jit-reader-load
+ jit-reader-unload
+ make-jit-reader-object
+ make-jit-reader-symtab
+ make-jit-reader-block
+ jit-reader-read-register
+ jit-reader-write-register
+
  ;; scm-lazy-string.c
  ;; FIXME: Where's the constructor?
 
diff --git a/gdb/guile/scm-jit-reader.c b/gdb/guile/scm-jit-reader.c
new file mode 100644
index 0000000..c1c0b20
--- /dev/null
+++ b/gdb/guile/scm-jit-reader.c
@@ -0,0 +1,712 @@ 
+/* 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 "jit.h"
+#include "jit-reader.h"
+#include "user-regs.h"
+#include "guile-internal.h"
+
+/* The JIT reader interface builds debug information out of object,
+   symtab, and block objects.  These objects communicate symbol table and
+   line number information from Scheme to GDB.  Here we define the names
+   for those data types.  */
+static const char jit_reader_object_smob_name[] = "gdb:jit-reader-object";
+static const char jit_reader_symtab_smob_name[] = "gdb:jit-reader-symtab";
+static const char jit_reader_block_smob_name[] = "gdb:jit-reader-block";
+
+/* SMOB tags for the opaque JIT reader types.  */
+static scm_t_bits jit_reader_object_smob_tag;
+static scm_t_bits jit_reader_symtab_smob_tag;
+static scm_t_bits jit_reader_block_smob_tag;
+
+/* Installing a JIT reader in Guile will save the JIT reader callbacks
+   into this vector.  */
+static SCM jitscm_closures = SCM_BOOL_F;
+
+/* Valid indices into jitscm_closures.  */
+enum {
+  READ,
+  UNWIND,
+  GET_FRAME_ID,
+  CLOSURE_COUNT
+};
+
+/* Installing a JIT reader will use this gdb_reader_funcs structure.  */
+static struct gdb_reader_funcs scm_gdb_reader_funcs;
+
+/* Instead of passing around a callbacks structure to the JIT unwinder
+   callbacks, we instead expose the callbacks like jit-reader-read-register as
+   functions bound in the (gdb) module.  Calling these helpers outside the
+   dynamic extent of an unwind / get-frame-id callback is an error.  We
+   facilitate this pattern by dynamically setting unwind_callbacks to the
+   captured callbakcs structure from within unwind or get-frame-id.  */
+static struct gdb_unwind_callbacks *unwind_callbacks;
+
+/* Raise a Guile exception with the given message.  */
+#define MISC_ERROR(str) scm_misc_error (FUNC_NAME, str, SCM_EOL)
+
+/* Raise an exception unless unwind_callbacks is non-NULL.  */
+#define ASSERT_WITHIN_UNWIND_CALLBACK()					\
+  do {									\
+    if (unwind_callbacks == NULL)					\
+      MISC_ERROR (_("This function may not be called outside JIT reader")); \
+  } while (0)
+
+/* Enter the dynamic extent of a JIT unwind callback.  */
+#define ENTER_UNWIND_CALLBACK(new_callbacks)				\
+  do {									\
+    gdb_assert (unwind_callbacks == NULL);				\
+    unwind_callbacks = new_callbacks;					\
+  } while (0)
+
+/* Leave the dynamic extent of a JIT unwind callback.  */
+#define LEAVE_UNWIND_CALLBACK()						\
+  do {									\
+    gdb_assert (unwind_callbacks != NULL);				\
+    unwind_callbacks = NULL;						\
+  } while (0)
+
+/* Data definitions for JIT reader objects.  These data structures are
+   managed by Guile's garbage collector, as are the sub-object pointers
+   like the symtabs list or the file_name and lines fields.  */
+struct jitscm_gdb_object
+{
+  SCM symtabs;
+};
+
+struct jitscm_gdb_symtab
+{
+  const char *file_name;
+  SCM blocks;
+  int nlines;
+  struct gdb_line_mapping *lines;
+};
+
+struct jitscm_gdb_block
+{
+  const char *name;
+  GDB_CORE_ADDR begin;
+  GDB_CORE_ADDR end;
+  SCM children;
+};
+
+/* Type predicates for JIT reader objects.  */
+static int
+jitscm_is_jit_reader_object (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (jit_reader_object_smob_tag, obj);
+}
+
+static int
+jitscm_is_jit_reader_symtab (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (jit_reader_symtab_smob_tag, obj);
+}
+
+static int
+jitscm_is_jit_reader_block (SCM obj)
+{
+  return SCM_SMOB_PREDICATE (jit_reader_block_smob_tag, obj);
+}
+
+/* Helper to check list contents.  */
+static int
+is_list_of (int (*pred) (SCM), SCM list)
+{
+  for (; scm_is_pair (list); list = scm_cdr (list))
+    if (!pred (scm_car (list)))
+      return 0;
+  return scm_is_null (list);
+}
+
+/* Build a JIT reader object from a list of JIT reader symtabs.  */
+static SCM
+jitscm_make_jit_reader_object (SCM symtabs)
+{
+  struct jitscm_gdb_object *data;
+
+  if (!is_list_of (jitscm_is_jit_reader_symtab, symtabs))
+    gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG1, symtabs,
+					  "list of JIT reader symtabs"));
+  data = scm_gc_malloc (sizeof (*data), jit_reader_object_smob_name);
+  data->symtabs = symtabs;
+  SCM_RETURN_NEWSMOB (jit_reader_object_smob_tag, data);
+}
+  
+/* Build a JIT reader symtab from a name, a list of child JIT reader
+   blocks, and a line mapping vector.  Each element of LINES should be a
+   (pc . line) pair.  */
+static SCM
+jitscm_make_jit_reader_symtab (SCM name, SCM blocks, SCM lines)
+{
+  struct jitscm_gdb_symtab *data;
+  char *c_name;
+  int i, c_nlines;
+  struct gdb_line_mapping *c_lines;
+
+  if (gdbscm_is_false (name))
+    c_name = NULL;
+  else
+    {
+      char *tmp = scm_to_utf8_string (name);
+      c_name = scm_gc_strdup (tmp, jit_reader_symtab_smob_name);
+      xfree (tmp);
+    }
+
+  if (!is_list_of (jitscm_is_jit_reader_block, blocks))
+    gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG2, blocks,
+					  "list of JIT reader blocks"));
+
+  if (gdbscm_is_false (lines))
+    lines = scm_vector (SCM_EOL);
+  c_nlines = scm_to_int (scm_vector_length (lines));
+  c_lines = scm_gc_malloc_pointerless (c_nlines * sizeof (*c_lines),
+				       jit_reader_symtab_smob_name);
+
+  for (i = 0; i < c_nlines; i++) {
+    SCM elt = scm_c_vector_ref (lines, i);
+    c_lines[i].pc = gdbscm_scm_to_ulongest (scm_car (elt));
+    c_lines[i].line = scm_to_int (scm_cdr (elt));
+  }
+
+  data = scm_gc_malloc (sizeof (*data), jit_reader_symtab_smob_name);
+  data->file_name = c_name;
+  data->blocks = blocks;
+  data->nlines = c_nlines;
+  data->lines = c_lines;
+
+  SCM_RETURN_NEWSMOB (jit_reader_symtab_smob_tag, data);
+}
+  
+/* Build a JIT reader block from its name, its beginning and end
+   addresses, and a list of child blocks.  */
+static SCM
+jitscm_make_jit_reader_block (SCM name, SCM begin, SCM end, SCM children)
+{
+  struct jitscm_gdb_block *data;
+  char *c_name;
+  GDB_CORE_ADDR c_begin, c_end;
+
+  if (gdbscm_is_false (name))
+    c_name = NULL;
+  else
+    {
+      char *tmp = scm_to_utf8_string (name);
+      c_name = scm_gc_strdup (tmp, jit_reader_block_smob_name);
+      xfree (tmp);
+    }
+
+  c_begin = gdbscm_scm_to_ulongest (begin);
+  c_end = gdbscm_scm_to_ulongest (end);
+
+  if (!is_list_of (jitscm_is_jit_reader_block, children))
+    gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG4, children,
+					  "list of JIT reader blocks"));
+
+  
+  data = scm_gc_malloc (sizeof (*data), jit_reader_block_smob_name);
+  data->name = c_name;
+  data->begin = c_begin;
+  data->end = c_end;
+  data->children = children;
+  SCM_RETURN_NEWSMOB (jit_reader_block_smob_tag, data);
+}
+
+/* Data accessors for JIT reader objects.  */
+static struct jitscm_gdb_object *
+jitscm_jit_reader_object_data (SCM obj)
+{
+  gdb_assert (jitscm_is_jit_reader_object (obj));
+  return (struct jitscm_gdb_object *) SCM_SMOB_DATA (obj);
+}
+
+static struct jitscm_gdb_symtab *
+jitscm_jit_reader_symtab_data (SCM obj)
+{
+  gdb_assert (jitscm_is_jit_reader_symtab (obj));
+  return (struct jitscm_gdb_symtab *) SCM_SMOB_DATA (obj);
+}
+
+static struct jitscm_gdb_block *
+jitscm_jit_reader_block_data (SCM obj)
+{
+  gdb_assert (jitscm_is_jit_reader_block (obj));
+  return (struct jitscm_gdb_block *) SCM_SMOB_DATA (obj);
+}
+
+/* Add the information from the list of Scheme JIT reader blocks to GDB,
+   using the gdb_symbol_callbacks interface.  Note that this function may
+   recurse for sub-blocks.  */
+static void
+create_jit_reader_blocks (struct gdb_symbol_callbacks *cb,
+			  struct gdb_symtab *symtab, struct gdb_block *parent,
+			  SCM block_list)
+{
+  for (; !scm_is_null (block_list); block_list = scm_cdr (block_list))
+    {
+      struct jitscm_gdb_block *data;
+      struct gdb_block *block;
+      
+      data = jitscm_jit_reader_block_data (scm_car (block_list));
+      block = cb->block_open (cb, symtab, parent, data->begin, data->end,
+			      data->name);
+      create_jit_reader_blocks (cb, symtab, block, data->children);
+    }
+}
+
+/* Add the information from the list of Scheme JIT reader symtabs to GDB,
+   using the gdb_symbol_callbacks interface.  The line data was already
+   parsed and unpacked when the individual symtab objects were
+   created.  */
+static void
+create_jit_reader_symtabs (struct gdb_symbol_callbacks *cb,
+			   struct gdb_object *object, SCM symtab_list)
+{
+  for (; !scm_is_null (symtab_list); symtab_list = scm_cdr (symtab_list))
+    {
+      struct jitscm_gdb_symtab *data;
+      struct gdb_symtab *symtab;
+      
+      data = jitscm_jit_reader_symtab_data (scm_car (symtab_list));
+      symtab = cb->symtab_open (cb, object, data->file_name);
+      create_jit_reader_blocks (cb, symtab, NULL, data->blocks);
+      cb->line_mapping_add (cb, symtab, data->nlines, data->lines);
+
+      cb->symtab_close (cb, symtab);
+    }
+}
+
+/* The top of the chain: creating GDB objfiles from Scheme data.  We
+   recurse down into child symtab objects, which recurses further into
+   blocks.  */
+static void
+create_jit_reader_objects (struct gdb_symbol_callbacks *cb,
+			   SCM object_list)
+{
+  for (; !scm_is_null (object_list); object_list = scm_cdr (object_list))
+    {
+      struct jitscm_gdb_object *data;
+      struct gdb_object *object;
+      
+      data = jitscm_jit_reader_object_data (scm_car (object_list));
+      object = cb->object_open (cb);
+      create_jit_reader_symtabs (cb, object, data->symtabs);
+      cb->object_close (cb, object);
+    }
+}
+
+struct read_debug_info_args {
+  struct gdb_reader_funcs *self;
+  struct gdb_symbol_callbacks *cb;
+  GDB_CORE_ADDR symfile_addr;
+  long symfile_size;
+};
+
+/* Helper to unpack the result of a read-debug-info call while within the
+   error-catching gdbscm_call_guile.  */
+static SCM
+do_read_debug_info (void *data)
+{
+  struct read_debug_info_args *args = data;
+  SCM result;
+  SCM read = scm_c_vector_ref (jitscm_closures, READ);
+
+  result = scm_call_2 (read,
+		       gdbscm_scm_from_ulongest (args->symfile_addr),
+		       scm_from_long (args->symfile_size));
+
+  /* RESULT should be a list of JIT reader objects, or false if we don't know
+     anything.  */
+  if (gdbscm_is_false (result))
+    return result;
+
+  /* As a convenience to users, we allow some simplifications, as follows.  A
+     single JIT reader block is equivalent to an anonymous symtab containing
+     that block.  */
+  if (jitscm_is_jit_reader_block (result))
+    result = jitscm_make_jit_reader_symtab (SCM_BOOL_F, scm_list_1 (result),
+					    SCM_BOOL_F);
+
+  /* A single JIT reader symtab is equivalent to an object containing that
+     symtab.  */
+  if (jitscm_is_jit_reader_symtab (result))
+    result = jitscm_make_jit_reader_object (scm_list_1 (result));
+  
+  /* A single JIT reader object is equivalent to a list of that object.  */
+  if (jitscm_is_jit_reader_object (result))
+    result = scm_list_1 (result);
+  
+  if (!is_list_of (jitscm_is_jit_reader_object, result))
+    gdbscm_invalid_object_error ("read-debug-info", 0, result,
+				 "expected a list of JIT reader objects");
+
+  create_jit_reader_objects (args->cb, result);
+
+  return SCM_BOOL_T;
+}
+  
+/* Parse the debug info from the SYMFILE_SIZE bytes of memory located at
+   SYMFILE_ADDR on the target.  These values are taken from the
+   corresponding "struct jit_code_entry" node that the target has
+   registered using the GDB JIT compilation interface.
+
+   Return GDB_FAIL on failure and GDB_SUCCESS on success.  */
+static enum gdb_status
+guile_jit_reader_read (struct gdb_reader_funcs *self,
+		       struct gdb_symbol_callbacks *cb,
+		       GDB_CORE_ADDR symfile_addr, long symfile_size)
+{
+  SCM result;
+  struct read_debug_info_args args = { self, cb, symfile_addr, symfile_size };
+
+  result = gdbscm_call_guile (do_read_debug_info, &args,
+			      gdbscm_memory_error_p);
+
+  if (gdbscm_is_exception (result))
+    {
+      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+      return GDB_FAIL;
+    }
+
+  return gdbscm_is_true (result) ? GDB_SUCCESS : GDB_FAIL;
+}
+
+/* Unwind the current frame, CB is the set of unwind callbacks that
+   are to be used to do this.
+
+   Return GDB_FAIL on failure and GDB_SUCCESS on success.  */
+static enum gdb_status
+guile_jit_reader_unwind (struct gdb_reader_funcs *self,
+			 struct gdb_unwind_callbacks *cb)
+{
+  SCM result;
+  SCM unwind = scm_c_vector_ref (jitscm_closures, UNWIND);
+
+  ENTER_UNWIND_CALLBACK (cb);
+  result = gdbscm_safe_call_0 (unwind, gdbscm_memory_error_p);
+  LEAVE_UNWIND_CALLBACK ();
+
+  if (gdbscm_is_exception (result))
+    {
+      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+      return GDB_FAIL;
+    }
+
+  return gdbscm_is_false (result) ? GDB_FAIL : GDB_SUCCESS;
+}
+
+/* Helper to unpack the (pc . fp) pair while within the error-catching
+   gdbscm_call_guile.  */
+static SCM
+do_get_frame_id (void *args)
+{
+  struct gdb_frame_id *frame_id = args;
+  SCM result;
+  SCM get_frame_id = scm_c_vector_ref (jitscm_closures, GET_FRAME_ID);
+
+  result = scm_call_0 (get_frame_id);
+
+  if (!scm_is_pair (result))
+    gdbscm_invalid_object_error ("get-frame-id", 0, result,
+				 "expected a (pc . fp) pair of addresses");
+
+  frame_id->code_address = gdbscm_scm_to_ulongest (scm_car (result));
+  frame_id->stack_address = gdbscm_scm_to_ulongest (scm_cdr (result));
+
+  return SCM_UNSPECIFIED;
+}
+  
+/* Return the frame ID corresponding to the current frame, using C to
+   read the current register values.  See the comment on struct
+   gdb_frame_id.  */
+static struct gdb_frame_id
+guile_jit_reader_get_frame_id (struct gdb_reader_funcs *self,
+			       struct gdb_unwind_callbacks *cb)
+{
+  SCM result;
+  struct gdb_frame_id frame_id = { 0, 0 };
+
+  ENTER_UNWIND_CALLBACK (cb);
+  result = gdbscm_call_guile (do_get_frame_id, &frame_id,
+			      gdbscm_memory_error_p);
+  LEAVE_UNWIND_CALLBACK ();
+
+  if (gdbscm_is_exception (result))
+    {
+      gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+      /* Sadly, nothing we can do here to indicate error.  */
+    }
+
+  return frame_id;
+}
+
+/* Called when a reader is being unloaded to drop our reference to the
+   reader closures.  */
+static void
+guile_jit_reader_destroy (struct gdb_reader_funcs *self)
+{
+  jitscm_closures = SCM_BOOL_F;
+}
+
+/* (jit-reader-load read-debug-info unwind get-frame-id)
+   Load a JIT reader.  */
+static SCM
+jitscm_jit_reader_load (SCM read, SCM unwind, SCM get_frame_id)
+{
+  if (jit_reader_loaded ())
+    MISC_ERROR (_("JIT reader already loaded.  Run jit-reader-unload first."));
+
+  jitscm_closures = scm_c_make_vector (CLOSURE_COUNT, SCM_UNSPECIFIED);
+
+  scm_c_vector_set_x (jitscm_closures, READ, read);
+  scm_c_vector_set_x (jitscm_closures, UNWIND, unwind);
+  scm_c_vector_set_x (jitscm_closures, GET_FRAME_ID, get_frame_id);
+
+  scm_gdb_reader_funcs.reader_version = GDB_READER_INTERFACE_VERSION;
+  scm_gdb_reader_funcs.priv_data = NULL;
+  scm_gdb_reader_funcs.read = guile_jit_reader_read;
+  scm_gdb_reader_funcs.unwind = guile_jit_reader_unwind;
+  scm_gdb_reader_funcs.get_frame_id = guile_jit_reader_get_frame_id;
+  scm_gdb_reader_funcs.destroy = guile_jit_reader_destroy;
+
+  jit_reader_load_funcs (&scm_gdb_reader_funcs);
+
+  return SCM_UNSPECIFIED;
+}
+
+/* (jit-reader-unload)
+   Unload the current JIT reader.  If no JIT reader is currently loaded,
+   signal an error.  */
+static SCM
+jitscm_jit_reader_unload (void)
+{
+  int i;
+  struct value *res_val = NULL; /* Initialize to appease gcc warning.  */
+  volatile struct gdb_exception except;
+
+  gdbscm_parse_function_args (FUNC_NAME, SCM_ARG1, NULL, "i", index, &i);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      jit_reader_unload ();
+    }
+  GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+  return SCM_UNSPECIFIED;
+}
+
+/* Convert the string REGISTER_SCM to a register number for the current
+   architecture.  */
+static int
+jitscm_scm_to_regnum (SCM register_scm, const char *func_name, int scm_arg)
+{
+  int regnum;
+
+  if (scm_is_string (register_scm))
+    {
+      volatile struct gdb_exception except;
+      struct cleanup *cleanup;
+      char *register_str;
+
+      gdbscm_parse_function_args (func_name, scm_arg, 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 (get_current_arch (),
+						register_str,
+						strlen (register_str));
+	}
+      do_cleanups (cleanup);
+      GDBSCM_HANDLE_GDB_EXCEPTION (except);
+
+      if (regnum < 0)
+	gdbscm_out_of_range_error (func_name, scm_arg, register_scm,
+				   _("unknown register"));
+    }
+  else
+    regnum = scm_to_int (register_scm);
+
+  return regnum;
+}
+
+/* Returns the value of a particular register in the current frame.
+   The current frame is the frame that needs to be unwound into the
+   outer (earlier) frame.
+
+   REGISTER_SCM names the register, which may be a string or the DWARF
+   register number of the register that needs to be unwound.
+
+   Returns the value of the register requested, as a bytevector, or false
+   in the case that the the value of the register has been optimized away
+   or is otherwise unavailable.  */
+static SCM
+jitscm_jit_reader_read_register (SCM register_scm)
+{
+  SCM ret;
+  int regnum;
+  struct gdb_reg_value *value;
+
+  ASSERT_WITHIN_UNWIND_CALLBACK ();
+
+  regnum = jitscm_scm_to_regnum (register_scm, FUNC_NAME, SCM_ARG1);
+
+  value = unwind_callbacks->reg_get (unwind_callbacks, regnum);
+
+  if (value->defined)
+    {
+      gdb_assert (value->size > 0);
+      ret = scm_c_make_bytevector (value->size);
+      memcpy (SCM_BYTEVECTOR_CONTENTS (ret), value->value, value->size);
+    }
+  else
+    ret = SCM_BOOL_F;
+
+  value->free (value);
+
+  return ret;
+}
+
+/* gdb_reg_value_free function for our allocated values.  */
+static void
+free_gdb_reg_value (struct gdb_reg_value * val)
+{
+  xfree (val);
+}
+
+/* Sets the previous value of a particular register.  REGISTER_SCM names
+   the register, which may be a string or the DWARF register number of the
+   register whose value is to be set.
+
+   VALUE_SCM should be a bytevector, or false to indicate that the
+   register is not defined.  */
+static SCM
+jitscm_jit_reader_write_register (SCM register_scm, SCM value_scm)
+{
+  int regnum;
+  int value_size;
+  struct gdb_reg_value *value;
+
+  ASSERT_WITHIN_UNWIND_CALLBACK ();
+
+  regnum = jitscm_scm_to_regnum (register_scm, FUNC_NAME, SCM_ARG1);
+
+  if (gdbscm_is_false (value_scm))
+    value_size = 1;
+  else
+    value_size = scm_to_int (scm_bytevector_length (value_scm));
+
+  value = xmalloc (sizeof (*value) - 1 + value_size);
+  value->size = value_size;
+  value->defined = !gdbscm_is_false (value_scm);
+  value->free = free_gdb_reg_value;
+  if (value->defined)
+    memcpy (value->value, SCM_BYTEVECTOR_CONTENTS (value_scm), value_size);
+
+  unwind_callbacks->reg_set (unwind_callbacks, regnum, value);
+
+  return SCM_UNSPECIFIED;
+}
+
+/* Functions exported to Scheme.  */
+static const scheme_function jit_functions[] =
+{
+  { "jit-reader-load", 3, 0, 0, jitscm_jit_reader_load,
+    "\
+Install a JIT reader.\n\
+\n\
+Arguments: read-debug-info unwind-frame get-frame-id" },
+
+  { "jit-reader-unload", 0, 0, 0, jitscm_jit_reader_unload,
+    "\
+Unload the current JIT reader." },
+
+  { "make-jit-reader-object", 1, 0, 0, jitscm_make_jit_reader_object,
+    "\
+Make a new JIT reader object.\n\
+\n\
+Takes one argument: a list of <gdb:jit-reader-symtab> objects." },
+
+  { "make-jit-reader-symtab", 3, 0, 0, jitscm_make_jit_reader_symtab,
+    "\
+Make a new JIT reader symbol table.\n\
+\n\
+Takes three arguments: a file name, a list of <gdb:jit-reader-block>\n\
+definitions, and a vector of line mapping information.  The file name may\n\
+be #f to indicate an unknown file name.  Each element of the line mapping\n\
+vector should be a pc-line pair."},
+
+  { "make-jit-reader-block", 4, 0, 0, jitscm_make_jit_reader_block,
+    "\
+Make a new JIT reader block.\n\
+\n\
+\n\
+Takes four arguments: the name of the block, the target address of the\n\
+first instruction, the target end address, and a possibly empty list of\n\
+child blocks.  The name may be #f to indicate an unknown name." },
+  
+  { "jit-reader-read-register", 1, 0, 0, jitscm_jit_reader_read_register,
+    "\
+Read a value from a register in the current frame.\n\
+\n\
+The register may be given by its DWARF register number, or as a string.\n\
+If it is a string, it will be looked up in the current architecture.\n\
+\n\
+The result is a bytevector that is as big as the register in question.\n\
+\n\
+This function may only be called within a unwind or get-frame-id\n\
+JIT reader callback.\n\
+Arguments: string-or-int"},
+
+  { "jit-reader-write-register", 2, 0, 0, jitscm_jit_reader_write_register,
+    "\
+Sets the previous value of a particular register in the current frame.\n\
+\n\
+The register may be given by its DWARF register number, or as a string.\n\
+If it is a string, it will be looked up in the current architecture.\n\
+\n\
+The value should be given as a bytevector that is as big as the register\n\
+in question.\n\
+\n\
+This function may only be called within the unwind JIT reader callback.\n\
+Arguments: <gdb:jit-reader-object> line-mapping-table"},
+
+  END_FUNCTIONS
+};
+
+/* Initialize the opaque JIT reader object types and define the JIT reader
+   Scheme functions.  */
+void
+gdbscm_initialize_jit_readers (void)
+{
+  jit_reader_object_smob_tag =
+    gdbscm_make_smob_type (jit_reader_object_smob_name, 0);
+  jit_reader_symtab_smob_tag =
+    gdbscm_make_smob_type (jit_reader_symtab_smob_name, 0);
+  jit_reader_block_smob_tag =
+    gdbscm_make_smob_type (jit_reader_block_smob_name, 0);
+
+  gdbscm_define_functions (jit_functions, 1);
+}
diff --git a/gdb/jit.c b/gdb/jit.c
index e39290c..ab1e9ad 100644
--- a/gdb/jit.c
+++ b/gdb/jit.c
@@ -159,9 +159,39 @@  static struct jit_reader
 typedef struct gdb_reader_funcs * (reader_init_fn_type) (void);
 static const char *reader_init_fn_sym = "gdb_init_reader";
 
+/* Return nonzero if a JIT reader is currently loaded.  */
+
+int
+jit_reader_loaded (void)
+{
+  return loaded_jit_reader != NULL;
+}
+
+static void
+jit_reader_load_funcs_with_handle (struct gdb_reader_funcs *funcs,
+				   void *handle)
+{
+  struct jit_reader *new_reader = NULL;
+
+  gdb_assert (!jit_reader_loaded ());
+  gdb_assert (funcs->reader_version == GDB_READER_INTERFACE_VERSION);
+
+  new_reader = XCNEW (struct jit_reader);
+  new_reader->functions = funcs;
+  new_reader->handle = handle;
+
+  loaded_jit_reader = new_reader;
+}
+
+void
+jit_reader_load_funcs (struct gdb_reader_funcs *funcs)
+{
+  jit_reader_load_funcs_with_handle (funcs, NULL);
+}
+
 /* Try to load FILE_NAME as a JIT debug info reader.  */
 
-static struct jit_reader *
+static void
 jit_reader_load (const char *file_name)
 {
   void *so;
@@ -185,15 +215,13 @@  jit_reader_load (const char *file_name)
     error (_("Reader not GPL compatible."));
 
   funcs = init_fn ();
+
   if (funcs->reader_version != GDB_READER_INTERFACE_VERSION)
     error (_("Reader version does not match GDB version."));
 
-  new_reader = XCNEW (struct jit_reader);
-  new_reader->functions = funcs;
-  new_reader->handle = so;
-
   discard_cleanups (old_cleanups);
-  return new_reader;
+
+  jit_reader_load_funcs_with_handle (funcs, so);
 }
 
 /* Provides the jit-reader-load command.  */
@@ -207,7 +235,7 @@  jit_reader_load_command (char *args, int from_tty)
   if (args == NULL)
     error (_("No reader name provided."));
 
-  if (loaded_jit_reader != NULL)
+  if (jit_reader_loaded ())
     error (_("JIT reader already loaded.  Run jit-reader-unload first."));
 
   if (IS_ABSOLUTE_PATH (args))
@@ -216,25 +244,33 @@  jit_reader_load_command (char *args, int from_tty)
     so_name = xstrprintf ("%s%s%s", jit_reader_dir, SLASH_STRING, args);
   prev_cleanup = make_cleanup (xfree, so_name);
 
-  loaded_jit_reader = jit_reader_load (so_name);
+  jit_reader_load (so_name);
   do_cleanups (prev_cleanup);
 }
 
-/* Provides the jit-reader-unload command.  */
-
-static void
-jit_reader_unload_command (char *args, int from_tty)
+/* Unload the current jit-reader, if one is present.  */
+void
+jit_reader_unload (void)
 {
-  if (!loaded_jit_reader)
+  if (!jit_reader_loaded ())
     error (_("No JIT reader loaded."));
 
   loaded_jit_reader->functions->destroy (loaded_jit_reader->functions);
 
-  gdb_dlclose (loaded_jit_reader->handle);
+  if (loaded_jit_reader->handle != NULL)
+    gdb_dlclose (loaded_jit_reader->handle);
   xfree (loaded_jit_reader);
   loaded_jit_reader = NULL;
 }
 
+/* Provides the jit-reader-unload command.  */
+
+static void
+jit_reader_unload_command (char *args, int from_tty)
+{
+  jit_reader_unload ();
+}
+
 /* Per-program space structure recording which objfile has the JIT
    symbols.  */
 
@@ -833,7 +869,7 @@  jit_reader_try_read_symtab (struct jit_code_entry *code_entry,
       &priv_data
     };
 
-  if (loaded_jit_reader == NULL)
+  if (!jit_reader_loaded ())
     return 0;
 
   priv_data = entry_addr;
@@ -1166,7 +1202,7 @@  jit_frame_sniffer (const struct frame_unwind *self,
   callbacks.reg_set = jit_unwind_reg_set_impl;
   callbacks.target_read = jit_target_read_impl;
 
-  if (loaded_jit_reader == NULL)
+  if (!jit_reader_loaded ())
     return 0;
 
   funcs = loaded_jit_reader->functions;
@@ -1223,7 +1259,7 @@  jit_frame_this_id (struct frame_info *this_frame, void **cache,
   callbacks.target_read = jit_target_read_impl;
   callbacks.priv_data = &private;
 
-  gdb_assert (loaded_jit_reader);
+  gdb_assert (jit_reader_loaded ());
   funcs = loaded_jit_reader->functions;
 
   frame_id = funcs->get_frame_id (funcs, &callbacks);
diff --git a/gdb/jit.h b/gdb/jit.h
index c576368..c568ae5 100644
--- a/gdb/jit.h
+++ b/gdb/jit.h
@@ -20,6 +20,8 @@ 
 #ifndef JIT_H
 #define JIT_H
 
+struct gdb_reader_funcs;
+
 /* When the JIT breakpoint fires, the inferior wants us to take one of
    these actions.  These values are used by the inferior, so the
    values of these enums cannot be changed.  */
@@ -80,4 +82,18 @@  extern void jit_breakpoint_re_set (void);
 
 extern void jit_event_handler (struct gdbarch *gdbarch);
 
+/* Return nonzero if a JIT reader is currently loaded.  */
+
+extern int jit_reader_loaded (void);
+
+/* Load a JIT reader from a gdb_reader_funcs structure, instead of from a
+   shared object.  */
+
+extern void jit_reader_load_funcs (struct gdb_reader_funcs *funcs);
+
+/* Unload the current JIT reader, throwing an exception if no JIT reader
+   is currently loaded.  */
+
+extern void jit_reader_unload (void);
+
 #endif /* JIT_H */
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 1976d63..065a633 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,5 +1,12 @@ 
 2015-02-23  Andy Wingo  <wingo@igalia.com>
 
+	* gdb.guile/scm-jit-reader.scm:
+	* gdb.guile/scm-jit-reader.exp:
+	* gdb.guile/scm-jit-reader.c:
+	* gdb.guile/jit-protocol.h: New files.
+
+2015-02-23  Andy Wingo  <wingo@igalia.com>
+
 	* gdb.base/jitreader.c (read_debug_info): Update for changes in
 	JIT reader interface and jithost.c test.
 	* gdb.base/jithost.c (main): No need to allocate symfile objects
diff --git a/gdb/testsuite/gdb.guile/jit-protocol.h b/gdb/testsuite/gdb.guile/jit-protocol.h
new file mode 100644
index 0000000..f7f9285
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/jit-protocol.h
@@ -0,0 +1,54 @@ 
+/* Copyright (C) 2009-2013 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/>.  */
+
+#ifdef JIT_H
+#error "We don't include jit.h directly since we'd like the jit-reader unit  \
+        tests to break if we make ABI incompatible changes to the structures \
+        re-declared here."
+#endif
+
+#ifndef JIT_PROTOCOL_H
+#define JIT_PROTOCOL_H
+
+#include <stdint.h>
+
+typedef enum
+{
+  JIT_NOACTION = 0,
+  JIT_REGISTER,
+  JIT_UNREGISTER
+} jit_actions_t;
+
+
+struct jit_code_entry
+{
+  struct jit_code_entry *next_entry;
+  struct jit_code_entry *prev_entry;
+  void *symfile_addr;
+  uint64_t symfile_size;
+};
+
+
+struct jit_descriptor
+{
+  uint32_t version;
+  uint32_t action_flag;
+  struct jit_code_entry *relevant_entry;
+  struct jit_code_entry *first_entry;
+};
+
+#endif /* JIT_PROTOCOL_H */
diff --git a/gdb/testsuite/gdb.guile/scm-jit-reader.c b/gdb/testsuite/gdb.guile/scm-jit-reader.c
new file mode 100644
index 0000000..dd9765d
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-jit-reader.c
@@ -0,0 +1,55 @@ 
+/* Copyright (C) 2009-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/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+
+#include "jit-protocol.h"
+
+void __attribute__((noinline)) __jit_debug_register_code () { }
+
+struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };
+struct jit_code_entry only_entry;
+
+typedef void (jit_function_t) ();
+
+int main (int argc, char **argv)
+{
+  char *code = mmap (NULL, getpagesize (), PROT_WRITE | PROT_EXEC,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  jit_function_t *function = (jit_function_t *) code;
+
+  code[0] = 0xcc; /* SIGTRAP  */
+  code[1] = 0xc3; /* RET  */
+
+  only_entry.symfile_addr = code;
+  only_entry.symfile_size = 2;
+
+  __jit_debug_descriptor.first_entry = &only_entry;
+  __jit_debug_descriptor.relevant_entry = &only_entry;
+  __jit_debug_descriptor.action_flag = JIT_REGISTER;
+  __jit_debug_descriptor.version = 1;
+  __jit_debug_register_code ();
+
+  function ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.guile/scm-jit-reader.exp b/gdb/testsuite/gdb.guile/scm-jit-reader.exp
new file mode 100644
index 0000000..59e9cbe
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-jit-reader.exp
@@ -0,0 +1,53 @@ 
+# Copyright 2012-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/>.
+
+load_lib gdb-guile.exp
+
+standard_testfile
+
+if { (![istarget x86_64-*-*] && ![istarget i?86-*-*]) || ![is_lp64_target] } {
+    return -1;
+}
+
+# We cannot use prepare_for_testing as we have to set the safe-patch
+# to check objfile and progspace printers.
+if {[build_executable $testfile.exp $testfile $srcfile debug] == -1} {
+    return -1
+}
+
+# Start with a fresh gdb.
+gdb_exit
+gdb_start
+
+# Skip all tests if Guile scripting is not enabled.
+if { [skip_guile_tests] } { continue }
+
+gdb_test_no_output "set guile print-stack full" \
+    "Set guile print-stack to full"
+
+set remote_guile_file [gdb_remote_download host \
+			    ${srcdir}/${subdir}/${testfile}.scm]
+gdb_scm_load_file ${remote_guile_file}
+
+gdb_load ${binfile}
+
+if {$verbose > 0} {
+    gdb_test_no_output "set debug jit 1"
+}
+
+gdb_run_cmd
+gdb_test "" "Program received signal SIGTRAP, .*" "expect SIGTRAP"
+
+gdb_test "bt" "jit_function_00 \\(\\) at foo.lua:42.*"
diff --git a/gdb/testsuite/gdb.guile/scm-jit-reader.scm b/gdb/testsuite/gdb.guile/scm-jit-reader.scm
new file mode 100644
index 0000000..d43f3d4
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-jit-reader.scm
@@ -0,0 +1,48 @@ 
+;; Scheme 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/>.
+
+;; This file is loaded with scm_c_primitive_load, which is ok, but files
+;; loaded with it are not compiled.  So we do very little here, and do
+;; most of the initialization in init.scm.
+
+(use-modules (gdb))
+
+(define (read-debug-info addr size)
+  (make-jit-reader-symtab
+   "foo.lua"
+   (list
+    (make-jit-reader-block "jit_function_00" addr (+ addr size) '()))
+   (vector (cons addr 42))))
+
+(define (unwind-frame)
+  (let* ((rip (jit-reader-read-register "rip"))
+         (rsp (jit-reader-read-register "rsp"))
+         (ptr-type (type-pointer (arch-void-type (current-arch))))
+         (ra-loc (value-cast (make-value rsp) (type-pointer ptr-type)))
+         (ra (value->integer (value-dereference ra-loc))))
+    (jit-reader-write-register "rip" ra)
+    (jit-reader-write-register "rsp" (+ rsp (type-sizeof ptr-type)))))
+
+(define (get-frame-id)
+  (cons (jit-reader-read-register "rip") (jit-reader-read-register "rsp")))
+
+(define (load-reader)
+  (jit-reader-load read-debug-info unwind-frame get-frame-id))
+
+(jit-reader-load read-debug-info unwind-frame get-frame-id)