From patchwork Thu Mar 5 15:57:53 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andy Wingo X-Patchwork-Id: 5478 Received: (qmail 105853 invoked by alias); 5 Mar 2015 15:58:05 -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 105844 invoked by uid 89); 5 Mar 2015 15:58:04 -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; Thu, 05 Mar 2015 15:58:00 +0000 Received: from sasl.smtp.pobox.com (unknown [127.0.0.1]) by pb-sasl1.pobox.com (Postfix) with ESMTP id 0318D3ACA0; Thu, 5 Mar 2015 10:57:58 -0500 (EST) Received: from pb-sasl1.int.icgroup.com (unknown [127.0.0.1]) by pb-sasl1.pobox.com (Postfix) with ESMTP id E8D373AC9F; Thu, 5 Mar 2015 10:57:57 -0500 (EST) 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 69F293AC9D; Thu, 5 Mar 2015 10:57:56 -0500 (EST) From: Andy Wingo To: gdb-patches@sourceware.org Cc: asmundak@google.com Subject: [PATCH] Add Guile frame unwinder interface Date: Thu, 05 Mar 2015 16:57:53 +0100 Message-ID: <87oao7wi66.fsf@igalia.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.4 (gnu/linux) MIME-Version: 1.0 X-Pobox-Relay-ID: 63B664DC-C350-11E4-AF78-B058D0B8C469-02397024!pb-sasl1.pobox.com X-IsSubscribed: yes Hi, This is an implementation of a frame unwinder interface in the spirit of Alexander's work in this thread: http://thread.gmane.org/gmane.comp.gdb.patches/103360/focus=105202 No documentation yet, and I am still wondering how to test it appropriately. However it does seem some feedback could be useful before I document the wrong thing; particualarly I would like feedback on the changes to frame-unwind.c and frame.c. However happily it does work in V8; eliding some helpers, the implementation looks like this: (use-modules (gdb frame-unwinders)) (define (unwind-v8-frame frame) (let ((isolate (cached-current-isolate))) (when isolate (let* ((this-pc (ephemeral-frame-read-register frame "rip")) (this-fp (ephemeral-frame-read-register frame "rbp")) (code (lookup-code-for-pc this-pc isolate))) (when code (set-ephemeral-frame-id! frame this-fp (code-instruction-start code)) (let* ((type (if (code-optimized? code) 'javascript 'optimized)) (prev-pc-address (compute-standard-frame-pc-address this-fp)) (prev-sp (compute-frame-older-sp this-fp type)) (prev-fp (compute-standard-frame-older-fp this-fp)) (prev-pc (value-dereference prev-pc-address))) (ephemeral-frame-add-saved-register! frame "rsp" prev-sp) (ephemeral-frame-add-saved-register! frame "rbp" prev-fp) (ephemeral-frame-add-saved-register! frame "rip" prev-pc))))))) (define* (install-frame-unwinders #:optional (objfile (current-objfile))) (add-frame-unwinder! (make-frame-unwinder "guile-v8-frame-unwinder" unwind-v8-frame))) And most happily, it requires no changes in V8 itself. Yaaay :) With an appropriate frame filter, a backtrace looks like this: #0 0x00000d3c5b0661a1 in TestCase () at /hack/v8/test/mjsunit/debug-step-4-in-frame.js:94 #1 0x00000d3c5b06a3d3 in () at /hack/v8/test/mjsunit/debug-step-4-in-frame.js:112 #2 0x00000d3c5b02c620 in [internal frame] () #3 0x00000d3c5b014d31 in [entry frame] () #4 0x0000000000b4e949 in v8::internal::Invoke([...]) at ../src/execution.cc:128 #5 0x0000000000b4ed23 in v8::internal::Execution::Call([...]) at ../src/execution.cc:179 #6 0x0000000000a3f813 in v8::Script::Run([...]) at ../src/api.cc:1514 #7 0x0000000000a149fa in v8::Shell::ExecuteString([...]) at ../src/d8.cc:281 #8 0x0000000000a194eb in v8::SourceGroup::Execute([...]) at ../src/d8.cc:1213 #9 0x0000000000a1a128 in v8::Shell::RunMain([...]) at ../src/d8.cc:1448 #10 0x0000000000a1efdc in v8::Shell::Main([...]) at ../src/d8.cc:1721 #11 0x0000000000a1f143 in main([...]) at ../src/d8.cc:1757 instead of this: #0 0x00000d3c5b0661a1 in ?? () #1 0x0000000002404940 in ?? () #2 0x0000219b8fc5d779 in ?? () #3 0x000018a8ddbf01d9 in ?? () #4 0x0000219b8fc62a81 in ?? () #5 0x000018a8ddbf0179 in ?? () #6 0x00007fffffffd500 in ?? () #7 0x00000d3c5b06a3d3 in ?? () #8 0x00001df7db238fb1 in ?? () #9 0x0000000000000000 in ?? () Yaaaaaaaaaaay :) Regards, Andy From 948e1bba3bd08fba22c44e4afe18436d84220147 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/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 an attempt to recursively unwind from the sentinel, and return NULL. * frame-unwind.h (frame_unwind_got_bytes): Make buf arg const. (frame_unwind_is_unwinding_innermost_frame): New declaration. * frame-unwind.c (unwinding_innermost_frame): New file-local variable. (innermost_frame_unwind_begin, innermost_frame_unwind_end): New functions. (frame_unwind_is_unwinding_innermost_frame): New exported predicate. (frame_unwind_find_by_frame): Arrange for frame_unwind_is_unwinding_innermost_frame 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/ChangeLog | 32 ++ gdb/Makefile.in | 6 + gdb/data-directory/Makefile.in | 2 + gdb/frame-unwind.c | 43 ++- gdb/frame-unwind.h | 7 +- gdb/frame.c | 13 + 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 | 566 ++++++++++++++++++++++++++++++++++ gdb/guile/scm-symbol.c | 4 +- 11 files changed, 882 insertions(+), 6 deletions(-) create mode 100644 gdb/guile/lib/gdb/frame-unwinders.scm create mode 100644 gdb/guile/scm-frame-unwinder.c diff --git a/gdb/ChangeLog b/gdb/ChangeLog index d55daf6..f9ea8e2 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,35 @@ +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 an attempt to recursively + unwind from the sentinel, and return NULL. + * frame-unwind.h (frame_unwind_got_bytes): Make buf arg const. + (frame_unwind_is_unwinding_innermost_frame): New declaration. + * frame-unwind.c (unwinding_innermost_frame): New file-local + variable. + (innermost_frame_unwind_begin, innermost_frame_unwind_end): New + functions. + (frame_unwind_is_unwinding_innermost_frame): New exported + predicate. + (frame_unwind_find_by_frame): Arrange for + frame_unwind_is_unwinding_innermost_frame 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/frame-unwind.c b/gdb/frame-unwind.c index e73650a..67f19ec 100644 --- a/gdb/frame-unwind.c +++ b/gdb/frame-unwind.c @@ -129,6 +129,33 @@ frame_unwind_try_unwinder (struct frame_info *this_frame, void **this_cache, gdb_assert_not_reached ("frame_unwind_try_unwinder"); } +/* Nonzero if we are finding the unwinder for the innermost frame. */ +static int unwinding_innermost_frame = 0; + +static void +innermost_frame_unwind_begin (void) +{ + if (unwinding_innermost_frame) + internal_error (__FILE__, __LINE__, + _("Recursion detected while unwinding innermost frame.")); + + unwinding_innermost_frame = 1; +} + +static void +innermost_frame_unwind_end (void) +{ + gdb_assert (unwinding_innermost_frame); + + unwinding_innermost_frame = 0; +} + +int +frame_unwind_is_unwinding_innermost_frame (void) +{ + return unwinding_innermost_frame; +} + /* Iterate through sniffers for THIS_FRAME frame until one returns with an unwinder implementation. THIS_FRAME->UNWIND must be NULL, it will get set by this function. Possibly initialize THIS_CACHE. */ @@ -141,23 +168,30 @@ frame_unwind_find_by_frame (struct frame_info *this_frame, void **this_cache) struct frame_unwind_table_entry *entry; const struct frame_unwind *unwinder_from_target; + if (frame_relative_level (this_frame) == 0) + innermost_frame_unwind_begin (); + unwinder_from_target = target_get_unwinder (); if (unwinder_from_target != NULL && frame_unwind_try_unwinder (this_frame, this_cache, unwinder_from_target)) - return; + goto done; unwinder_from_target = target_get_tailcall_unwinder (); if (unwinder_from_target != NULL && frame_unwind_try_unwinder (this_frame, this_cache, unwinder_from_target)) - return; + goto done; for (entry = table->list; entry != NULL; entry = entry->next) if (frame_unwind_try_unwinder (this_frame, this_cache, entry->unwinder)) - return; + goto done; internal_error (__FILE__, __LINE__, _("frame_unwind_find_by_frame failed")); + + done: + if (frame_relative_level (this_frame) == 0) + innermost_frame_unwind_end (); } /* A default frame sniffer which always accepts the frame. Used by @@ -249,7 +283,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..7f12211 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 a nonzero if we are in the process of finding an unwinder for the + innermost frame. See the comments in get_current_frame(). */ + +extern int frame_unwind_is_unwinding_innermost_frame (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..46bb8a7 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -2209,6 +2209,19 @@ get_prev_frame (struct frame_info *this_frame) return NULL; } + /* Unwinders implemented in Python or Scheme could end up calling a GDB + function that gets the current frame, for example to get the current + architecture. When in the process of unwinding the innermost frame, this + would cause unbounded recursion. Instead short-circuit the computation, + which will cause callers to fall back to the sentinel frame. */ + if (this_frame->level == -1 + && frame_unwind_is_unwinding_innermost_frame ()) + { + frame_debug_got_null_frame (this_frame, + "recursive unwind of innermost frame"); + 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..009b13d --- /dev/null +++ b/gdb/guile/scm-frame-unwinder.c @@ -0,0 +1,566 @@ +/* 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 + , or #f to indicate that the register was not saved by the + ephemeral frame. */ + +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 (!gdbscm_is_false (value_scm)) + { + 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. Alternately, passing #f\n\ +as the value will mark the register as unavailable." }, + + 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);