mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
like this:
mdb> stack
0 pred mutrec.q1/3-0 (det) (mutrec.m:133)
1 ┌ 2* pred mutrec.p2/3-0 (det) (mutrec.m:106 and others)
3 │ pred mutrec.p3/3-0 (det) (mutrec.m:122)
4 │ 2* pred mutrec.p2/3-0 (det) (mutrec.m:102 and others)
6 └ pred mutrec.p3/3-0 (det) (mutrec.m:122)
7 2* pred mutrec.p1/3-0 (det) (mutrec.m:82 and others)
9 pred mutrec.test/2-0 (det) (mutrec.m:42)
10 pred mutrec.main/2-0 (det) (mutrec.m:33)
This implements a suggestion by Peter Wang from *April 2012*.
runtime/mercury_stack_trace.h:
Add a new field to the data structure that represents each line
in a stack trace. This field represents the string, if any,
that implements the boxes around the calls in the clique.
This will be two characters, a box character and a space,
e.g. for the call at level 6, it will be "└ ".
runtime/mercury_stack_trace.c:
Use the new capability to add boxes to calls in cliques,
and equal-length padding to the calls that are not in cliques
but which share stack traces with calls that are.
Initialize some variables closer to their first use.
Group some related statements together. Add some comments.
Delete some already-acted-upon TODOs.
tests/debugger/mutrec.exp:
tests/debugger/mutrec_higher_order.exp:
Expect the updated clique markers.
NEWS.md:
doc/user_guide.texi:
Document the change.
413 lines
18 KiB
C
413 lines
18 KiB
C
// vim: ts=4 sw=4 expandtab ft=c
|
|
|
|
// Copyright (C) 1998-2001,2003-2006,2008,2011-2012 The University of Melbourne.
|
|
// Copyright (C) 2014-2016, 2018 The Mercury team.
|
|
// This file is distributed under the terms specified in COPYING.LIB.
|
|
|
|
#ifndef MERCURY_STACK_TRACE_H
|
|
#define MERCURY_STACK_TRACE_H
|
|
|
|
#include "mercury_regs.h"
|
|
#include "mercury_stack_layout.h"
|
|
#include <stdio.h>
|
|
|
|
// mercury_stack_trace.h:
|
|
//
|
|
// Definitions for use by the stack tracing.
|
|
|
|
typedef MR_Unsigned MR_FrameLimit;
|
|
typedef MR_Unsigned MR_SpecLineLimit;
|
|
typedef MR_Unsigned MR_Level;
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// MR_dump_stack
|
|
//
|
|
// Given the succip, det stack pointer and current frame, generate a
|
|
// stack dump showing the name of each active procedure on the stack.
|
|
// If include_trace_data data is set, also print the call event number,
|
|
// call sequence number and depth for every traced procedure.
|
|
// NOTE: MR_dump_stack will assume that the succip is for the topmost
|
|
// stack frame. If you call MR_dump_stack from some foreign_proc,
|
|
// that may not be the case.
|
|
// Due to some optimizations (or lack thereof) the MR_dump_stack call
|
|
// may end up inside code that has a stack frame allocated, but
|
|
// that has a succip for the previous stack frame.
|
|
// Don't call MR_dump_stack from a Mercury procedure defined by a foreign_proc
|
|
// (calling from other C code in the runtime is probably ok, provided the
|
|
// succip corresponds to the topmost stack frame).
|
|
// (See library/require.m for a technique for calling MR_dump_stack
|
|
// from Mercury).
|
|
// If you need a more convenient way of calling this from Mercury code,
|
|
// it would probably be best to do it using an impure predicate defined
|
|
// using `:- external'.
|
|
|
|
extern void MR_dump_stack(MR_Code *success_pointer,
|
|
MR_Word *det_stack_pointer, MR_Word *current_frame,
|
|
MR_bool include_trace_data);
|
|
|
|
typedef struct {
|
|
// The min_level and max_level fields give the range of call levels
|
|
// covered by this dump record. The frame_count field gives the number
|
|
// of stack frames covered by this dump record.
|
|
//
|
|
// Normally, each call has its own frame, which translates into
|
|
// max_level+1-min_level being equal to frame_count. However,
|
|
// frame_count can be less than this if the procedure has tail
|
|
// recursion events. However, frame_count does not have to be one
|
|
// for such procedures, since not all recursive calls are tail
|
|
// recursive calls.
|
|
//
|
|
// If include_trace_data is TRUE, frame_count should be 1.
|
|
//
|
|
// If the clique frame marker field is non-NULL, then it points to
|
|
// a short (two-character) string that indicates whether the call or calls
|
|
// denoted by this structure is inside a clique or not, and if it is,
|
|
// then whether it the first call in the clique, the last call in the
|
|
// clique, or neither. Note that the string will be UTF-8 encoded, and
|
|
// will often contain non-ASCII characters.
|
|
|
|
const MR_ProcLayout *MR_sdi_proc_layout;
|
|
MR_Level MR_sdi_min_level;
|
|
MR_Level MR_sdi_max_level;
|
|
MR_Unsigned MR_sdi_num_frames;
|
|
const char *MR_sdi_filename;
|
|
int MR_sdi_linenumber;
|
|
MR_bool MR_sdi_context_mismatch;
|
|
const char *MR_sdi_clique_frame_marker;
|
|
|
|
// These fields are meaningful only if include_trace_data is TRUE.
|
|
MR_Word *MR_sdi_base_sp;
|
|
MR_Word *MR_sdi_base_curfr;
|
|
const char *MR_sdi_goal_path;
|
|
} MR_StackFrameDumpInfo;
|
|
|
|
typedef void (*MR_PrintStackRecord)(FILE *fp,
|
|
MR_bool include_trace_data,
|
|
const MR_StackFrameDumpInfo *frame_dump_info);
|
|
|
|
// MR_dump_stack_from_layout
|
|
//
|
|
// This function does the same job and makes the same assumptions
|
|
// as MR_dump_stack, but instead of the succip, it takes the label
|
|
// layout of the current point in the current procedure as input.
|
|
// It also takes a parameter that tells it where to put the stack dump
|
|
// and flags that say whether to include execution trace data and/or
|
|
// line numbers. If limit is nonzero, dumps at most limit frames.
|
|
//
|
|
// If the entire wanted part of the stack was printed successfully,
|
|
// the return value is NULL; otherwise, it is a string indicating
|
|
// why the dump was cut short.
|
|
|
|
extern const char *MR_dump_stack_from_layout(FILE *fp,
|
|
const MR_LabelLayout *label_layout,
|
|
MR_Word *det_stack_pointer,
|
|
MR_Word *current_frame,
|
|
MR_bool include_trace_data,
|
|
MR_bool include_contexts,
|
|
MR_FrameLimit frame_limit,
|
|
MR_SpecLineLimit line_limit,
|
|
MR_PrintStackRecord print_stack_record);
|
|
|
|
extern const char *MR_dump_stack_from_layout_clique(FILE *fp,
|
|
const MR_LabelLayout *label_layout,
|
|
MR_Word *det_stack_pointer,
|
|
MR_Word *current_frame,
|
|
MR_bool include_trace_data,
|
|
MR_bool include_contexts,
|
|
MR_bool detect_cliques,
|
|
MR_SpecLineLimit clique_line_limit,
|
|
MR_FrameLimit frame_limit,
|
|
MR_SpecLineLimit line_limit,
|
|
MR_PrintStackRecord print_stack_record);
|
|
|
|
// MR_dump_nondet_stack
|
|
//
|
|
// This function dumps the control slots of the nondet stack.
|
|
// If frame_limit is nonzero, dumps at most limit frames.
|
|
// If line_limit is nonzero, dumps at most limit lines.
|
|
// The output format is not meant to be intelligible to non-implementors.
|
|
|
|
extern void MR_dump_nondet_stack(FILE *fp,
|
|
MR_FrameLimit frame_limit, MR_SpecLineLimit line_limit,
|
|
MR_Word *maxfr);
|
|
|
|
// MR_dump_nondet_stack_from_layout
|
|
//
|
|
// This function dumps the nondet stack.
|
|
// If frame_limit is nonzero, dumps at most limit frames.
|
|
// If line_limit is nonzero, dumps at most limit lines.
|
|
// The output format is not meant to be intelligible to non-implementors.
|
|
|
|
extern void MR_dump_nondet_stack_from_layout(FILE *fp,
|
|
MR_FrameLimit frame_limit, MR_SpecLineLimit line_limit,
|
|
MR_Word *maxfr, const MR_LabelLayout *label_layout,
|
|
MR_Word *base_sp, MR_Word *base_curfr);
|
|
|
|
// MR_traverse_nondet_stack_from_layout
|
|
//
|
|
// This function traverses the nondet stack, calling the specified
|
|
// function for each frame.
|
|
|
|
typedef void MR_TraverseNondetFrameFunc(void *user_data,
|
|
const MR_LabelLayout *layout, MR_Word *base_sp,
|
|
MR_Word *base_curfr);
|
|
|
|
extern void MR_traverse_nondet_stack_from_layout(
|
|
MR_Word *maxfr, const MR_LabelLayout *label_layout,
|
|
MR_Word *base_sp, MR_Word *base_curfr,
|
|
MR_TraverseNondetFrameFunc *traverse_frame_func,
|
|
void *traverse_frame_func_data);
|
|
|
|
// MR_find_clique_entry
|
|
//
|
|
// Walk the stack from the current event to the stack frame of main.
|
|
// The initial part of this walk visits the stack frames of procedures
|
|
// that are mutually recursive with the current event's procedure;
|
|
// the rest of the walk visits the frames of other procedures.
|
|
// This function find the boundary between these two parts.
|
|
//
|
|
// If we cannot walk all the way to main (e.g. because some stack frames
|
|
// have no layout information, or because the stack does not have the required
|
|
// depth), we return a pointer to an error message, and neither
|
|
// *clique_entry_level nor *first_outside_ancestor_level will be meaningful.
|
|
//
|
|
// If we can walk all the way to main, then we will set *clique_entry_level
|
|
// to be the level on the stack (in the sense of a number you can give to
|
|
// MR_find_nth_ancestor) of the stack frame that is in the initial mutually
|
|
// recursive group, but whose caller is not, and it will set
|
|
// *first_outside_ancestor_level to the level of the caller, unless there
|
|
// is no such caller, in which case we set *first_outside_ancestor_level
|
|
// to a negative number.
|
|
//
|
|
// Either clique_entry_level or first_outside_ancestor_level may be NULL,
|
|
// if the caller does not need one or other of these numbers.
|
|
|
|
extern const char *MR_find_clique_entry(
|
|
const MR_LabelLayout *label_layout,
|
|
MR_Word *det_stack_pointer, MR_Word *current_frame,
|
|
int *clique_entry_level,
|
|
int *first_outside_ancestor_level);
|
|
|
|
// MR_find_nth_ancestor
|
|
//
|
|
// Return the layout structure of the return label of the call
|
|
// ancestor_level levels above the current call. Label_layout
|
|
// tells us how to decipher the stack of the current call, while
|
|
// *stack_trace_sp and *stack_trace_curfr tell us where it is.
|
|
// On return, *stack_trace_sp and *stack_trace_curfr will be
|
|
// set up to match the specified ancestor.
|
|
//
|
|
// If the required stack walk is not possible (e.g. because some
|
|
// stack frames have no layout information, or because the stack
|
|
// does not have the required depth), the return value will be NULL,
|
|
// and problem will point to an error message.
|
|
|
|
extern const MR_LabelLayout *MR_find_nth_ancestor(
|
|
const MR_LabelLayout *label_layout,
|
|
MR_Level ancestor_level, MR_Word **stack_trace_sp,
|
|
MR_Word **stack_trace_curfr,
|
|
MR_Level *actual_level_ptr, const char **problem);
|
|
|
|
// MR_stack_walk_step
|
|
//
|
|
// This function takes the entry_layout for the current stack
|
|
// frame (which is the topmost stack frame from the two stack
|
|
// pointers given), and moves down one stack frame, i.e. to the
|
|
// caller's frame, setting the stack pointers to their new levels.
|
|
// The number of times that the topmost stack has been reused
|
|
// is returned in *reused_frames_ptr.
|
|
//
|
|
// *return_label_layout_ptr will be set to the stack_layout of the
|
|
// continuation label, or NULL if the bottom of the stack has
|
|
// been reached.
|
|
//
|
|
// The meanings of the possible return values from MR_stack_walk_step
|
|
// are as follows:
|
|
//
|
|
// MR_STEP_OK: everything is fine.
|
|
// MR_STEP_ERROR_BEFORE: entry_layout has no valid stack trace info.
|
|
// MR_STEP_ERROR_AFTER: entry_layout has valid stack trace info,
|
|
// but its caller does not.
|
|
//
|
|
// If a MR_stack_walk_step encounters a problem, it will set problem_ptr
|
|
// to point to a string representation of the error.
|
|
//
|
|
// Note that for nondeterministic code, this function will only
|
|
// traverse the success continuations (via MR_succfr),
|
|
// not the frames which represent failure continuations
|
|
// (which would be accessible via MR_redofr).
|
|
|
|
typedef enum {
|
|
MR_STEP_ERROR_BEFORE,
|
|
MR_STEP_ERROR_AFTER,
|
|
MR_STEP_OK
|
|
} MR_StackWalkStepResult;
|
|
|
|
extern MR_StackWalkStepResult
|
|
MR_stack_walk_step(const MR_ProcLayout *entry_layout,
|
|
const MR_LabelLayout **return_label_layout,
|
|
MR_Word **stack_trace_sp_ptr,
|
|
MR_Word **stack_trace_curfr_ptr,
|
|
MR_Unsigned *reused_frames_ptr,
|
|
const char **problem_ptr);
|
|
|
|
// MR_stack_trace_bottom_ip should be set to the address of global_success,
|
|
// the label main/2 goes to on success. Stack dumps terminate when they
|
|
// reach a stack frame whose saved succip slot contains this address.
|
|
|
|
extern MR_Code *MR_stack_trace_bottom_ip;
|
|
|
|
// The bottom nondet stack frame is created by the runtime system before
|
|
// it calls "main/2". MR_nondet_stack_trace_bottom_fr holds the address
|
|
// of this frame. Nondet stack dumps continue only as long as the address
|
|
// of the frame being dumped is above this address.
|
|
//
|
|
// Since address comparisons with MR_nondet_stack_trace_bottom_fr make sense
|
|
// only if the other pointer is also in the same stack segment (if the grade
|
|
// allows stack segments), we record the identity of the nondet stack segment
|
|
// that contains MR_nondet_stack_trace_bottom_fr in
|
|
// MR_nondet_stack_trace_bottom_zone.
|
|
//
|
|
// MR_above_bottom_nondet_frame(fr) returns true for all nondet stack frames
|
|
// that are conceptually above the bottom frame created by the runtime.
|
|
// MR_at_or_above_bottom_nondet_frame(fr) returns true for this bottom
|
|
// frame as well. We use these macros to prevent stack traces descending
|
|
// into nonexistent stack frames *below* the bottom frame.
|
|
|
|
extern MR_Word *MR_nondet_stack_trace_bottom_fr;
|
|
#ifdef MR_STACK_SEGMENTS
|
|
extern MR_MemoryZone *MR_nondet_stack_trace_bottom_zone;
|
|
#endif
|
|
|
|
#ifndef MR_STACK_SEGMENTS
|
|
#define MR_above_bottom_nondet_frame(fr) \
|
|
((fr) > MR_nondet_stack_trace_bottom_fr)
|
|
#define MR_at_or_above_bottom_nondet_frame(fr) \
|
|
((fr) >= MR_nondet_stack_trace_bottom_fr)
|
|
#else
|
|
#define MR_above_bottom_nondet_frame(fr) \
|
|
(!MR_in_zone((fr), MR_nondet_stack_trace_bottom_zone) \
|
|
|| ((fr) > MR_nondet_stack_trace_bottom_fr))
|
|
#define MR_at_or_above_bottom_nondet_frame(fr) \
|
|
(!MR_in_zone((fr), MR_nondet_stack_trace_bottom_zone) \
|
|
|| ((fr) >= MR_nondet_stack_trace_bottom_fr))
|
|
#endif
|
|
|
|
// The different Mercury determinisms are internally represented by integers.
|
|
// This array gives the correspondence with the internal representation and
|
|
// the names that are usually used to denote determinisms.
|
|
|
|
extern const char *MR_detism_names[];
|
|
|
|
// MR_find_context attempts to look up the file name and line number
|
|
// corresponding to a label identified by its layout structure. If successful,
|
|
// it fills in *fileptr and *lineptr accordingly, and returns MR_TRUE;
|
|
// otherwise, it returns MR_FALSE.
|
|
|
|
extern MR_bool MR_find_context(const MR_LabelLayout *label,
|
|
const char **fileptr, int *lineptr);
|
|
|
|
// MR_print_call_trace_info prints the call event number, call sequence number
|
|
// and call depth of the call stored in the stack frame of the procedure
|
|
// identified by the given proc layout. It requires the procedure to have
|
|
// trace layout information, and the relevant one of base_sp and base_curfr
|
|
// to be non-NULL, since these numbers are stored in stack slots.
|
|
//
|
|
// MR_maybe_print_call_trace_info calls MR_print_call_trace_info if
|
|
// include_trace_data is MR_TRUE and the other conditions required by
|
|
// MR_print_call_trace_info are satisfied.
|
|
|
|
extern void MR_print_call_trace_info(FILE *fp,
|
|
const MR_ProcLayout *proc_layout,
|
|
MR_Word *base_sp, MR_Word *base_curfr);
|
|
|
|
extern void MR_maybe_print_call_trace_info(FILE *fp,
|
|
MR_bool include_trace_data,
|
|
const MR_ProcLayout *proc_layout,
|
|
MR_Word *base_sp, MR_Word *base_curfr);
|
|
|
|
// MR_print_proc_id prints an identification of the given procedure,
|
|
// consisting of "pred" or "func", module name, pred or func name, arity,
|
|
// mode number and determinism. It does not output a newline, so that
|
|
// the caller can put something else after the procedure id on the same line.
|
|
|
|
extern void MR_print_proc_id(FILE *fp, const MR_ProcLayout *entry);
|
|
|
|
// MR_print_pred_id prints everything that MR_print_proc_id does, except
|
|
// the mode number and determinism.
|
|
|
|
extern void MR_print_pred_id(FILE *fp, const MR_ProcLayout *entry);
|
|
|
|
// MR_print_proc_spec prints a string that uniquely specifies the given
|
|
// procedure to the debugger.
|
|
|
|
extern void MR_print_proc_spec(FILE *fp,
|
|
const MR_ProcLayout *entry);
|
|
|
|
// MR_print_proc_separate prints a string that uniquely specifies the given
|
|
// procedure to the debugger, with each component of a name in a separate field
|
|
// to allow the output to be processed by tools (e.g. awk scripts).
|
|
|
|
extern void MR_print_proc_separate(FILE *fp,
|
|
const MR_ProcLayout *entry);
|
|
|
|
// MR_print_proc_id_trace_and_context prints an identification of the given
|
|
// procedure, together with call trace information (if available), a context
|
|
// within the procedure, and possibly a context identifying the caller.
|
|
// The pos argument says where (if anywhere) the contexts should appear;
|
|
// the user_event_context argument says what parts of the context (if any)
|
|
// to print for user defined events.
|
|
|
|
typedef enum {
|
|
MR_CONTEXT_NOWHERE,
|
|
MR_CONTEXT_BEFORE,
|
|
MR_CONTEXT_AFTER,
|
|
MR_CONTEXT_PREVLINE,
|
|
MR_CONTEXT_NEXTLINE
|
|
} MR_ContextPosition;
|
|
|
|
typedef enum {
|
|
MR_USER_EVENT_CONTEXT_NONE,
|
|
MR_USER_EVENT_CONTEXT_FILE,
|
|
MR_USER_EVENT_CONTEXT_PROC,
|
|
MR_USER_EVENT_CONTEXT_FULL
|
|
} MR_UserEventContext;
|
|
|
|
extern void MR_print_proc_id_trace_and_context(FILE *fp,
|
|
MR_bool include_trace_data, MR_ContextPosition pos,
|
|
MR_UserEventContext user_event_context,
|
|
const MR_ProcLayout *proc_layout,
|
|
const char *maybe_user_event_name,
|
|
MR_Word *base_sp, MR_Word *base_curfr,
|
|
const char *path, const char *filename, int lineno,
|
|
MR_bool print_parent,
|
|
const char *parent_filename, int parent_lineno,
|
|
int indent);
|
|
|
|
// MR_dump_stack_record_print() prints one line of a stack dump.
|
|
|
|
extern void MR_dump_stack_record_print(FILE *fp,
|
|
MR_bool include_trace_data,
|
|
const MR_StackFrameDumpInfo *frame_dump_info);
|
|
|
|
// Find the first call event on the stack whose event number or sequence number
|
|
// is less than or equal to the given event number or sequence number.
|
|
// The level of the call in the stack is returned. This can then be passed to
|
|
// MR_trace_retry as the ancestor_level. If no such call is found then -1 is
|
|
// returned and problem is set to the reason why the call could not be found.
|
|
|
|
typedef enum {
|
|
MR_FIND_FIRST_CALL_BEFORE_SEQ,
|
|
MR_FIND_FIRST_CALL_BEFORE_EVENT
|
|
} MR_FindFirstCallSeqOrEvent;
|
|
|
|
extern int MR_find_first_call_less_eq_seq_or_event(
|
|
MR_FindFirstCallSeqOrEvent seq_or_event,
|
|
MR_Unsigned seq_no_or_event_no,
|
|
const MR_LabelLayout *label_layout,
|
|
MR_Word *det_stack_pointer, MR_Word *current_frame,
|
|
const char **problem);
|
|
|
|
#endif // MERCURY_STACK_TRACE_H
|