1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-30 03:40:34 +02:00

narrowing stacks to prompts; backtrace shows frames from start-stack

* libguile/stacks.c (scm_sys_stacks): New global variable, moved here
  from boot-9.scm.
  (scm_init_stacks): Define scm_sys_stacks to %stacks.
  (stack_depth): Remove narrowing by frame pointer.
  (find_prompt): New helper.
  (narrow_stack): Clean up a bit, and allow narrowing by prompt tag.
  (scm_make_stack): Update docs, and use scm_stack_id to get the stack
  id.
  (scm_stack_id): The current stack id may be fetched as the cdar of
  %stacks.
  (stack_id_with_fp): Remove helper.

* module/ice-9/boot-9.scm (%start-stack): Fix indentation.
  (%stacks): Remove definition, it's in stacks.c now.
  (default-pre-unwind-handler): Narrow by another frame.
  (save-stack): Remove special handling for certain stack ids, as it is
  often possible that the function isn't on the stack -- in the
  interpreter, or after a tail call. Better to narrow by prompt ids.

* module/system/vm/debug.scm (print-frames): Change to operate on a
  vector of frames.
  (run-debugger): Change to receive a vector of frames. The debugger
  also has the full stack, so it can re-narrow (or widen) to get the
  whole stack, if the user wants.
  (stack->vector): New helper.
  (debug-pre-unwind-handler): Narrow by more frames, and to the most
  recent start-stack invocation. Adapt to run-debugger change.
This commit is contained in:
Andy Wingo 2010-03-13 21:03:06 +01:00
parent 01c0082fae
commit 06dcb9dfb6
3 changed files with 227 additions and 173 deletions

View file

@ -24,6 +24,7 @@
#endif
#include "libguile/_scm.h"
#include "libguile/control.h"
#include "libguile/eval.h"
#include "libguile/debug.h"
#include "libguile/continuations.h"
@ -41,6 +42,8 @@
#include "libguile/private-options.h"
static SCM scm_sys_stacks;
/* {Stacks}
*
@ -59,17 +62,14 @@
static SCM stack_id_with_fp (SCM frame, SCM **fp);
/* Count number of debug info frames on a stack, beginning with FRAME.
*/
static long
stack_depth (SCM frame, SCM *fp)
stack_depth (SCM frame)
{
long n = 0;
/* count frames, skipping boot frames */
for (; scm_is_true (frame) && SCM_VM_FRAME_FP (frame) > fp;
frame = scm_frame_previous (frame))
for (; scm_is_true (frame); frame = scm_frame_previous (frame))
++n;
return n;
}
@ -95,6 +95,21 @@ stack_depth (SCM frame, SCM *fp)
* encountered.
*/
static SCM
find_prompt (SCM key)
{
SCM winds;
for (winds = scm_i_dynwinds (); scm_is_pair (winds); winds = scm_cdr (winds))
{
SCM elt = scm_car (winds);
if (SCM_PROMPT_P (elt) && SCM_PROMPT_TAG (elt) == key)
return elt;
}
scm_misc_error ("make-stack", "Prompt tag not found while narrowing stack",
scm_list_1 (key));
return SCM_BOOL_F; /* not reached */
}
static void
narrow_stack (SCM stack, long inner, SCM inner_key, long outer, SCM outer_key)
{
@ -105,16 +120,7 @@ narrow_stack (SCM stack, long inner, SCM inner_key, long outer, SCM outer_key)
frame = SCM_STACK_FRAME (stack);
/* Cut inner part. */
if (scm_is_eq (inner_key, SCM_BOOL_T))
{
/* Cut specified number of frames. */
for (; inner && len; --inner)
{
len--;
frame = scm_frame_previous (frame);
}
}
else
if (scm_is_true (scm_procedure_p (inner_key)))
{
/* Cut until the given procedure is seen. */
for (; inner && len ; --inner)
@ -126,11 +132,33 @@ narrow_stack (SCM stack, long inner, SCM inner_key, long outer, SCM outer_key)
break;
}
}
else if (scm_is_symbol (inner_key))
{
/* Cut until the given prompt tag is seen. FIXME, assumes prompt tags are
symbols. */
SCM prompt = find_prompt (inner_key);
for (; len; len--, frame = scm_frame_previous (frame))
if (SCM_PROMPT_REGISTERS (prompt)->fp
== SCM_VM_FRAME_FP (frame) - SCM_VM_FRAME_OFFSET (frame))
break;
}
else
{
/* Cut specified number of frames. */
for (; inner && len; --inner)
{
len--;
frame = scm_frame_previous (frame);
}
}
SCM_SET_STACK_LENGTH (stack, len);
SCM_SET_STACK_FRAME (stack, frame);
/* Cut outer part. */
if (scm_is_true (scm_procedure_p (outer_key)))
{
/* Cut until the given procedure is seen. */
for (; outer && len ; --outer)
{
frame = scm_stack_ref (stack, scm_from_long (len - 1));
@ -138,6 +166,30 @@ narrow_stack (SCM stack, long inner, SCM inner_key, long outer, SCM outer_key)
if (scm_is_eq (scm_frame_procedure (frame), outer_key))
break;
}
}
else if (scm_is_symbol (outer_key))
{
/* Cut until the given prompt tag is seen. FIXME, assumes prompt tags are
symbols. */
SCM prompt = find_prompt (outer_key);
while (len)
{
frame = scm_stack_ref (stack, scm_from_long (len - 1));
len--;
if (SCM_PROMPT_REGISTERS (prompt)->fp
== SCM_VM_FRAME_FP (frame) - SCM_VM_FRAME_OFFSET (frame))
break;
}
}
else
{
/* Cut specified number of frames. */
for (; outer && len ; --outer)
{
frame = scm_stack_ref (stack, scm_from_long (len - 1));
len--;
}
}
SCM_SET_STACK_LENGTH (stack, len);
}
@ -163,24 +215,33 @@ SCM_DEFINE (scm_make_stack, "make-stack", 1, 0, 1,
"Create a new stack. If @var{obj} is @code{#t}, the current\n"
"evaluation stack is used for creating the stack frames,\n"
"otherwise the frames are taken from @var{obj} (which must be\n"
"either a debug object or a continuation).\n\n"
"a continuation or a frame object).\n"
"\n"
"@var{args} should be a list containing any combination of\n"
"integer, procedure and @code{#t} values.\n\n"
"integer, procedure, prompt tag and @code{#t} values.\n"
"\n"
"These values specify various ways of cutting away uninteresting\n"
"stack frames from the top and bottom of the stack that\n"
"@code{make-stack} returns. They come in pairs like this:\n"
"@code{(@var{inner_cut_1} @var{outer_cut_1} @var{inner_cut_2}\n"
"@var{outer_cut_2} @dots{})}.\n\n"
"Each @var{inner_cut_N} can be @code{#t}, an integer, or a\n"
"procedure. @code{#t} means to cut away all frames up to but\n"
"excluding the first user module frame. An integer means to cut\n"
"away exactly that number of frames. A procedure means to cut\n"
"away all frames up to but excluding the application frame whose\n"
"procedure matches the specified one.\n\n"
"Each @var{outer_cut_N} can be an integer or a procedure. An\n"
"integer means to cut away that number of frames. A procedure\n"
"means to cut away frames down to but excluding the application\n"
"frame whose procedure matches the specified one.\n\n"
"@var{outer_cut_2} @dots{})}.\n"
"\n"
"Each @var{inner_cut_N} can be @code{#t}, an integer, a prompt\n"
"tag, or a procedure. @code{#t} means to cut away all frames up\n"
"to but excluding the first user module frame. An integer means\n"
"to cut away exactly that number of frames. A prompt tag means\n"
"to cut away all frames that are inside a prompt with the given\n"
"tag. A procedure means to cut away all frames up to but\n"
"excluding the application frame whose procedure matches the\n"
"specified one.\n"
"\n"
"Each @var{outer_cut_N} can be an integer, a prompt tag, or a\n"
"procedure. An integer means to cut away that number of frames.\n"
"A prompt tag means to cut away all frames that are outside a\n"
"prompt with the given tag. A procedure means to cut away\n"
"frames down to but excluding the application frame whose\n"
"procedure matches the specified one.\n"
"\n"
"If the @var{outer_cut_N} of the last pair is missing, it is\n"
"taken as 0.")
#define FUNC_NAME s_scm_make_stack
@ -189,7 +250,6 @@ SCM_DEFINE (scm_make_stack, "make-stack", 1, 0, 1,
int maxp;
SCM frame;
SCM stack;
SCM id, *id_fp;
SCM inner_cut, outer_cut;
/* Extract a pointer to the innermost frame of whatever object
@ -209,6 +269,8 @@ SCM_DEFINE (scm_make_stack, "make-stack", 1, 0, 1,
else if (SCM_VM_FRAME_P (obj))
frame = obj;
else if (SCM_CONTINUATIONP (obj))
/* FIXME: Narrowing to prompt tags should narrow with respect to the prompts
that were in place when the continuation was captured. */
frame = scm_i_continuation_to_frame (obj);
else
{
@ -224,20 +286,16 @@ SCM_DEFINE (scm_make_stack, "make-stack", 1, 0, 1,
if (scm_is_false (frame))
return SCM_BOOL_F;
/* Get ID of the stack corresponding to the given frame. */
id = stack_id_with_fp (frame, &id_fp);
/* Count number of frames. Also get stack id tag and check whether
there are more stackframes than we want to record
(SCM_BACKTRACE_MAXDEPTH). */
id = SCM_BOOL_F;
maxp = 0;
n = stack_depth (frame, id_fp);
n = stack_depth (frame);
/* Make the stack object. */
stack = scm_make_struct (scm_stack_type, SCM_INUM0, SCM_EOL);
SCM_SET_STACK_LENGTH (stack, n);
SCM_SET_STACK_ID (stack, id);
SCM_SET_STACK_ID (stack, scm_stack_id (obj));
SCM_SET_STACK_FRAME (stack, frame);
/* Narrow the stack according to the arguments given to scm_make_stack. */
@ -258,9 +316,9 @@ SCM_DEFINE (scm_make_stack, "make-stack", 1, 0, 1,
narrow_stack (stack,
scm_is_integer (inner_cut) ? scm_to_int (inner_cut) : n,
scm_is_integer (inner_cut) ? 0 : inner_cut,
scm_is_integer (inner_cut) ? SCM_BOOL_T : inner_cut,
scm_is_integer (outer_cut) ? scm_to_int (outer_cut) : n,
scm_is_integer (outer_cut) ? 0 : outer_cut);
scm_is_integer (outer_cut) ? SCM_BOOL_T : outer_cut);
n = SCM_STACK_LENGTH (stack);
}
@ -277,44 +335,26 @@ SCM_DEFINE (scm_stack_id, "stack-id", 1, 0, 0,
"Return the identifier given to @var{stack} by @code{start-stack}.")
#define FUNC_NAME s_scm_stack_id
{
SCM frame, *id_fp;
if (scm_is_eq (stack, SCM_BOOL_T))
if (scm_is_eq (stack, SCM_BOOL_T)
/* FIXME: frame case assumes frame still live on the stack, and no
intervening start-stack. Hmm... */
|| SCM_VM_FRAME_P (stack))
{
struct scm_vm *vp = SCM_VM_DATA (scm_the_vm ());
frame = scm_c_make_frame (scm_the_vm (), vp->fp, vp->sp, vp->ip, 0);
/* Fetch most recent start-stack tag. */
SCM stacks = scm_fluid_ref (scm_sys_stacks);
return scm_is_pair (stacks) ? scm_caar (stacks) : SCM_BOOL_F;
}
else if (SCM_VM_FRAME_P (stack))
frame = stack;
else if (SCM_CONTINUATIONP (stack))
frame = scm_i_continuation_to_frame (stack);
/* FIXME: implement me */
return SCM_BOOL_F;
else
{
SCM_WRONG_TYPE_ARG (SCM_ARG1, stack);
/* not reached */
}
return stack_id_with_fp (frame, &id_fp);
}
#undef FUNC_NAME
static SCM
stack_id_with_fp (SCM frame, SCM **fp)
{
SCM holder = SCM_VM_FRAME_STACK_HOLDER (frame);
if (SCM_VM_CONT_P (holder))
{
*fp = NULL;
return SCM_BOOL_F;
}
else
{
*fp = NULL;
return SCM_BOOL_F;
}
}
SCM_DEFINE (scm_stack_ref, "stack-ref", 2, 0, 0,
(SCM stack, SCM index),
"Return the @var{index}'th frame from @var{stack}.")
@ -347,6 +387,9 @@ SCM_DEFINE (scm_stack_length, "stack-length", 1, 0, 0,
void
scm_init_stacks ()
{
scm_sys_stacks = scm_make_fluid ();
scm_c_define ("%stacks", scm_sys_stacks);
scm_stack_type = scm_make_vtable (scm_from_locale_string (SCM_STACK_LAYOUT),
SCM_UNDEFINED);
scm_set_struct_vtable_name_x (scm_stack_type,

View file

@ -1030,7 +1030,7 @@ If there is no handler at all, Guile prints an error and then exits."
;;; {The interpreter stack}
;;;
(define %stacks (make-fluid))
;; %stacks defined in stacks.c
(define (%start-stack tag thunk)
(let ((prompt-tag (make-prompt-tag "start-stack")))
(call-with-prompt
@ -2742,7 +2742,8 @@ module '(ice-9 q) '(make-q q-length))}."
(define (set-repl-prompt! v) (set! scm-repl-prompt v))
(define (default-pre-unwind-handler key . args)
(save-stack 1)
;; Narrow by two more frames: this one, and the throw handler.
(save-stack 2)
(apply throw key args))
(begin-deprecated
@ -2839,28 +2840,25 @@ module '(ice-9 q) '(make-q q-length))}."
;;(define the-last-stack (make-fluid)) Defined by scm_init_backtrace ()
(define before-signal-stack (make-fluid))
;; FIXME: stack-saved? is broken in the presence of threads.
(define stack-saved? #f)
(define (save-stack . narrowing)
(or stack-saved?
(cond ((not (memq 'debug (debug-options-interface)))
(fluid-set! the-last-stack #f)
(set! stack-saved? #t))
(else
(fluid-set!
the-last-stack
(case (stack-id #t)
((repl-stack)
(apply make-stack #t save-stack primitive-eval #t 0 narrowing))
((load-stack)
(apply make-stack #t save-stack 0 #t 0 narrowing))
((#t)
(apply make-stack #t save-stack 0 1 narrowing))
(else
(let ((id (stack-id #t)))
(and (procedure? id)
(apply make-stack #t save-stack id #t 0 narrowing))))))
(set! stack-saved? #t)))))
(if (not stack-saved?)
(begin
(let ((stacks (fluid-ref %stacks)))
(fluid-set! the-last-stack
;; (make-stack obj inner outer inner outer ...)
;;
;; In this case, cut away the make-stack frame, the
;; save-stack frame, and then narrow as specified by the
;; user, delimited by the nearest start-stack invocation,
;; if any.
(apply make-stack #t
2
(if (pair? stacks) (cdar stacks) 0)
narrowing)))
(set! stack-saved? #t))))
(define before-error-hook (make-hook))
(define after-error-hook (make-hook))

View file

@ -96,27 +96,21 @@
x))))
(frame-bindings frame))))))
(define* (collect-frames frame #:key count)
(cond
((not count)
(let lp ((frame frame) (out '()))
(if (not frame)
out
(lp (frame-previous frame) (cons frame out)))))
;; should also have a from-end option, either via negative count or
;; another kwarg
((>= count 0)
(let lp ((frame frame) (out '()) (count count))
(if (or (not frame) (zero? count))
out
(lp (frame-previous frame) (cons frame out) (1- count)))))))
(define* (print-frames frames #:optional (port (current-output-port))
#:key (start-index (1- (length frames))) (width 72)
(full? #f))
(let lp ((frames frames) (i start-index) (last-file ""))
(if (pair? frames)
(let* ((frame (car frames))
(define* (print-frames frames
#:optional (port (current-output-port))
#:key (width 72) (full? #f) (forward? #f) count)
(let* ((len (vector-length frames))
(lower-idx (if (or (not count) (positive? count))
0
(max 0 (+ len count))))
(upper-idx (if (and count (negative? count))
(1- len)
(1- (if count (min count len) len))))
(inc (if forward? 1 -1)))
(let lp ((i (if forward? lower-idx upper-idx))
(last-file ""))
(if (<= lower-idx i upper-idx)
(let* ((frame (vector-ref frames i))
(source (frame-source frame))
(file (and source
(or (source:file source)
@ -129,7 +123,7 @@
(if full?
(print-locals frame #:width width
#:per-line-prefix " "))
(lp (cdr frames) (1- i) (or file last-file))))))
(lp (+ i inc) (or file last-file)))))))
;;;
@ -150,31 +144,22 @@
(set! (prop vm) debugger)
debugger)))))
(define* (run-debugger frame #:optional (vm (the-vm)))
(define* (run-debugger stack frames i #:optional (vm (the-vm)))
(let* ((db (vm-debugger vm))
(level (debugger-level db)))
(dynamic-wind
(lambda () (set! (debugger-level db) (1+ level)))
(lambda () (debugger-repl db frame))
(lambda () (debugger-repl db stack frames i))
(lambda () (set! (debugger-level db) level)))))
(define (debugger-repl db frame)
(let ((top frame)
(cur frame)
(index 0)
(define (debugger-repl db stack frames index)
(let ((top (vector-ref frames 0))
(cur (vector-ref frames index))
(level (debugger-level db))
(last #f))
(define (frame-index frame)
(let lp ((idx 0) (walk top))
(if (= (frame-return-address frame) (frame-return-address walk))
idx
(lp (1+ idx) (frame-previous walk)))))
(define (frame-at-index idx)
(let lp ((idx idx) (walk top))
(cond
((not walk) #f)
((zero? idx) walk)
(else (lp (1- idx) (frame-previous walk))))))
(and (< idx (vector-length frames))
(vector-ref frames idx)))
(define (show-frame)
;; #2 0x009600e0 in do_std_select (args=0xbfffd9e0) at threads.c:1668
;; 1668 select (select_args->nfds,
@ -214,30 +199,31 @@
(define-command ((commands backtrace bt) #:optional count
#:key (width 72) full?)
"Print a backtrace of all stack frames, or innermost COUNT frames."
(print-frames (collect-frames top #:count count)
"Print a backtrace of all stack frames, or innermost COUNT frames.
If COUNT is negative, the last COUNT frames will be shown."
(print-frames frames
#:count count
#:width width
#:full? full?))
(define-command ((commands up) #:optional (count 1))
"Select and print stack frames that called this one.
An argument says how many frames up to go"
(if (or (not (integer? count)) (<= count 0))
(format #t "Invalid argument to `up': expected a positive integer for COUNT.~%")
(let lp ((n count))
(cond
((zero? n) (show-frame))
((frame-previous cur)
=> (lambda (new)
(set! cur new)
(set! index (1+ index))
(lp (1- n))))
((= n count)
((or (not (integer? count)) (<= count 0))
(format #t "Invalid argument to `up': expected a positive integer for COUNT.~%"))
((>= (+ count index) (vector-length frames))
(cond
((= index (1- (vector-length frames)))
(format #t "Already at outermost frame.\n"))
(else
(format #t "Reached outermost frame after walking ~a frames.\n"
(- count n))
(show-frame))))))
(set! index (1- (vector-length frames)))
(set! cur (vector-ref frames index))
(show-frame))))
(else
(set! index (+ count index))
(set! cur (vector-ref frames index))
(show-frame))))
(define-command ((commands down) #:optional (count 1))
"Select and print stack frames called by this one.
@ -245,11 +231,17 @@ An argument says how many frames down to go"
(cond
((or (not (integer? count)) (<= count 0))
(format #t "Invalid argument to `down': expected a positive integer for COUNT.~%"))
((= index 0)
(format #t "Already at innermost frame.~%"))
((< (- index count) 0)
(cond
((zero? index)
(format #t "Already at innermost frame.\n"))
(else
(set! index (max (- index count) 0))
(set! cur (frame-at-index index))
(set! index 0)
(set! cur (vector-ref frames index))
(show-frame))))
(else
(set! index (- index count))
(set! cur (vector-ref frames index))
(show-frame))))
(define-command ((commands frame f) #:optional idx)
@ -377,8 +369,23 @@ With an argument, select a frame by index, then show it."
;; hm, trace via reassigning global vars. tricksy.
;; (state associated with vm ?)
(define (stack->vector stack)
(let* ((len (stack-length stack))
(v (make-vector len)))
(if (positive? len)
(let lp ((i 0) (frame (stack-ref stack 0)))
(if (< i len)
(begin
(vector-set! v i frame)
(lp (1+ i) (frame-previous frame))))))
v))
(define (debug-pre-unwind-handler key . args)
(let ((stack (make-stack #t 2)))
;; Narrow the stack by three frames: make-stack, this one, and the throw
;; handler.
(cond
((make-stack #t 3) =>
(lambda (stack)
(pmatch args
((,subr ,msg ,args . ,rest)
(format #t "Throw to key `~a':\n" key)
@ -386,6 +393,12 @@ With an argument, select a frame by index, then show it."
(else
(format #t "Throw to key `~a' with args `~s'." key args)))
(format #t "Entering the debugger. Type `bt' for a backtrace or `c' to continue.\n")
(run-debugger (stack-ref stack 0)))
(run-debugger stack
(stack->vector
;; by default, narrow to the most recent start-stack
(make-stack (stack-ref stack 0) 0
(and (pair? (fluid-ref %stacks))
(cdar (fluid-ref %stacks)))))
0))))
(save-stack debug-pre-unwind-handler)
(apply throw key args))