// vim: ts=4 sw=4 expandtab ft=c // Copyright (C) 1998-2012 The University of Melbourne. // Copyright (C) 2013-2023 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 #include #include #include #include #include #ifdef MR_HAVE_UNISTD_H #include #endif #ifdef MR_HAVE_SYS_TYPES_H #include #endif #ifdef MR_HAVE_SYS_WAIT_H #include #endif #ifdef MR_HAVE_TERMIOS_H #include #endif #ifdef MR_HAVE_FCNTL_H #include #endif #ifdef MR_HAVE_SYS_IOCTL_H #include #endif #ifdef MR_HAVE_SYS_STROPTS_H #include #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 // (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-2023 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"); }