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