[v4] Add Guile frame unwinder interface
Commit Message
Voici a new version of the Guile frame unwinder patch. Changes:
* Separate ephemeral frame object info ephemeral frame + unwind info
objects; see:
http://article.gmane.org/gmane.comp.gdb.patches/105759
* Update management of unwinders as in newest frame filter patch
(http://article.gmane.org/gmane.comp.gdb.patches/105616)
- Specify #:scope when registering, not when creating unwinders
- s/add-frame-unwinder!/register-frame-unwinder!/
- s/remove-frame-unwinder!/unregister-frame-unwinder!/
- Add s/set-frame-unwinder-enabled!/
* Adapt for new TRY/CATCH/END_CATCH
Regards,
Andy
From 55dae8b56541ef51a03676ce48ea6c2cdeb25f29 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 | 205 +++++++
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 | 593 +++++++++++++++++++++
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 | 42 ++
18 files changed, 1311 insertions(+), 3 deletions(-)
create mode 100644 gdb/guile/lib/gdb/frame-unwinders.scm
create mode 100644 gdb/guile/scm-frame-unwinder.c
create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder-gdb.scm.in
create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder.c
create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder.exp
create mode 100644 gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
Comments
Andy Wingo writes:
> Voici a new version of the Guile frame unwinder patch. Changes:
>
> * Separate ephemeral frame object info ephemeral frame + unwind info
> objects; see:
>
> http://article.gmane.org/gmane.comp.gdb.patches/105759
>
> * Update management of unwinders as in newest frame filter patch
> (http://article.gmane.org/gmane.comp.gdb.patches/105616)
>
> - Specify #:scope when registering, not when creating unwinders
>
> - s/add-frame-unwinder!/register-frame-unwinder!/
>
> - s/remove-frame-unwinder!/unregister-frame-unwinder!/
>
> - Add s/set-frame-unwinder-enabled!/
>
> * Adapt for new TRY/CATCH/END_CATCH
>
> Regards,
>
> Andy
>
> >From 55dae8b56541ef51a03676ce48ea6c2cdeb25f29 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.
> ...
>
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index fed8035..08e08db 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 \
> @@ -2416,6 +2418,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 b4916b0..14d3653 100644
> --- a/gdb/data-directory/Makefile.in
> +++ b/gdb/data-directory/Makefile.in
> @@ -88,6 +88,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 \
> @@ -100,6 +101,7 @@ GUILE_NO_UNBOUND_WARNING_COMPILED_FILES = \
> GUILE_COMPILED_FILES = \
> gdb/experimental.go \
> gdb/frame-filters.go \
> + gdb/frame-unwinders.go \
> gdb/iterator.go \
> gdb/printing.go \
> gdb/support.go \
> diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
> index 162ace7..3be1926 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-15 Andy Wingo <wingo@igalia.com>
>
> * guile.texi (Guile Frame Filter API)
> diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
> index 3045d90..fdcbca3 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
> @@ -2137,6 +2138,210 @@ also possible to do the job of an decorator with a filter. Still,
> avoiding the stream interfaces can often be a good reason to use the
> simpler decorator layer.
>
> +@node Guile Frame Unwinder API
> +@subsubsection Unwinding Frames in Guile
> +@cindex frame unwinder api, guile
> +
> +In @value{GDBN} terminology, ``unwinding'' is the process of finding
> +an older (outer) frame on the stack. Unwinders form the core of
> +backtrace computation in @value{GDBN}. @value{GDBN} comes with
> +unwinders for each target architecture that it supports, and these
> +usually suffice to unwind the stack. However, some target programs
> +can have non-standard frame layouts that cannot be unwound by the
> +standard unwinders. This is often the case when working with
> +just-in-time compilation environments, for example in JavaScript
> +implementations. In such cases, users can define custom code in Guile
> +to programmatically unwind the problematic stack frames.
> +
> +Before getting into the API, we should discuss how unwinders work in
> +@value{GDBN}.
> +
> +As an example, consider a stack in which we have already computed
> +frame 0 and we want to compute frame 1. We say that frame 0 is the
> +``inner'' frame, and frame 1 will be the ``outer'' frame.
> +
> +Unwinding starts with a model of the state of all registers in an
> +inner, already unwound frame. In our case, we start with frame 0.
> +@value{GDBN} then constructs a 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
> +creates and returns an ``unwind info'' object for that frame. The
> +unwind info object contains a frame ID for 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{]}
> +Make a new frame unwinder.
> +
> +The unwinder will be identified by the string @var{name}.
> +@var{procedure} should be a function of one argument, taking an
> +ephemeral frame object. If the unwinder procedure decides to handle
> +the frame, it should return an unwind info object. Otherwise the
> +unwinder returns @code{#f}, @value{GDBN} will continue to search its
> +list for an unwinder.
> +
> +The unwinder will be initially enabled, unless the keyword argument
> +@code{#:enabled? #f} is given. Even if the unwinder is marked as
> +enabled, it will need to be registered with @value{GDBN} via
> +@code{register-frame-unwinder!} in order to take effect. When
> +registered, 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
Hi.
It would be good to forcefully limit the values of priority such that
a possible future implementation that referenced priorities
in C(/C++) could just use ints (or unsigned ints).
> +
> +@deffn {Scheme Procedure} all-frame-unwinders
> +Return a list of all frame unwinders.
> +@end deffn
> +
> +@deffn {Scheme Procedure} register-frame-unwinder! unwinder @
> + @r{[}#:scope scope@r{]}
> +Register the frame unwinder @var{unwinder} with @value{GDBN}.
> +
> +By default, the scope of the unwinder is global, meaning that it is
> +associated with all objfiles and progspaces. Pass an objfile or a
> +progspace as the @code{#:scope} keyword argument to instead scope the
> +unwinder into a specific objfile or progspace, respectively.
> +
> +The unwinder's name will be checked for uniqueness within its registered
> +scope.
> +@end deffn
> +
> +@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
> +@deffnx {Scheme Procedure} enable-frame-unwinder! unwinder
> +@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
> +Mark a frame unwinder as enabled, if @var{enabled?} is true, or
> +as disabled otherwise.
If we have {en,dis}able-frame-unwinder! then we should have
them for all things that can be enabled/disabled.
Such redundancy in the lowest level API bothers me,
and if a particular user wants to provide their own wrappers
it's trivial.
> +
> +@var{unwinder} can either be a frame unwinder object, or it can be a
> +string naming an unwinder in the current scope. If no such unwinder
> +is found, an error is signalled.
I think these should operate on just an unwinder object.
There is a convention for naming such objects (pretty-printers,
type-printers, frame-filters, xmethods, and so on) which involves
two pieces: scope(/locus) name and object name.
But that feels like something for a higher level API than
these primitives.
For reference sake, there's a further convention for pretty-printers
that split "object name" into two pieces, but it's not relevant
to this API, the name is still one string.
> +
> +@code{enable-frame-unwinder!} and @code{disable-frame-unwinder} are simple
> +wrappers around @code{set-frame-unwinder-enabled!} which pass @code{#t}
> +or @code{#f} as the @var{enabled?} argument, respectively.
> +@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. Currently ephemeral frames can only be used in two limited
> +ways: to read registers from the frame, and to construct an unwind
> +info object for a successful unwinder return.
> +
> +@deffn {Scheme Procedure} 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
> +
> +If an unwinder successfully unwinds a frame, it should return an
> +unwind info object created via the @code{make-unwind-info} procedure.
> +An unwind info object specifies the frame ID for its associated
> +ephemeral frame, and also records values of registers that are saved
> +within the frame.
> +
> +@deffn {Scheme Procedure} make-unwind-info frame fp [pc [special]]
> +Make an unwind info object for the ephemeral frame @var{frame}.
What if we defer adding "special" to a later patch?
It seems like it would be straightforward to add at a later date.
As much as you don't like "sniffer", "special" bugs me 10x more. :-)
> +
> +@var{fp}, @var{pc}, and @var{special} are used to build an identifier
> +(frame ID) for the frame. 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
> +
> +After building an unwind info object for an ephemeral frame, an
> +unwinder can call @code{unwind-info-add-saved-register!} to record
> +saved registers. The values of the saved registers logically belong
> +to the frame that is older than the ephemeral frame being unwound, not
> +to the ephemeral frame itself.
> +
> +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
> +Set the saved value of a register in a ephemeral frame.
> +
> +@var{register} names a register, and should be a string, for example
> +@samp{rip}. @var{value} is the register value, as a @value{GDBN}
> +value.
> +@end deffn
> +
> +Any register whose value is not recorded as saved via
> +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
> +in the outer frame.
> +
> @node Commands In Guile
> @subsubsection Commands In Guile
>
Bleah.
I need to head out.
I'll get to the code tonight or tomorrow.
Andy Wingo writes:
> Voici a new version of the Guile frame unwinder patch. Changes:
>
> * Separate ephemeral frame object info ephemeral frame + unwind info
> objects; see:
>
> http://article.gmane.org/gmane.comp.gdb.patches/105759
>
> * Update management of unwinders as in newest frame filter patch
> (http://article.gmane.org/gmane.comp.gdb.patches/105616)
>
> - Specify #:scope when registering, not when creating unwinders
>
> - s/add-frame-unwinder!/register-frame-unwinder!/
>
> - s/remove-frame-unwinder!/unregister-frame-unwinder!/
>
> - Add s/set-frame-unwinder-enabled!/
>
> * Adapt for new TRY/CATCH/END_CATCH
>
> Regards,
>
> Andy
>
> >From 55dae8b56541ef51a03676ce48ea6c2cdeb25f29 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.
> ...
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index fed8035..08e08db 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 \
> @@ -2416,6 +2418,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 b4916b0..14d3653 100644
> --- a/gdb/data-directory/Makefile.in
> +++ b/gdb/data-directory/Makefile.in
> @@ -88,6 +88,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 \
> @@ -100,6 +101,7 @@ GUILE_NO_UNBOUND_WARNING_COMPILED_FILES = \
> GUILE_COMPILED_FILES = \
> gdb/experimental.go \
> gdb/frame-filters.go \
> + gdb/frame-unwinders.go \
> gdb/iterator.go \
> gdb/printing.go \
> gdb/support.go \
> diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog
> index 162ace7..3be1926 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-15 Andy Wingo <wingo@igalia.com>
>
> * guile.texi (Guile Frame Filter API)
> diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
> index 3045d90..fdcbca3 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
> @@ -2137,6 +2138,210 @@ also possible to do the job of an decorator with a filter. Still,
> avoiding the stream interfaces can often be a good reason to use the
> simpler decorator layer.
>
> +@node Guile Frame Unwinder API
> +@subsubsection Unwinding Frames in Guile
> +@cindex frame unwinder api, guile
> +
> +In @value{GDBN} terminology, ``unwinding'' is the process of finding
> +an older (outer) frame on the stack. Unwinders form the core of
> +backtrace computation in @value{GDBN}. @value{GDBN} comes with
> +unwinders for each target architecture that it supports, and these
> +usually suffice to unwind the stack. However, some target programs
> +can have non-standard frame layouts that cannot be unwound by the
> +standard unwinders. This is often the case when working with
> +just-in-time compilation environments, for example in JavaScript
> +implementations. In such cases, users can define custom code in Guile
> +to programmatically unwind the problematic stack frames.
> +
> +Before getting into the API, we should discuss how unwinders work in
> +@value{GDBN}.
> +
> +As an example, consider a stack in which we have already computed
> +frame 0 and we want to compute frame 1. We say that frame 0 is the
> +``inner'' frame, and frame 1 will be the ``outer'' frame.
> +
> +Unwinding starts with a model of the state of all registers in an
> +inner, already unwound frame. In our case, we start with frame 0.
> +@value{GDBN} then constructs a 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
> +creates and returns an ``unwind info'' object for that frame. The
> +unwind info object contains a frame ID for 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{]}
> +Make a new frame unwinder.
> +
> +The unwinder will be identified by the string @var{name}.
> +@var{procedure} should be a function of one argument, taking an
> +ephemeral frame object. If the unwinder procedure decides to handle
> +the frame, it should return an unwind info object. Otherwise the
> +unwinder returns @code{#f}, @value{GDBN} will continue to search its
> +list for an unwinder.
> +
> +The unwinder will be initially enabled, unless the keyword argument
> +@code{#:enabled? #f} is given. Even if the unwinder is marked as
> +enabled, it will need to be registered with @value{GDBN} via
> +@code{register-frame-unwinder!} in order to take effect. When
> +registered, 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} register-frame-unwinder! unwinder @
> + @r{[}#:scope scope@r{]}
> +Register the frame unwinder @var{unwinder} with @value{GDBN}.
> +
> +By default, the scope of the unwinder is global, meaning that it is
> +associated with all objfiles and progspaces. Pass an objfile or a
> +progspace as the @code{#:scope} keyword argument to instead scope the
> +unwinder into a specific objfile or progspace, respectively.
> +
> +The unwinder's name will be checked for uniqueness within its registered
> +scope.
> +@end deffn
> +
> +@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
> +@deffnx {Scheme Procedure} enable-frame-unwinder! unwinder
> +@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
> +Mark a frame unwinder as enabled, if @var{enabled?} is true, or
> +as disabled otherwise.
> +
> +@var{unwinder} can either be a frame unwinder object, or it can be a
> +string naming an unwinder in the current scope. If no such unwinder
> +is found, an error is signalled.
> +
> +@code{enable-frame-unwinder!} and @code{disable-frame-unwinder} are simple
> +wrappers around @code{set-frame-unwinder-enabled!} which pass @code{#t}
> +or @code{#f} as the @var{enabled?} argument, respectively.
> +@end deffn
Remove enable-frame-unwinder!, disable-frame-unwinder!
and make set-frame-unwinder-enabled! take a frame-unwinder object only.
> +
> +@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
As discussed let's remove priority for now.
> +@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
s/ephemeral/pending/
> +valid only while they are being unwound; any access to an ephemeral
> +frame outside the extent of their unwind operation will signal an
> +error. Currently ephemeral frames can only be used in two limited
> +ways: to read registers from the frame, and to construct an unwind
> +info object for a successful unwinder return.
> +
> +@deffn {Scheme Procedure} 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
> +
> +If an unwinder successfully unwinds a frame, it should return an
> +unwind info object created via the @code{make-unwind-info} procedure.
> +An unwind info object specifies the frame ID for its associated
> +ephemeral frame, and also records values of registers that are saved
> +within the frame.
> +
> +@deffn {Scheme Procedure} make-unwind-info frame fp [pc [special]]
As discussed make the frame id a parameter,
and have a make-frame-id constructor.
(make-frame-id sp #:pc pc #:special special) -> <gdb:frame-id>
[I'm going to give up trying to defer special for another day.
With adequate documentation it could be tolerable.]
> +Make an unwind info object for the ephemeral frame @var{frame}.
> +
> +@var{fp}, @var{pc}, and @var{special} are used to build an identifier
> +(frame ID) for the frame. 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
> +
> +After building an unwind info object for an ephemeral frame, an
> +unwinder can call @code{unwind-info-add-saved-register!} to record
> +saved registers. The values of the saved registers logically belong
> +to the frame that is older than the ephemeral frame being unwound, not
> +to the ephemeral frame itself.
> +
> +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
> +Set the saved value of a register in a ephemeral frame.
> +
> +@var{register} names a register, and should be a string, for example
> +@samp{rip}. @var{value} is the register value, as a @value{GDBN}
To be more universal let's replace "rip" here with "pc".
> +value.
> +@end deffn
> +
> +Any register whose value is not recorded as saved via
> +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
> +in the outer frame.
"outer frame" here is confusing.
I'd have thought this would read "``not saved'' in the frame".
[with the frame?]
> +
> @node Commands In Guile
> @subsubsection Commands In Guile
>
> diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c
> index bba1ae7..4347dca 100644
> --- a/gdb/frame-unwind.c
> +++ b/gdb/frame-unwind.c
> @@ -87,6 +87,34 @@ frame_unwind_append_unwinder (struct gdbarch *gdbarch,
> (*ip)->unwinder = unwinder;
> }
>
> +/* Nonzero if we are finding the unwinder for a frame; see
> + frame_unwind_try_handler. */
> +static int is_unwinding = 0;
> +
> +/* Return nonzero if we are inside a sniffer call. */
> +
> +int
> +frame_unwind_is_unwinding (void)
> +{
> + return is_unwinding;
> +}
> +
> +/* Cleanup helpers for is_unwinding. */
> +
> +static void
> +unset_is_unwinding (void *unused)
> +{
> + is_unwinding = 0;
> +}
> +
> +static struct cleanup*
> +set_is_unwinding (void)
> +{
> + is_unwinding = 1;
> +
> + return make_cleanup (unset_is_unwinding, NULL);
> +}
> +
> /* Call SNIFFER from UNWINDER. If it succeeded set UNWINDER for
> THIS_FRAME and return 1. Otherwise the function keeps THIS_FRAME
> unchanged and returns 0. */
> @@ -98,11 +126,18 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
> struct cleanup *old_cleanup;
> int res = 0;
>
> + if (is_unwinding)
> + internal_error (__FILE__, __LINE__,
> + _("Recursion detected while finding an unwinder."));
> old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder);
>
> TRY
> {
> + struct cleanup *cleanup = set_is_unwinding ();
> +
> res = unwinder->sniffer (unwinder, this_frame, this_cache);
> +
> + do_cleanups (cleanup);
I didn't want to impose an order on the commits of python and scheme.
Once both are in we'll want to have python use set_is_unwinding too.
> }
> CATCH (ex, RETURN_MASK_ERROR)
> {
> @@ -252,7 +287,8 @@ frame_unwind_got_constant (struct frame_info *frame, int regnum,
> }
>
> struct value *
> -frame_unwind_got_bytes (struct frame_info *frame, int regnum, gdb_byte *buf)
> +frame_unwind_got_bytes (struct frame_info *frame, int regnum,
> + const gdb_byte *buf)
> {
> struct gdbarch *gdbarch = frame_unwind_arch (frame);
> struct value *reg_val;
> diff --git a/gdb/frame-unwind.h b/gdb/frame-unwind.h
> index 44add12..3e322f2 100644
> --- a/gdb/frame-unwind.h
> +++ b/gdb/frame-unwind.h
> @@ -179,6 +179,11 @@ extern void frame_unwind_append_unwinder (struct gdbarch *gdbarch,
> extern void frame_unwind_find_by_frame (struct frame_info *this_frame,
> void **this_cache);
>
> +/* Return nonzero if we are in the process of finding an unwinder for a frame.
> + See the comments in get_current_frame. */
> +
> +extern int frame_unwind_is_unwinding (void);
> +
> /* Helper functions for value-based register unwinding. These return
> a (possibly lazy) value of the appropriate type. */
>
> @@ -210,7 +215,7 @@ struct value *frame_unwind_got_constant (struct frame_info *frame, int regnum,
> inside BUF. */
>
> struct value *frame_unwind_got_bytes (struct frame_info *frame, int regnum,
> - gdb_byte *buf);
> + const gdb_byte *buf);
>
> /* Return a value which indicates that FRAME's saved version of REGNUM
> has a known constant (computed) value of ADDR. Convert the
> diff --git a/gdb/frame.c b/gdb/frame.c
> index b3cbf23..9e63f21 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -2212,6 +2212,22 @@ get_prev_frame (struct frame_info *this_frame)
> return NULL;
> }
>
> + /* Unwinders implemented in Python or Scheme could eventually make an API call
> + that would cause GDB to try to unwind a frame while unwinding a frame.
> + Because already-unwound frames will be found in the frame cache, unwinding
> + will only happen at the old end of the stack, which means that any
> + recursive unwinding attempt will surely lead to unbounded recursion. Ways
> + this can happen include such common functions as `get_current_arch' or
> + `lookup_symbol', via `get_selected_frame', so it's impractical to simply
> + declare these an error. Instead, we detect this case and return NULL,
> + indicating that the known stack of frames ends here. */
> + if (frame_unwind_is_unwinding ())
> + {
> + frame_debug_got_null_frame (this_frame,
> + "get_prev_frame within unwinder sniffer");
> + return NULL;
> + }
> +
> return get_prev_frame_always (this_frame);
> }
>
> diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
> index d7c166c..9ca807e 100644
> --- a/gdb/guile/guile-internal.h
> +++ b/gdb/guile/guile-internal.h
> @@ -598,6 +598,7 @@ extern void gdbscm_initialize_disasm (void);
> extern void gdbscm_initialize_exceptions (void);
> extern void gdbscm_initialize_frames (void);
> extern void gdbscm_initialize_frame_filters (void);
> +extern void gdbscm_initialize_frame_unwinders (void);
> extern void gdbscm_initialize_iterators (void);
> extern void gdbscm_initialize_lazy_strings (void);
> extern void gdbscm_initialize_math (void);
> diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
> index 176e97e..d1850e7 100644
> --- a/gdb/guile/guile.c
> +++ b/gdb/guile/guile.c
> @@ -670,6 +670,7 @@ initialize_gdb_module (void *data)
> gdbscm_initialize_disasm ();
> gdbscm_initialize_frames ();
> gdbscm_initialize_frame_filters ();
> + gdbscm_initialize_frame_unwinders ();
> gdbscm_initialize_iterators ();
> gdbscm_initialize_lazy_strings ();
> gdbscm_initialize_math ();
> diff --git a/gdb/guile/lib/gdb/frame-unwinders.scm b/gdb/guile/lib/gdb/frame-unwinders.scm
> new file mode 100644
> index 0000000..ead2443
> --- /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 (ephemeral-frame-read-register
> +
> + make-unwind-info
> + unwind-info-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
> +
> + register-frame-unwinder!
> + unregister-frame-unwinder!
> + set-frame-unwinder-enabled!
> + 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 -> unwind-info | #f
> + (procedure frame-unwinder-procedure)
> + ;; objfile | progspace | #f
> + (scope frame-unwinder-scope set-scope!))
> +
> +(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
In addition to removing priority (for now), let's remove enabled? as
a parameter here. The user can disable the unwinder before registering it.
> + "Make and return a new frame unwinder. NAME and PROCEDURE are
> +required arguments. Specify #:priority or #:enabled? to set the
> +priority and enabled status of the unwinder.
> +
> +The unwinder must be registered with GDB via `register-frame-unwinder!'
> +before it is active."
> + (let ((registered? #f) (scope #f))
> + (%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))))))
Not used anywhere. Delete?
> +
> +(define (prune-frame-unwinders!)
> + "Prune frame unwinders whose objfile or progspace has gone away,
> +returning a fresh list of frame unwinders."
s/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* (register-frame-unwinder! unwinder #:key scope)
> + "Register a frame unwinder with GDB. Frame unwinders must be
> +registered before they will be used to unwind backtraces.
> +
> +By default, the unwinder will be registered globally. Specify an
> +objfile or a progspace as the #:scope keyword argument to limit the
> +unwinder to a specific scope."
> + (define (check-scope!)
> + (cond
> + ((objfile? scope)
> + (unless (objfile-valid? scope)
> + (error "Objfile is not valid" scope)))
> + ((progspace? scope)
> + (unless (progspace-valid? scope)
> + (error "Progspace is not valid" scope)))
> + (scope
> + (error "Invalid scope" scope))))
Guile question: Does scope default to #f?
> + (define (duplicate-unwinder? other)
> + (and (equal? (frame-unwinder-name other)
> + (frame-unwinder-name unwinder))
> + (same-scope? (frame-unwinder-scope other) scope)))
> + (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*)))))))
> +
> + (check-scope!)
> + (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-scope! unwinder scope)
> + (set! *frame-unwinders*
> + (insert-sorted unwinder *frame-unwinders* priority>=?)))
> +
> +(define (unregister-frame-unwinder! unwinder)
Executive decision: s/unregister-frame-unwinder!/remove-frame-unwinder!/
I know register/unregister was my suggestion, but "unregister"
doesn't read very well to me.
> + "Unregister a frame unwinder."
> + (set-registered?! unwinder #f)
> + (set-scope! unwinder #f)
> + (set! *frame-unwinders* (delq unwinder *frame-unwinders*)))
> +
> +(define* (find-frame-unwinder-by-name name #:optional
> + (scope (current-progspace)))
The default scope when registering an unwinder is global.
Probably should have the same default throughout. I like global.
> + (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 (set-frame-unwinder-enabled! unwinder enabled?)
> + "Enable or disable a frame unwinder."
> + (let ((unwinder (if (frame-unwinder? unwinder)
> + unwinder
> + (find-frame-unwinder-by-name unwinder))))
Restrict this to only take an unwinder object.
> + (set-enabled?! unwinder enabled?)
> + *unspecified*))
> +
> +(define (enable-frame-unwinder! unwinder)
> + "Mark a frame unwinder as enabled."
> + (set-frame-unwinder-enabled! unwinder #t))
Delete.
Though I don't mind them living in some "utils" like module.
These ones can even take a name+scope argument.
> +
> +(define (disable-frame-unwinder! unwinder)
> + "Mark a frame unwinder as disabled."
> + (set-frame-unwinder-enabled! unwinder #f))
Ditto.
> +
> +(define (unwind-frame frame)
> + (let ((scope (current-progspace)))
> + (or-map (lambda (unwinder)
> + (and (frame-unwinder-enabled? unwinder)
> + (same-scope? (frame-unwinder-scope unwinder) scope)
> + ((frame-unwinder-procedure unwinder) frame)))
> + *frame-unwinders*)))
This needs to first loop over all unwinders in all objfiles in
current-progspace, then current-progspace, then global ones.
[Right?]
I sometimes wonder if all these things (pretty-printers, type-printers,
xmethods, unwinders, etc.) need to first check the current objfile,
and then all remaining objfiles (of the current progspace) but that's
a level of complication I haven't seen a need for yet.
This also needs to confirm each attempted unwinder is still valid.
I still prefer recording unwinders with the gdb:objfile, gdb:progspace
objects, but this is one case where I don't mind the inconsistency:
More boilerplate for different ways of doing things.
Making sure one can later be converted to the other is the hard/risky part.
> +
> +(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..b57806c
> --- /dev/null
> +++ b/gdb/guile/scm-frame-unwinder.c
> @@ -0,0 +1,593 @@
> +/* 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. */
s/apply-frame-filter/unwind-frame/ ?
> +static SCM unwind_frame = SCM_BOOL_F;
> +
> +/* Key that we use when associating data with an architecture. */
> +static struct gdbarch_data *uwscm_gdbarch_data;
> +
> +/* The frame unwinder interface makes ephemeral frame objects when trying
> + to unwind frames, and unwind info objects when unwinding is successful.
> + Here we define the names for the ephemeral frame and unwind info Scheme
> + data types. */
> +static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
> +static const char unwind_info_smob_name[] = "gdb:unwind-info";
> +
> +/* SMOB tag for ephemeral frames and unwind info. */
> +static scm_t_bits ephemeral_frame_smob_tag;
> +static scm_t_bits unwind_info_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;
> +};
> +
> +/* Data associated with an unwind info object. */
> +struct uwscm_unwind_info
> +{
> + /* The associated ephemeral frame. */
> + SCM frame;
> +
> + /* The frame_id for the associated ephemeral frame. */
> + struct frame_id frame_id;
> +
> + /* A list of (REGNUM . VALUE) pairs, indicating register values for the
> + associated 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);
> +}
> +
> +/* Type predicate for unwind info. */
> +
> +static int
> +uwscm_is_unwind_info (SCM obj)
> +{
> + return SCM_SMOB_PREDICATE (unwind_info_smob_tag, obj);
> +}
> +
> +/* Data accessor for unwind_info. */
> +
> +static struct uwscm_unwind_info *
> +uwscm_unwind_info_data (SCM obj)
> +{
> + gdb_assert (uwscm_is_unwind_info (obj));
> + return (struct uwscm_unwind_info *) SCM_SMOB_DATA (obj);
> +}
> +
> +/* Build a ephemeral frame. */
> +
> +static SCM
> +uwscm_make_ephemeral_frame (struct frame_info *this_frame)
> +{
> + struct uwscm_ephemeral_frame *data;
> +
> + data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
> +
> + data->this_frame = this_frame;
> + TRY
> + {
> + data->gdbarch = get_frame_arch (this_frame);
> + }
> + CATCH (except, RETURN_MASK_ALL)
> + {
> + GDBSCM_HANDLE_GDB_EXCEPTION (except);
> + }
> + END_CATCH
> +
> + SCM_RETURN_NEWSMOB (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);
> +}
> +
> +/* Is this an unwind info whose associated ephemeral frame is valid? */
> +
> +static int
> +uwscm_is_valid_unwind_info (SCM obj)
> +{
> + return uwscm_is_unwind_info (obj)
> + && uwscm_ephemeral_frame_is_valid (uwscm_unwind_info_data (obj)->frame);
> +}
> +
> +/* 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));
Space before (.
> + 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>"));
> +}
> +
> +/* Raise a Scheme exception if OBJ is not an unwind info object whose
> + associated ephemeral frame is valid. */
> +
> +static void
> +uwscm_assert_valid_unwind_info (SCM obj, const char *func_name, int pos)
> +{
> + if (!uwscm_is_valid_unwind_info (obj))
> + gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
> + "valid <gdb:unwind-info>"));
> +}
> +
> +/* Helper to convert a frame ID component to a CORE_ADDR. */
> +
> +static CORE_ADDR
> +uwscm_value_to_addr (SCM value, int arg)
> +{
> + struct value *c_value;
> + CORE_ADDR ret;
> +
> + if (!vlscm_is_value (value))
> + gdbscm_throw (gdbscm_make_type_error ("make-unwind-info",
> + arg, value, "<gdb:value> object"));
> +
> + c_value = vlscm_scm_to_value (value);
> +
> + TRY
> + {
> + ret = value_as_address (c_value);
> + }
> + CATCH (except, RETURN_MASK_ALL)
> + {
> + GDBSCM_HANDLE_GDB_EXCEPTION (except);
> + }
> + END_CATCH
> +
> + return ret;
> +}
> +
> +/* (make-unwind-info ephemeral-frame sp [pc [special]])
> +
> + Set the frame ID on this ephemeral frame. */
> +
> +static SCM
> +gdbscm_make_unwind_info (SCM ephemeral_frame, SCM sp, SCM pc, SCM special)
> +{
> + struct uwscm_unwind_info *data;
> + struct frame_id frame_id;
> +
> + uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
> +
> + if (SCM_UNBNDP (pc))
> + 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 (pc, SCM_ARG3));
> + else
> + frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
> + uwscm_value_to_addr (pc, SCM_ARG3),
> + uwscm_value_to_addr (special, SCM_ARG4));
> +
> + data = scm_gc_malloc (sizeof (*data), unwind_info_smob_name);
> +
> + data->frame = ephemeral_frame;
> + data->frame_id = frame_id;
> + data->registers = SCM_EOL;
> +
> + SCM_RETURN_NEWSMOB (unwind_info_smob_tag, data);
> +}
> +
> +/* Convert the string REGISTER_SCM to a register number for the given
> + architecture. */
> +
> +static int
> +uwscm_scm_to_regnum (SCM register_scm, struct gdbarch *gdbarch,
> + const char *func_name, int arg)
> +{
> + int regnum;
> +
remove blank line
> + struct cleanup *cleanup;
> + char *register_str;
> +
> + gdbscm_parse_function_args (func_name, arg, NULL,
> + "s", register_scm, ®ister_str);
> + cleanup = make_cleanup (xfree, register_str);
> +
> + TRY
> + {
> + regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
> + strlen (register_str));
> + }
> + CATCH (except, RETURN_MASK_ALL)
> + {
> + do_cleanups (cleanup);
> + GDBSCM_HANDLE_GDB_EXCEPTION (except);
> + }
> + END_CATCH
> +
> + do_cleanups (cleanup);
> +
> + if (regnum < 0)
> + gdbscm_out_of_range_error (func_name, arg,
> + register_scm, _("unknown register"));
> +
> + return regnum;
> +}
> +
> +/* (ephemeral-frame-read-register <gdb:ephemeral-frame> string)
> + -> <gdb:value>
> +
> + Sniffs a register value from an ephemeral frame. */
> +
> +static SCM
> +gdbscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
> +{
> + 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, FUNC_NAME,
> + SCM_ARG2);
> +
> + TRY
> + {
> + gdb_byte buffer[MAX_REGISTER_SIZE];
buffer is unused
> +
> + value = get_frame_register_value (data->this_frame, regnum);
> + }
> + CATCH (except, RETURN_MASK_ALL)
> + {
> + GDBSCM_HANDLE_GDB_EXCEPTION (except);
> + }
> + END_CATCH
> +
> + if (value == NULL)
> + gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, register_scm,
> + _("Cannot read register from frame."));
The convention I've been following for error text here is no leading
capitalization and no trailing period.
> +
> + return vlscm_scm_from_value (value);
> +}
> +
> +/* (unwind-info-add-saved-register! unwind-info register value)
> +
> + Records the saved value of a particular register in UNWIND_INFO's
> + corresponding ephemeral frame. REGISTER_SCM names the register, as a
> + string, and VALUE_SCM is a <gdb:value>. */
> +
> +static SCM
> +gdbscm_unwind_info_add_saved_register_x (SCM unwind_info, SCM register_scm,
> + SCM value_scm)
> +{
> + struct uwscm_unwind_info *data;
> + struct uwscm_ephemeral_frame *frame_data;
> + struct value *value;
> + int regnum;
> + int value_size;
> +
> + uwscm_assert_valid_unwind_info (unwind_info, FUNC_NAME, SCM_ARG1);
> + data = uwscm_unwind_info_data (unwind_info);
> + frame_data = uwscm_ephemeral_frame_data (data->frame);
> + regnum = uwscm_scm_to_regnum (register_scm, frame_data->gdbarch,
> + FUNC_NAME, SCM_ARG2);
> +
> + if (!vlscm_is_value (value_scm))
> + gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG3,
> + value_scm,
> + "<gdb:value> object"));
> +
> + value = vlscm_scm_to_value (value_scm);
> + value_size = TYPE_LENGTH (value_enclosing_type (value));
> +
> + if (value_size != register_size (frame_data->gdbarch, regnum))
> + gdbscm_invalid_object_error ("unwind-info-add-saved-register!",
> + SCM_ARG3, value_scm,
> + "wrong sized value for register");
> +
> + data->registers = scm_assv_set_x (data->registers,
> + scm_from_int (regnum),
> + value_scm);
> +
> + return SCM_UNSPECIFIED;
> +}
> +
> +/* frame_unwind.this_id method. */
> +
> +static void
> +uwscm_this_id (struct frame_info *this_frame, void **cache_ptr,
> + struct frame_id *this_id)
> +{
> + SCM unwind_info = PTR2SCM (*cache_ptr);
> + struct uwscm_unwind_info *data;
> +
> + data = uwscm_unwind_info_data (unwind_info);
> + *this_id = data->frame_id;
> +}
> +
> +/* frame_unwind.prev_register. */
> +
> +static struct value *
> +uwscm_prev_register (struct frame_info *this_frame, void **cache_ptr,
> + int regnum)
> +{
> + SCM unwind_info = PTR2SCM (*cache_ptr);
> + struct uwscm_unwind_info *data;
> + SCM value_scm;
> + struct value *c_value;
> + const gdb_byte *buf;
> +
> + data = uwscm_unwind_info_data (unwind_info);
> + value_scm = scm_assv_ref (data->registers, scm_from_int (regnum));
> + if (gdbscm_is_false (value_scm))
> + return frame_unwind_got_optimized (this_frame, regnum);
> +
> + c_value = vlscm_scm_to_value (value_scm);
> + buf = value_contents (c_value);
> +
> + return frame_unwind_got_bytes (this_frame, regnum, buf);
> +}
> +
> +/* Call unwind-frame on the ephemeral frame and check that the return
> + value is either a valid unwind info object, for a successful unwind, or
> + #f otherwise. */
> +
> +static SCM
> +do_call_unwind_frame (void *data)
> +{
> + SCM ephemeral_frame = PTR2SCM (data);
> + SCM result;
> +
> + result = scm_call_1 (scm_variable_ref (unwind_frame), ephemeral_frame);
> +
> + if (!gdbscm_is_false (result))
> + uwscm_assert_valid_unwind_info (result, "unwind-frame", 0);
> +
> + return result;
> +}
> +
> +/* Sniffer implementation. */
> +
> +static int
> +uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
> + void **cache_ptr)
> +{
> + SCM 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;
> +
> + ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
> + /* Recurse through gdbscm_call_guile so that we can just throw
> + exceptions on error. */
> + result = gdbscm_call_guile (do_call_unwind_frame,
> + SCM2PTR (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);
> +
> + if (gdbscm_is_exception (result))
> + {
> + gdbscm_print_gdb_exception (SCM_BOOL_F, result);
> + return 0;
> + }
> +
> + if (gdbscm_is_false (result))
> + return 0;
> +
> + *cache_ptr = SCM2PTR (scm_gc_protect_object (result));
> +
> + return 1;
> +}
> +
> +/* Frame cache release shim. */
> +
> +static void
> +uwscm_dealloc_cache (struct frame_info *this_frame, void *cache)
> +{
> + scm_gc_unprotect_object (PTR2SCM (cache));
> +}
> +
> +struct uwscm_gdbarch_data_type
> +{
> + /* Has the unwinder shim been prepended? */
> + int unwinder_registered;
> +};
> +
> +static void *
> +uwscm_gdbarch_data_init (struct gdbarch *gdbarch)
> +{
> + return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct uwscm_gdbarch_data_type);
> +}
> +
> +/* New inferior architecture callback: register the Guile sniffers
> + intermediary. */
> +
> +static void
> +uwscm_on_new_gdbarch (struct gdbarch *newarch)
> +{
> + struct uwscm_gdbarch_data_type *data =
> + gdbarch_data (newarch, uwscm_gdbarch_data);
> +
> + if (!data->unwinder_registered)
> + {
> + struct frame_unwind *unwinder
> + = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
> +
> + unwinder->type = NORMAL_FRAME;
> + unwinder->stop_reason = default_frame_unwind_stop_reason;
> + unwinder->this_id = uwscm_this_id;
> + unwinder->prev_register = uwscm_prev_register;
> + unwinder->unwind_data = (void *) newarch;
> + unwinder->sniffer = uwscm_sniffer;
> + unwinder->dealloc_cache = uwscm_dealloc_cache;
> + frame_unwind_prepend_unwinder (newarch, unwinder);
> + data->unwinder_registered = 1;
> + }
> +}
> +
> +static const scheme_function unwind_functions[] =
> +{
> + { "make-unwind-info", 2, 2, 0, gdbscm_make_unwind_info,
> + "\
> +Make an unwind info object for an ephemeral 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\
> +A frame identifier is a unique name for a frame that remains valid as\n\
> +long as the frame itself is valid. Usually the frame identifier is\n\
> +built from from the frame's stack address and code address. The stack\n\
> +address, passed as the second argument, should normally be a pointer to\n\
> +the new end of the stack when the function was called, as a GDB value.\n\
> +Similarly the code address, the third argument, should be given as the\n\
> +address of the entry point of the 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,
> + gdbscm_ephemeral_frame_read_register,
> + "\
> +Return the value of a register in an ephemeral frame.\n\
> +\n\
> + Arguments: <gdb:ephemeral-frame> string" },
> +
> + { "unwind-info-add-saved-register!", 3, 0, 0,
> + gdbscm_unwind_info_add_saved_register_x,
> + "\
> +Record the saved value of a register for an ephemeral frame.\n\
> +\n\
> +After reading an ephemeral frame's registers and determining that it\n\
> +can handle the frame, an unwinder will create an unwind info object and\n\
> +call this function to record saved registers for the frame. The values\n\
> +of the saved registers logically belong to the frame that is older than\n\
> +the ephemeral frame being unwound, not to the ephemeral frame itself.\n\
> +\n\
> +The first argument should be a <gdb:unwind-info> object. The second\n\
> +names a register, and should be a string, for example \"rip\". The\n\
> +third argument is the value, as a GDB value. By default, any register\n\
> +whose value is not added to the unwind info's saved register set\n\
> +will be marked as \"not saved\" in the outer frame." },
> +
> + END_FUNCTIONS
> +};
> +
> +/* Called by lib/gdb/frame-unwinders.scm. */
> +
> +static void
> +gdbscm_load_frame_unwinders (void *unused)
> +{
> + if (gdbscm_frame_unwinders_loaded)
> + return;
> +
> + gdbscm_frame_unwinders_loaded = 1;
> +
> + gdbscm_define_functions (unwind_functions, 0);
> +
> + unwind_frame = scm_c_lookup ("unwind-frame");
> +}
> +
> +/* Initialize the opaque ephemeral frame and unwind info types 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);
> + unwind_info_smob_tag =
> + gdbscm_make_smob_type (unwind_info_smob_name, 0);
> +
> + uwscm_gdbarch_data =
> + gdbarch_data_register_post_init (uwscm_gdbarch_data_init);
> + observer_attach_architecture_changed (uwscm_on_new_gdbarch);
> +
> + scm_c_register_extension ("gdb", "gdbscm_load_frame_unwinders",
> + gdbscm_load_frame_unwinders, NULL);
> +}
> diff --git a/gdb/guile/scm-symbol.c b/gdb/guile/scm-symbol.c
> index 99ef928..1ad6d0c 100644
> --- a/gdb/guile/scm-symbol.c
> +++ b/gdb/guile/scm-symbol.c
> @@ -605,7 +605,9 @@ gdbscm_lookup_symbol (SCM name_scm, SCM rest)
>
> TRY
> {
> - selected_frame = get_selected_frame (_("no frame selected"));
> + selected_frame = get_selected_frame_if_set ();
> + if (selected_frame == NULL)
> + selected_frame = get_current_frame ();
> block = get_frame_block (selected_frame, NULL);
> }
> CATCH (except, RETURN_MASK_ALL)
> diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
> index 9e94ddf..52a60f8 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.
Convention is to write "New file." for each new file.
> +
> 2015-03-05 Andy Wingo <wingo@igalia.com>
>
> * gdb.guile/amd64-scm-frame-filter-invalidarg.S:
> 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..28f5936
> --- /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)
> + (register-frame-unwinder! (make-frame-unwinder name unwind-frame
> + #:priority priority)
> + #:scope 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 @@
The rule is all test files get a copyright header,
regardless of how trivial the file is.
> +int f2 (int a)
And must follow gdb coding conventions (unless the test explicitly
needs to do something differently).
->
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..e22a8d9
> --- /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"
IIRC use of perror if runto_main fails is deprecated,
use fail instead.
I didn't see anything in testsuite/README or
https://sourceware.org/gdb/wiki/Internals%20GDB-Testsuite-Coding-Standards
We should get this written down.
> + 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.*" \
Trailing .* is to be avoided if possible.
Can it be avoided here, and below?
> + "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 .*cabba9e5 .*cabba9e5 .*"
IWBN to test that unwinding handles unwinder enabling.
E.g., try a backtrace with and without an unwinder enabled.
This brings up an issue I haven't seen discussed before.
Apologies if I missed it.
Should enabling/disabling/registering/removing an unwinder
invalidate the frame cache? Seems like it.
> 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..e299d49
> --- /dev/null
> +++ b/gdb/testsuite/gdb.guile/scm-frame-unwinder.scm
> @@ -0,0 +1,42 @@
> +;; Copyright (C) 2015 Free Software Foundation, Inc.
> +;;
> +;; This program is free software; you can redistribute it and/or modify
> +;; it under the terms of the GNU General Public License as published by
> +;; the Free Software Foundation; either version 3 of the License, or
> +;; (at your option) any later version.
> +;;
> +;; This program is distributed in the hope that it will be useful,
> +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
> +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +;; GNU General Public License for more details.
> +;;
> +;; You should have received a copy of the GNU General Public License
> +;; along with this program. If not, see <http://www.gnu.org/licenses/>.
> +
> +;; This file is part of the GDB test-suite. It tests Guile-based frame
> +;; filters.
> +
> +(use-modules (gdb)
> + (gdb frame-unwinders))
> +
> +(define (dummy-unwinder frame)
> + #f)
> +
> +(register-frame-unwinder!
> + (make-frame-unwinder "Dummy" dummy-unwinder #: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"))
rip,rsp are architecture-specific names but the .exp file
doesn't check for amd64. IWBN if the test wasn't architecture-specific.
Will "pc" and "sp" work here?
> + (prev-pc (value-cast (make-value #xcabba9e5) (value-type this-pc)))
> + (prev-sp (value-add this-sp 32))
> + (unwind-info (make-unwind-info frame this-sp this-pc)))
> + (unwind-info-add-saved-register! unwind-info "rip" prev-pc)
> + (unwind-info-add-saved-register! unwind-info "rsp" prev-sp)
> + unwind-info))
> +
> +(register-frame-unwinder!
> + (make-frame-unwinder "Synthetic" synthetic-unwinder #:priority 150
> + #:enabled? #f))
> --
> 2.1.4
>
Hi!
I have a new version of this patch that I'll post as a separate thread,
addressing all the issues except a couple that I comment below.
On Tue 24 Mar 2015 23:07, Doug Evans <dje@google.com> writes:
> > +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
> > +Set the saved value of a register in a ephemeral frame.
> > +
> > +@var{register} names a register, and should be a string, for example
> > +@samp{rip}. @var{value} is the register value, as a @value{GDBN}
>
> To be more universal let's replace "rip" here with "pc".
I kept in "rip" because user regs like "pc" don't work on pending
frames, and I didn't want to mislead the reader that user regs would
work. (OK, "pc" is probably a user reg only on some architectures.)
WDYT? Still change to "pc"?
> > +Any register whose value is not recorded as saved via
> > +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
> > +in the outer frame.
>
> "outer frame" here is confusing.
> I'd have thought this would read "``not saved'' in the frame".
> [with the frame?]
Let's say you are unwinding pending frame 1, which will be frame-1. You
add some saved registers to it. Those registers will become the values
of (frame-read-register (frame-older frame-1) "foo"). If a register
isn't added to the set, then the frame-read-register on the older frame
will return "not saved". Is that clear? It's a bit confusing but I
think it reflects the problem space...
> > +(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
>
> In addition to removing priority (for now), let's remove enabled? as
> a parameter here. The user can disable the unwinder before registering it.
I kept it in, as noted in the patch set header of the next mail, because
that's more like frame filters which indeed have a priority and also
have an enabled flag. Please let me know again if this bugs you and
I'll take them both out.
> > +(define* (register-frame-unwinder! unwinder #:key scope)
>
> Guile question: Does scope default to #f?
Yep.
> > +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 .*cabba9e5 .*cabba9e5 .*"
>
> IWBN to test that unwinding handles unwinder enabling.
> E.g., try a backtrace with and without an unwinder enabled.
>
> This brings up an issue I haven't seen discussed before.
> Apologies if I missed it.
> Should enabling/disabling/registering/removing an unwinder
> invalidate the frame cache? Seems like it.
I can't do it here for the reason you mention. I have added something
that does a bt from before the enable!, and also doesn't have a trailing
.*. A followup perhaps?
> > +(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"))
>
> rip,rsp are architecture-specific names but the .exp file
> doesn't check for amd64. IWBN if the test wasn't architecture-specific.
> Will "pc" and "sp" work here?
They won't currently. Should I look into making user regs work or
somehow mark this test as architecture-specific?
Thanks for your time, Doug!
Andy
Andy Wingo <wingo@igalia.com> writes:
> Hi!
Hi.
When we last left our story ...
> I have a new version of this patch that I'll post as a separate thread,
> addressing all the issues except a couple that I comment below.
>
> On Tue 24 Mar 2015 23:07, Doug Evans <dje@google.com> writes:
>
>> > +@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
>> > +Set the saved value of a register in a ephemeral frame.
>> > +
>> > +@var{register} names a register, and should be a string, for example
>> > +@samp{rip}. @var{value} is the register value, as a @value{GDBN}
>>
>> To be more universal let's replace "rip" here with "pc".
>
> I kept in "rip" because user regs like "pc" don't work on pending
> frames, and I didn't want to mislead the reader that user regs would
> work. (OK, "pc" is probably a user reg only on some architectures.)
> WDYT? Still change to "pc"?
"rip" is amd64-specific, and we don't really distinguish "rip" from "pc".
They're different, technically (one's a "real" register, ref: amd64-tdep.c,
and the other is a "user" reg - ref: user-regs.c), but if they're not
equivalent I think we've got problems, and not just in the unwinders.
I guess I need to understand better why "rip" works whereas "pc" doesn't work.
>> > +Any register whose value is not recorded as saved via
>> > +@code{unwind-info-add-saved-register!} will be marked as ``not saved''
>> > +in the outer frame.
>>
>> "outer frame" here is confusing.
>> I'd have thought this would read "``not saved'' in the frame".
>> [with the frame?]
>
> Let's say you are unwinding pending frame 1, which will be frame-1. You
> add some saved registers to it. Those registers will become the values
> of (frame-read-register (frame-older frame-1) "foo"). If a register
> isn't added to the set, then the frame-read-register on the older frame
> will return "not saved". Is that clear? It's a bit confusing but I
> think it reflects the problem space...
It's the "outer" that's confusing, not the function's purpose.
I often come into a manual at the point that describes
the function I'm interested in. If I read the introductory text I can
infer that "outer" here equates to the pending frame we are building,
but when I imagine reading this text "standalone" (so to speak) I wonder
if the reader might think "outer to the pending frame being constructed?".
I dunno.
I think this would be clearer:
"... will be marked as ``not saved'' in the pending frame being unwound."
Or one could say both:
"... will be marked as ``not saved'' in the outer frame (the pending
frame being unwound)."
Or some such.
>> > +(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
>>
>> In addition to removing priority (for now), let's remove enabled? as
>> a parameter here. The user can disable the unwinder before registering it.
>
> I kept it in, as noted in the patch set header of the next mail, because
> that's more like frame filters which indeed have a priority and also
> have an enabled flag. Please let me know again if this bugs you and
> I'll take them both out.
But there isn't a real reason for having it there either.
But whatever, this isn't important enough.
Re: priority: Ok.
>> > +(define* (register-frame-unwinder! unwinder #:key scope)
>>
>> Guile question: Does scope default to #f?
>
> Yep.
>
>> > +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 .*cabba9e5 .*cabba9e5 .*"
>>
>> IWBN to test that unwinding handles unwinder enabling.
>> E.g., try a backtrace with and without an unwinder enabled.
>>
>> This brings up an issue I haven't seen discussed before.
>> Apologies if I missed it.
>> Should enabling/disabling/registering/removing an unwinder
>> invalidate the frame cache? Seems like it.
>
> I can't do it here for the reason you mention. I have added something
> that does a bt from before the enable!, and also doesn't have a trailing
> .*. A followup perhaps?
I guess I'd have to see what happens if I do a backtrace with an
unwinder enabled, then disable it, and then do another backtrace
and see if the result of the second backtrace is identical to a
backtrace where the unwinder was originally disabled.
If your patch has a test for this, great.
And if the test works without invalidating the frame cache
I'd be curious to know why.
[Easy enough to check on my own of course, and will if I get time.]
>> > +(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"))
>>
>> rip,rsp are architecture-specific names but the .exp file
>> doesn't check for amd64. IWBN if the test wasn't architecture-specific.
>> Will "pc" and "sp" work here?
>
> They won't currently. Should I look into making user regs work or
> somehow mark this test as architecture-specific?
While fetching "pc" does take a different code path than "rip", it
does end up getting mapped to "rip". Plus if they're not equivalent
using this feature across different architectures
is going to be a pain. Users generally just always type "pc" without
having to care what the spelling is of the real h/w register.
Why doesn't pc,sp work here?
I can imagine it not working on arches that don't set the pc,sp regnums,
but that's just a guess.
@@ -1,5 +1,34 @@
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-03-05 Andy Wingo <wingo@igalia.com>
+
* guile/scm-frame-filter.c:
* guile/lib/gdb/frame-filters.scm: New files.
* guile/guile.c (guile_extension_ops): Add the Guile frame
@@ -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 \
@@ -2416,6 +2418,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)
@@ -88,6 +88,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 \
@@ -100,6 +101,7 @@ GUILE_NO_UNBOUND_WARNING_COMPILED_FILES = \
GUILE_COMPILED_FILES = \
gdb/experimental.go \
gdb/frame-filters.go \
+ gdb/frame-unwinders.go \
gdb/iterator.go \
gdb/printing.go \
gdb/support.go \
@@ -1,3 +1,7 @@
+2015-03-06 Andy Wingo <wingo@igalia.com>
+
+ * guile.texi (Guile Frame Unwinder API): New section.
+
2015-02-15 Andy Wingo <wingo@igalia.com>
* guile.texi (Guile Frame Filter API)
@@ -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
@@ -2137,6 +2138,210 @@ also possible to do the job of an decorator with a filter. Still,
avoiding the stream interfaces can often be a good reason to use the
simpler decorator layer.
+@node Guile Frame Unwinder API
+@subsubsection Unwinding Frames in Guile
+@cindex frame unwinder api, guile
+
+In @value{GDBN} terminology, ``unwinding'' is the process of finding
+an older (outer) frame on the stack. Unwinders form the core of
+backtrace computation in @value{GDBN}. @value{GDBN} comes with
+unwinders for each target architecture that it supports, and these
+usually suffice to unwind the stack. However, some target programs
+can have non-standard frame layouts that cannot be unwound by the
+standard unwinders. This is often the case when working with
+just-in-time compilation environments, for example in JavaScript
+implementations. In such cases, users can define custom code in Guile
+to programmatically unwind the problematic stack frames.
+
+Before getting into the API, we should discuss how unwinders work in
+@value{GDBN}.
+
+As an example, consider a stack in which we have already computed
+frame 0 and we want to compute frame 1. We say that frame 0 is the
+``inner'' frame, and frame 1 will be the ``outer'' frame.
+
+Unwinding starts with a model of the state of all registers in an
+inner, already unwound frame. In our case, we start with frame 0.
+@value{GDBN} then constructs a 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
+creates and returns an ``unwind info'' object for that frame. The
+unwind info object contains a frame ID for 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{]}
+Make a new frame unwinder.
+
+The unwinder will be identified by the string @var{name}.
+@var{procedure} should be a function of one argument, taking an
+ephemeral frame object. If the unwinder procedure decides to handle
+the frame, it should return an unwind info object. Otherwise the
+unwinder returns @code{#f}, @value{GDBN} will continue to search its
+list for an unwinder.
+
+The unwinder will be initially enabled, unless the keyword argument
+@code{#:enabled? #f} is given. Even if the unwinder is marked as
+enabled, it will need to be registered with @value{GDBN} via
+@code{register-frame-unwinder!} in order to take effect. When
+registered, 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} register-frame-unwinder! unwinder @
+ @r{[}#:scope scope@r{]}
+Register the frame unwinder @var{unwinder} with @value{GDBN}.
+
+By default, the scope of the unwinder is global, meaning that it is
+associated with all objfiles and progspaces. Pass an objfile or a
+progspace as the @code{#:scope} keyword argument to instead scope the
+unwinder into a specific objfile or progspace, respectively.
+
+The unwinder's name will be checked for uniqueness within its registered
+scope.
+@end deffn
+
+@deffn {Scheme Procedure} set-frame-unwinder-enabled! unwinder enabled?
+@deffnx {Scheme Procedure} enable-frame-unwinder! unwinder
+@deffnx {Scheme Procedure} disable-frame-unwinder! unwinder
+Mark a frame unwinder as enabled, if @var{enabled?} is true, or
+as disabled otherwise.
+
+@var{unwinder} can either be a frame unwinder object, or it can be a
+string naming an unwinder in the current scope. If no such unwinder
+is found, an error is signalled.
+
+@code{enable-frame-unwinder!} and @code{disable-frame-unwinder} are simple
+wrappers around @code{set-frame-unwinder-enabled!} which pass @code{#t}
+or @code{#f} as the @var{enabled?} argument, respectively.
+@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. Currently ephemeral frames can only be used in two limited
+ways: to read registers from the frame, and to construct an unwind
+info object for a successful unwinder return.
+
+@deffn {Scheme Procedure} 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
+
+If an unwinder successfully unwinds a frame, it should return an
+unwind info object created via the @code{make-unwind-info} procedure.
+An unwind info object specifies the frame ID for its associated
+ephemeral frame, and also records values of registers that are saved
+within the frame.
+
+@deffn {Scheme Procedure} make-unwind-info frame fp [pc [special]]
+Make an unwind info object for the ephemeral frame @var{frame}.
+
+@var{fp}, @var{pc}, and @var{special} are used to build an identifier
+(frame ID) for the frame. 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
+
+After building an unwind info object for an ephemeral frame, an
+unwinder can call @code{unwind-info-add-saved-register!} to record
+saved registers. The values of the saved registers logically belong
+to the frame that is older than the ephemeral frame being unwound, not
+to the ephemeral frame itself.
+
+@deffn {Scheme Procedure} unwind-info-add-saved-register! unwind-info register value
+Set the saved value of a register in a ephemeral frame.
+
+@var{register} names a register, and should be a string, for example
+@samp{rip}. @var{value} is the register value, as a @value{GDBN}
+value.
+@end deffn
+
+Any register whose value is not recorded as saved via
+@code{unwind-info-add-saved-register!} will be marked as ``not saved''
+in the outer frame.
+
@node Commands In Guile
@subsubsection Commands In Guile
@@ -87,6 +87,34 @@ frame_unwind_append_unwinder (struct gdbarch *gdbarch,
(*ip)->unwinder = unwinder;
}
+/* Nonzero if we are finding the unwinder for a frame; see
+ frame_unwind_try_handler. */
+static int is_unwinding = 0;
+
+/* Return nonzero if we are inside a sniffer call. */
+
+int
+frame_unwind_is_unwinding (void)
+{
+ return is_unwinding;
+}
+
+/* Cleanup helpers for is_unwinding. */
+
+static void
+unset_is_unwinding (void *unused)
+{
+ is_unwinding = 0;
+}
+
+static struct cleanup*
+set_is_unwinding (void)
+{
+ is_unwinding = 1;
+
+ return make_cleanup (unset_is_unwinding, NULL);
+}
+
/* Call SNIFFER from UNWINDER. If it succeeded set UNWINDER for
THIS_FRAME and return 1. Otherwise the function keeps THIS_FRAME
unchanged and returns 0. */
@@ -98,11 +126,18 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache,
struct cleanup *old_cleanup;
int res = 0;
+ if (is_unwinding)
+ internal_error (__FILE__, __LINE__,
+ _("Recursion detected while finding an unwinder."));
old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder);
TRY
{
+ struct cleanup *cleanup = set_is_unwinding ();
+
res = unwinder->sniffer (unwinder, this_frame, this_cache);
+
+ do_cleanups (cleanup);
}
CATCH (ex, RETURN_MASK_ERROR)
{
@@ -252,7 +287,8 @@ frame_unwind_got_constant (struct frame_info *frame, int regnum,
}
struct value *
-frame_unwind_got_bytes (struct frame_info *frame, int regnum, gdb_byte *buf)
+frame_unwind_got_bytes (struct frame_info *frame, int regnum,
+ const gdb_byte *buf)
{
struct gdbarch *gdbarch = frame_unwind_arch (frame);
struct value *reg_val;
@@ -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
@@ -2212,6 +2212,22 @@ get_prev_frame (struct frame_info *this_frame)
return NULL;
}
+ /* Unwinders implemented in Python or Scheme could eventually make an API call
+ that would cause GDB to try to unwind a frame while unwinding a frame.
+ Because already-unwound frames will be found in the frame cache, unwinding
+ will only happen at the old end of the stack, which means that any
+ recursive unwinding attempt will surely lead to unbounded recursion. Ways
+ this can happen include such common functions as `get_current_arch' or
+ `lookup_symbol', via `get_selected_frame', so it's impractical to simply
+ declare these an error. Instead, we detect this case and return NULL,
+ indicating that the known stack of frames ends here. */
+ if (frame_unwind_is_unwinding ())
+ {
+ frame_debug_got_null_frame (this_frame,
+ "get_prev_frame within unwinder sniffer");
+ return NULL;
+ }
+
return get_prev_frame_always (this_frame);
}
@@ -598,6 +598,7 @@ extern void gdbscm_initialize_disasm (void);
extern void gdbscm_initialize_exceptions (void);
extern void gdbscm_initialize_frames (void);
extern void gdbscm_initialize_frame_filters (void);
+extern void gdbscm_initialize_frame_unwinders (void);
extern void gdbscm_initialize_iterators (void);
extern void gdbscm_initialize_lazy_strings (void);
extern void gdbscm_initialize_math (void);
@@ -670,6 +670,7 @@ initialize_gdb_module (void *data)
gdbscm_initialize_disasm ();
gdbscm_initialize_frames ();
gdbscm_initialize_frame_filters ();
+ gdbscm_initialize_frame_unwinders ();
gdbscm_initialize_iterators ();
gdbscm_initialize_lazy_strings ();
gdbscm_initialize_math ();
new file mode 100644
@@ -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 (ephemeral-frame-read-register
+
+ make-unwind-info
+ unwind-info-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
+
+ register-frame-unwinder!
+ unregister-frame-unwinder!
+ set-frame-unwinder-enabled!
+ 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 -> unwind-info | #f
+ (procedure frame-unwinder-procedure)
+ ;; objfile | progspace | #f
+ (scope frame-unwinder-scope set-scope!))
+
+(define* (make-frame-unwinder name procedure #:key (priority 20) (enabled? #t))
+ "Make and return a new frame unwinder. NAME and PROCEDURE are
+required arguments. Specify #:priority or #:enabled? to set the
+priority and enabled status of the unwinder.
+
+The unwinder must be registered with GDB via `register-frame-unwinder!'
+before it is active."
+ (let ((registered? #f) (scope #f))
+ (%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* (register-frame-unwinder! unwinder #:key scope)
+ "Register a frame unwinder with GDB. Frame unwinders must be
+registered before they will be used to unwind backtraces.
+
+By default, the unwinder will be registered globally. Specify an
+objfile or a progspace as the #:scope keyword argument to limit the
+unwinder to a specific scope."
+ (define (check-scope!)
+ (cond
+ ((objfile? scope)
+ (unless (objfile-valid? scope)
+ (error "Objfile is not valid" scope)))
+ ((progspace? scope)
+ (unless (progspace-valid? scope)
+ (error "Progspace is not valid" scope)))
+ (scope
+ (error "Invalid scope" scope))))
+ (define (duplicate-unwinder? other)
+ (and (equal? (frame-unwinder-name other)
+ (frame-unwinder-name unwinder))
+ (same-scope? (frame-unwinder-scope other) scope)))
+ (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*)))))))
+
+ (check-scope!)
+ (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-scope! unwinder scope)
+ (set! *frame-unwinders*
+ (insert-sorted unwinder *frame-unwinders* priority>=?)))
+
+(define (unregister-frame-unwinder! unwinder)
+ "Unregister a frame unwinder."
+ (set-registered?! unwinder #f)
+ (set-scope! 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 (set-frame-unwinder-enabled! unwinder enabled?)
+ "Enable or disable a frame unwinder."
+ (let ((unwinder (if (frame-unwinder? unwinder)
+ unwinder
+ (find-frame-unwinder-by-name unwinder))))
+ (set-enabled?! unwinder enabled?)
+ *unspecified*))
+
+(define (enable-frame-unwinder! unwinder)
+ "Mark a frame unwinder as enabled."
+ (set-frame-unwinder-enabled! unwinder #t))
+
+(define (disable-frame-unwinder! unwinder)
+ "Mark a frame unwinder as disabled."
+ (set-frame-unwinder-enabled! unwinder #f))
+
+(define (unwind-frame frame)
+ (let ((scope (current-progspace)))
+ (or-map (lambda (unwinder)
+ (and (frame-unwinder-enabled? unwinder)
+ (same-scope? (frame-unwinder-scope unwinder) scope)
+ ((frame-unwinder-procedure unwinder) frame)))
+ *frame-unwinders*)))
+
+(load-extension "gdb" "gdbscm_load_frame_unwinders")
new file mode 100644
@@ -0,0 +1,593 @@
+/* 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 makes ephemeral frame objects when trying
+ to unwind frames, and unwind info objects when unwinding is successful.
+ Here we define the names for the ephemeral frame and unwind info Scheme
+ data types. */
+static const char ephemeral_frame_smob_name[] = "gdb:ephemeral-frame";
+static const char unwind_info_smob_name[] = "gdb:unwind-info";
+
+/* SMOB tag for ephemeral frames and unwind info. */
+static scm_t_bits ephemeral_frame_smob_tag;
+static scm_t_bits unwind_info_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;
+};
+
+/* Data associated with an unwind info object. */
+struct uwscm_unwind_info
+{
+ /* The associated ephemeral frame. */
+ SCM frame;
+
+ /* The frame_id for the associated ephemeral frame. */
+ struct frame_id frame_id;
+
+ /* A list of (REGNUM . VALUE) pairs, indicating register values for the
+ associated 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);
+}
+
+/* Type predicate for unwind info. */
+
+static int
+uwscm_is_unwind_info (SCM obj)
+{
+ return SCM_SMOB_PREDICATE (unwind_info_smob_tag, obj);
+}
+
+/* Data accessor for unwind_info. */
+
+static struct uwscm_unwind_info *
+uwscm_unwind_info_data (SCM obj)
+{
+ gdb_assert (uwscm_is_unwind_info (obj));
+ return (struct uwscm_unwind_info *) SCM_SMOB_DATA (obj);
+}
+
+/* Build a ephemeral frame. */
+
+static SCM
+uwscm_make_ephemeral_frame (struct frame_info *this_frame)
+{
+ struct uwscm_ephemeral_frame *data;
+
+ data = scm_gc_malloc (sizeof (*data), ephemeral_frame_smob_name);
+
+ data->this_frame = this_frame;
+ TRY
+ {
+ data->gdbarch = get_frame_arch (this_frame);
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ GDBSCM_HANDLE_GDB_EXCEPTION (except);
+ }
+ END_CATCH
+
+ SCM_RETURN_NEWSMOB (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);
+}
+
+/* Is this an unwind info whose associated ephemeral frame is valid? */
+
+static int
+uwscm_is_valid_unwind_info (SCM obj)
+{
+ return uwscm_is_unwind_info (obj)
+ && uwscm_ephemeral_frame_is_valid (uwscm_unwind_info_data (obj)->frame);
+}
+
+/* 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>"));
+}
+
+/* Raise a Scheme exception if OBJ is not an unwind info object whose
+ associated ephemeral frame is valid. */
+
+static void
+uwscm_assert_valid_unwind_info (SCM obj, const char *func_name, int pos)
+{
+ if (!uwscm_is_valid_unwind_info (obj))
+ gdbscm_throw (gdbscm_make_type_error (func_name, pos, obj,
+ "valid <gdb:unwind-info>"));
+}
+
+/* Helper to convert a frame ID component to a CORE_ADDR. */
+
+static CORE_ADDR
+uwscm_value_to_addr (SCM value, int arg)
+{
+ struct value *c_value;
+ CORE_ADDR ret;
+
+ if (!vlscm_is_value (value))
+ gdbscm_throw (gdbscm_make_type_error ("make-unwind-info",
+ arg, value, "<gdb:value> object"));
+
+ c_value = vlscm_scm_to_value (value);
+
+ TRY
+ {
+ ret = value_as_address (c_value);
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ GDBSCM_HANDLE_GDB_EXCEPTION (except);
+ }
+ END_CATCH
+
+ return ret;
+}
+
+/* (make-unwind-info ephemeral-frame sp [pc [special]])
+
+ Set the frame ID on this ephemeral frame. */
+
+static SCM
+gdbscm_make_unwind_info (SCM ephemeral_frame, SCM sp, SCM pc, SCM special)
+{
+ struct uwscm_unwind_info *data;
+ struct frame_id frame_id;
+
+ uwscm_assert_valid_ephemeral_frame (ephemeral_frame, FUNC_NAME, SCM_ARG1);
+
+ if (SCM_UNBNDP (pc))
+ 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 (pc, SCM_ARG3));
+ else
+ frame_id = frame_id_build_special (uwscm_value_to_addr (sp, SCM_ARG2),
+ uwscm_value_to_addr (pc, SCM_ARG3),
+ uwscm_value_to_addr (special, SCM_ARG4));
+
+ data = scm_gc_malloc (sizeof (*data), unwind_info_smob_name);
+
+ data->frame = ephemeral_frame;
+ data->frame_id = frame_id;
+ data->registers = SCM_EOL;
+
+ SCM_RETURN_NEWSMOB (unwind_info_smob_tag, data);
+}
+
+/* Convert the string REGISTER_SCM to a register number for the given
+ architecture. */
+
+static int
+uwscm_scm_to_regnum (SCM register_scm, struct gdbarch *gdbarch,
+ const char *func_name, int arg)
+{
+ int regnum;
+
+ struct cleanup *cleanup;
+ char *register_str;
+
+ gdbscm_parse_function_args (func_name, arg, NULL,
+ "s", register_scm, ®ister_str);
+ cleanup = make_cleanup (xfree, register_str);
+
+ TRY
+ {
+ regnum = user_reg_map_name_to_regnum (gdbarch, register_str,
+ strlen (register_str));
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ do_cleanups (cleanup);
+ GDBSCM_HANDLE_GDB_EXCEPTION (except);
+ }
+ END_CATCH
+
+ do_cleanups (cleanup);
+
+ if (regnum < 0)
+ gdbscm_out_of_range_error (func_name, arg,
+ register_scm, _("unknown register"));
+
+ return regnum;
+}
+
+/* (ephemeral-frame-read-register <gdb:ephemeral-frame> string)
+ -> <gdb:value>
+
+ Sniffs a register value from an ephemeral frame. */
+
+static SCM
+gdbscm_ephemeral_frame_read_register (SCM ephemeral_frame, SCM register_scm)
+{
+ 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, FUNC_NAME,
+ SCM_ARG2);
+
+ TRY
+ {
+ gdb_byte buffer[MAX_REGISTER_SIZE];
+
+ value = get_frame_register_value (data->this_frame, regnum);
+ }
+ CATCH (except, RETURN_MASK_ALL)
+ {
+ GDBSCM_HANDLE_GDB_EXCEPTION (except);
+ }
+ END_CATCH
+
+ if (value == NULL)
+ gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, register_scm,
+ _("Cannot read register from frame."));
+
+ return vlscm_scm_from_value (value);
+}
+
+/* (unwind-info-add-saved-register! unwind-info register value)
+
+ Records the saved value of a particular register in UNWIND_INFO's
+ corresponding ephemeral frame. REGISTER_SCM names the register, as a
+ string, and VALUE_SCM is a <gdb:value>. */
+
+static SCM
+gdbscm_unwind_info_add_saved_register_x (SCM unwind_info, SCM register_scm,
+ SCM value_scm)
+{
+ struct uwscm_unwind_info *data;
+ struct uwscm_ephemeral_frame *frame_data;
+ struct value *value;
+ int regnum;
+ int value_size;
+
+ uwscm_assert_valid_unwind_info (unwind_info, FUNC_NAME, SCM_ARG1);
+ data = uwscm_unwind_info_data (unwind_info);
+ frame_data = uwscm_ephemeral_frame_data (data->frame);
+ regnum = uwscm_scm_to_regnum (register_scm, frame_data->gdbarch,
+ FUNC_NAME, SCM_ARG2);
+
+ if (!vlscm_is_value (value_scm))
+ gdbscm_throw (gdbscm_make_type_error (FUNC_NAME, SCM_ARG3,
+ value_scm,
+ "<gdb:value> object"));
+
+ value = vlscm_scm_to_value (value_scm);
+ value_size = TYPE_LENGTH (value_enclosing_type (value));
+
+ if (value_size != register_size (frame_data->gdbarch, regnum))
+ gdbscm_invalid_object_error ("unwind-info-add-saved-register!",
+ SCM_ARG3, value_scm,
+ "wrong sized value for register");
+
+ data->registers = scm_assv_set_x (data->registers,
+ scm_from_int (regnum),
+ value_scm);
+
+ return SCM_UNSPECIFIED;
+}
+
+/* frame_unwind.this_id method. */
+
+static void
+uwscm_this_id (struct frame_info *this_frame, void **cache_ptr,
+ struct frame_id *this_id)
+{
+ SCM unwind_info = PTR2SCM (*cache_ptr);
+ struct uwscm_unwind_info *data;
+
+ data = uwscm_unwind_info_data (unwind_info);
+ *this_id = data->frame_id;
+}
+
+/* frame_unwind.prev_register. */
+
+static struct value *
+uwscm_prev_register (struct frame_info *this_frame, void **cache_ptr,
+ int regnum)
+{
+ SCM unwind_info = PTR2SCM (*cache_ptr);
+ struct uwscm_unwind_info *data;
+ SCM value_scm;
+ struct value *c_value;
+ const gdb_byte *buf;
+
+ data = uwscm_unwind_info_data (unwind_info);
+ value_scm = scm_assv_ref (data->registers, scm_from_int (regnum));
+ if (gdbscm_is_false (value_scm))
+ return frame_unwind_got_optimized (this_frame, regnum);
+
+ c_value = vlscm_scm_to_value (value_scm);
+ buf = value_contents (c_value);
+
+ return frame_unwind_got_bytes (this_frame, regnum, buf);
+}
+
+/* Call unwind-frame on the ephemeral frame and check that the return
+ value is either a valid unwind info object, for a successful unwind, or
+ #f otherwise. */
+
+static SCM
+do_call_unwind_frame (void *data)
+{
+ SCM ephemeral_frame = PTR2SCM (data);
+ SCM result;
+
+ result = scm_call_1 (scm_variable_ref (unwind_frame), ephemeral_frame);
+
+ if (!gdbscm_is_false (result))
+ uwscm_assert_valid_unwind_info (result, "unwind-frame", 0);
+
+ return result;
+}
+
+/* Sniffer implementation. */
+
+static int
+uwscm_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+ void **cache_ptr)
+{
+ SCM 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;
+
+ ephemeral_frame = uwscm_make_ephemeral_frame (this_frame);
+ /* Recurse through gdbscm_call_guile so that we can just throw
+ exceptions on error. */
+ result = gdbscm_call_guile (do_call_unwind_frame,
+ SCM2PTR (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);
+
+ if (gdbscm_is_exception (result))
+ {
+ gdbscm_print_gdb_exception (SCM_BOOL_F, result);
+ return 0;
+ }
+
+ if (gdbscm_is_false (result))
+ return 0;
+
+ *cache_ptr = SCM2PTR (scm_gc_protect_object (result));
+
+ return 1;
+}
+
+/* Frame cache release shim. */
+
+static void
+uwscm_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+ scm_gc_unprotect_object (PTR2SCM (cache));
+}
+
+struct uwscm_gdbarch_data_type
+{
+ /* Has the unwinder shim been prepended? */
+ int unwinder_registered;
+};
+
+static void *
+uwscm_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+ return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct uwscm_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Guile sniffers
+ intermediary. */
+
+static void
+uwscm_on_new_gdbarch (struct gdbarch *newarch)
+{
+ struct uwscm_gdbarch_data_type *data =
+ gdbarch_data (newarch, uwscm_gdbarch_data);
+
+ if (!data->unwinder_registered)
+ {
+ struct frame_unwind *unwinder
+ = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
+
+ unwinder->type = NORMAL_FRAME;
+ unwinder->stop_reason = default_frame_unwind_stop_reason;
+ unwinder->this_id = uwscm_this_id;
+ unwinder->prev_register = uwscm_prev_register;
+ unwinder->unwind_data = (void *) newarch;
+ unwinder->sniffer = uwscm_sniffer;
+ unwinder->dealloc_cache = uwscm_dealloc_cache;
+ frame_unwind_prepend_unwinder (newarch, unwinder);
+ data->unwinder_registered = 1;
+ }
+}
+
+static const scheme_function unwind_functions[] =
+{
+ { "make-unwind-info", 2, 2, 0, gdbscm_make_unwind_info,
+ "\
+Make an unwind info object for an ephemeral 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\
+A frame identifier is a unique name for a frame that remains valid as\n\
+long as the frame itself is valid. Usually the frame identifier is\n\
+built from from the frame's stack address and code address. The stack\n\
+address, passed as the second argument, should normally be a pointer to\n\
+the new end of the stack when the function was called, as a GDB value.\n\
+Similarly the code address, the third argument, should be given as the\n\
+address of the entry point of the 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,
+ gdbscm_ephemeral_frame_read_register,
+ "\
+Return the value of a register in an ephemeral frame.\n\
+\n\
+ Arguments: <gdb:ephemeral-frame> string" },
+
+ { "unwind-info-add-saved-register!", 3, 0, 0,
+ gdbscm_unwind_info_add_saved_register_x,
+ "\
+Record the saved value of a register for an ephemeral frame.\n\
+\n\
+After reading an ephemeral frame's registers and determining that it\n\
+can handle the frame, an unwinder will create an unwind info object and\n\
+call this function to record saved registers for the frame. The values\n\
+of the saved registers logically belong to the frame that is older than\n\
+the ephemeral frame being unwound, not to the ephemeral frame itself.\n\
+\n\
+The first argument should be a <gdb:unwind-info> object. The second\n\
+names a register, and should be a string, for example \"rip\". The\n\
+third argument is the value, as a GDB value. By default, any register\n\
+whose value is not added to the unwind info's saved register set\n\
+will be marked as \"not saved\" in the outer frame." },
+
+ END_FUNCTIONS
+};
+
+/* Called by lib/gdb/frame-unwinders.scm. */
+
+static void
+gdbscm_load_frame_unwinders (void *unused)
+{
+ if (gdbscm_frame_unwinders_loaded)
+ return;
+
+ gdbscm_frame_unwinders_loaded = 1;
+
+ gdbscm_define_functions (unwind_functions, 0);
+
+ unwind_frame = scm_c_lookup ("unwind-frame");
+}
+
+/* Initialize the opaque ephemeral frame and unwind info types 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);
+ unwind_info_smob_tag =
+ gdbscm_make_smob_type (unwind_info_smob_name, 0);
+
+ uwscm_gdbarch_data =
+ gdbarch_data_register_post_init (uwscm_gdbarch_data_init);
+ observer_attach_architecture_changed (uwscm_on_new_gdbarch);
+
+ scm_c_register_extension ("gdb", "gdbscm_load_frame_unwinders",
+ gdbscm_load_frame_unwinders, NULL);
+}
@@ -605,7 +605,9 @@ gdbscm_lookup_symbol (SCM name_scm, SCM rest)
TRY
{
- selected_frame = get_selected_frame (_("no frame selected"));
+ selected_frame = get_selected_frame_if_set ();
+ if (selected_frame == NULL)
+ selected_frame = get_current_frame ();
block = get_frame_block (selected_frame, NULL);
}
CATCH (except, RETURN_MASK_ALL)
@@ -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-03-05 Andy Wingo <wingo@igalia.com>
* gdb.guile/amd64-scm-frame-filter-invalidarg.S:
new file mode 100644
@@ -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)
+ (register-frame-unwinder! (make-frame-unwinder name unwind-frame
+ #:priority priority)
+ #:scope objfile))
+ (add-unwinder! "Auto-loaded dummy" 100)
+ (add-unwinder! "Auto-loaded dummy 2" 200))
+
+(install-unwinders!)
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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 .*cabba9e5 .*cabba9e5 .*"
new file mode 100644
@@ -0,0 +1,42 @@
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is part of the GDB test-suite. It tests Guile-based frame
+;; filters.
+
+(use-modules (gdb)
+ (gdb frame-unwinders))
+
+(define (dummy-unwinder frame)
+ #f)
+
+(register-frame-unwinder!
+ (make-frame-unwinder "Dummy" dummy-unwinder #: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"))
+ (prev-pc (value-cast (make-value #xcabba9e5) (value-type this-pc)))
+ (prev-sp (value-add this-sp 32))
+ (unwind-info (make-unwind-info frame this-sp this-pc)))
+ (unwind-info-add-saved-register! unwind-info "rip" prev-pc)
+ (unwind-info-add-saved-register! unwind-info "rsp" prev-sp)
+ unwind-info))
+
+(register-frame-unwinder!
+ (make-frame-unwinder "Synthetic" synthetic-unwinder #:priority 150
+ #:enabled? #f))