Message ID | 87bnjtc4o4.fsf@igalia.com |
---|---|
State | New |
Headers | show |
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.
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 65f2712..bd49503 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -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 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 + +@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 diff --git a/gdb/frame-unwind.c b/gdb/frame-unwind.c index bba1ae7..4347dca 100644 --- a/gdb/frame-unwind.c +++ b/gdb/frame-unwind.c @@ -87,6 +87,34 @@ frame_unwind_append_unwinder (struct gdbarch *gdbarch, (*ip)->unwinder = unwinder; } +/* Nonzero if we are finding the unwinder for a frame; see + frame_unwind_try_handler. */ +static int is_unwinding = 0; + +/* Return nonzero if we are inside a sniffer call. */ + +int +frame_unwind_is_unwinding (void) +{ + return is_unwinding; +} + +/* Cleanup helpers for is_unwinding. */ + +static void +unset_is_unwinding (void *unused) +{ + is_unwinding = 0; +} + +static struct cleanup* +set_is_unwinding (void) +{ + is_unwinding = 1; + + return make_cleanup (unset_is_unwinding, NULL); +} + /* Call SNIFFER from UNWINDER. If it succeeded set UNWINDER for THIS_FRAME and return 1. Otherwise the function keeps THIS_FRAME unchanged and returns 0. */ @@ -98,11 +126,18 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache, struct cleanup *old_cleanup; int res = 0; + if (is_unwinding) + internal_error (__FILE__, __LINE__, + _("Recursion detected while finding an unwinder.")); old_cleanup = frame_prepare_for_sniffer (this_frame, unwinder); TRY { + struct cleanup *cleanup = set_is_unwinding (); + res = unwinder->sniffer (unwinder, this_frame, this_cache); + + do_cleanups (cleanup); } CATCH (ex, RETURN_MASK_ERROR) { @@ -252,7 +287,8 @@ frame_unwind_got_constant (struct frame_info *frame, int regnum, } struct value * -frame_unwind_got_bytes (struct frame_info *frame, int regnum, gdb_byte *buf) +frame_unwind_got_bytes (struct frame_info *frame, int regnum, + const gdb_byte *buf) { struct gdbarch *gdbarch = frame_unwind_arch (frame); struct value *reg_val; diff --git a/gdb/frame-unwind.h b/gdb/frame-unwind.h index 44add12..3e322f2 100644 --- a/gdb/frame-unwind.h +++ b/gdb/frame-unwind.h @@ -179,6 +179,11 @@ extern void frame_unwind_append_unwinder (struct gdbarch *gdbarch, extern void frame_unwind_find_by_frame (struct frame_info *this_frame, void **this_cache); +/* Return nonzero if we are in the process of finding an unwinder for a frame. + See the comments in get_current_frame. */ + +extern int frame_unwind_is_unwinding (void); + /* Helper functions for value-based register unwinding. These return a (possibly lazy) value of the appropriate type. */ @@ -210,7 +215,7 @@ struct value *frame_unwind_got_constant (struct frame_info *frame, int regnum, inside BUF. */ struct value *frame_unwind_got_bytes (struct frame_info *frame, int regnum, - gdb_byte *buf); + const gdb_byte *buf); /* Return a value which indicates that FRAME's saved version of REGNUM has a known constant (computed) value of ADDR. Convert the diff --git a/gdb/frame.c b/gdb/frame.c index b3cbf23..9e63f21 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -2212,6 +2212,22 @@ get_prev_frame (struct frame_info *this_frame) return NULL; } + /* Unwinders implemented in Python or Scheme could eventually make an API call + that would cause GDB to try to unwind a frame while unwinding a frame. + Because already-unwound frames will be found in the frame cache, unwinding + will only happen at the old end of the stack, which means that any + recursive unwinding attempt will surely lead to unbounded recursion. Ways + this can happen include such common functions as `get_current_arch' or + `lookup_symbol', via `get_selected_frame', so it's impractical to simply + declare these an error. Instead, we detect this case and return NULL, + indicating that the known stack of frames ends here. */ + if (frame_unwind_is_unwinding ()) + { + frame_debug_got_null_frame (this_frame, + "get_prev_frame within unwinder sniffer"); + return NULL; + } + return get_prev_frame_always (this_frame); } diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h index d7c166c..9ca807e 100644 --- a/gdb/guile/guile-internal.h +++ b/gdb/guile/guile-internal.h @@ -598,6 +598,7 @@ extern void gdbscm_initialize_disasm (void); extern void gdbscm_initialize_exceptions (void); extern void gdbscm_initialize_frames (void); extern void gdbscm_initialize_frame_filters (void); +extern void gdbscm_initialize_frame_unwinders (void); extern void gdbscm_initialize_iterators (void); extern void gdbscm_initialize_lazy_strings (void); extern void gdbscm_initialize_math (void); diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c index 176e97e..d1850e7 100644 --- a/gdb/guile/guile.c +++ b/gdb/guile/guile.c @@ -670,6 +670,7 @@ initialize_gdb_module (void *data) gdbscm_initialize_disasm (); gdbscm_initialize_frames (); gdbscm_initialize_frame_filters (); + gdbscm_initialize_frame_unwinders (); gdbscm_initialize_iterators (); gdbscm_initialize_lazy_strings (); gdbscm_initialize_math (); diff --git a/gdb/guile/lib/gdb/frame-unwinders.scm b/gdb/guile/lib/gdb/frame-unwinders.scm new file mode 100644 index 0000000..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)) + "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") 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. */ +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); +} 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. + 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 @@ +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" + 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 .*" 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")) + (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))
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