mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-06 07:49:02 +00:00
trace/mercury_trace_internal.c:
Whe printing the "there is no such variable" message at
conditional break points, print the name of the variable.
The original message can be misread to mean that there
is nothing at *the selected path*; the new one cannot.
trace/mercury_trace_spy.c:
trace/mercury_trace_spy.h:
Add a mechanism to enable the above change.
tests/debugger/cond.exp:
Expect the updated error message.
1753 lines
58 KiB
C
1753 lines
58 KiB
C
// vim: ts=4 sw=4 expandtab ft=c
|
|
|
|
// Copyright (C) 1998-2012 The University of Melbourne.
|
|
// Copyright (C) 2013-2025 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) does not generate any trace events,
|
|
// (b) does not generate any unwanted debugging output, and
|
|
// (c) does not 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) {
|
|
const char *cond_var_name;
|
|
|
|
fprintf(MR_mdb_err, "mdb: couldn't evaluate break point condition\n");
|
|
fprintf(MR_mdb_err, " ");
|
|
MR_print_spy_cond(MR_mdb_err, MR_spy_point_cond_bad, &cond_var_name);
|
|
fprintf(MR_mdb_err, ".\n");
|
|
if (MR_streq(MR_spy_point_cond_problem, "there is no such variable") &&
|
|
cond_var_name != NULL)
|
|
{
|
|
fprintf(MR_mdb_err,
|
|
"The error is: there is no variable named %s.\n",
|
|
cond_var_name);
|
|
} else {
|
|
fprintf(MR_mdb_err,
|
|
"The error is: %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-2025 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", "list_cmd", MR_trace_cmd_list_cmd,
|
|
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", "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");
|
|
}
|