/****************************************************************************** * Copyright (c) 2000-2020 Ericsson Telecom AB * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html * * Contributors: * * Baranyi, Botond – initial implementation * ******************************************************************************/ #include "DebuggerUI.hh" #include "DebugCommands.hh" #include "Debugger.hh" #include <stdio.h> #include <ctype.h> #ifdef ADVANCED_DEBUGGER_UI #include <editline/readline.h> // use a different file, than the MCTR CLI, since not all commands are the same #define TTCN3_HISTORY_FILENAME ".ttcn3_history_single" #endif #define PROMPT_TEXT "DEBUG> " #define BATCH_TEXT "batch" #define HELP_TEXT "help" const TTCN_Debugger_UI::command_t TTCN_Debugger_UI::debug_command_list[] = { { D_SWITCH_TEXT, D_SWITCH, D_SWITCH_TEXT " on|off", "Switch the debugger on or off." }, { D_SET_BREAKPOINT_TEXT, D_SET_BREAKPOINT, D_SET_BREAKPOINT_TEXT " <module> <line>|<function> [<batch_file>]", "Add a breakpoint at the specified location, or change the batch file of " "an existing breakpoint." }, { D_REMOVE_BREAKPOINT_TEXT, D_REMOVE_BREAKPOINT, D_REMOVE_BREAKPOINT_TEXT " all|<module> [all|<line>|<function>]", "Remove a breakpoint, or all breakpoints from a module, or all breakpoints " "from all modules." }, { D_SET_AUTOMATIC_BREAKPOINT_TEXT, D_SET_AUTOMATIC_BREAKPOINT, D_SET_AUTOMATIC_BREAKPOINT_TEXT " error|fail on|off [<batch_file>]", "Switch an automatic breakpoint (truggered by an event) on or off, and/or " "change its batch file." }, { D_SET_OUTPUT_TEXT, D_SET_OUTPUT, D_SET_OUTPUT_TEXT " console|file|both [file_name]", "Set the output of the debugger." }, { D_SET_GLOBAL_BATCH_FILE_TEXT, D_SET_GLOBAL_BATCH_FILE, D_SET_GLOBAL_BATCH_FILE_TEXT " on|off [batch_file_name]", "Set whether a batch file should be executed automatically when test execution " "is halted (breakpoint-specific batch files override this setting)." }, { D_FUNCTION_CALL_CONFIG_TEXT, D_FUNCTION_CALL_CONFIG, D_FUNCTION_CALL_CONFIG_TEXT " file|<limit>|all [<file_name>]", "Configure the storing of function call data." }, { D_PRINT_SETTINGS_TEXT, D_PRINT_SETTINGS, D_PRINT_SETTINGS_TEXT, "Prints the debugger's settings." }, { D_PRINT_CALL_STACK_TEXT, D_PRINT_CALL_STACK, D_PRINT_CALL_STACK_TEXT, "Print call stack." }, { D_SET_STACK_LEVEL_TEXT, D_SET_STACK_LEVEL, D_SET_STACK_LEVEL_TEXT " <level>", "Set the stack level to print debug information from." }, { D_LIST_VARIABLES_TEXT, D_LIST_VARIABLES, D_LIST_VARIABLES_TEXT " [local|global|comp|all] [pattern]", "List variable names." }, { D_PRINT_VARIABLE_TEXT, D_PRINT_VARIABLE, D_PRINT_VARIABLE_TEXT " <variable_name>|$ [{ <variable_name>|$}]", "Print current value of one or more variables ('$' is substituted with the " "result of the last " D_LIST_VARIABLES_TEXT " command)." }, { D_OVERWRITE_VARIABLE_TEXT, D_OVERWRITE_VARIABLE, D_OVERWRITE_VARIABLE_TEXT " <variable_name> <value>", "Overwrite the current value of a variable." }, { D_PRINT_FUNCTION_CALLS_TEXT, D_PRINT_FUNCTION_CALLS, D_PRINT_FUNCTION_CALLS_TEXT " [all|<amount>]", "Print function call data." }, { D_STEP_OVER_TEXT, D_STEP_OVER, D_STEP_OVER_TEXT, "Resume test execution until the next line of code (in this function or the " "caller function)." }, { D_STEP_INTO_TEXT, D_STEP_INTO, D_STEP_INTO_TEXT, "Resume test execution until the next line of code (on any stack level)." }, { D_STEP_OUT_TEXT, D_STEP_OUT, D_STEP_OUT_TEXT, "Resume test execution until the next line of code in the caller function." }, { D_RUN_TO_CURSOR_TEXT, D_RUN_TO_CURSOR, D_RUN_TO_CURSOR_TEXT " <module> <line>|<function>", "Resume test execution until the specified location." }, { D_HALT_TEXT, D_HALT, D_HALT_TEXT, "Halt test execution." }, { D_CONTINUE_TEXT, D_CONTINUE, D_CONTINUE_TEXT, "Resume halted test execution." }, { D_EXIT_TEXT, D_EXIT, D_EXIT_TEXT " test|all", "Exit the current test or the execution of all tests." }, { NULL, D_ERROR, NULL, NULL } }; #ifdef ADVANCED_DEBUGGER_UI char* TTCN_Debugger_UI::ttcn3_history_filename = NULL; #endif /** local function for extracting the command name and its arguments from an * input line * @param arguments [in] input line * @param len [in] length of the input line * @param start [in] indicates the position to start searching from * @param start [out] the next argument's start position (set to len if no further * arguments were found) * @param end [out] the position of the first character after the next argument */ static void get_next_argument_loc(const char* arguments, size_t len, size_t& start, size_t& end) { while (start < len && isspace(arguments[start])) { ++start; } end = start; while (end < len && !isspace(arguments[end])) { ++end; } } void TTCN_Debugger_UI::process_command(const char* p_line_read) { // locate the command text size_t len = strlen(p_line_read); size_t start = 0; size_t end = 0; get_next_argument_loc(p_line_read, len, start, end); if (start == len) { // empty command return; } #ifdef ADVANCED_DEBUGGER_UI add_history(p_line_read + start); #endif for (const command_t *command = debug_command_list; command->name != NULL; ++command) { if (!strncmp(p_line_read + start, command->name, end - start)) { // count the arguments int argument_count = 0; size_t start_tmp = start; size_t end_tmp = end; while (start_tmp < len) { start_tmp = end_tmp; get_next_argument_loc(p_line_read, len, start_tmp, end_tmp); if (start_tmp < len) { ++argument_count; } } // extract the arguments into a string array char** arguments; if (argument_count > 0) { arguments = new char*[argument_count]; for (int i = 0; i < argument_count; ++i) { start = end; get_next_argument_loc(p_line_read, len, start, end); arguments[i] = mcopystrn(p_line_read + start, end - start); } } else { arguments = NULL; } ttcn3_debugger.execute_command(command->commandID, argument_count, arguments); if (argument_count > 0) { for (int i = 0; i < argument_count; ++i) { Free(arguments[i]); } delete [] arguments; } return; } } if (!strncmp(p_line_read + start, BATCH_TEXT, end - start)) { start = end; get_next_argument_loc(p_line_read, len, start, end); // just to skip to the argument // the entire argument list is treated as one file name (even if it contains spaces) execute_batch_file(p_line_read + start); return; } if (!strncmp(p_line_read + start, HELP_TEXT, end - start)) { start = end; get_next_argument_loc(p_line_read, len, start, end); // just to skip to the argument help(p_line_read + start); return; } puts("Unknown command, try again..."); } void TTCN_Debugger_UI::help(const char* p_argument) { if (*p_argument == 0) { puts("Help is available for the following commands:"); printf(BATCH_TEXT); for (const command_t *command = debug_command_list; command->name != NULL; command++) { printf(" %s", command->name); } putchar('\n'); } else { for (const command_t *command = debug_command_list; command->name != NULL; command++) { if (!strncmp(p_argument, command->name, strlen(command->name))) { printf("%s usage: %s\n%s\n", command->name, command->synopsis, command->description); return; } } if (!strcmp(p_argument, BATCH_TEXT)) { puts(BATCH_TEXT " usage: " BATCH_TEXT "\nRun commands from batch file."); } else { printf("No help for %s.\n", p_argument); } } } void TTCN_Debugger_UI::init() { #ifdef ADVANCED_DEBUGGER_UI // initialize history library using_history(); // calculate history file name const char *home_directory = getenv("HOME"); if (home_directory == NULL) { home_directory = "."; } ttcn3_history_filename = mprintf("%s/%s", home_directory, TTCN3_HISTORY_FILENAME); // read history from file, don't bother if it does not exist read_history(ttcn3_history_filename); // set our own command completion function #ifdef OLD_LIBEDIT rl_completion_entry_function = (Function*)complete_command; #else rl_completion_entry_function = complete_command; #endif #endif } void TTCN_Debugger_UI::clean_up() { #ifdef ADVANCED_DEBUGGER_UI if (write_history(ttcn3_history_filename)) { puts("Could not save debugger command history."); } Free(ttcn3_history_filename); #endif } void TTCN_Debugger_UI::read_loop() { while (ttcn3_debugger.is_halted()) { #ifdef ADVANCED_DEBUGGER_UI // print the prompt and read a line using the readline(), which // automatically handles command completion and command history char* line = readline(PROMPT_TEXT); if (line != NULL) { #else // simplified debugger UI: use a simple fgets printf(PROMPT_TEXT); char line[1024]; char* res = fgets(line, sizeof(line), stdin); if (res != NULL) { #endif process_command(line); #ifdef ADVANCED_DEBUGGER_UI free(line); #endif } else { // EOF was received -> exit all puts("exit all"); char** args = new char*[1]; args[0] = const_cast<char*>("all"); ttcn3_debugger.execute_command(D_EXIT, 1, args); delete [] args; } } } void TTCN_Debugger_UI::execute_batch_file(const char* p_file_name) { FILE* fp = fopen(p_file_name, "r"); if (fp == NULL) { printf("Failed to open file '%s' for reading.\n", p_file_name); return; } else { printf("Executing batch file '%s'.\n", p_file_name); } char line[1024]; while (fgets(line, sizeof(line), fp) != NULL) { size_t len = strlen(line); if (line[len - 1] == '\n') { line[len - 1] = '\0'; --len; } if (len != 0) { printf("%s\n", line); process_command(line); } } if (!feof(fp)) { printf("Error occurred while reading batch file '%s' (error code: %d).\n", p_file_name, ferror(fp)); } fclose(fp); } void TTCN_Debugger_UI::print(const char* p_str) { puts(p_str); } #ifdef ADVANCED_DEBUGGER_UI char* TTCN_Debugger_UI::complete_command(const char* p_prefix, int p_state) { static int command_index; static size_t prefix_len; const char *command_name; if (p_state == 0) { command_index = 0; prefix_len = strlen(p_prefix); } while ((command_name = debug_command_list[command_index].name)) { ++command_index; if (strncmp(p_prefix, command_name, prefix_len) == 0) { // must allocate buffer for returned string (readline() frees it) return strdup(command_name); } } // no match found return NULL; } #endif