From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Andy Wingo Newsgroups: gmane.lisp.guile.user Subject: Potluck dish: GDB frame filter interface Date: Tue, 17 Feb 2015 12:10:01 +0100 Message-ID: <87y4nwx08m.fsf@pobox.com> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain X-Trace: ger.gmane.org 1424171441 26444 80.91.229.3 (17 Feb 2015 11:10:41 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Tue, 17 Feb 2015 11:10:41 +0000 (UTC) To: guile-user@gnu.org Original-X-From: guile-user-bounces+guile-user=m.gmane.org@gnu.org Tue Feb 17 12:10:30 2015 Return-path: Envelope-to: guile-user@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1YNg2v-0000hj-KS for guile-user@m.gmane.org; Tue, 17 Feb 2015 12:10:29 +0100 Original-Received: from localhost ([::1]:44741 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YNg2u-0004C4-T7 for guile-user@m.gmane.org; Tue, 17 Feb 2015 06:10:28 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:44520) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YNg2d-0004BK-Gz for guile-user@gnu.org; Tue, 17 Feb 2015 06:10:14 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YNg2Y-0006sp-Ix for guile-user@gnu.org; Tue, 17 Feb 2015 06:10:11 -0500 Original-Received: from pb-sasl1.int.icgroup.com ([208.72.237.25]:50258 helo=sasl.smtp.pobox.com) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YNg2Y-0006px-D8 for guile-user@gnu.org; Tue, 17 Feb 2015 06:10:06 -0500 Original-Received: from sasl.smtp.pobox.com (unknown [127.0.0.1]) by pb-sasl1.pobox.com (Postfix) with ESMTP id 68DB333ECE for ; Tue, 17 Feb 2015 06:10:05 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=pobox.com; h=from:to :subject:date:message-id:mime-version:content-type; s=sasl; bh=d qGRO0DgM0WJSK0Wsf5x2ckWMrU=; b=B2o8GOw56wMmOud0zCM0B9z8i0xdemUFS aGAgmEuoDr2dEeUDo58E6BN6AwbLEjEYOVQpHpRqZ0Cx++zu5HG9481HCMOoXqyd jNmkF5pWV6SLfSNqEnVuVqM+iYsUZ2nDW/ENQxsJIp0EBcvH8dFPVD4KqRVK717b FDk0SMpBfY= DomainKey-Signature: a=rsa-sha1; c=nofws; d=pobox.com; h=from:to:subject :date:message-id:mime-version:content-type; q=dns; s=sasl; b=QOD 4FiAG5W6DHlg7TpjxwLNwqgXWNXv2zjyun7BS9zfqOipyK1oXg8wJIgof/iBAOz7 SRHjbGdRjMS4uCCvpqlzIqPQHqvjAxGg5WEpIz66mN5kw85nzCykbwqSWUxBc60M FGurtk4VoLVpN+8PRX0opVP07hmoAi9WIpoGPUKg= Original-Received: from pb-sasl1.int.icgroup.com (unknown [127.0.0.1]) by pb-sasl1.pobox.com (Postfix) with ESMTP id 60B8C33ECD for ; Tue, 17 Feb 2015 06:10:05 -0500 (EST) Original-Received: from badger (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 5D7B533ECB for ; Tue, 17 Feb 2015 06:10:04 -0500 (EST) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.4 (gnu/linux) X-Pobox-Relay-ID: 862855EC-B695-11E4-981F-8FDD009B7A5A-02397024!pb-sasl1.pobox.com X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 208.72.237.25 X-BeenThere: guile-user@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: General Guile related discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guile-user-bounces+guile-user=m.gmane.org@gnu.org Original-Sender: guile-user-bounces+guile-user=m.gmane.org@gnu.org Xref: news.gmane.org gmane.lisp.guile.user:11762 Archived-At: Hi! A little potluck dish. I have some patches on the GDB mailing list to expose the "frame filter" interface to Guile. This allows you to filter the frames shown in a GDB backtrace using Guile. You can alter the printed function address, name, arguments, file, line, and locals. You can also elide, insert, and nest the printed frames. It's cosmetic but can be useful. Documentation follows this mail. To illustrate, on master I implemented a filter that zips a SRFI-41 stream of GDB frames with a SRFI-41 stream of Guile VM frames, nesting the Guile frames in the right places on the GDB stack. For example, here is the top of a stack trace after starting the REPL: (gdb) bt #0 0x00007ffff736aadd in () #1 0x00007ffff7b093ac in fport_fill_input (port=) at fports.c:756 #2 0x00007ffff7b36138 in scm_slow_get_byte_or_eof_unlocked (port=port@entry=#) at ports.c:2399 #3 0x00007ffff7b361c8 in scm_get_byte_or_eof (port=#) at ../libguile/ports.h:433 #4 0x00007ffff7b361c8 in scm_get_byte_or_eof (port=#) at ports.c:1417 #5 0x00007ffff1571447 in () #6 0x00007ffff155ac4c in () #7 0x00007ffff155b3c5 in () #8 0x00007ffff17be0e2 in %readline [scm_readline] (text="scheme@(guile-user)> ") at readline.c:249 #9 0x00007ffff17be0e2 in %readline [scm_readline] (text="scheme@(guile-user)> ", inp=#, outp=#, read_hook=) at readline.c:201 #10 0x00007ffff7b6ed91 in vm_debug_engine (thread=0x0, vp=0x68fd80, registers=0x1, resume=0) at vm-engine.c:815 #10 0x00007ffff7b8b608 in [subr call] () #10 0x00007ffff19ccc50 in 0x7ffff19ccc50 () #10 0x00007ffff19c8244 in 0x7ffff19c8244 () #10 0x00007ffff19c8458 in get-character () #11 0x00007ffff7b74380 in scm_call_n (proc=#, argv=argv@entry=0x0, nargs=nargs@entry=0) at vm.c:1258 #12 0x00007ffff7afb9d9 in scm_call_0 (proc=) at eval.c:475 #13 0x00007ffff7b74a0e in sf_fill_input (port=#) at vports.c:94 #14 0x00007ffff7b36138 in scm_slow_get_byte_or_eof_unlocked (port=port@entry=#) at ports.c:2399 #15 0x00007ffff7b37ba6 in peek-char [scm_peek_char] (port=#) at ../libguile/ports.h:433 #16 0x00007ffff7b37ba6 in peek-char [scm_peek_char] (len=, buf=0x7fffffffd940 "\210m\210", codepoint=, port=#) at ports.c:1681 #17 0x00007ffff7b37ba6 in peek-char [scm_peek_char] (len=, buf=0x7fffffffd940 "\210m\210", codepoint=, port=#) at ports.c:1909 #18 0x00007ffff7b37ba6 in peek-char [scm_peek_char] (port=#) at ports.c:2252 #19 0x00007ffff7b6edd0 in vm_debug_engine (thread=0x0, vp=0x68fd80, registers=0x1, resume=0) at vm-engine.c:806 #19 0x00007ffff7b8b4e8 in [subr call] () #19 0x00007ffff7e144c0 in flush-leading-whitespace () #19 0x00007ffff7e1569c in 0x7ffff7e1569c () #19 0x00007ffff2710a70 in with-input-from-port () #19 0x00007ffff19cbe8c in 0x7ffff19cbe8c () #19 0x00007ffff26fdec4 in catch () #19 0x00007ffff7e14640 in run-repl* () #19 0x00007ffff7e1517c in start-repl* () #19 0x00007ffff7e1d428 in 0x7ffff7e1d428 () #19 0x00007ffff7e1d5d4 in 0x7ffff7e1d5d4 () #19 0x00007ffff2714154 in call-with-prompt () #20 0x00007ffff7b74380 in scm_call_n (proc=#, argv=argv@entry=0x7fffffffdbb8, nargs=nargs@entry=1) at vm.c:1258 #21 0x00007ffff7afcac7 in scm_primitive_eval (exp=exp@entry=((@ (ice-9 control) %) (begin (load-user-init) ((@ (ice-9 top-repl) top-repl))))) at eval.c:656 #22 0x00007ffff7afcb23 in eval [scm_eval] (exp=((@ (ice-9 control) %) (begin (load-user-init) ((@ (ice-9 top-repl) top-repl)))), module_or_state=module_or_state@entry="#" = {...}) at eval.c:690 #23 0x00007ffff7b465dd in scm_shell (argc=1, argv=0x7fffffffe1f8) at script.c:454 [...] You see the nested function frames corresponding to the Guile calls that are active. It would be nice to get the arguments but that's a bit tricky, for a few reasons, but you can do a "bt full" and print out all the stack slots. Anyway, just a wee potluck dish. Happy hacking! Andy @node Guile Frame Filter API @subsubsection Filtering Frames in Guile @cindex frame filters api, guile Frame filters allow the user to programmatically alter the way a backtrace (@pxref{Backtrace}) prints. Frame filters can reorganize, annotate, insert, and remove frames in a backtrace. Only commands that print a backtrace, or, in the case of @sc{gdb/mi} commands (@pxref{GDB/MI}), those that return a collection of frames are affected. The commands that work with frame filters are: @table @code @item backtrace @xref{backtrace-command,, The backtrace command}. @item -stack-list-frames @xref{-stack-list-frames,, The -stack-list-frames command}. @item -stack-list-variables @xref{-stack-list-variables,, The -stack-list-variables command}. @item -stack-list-arguments @xref{-stack-list-arguments,, The -stack-list-arguments command}. @item -stack-list-locals @xref{-stack-list-locals,, The -stack-list-locals command}. @end table @cindex frame annotator A frame filter is a function that takes a SRFI-41 stream of annotated frame objects as an argument, and returns a potentially modified stream of annotated frame objects. @xref{SRFI-41,,,guile,The Guile Reference Manual}, for more on the SRFI-41 specification for lazy streams. Operating over a stream allows frame filters to inspect, reorganize, insert, and remove frames. @value{GDBN} also provides a more simple @dfn{frame annotator} API that works on individual frames, for the common case in which the user does not need to reorganize the backtrace. Both APIs are described below. There can be multiple frame filters registered with @value{GDBN}, and each one may be individually enabled or disabled at will. Multiple frame filters can be enabled at the same time. Frame filters have an associated priority which determines the order in which they are applied over the annotated frame stream. For example, if there are two filters registered and enabled, @var{f1} and @var{f2}, and the priority of @var{f2} is greater than that of @var{f1}, then the result of frame filtering will be @code{(@var{f2} (@var{f1} @var{stream}))}. In this way, higher-priority frame filters get the last word on the backtrace that is ultimately printed. An important consideration when designing frame filters, and well worth reflecting upon, is that frame filters should avoid unwinding the call stack if possible. Some stacks can run very deep, into the tens of thousands in some cases. To search every frame when a frame filter executes may be too expensive at that step. The frame filter cannot know how many frames it has to iterate over, and it may have to iterate through them all. This ends up duplicating effort as @value{GDBN} performs this iteration when it prints the frames. Therefore a frame filter should avoid peeking ahead in the frame stream, if possible. @xref{Writing a Frame Filter}, for examples on how to write a good frame filter. To use frame filters, first load the @code{(gdb frames)} module to have access to the procedures that manipulate frame filters: @example (use-modules (gdb frames)) @end example @deffn {Scheme Procedure} add-frame-filter! name filter @ @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]} @ @r{[}#:objfile objfile@r{]} @r{[}#:progspace progspace@r{]} Register the frame filter procedure @var{filter} with @value{GDBN}. @var{filter} should be a function of one argument, taking a SRFI-41 stream of annotated frames and returning a possibily modified stream of annotated frames. The filter is identified by @var{name}, which should be unique among all known filters. The filter will be registered with the given @var{priority}, which should be a number, and which defaults to 20 if not given. By default, the filter is global, meaning that it is associated with all objfiles and progspaces. Pass one of @code{#:objfile} or @code{#:progspace} to instead associate the filter with a specific objfile or progspace, respectively. The filter will be initially enabled, unless the keyword argument @code{#:enabled? #f} is given. @end deffn @deffn {Scheme Procedure} all-frame-filters Return a list of the names of all frame filters. @end deffn @deffn {Scheme Procedure} remove-frame-filter! name @deffnx {Scheme Procedure} enable-frame-filter! name @deffnx {Scheme Procedure} disable-frame-filter! name Remove, enable, or disable a frame filter, respectively. @var{name} should correspond to the name of a filter previously added with @code{add-frame-filter!}. If no such filter is found, an error is signalled. @end deffn When a command is executed from @value{GDBN} that is compatible with frame filters, @value{GDBN} selects all filters registered in the current progspace, filters for all objfiles, and filters with no associated objfile or progspace. That list is then sorted by priority, as described above, and applied to the annotated frame stream. An annotated frame is a Guile record type that holds information about a frame: its function name, its arguments, its locals, and so on. An annotated frame is always associated with a @value{GDBN} frame object. To add, remove, or otherwise alter information associated with an annotated frame, use the @code{reannotate-frame} procedure. @deffn {Scheme Procedure} reannotate-frame! ann @ @r{[}#:function-name function-name@r{]} @ @r{[}#:address address@r{]} @ @r{[}#:filename filename@r{]} @ @r{[}#:line line@r{]} @ @r{[}#:arguments arguments@r{]} @ @r{[}#:locals locals@r{]} @ @r{[}#:children children@r{]} Take the annotated frame object @var{ann} and return a new annotated frame object, replacing the fields specified by the keyword arguments with their new values. For example, calling @code{(reannotate-frame @var{x} #:function-name "foo")} will create a new annotated frame object that inherits all fields from @var{x}, but whose function name has been set to @samp{foo}. @end deffn The @code{(gdb frames)} module defines accessors for the various fields of annotated frame objects. @deffn {Scheme Procedure} annotated-frame-frame ann Return the @value{GDBN} frame object associated with the annotated frame @var{ann}. @xref{Frames In Guile}. @end deffn @deffn {Scheme Procedure} annotated-frame-function-name ann Return the function name associated with the annotated frame @var{ann}, as a string, or @code{#f} if not available. @end deffn @deffn {Scheme Procedure} annotated-frame-address ann Return the address associated with the annotated frame @var{ann}, as an integer. @end deffn @deffn {Scheme Procedure} annotated-frame-filename ann Return the file name associated with the annotated frame @var{ann}, as a string, or @code{#f} if not available. @end deffn @deffn {Scheme Procedure} annotated-frame-line ann Return the line number associated with the annotated frame @var{ann}, as an integer, or @code{#f} if not available. @end deffn @deffn {Scheme Procedure} annotated-frame-arguments ann Return a list of the function arguments associated with the annotated frame @var{ann}. Each item of the list should either be a @value{GDBN} symbol (@pxref{Symbols In Guile}), a pair of a @value{GDBN} symbol and a @value{GDBN} value (@pxref{Values From Inferior In Guile}, or a pair of a string and a @value{GDBN} value. In the first case, the value will be loaded from the frame if needed. @end deffn @deffn {Scheme Procedure} annotated-frame-locals ann Return a list of the function arguments associated with the annotated frame @var{ann}, in the same format as for @code{annotated-frame-arguments}. @end deffn Annotated frames may also have child frames. By default, no frame has a child frame, but filters may reorganize the frame stream into a stream of frame trees, by populating the child list. Of course, such a reorganization is ultimately cosmetic, as it doesn't alter the stack of frames seen by @value{GDBN} and navigable by the user, for example by using the @code{frame} command. Still, nesting frames may lead to a more understandable presentation of a backtrace. @deffn {Scheme Procedure} annotated-frame-children ann Return a list of the child frames associated with the annotated frame @var{ann}. Each item of the list should be an annotated frame object. @end deffn While frame filters can both reorganize and reannotate the frame stream, it is often the case that one only wants to reannotate the frames in a stream, without reorganizing then. In that case there is a simpler API for frame annotators that simply maps annotated frames to annotated frames. @deffn {Scheme Procedure} add-frame-annotator! name annotator @ @r{[}#:priority priority@r{]} @r{[}#:enabled? boolean@r{]} @ @r{[}#:objfile objfile@r{]} @r{[}#:progspace progspace@r{]} Register the frame annotator procedure @var{annotator} with @value{GDBN}. @var{annotator} should be a function of one argument, taking annotated frame object and returning a possibily modified annotated frame. The annotator is identified by @var{name}, which should be unique among all known annotators. The annotator has an associated priority, as with frame filters. See the documentation on @code{add-frame-filter!}, for more. The annotator will be initially enabled, unless the keyword argument @code{#:enabled? #f} is given. @end deffn @deffn {Scheme Procedure} all-frame-annotators Return a list of the names of all frame annotators. @end deffn @deffn {Scheme Procedure} remove-frame-annotator! name @deffnx {Scheme Procedure} enable-frame-annotator! name @deffnx {Scheme Procedure} disable-frame-annotator! name Remove, enable, or disable a frame annotator, respectively. @var{name} should correspond to the name of a annotator previously added with @code{add-frame-annotator!}. If no such annotator is found, an error is signalled. @end deffn @node Writing a Frame Filter in Guile @subsubsection Writing a Frame Filter in Guile @cindex writing a frame filter in guile The simplest kind of frame filter just takes the incoming stream of frames and produces an identical stream of values. For example: @example (use-modules (gdb frames)) (define (identity-frame-filter stream) (cond ((stream-null? stream) ;; End of stream? Then return end-of-stream. stream-null) (else ;; Otherwise recurse on the tail of the stream. (stream-cons (stream-car stream) (identity-frame-filter (stream-cdr stream)))))) @end example If you are not familiar with SRFI-41 streams, you might think that this would eagerly traverse the whole stack of frames. This would be bad because we don't want to produce an entire backtrace at once when the user might cancel after only seeing one page. However this is not the case, because unlike normal Scheme procedures, @code{stream-cons} is @emph{lazy} in its arguments, which is to say that its arguments are only evaluated when they are accessed via @code{stream-car} and @code{stream-cdr}. In this way the stream looks infinite, but in reality only produces values as they are requested by the caller. To use this frame filter, we have to register it with @value{GDBN}. @example (add-frame-filter! "identity" identity-frame-filter) @end example Now our filter will run each time a backtrace is printed, or in general for any @value{GDBN} command that uses the frame filter interface. Filters are enabled when they are added. You can control the enabled or disabled state of a filter using the appropriate procedures: @example (disable-frame-filter! "identity") (enable-frame-filter! "identity") @end example Finally, we can remove all filters with a simple application of @code{for-each}: @example (for-each remove-frame-filter! (all-frame-filters)) @end example The same general mechanics apply to frame annotators as well. Let us define a more interesting example. For example, in Guile there is a function @code{scm_call_n}, which may be invoked directly but is often invoked via well-known wrappers like @code{scm_call_0}, @code{scm_call_1}, and so on. For example here is part of a backtrace of an optimized Guile build, when you first start a Guile REPL: @smallexample #10 0x00007ffff7b6ed91 in vm_debug_engine ([...]) at vm-engine.c:815 #11 0x00007ffff7b74380 in scm_call_n ([...]) at vm.c:1258 #12 0x00007ffff7afb9d9 in scm_call_0 ([...]) at eval.c:475 #13 0x00007ffff7b74a0e in sf_fill_input ([...]) at vports.c:94 @end smallexample For the sake of the example, the arguments to each have been abbreviated to @code{[...]}. Now, it might be nice if we could nest @code{scm_call_n} inside @code{scm_call_0}, so let's do that: @smallexample (use-modules (gdb) (gdb frames) (srfi srfi-41)) (define (nest-scm-call-filter stream) ;; When we have the new head and tail, use this helper to make a ;; stream from them, lazily recursing on the tail. (define (recur head tail) (stream-cons head (nest-scm-call-filter tail))) (cond ((stream-null? stream) ;; No more frames? Just return the stream as is. stream) (else (let ((head (stream-car stream)) (tail (stream-cdr stream))) (cond ;; Is this a call to scm_call_n and is there a next frame? ((and (equal? (annotated-frame-function-name head) "scm_call_n") (not (stream-null? tail))) (let* ((next (stream-car tail)) (next-name (annotated-frame-function-name next))) (cond ;; Does the next frame have a function name and ;; does it start with "scm_call_"? ((and next-name (string-prefix? "scm_call_" next-name)) ;; A match! Add `head' to the child list of `next'. (let ((children (cons head (annotated-frame-children next)))) (recur (reannotate-frame next #:children children) (stream-cdr tail)))) (else (recur head tail))))) (else (recur head tail))))))) (add-frame-filter! "nest-scm-call" nest-scm-call-filter) @end smallexample With this filter in place, the resulting backtrace looks like: @smallexample #10 0x00007ffff7b6ed91 in vm_debug_engine ([...]) at vm-engine.c:815 #12 0x00007ffff7afb9d9 in scm_call_0 ([...]) at eval.c:475 #11 0x00007ffff7b74380 in scm_call_n ([...]) at vm.c:1258 #13 0x00007ffff7b74a0e in sf_fill_input ([...]) at vports.c:94 @end smallexample As you can see, frame #11 has been nested below frame #12. Sometimes, though, all this stream processing and stream recursion can be too complicated if your desire is just to annotate individual frames. In that situation, the frame annotator API can be more appropriate. For example, if we know that there are some C procedures that have ``aliases'' in some other language, like Scheme, then we can annotate them in the backtrace with their Scheme names. @smallexample (use-modules (gdb frames)) (define *function-name-aliases* '(("scm_primitive_eval" . "primitive-eval"))) (define (alias-annotator ann) (let* ((name (annotated-frame-function-name ann)) (alias (assoc-ref *function-name-aliases* name))) (if alias (reannotate-frame ann #:function-name (string-append "[" alias "] " name)) ann))) (add-frame-annotator! "alias-annotator" alias-annotator) @end smallexample A backtrace with this annotator in place produces: @smallexample #19 [...] in vm_debug_engine ([...]) at vm-engine.c:806 #20 [...] in scm_call_n ([...]) at vm.c:1258 #21 [...] in [primitive-eval] scm_primitive_eval ([...]) at eval.c:656 #22 [...] in scm_eval ([...]) at eval.c:690 #23 [...] in scm_shell ([...]) at script.c:454 @end smallexample Again, parts have been elided with @code{[...]}. It is possible to do the job of an annotator with a filter, but if the task is simple enough for an annotator, it's much less code, as the above example shows. -- http://wingolog.org/