mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-11 20:03:28 +00:00
LICENSE:
compiler/handle_options.m:
doc/*.texi:
profiler/mercury_profile.m:
trace/mercury_trace_internal.c:
As above.
1741 lines
58 KiB
C
1741 lines
58 KiB
C
// vim: ts=4 sw=4 expandtab ft=c
|
|
|
|
// Copyright (C) 1998-2012 The University of Melbourne.
|
|
// Copyright (C) 2013-2018 The Mercury team.
|
|
// This file is distributed under the terms specified in COPYING.LIB.
|
|
|
|
// This file contains the top level of the code of the internal, in-process
|
|
// debugger. The functions implementing the commands themselves are in the
|
|
// files mercury_trace_cmd_*.c.
|
|
//
|
|
// Main author: Zoltan Somogyi.
|
|
|
|
#ifndef _GNU_SOURCE
|
|
// For the GNU C library we need to define the following in order to make
|
|
// the declarations for the UNIX98 pseudoterminal functions visible.
|
|
// Note that we need to define this *before* stdlib.h is included.
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include "mercury_imp.h"
|
|
#include "mercury_layout_util.h"
|
|
#include "mercury_array_macros.h"
|
|
#include "mercury_getopt.h"
|
|
#include "mercury_signal.h"
|
|
#include "mercury_builtin_types.h"
|
|
#include "mercury_deep_profiling.h"
|
|
#include "mercury_runtime_util.h"
|
|
|
|
#include "mercury_event_spec.h"
|
|
|
|
#include "mercury_trace.h"
|
|
#include "mercury_trace_internal.h"
|
|
#include "mercury_trace_cmds.h"
|
|
#include "mercury_trace_declarative.h"
|
|
#include "mercury_trace_alias.h"
|
|
#include "mercury_trace_help.h"
|
|
#include "mercury_trace_browse.h"
|
|
#include "mercury_trace_spy.h"
|
|
#include "mercury_trace_tables.h"
|
|
#include "mercury_trace_util.h"
|
|
#include "mercury_trace_vars.h"
|
|
#include "mercury_trace_hold_vars.h"
|
|
#include "mercury_trace_readline.h"
|
|
#include "mercury_trace_source.h"
|
|
#include "mercury_trace_command_queue.h"
|
|
|
|
#include "mercury_trace_cmd_forward.h"
|
|
#include "mercury_trace_cmd_backward.h"
|
|
#include "mercury_trace_cmd_browsing.h"
|
|
#include "mercury_trace_cmd_breakpoint.h"
|
|
#include "mercury_trace_cmd_queries.h"
|
|
#include "mercury_trace_cmd_table_io.h"
|
|
#include "mercury_trace_cmd_parameter.h"
|
|
#include "mercury_trace_cmd_help.h"
|
|
#include "mercury_trace_cmd_dd.h"
|
|
#include "mercury_trace_cmd_misc.h"
|
|
#include "mercury_trace_cmd_exp.h"
|
|
#include "mercury_trace_cmd_developer.h"
|
|
|
|
#include "mdb.browse.mh"
|
|
#include "mdb.listing.mh"
|
|
#include "mdb.diff.mh"
|
|
#include "mdb.browser_info.mh"
|
|
#include "mdb.declarative_execution.mh"
|
|
#include "mdbcomp.program_representation.mh"
|
|
#include "mdbcomp.slice_and_dice.mh"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
|
|
#ifdef MR_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_WAIT_H
|
|
#include <sys/wait.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_TERMIOS_H
|
|
#include <termios.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_STROPTS_H
|
|
#include <sys/stropts.h>
|
|
#endif
|
|
|
|
// Special characters used in mdb commands.
|
|
#define DOUBLE_QUOTE_CHAR '"'
|
|
#define SINGLE_QUOTE_CHAR '\''
|
|
#define ESCAPE_CHAR '\\'
|
|
|
|
// The initial size of arrays of words.
|
|
#define MR_INIT_WORD_COUNT 20
|
|
|
|
// An upper bound on the maximum number of characters in a number.
|
|
// If a number has more than this many chars, the user is in trouble.
|
|
#define MR_NUMBER_LEN 80
|
|
|
|
#define MDBRC_FILENAME ".mdbrc"
|
|
#define DEFAULT_MDBRC_FILENAME "mdbrc"
|
|
|
|
// XXX We should consider whether all the static variables in this module
|
|
// should be thread local.
|
|
|
|
// Debugger I/O streams.
|
|
// Replacements for stdin/stdout/stderr respectively.
|
|
//
|
|
// The distinction between MR_mdb_out and MR_mdb_err is analogous to
|
|
// the distinction between stdout and stderr: ordinary output, including
|
|
// information messages about conditions which are not errors, should
|
|
// go to MR_mdb_out, but error messages should go to MR_mdb_err.
|
|
//
|
|
// Note that MR_mdb_out and MR_mdb_err may both write to the same
|
|
// file, so we need to be careful to ensure that buffering does
|
|
// not stuff up the interleaving of error messages and ordinary output.
|
|
// To ensure this, we do two things:
|
|
//
|
|
// - MR_mdb_err is unbuffered
|
|
// - we always fflush(MR_mdb_out) before writing to MR_mdb_err
|
|
|
|
FILE *MR_mdb_in;
|
|
FILE *MR_mdb_out;
|
|
FILE *MR_mdb_err;
|
|
|
|
// MR_have_mdb_window and MR_mdb_window_pid are set by mercury_trace_internal.c ** after the xterm window for mdb has been spawned. The window process is
|
|
// killed by MR_trace_internal_kill_mdb_window(), which is called by
|
|
// MR_trace_final() through the MR_trace_shutdown() pointer. This indirect call
|
|
// is used to avoid references to the non-ISO header file <unistd.h>
|
|
// (for pid_t) in the runtime headers.
|
|
|
|
static MR_bool MR_have_mdb_window = MR_FALSE;
|
|
static pid_t MR_mdb_window_pid = 0;
|
|
|
|
// The details of the source server, if any.
|
|
|
|
MR_TraceSourceServer MR_trace_source_server =
|
|
{ NULL, NULL, MR_FALSE };
|
|
|
|
MR_bool MR_trace_internal_interacting = MR_FALSE;
|
|
|
|
MR_bool MR_trace_echo_queue_commands = MR_FALSE;
|
|
|
|
static void MR_trace_internal_ensure_init(void);
|
|
static MR_bool MR_trace_internal_create_mdb_window(void);
|
|
static void MR_trace_internal_kill_mdb_window(void);
|
|
static void MR_trace_internal_init_from_env(void);
|
|
static void MR_trace_internal_init_from_local(void);
|
|
static void MR_trace_internal_init_from_home_dir(void);
|
|
static MR_Next MR_trace_debug_cmd(char *line, MR_TraceCmdInfo *cmd,
|
|
MR_EventInfo *event_info, MR_Code **jumpaddr);
|
|
|
|
static MR_TraceCmdFunc MR_trace_handle_cmd;
|
|
|
|
static void MR_mdb_print_proc_id_and_nl(void *data,
|
|
const MR_ProcLayout *entry_layout);
|
|
static int MR_trace_var_print_list(MR_SpyPrintList print_list);
|
|
|
|
static const char *MR_trace_parse_line(char *line, char ***words,
|
|
int *word_max, int *word_count);
|
|
static const char *MR_trace_break_into_words(char *line, char ***words_ptr,
|
|
int *word_max_ptr, int *word_count_ptr);
|
|
static const char *MR_trace_break_off_one_word(char *line, int char_pos,
|
|
int *new_char_pos_ptr);
|
|
static MR_bool MR_trace_continue_line(char *ptr, MR_bool *single_quoted,
|
|
MR_bool *double_quoted);
|
|
static MR_Code *MR_trace_event_internal_report(MR_TraceCmdInfo *cmd,
|
|
MR_SpyPrintList print_list,
|
|
MR_EventInfo *event_info);
|
|
|
|
static char *MR_trace_command_completer_next(const char *word,
|
|
size_t word_len, MR_CompleterData *data);
|
|
|
|
MR_SavedDebugState MR_saved_debug_state;
|
|
|
|
MR_Code *
|
|
MR_trace_event_internal(MR_TraceCmdInfo *cmd, MR_bool interactive,
|
|
MR_SpyPrintList print_list, MR_EventInfo *event_info, const char *msg)
|
|
{
|
|
MR_Code *jumpaddr;
|
|
char *line;
|
|
MR_Next res;
|
|
|
|
if (! interactive) {
|
|
return MR_trace_event_internal_report(cmd, print_list, event_info);
|
|
}
|
|
|
|
// We want to make sure that the Mercury code used to implement some
|
|
// of the debugger's commands (a) doesn't generate any trace events,
|
|
// (b) doesn't generate any unwanted debugging output, and (c) doesn't
|
|
// do any I/O tabling.
|
|
|
|
MR_turn_off_debug(&MR_saved_debug_state, MR_FALSE);
|
|
|
|
MR_trace_internal_ensure_init();
|
|
MR_trace_browse_ensure_init();
|
|
MR_trace_listing_path_ensure_init();
|
|
|
|
if (MR_spy_point_cond_problem != NULL) {
|
|
fprintf(MR_mdb_err, "mdb: couldn't evaluate break point condition\n");
|
|
MR_print_spy_cond(MR_mdb_err, MR_spy_point_cond_bad);
|
|
fprintf(MR_mdb_err, ": %s.\n", MR_spy_point_cond_problem);
|
|
MR_spy_point_cond_bad = NULL;
|
|
MR_spy_point_cond_problem = NULL;
|
|
}
|
|
|
|
if (msg != NULL) {
|
|
fprintf(MR_mdb_out, "%s", msg);
|
|
}
|
|
|
|
MR_trace_event_print_internal_report(event_info);
|
|
MR_trace_maybe_sync_source_window(event_info, MR_FALSE);
|
|
|
|
MR_trace_init_point_vars(event_info->MR_event_sll,
|
|
event_info->MR_saved_regs, event_info->MR_saved_f_regs,
|
|
event_info->MR_trace_port, MR_print_optionals);
|
|
|
|
(void) MR_trace_var_print_list(print_list);
|
|
|
|
// By default, return where we came from.
|
|
jumpaddr = NULL;
|
|
|
|
do {
|
|
line = MR_trace_get_command("mdb> ", MR_mdb_in, MR_mdb_out);
|
|
res = MR_trace_debug_cmd(line, cmd, event_info, &jumpaddr);
|
|
fflush(MR_mdb_err);
|
|
} while (res == KEEP_INTERACTING);
|
|
|
|
cmd->MR_trace_must_check = (! cmd->MR_trace_strict) ||
|
|
(cmd->MR_trace_print_level != MR_PRINT_LEVEL_NONE);
|
|
|
|
#ifdef MR_TRACE_CHECK_INTEGRITY
|
|
cmd->MR_trace_must_check = cmd->MR_trace_must_check
|
|
|| cmd->MR_trace_check_integrity;
|
|
#endif
|
|
|
|
MR_scroll_next = 0;
|
|
MR_turn_debug_back_on(&MR_saved_debug_state);
|
|
return jumpaddr;
|
|
}
|
|
|
|
static const char MR_trace_banner[] =
|
|
"Melbourne Mercury Debugger, mdb version %s.\n\
|
|
Copyright 1998-2012 The University of Melbourne.\n\
|
|
Copyright 2013-2019 The Mercury team.\n\
|
|
mdb is free software; there is absolutely no warranty for mdb.\n";
|
|
|
|
static FILE *
|
|
MR_try_fopen(const char *filename, const char *mode, FILE *default_file)
|
|
{
|
|
if (filename == NULL) {
|
|
return default_file;
|
|
} else {
|
|
FILE *f;
|
|
char errbuf[MR_STRERROR_BUF_SIZE];
|
|
|
|
f = fopen(filename, mode);
|
|
if (f == NULL) {
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "mdb: error opening `%s': %s\n",
|
|
filename, MR_strerror(errno, errbuf, sizeof(errbuf)));
|
|
return default_file;
|
|
} else {
|
|
return f;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
MR_trace_internal_ensure_init(void)
|
|
{
|
|
static MR_bool MR_trace_internal_initialized = MR_FALSE;
|
|
|
|
if (! MR_trace_internal_initialized) {
|
|
char *env;
|
|
MR_Unsigned n;
|
|
|
|
if (MR_mdb_benchmark_silent) {
|
|
(void) close(1);
|
|
if (open("/dev/null", O_WRONLY) != 1) {
|
|
fprintf(stderr, "cannot silence stdout");
|
|
exit(1);
|
|
}
|
|
|
|
(void) close(2);
|
|
if (open("/dev/null", O_WRONLY) != 2) {
|
|
// There is nowhere to report the error.
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (MR_mdb_in_window) {
|
|
// If opening the window fails, fall back on using
|
|
// MR_mdb_*_filename, or stdin, stdout and stderr.
|
|
|
|
MR_mdb_in_window = MR_trace_internal_create_mdb_window();
|
|
if (! MR_mdb_in_window) {
|
|
MR_mdb_warning("Try `mdb --program-in-window' instead.\n");
|
|
}
|
|
}
|
|
|
|
if (! MR_mdb_in_window) {
|
|
MR_mdb_in = MR_try_fopen(MR_mdb_in_filename, "r", stdin);
|
|
MR_mdb_out = MR_try_fopen(MR_mdb_out_filename, "w", stdout);
|
|
MR_mdb_err = MR_try_fopen(MR_mdb_err_filename, "w", stderr);
|
|
}
|
|
|
|
// Ensure that MR_mdb_err is not buffered.
|
|
setvbuf(MR_mdb_err, NULL, _IONBF, 0);
|
|
|
|
if (getenv("MERCURY_SUPPRESS_MDB_BANNER") == NULL) {
|
|
fprintf(MR_mdb_out, MR_trace_banner, MR_VERSION);
|
|
}
|
|
|
|
if (getenv("MERCURY_DEBUG_ECHO_QUEUE_COMMANDS") != NULL) {
|
|
MR_trace_echo_queue_commands = MR_TRUE;
|
|
}
|
|
|
|
env = getenv("LINES");
|
|
if (env != NULL && MR_trace_is_natural_number(env, &n)) {
|
|
MR_scroll_limit = n;
|
|
}
|
|
|
|
// We call these functions in this order because we want the .mdbrc
|
|
// file in the current (local) directory to be able to override
|
|
// any actions from the system's standard .mdbrc file, and the
|
|
// .mdbrc file (if any) referred to by the current setting of the
|
|
// MERCURY_DEBUGGER_INIT environment to override both.
|
|
|
|
MR_trace_internal_init_from_home_dir();
|
|
MR_trace_internal_init_from_local();
|
|
MR_trace_internal_init_from_env();
|
|
|
|
MR_saved_debug_state.MR_sds_io_tabling_enabled = MR_TRUE;
|
|
MR_io_tabling_phase = MR_IO_TABLING_BEFORE;
|
|
MR_io_tabling_start = MR_IO_ACTION_MAX;
|
|
MR_io_tabling_end = MR_IO_ACTION_MAX;
|
|
|
|
MR_trace_internal_initialized = MR_TRUE;
|
|
}
|
|
}
|
|
|
|
static volatile sig_atomic_t MR_got_alarm = MR_FALSE;
|
|
|
|
static void
|
|
MR_trace_internal_alarm_handler(void)
|
|
{
|
|
MR_got_alarm = MR_TRUE;
|
|
}
|
|
|
|
static MR_bool
|
|
MR_trace_internal_create_mdb_window(void)
|
|
{
|
|
// XXX The code to find and open a pseudo-terminal is nowhere
|
|
// near as portable as I would like, but given the huge variety
|
|
// of methods for allocating pseudo-terminals it will have to do.
|
|
// Most systems seem to be standardising on this method (from UNIX98).
|
|
// See the xterm or expect source for a more complete version
|
|
// (it's a bit too entwined in the rest of the code to just lift
|
|
// it out and use it here).
|
|
//
|
|
// XXX Add support for MS Windows.
|
|
|
|
#if defined(MR_HAVE_OPEN) && defined(O_RDWR) && defined(MR_HAVE_FDOPEN) && \
|
|
defined(MR_HAVE_CLOSE) && defined(MR_HAVE_DUP) && \
|
|
defined(MR_HAVE_DUP2) && defined(MR_HAVE_FORK) && \
|
|
defined(MR_HAVE_EXECLP) && \
|
|
defined(MR_HAVE_GRANTPT) && defined(MR_HAVE_UNLOCKPT) && \
|
|
defined(MR_HAVE_PTSNAME) && defined(MR_HAVE_ACCESS) && defined(F_OK)
|
|
|
|
int master_fd = -1;
|
|
int slave_fd = -1;
|
|
char *slave_name;
|
|
#if defined(MR_HAVE_TERMIOS_H) && defined(MR_HAVE_TCGETATTR) && \
|
|
defined(MR_HAVE_TCSETATTR) && defined(ECHO) && defined(TCSADRAIN)
|
|
struct termios termio;
|
|
#endif
|
|
|
|
// First check whether /dev/ptmx even exists, so that we can give
|
|
// a slightly better error message if it doesn't.
|
|
|
|
if (access("/dev/ptmx", F_OK) != 0) {
|
|
MR_mdb_perror("can't access /dev/ptmx");
|
|
MR_mdb_warning(
|
|
"Sorry, `mdb --window' not supported on this platform.\n");
|
|
return MR_FALSE;
|
|
}
|
|
|
|
// OK, /dev/ptmx exists; now go ahead and open it.
|
|
master_fd = open("/dev/ptmx", O_RDWR);
|
|
if (master_fd == -1 || grantpt(master_fd) == -1
|
|
|| unlockpt(master_fd) == -1)
|
|
{
|
|
MR_mdb_perror("error opening master pseudo-terminal for mdb window");
|
|
close(master_fd);
|
|
return MR_FALSE;
|
|
}
|
|
if ((slave_name = ptsname(master_fd)) == NULL) {
|
|
MR_mdb_perror("error getting name of pseudo-terminal for mdb window");
|
|
close(master_fd);
|
|
return MR_FALSE;
|
|
}
|
|
slave_fd = open(slave_name, O_RDWR);
|
|
if (slave_fd == -1) {
|
|
close(master_fd);
|
|
MR_mdb_perror("opening slave pseudo-terminal for mdb window failed");
|
|
return MR_FALSE;
|
|
}
|
|
|
|
#if defined(MR_HAVE_IOCTL) && defined(I_PUSH)
|
|
// Magic STREAMS incantations to make this work on Solaris.
|
|
ioctl(slave_fd, I_PUSH, "ptem");
|
|
ioctl(slave_fd, I_PUSH, "ldterm");
|
|
ioctl(slave_fd, I_PUSH, "ttcompat");
|
|
#endif
|
|
|
|
#if defined(MR_HAVE_TCGETATTR) && defined(MR_HAVE_TCSETATTR) && \
|
|
defined(ECHO) && defined(TCSADRAIN)
|
|
// Turn off echoing before starting the xterm so that the user doesn't see
|
|
// the window ID printed by xterm on startup (this behaviour is not
|
|
// documented in the xterm manual).
|
|
|
|
tcgetattr(slave_fd, &termio);
|
|
termio.c_lflag &= ~ECHO;
|
|
tcsetattr(slave_fd, TCSADRAIN, &termio);
|
|
#endif
|
|
|
|
MR_mdb_window_pid = fork();
|
|
if (MR_mdb_window_pid == -1) {
|
|
MR_mdb_perror("fork() for mdb window failed");
|
|
close(master_fd);
|
|
close(slave_fd);
|
|
return MR_FALSE;
|
|
} else if (MR_mdb_window_pid == 0) {
|
|
// Child - exec() the xterm.
|
|
|
|
char xterm_arg[50];
|
|
|
|
close(slave_fd);
|
|
|
|
#if defined(MR_HAVE_SETPGID)
|
|
// Put the xterm in a new process group so it won't be killed
|
|
// by SIGINT signals sent to the program.
|
|
|
|
if (setpgid(0, 0) < 0) {
|
|
MR_mdb_perror("setpgid() failed");
|
|
close(master_fd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
// The XX part is required by xterm, but it's not needed for the way
|
|
// we are using xterm (it's meant to be an identifier for the
|
|
// pseudo-terminal). Different versions of xterm use different formats,
|
|
// so it's best to just leave it blank.
|
|
//
|
|
// XXX Some versions of xterm (such as that distributed with
|
|
// XFree86 3.3.6) give a warning about this (but it still works).
|
|
// The latest version distributed with XFree86 4 does not give
|
|
// a warning.
|
|
|
|
sprintf(xterm_arg, "-SXX%d", master_fd);
|
|
|
|
execlp("xterm", "xterm", "-T", "mdb", xterm_arg, NULL);
|
|
MR_mdb_perror("execution of xterm failed");
|
|
exit(EXIT_FAILURE);
|
|
} else {
|
|
// Parent - set up the mdb I/O streams to point to the pseudo-terminal.
|
|
|
|
MR_signal_action old_alarm_action;
|
|
int err_fd = -1;
|
|
int out_fd = -1;
|
|
|
|
MR_mdb_in = MR_mdb_out = MR_mdb_err = NULL;
|
|
MR_have_mdb_window = MR_TRUE;
|
|
|
|
close(master_fd);
|
|
|
|
// Read the first line of output -- this is a window ID written by
|
|
// xterm. The alarm() and associated signal handling is to gracefully
|
|
// handle the case where the xterm failed to start, for example
|
|
// because the DISPLAY variable was invalid. We don't want to restart
|
|
// the read() below if it times out.
|
|
|
|
MR_get_signal_action(SIGALRM, &old_alarm_action,
|
|
"error retrieving alarm handler");
|
|
MR_setup_signal_no_restart(SIGALRM, MR_trace_internal_alarm_handler,
|
|
MR_FALSE, "error setting up alarm handler");
|
|
MR_got_alarm = MR_FALSE;
|
|
alarm(10); // 10 second timeout
|
|
while (1) {
|
|
char c;
|
|
int status;
|
|
|
|
status = read(slave_fd, &c, 1);
|
|
if (status == -1) {
|
|
if (MR_got_alarm) {
|
|
MR_mdb_warning("timeout starting mdb window");
|
|
goto parent_error;
|
|
} else if (!MR_is_eintr(errno)) {
|
|
MR_mdb_perror("error reading from mdb window");
|
|
goto parent_error;
|
|
}
|
|
} else if (status == 0 || c == '\n') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reset the alarm handler.
|
|
alarm(0);
|
|
MR_set_signal_action(SIGALRM, &old_alarm_action,
|
|
"error resetting alarm handler");
|
|
|
|
#if defined(MR_HAVE_TCGETATTR) && defined(MR_HAVE_TCSETATTR) && \
|
|
defined(ECHO) && defined(TCSADRAIN)
|
|
// Restore echoing.
|
|
termio.c_lflag |= ECHO;
|
|
tcsetattr(slave_fd, TCSADRAIN, &termio);
|
|
#endif
|
|
|
|
if ((out_fd = dup(slave_fd)) == -1) {
|
|
MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
|
|
goto parent_error;
|
|
}
|
|
if ((err_fd = dup(slave_fd)) == -1) {
|
|
MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
|
|
goto parent_error;
|
|
}
|
|
|
|
MR_mdb_in = fdopen(slave_fd, "r");
|
|
if (MR_mdb_in == NULL) {
|
|
MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
|
|
goto parent_error;
|
|
}
|
|
MR_mdb_out = fdopen(out_fd, "w");
|
|
if (MR_mdb_out == NULL) {
|
|
MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
|
|
goto parent_error;
|
|
}
|
|
MR_mdb_err = fdopen(err_fd, "w");
|
|
if (MR_mdb_err == NULL) {
|
|
MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
|
|
goto parent_error;
|
|
}
|
|
|
|
MR_have_mdb_window = MR_TRUE;
|
|
MR_trace_shutdown = MR_trace_internal_kill_mdb_window;
|
|
return MR_TRUE;
|
|
|
|
parent_error:
|
|
MR_trace_internal_kill_mdb_window();
|
|
if (MR_mdb_in) {
|
|
fclose(MR_mdb_in);
|
|
}
|
|
|
|
if (MR_mdb_out) {
|
|
fclose(MR_mdb_out);
|
|
}
|
|
|
|
if (MR_mdb_err) {
|
|
fclose(MR_mdb_err);
|
|
}
|
|
|
|
close(slave_fd);
|
|
close(out_fd);
|
|
close(err_fd);
|
|
return MR_FALSE;
|
|
|
|
}
|
|
|
|
#else // !MR_HAVE_OPEN, etc.
|
|
MR_mdb_warning("Sorry, `mdb --window' not supported on this platform.\n");
|
|
return MR_FALSE;
|
|
#endif // !MR_HAVE_OPEN, etc.
|
|
}
|
|
|
|
static void
|
|
MR_trace_internal_kill_mdb_window(void)
|
|
{
|
|
#if defined(MR_HAVE_KILL) && defined(MR_HAVE_WAIT) && defined(SIGTERM)
|
|
if (MR_have_mdb_window) {
|
|
int status;
|
|
status = kill(MR_mdb_window_pid, SIGTERM);
|
|
if (status != -1) {
|
|
do {
|
|
status = wait(NULL);
|
|
if (status == -1 && !MR_is_eintr(errno)) {
|
|
break;
|
|
}
|
|
} while (status != MR_mdb_window_pid);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
MR_trace_internal_init_from_env(void)
|
|
{
|
|
char *init;
|
|
|
|
init = getenv("MERCURY_DEBUGGER_INIT");
|
|
if (init != NULL && strlen(init) > 0) {
|
|
(void) MR_trace_source(init, MR_FALSE, NULL, 0);
|
|
// If the source failed, the error message has been printed.
|
|
}
|
|
}
|
|
|
|
static void
|
|
MR_trace_internal_init_from_local(void)
|
|
{
|
|
FILE *fp;
|
|
const char *init;
|
|
|
|
init = MDBRC_FILENAME;
|
|
if ((fp = fopen(init, "r")) != NULL) {
|
|
MR_trace_source_from_open_file(fp, NULL, 0);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
MR_trace_internal_init_from_home_dir(void)
|
|
{
|
|
char *env;
|
|
char *buf;
|
|
FILE *fp;
|
|
|
|
// XXX This code is too Unix specific.
|
|
|
|
env = getenv("HOME");
|
|
if (env == NULL) {
|
|
return;
|
|
}
|
|
|
|
buf = MR_NEW_ARRAY(char, strlen(env) + strlen(MDBRC_FILENAME) + 2);
|
|
(void) strcpy(buf, env);
|
|
(void) strcat(buf, "/");
|
|
(void) strcat(buf, MDBRC_FILENAME);
|
|
if ((fp = fopen(buf, "r")) != NULL) {
|
|
MR_trace_source_from_open_file(fp, NULL, 0);
|
|
fclose(fp);
|
|
}
|
|
|
|
MR_free(buf);
|
|
}
|
|
|
|
MR_bool
|
|
MR_trace_source(const char *filename, MR_bool ignore_errors,
|
|
char **args, int num_args)
|
|
{
|
|
FILE *fp;
|
|
char errbuf[MR_STRERROR_BUF_SIZE];
|
|
|
|
if ((fp = fopen(filename, "r")) != NULL) {
|
|
MR_trace_source_from_open_file(fp, args, num_args);
|
|
fclose(fp);
|
|
return MR_TRUE;
|
|
}
|
|
|
|
if (! ignore_errors) {
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "%s: %s.\n",
|
|
filename, MR_strerror(errno, errbuf, sizeof(errbuf)));
|
|
}
|
|
|
|
return MR_FALSE;
|
|
}
|
|
|
|
void
|
|
MR_trace_source_from_open_file(FILE *fp, char **args, int num_args)
|
|
{
|
|
char *contents;
|
|
MR_CmdLines *line;
|
|
MR_CmdLines *first_line;
|
|
MR_CmdLines *prev_line;
|
|
|
|
// Invariant: either both first_line and prev_line are NULL, or neither is.
|
|
|
|
first_line = NULL;
|
|
prev_line = NULL;
|
|
|
|
// Insert the sourced commands at the front of the command queue,
|
|
// preserving their order in the sourced file.
|
|
|
|
while ((contents = MR_trace_readline_from_script(fp, args, num_args))
|
|
!= NULL)
|
|
{
|
|
line = MR_NEW(MR_CmdLines);
|
|
line->MR_cmd_line_contents = MR_copy_string(contents);
|
|
line->MR_cmd_line_next = NULL;
|
|
|
|
if (first_line == NULL) {
|
|
first_line = line;
|
|
} else {
|
|
MR_assert(prev_line != NULL);
|
|
prev_line->MR_cmd_line_next = line;
|
|
}
|
|
|
|
prev_line = line;
|
|
}
|
|
|
|
MR_insert_command_lines_at_tail(first_line);
|
|
|
|
MR_trace_internal_interacting = MR_FALSE;
|
|
}
|
|
|
|
void
|
|
MR_trace_do_noop(void)
|
|
{
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "This command is a no-op from this port.\n");
|
|
}
|
|
|
|
void
|
|
MR_trace_do_noop_tail_rec(void)
|
|
{
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err,
|
|
"Due to the reuse of stack frames by tail recursive procedures,\n"
|
|
"this command is a no-op from this port.\n");
|
|
}
|
|
|
|
// This function is just a wrapper for MR_print_proc_id_and_nl,
|
|
// with the first argument type being `void *' rather than `FILE *',
|
|
// so that this function's address can be passed to
|
|
// MR_process_matching_procedures().
|
|
|
|
MR_Next
|
|
MR_trace_cmd_shell(char **words, int word_count, MR_TraceCmdInfo *cmd,
|
|
MR_EventInfo *event_info, MR_Code **jumpaddr)
|
|
{
|
|
char* command_string;
|
|
size_t command_string_length;
|
|
int word_num;
|
|
|
|
command_string_length = 1;
|
|
for (word_num = 1; word_num < word_count; word_num++) {
|
|
command_string_length += strlen(words[word_num]) + 1;
|
|
}
|
|
command_string = (char*) MR_malloc(sizeof(char) * command_string_length);
|
|
command_string[0] = '\0';
|
|
for (word_num = 1; word_num < word_count; word_num++) {
|
|
strcat(command_string, words[word_num]);
|
|
strcat(command_string, " ");
|
|
}
|
|
|
|
MR_trace_call_system_display_error_on_failure(MR_mdb_err, command_string);
|
|
MR_free(command_string);
|
|
|
|
return KEEP_INTERACTING;
|
|
}
|
|
|
|
static void
|
|
MR_mdb_print_proc_id_and_nl(void *data, const MR_ProcLayout *entry_layout)
|
|
{
|
|
FILE *fp;
|
|
|
|
fp = (FILE *) data;
|
|
MR_print_proc_id_and_nl(fp, entry_layout);
|
|
}
|
|
|
|
static int
|
|
MR_trace_var_print_list(MR_SpyPrintList print_list)
|
|
{
|
|
MR_SpyPrint node;
|
|
const char *problem;
|
|
MR_VarSpec *after_var_spec;
|
|
int count;
|
|
|
|
count = 0;
|
|
for (; print_list != NULL; print_list = print_list->MR_pl_next) {
|
|
count++;
|
|
node = print_list->MR_pl_cur;
|
|
after_var_spec = NULL;
|
|
|
|
switch (node->MR_p_what) {
|
|
case MR_SPY_PRINT_ALL:
|
|
problem = MR_trace_browse_all(MR_mdb_out,
|
|
MR_trace_browse_internal, node->MR_p_format);
|
|
break;
|
|
|
|
case MR_SPY_PRINT_GOAL:
|
|
problem = MR_trace_browse_one_goal(MR_mdb_out,
|
|
MR_trace_browse_goal_internal, MR_BROWSE_CALLER_PRINT,
|
|
node->MR_p_format);
|
|
break;
|
|
|
|
case MR_SPY_PRINT_ONE:
|
|
problem = MR_trace_browse_one_path(MR_mdb_out, MR_TRUE,
|
|
node->MR_p_var_spec, node->MR_p_path,
|
|
MR_trace_browse_internal, MR_BROWSE_CALLER_PRINT,
|
|
node->MR_p_format, MR_FALSE);
|
|
if (problem != NULL &&
|
|
MR_streq(problem, "there is no such variable"))
|
|
{
|
|
if (node->MR_p_warn) {
|
|
problem = "there is no variable named";
|
|
after_var_spec = &node->MR_p_var_spec;
|
|
} else {
|
|
problem = NULL;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
MR_fatal_error("invalid node->MR_p_what");
|
|
break;
|
|
}
|
|
|
|
if (problem != NULL) {
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "mdb: %s", problem);
|
|
if (after_var_spec != NULL) {
|
|
fprintf(MR_mdb_err, " ");
|
|
MR_print_var_spec(MR_mdb_err, after_var_spec);
|
|
}
|
|
fprintf(MR_mdb_err, ".\n");
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static MR_Next
|
|
MR_trace_debug_cmd(char *line, MR_TraceCmdInfo *cmd,
|
|
MR_EventInfo *event_info, MR_Code **jumpaddr)
|
|
{
|
|
char **words;
|
|
char **orig_words = NULL;
|
|
int word_max;
|
|
int word_count;
|
|
const char *problem;
|
|
MR_Next next;
|
|
|
|
problem = MR_trace_parse_line(line, &words, &word_max, &word_count);
|
|
if (problem != NULL) {
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "%s.\n", problem);
|
|
return KEEP_INTERACTING;
|
|
}
|
|
|
|
MR_trace_expand_aliases(&words, &word_max, &word_count);
|
|
|
|
// At this point, the first word_count members of the words array contain
|
|
// the command. We save the value of words for freeing just before return,
|
|
// since the variable words itself can be overwritten by option processing.
|
|
|
|
orig_words = words;
|
|
|
|
// Now we check for a special case.
|
|
|
|
if (word_count == 0) {
|
|
// Normally EMPTY is aliased to "step", so this won't happen.
|
|
// This can only occur if the user has unaliased EMPTY.
|
|
// In that case, if we get an empty command line, we ignore it.
|
|
|
|
next = KEEP_INTERACTING;
|
|
} else {
|
|
// Call the command dispatcher.
|
|
next = MR_trace_handle_cmd(words, word_count, cmd, event_info,
|
|
jumpaddr);
|
|
}
|
|
|
|
MR_free(line);
|
|
MR_free(orig_words);
|
|
|
|
return next;
|
|
}
|
|
|
|
static const char *MR_current_cmd_category;
|
|
static const char *MR_current_cmd_name;
|
|
|
|
// IMPORTANT: if you add any new commands, you will need to
|
|
// (a) include them in MR_trace_command_table, defined below.
|
|
// (b) document them in doc/user_guide.texi
|
|
|
|
static MR_Next
|
|
MR_trace_handle_cmd(char **words, int word_count, MR_TraceCmdInfo *cmd,
|
|
MR_EventInfo *event_info, MR_Code **jumpaddr)
|
|
{
|
|
const MR_TraceCmdTableEntry *cmd_table_entry;
|
|
|
|
// The code for many commands calls getopt, and getopt may print to stderr.
|
|
// We flush MR_mdb_out here to make sure that all normal output so far
|
|
// (including the echoed command, if echoing is turned on) gets output
|
|
// first.
|
|
|
|
fflush(MR_mdb_out);
|
|
|
|
cmd_table_entry = MR_trace_valid_command(words[0]);
|
|
if (cmd_table_entry != NULL) {
|
|
MR_current_cmd_category = cmd_table_entry->MR_cmd_category;
|
|
MR_current_cmd_name = cmd_table_entry->MR_cmd_name;
|
|
|
|
return (*cmd_table_entry->MR_cmd_function)(words, word_count, cmd,
|
|
event_info, jumpaddr);
|
|
} else {
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "Unknown command `%s'. "
|
|
"Give the command `help' for help.\n", words[0]);
|
|
}
|
|
|
|
return KEEP_INTERACTING;
|
|
}
|
|
|
|
void
|
|
MR_trace_usage_cur_cmd(void)
|
|
{
|
|
// MR_current_cmd_category is unused now, for but could be used later.
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err,
|
|
"mdb: %s: usage error -- type `help %s' for help.\n",
|
|
MR_current_cmd_name, MR_current_cmd_name);
|
|
}
|
|
|
|
// Given a text line, break it up into words composed of non-space characters
|
|
// separated by space characters. Make each word a NULL-terminated string,
|
|
// overwriting some spaces in the line array in the process.
|
|
//
|
|
// If the first word is a number but the second is not, swap the two.
|
|
// If the first word has a number prefix, separate it out.
|
|
//
|
|
// On return *words will point to an array of strings, with space for
|
|
// *words_max strings. The number of strings (words) filled in will be
|
|
// given by *word_count.
|
|
//
|
|
// The space for the *words array is allocated with MR_malloc().
|
|
// It is the caller's responsibility to free it when appropriate.
|
|
// The elements of the *words array point to memory from the line array.
|
|
// The lifetime of the elements of the *words array expires when
|
|
// the line array is MR_free()'d or further modified or when
|
|
// MR_trace_parse_line is called again, whichever comes first.
|
|
//
|
|
// The return value is NULL if everything went OK, and an error message
|
|
// otherwise.
|
|
|
|
static const char *
|
|
MR_trace_parse_line(char *line, char ***words, int *word_max, int *word_count)
|
|
{
|
|
char **raw_words;
|
|
int raw_word_max;
|
|
int raw_word_count;
|
|
static char count_buf[MR_NUMBER_LEN + 1];
|
|
char *s;
|
|
MR_Unsigned i;
|
|
const char *problem;
|
|
|
|
// Handle a possible number prefix on the first word on the line,
|
|
// separating it out into a word on its own.
|
|
|
|
problem = MR_trace_break_into_words(line, &raw_words, &raw_word_max,
|
|
&raw_word_count);
|
|
if (problem != NULL) {
|
|
return problem;
|
|
}
|
|
|
|
if (raw_word_count > 0 && MR_isdigit(*raw_words[0])) {
|
|
i = 0;
|
|
s = raw_words[0];
|
|
while (MR_isdigit(*s)) {
|
|
if (i >= MR_NUMBER_LEN) {
|
|
return "too large a number";
|
|
}
|
|
|
|
count_buf[i] = *s;
|
|
i++;
|
|
s++;
|
|
}
|
|
|
|
count_buf[i] = '\0';
|
|
|
|
if (*s != '\0') {
|
|
// Only part of the first word constitutes a number.
|
|
// Put it in an extra word at the start.
|
|
MR_ensure_big_enough(raw_word_count, raw_word, char *,
|
|
MR_INIT_WORD_COUNT);
|
|
|
|
for (i = raw_word_count; i > 0; i--) {
|
|
raw_words[i] = raw_words[i-1];
|
|
}
|
|
|
|
raw_words[0] = count_buf;
|
|
raw_words[1] = s;
|
|
raw_word_count++;
|
|
}
|
|
}
|
|
|
|
// If the first word is a number, try to exchange it with the command word,
|
|
// to put the command word first.
|
|
|
|
if (raw_word_count > 1 && MR_trace_is_natural_number(raw_words[0], &i)
|
|
&& ! MR_trace_is_natural_number(raw_words[1], &i))
|
|
{
|
|
s = raw_words[0];
|
|
raw_words[0] = raw_words[1];
|
|
raw_words[1] = s;
|
|
}
|
|
|
|
*words = raw_words;
|
|
*word_max = raw_word_max;
|
|
*word_count = raw_word_count;
|
|
return NULL;
|
|
}
|
|
|
|
// Given a text line, break it up into words. Words are composed of
|
|
// non-space characters separated by space characters, except where
|
|
// quotes (') or escapes (\) change the treatment of characters. Make
|
|
// each word a NULL-terminated string, and remove the quotes and escapes,
|
|
// overwriting some parts of the line array in the process.
|
|
// XXX The "condition" command would work better if single quotes were
|
|
// left in place; that way, users could type "condition X = '+'"
|
|
// instead of "condition X = \'+\'".
|
|
//
|
|
// On return *words will point to an array of strings, with space for
|
|
// *words_max strings. The number of strings filled in will be given by
|
|
// the return value. The memory for *words is allocated with MR_malloc(),
|
|
// and it is the responsibility of the caller to MR_free() it when appropriate.
|
|
|
|
static const char *
|
|
MR_trace_break_into_words(char *line, char ***words_ptr, int *word_max_ptr,
|
|
int *word_count)
|
|
{
|
|
int word_max;
|
|
char **words;
|
|
int token_number;
|
|
int char_pos;
|
|
int new_char_pos;
|
|
const char *problem;
|
|
|
|
token_number = 0;
|
|
char_pos = 0;
|
|
|
|
word_max = 0;
|
|
words = NULL;
|
|
|
|
// Each iteration of this loop processes one token, or end of line.
|
|
for (;;) {
|
|
while (line[char_pos] != '\0' && MR_isspace(line[char_pos])) {
|
|
char_pos++;
|
|
}
|
|
|
|
if (line[char_pos] == '\0') {
|
|
*words_ptr = words;
|
|
*word_max_ptr = word_max;
|
|
*word_count = token_number;
|
|
return NULL;
|
|
}
|
|
|
|
MR_ensure_big_enough(token_number, word, char *, MR_INIT_WORD_COUNT);
|
|
words[token_number] = line + char_pos;
|
|
problem = MR_trace_break_off_one_word(line, char_pos, &new_char_pos);
|
|
if (problem != NULL) {
|
|
return problem;
|
|
}
|
|
|
|
char_pos = new_char_pos;
|
|
token_number++;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
MR_trace_break_off_one_word(char *line, int char_pos, int *new_char_pos_ptr)
|
|
{
|
|
int lag = 0;
|
|
MR_bool single_quoted = MR_FALSE;
|
|
MR_bool double_quoted = MR_FALSE;
|
|
MR_bool another = MR_FALSE;
|
|
|
|
while (line[char_pos] != '\0') {
|
|
if (! single_quoted && ! double_quoted && MR_isspace(line[char_pos])) {
|
|
another = MR_TRUE;
|
|
break;
|
|
}
|
|
|
|
if (! double_quoted && line[char_pos] == SINGLE_QUOTE_CHAR) {
|
|
lag++;
|
|
char_pos++;
|
|
single_quoted = ! single_quoted;
|
|
} else if (! single_quoted && line[char_pos] == DOUBLE_QUOTE_CHAR) {
|
|
if (lag != 0) {
|
|
line[char_pos - lag] = line[char_pos];
|
|
}
|
|
char_pos++;
|
|
double_quoted = ! double_quoted;
|
|
} else {
|
|
if (line[char_pos] == ESCAPE_CHAR) {
|
|
lag++;
|
|
char_pos++;
|
|
if (line[char_pos] == '\0') {
|
|
return "bad backslash";
|
|
}
|
|
}
|
|
|
|
if (lag != 0) {
|
|
line[char_pos - lag] = line[char_pos];
|
|
}
|
|
char_pos++;
|
|
}
|
|
}
|
|
|
|
if (single_quoted) {
|
|
return "unmatched single quote";
|
|
}
|
|
|
|
if (double_quoted) {
|
|
return "unmatched double quote";
|
|
}
|
|
|
|
line[char_pos - lag] = '\0';
|
|
if (another) {
|
|
char_pos++;
|
|
}
|
|
|
|
*new_char_pos_ptr = char_pos;
|
|
return NULL;
|
|
}
|
|
|
|
// Call MR_trace_getline to get the next line of input, then do some further
|
|
// processing. If the input has reached EOF, return the command "quit".
|
|
// If the line contains multiple commands then split it and only return
|
|
// the first one. If the newline at the end is either quoted or escaped,
|
|
// read another line (using the prompt '>') and append it to the first.
|
|
// The command is returned in a MR_malloc'd buffer.
|
|
|
|
char *
|
|
MR_trace_get_command(const char *prompt, FILE *mdb_in, FILE *mdb_out)
|
|
{
|
|
char *line;
|
|
char *ptr;
|
|
char *cmd_chars;
|
|
int cmd_char_max;
|
|
MR_bool single_quoted;
|
|
MR_bool double_quoted;
|
|
size_t len;
|
|
size_t extra_len;
|
|
|
|
line = MR_trace_getline(prompt, mdb_in, mdb_out);
|
|
|
|
if (line == NULL) {
|
|
// We got an EOF. We arrange things so we don't have to treat this case
|
|
// specially in the command interpreter.
|
|
|
|
line = MR_copy_string("quit");
|
|
return line;
|
|
}
|
|
|
|
len = strlen(line);
|
|
ptr = line;
|
|
cmd_chars = line;
|
|
cmd_char_max = len + 1;
|
|
single_quoted = MR_FALSE;
|
|
double_quoted = MR_FALSE;
|
|
while (MR_trace_continue_line(ptr, &single_quoted, &double_quoted)) {
|
|
// We were inside quotes when the end of the line was reached,
|
|
// or the newline was escaped, so input continues on the next line.
|
|
// We append it to the first line, allocating more space if necessary.
|
|
|
|
line = MR_trace_getline("> ", mdb_in, mdb_out);
|
|
if (line == NULL) {
|
|
// We got an EOF... we need to stop processing the input,
|
|
// even though it is not syntactically correct, otherwise we might
|
|
// get into an infinite loop if we keep getting EOF.
|
|
|
|
break;
|
|
}
|
|
extra_len = strlen(line);
|
|
// cmd_char_max is always > 0
|
|
MR_ensure_big_enough(len + extra_len + 1, cmd_char, char, 0);
|
|
ptr = cmd_chars + len;
|
|
strcpy(ptr, line);
|
|
MR_free(line);
|
|
len = len + extra_len;
|
|
}
|
|
|
|
return cmd_chars;
|
|
}
|
|
|
|
// If there any lines waiting in the queue, return the first of these.
|
|
// If not, print the prompt to mdb_out, read a line from mdb_in,
|
|
// and return it in a MR_malloc'd buffer holding the line (without the final
|
|
// newline).
|
|
// If EOF occurs on a nonempty line, treat the EOF as a newline; if EOF
|
|
// occurs on an empty line, return NULL.
|
|
//
|
|
// Whether the line is read from the queue or from mdb_in, if this function
|
|
// returns a non-NULL value, then the memory for the line returned will have
|
|
// been allocated with MR_malloc(), and it is the caller's responsibility
|
|
// to MR_free() it when appropriate.
|
|
|
|
char *
|
|
MR_trace_getline(const char *prompt, FILE *mdb_in, FILE *mdb_out)
|
|
{
|
|
char *line;
|
|
|
|
line = MR_trace_getline_command_queue();
|
|
if (line != NULL) {
|
|
return line;
|
|
}
|
|
|
|
MR_trace_internal_interacting = MR_TRUE;
|
|
|
|
line = MR_trace_readline(prompt, mdb_in, mdb_out);
|
|
|
|
if (MR_echo_commands && line != NULL) {
|
|
fputs(line, mdb_out);
|
|
putc('\n', mdb_out);
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
// This returns MR_TRUE iff the given line continues on to the next line,
|
|
// because the newline is in quotes or escaped. The second parameter
|
|
// indicates whether we are inside quotes or not, and is updated by
|
|
// this function. If an unquoted and unescaped semicolon is encountered,
|
|
// the line is split at that point.
|
|
|
|
static MR_bool
|
|
MR_trace_continue_line(char *ptr, MR_bool *single_quoted,
|
|
MR_bool *double_quoted)
|
|
{
|
|
MR_bool escaped = MR_FALSE;
|
|
|
|
while (*ptr != '\0') {
|
|
if (escaped) {
|
|
// Do nothing special.
|
|
escaped = MR_FALSE;
|
|
} else if (*ptr == ESCAPE_CHAR) {
|
|
escaped = MR_TRUE;
|
|
} else if (! (*double_quoted) && *ptr == SINGLE_QUOTE_CHAR) {
|
|
*single_quoted = ! (*single_quoted);
|
|
} else if (! (*single_quoted) && *ptr == DOUBLE_QUOTE_CHAR) {
|
|
*double_quoted = ! (*double_quoted);
|
|
} else if (! (*single_quoted) && ! (*double_quoted) && *ptr == ';') {
|
|
// The line contains at least two commands. Return only the first
|
|
// command now; put the others back in the input to be processed
|
|
// later.
|
|
|
|
*ptr = '\0';
|
|
MR_insert_command_line_at_head(MR_copy_string(ptr + 1));
|
|
return MR_FALSE;
|
|
}
|
|
|
|
++ptr;
|
|
}
|
|
|
|
if (escaped) {
|
|
// Replace the escaped newline with a space.
|
|
*(ptr - 1) = ' ';
|
|
}
|
|
|
|
return (*single_quoted || *double_quoted || escaped);
|
|
}
|
|
|
|
static MR_Code *
|
|
MR_trace_event_internal_report(MR_TraceCmdInfo *cmd,
|
|
MR_SpyPrintList print_list, MR_EventInfo *event_info)
|
|
{
|
|
char *buf;
|
|
int i;
|
|
int len;
|
|
MR_SpyPrintList list;
|
|
|
|
list = print_list;
|
|
len = 0;
|
|
for (; list != NULL; list = list->MR_pl_next) {
|
|
len++;
|
|
}
|
|
|
|
// We try to leave one line for the prompt itself.
|
|
if (MR_scroll_control && MR_scroll_next + len >= MR_scroll_limit - 1) {
|
|
try_again:
|
|
buf = MR_trace_getline("--more-- ", MR_mdb_in, MR_mdb_out);
|
|
if (buf != NULL) {
|
|
for (i = 0; buf[i] != '\0' && MR_isspace(buf[i]); i++)
|
|
;
|
|
|
|
if (buf[i] != '\0' && !MR_isspace(buf[i])) {
|
|
switch (buf[i]) {
|
|
case 'a':
|
|
cmd->MR_trace_print_level_specified = MR_TRUE;
|
|
cmd->MR_trace_print_level = MR_PRINT_LEVEL_ALL;
|
|
break;
|
|
|
|
case 'n':
|
|
cmd->MR_trace_print_level_specified = MR_TRUE;
|
|
cmd->MR_trace_print_level = MR_PRINT_LEVEL_NONE;
|
|
break;
|
|
|
|
case 's':
|
|
cmd->MR_trace_print_level_specified = MR_TRUE;
|
|
cmd->MR_trace_print_level = MR_PRINT_LEVEL_SOME;
|
|
break;
|
|
|
|
case 'q':
|
|
MR_free(buf);
|
|
return MR_trace_event_internal(cmd, MR_TRUE, NULL,
|
|
event_info, NULL);
|
|
|
|
default:
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "unknown command, try again\n");
|
|
MR_free(buf);
|
|
goto try_again;
|
|
}
|
|
}
|
|
|
|
MR_free(buf);
|
|
}
|
|
|
|
MR_scroll_next = 0;
|
|
}
|
|
|
|
MR_trace_event_print_internal_report(event_info);
|
|
MR_scroll_next++;
|
|
|
|
if (print_list != NULL) {
|
|
MR_trace_init_point_vars(event_info->MR_event_sll,
|
|
event_info->MR_saved_regs, event_info->MR_saved_f_regs,
|
|
event_info->MR_trace_port, MR_print_optionals);
|
|
MR_scroll_next += MR_trace_var_print_list(print_list);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
MR_trace_event_print_internal_report(MR_EventInfo *event_info)
|
|
{
|
|
const MR_LabelLayout *label_layout;
|
|
const MR_LabelLayout *parent;
|
|
const char *filename;
|
|
const char *parent_filename;
|
|
int lineno;
|
|
int parent_lineno;
|
|
const char *problem; // Not used.
|
|
MR_Word *base_sp;
|
|
MR_Word *base_curfr;
|
|
int indent;
|
|
const char *maybe_user_event_name;
|
|
MR_Level actual_level;
|
|
|
|
lineno = 0;
|
|
parent_lineno = 0;
|
|
filename = "";
|
|
parent_filename = "";
|
|
|
|
// The code below does has a job that is very similar to the job
|
|
// of the function MR_print_call_trace_info in
|
|
// runtime/mercury_stack_trace.c. Any changes here will probably require
|
|
// similar changes there.
|
|
|
|
if (MR_standardize_event_details) {
|
|
char buf[64];
|
|
MR_Unsigned event_num;
|
|
MR_Unsigned call_num;
|
|
|
|
// Do not print the context id. It contains the values of the arguments
|
|
// cast to integers. Since those arguments may originally have been
|
|
// addresses, their values may differ from run to run.
|
|
|
|
event_num = MR_standardize_event_num(event_info->MR_event_number);
|
|
call_num = MR_standardize_call_num(event_info->MR_call_seqno);
|
|
MR_snprintf(buf, 64, "E%ld", (long) event_num);
|
|
fprintf(MR_mdb_out, "%8s: ", buf);
|
|
MR_snprintf(buf, 64, "C%ld", (long) call_num);
|
|
fprintf(MR_mdb_out, "%6s ", buf);
|
|
fprintf(MR_mdb_out, "%s",
|
|
MR_simplified_port_names[event_info->MR_trace_port]);
|
|
} else {
|
|
#ifdef MR_USE_MINIMAL_MODEL_OWN_STACKS
|
|
MR_Generator *generator;
|
|
int i;
|
|
|
|
generator = MR_ENGINE(MR_eng_this_context)->MR_ctxt_owner_generator;
|
|
if (generator != NULL) {
|
|
fprintf(MR_mdb_out, "%s ", MR_gen_subgoal(generator));
|
|
}
|
|
#endif
|
|
|
|
fprintf(MR_mdb_out, "%8ld: %6ld %2ld %s",
|
|
(long) event_info->MR_event_number,
|
|
(long) event_info->MR_call_seqno,
|
|
(long) event_info->MR_call_depth,
|
|
MR_simplified_port_names[event_info->MR_trace_port]);
|
|
}
|
|
|
|
// The printf printed 24 characters.
|
|
indent = 24;
|
|
|
|
label_layout = event_info->MR_event_sll;
|
|
(void) MR_find_context(label_layout, &filename, &lineno);
|
|
if (MR_port_is_interface(event_info->MR_trace_port)) {
|
|
base_sp = MR_saved_sp(event_info->MR_saved_regs);
|
|
base_curfr = MR_saved_curfr(event_info->MR_saved_regs);
|
|
parent = MR_find_nth_ancestor(label_layout, 1, &base_sp, &base_curfr,
|
|
&actual_level, &problem);
|
|
if (actual_level == 1 && parent != NULL) {
|
|
(void) MR_find_context(parent, &parent_filename, &parent_lineno);
|
|
}
|
|
}
|
|
|
|
if (label_layout->MR_sll_port >= 0 &&
|
|
(MR_TracePort) label_layout->MR_sll_port == MR_PORT_USER)
|
|
{
|
|
maybe_user_event_name =
|
|
MR_user_event_spec(label_layout).MR_ues_event_name;
|
|
fprintf(MR_mdb_out, " <%s>", maybe_user_event_name);
|
|
} else {
|
|
maybe_user_event_name = NULL;
|
|
}
|
|
|
|
MR_print_proc_id_trace_and_context(MR_mdb_out, MR_FALSE,
|
|
MR_context_position, MR_user_event_context, label_layout->MR_sll_entry,
|
|
maybe_user_event_name, base_sp, base_curfr,
|
|
( MR_print_goal_paths ? event_info->MR_event_path : "" ),
|
|
filename, lineno, MR_port_is_interface(event_info->MR_trace_port),
|
|
parent_filename, parent_lineno, indent);
|
|
}
|
|
|
|
static const MR_TraceCmdTableEntry MR_trace_command_table[] =
|
|
{
|
|
// The first two fields of this block should be the same
|
|
// as in the file doc/mdb_command_list.
|
|
|
|
{ "forward", "step", MR_trace_cmd_step,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "goto", MR_trace_cmd_goto,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "next", MR_trace_cmd_next,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "finish", MR_trace_cmd_finish,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "fail", MR_trace_cmd_fail,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "exception", MR_trace_cmd_exception,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "return", MR_trace_cmd_return,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "user", MR_trace_cmd_user,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "forward", MR_trace_cmd_forward,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "mindepth", MR_trace_cmd_mindepth,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "maxdepth", MR_trace_cmd_maxdepth,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
{ "forward", "continue", MR_trace_cmd_continue,
|
|
MR_trace_movement_cmd_args, MR_trace_null_completer },
|
|
|
|
{ "backward", "retry", MR_trace_cmd_retry,
|
|
MR_trace_retry_cmd_args, MR_trace_null_completer },
|
|
|
|
{ "browsing", "level", MR_trace_cmd_level,
|
|
MR_trace_stack_cmd_args, MR_trace_null_completer },
|
|
{ "browsing", "up", MR_trace_cmd_up,
|
|
MR_trace_stack_cmd_args, MR_trace_null_completer },
|
|
{ "browsing", "down", MR_trace_cmd_down,
|
|
MR_trace_stack_cmd_args, MR_trace_null_completer },
|
|
{ "browsing", "vars", MR_trace_cmd_vars,
|
|
NULL, MR_trace_null_completer },
|
|
{ "browsing", "held_vars", MR_trace_cmd_held_vars,
|
|
NULL, MR_trace_null_completer },
|
|
{ "browsing", "print", MR_trace_cmd_print,
|
|
MR_trace_print_cmd_args, MR_trace_var_completer },
|
|
{ "browsing", "browse", MR_trace_cmd_browse,
|
|
MR_trace_print_cmd_args, MR_trace_var_completer },
|
|
{ "browsing", "stack", MR_trace_cmd_stack,
|
|
MR_trace_stack_cmd_args, MR_trace_null_completer },
|
|
{ "browsing", "current", MR_trace_cmd_current,
|
|
NULL, MR_trace_null_completer },
|
|
{ "browsing", "view", MR_trace_cmd_view,
|
|
MR_trace_view_cmd_args, MR_trace_null_completer },
|
|
{ "browsing", "hold", MR_trace_cmd_hold,
|
|
NULL, MR_trace_var_completer },
|
|
{ "browsing", "diff", MR_trace_cmd_diff,
|
|
NULL, MR_trace_var_completer },
|
|
{ "browsing", "dump", MR_trace_cmd_dump,
|
|
NULL, MR_trace_var_completer },
|
|
{ "browsing", "list", MR_trace_cmd_list,
|
|
NULL, MR_trace_null_completer },
|
|
|
|
{ "breakpoint", "break", MR_trace_cmd_break,
|
|
MR_trace_break_cmd_args, MR_trace_break_completer },
|
|
{ "breakpoint", "condition", MR_trace_cmd_condition,
|
|
NULL, MR_trace_null_completer },
|
|
{ "breakpoint", "ignore", MR_trace_cmd_ignore,
|
|
MR_trace_ignore_cmd_args, MR_trace_null_completer },
|
|
{ "breakpoint", "break_print", MR_trace_cmd_break_print,
|
|
NULL, MR_trace_var_completer },
|
|
{ "breakpoint", "enable", MR_trace_cmd_enable,
|
|
NULL, MR_trace_null_completer },
|
|
{ "breakpoint", "disable", MR_trace_cmd_disable,
|
|
NULL, MR_trace_null_completer },
|
|
{ "breakpoint", "delete", MR_trace_cmd_delete,
|
|
NULL, MR_trace_null_completer },
|
|
{ "breakpoint", "register", MR_trace_cmd_register,
|
|
NULL, MR_trace_null_completer },
|
|
{ "breakpoint", "modules", MR_trace_cmd_modules,
|
|
NULL, MR_trace_null_completer },
|
|
{ "breakpoint", "procedures", MR_trace_cmd_procedures,
|
|
NULL, MR_trace_module_completer },
|
|
|
|
// XXX For queries we should complete on all modules, not just those
|
|
// that were compiled with tracing enabled.
|
|
|
|
{ "queries", "query", MR_trace_cmd_query,
|
|
NULL, MR_trace_module_completer },
|
|
{ "queries", "cc_query", MR_trace_cmd_cc_query,
|
|
NULL, MR_trace_module_completer },
|
|
{ "queries", "io_query", MR_trace_cmd_io_query,
|
|
NULL, MR_trace_module_completer },
|
|
|
|
{ "table_io", "table_io", MR_trace_cmd_table_io,
|
|
MR_trace_table_io_cmd_args, MR_trace_null_completer },
|
|
|
|
{ "parameter", "mmc_options", MR_trace_cmd_mmc_options,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "printlevel", MR_trace_cmd_printlevel,
|
|
MR_trace_printlevel_cmd_args, MR_trace_null_completer },
|
|
{ "parameter", "scroll", MR_trace_cmd_scroll,
|
|
MR_trace_on_off_args, MR_trace_null_completer },
|
|
{ "parameter", "stack_default_limit", MR_trace_cmd_stack_default_limit,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "goal_paths", MR_trace_cmd_goal_paths,
|
|
MR_trace_on_off_args, MR_trace_null_completer },
|
|
{ "parameter", "scope", MR_trace_cmd_scope,
|
|
MR_trace_scope_cmd_args, MR_trace_null_completer },
|
|
{ "parameter", "echo", MR_trace_cmd_echo,
|
|
MR_trace_on_off_args, MR_trace_null_completer },
|
|
{ "parameter", "context", MR_trace_cmd_context,
|
|
MR_trace_context_cmd_args, MR_trace_null_completer },
|
|
{ "parameter", "user_event_context", MR_trace_cmd_user_event_context,
|
|
MR_trace_user_event_context_cmd_args, MR_trace_null_completer },
|
|
{ "parameter", "list_context_lines", MR_trace_cmd_list_context_lines,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "list_path", MR_trace_cmd_list_path,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "push_list_dir", MR_trace_cmd_push_list_dir,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "pop_list_dir", MR_trace_cmd_pop_list_dir,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "fail_trace_counts", MR_trace_cmd_fail_trace_counts,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "parameter", "pass_trace_counts", MR_trace_cmd_pass_trace_counts,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "parameter", "max_io_actions", MR_trace_cmd_max_io_actions,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "xml_browser_cmd", MR_trace_cmd_xml_browser_cmd,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "xml_tmp_filename", MR_trace_cmd_xml_tmp_filename,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "web_browser_cmd", MR_trace_cmd_web_browser_cmd,
|
|
NULL, MR_trace_null_completer },
|
|
{ "parameter", "format", MR_trace_cmd_format,
|
|
MR_trace_format_cmd_args, MR_trace_null_completer },
|
|
{ "parameter", "format_param", MR_trace_cmd_format_param,
|
|
MR_trace_format_param_cmd_args, MR_trace_null_completer },
|
|
{ "parameter", "alias", MR_trace_cmd_alias,
|
|
NULL, MR_trace_command_completer },
|
|
{ "parameter", "unalias", MR_trace_cmd_unalias,
|
|
NULL, MR_trace_alias_completer },
|
|
|
|
{ "help", "document_category", MR_trace_cmd_document_category,
|
|
NULL, MR_trace_null_completer },
|
|
{ "help", "document", MR_trace_cmd_document,
|
|
NULL, MR_trace_null_completer },
|
|
{ "help", "help", MR_trace_cmd_help,
|
|
NULL, MR_trace_help_completer },
|
|
|
|
{ "dd", "dd", MR_trace_cmd_dd,
|
|
MR_trace_dd_cmd_args, MR_trace_null_completer },
|
|
{ "dd", "trust", MR_trace_cmd_trust,
|
|
NULL, MR_trace_proc_spec_completer },
|
|
{ "dd", "untrust", MR_trace_cmd_untrust,
|
|
NULL, MR_trace_null_completer },
|
|
{ "dd", "trusted", MR_trace_cmd_trusted,
|
|
NULL, MR_trace_null_completer },
|
|
|
|
{ "misc", "source", MR_trace_cmd_source,
|
|
MR_trace_source_cmd_args, MR_trace_filename_completer },
|
|
{ "misc", "save", MR_trace_cmd_save,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "misc", "quit", MR_trace_cmd_quit,
|
|
MR_trace_quit_cmd_args, MR_trace_null_completer },
|
|
{ "misc", "shell", MR_trace_cmd_shell,
|
|
NULL, MR_trace_null_completer },
|
|
|
|
{ "exp", "histogram_all", MR_trace_cmd_histogram_all,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "exp", "histogram_exp", MR_trace_cmd_histogram_exp,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "exp", "clear_histogram", MR_trace_cmd_clear_histogram,
|
|
NULL, MR_trace_null_completer },
|
|
{ "exp", "dice", MR_trace_cmd_dice,
|
|
NULL, MR_trace_null_completer },
|
|
|
|
{ "developer", "var_details", MR_trace_cmd_var_details,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "term_size", MR_trace_cmd_term_size,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "flag", MR_trace_cmd_flag,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "subgoal", MR_trace_cmd_subgoal,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "consumer", MR_trace_cmd_consumer,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "gen_stack", MR_trace_cmd_gen_stack,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "cut_stack", MR_trace_cmd_cut_stack,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "pneg_stack", MR_trace_cmd_pneg_stack,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "mm_stacks", MR_trace_cmd_mm_stacks,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "nondet_stack", MR_trace_cmd_nondet_stack,
|
|
MR_trace_nondet_stack_cmd_args, MR_trace_null_completer },
|
|
{ "developer", "stack_regs", MR_trace_cmd_stack_regs,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "all_regs", MR_trace_cmd_all_regs,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "debug_vars", MR_trace_cmd_debug_vars,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "stats", MR_trace_cmd_stats,
|
|
MR_trace_stats_cmd_args, MR_trace_filename_completer },
|
|
{ "developer", "print_optionals", MR_trace_cmd_print_optionals,
|
|
MR_trace_on_off_args, MR_trace_null_completer },
|
|
{ "developer", "unhide_events", MR_trace_cmd_unhide_events,
|
|
MR_trace_on_off_args, MR_trace_null_completer },
|
|
{ "developer", "table", MR_trace_cmd_table,
|
|
NULL, MR_trace_proc_spec_completer },
|
|
{ "developer", "type_ctor", MR_trace_cmd_type_ctor,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "class_decl", MR_trace_cmd_class_decl,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "all_type_ctors", MR_trace_cmd_all_type_ctors,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "all_class_decls", MR_trace_cmd_all_class_decls,
|
|
NULL, MR_trace_null_completer },
|
|
{ "developer", "all_procedures", MR_trace_cmd_all_procedures,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "developer", "ambiguity", MR_trace_cmd_ambiguity,
|
|
NULL, MR_trace_filename_completer },
|
|
{ "developer", "trail_details", MR_trace_cmd_trail_details,
|
|
NULL, MR_trace_filename_completer },
|
|
|
|
// End of doc/mdb_command_list.
|
|
{ NULL, "NUMBER", NULL,
|
|
NULL, MR_trace_null_completer },
|
|
{ NULL, "EMPTY", NULL,
|
|
NULL, MR_trace_null_completer },
|
|
{ NULL, NULL, NULL,
|
|
NULL, MR_trace_null_completer },
|
|
};
|
|
|
|
MR_bool
|
|
MR_trace_command_completion_info(const char *word,
|
|
MR_MakeCompleter *completer, const char *const **fixed_args)
|
|
{
|
|
const MR_TraceCmdTableEntry *command_info;
|
|
|
|
command_info = MR_trace_valid_command(word);
|
|
if (command_info == NULL) {
|
|
return MR_FALSE;
|
|
} else {
|
|
*completer = command_info->MR_cmd_arg_completer;
|
|
*fixed_args = command_info->MR_cmd_arg_strings;
|
|
return MR_TRUE;
|
|
}
|
|
}
|
|
|
|
const MR_TraceCmdTableEntry *
|
|
MR_trace_valid_command(const char *word)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; MR_trace_command_table[i].MR_cmd_name != NULL; i++) {
|
|
if (MR_streq(MR_trace_command_table[i].MR_cmd_name, word)) {
|
|
return &MR_trace_command_table[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MR_CompleterList *
|
|
MR_trace_command_completer(const char *word, size_t word_len)
|
|
{
|
|
return MR_new_completer_elem(&MR_trace_command_completer_next,
|
|
(MR_CompleterData) 0, MR_trace_no_free);
|
|
}
|
|
|
|
static char *
|
|
MR_trace_command_completer_next(const char *word, size_t word_len,
|
|
MR_CompleterData *data)
|
|
{
|
|
MR_Integer command_index;
|
|
|
|
command_index = (MR_Integer) *data;
|
|
while (1) {
|
|
const char *command;
|
|
const char *category;
|
|
|
|
category = MR_trace_command_table[command_index].MR_cmd_category;
|
|
command = MR_trace_command_table[command_index].MR_cmd_name;
|
|
command_index++;
|
|
*data = (void *) command_index;
|
|
|
|
// We don't complete on the "EMPTY" and "NUMBER" entries
|
|
// in the list of commands (they have a category entry of NULL).
|
|
|
|
if (command == NULL) {
|
|
return NULL;
|
|
} else if (category != NULL && MR_strneq(word, command, word_len)) {
|
|
return MR_copy_string(command);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MR_trace_interrupt_message(void)
|
|
{
|
|
fprintf(MR_mdb_out, "\nmdb: got interrupt signal\n");
|
|
}
|